veloren_rtsim/rule/npc_ai/
util.rs1use 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: ActorId) -> Option<Vec3<f32>> {
9 ctx.data.actors.get(actor).map(|actor| actor.wpos)
10}
11
12pub fn actor_exists(ctx: &NpcCtx, actor: ActorId) -> bool { ctx.data.actors.contains_key(actor) }
13
14pub fn actor_name(ctx: &NpcCtx, actor: ActorId) -> Option<String> {
15 ctx.data
16 .actors
17 .get(actor)
18 .and_then(|actor| actor.get_name())
19}
20
21pub fn talk<S: State>(tgt: ActorId) -> impl Action<S> + Clone {
22 just(move |ctx, _| ctx.controller.do_talk(tgt)).debug(|| "talking")
23}
24
25pub fn do_dialogue<S: State, T: Default + Clone + Send + Sync + 'static, A: Action<S, T>>(
26 tgt: ActorId,
27 f: impl Fn(DialogueSession) -> A + Clone + Send + Sync + 'static,
28) -> impl Action<S, T> {
29 now(move |ctx, _| {
30 let session = ctx.controller.dialogue_start(tgt);
31 f(session)
32 .stop_if(move |ctx: &mut NpcCtx| {
34 let mut stop = false;
35 ctx.inbox.retain(|input| {
36 if let NpcInput::Dialogue(_, dialogue) = input
37 && dialogue.id == session.id
38 && let DialogueKind::End = dialogue.kind
39 {
40 stop = true;
41 false
42 } else {
43 true
44 }
45 });
46 stop
47 })
48 .stop_if(timeout(600.0))
50 .and_then(move |x: Option<Option<T>>| just(move |ctx, _| {
51 ctx.controller.do_idle();
52 ctx.controller.dialogue_end(session);
53 x.clone().flatten().unwrap_or_default()
54 }))
55 .when_cancelled(move |ctx: &mut NpcCtx| ctx.controller.dialogue_end(session))
57 })
58 .with_important_priority()
59}
60
61impl DialogueSession {
62 pub fn ask_question<
67 S: State,
68 R: Into<Response>,
69 T: Default + Send + Sync + 'static,
70 A: Action<S, T>,
71 >(
72 self,
73 question: Content,
74 responses: impl IntoIterator<Item = (R, A)> + Send + Sync + 'static,
75 ) -> impl Action<S, T> {
76 let (responses, actions): (Vec<_>, Vec<_>) = responses
77 .into_iter()
78 .enumerate()
79 .map(|(idx, (r, a))| ((idx as u16, r.into()), a))
80 .unzip();
81
82 let actions_once = Arc::new(take_once::TakeOnce::new());
83 let _ = actions_once.store(actions);
84
85 now(move |ctx, _| {
86 let q_tag = ctx.controller.dialogue_question(
87 self,
88 question.clone(),
89 responses.iter().cloned(),
90 );
91 let responses = responses.clone();
92 until(move |ctx, _| {
93 let mut id = None;
94 ctx.inbox.retain(|input| {
95 if let NpcInput::Dialogue(_, dialogue) = input
96 && dialogue.id == self.id
98 && let DialogueKind::Response { tag, response_id, response, .. } = &dialogue.kind
99 && *tag == q_tag
101 && responses.iter().any(|(r_id, r)| r_id == response_id && r == response)
103 {
104 id = Some(*response_id);
105 false
106 } else {
107 true
108 }
109 });
110 match id {
111 None => ControlFlow::Continue(talk(self.target)),
113 Some(response_id) => ControlFlow::Break(response_id),
114 }
115 })
116 })
117 .and_then(move |response_id| talk(self.target).repeat().stop_if(timeout(0.5)).map(move |_, _| response_id))
119 .stop_if(timeout(60.0))
122 .and_then(move |resp: Option<u16>| {
123 if let Some(action) = resp.and_then(|resp| actions_once.take().unwrap().into_iter().nth(resp as usize)) {
124 action.map(|x, _| x).boxed()
125 } else {
126 idle().map(|_, _| Default::default()).boxed()
127 }
128 })
129 }
130
131 pub fn ask_yes_no_question<S: State>(self, question: Content) -> impl Action<S, bool> {
132 let answer = |is_yes| just(move |_, _| is_yes);
133 self.ask_question(question, [
134 (Content::localized("common-yes"), answer(true)),
135 (Content::localized("common-no"), answer(false)),
136 ])
137 }
138
139 pub fn say_statement_with_gift<S: State>(
140 self,
141 stmt: Content,
142 item: Option<(Arc<ItemDef>, u32)>,
143 ) -> impl Action<S> {
144 now(move |ctx, _| {
145 let s_tag = ctx
146 .controller
147 .dialogue_statement(self, stmt.clone(), item.clone());
148 talk(self.target)
150 .repeat()
151 .stop_if(timeout(1.5))
153 .then(until(move |ctx, _| {
155 let mut acked = false;
156 ctx.inbox.retain(|input| {
157 if let NpcInput::Dialogue(_, dialogue) = input
158 && dialogue.id == self.id
160 && let DialogueKind::Ack { tag } = &dialogue.kind
161 && *tag == s_tag
163 {
164 acked = true;
165 false
166 } else {
167 true
168 }
169 });
170 if acked {
171 ControlFlow::Break(())
172 } else {
173 ControlFlow::Continue(talk(self.target))
174 }
175 })
176 .stop_if(timeout(30.0)))
178 })
179 .map(|_, _| ())
180 }
181
182 pub fn say_statement<S: State>(self, stmt: Content) -> impl Action<S> {
183 self.say_statement_with_gift(stmt, None)
184 }
185
186 pub fn give_marker<S: State>(self, marker: Marker) -> impl Action<S> {
187 just(move |ctx, _| ctx.controller.dialogue_marker(self, marker.clone()))
188 }
189}