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::sync::atomic::{AtomicU8, AtomicU64, Ordering};
9
10/// The easiest way to think about quests is as a virtual Jira board (or,
11/// perhaps, a community jobs noticeboard).
12///
13/// This type represents the board. In effect, it is a big database of active
14/// and resolved quests. Quests are not, by themselves, 'active' participants in
15/// the world. They are informal contracts, and it is up to the NPCs and players
16/// that interact with them to drive them forward.
17///
18/// Quests that are resolved or that have been active for some time without
19/// activity may be garbage-collected, although the exact mechanism for this has
20/// not yet been defined.
21#[derive(Default, Serialize, Deserialize)]
22pub struct Quests {
23    /// Because quests can be created in a multi-threaded context, we use an
24    /// atomic counter to generate IDs for them. Quest insertion happens at
25    /// the end of each tick. This is guarded by a utility function, so
26    /// unregistered quests *shouldn't* be visible to the rest of the code.
27    id_counter: AtomicU64,
28    quests: HashMap<QuestId, Quest>,
29    #[serde(skip)]
30    related_quests: HashMap<Actor, HashSet<QuestId>>,
31}
32
33impl Clone for Quests {
34    fn clone(&self) -> Self {
35        Self {
36            // This isn't strictly kosher in a multi-threaded context, but we assume that clones
37            // only happen on the main thread when we don't care about synchronisation
38            id_counter: AtomicU64::new(self.id_counter.load(Ordering::SeqCst)),
39            quests: self.quests.clone(),
40            // Bit of a hack: we assume that cloning only happens for the sake of persistence, and
41            // we don't persisted the related quests cache
42            related_quests: HashMap::default(),
43        }
44    }
45}
46
47impl Quests {
48    /// Register a new quest ID. It can be defined later with
49    /// [`Quests::create`].
50    ///
51    /// Critically, this function works in a shared + concurrent context, which
52    /// allows us to run it in parallel within the NPC AI code.
53    pub fn register(&self) -> QuestId { QuestId(self.id_counter.fetch_add(1, Ordering::Relaxed)) }
54
55    /// Create a quest with the given ID.
56    ///
57    /// This ID should be generated by [`Quests::register`] and used only once
58    /// to create a quest.
59    pub fn create(&mut self, id: QuestId, quest: Quest) {
60        // Update quest lookup table
61        quest.for_related_actors(|actor| {
62            self.related_quests.entry(actor).or_default().insert(id);
63        });
64        self.quests.entry(id).or_insert(quest);
65    }
66
67    pub fn get(&self, id: QuestId) -> Option<&Quest> { self.quests.get(&id) }
68
69    pub fn related_to(&self, actor: impl Into<Actor>) -> impl Iterator<Item = QuestId> + '_ {
70        match self.related_quests.get(&actor.into()) {
71            Some(quests) => Either::Left(
72                quests.iter()
73                // Don't consider resolved quests to be related
74                .filter(|id| self.get(**id).is_some_and(|q| q.resolution().is_none()))
75                .copied(),
76            ),
77            None => Either::Right(core::iter::empty()),
78        }
79    }
80
81    /// Find all of the actors that are related to another actor via a quest
82    pub fn related_actors(
83        &self,
84        actor: impl Into<Actor>,
85    ) -> impl ExactSizeIterator<Item = Actor> + '_ {
86        let actor = actor.into();
87        let mut related = HashSet::new();
88        for quest_id in self.related_to(actor) {
89            if let Some(quest) = self.quests.get(&quest_id)
90                // resolved quests aren't relevant
91                && quest.resolution().is_none()
92            {
93                quest.for_related_actors(|a| {
94                    if a != actor {
95                        related.insert(a);
96                    }
97                });
98            }
99        }
100        related.into_iter()
101    }
102
103    pub(super) fn prepare(&mut self) {
104        // Populate quest lookup table
105        for (quest_id, quest) in &self.quests {
106            quest.for_related_actors(|actor| {
107                self.related_quests
108                    .entry(actor)
109                    .or_default()
110                    .insert(*quest_id);
111            });
112        }
113    }
114}
115
116#[derive(Clone, Serialize, Deserialize)]
117pub struct Quest {
118    /// The actor responsible for arbitrating over the quest.
119    ///
120    /// Quests can only be resolved by their designated arbiter. The arbiter is
121    /// defined when the quest is created, and the arbiter receives the
122    /// quest deposit (see [`Quests::resolve`]).
123    ///
124    /// In the future, this can be extended to include factions, multiple
125    /// actors, or even some system in the world (such as a noticeboard
126    /// system, so that players can assign one-another quests).
127    pub arbiter: Actor,
128
129    /// A machine-intelligible description of the quest and its completion
130    /// conditions.
131    ///
132    /// We try to avoid being prescriptive about what form quests can take
133    pub kind: QuestKind,
134
135    /// The time before which the quest should be completed to be considered
136    /// successful.
137    pub timeout: Option<Time>,
138
139    outcome: QuestOutcome,
140
141    /// The only aspect of the quest that mutates over time. Resolving quests is
142    /// monotonic: once resolved, they cannot be unresolved (to avoid the
143    /// deposit being paid back twice, for example).
144    res: QuestRes,
145}
146
147impl Quest {
148    /// Create a new escort quest that requires an escoter to travel with an
149    /// escortee to a destination.
150    ///
151    /// The escortee is considered to be the quest arbiter.
152    pub fn escort(escortee: Actor, escorter: Actor, to: SiteId) -> Self {
153        Self {
154            arbiter: escortee,
155            kind: QuestKind::Escort {
156                escortee,
157                escorter,
158                to,
159            },
160            timeout: None,
161            outcome: QuestOutcome::default(),
162            res: QuestRes(AtomicU8::new(0)),
163        }
164    }
165
166    /// Create a new slay quest that requires the slayer to kill a target.
167    pub fn slay(arbiter: Actor, target: Actor, slayer: Actor) -> Self {
168        Self {
169            arbiter,
170            kind: QuestKind::Slay { target, slayer },
171            timeout: None,
172            outcome: QuestOutcome::default(),
173            res: QuestRes(AtomicU8::new(0)),
174        }
175    }
176
177    /// Deposit an item (usually for payment to whoever completes the quest) in
178    /// the quest for safekeeping.
179    ///
180    /// Deposits are paid out to the arbiter when a quest is resolved. The
181    /// arbiter usually passes the deposit on to the character that
182    /// completed the quest, but this is not the concern of the quest system.
183    pub fn with_deposit(mut self, item: ItemResource, amount: f32) -> Self {
184        self.outcome.deposit = Some((item, amount));
185        self
186    }
187
188    /// Add a timeout to the quest, beyond which the quest is considered to be
189    /// failed.
190    pub fn with_timeout(mut self, time: Time) -> Self {
191        self.timeout = Some(time);
192        self
193    }
194
195    /// Resolve a quest.
196    ///
197    /// Quest resolution is monotonic and so can only be done once: all future
198    /// attempts will fail. If this is the first attempt at resolving (i.e:
199    /// the function returns `Some(...)`), the deposit will be returned.
200    ///
201    /// Note that the result type of this function only indicates whether
202    /// updating the resolution status of the quest was successful:
203    /// `Some(...)` will still be returned if the quest was resolved by failure
204    /// too.
205    ///
206    /// The `requester` parameter is a sanity test: you should pass the actor
207    /// that originated the request to resolve, and the function will ensure
208    /// that this matches the quest's designated arbiter.
209    pub fn resolve(&self, requester: impl Into<Actor>, res: bool) -> Option<QuestOutcome> {
210        if self.arbiter != requester.into() {
211            // Actor that requested quest resolution did not match the designated arbiter,
212            // resolution not permitted!
213            None
214        } else {
215            self.res
216                .0
217                .compare_exchange(
218                    0,
219                    if res { 2 } else { 1 },
220                    Ordering::Relaxed,
221                    Ordering::Relaxed,
222                )
223                .ok()
224                .map(|_| self.outcome.clone())
225        }
226    }
227
228    pub fn resolution(&self) -> Option<bool> { self.res.get() }
229
230    pub fn get_related_actors(&self) -> HashSet<Actor> {
231        let mut related = HashSet::default();
232        self.for_related_actors(|actor| {
233            related.insert(actor);
234        });
235        related
236    }
237
238    fn for_related_actors(&self, mut f: impl FnMut(Actor)) {
239        f(self.arbiter);
240        match &self.kind {
241            QuestKind::Escort {
242                escortee,
243                escorter,
244                to: _,
245            } => {
246                f(*escortee);
247                f(*escorter);
248            },
249            QuestKind::Slay { target, slayer } => {
250                f(*target);
251                f(*slayer);
252            },
253        }
254    }
255}
256
257// 0 = unresolved, 1 = fail, 2.. = success
258#[derive(Default, Serialize, Deserialize)]
259struct QuestRes(AtomicU8);
260
261impl QuestRes {
262    fn get(&self) -> Option<bool> {
263        match self.0.load(Ordering::Relaxed) {
264            0 => None,
265            1 => Some(false),
266            _ => Some(true),
267        }
268    }
269}
270
271impl Clone for QuestRes {
272    fn clone(&self) -> Self {
273        // This isn't strictly kosher in a multi-threaded context, but we assume that
274        // clones only happen on the main thread when we don't care about
275        // synchronisation
276        Self(AtomicU8::new(self.0.load(Ordering::Relaxed)))
277    }
278}
279
280/// Produced on completion of a quest.
281///
282/// Can represent both success and failure outcomes, such as:
283///
284/// - The release of a payment deposit
285/// - A change in reputation/goodwill
286/// - The role of an NPC or playing changing (promotion!)
287/// - etc.
288#[derive(Clone, Default, Serialize, Deserialize)]
289pub struct QuestOutcome {
290    /// An item held in deposit.
291    ///
292    /// When the quest is resolved, it is returned to the arbiter (usually to
293    /// then be passed to the quest completer, although not always).
294    ///
295    /// Deposits exist to avoid NPCs (or players) constantly needing to track
296    /// 'earmarked' items in their inventories that correspond to payments.
297    pub deposit: Option<(ItemResource, f32)>,
298}
299
300#[derive(Clone, Serialize, Deserialize)]
301pub enum QuestKind {
302    Escort {
303        escortee: Actor,
304        escorter: Actor,
305        to: SiteId,
306    },
307    Slay {
308        target: Actor,
309        slayer: Actor,
310    },
311}