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.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 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 .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 .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 .when_cancelled(move |ctx: &mut NpcCtx| ctx.controller.dialogue_end(session))
80 })
81}
82
83impl DialogueSession {
84 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 && dialogue.id == self.id
120 && let DialogueKind::Response { tag, response_id, response, .. } = &dialogue.kind
121 && *tag == q_tag
123 && 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 None => ControlFlow::Continue(talk(self.target)),
135 Some(response_id) => ControlFlow::Break(response_id),
136 }
137 })
138 })
139 .and_then(move |response_id| talk(self.target).repeat().stop_if(timeout(0.5)).map(move |_, _| response_id))
141 .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 talk(self.target)
172 .repeat()
173 .stop_if(timeout(1.5))
175 .then(until(move |ctx, _| {
177 let mut acked = false;
178 ctx.inbox.retain(|input| {
179 if let NpcInput::Dialogue(_, dialogue) = input
180 && dialogue.id == self.id
182 && let DialogueKind::Ack { tag } = &dialogue.kind
183 && *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 .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}