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