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: 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 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 .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 .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 .when_cancelled(move |ctx: &mut NpcCtx| ctx.controller.dialogue_end(session))
75 })
76 .with_important_priority()
77}
78
79impl DialogueSession {
80 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 && dialogue.id == self.id
116 && let DialogueKind::Response { tag, response_id, response, .. } = &dialogue.kind
117 && *tag == q_tag
119 && 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 None => ControlFlow::Continue(talk(self.target)),
131 Some(response_id) => ControlFlow::Break(response_id),
132 }
133 })
134 })
135 .and_then(move |response_id| talk(self.target).repeat().stop_if(timeout(0.5)).map(move |_, _| response_id))
137 .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 talk(self.target)
168 .repeat()
169 .stop_if(timeout(1.5))
171 .then(until(move |ctx, _| {
173 let mut acked = false;
174 ctx.inbox.retain(|input| {
175 if let NpcInput::Dialogue(_, dialogue) = input
176 && dialogue.id == self.id
178 && let DialogueKind::Ack { tag } = &dialogue.kind
179 && *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 .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}