veloren_rtsim/data/quest.rs
1use common::{
2 resources::Time,
3 rtsim::{Actor, ItemResource, QuestId, SiteId},
4};
5use hashbrown::{HashMap, HashSet};
6use itertools::Either;
7use serde::{Deserialize, Serialize};
8use std::{
9 num::NonZeroU32,
10 sync::atomic::{AtomicU8, AtomicU64, Ordering},
11};
12use vek::Vec2;
13
14/// The easiest way to think about quests is as a virtual Jira board (or,
15/// perhaps, a community jobs noticeboard).
16///
17/// This type represents the board. In effect, it is a big database of active
18/// and resolved quests. Quests are not, by themselves, 'active' participants in
19/// the world. They are informal contracts, and it is up to the NPCs and players
20/// that interact with them to drive them forward.
21///
22/// Quests that are resolved or that have been active for some time without
23/// activity may be garbage-collected, although the exact mechanism for this has
24/// not yet been defined.
25#[derive(Default, Serialize, Deserialize)]
26pub struct Quests {
27 /// Because quests can be created in a multi-threaded context, we use an
28 /// atomic counter to generate IDs for them. Quest insertion happens at
29 /// the end of each tick. This is guarded by a utility function, so
30 /// unregistered quests *shouldn't* be visible to the rest of the code.
31 id_counter: AtomicU64,
32 quests: HashMap<QuestId, Quest>,
33 #[serde(skip)]
34 related_quests: HashMap<Actor, HashSet<QuestId>>,
35}
36
37impl Clone for Quests {
38 fn clone(&self) -> Self {
39 Self {
40 // This isn't strictly kosher in a multi-threaded context, but we assume that clones
41 // only happen on the main thread when we don't care about synchronisation
42 id_counter: AtomicU64::new(self.id_counter.load(Ordering::SeqCst)),
43 quests: self.quests.clone(),
44 // Bit of a hack: we assume that cloning only happens for the sake of persistence, and
45 // we don't persisted the related quests cache
46 related_quests: HashMap::default(),
47 }
48 }
49}
50
51impl Quests {
52 /// Register a new quest ID. It can be defined later with
53 /// [`Quests::create`].
54 ///
55 /// Critically, this function works in a shared + concurrent context, which
56 /// allows us to run it in parallel within the NPC AI code.
57 pub fn register(&self) -> QuestId { QuestId(self.id_counter.fetch_add(1, Ordering::Relaxed)) }
58
59 /// Create a quest with the given ID.
60 ///
61 /// This ID should be generated by [`Quests::register`] and used only once
62 /// to create a quest.
63 pub fn create(&mut self, id: QuestId, quest: Quest) {
64 // Update quest lookup table
65 quest.for_related_actors(|actor| {
66 self.related_quests.entry(actor).or_default().insert(id);
67 });
68 self.quests.entry(id).or_insert(quest);
69 }
70
71 pub fn get(&self, id: QuestId) -> Option<&Quest> { self.quests.get(&id) }
72
73 pub fn related_to(&self, actor: impl Into<Actor>) -> impl Iterator<Item = QuestId> + '_ {
74 match self.related_quests.get(&actor.into()) {
75 Some(quests) => Either::Left(
76 quests.iter()
77 // Don't consider resolved quests to be related
78 .filter(|id| self.get(**id).is_some_and(|q| q.resolution().is_none()))
79 .copied(),
80 ),
81 None => Either::Right(core::iter::empty()),
82 }
83 }
84
85 /// Find all of the actors that are related to another actor via a quest
86 pub fn related_actors(
87 &self,
88 actor: impl Into<Actor>,
89 ) -> impl ExactSizeIterator<Item = Actor> + '_ {
90 let actor = actor.into();
91 let mut related = HashSet::new();
92 for quest_id in self.related_to(actor) {
93 if let Some(quest) = self.quests.get(&quest_id)
94 // resolved quests aren't relevant
95 && quest.resolution().is_none()
96 {
97 quest.for_related_actors(|a| {
98 if a != actor {
99 related.insert(a);
100 }
101 });
102 }
103 }
104 related.into_iter()
105 }
106
107 pub(super) fn prepare(&mut self) {
108 // Populate quest lookup table
109 for (quest_id, quest) in &self.quests {
110 quest.for_related_actors(|actor| {
111 self.related_quests
112 .entry(actor)
113 .or_default()
114 .insert(*quest_id);
115 });
116 }
117 }
118}
119
120#[derive(Clone, Serialize, Deserialize)]
121pub struct Quest {
122 /// The actor responsible for arbitrating over the quest.
123 ///
124 /// Quests can only be resolved by their designated arbiter. The arbiter is
125 /// defined when the quest is created, and the arbiter receives the
126 /// quest deposit (see [`Quests::resolve`]).
127 ///
128 /// In the future, this can be extended to include factions, multiple
129 /// actors, or even some system in the world (such as a noticeboard
130 /// system, so that players can assign one-another quests).
131 pub arbiter: Actor,
132
133 /// A machine-intelligible description of the quest and its completion
134 /// conditions.
135 ///
136 /// We try to avoid being prescriptive about what form quests can take
137 pub kind: QuestKind,
138
139 /// The time before which the quest should be completed to be considered
140 /// successful.
141 pub timeout: Option<Time>,
142
143 outcome: QuestOutcome,
144
145 /// The only aspect of the quest that mutates over time. Resolving quests is
146 /// monotonic: once resolved, they cannot be unresolved (to avoid the
147 /// deposit being paid back twice, for example).
148 res: QuestRes,
149}
150
151impl Quest {
152 /// Create a new escort quest that requires an escorter to travel with an
153 /// escortee to a destination.
154 ///
155 /// The escortee is considered to be the quest arbiter.
156 pub fn escort(escortee: Actor, escorter: Actor, to: SiteId) -> Self {
157 Self {
158 arbiter: escortee,
159 kind: QuestKind::Escort {
160 escortee,
161 escorter,
162 to,
163 },
164 timeout: None,
165 outcome: QuestOutcome::default(),
166 res: QuestRes(AtomicU8::new(0)),
167 }
168 }
169
170 /// Create a new slay quest that requires the slayer to kill a target.
171 pub fn slay(arbiter: Actor, target: Actor, slayer: Actor) -> Self {
172 Self {
173 arbiter,
174 kind: QuestKind::Slay { target, slayer },
175 timeout: None,
176 outcome: QuestOutcome::default(),
177 res: QuestRes(AtomicU8::new(0)),
178 }
179 }
180
181 /// Create a new courier quest that requires an individual to deliver an
182 /// item to another individual. The arbiter is the individual that is
183 /// expecting to receive the package, and the source is the individual
184 /// that created the quest opportunity. The messenger is the individual
185 /// making the journey with the package in hand.
186 pub fn courier(arbiter: Actor, instance: CourierQuestInstance) -> Self {
187 Self {
188 arbiter,
189 kind: QuestKind::Courier { instance },
190 timeout: None,
191 outcome: QuestOutcome::default(),
192 res: QuestRes(AtomicU8::new(0)),
193 }
194 }
195
196 /// Deposit an item (usually for payment to whoever completes the quest) in
197 /// the quest for safekeeping.
198 ///
199 /// Deposits are paid out to the arbiter when a quest is resolved. The
200 /// arbiter usually passes the deposit on to the character that
201 /// completed the quest, but this is not the concern of the quest system.
202 pub fn with_deposit(mut self, item: ItemResource, amount: f32) -> Self {
203 self.outcome.deposit = Some((item, amount));
204 self
205 }
206
207 /// Add a timeout to the quest, beyond which the quest is considered to be
208 /// failed.
209 pub fn with_timeout(mut self, time: Time) -> Self {
210 self.timeout = Some(time);
211 self
212 }
213
214 /// Resolve a quest.
215 ///
216 /// Quest resolution is monotonic and so can only be done once: all future
217 /// attempts will fail. If this is the first attempt at resolving (i.e:
218 /// the function returns `Some(...)`), the deposit will be returned.
219 ///
220 /// Note that the result type of this function only indicates whether
221 /// updating the resolution status of the quest was successful:
222 /// `Some(...)` will still be returned if the quest was resolved by failure
223 /// too.
224 ///
225 /// The `requester` parameter is a sanity test: you should pass the actor
226 /// that originated the request to resolve, and the function will ensure
227 /// that this matches the quest's designated arbiter.
228 pub fn resolve(&self, requester: impl Into<Actor>, res: bool) -> Option<QuestOutcome> {
229 if self.arbiter != requester.into() {
230 // Actor that requested quest resolution did not match the designated arbiter,
231 // resolution not permitted!
232 None
233 } else {
234 self.res
235 .0
236 .compare_exchange(
237 0,
238 if res { 2 } else { 1 },
239 Ordering::Relaxed,
240 Ordering::Relaxed,
241 )
242 .ok()
243 .map(|_| self.outcome.clone())
244 }
245 }
246
247 pub fn resolution(&self) -> Option<bool> { self.res.get() }
248
249 pub fn get_related_actors(&self) -> HashSet<Actor> {
250 let mut related = HashSet::default();
251 self.for_related_actors(|actor| {
252 related.insert(actor);
253 });
254 related
255 }
256
257 fn for_related_actors(&self, mut f: impl FnMut(Actor)) {
258 f(self.arbiter);
259 match &self.kind {
260 QuestKind::Escort {
261 escortee,
262 escorter,
263 to: _,
264 } => {
265 f(*escortee);
266 f(*escorter);
267 },
268 QuestKind::Slay { target, slayer } => {
269 f(*target);
270 f(*slayer);
271 },
272 QuestKind::Courier { instance } => {
273 f(instance.source_actor);
274 f(instance.messenger);
275 },
276 }
277 }
278}
279
280// 0 = unresolved, 1 = fail, 2.. = success
281#[derive(Default, Serialize, Deserialize)]
282struct QuestRes(AtomicU8);
283
284impl QuestRes {
285 fn get(&self) -> Option<bool> {
286 match self.0.load(Ordering::Relaxed) {
287 0 => None,
288 1 => Some(false),
289 _ => Some(true),
290 }
291 }
292}
293
294impl Clone for QuestRes {
295 fn clone(&self) -> Self {
296 // This isn't strictly kosher in a multi-threaded context, but we assume that
297 // clones only happen on the main thread when we don't care about
298 // synchronisation
299 Self(AtomicU8::new(self.0.load(Ordering::Relaxed)))
300 }
301}
302
303/// Produced on completion of a quest.
304///
305/// Can represent both success and failure outcomes, such as:
306///
307/// - The release of a payment deposit
308/// - A change in reputation/goodwill
309/// - The role of an NPC or playing changing (promotion!)
310/// - etc.
311#[derive(Clone, Default, Serialize, Deserialize)]
312pub struct QuestOutcome {
313 /// An item held in deposit.
314 ///
315 /// When the quest is resolved, it is returned to the arbiter (usually to
316 /// then be passed to the quest completer, although not always).
317 ///
318 /// Deposits exist to avoid NPCs (or players) constantly needing to track
319 /// 'earmarked' items in their inventories that correspond to payments.
320 pub deposit: Option<(ItemResource, f32)>,
321}
322
323#[derive(Clone, Serialize, Deserialize)]
324pub enum QuestKind {
325 Escort {
326 escortee: Actor,
327 escorter: Actor,
328 to: SiteId,
329 },
330 Slay {
331 target: Actor,
332 slayer: Actor,
333 },
334 Courier {
335 instance: CourierQuestInstance,
336 },
337}
338
339/// [`COURIER_QUEST_VARIANTS`] is worth looking at, as it contains all possible
340/// quest permutations and can be extended.
341#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord, Hash)]
342pub enum CourierQuest {
343 /// Find an NPC and speak to them. No items are required.
344 Message,
345 /// Find an NPC and give them something.
346 Deliver {
347 payload: Payload,
348 recipient: Recipient,
349 },
350}
351
352/// For [`CourierQuest`].
353#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord, Hash)]
354pub enum Recipient {
355 Other,
356 Giver,
357}
358
359/// For [`CourierQuest`].
360#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord, Hash)]
361pub enum Payload {
362 LegoomLeaf,
363 GnarlingCarving,
364}
365
366impl CourierQuest {
367 pub fn payload(self) -> Option<Payload> {
368 match self {
369 CourierQuest::Message => None,
370 CourierQuest::Deliver { payload, .. } => Some(payload),
371 }
372 }
373
374 pub fn delivers_to_giver(self) -> bool {
375 matches!(self, CourierQuest::Deliver {
376 recipient: Recipient::Giver,
377 ..
378 })
379 }
380}
381
382/// Stack-allocated array of all of the possible courier quests that can be
383/// rolled.
384pub const COURIER_QUEST_VARIANTS: [CourierQuest; 5] = [
385 // Add more here later!
386 CourierQuest::Message,
387 CourierQuest::Deliver {
388 payload: Payload::GnarlingCarving,
389 recipient: Recipient::Other,
390 },
391 CourierQuest::Deliver {
392 payload: Payload::GnarlingCarving,
393 recipient: Recipient::Giver,
394 },
395 CourierQuest::Deliver {
396 payload: Payload::LegoomLeaf,
397 recipient: Recipient::Other,
398 },
399 CourierQuest::Deliver {
400 payload: Payload::LegoomLeaf,
401 recipient: Recipient::Giver,
402 },
403];
404
405/// Contains a completable courier quest.
406#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
407pub struct CourierQuestInstance {
408 pub kind: CourierQuest,
409 pub spot: Option<Vec2<i32>>,
410 /// The source site may or may not be useful in all situations, but we do
411 /// want to keep it available in case we need to have dialogue that says
412 /// something like "Wow, you came all the way from <site> to send me this?"
413 pub source_site: Option<SiteId>,
414 pub source_actor: Actor,
415 /// This is `source_actor` for fetch quests.
416 pub target_actor: Actor,
417 /// Target actor's site, if available.
418 pub target_site: Option<SiteId>,
419 /// Usually this is the player.
420 pub messenger: Actor,
421 /// The distance to be traversed, calculated once at quest start.
422 pub distance: NonZeroU32,
423}