overlord_event_system/async_handler/
quests.rs1use crate::{
2 async_handler::handler::OverlordAsyncEventHandler, event::OverlordEvent,
3 game_config_helpers::GameConfigLookup, quests::make_quest_instance, state::OverlordState,
4};
5
6use essences::{
7 currency::CurrencySource,
8 quest::{QuestGroupType, QuestTemplate, QuestsTrackReward},
9};
10
11use event_system::{
12 event::EventPluginized, script::runner::ScriptRandom, system::EventHandleResult,
13};
14use uuid::Uuid;
15
16impl OverlordAsyncEventHandler {
17 pub fn handle_claim_quest(
18 &mut self,
19 quest_id: Uuid,
20 rand_gen: rand::rngs::StdRng,
21 mut state: OverlordState,
22 ) -> EventHandleResult<OverlordEvent, OverlordState> {
23 let Ok(quest) = self.get_quest(quest_id) else {
24 tracing::error!("Couldn't get quest with id = {} in config", quest_id);
25 return EventHandleResult::fail(state);
26 };
27
28 let Some(quest_instance) = state.quest_groups.find_in_non_patron(quest_id) else {
29 tracing::error!("Couldn't find quest with id = {} in state", quest_id);
30 return EventHandleResult::fail(state);
31 };
32
33 if quest.quest_group_type == QuestGroupType::PatronDaily
34 || quest.quest_group_type == QuestGroupType::PatronLifetime
35 {
36 tracing::error!("Quest with id = {} is a patron_quest", quest_id);
37 return EventHandleResult::fail(state);
38 }
39
40 if quest.quest_group_type == QuestGroupType::Hidden {
41 tracing::error!("Quest with id = {} is a hidden_quest", quest_id);
42 return EventHandleResult::fail(state);
43 }
44
45 if !quest_instance.is_completed(quest.progress_target) {
46 tracing::error!(
47 "Tried claiming quest with id = {}, that is not completed:\n Current: {}, Target: {}",
48 quest_id,
49 quest_instance.current,
50 quest.progress_target,
51 );
52 return EventHandleResult::fail(state);
53 }
54
55 if let Err(e) = state.quest_groups.mark_quest_claimed(quest_id) {
56 tracing::error!("Got error, trying to mark quest as claimed: {:?}", e);
57 return EventHandleResult::fail(state);
58 }
59
60 state.quest_groups.retain_lifetime(quest_id);
63 state.quest_groups.reset_loop_task(quest_id);
64
65 match quest.quest_group_type {
66 QuestGroupType::Daily => {
67 state.quest_groups.daily.progress_track.current_points += quest.progression_points;
68 }
69 QuestGroupType::Weekly => {
70 state.quest_groups.weekly.progress_track.current_points += quest.progression_points;
71 }
72 QuestGroupType::Achievement => {
73 state
74 .quest_groups
75 .achievements
76 .progress_track
77 .current_points += quest.progression_points;
78 }
79 _ => {}
80 }
81
82 let mut events = vec![];
83
84 if let Some(bundle_id) = quest.bundle_id {
86 events.push(EventPluginized::now(OverlordEvent::AddBundleGroup {
87 bundle_ids: vec![bundle_id],
88 source: essences::currency::CurrencySource::QuestClaim,
89 }));
90 }
91
92 if !quest.next_quest_ids.is_empty() {
93 events.push(EventPluginized::now(OverlordEvent::NewQuests {
94 quest_ids: quest.next_quest_ids,
95 }));
96 }
97
98 if let Some(additional_quests_script) = quest.additional_quests_script {
99 match self.script_runner.run_event(
100 |mut scope_setter| {
101 scope_setter.set_const("Random", ScriptRandom::new(rand_gen));
102 scope_setter.set_const("CharacterState", state.character_state.clone());
103 scope_setter
104 },
105 &state,
106 &additional_quests_script,
107 ) {
108 Ok(script_events) => {
109 events.append(
110 &mut script_events
111 .into_iter()
112 .map(EventPluginized::now)
113 .collect(),
114 );
115 }
116 Err(err) => {
117 tracing::error!("Additional quests script failed with error: {err:?}");
118 return EventHandleResult::fail(state);
119 }
120 }
121 }
122
123 EventHandleResult::ok_events(state, events)
124 }
125
126 pub fn handle_new_quests(
127 &self,
128 quest_ids: Vec<Uuid>,
129 mut state: OverlordState,
130 ) -> EventHandleResult<OverlordEvent, OverlordState> {
131 for quest_id in quest_ids {
132 let Ok(quest) = self.get_quest(quest_id) else {
133 tracing::error!("Couldn't get quest with id = {}", quest_id);
134 return EventHandleResult::fail(state);
135 };
136
137 if state.quest_groups.find_in_all(quest_id).is_some() {
138 tracing::warn!("Quest with id = {} is already in state, skipping", quest_id);
139 continue;
140 }
141
142 state.quest_groups.push(
143 &make_quest_instance(
144 &quest,
145 &state.character_state,
146 &self.game_config.get(),
147 &self.script_runner,
148 ),
149 &quest.quest_group_type,
150 );
151 }
152
153 EventHandleResult::ok(state)
154 }
155
156 pub fn handle_update_active_loop_task_id(
157 &self,
158 quest_id: Uuid,
159 mut state: OverlordState,
160 ) -> EventHandleResult<OverlordEvent, OverlordState> {
161 let resolved_id = if state
162 .quest_groups
163 .loop_tasks
164 .iter()
165 .any(|q| q.id == quest_id)
166 {
167 quest_id
168 } else {
169 tracing::warn!(
170 "Loop task quest_id={quest_id} not found in state.loop_tasks, falling back to default loop task from config"
171 );
172 let game_config = self.game_config.get();
173 let fallback_id = match self.script_runner.run_expression::<Uuid>(
174 |mut scope_setter| {
175 scope_setter.set_const(
176 "CustomValues",
177 state.character_state.character.custom_values.clone(),
178 );
179 scope_setter
180 },
181 &game_config.game_settings.get_default_loop_task_id,
182 ) {
183 Ok(id) => id,
184 Err(err) => {
185 tracing::error!("Failed to evaluate get_default_loop_task_id script: {err}");
186 return EventHandleResult::fail(state);
187 }
188 };
189 if state
190 .quest_groups
191 .loop_tasks
192 .iter()
193 .any(|q| q.id == fallback_id)
194 {
195 fallback_id
196 } else if let Some(first_task) = state.quest_groups.loop_tasks.first() {
197 tracing::warn!(
198 "Fallback quest {fallback_id} from config not found in state, \
199 using first available loop task {}",
200 first_task.id
201 );
202 first_task.id
203 } else {
204 tracing::error!("No loop tasks available in state");
205 return EventHandleResult::fail(state);
206 }
207 };
208
209 state.character_state.character.active_loop_task_id = Some(resolved_id);
210
211 EventHandleResult::ok(state)
212 }
213
214 pub fn handle_patron_quest_completed(
215 &self,
216 quest_id: Uuid,
217 mut state: OverlordState,
218 ) -> EventHandleResult<OverlordEvent, OverlordState> {
219 if state.patron.is_none() {
220 tracing::error!("Tried completing patron quest but there is no patron");
221 return EventHandleResult::fail(state);
222 }
223
224 let Ok(quest) = self.get_quest(quest_id) else {
225 tracing::error!("Couldn't get quest with id = {}", quest_id);
226 return EventHandleResult::fail(state);
227 };
228
229 let Some(quest_instance) = state.quest_groups.find_in_patron(quest_id) else {
230 tracing::error!("Couldn't find quest with id = {} in state", quest_id);
231 return EventHandleResult::fail(state);
232 };
233
234 if !(quest.quest_group_type == QuestGroupType::PatronDaily
235 || quest.quest_group_type == QuestGroupType::PatronLifetime)
236 {
237 tracing::error!("Quest with id = {} is not patron_quest", quest_id);
238 return EventHandleResult::fail(state);
239 }
240
241 if !quest_instance.is_completed(quest.progress_target) {
242 tracing::error!(
243 "Tried claiming quest with id = {}, that is not completed:\n Current: {}, Target: {}",
244 quest_id,
245 quest_instance.current,
246 quest.progress_target,
247 );
248 return EventHandleResult::fail(state);
249 }
250
251 state.quest_groups.retain_patron(quest.id);
252
253 let mut events = vec![];
254
255 if !quest.next_quest_ids.is_empty() {
256 events.push(EventPluginized::now(OverlordEvent::NewQuests {
257 quest_ids: quest.next_quest_ids,
258 }));
259 }
260
261 EventHandleResult::ok_events(state, events)
262 }
263
264 pub fn handle_hidden_quest_completed(
265 &self,
266 quest_id: Uuid,
267 rand_gen: rand::rngs::StdRng,
268 mut state: OverlordState,
269 ) -> EventHandleResult<OverlordEvent, OverlordState> {
270 let Ok(quest) = self.get_quest(quest_id) else {
271 tracing::error!("Couldn't get quest with id = {}", quest_id);
272 return EventHandleResult::fail(state);
273 };
274
275 let Some(quest_instance) = state.quest_groups.find_in_hidden(quest_id) else {
276 tracing::error!("Couldn't find quest with id = {} in state", quest_id);
277 return EventHandleResult::fail(state);
278 };
279
280 if quest.quest_group_type != QuestGroupType::Hidden {
281 tracing::error!("Quest with id = {} is not hidden", quest_id);
282 return EventHandleResult::fail(state);
283 }
284
285 if !quest_instance.is_completed(quest.progress_target) {
286 tracing::error!(
287 "Tried claiming quest with id = {}, that is not completed:\n Current: {}, Target: {}",
288 quest_id,
289 quest_instance.current,
290 quest.progress_target,
291 );
292 return EventHandleResult::fail(state);
293 }
294
295 state.quest_groups.retain_hidden(quest.id);
296
297 let mut events = vec![];
298
299 if let Some(bundle_id) = quest.bundle_id {
301 events.push(EventPluginized::now(OverlordEvent::AddBundleGroup {
302 bundle_ids: vec![bundle_id],
303 source: essences::currency::CurrencySource::QuestClaim,
304 }));
305 }
306
307 if !quest.next_quest_ids.is_empty() {
308 events.push(EventPluginized::now(OverlordEvent::NewQuests {
309 quest_ids: quest.next_quest_ids,
310 }));
311 }
312
313 if let Some(additional_quests_script) = quest.additional_quests_script {
314 match self.script_runner.run_event(
315 |mut scope_setter| {
316 scope_setter.set_const("Random", ScriptRandom::new(rand_gen));
317 scope_setter.set_const("CharacterState", state.character_state.clone());
318 scope_setter
319 },
320 &state,
321 &additional_quests_script,
322 ) {
323 Ok(script_events) => {
324 events.append(
325 &mut script_events
326 .into_iter()
327 .map(EventPluginized::now)
328 .collect(),
329 );
330 }
331 Err(err) => {
332 tracing::error!("Additional quests script failed with error: {err:?}");
334 return EventHandleResult::fail(state);
335 }
336 }
337 }
338
339 EventHandleResult::ok_events(state, events)
340 }
341
342 pub fn handle_claim_quest_progression_reward(
343 &self,
344 quest_group_type: QuestGroupType,
345 mut state: OverlordState,
346 ) -> EventHandleResult<OverlordEvent, OverlordState> {
347 let (current_points, rewards) = match quest_group_type {
348 QuestGroupType::Daily => (
349 state.quest_groups.daily.progress_track.current_points,
350 &mut state.quest_groups.daily.progress_track.rewards,
351 ),
352 QuestGroupType::Weekly => (
353 state.quest_groups.weekly.progress_track.current_points,
354 &mut state.quest_groups.weekly.progress_track.rewards,
355 ),
356 QuestGroupType::Achievement => (
357 state
358 .quest_groups
359 .achievements
360 .progress_track
361 .current_points,
362 &mut state.quest_groups.achievements.progress_track.rewards,
363 ),
364 _ => {
365 tracing::error!(
366 "Tried claiming quest progression reward with quest_group_type = {quest_group_type:?}"
367 );
368 return EventHandleResult::fail(state);
369 }
370 };
371
372 let mut available_rewards: Vec<&mut QuestsTrackReward> = rewards
373 .iter_mut()
374 .filter(|x| !x.is_claimed && current_points >= x.points_required)
375 .collect();
376
377 if available_rewards.is_empty() {
378 tracing::error!(
379 "No available rewards found for current_points = {} and quest_group_type = {}",
380 current_points,
381 quest_group_type
382 );
383 return EventHandleResult::fail(state);
384 }
385
386 let mut all_claimed_rewards = vec![];
388 if quest_group_type == QuestGroupType::Achievement {
389 available_rewards.sort_by_key(|r| r.points_required);
390 let reward = &mut available_rewards[0];
391 reward.is_claimed = true;
392 all_claimed_rewards.extend(reward.reward.iter().cloned());
393 } else {
394 for reward in available_rewards {
395 reward.is_claimed = true;
396 all_claimed_rewards.extend(reward.reward.iter().cloned());
397 }
398 }
399
400 let events = vec![Self::currency_increase(
401 &all_claimed_rewards,
402 CurrencySource::QuestsTrackReward,
403 )];
404
405 EventHandleResult::ok_events(state, events)
406 }
407
408 fn get_quest(&self, quest_id: Uuid) -> anyhow::Result<QuestTemplate> {
409 let game_config = self.game_config.get();
410
411 Ok(game_config.require_quest(quest_id)?.clone())
412 }
413
414 pub fn handle_reset_repeating_quests(
415 &self,
416 quest_ids: Vec<Uuid>,
417 mut state: OverlordState,
418 ) -> EventHandleResult<OverlordEvent, OverlordState> {
419 for quest_id in quest_ids {
420 let Some(quest) = state.quest_groups.find_in_repeatable_mut(quest_id) else {
421 tracing::error!(
422 "Couldn't find repeatable quest with id = {}, in state",
423 quest_id
424 );
425 return EventHandleResult::fail(state);
426 };
427
428 quest.current = 0;
429 quest.is_claimed = false;
430 }
431
432 EventHandleResult::ok(state)
433 }
434}