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}