veloren_rtsim/rule/npc_ai/
dialogue.rs

1use super::*;
2
3use std::sync::Arc;
4
5pub fn talk<S: State>(tgt: Actor) -> impl Action<S> + Clone {
6    just(move |ctx, _| ctx.controller.do_talk(tgt)).debug(|| "talking")
7}
8
9pub fn do_dialogue<S: State, A: Action<S>>(
10    tgt: Actor,
11    f: impl Fn(DialogueSession) -> A + Send + Sync + 'static,
12) -> impl Action<S> {
13    now(move |ctx, _| {
14        let session = ctx.controller.dialogue_start(tgt);
15        f(session)
16            // If an end dialogue message is received, stop the dialogue
17            .stop_if(move |ctx: &mut NpcCtx| {
18                let mut stop = false;
19                ctx.inbox.retain(|input| {
20                    if let NpcInput::Dialogue(_, dialogue) = input
21                        && dialogue.id == session.id
22                        && let DialogueKind::End = dialogue.kind
23                    {
24                        stop = true;
25                        false
26                    } else {
27                        true
28                    }
29                });
30                stop
31            })
32            .then(just(move |ctx, _| {
33                ctx.controller.do_idle();
34                ctx.controller.dialogue_end(session);
35            }))
36    })
37}
38
39impl DialogueSession {
40    /// Ask a question as part of a dialogue.
41    ///
42    /// Responses will be verified against the original response options and
43    /// dialogue participant to prevent spoofing.
44    pub fn ask_question<S: State, T: Into<Option<(u16, U)>>, U: Into<Response>>(
45        self,
46        question: Content,
47        responses: impl IntoIterator<Item = T> + Clone + Send + Sync + 'static,
48    ) -> impl Action<S, Option<u16>> {
49        let responses = responses
50            .clone()
51            .into_iter()
52            .flat_map(Into::into)
53            .map(|(id, r)| (id, r.into()))
54            .collect::<Arc<[_]>>();
55
56        now(move |ctx, _| {
57            let q_tag = ctx.controller.dialogue_question(
58                self,
59                question.clone(),
60                responses.iter().cloned(),
61            );
62            let responses = responses.clone();
63            until(move |ctx, _| {
64                let mut id = None;
65                ctx.inbox.retain(|input| {
66                    if let NpcInput::Dialogue(_, dialogue) = input
67                        // Check that the response is for the same dialogue
68                        && dialogue.id == self.id
69                        && let DialogueKind::Response { tag, response_id, response, .. } = &dialogue.kind
70                        // Check that the response relates the the question just asked
71                        && *tag == q_tag
72                        // Check that the response matches one of our requested responses
73                        && responses.iter().any(|(r_id, r)| r_id == response_id && r == response)
74                    {
75                        id = Some(*response_id);
76                        false
77                    } else {
78                        true
79                    }
80                });
81                match id {
82                    // TODO: Should be 'engage target in conversation'
83                    None => ControlFlow::Continue(talk(self.target)),
84                    Some(response_id) => ControlFlow::Break(response_id),
85                }
86            })
87        })
88            // Add some thinking time after hearing a response
89            .and_then(move |response_id| talk(self.target).repeat().stop_if(timeout(0.5)).map(move |_, _| response_id))
90            // If all else fails, add a timeout to dialogues
91            // TODO: Only timeout if no messages have been received recently
92            .stop_if(timeout(60.0))
93    }
94
95    pub fn say_statement<S: State>(self, stmt: Content) -> impl Action<S> {
96        now(move |ctx, _| {
97            ctx.controller.dialogue_statement(self, stmt.clone());
98            idle()
99        })
100        .then(talk(self.target)
101            .repeat()
102            // Wait for a while before making the statement to allow other dialogue to be read
103            .stop_if(timeout(2.5)))
104        .map(|_, _| ())
105    }
106}