veloren_rtsim/rule/npc_ai/
util.rs

1use super::*;
2
3pub fn site_name(ctx: &NpcCtx, site_id: impl Into<Option<SiteId>>) -> Option<String> {
4    let world_site = ctx.data.sites.get(site_id.into()?)?.world_site?;
5    Some(ctx.index.sites.get(world_site).name()?.to_string())
6}
7
8pub fn locate_actor(ctx: &NpcCtx, actor: Actor) -> Option<Vec3<f32>> {
9    match actor {
10        Actor::Npc(npc_id) => ctx.data.npcs.get(npc_id).map(|npc| npc.wpos),
11        Actor::Character(character_id) => ctx
12            .system_data
13            .id_maps
14            .character_entity(character_id)
15            .and_then(|c| ctx.system_data.positions.get(c))
16            .map(|p| p.0),
17    }
18}
19
20pub fn actor_exists(ctx: &NpcCtx, actor: Actor) -> bool {
21    match actor {
22        Actor::Npc(npc_id) => ctx.data.npcs.contains_key(npc_id),
23        Actor::Character(character_id) => ctx
24            .system_data
25            .id_maps
26            .character_entity(character_id)
27            .is_some(),
28    }
29}
30
31pub fn actor_name(ctx: &NpcCtx, actor: Actor) -> Option<String> {
32    match actor {
33        Actor::Npc(npc_id) => ctx.data.npcs.get(npc_id).and_then(|npc| npc.get_name()),
34        // TODO
35        Actor::Character(_) => None,
36    }
37}
38
39pub fn talk<S: State>(tgt: Actor) -> impl Action<S> + Clone {
40    just(move |ctx, _| ctx.controller.do_talk(tgt)).debug(|| "talking")
41}
42
43pub fn do_dialogue<S: State, T: Default + Clone + Send + Sync + 'static, A: Action<S, T>>(
44    tgt: Actor,
45    f: impl Fn(DialogueSession) -> A + Clone + Send + Sync + 'static,
46) -> impl Action<S, T> {
47    now(move |ctx, _| {
48        let session = ctx.controller.dialogue_start(tgt);
49        f(session)
50            // If an end dialogue message is received, stop the dialogue
51            .stop_if(move |ctx: &mut NpcCtx| {
52                let mut stop = false;
53                ctx.inbox.retain(|input| {
54                    if let NpcInput::Dialogue(_, dialogue) = input
55                        && dialogue.id == session.id
56                        && let DialogueKind::End = dialogue.kind
57                    {
58                        stop = true;
59                        false
60                    } else {
61                        true
62                    }
63                });
64                stop
65            })
66            // As a backstop, timeout after 10 minutes of dialogue to avoid accidentally soft-locking NPCs
67            .stop_if(timeout(600.0))
68            .and_then(move |x: Option<Option<T>>| just(move |ctx, _| {
69                ctx.controller.do_idle();
70                ctx.controller.dialogue_end(session);
71                x.clone().flatten().unwrap_or_default()
72            }))
73            // Handle early cancellation elegantly
74            .when_cancelled(move |ctx: &mut NpcCtx| ctx.controller.dialogue_end(session))
75    })
76    .with_important_priority()
77}
78
79impl DialogueSession {
80    /// Ask a question as part of a dialogue.
81    ///
82    /// Responses will be verified against the original response options and
83    /// dialogue participant to prevent spoofing.
84    pub fn ask_question<
85        S: State,
86        R: Into<Response>,
87        T: Default + Send + Sync + 'static,
88        A: Action<S, T>,
89    >(
90        self,
91        question: Content,
92        responses: impl IntoIterator<Item = (R, A)> + Send + Sync + 'static,
93    ) -> impl Action<S, T> {
94        let (responses, actions): (Vec<_>, Vec<_>) = responses
95            .into_iter()
96            .enumerate()
97            .map(|(idx, (r, a))| ((idx as u16, r.into()), a))
98            .unzip();
99
100        let actions_once = Arc::new(take_once::TakeOnce::new());
101        let _ = actions_once.store(actions);
102
103        now(move |ctx, _| {
104            let q_tag = ctx.controller.dialogue_question(
105                self,
106                question.clone(),
107                responses.iter().cloned(),
108            );
109            let responses = responses.clone();
110            until(move |ctx, _| {
111                let mut id = None;
112                ctx.inbox.retain(|input| {
113                    if let NpcInput::Dialogue(_, dialogue) = input
114                        // Check that the response is for the same dialogue
115                        && dialogue.id == self.id
116                        && let DialogueKind::Response { tag, response_id, response, .. } = &dialogue.kind
117                        // Check that the response relates to the question just asked
118                        && *tag == q_tag
119                        // Check that the response matches one of our requested responses
120                        && responses.iter().any(|(r_id, r)| r_id == response_id && r == response)
121                    {
122                        id = Some(*response_id);
123                        false
124                    } else {
125                        true
126                    }
127                });
128                match id {
129                    // TODO: Should be 'engage target in conversation'
130                    None => ControlFlow::Continue(talk(self.target)),
131                    Some(response_id) => ControlFlow::Break(response_id),
132                }
133            })
134        })
135            // Add some thinking time after hearing a response
136            .and_then(move |response_id| talk(self.target).repeat().stop_if(timeout(0.5)).map(move |_, _| response_id))
137            // If all else fails, add a timeout to dialogues
138            // TODO: Only timeout if no messages have been received recently
139            .stop_if(timeout(60.0))
140            .and_then(move |resp: Option<u16>| {
141                if let Some(action) = resp.and_then(|resp| actions_once.take().unwrap().into_iter().nth(resp as usize)) {
142                    action.map(|x, _| x).boxed()
143                } else {
144                    idle().map(|_, _| Default::default()).boxed()
145                }
146            })
147    }
148
149    pub fn ask_yes_no_question<S: State>(self, question: Content) -> impl Action<S, bool> {
150        let answer = |is_yes| just(move |_, _| is_yes);
151        self.ask_question(question, [
152            (Content::localized("common-yes"), answer(true)),
153            (Content::localized("common-no"), answer(false)),
154        ])
155    }
156
157    pub fn say_statement_with_gift<S: State>(
158        self,
159        stmt: Content,
160        item: Option<(Arc<ItemDef>, u32)>,
161    ) -> impl Action<S> {
162        now(move |ctx, _| {
163            let s_tag = ctx
164                .controller
165                .dialogue_statement(self, stmt.clone(), item.clone());
166            // Wait for the ack
167            talk(self.target)
168            .repeat()
169            // Wait for a while before accepting the ACK to avoid dialogue progressing too fast
170            .stop_if(timeout(1.5))
171            // Wait for the ACK
172            .then(until(move |ctx, _| {
173                    let mut acked = false;
174                    ctx.inbox.retain(|input| {
175                        if let NpcInput::Dialogue(_, dialogue) = input
176                            // Check that the response is for the same dialogue
177                            && dialogue.id == self.id
178                            && let DialogueKind::Ack { tag } = &dialogue.kind
179                            // Check that the ACL relates to the statement just given
180                            && *tag == s_tag
181                        {
182                            acked = true;
183                            false
184                        } else {
185                            true
186                        }
187                    });
188                    if acked {
189                       ControlFlow::Break(())
190                    } else {
191                        ControlFlow::Continue(talk(self.target))
192                    }
193                })
194                    // As a final safeguard, timeout after a while
195                    .stop_if(timeout(30.0)))
196        })
197        .map(|_, _| ())
198    }
199
200    pub fn say_statement<S: State>(self, stmt: Content) -> impl Action<S> {
201        self.say_statement_with_gift(stmt, None)
202    }
203
204    pub fn give_marker<S: State>(self, marker: Marker) -> impl Action<S> {
205        just(move |ctx, _| ctx.controller.dialogue_marker(self, marker.clone()))
206    }
207}