Skip to main content

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}