overlord_event_system/async_handler/
items.rs1use crate::{
2 async_handler::handler::OverlordAsyncEventHandler, event::OverlordEvent,
3 game_config_helpers::GameConfigLookup, state::OverlordState,
4};
5
6#[allow(unused_imports)]
7use essences::currency::check_can_decrease_currencies;
8
9use configs::buffs::{apply_buff_multiplier, currency_exp_buff_multiplier};
10use essences::{
11 autochest::{AutoChestFilter, filter_items},
12 currency::{CurrencyConsumer, CurrencySource, CurrencyUnit, increase_currencies},
13 items::{Item, ItemType},
14};
15
16use event_system::{event::EventPluginized, return_if_frontend, system::EventHandleResult};
17
18use uuid::Uuid;
19
20impl OverlordAsyncEventHandler {
21 #[allow(dead_code)]
22 fn open_item_case(
23 &self,
24 batch_size: i64,
25 state: OverlordState,
26 ) -> EventHandleResult<OverlordEvent, OverlordState> {
27 let game_config = self.game_config.get();
28
29 tracing::debug!("Open case handler !frontend");
30 if state
31 .character_state
32 .inventory
33 .iter()
34 .any(|x| !x.is_equipped)
35 {
36 tracing::error!("Can't open item_case, inventory contains unequipped items");
37 return EventHandleResult::fail(state);
38 }
39
40 let character_item_case_level = state.character_state.character.item_case_level;
41 let Ok(case_settings) =
42 game_config.require_item_case_settings_by_level(character_item_case_level)
43 else {
44 tracing::error!(
45 "Couldn't find item_case_settings with level = {character_item_case_level}"
46 );
47 return EventHandleResult::fail(state);
48 };
49 if batch_size > case_settings.auto_chest_settings.max_batch_size {
50 tracing::error!(
51 "Batch size {} is bigger than max allowed {}",
52 batch_size,
53 case_settings.auto_chest_settings.max_batch_size
54 );
55 return EventHandleResult::fail(state);
56 }
57
58 let items = (0..batch_size)
59 .filter_map(|_| {
60 use crate::{cases, gacha};
61
62 let mut item = match gacha::item_case::try_open_item_case(
63 &state.character_state,
64 &game_config,
65 None,
66 &self.script_runner,
67 ) {
68 Ok(x) => x,
69 Err(e) => {
70 tracing::error!("Couldn't open case: {e}");
71 return None;
72 }
73 };
74
75 match cases::try_finalize_item(&mut item, &game_config, &self.script_runner) {
76 Ok(()) => Some(item),
77 Err(e) => {
78 tracing::error!("Failed to finalize item: {}", e);
79 None
80 }
81 }
82 })
83 .collect();
84
85 EventHandleResult::ok_events(
86 state,
87 vec![EventPluginized::now(OverlordEvent::PlayerNewItems {
88 items,
89 })],
90 )
91 }
92
93 #[allow(unused_variables)]
94 pub fn handle_open_item_case(
95 &self,
96 batch_size: i64,
97 state: OverlordState,
98 ) -> EventHandleResult<OverlordEvent, OverlordState> {
99 return_if_frontend!(state);
100
101 #[cfg(not(feature = "frontend"))]
102 {
103 let game_config = self.game_config.get();
104
105 if state.character_state.character.auto_chest_enabled {
106 tracing::error!("Can't open item case when auto_chest is enabled");
107 return EventHandleResult::fail(state);
108 }
109
110 let required_currency = vec![CurrencyUnit {
111 currency_id: game_config.game_settings.item_case_currency_id,
112 amount: batch_size,
113 }];
114
115 if !check_can_decrease_currencies(&state.character_state.currencies, &required_currency)
116 {
117 tracing::error!(
118 "Not enough currency for open_item_case opening\nGot = {:?}\nRequired = {:?}",
119 state.character_state.currencies,
120 required_currency
121 );
122
123 return EventHandleResult::fail(state);
124 };
125
126 if !self.frontend {
127 return self.open_item_case(batch_size, state);
128 }
129 EventHandleResult::ok(state)
130 }
131 }
132
133 #[allow(unused_variables)]
134 pub fn handle_auto_chest_open_item_case(
135 &self,
136 batch_size: i64,
137 state: OverlordState,
138 ) -> EventHandleResult<OverlordEvent, OverlordState> {
139 return_if_frontend!(state);
140
141 #[cfg(not(feature = "frontend"))]
142 {
143 let game_config = self.game_config.get();
144
145 if !state.character_state.character.auto_chest_enabled {
146 tracing::error!("Can't open auto_chest item case when auto_chest is disabled");
147 return EventHandleResult::fail(state);
148 }
149
150 let required_currency = vec![CurrencyUnit {
151 currency_id: game_config.game_settings.item_case_currency_id,
152 amount: batch_size,
153 }];
154
155 if !check_can_decrease_currencies(&state.character_state.currencies, &required_currency)
156 {
157 tracing::error!(
158 "Not enough currency for auto_chest_item_case opening\nGot = {:?}\nRequired = {:?}",
159 state.character_state.currencies,
160 required_currency
161 );
162
163 return EventHandleResult::ok_events(
164 state,
165 vec![EventPluginized::now(OverlordEvent::DisableAutoChest {})],
166 );
167 };
168
169 if !self.frontend {
170 return self.open_item_case(batch_size, state);
171 }
172 EventHandleResult::ok(state)
173 }
174 }
175
176 pub fn handle_player_new_items(
177 &self,
178 items: &[Item],
179 mut state: OverlordState,
180 ) -> EventHandleResult<OverlordEvent, OverlordState> {
181 let game_config = self.game_config.get();
182
183 let required_currency = vec![CurrencyUnit {
184 currency_id: game_config.game_settings.item_case_currency_id,
185 amount: items.len() as i64,
186 }];
187
188 let mut events = vec![];
189
190 let Some(currency_event) =
191 Self::currency_decrease(&state, &required_currency, CurrencyConsumer::ItemCaseOpen)
192 else {
193 return EventHandleResult::fail(state);
194 };
195 events.push(currency_event);
196
197 let mut items = items.to_owned();
198
199 if state.character_state.character.auto_chest_enabled {
200 let filter = state.auto_chest.active_filter.clone();
201 let power_compare_enabled = state.auto_chest.power_compare_enabled;
202 let items_to_equip = match self.filter_new_items(
203 &filter,
204 power_compare_enabled,
205 &items,
206 &mut state,
207 &mut events,
208 ) {
209 Ok(items_to_equip) => items_to_equip,
210 Err(e) => {
211 tracing::error!("Something went wrong while filtering = {}", e);
212 return EventHandleResult::fail(state);
213 }
214 };
215
216 if items_to_equip.is_empty() {
217 events.push(EventPluginized::delayed(
218 OverlordEvent::AutoChestOpenItemCase {
219 batch_size: state.auto_chest.batch_size,
220 },
221 game_config.game_settings.auto_chest_pause_duration_ticks,
222 ));
223 return EventHandleResult::ok_events(state, events);
224 };
225
226 items = items_to_equip;
227 }
228
229 for item in &items {
230 if let Some(item_template) = game_config.item_template(item.item_template_id)
231 && let Some(skin_id) = item_template.skin_id
232 {
233 if game_config.require_skin(skin_id).is_err() {
234 tracing::error!(
235 "Skin with id={} not found in config for item_template_id={}",
236 skin_id,
237 item.item_template_id
238 );
239 return EventHandleResult::fail(state);
240 }
241
242 if !state
243 .character_state
244 .character_skins
245 .is_already_unlocked(skin_id)
246 {
247 state
248 .character_state
249 .character_skins
250 .blocked
251 .retain(|s| *s != skin_id);
252 state
253 .character_state
254 .character_skins
255 .available
256 .push(skin_id);
257 }
258 }
259 }
260
261 state.character_state.inventory.append(&mut items);
262
263 EventHandleResult::ok_events(state, events)
264 }
265
266 fn filter_new_items(
267 &self,
268 filter: &Option<AutoChestFilter>,
269 power_compare_enabled: bool,
270 items: &[Item],
271 state: &mut OverlordState,
272 events: &mut Vec<EventPluginized<OverlordEvent, OverlordState>>,
273 ) -> anyhow::Result<Vec<Item>> {
274 let game_config = self.game_config.get();
275
276 let mut item_powers = std::collections::HashMap::new();
278 for item in items {
279 let power =
280 state.calculate_item_power(item.clone(), &game_config, &self.script_runner)?;
281 item_powers.insert(item.id, power);
282 }
283
284 let mut equipped_item_powers = std::collections::HashMap::new();
286 for item in &state.character_state.inventory {
287 if item.is_equipped {
288 let power =
289 state.calculate_item_power(item.clone(), &game_config, &self.script_runner)?;
290 equipped_item_powers.insert(item.item_type, power);
291 }
292 }
293
294 let (items_to_sell, items_to_equip) = filter_items(
295 items,
296 filter,
297 power_compare_enabled,
298 &game_config.item_rarities,
299 &item_powers,
300 &equipped_item_powers,
301 )?;
302
303 let buff_mult = currency_exp_buff_multiplier(
304 &state.character_state.active_buffs,
305 &game_config.buff_templates,
306 ::time::utc_now(),
307 );
308
309 let mut gained_currencies = vec![];
310
311 for item in items_to_sell {
312 let scaled_price: Vec<CurrencyUnit> = item
313 .price
314 .iter()
315 .map(|c| CurrencyUnit {
316 currency_id: c.currency_id,
317 amount: apply_buff_multiplier(c.amount, buff_mult),
318 })
319 .collect();
320 increase_currencies(&mut gained_currencies, &scaled_price);
321 state.character_state.character.character_experience +=
322 apply_buff_multiplier(item.experience, buff_mult);
323 events.push(EventPluginized::now(OverlordEvent::ItemSold {
324 item_id: item.id,
325 }));
326 }
327
328 events.push(Self::currency_increase(
329 &gained_currencies,
330 CurrencySource::AutoItemSell,
331 ));
332
333 Ok(items_to_equip)
334 }
335
336 pub fn handle_player_equip_item(
337 &self,
338 item_id: Uuid,
339 mut state: OverlordState,
340 ) -> EventHandleResult<OverlordEvent, OverlordState> {
341 let game_config = self.game_config.get();
342
343 let Some(item) = state
344 .character_state
345 .inventory
346 .iter()
347 .find(|x| x.id == item_id)
348 .cloned()
349 else {
350 tracing::error!("Tried equiping non-existing item_id={}", item_id);
351 return EventHandleResult::fail(state);
352 };
353
354 let prev_item = state
355 .character_state
356 .inventory
357 .iter()
358 .find(|inv_item| {
359 inv_item.item_type == item.item_type
360 && inv_item.id != item.id
361 && inv_item.is_equipped
362 })
363 .cloned();
364
365 state
366 .character_state
367 .inventory
368 .iter_mut()
369 .map(|inv_item| {
370 if inv_item.item_type == item.item_type {
371 inv_item.is_equipped = false;
372 }
373 if inv_item.id == item.id {
374 inv_item.is_equipped = true;
375 }
376 })
377 .count();
378
379 let mut equip_skin_ids = vec![];
380 let mut unequip_skin_ids = vec![];
381
382 let gear_override_enabled = state
383 .character_state
384 .character_settings
385 .gear_override_enabled_item_types
386 .contains(&item.item_type);
387
388 if !gear_override_enabled {
389 let currently_equipped = &state.character_state.character_skins.equipped;
390
391 if let Some(prev_item) = prev_item
394 && let Some(prev_item_template) =
395 game_config.item_template(prev_item.item_template_id)
396 && let Some(prev_skin_id) = prev_item_template.skin_id
397 && currently_equipped.contains(&prev_skin_id)
398 {
399 unequip_skin_ids.push(prev_skin_id);
400 }
401
402 if let Some(item_template) = game_config.item_template(item.item_template_id)
406 && let Some(skin_id) = item_template.skin_id
407 {
408 if game_config.require_skin(skin_id).is_err() {
409 tracing::error!(
410 "Skin with id={} not found in config for item_template_id={}",
411 skin_id,
412 item.item_template_id
413 );
414 return EventHandleResult::fail(state);
415 }
416
417 if currently_equipped.contains(&skin_id) {
418 unequip_skin_ids.retain(|id| *id != skin_id);
419 } else {
420 equip_skin_ids.push(skin_id);
421 }
422 }
423 }
424
425 let mut events = vec![];
426 if !equip_skin_ids.is_empty() || !unequip_skin_ids.is_empty() {
427 events.push(EventPluginized::now(OverlordEvent::EquipAndUnequipSkins {
428 equip_skin_ids,
429 unequip_skin_ids,
430 }));
431 }
432
433 if state.character_state.character.auto_chest_enabled {
434 if state
435 .character_state
436 .inventory
437 .iter()
438 .any(|item| !item.is_equipped)
439 {
440 return EventHandleResult::ok_events(state, events);
441 };
442 events.push(EventPluginized::delayed(
443 OverlordEvent::AutoChestOpenItemCase {
444 batch_size: state.auto_chest.batch_size,
445 },
446 game_config.game_settings.auto_chest_pause_duration_ticks,
447 ));
448 }
449
450 EventHandleResult::ok_events(state, events)
451 }
452
453 pub fn handle_sell_item(
454 &self,
455 item_id: Uuid,
456 mut state: OverlordState,
457 ) -> EventHandleResult<OverlordEvent, OverlordState> {
458 let game_config = self.game_config.get();
459
460 let Some(inv_item_idx) = state
461 .character_state
462 .inventory
463 .iter()
464 .position(|x| x.id == item_id)
465 else {
466 tracing::error!("Tried selling undefined item: item_id={}", item_id);
467 return EventHandleResult::fail(state);
468 };
469
470 let removed_item = state.character_state.inventory.remove(inv_item_idx);
471
472 let mut events = Vec::new();
473
474 let buff_mult = currency_exp_buff_multiplier(
475 &state.character_state.active_buffs,
476 &game_config.buff_templates,
477 ::time::utc_now(),
478 );
479 let scaled_price: Vec<CurrencyUnit> = removed_item
480 .price
481 .iter()
482 .map(|c| CurrencyUnit {
483 currency_id: c.currency_id,
484 amount: apply_buff_multiplier(c.amount, buff_mult),
485 })
486 .collect();
487
488 events.push(Self::currency_increase(
489 &scaled_price,
490 CurrencySource::ItemSell,
491 ));
492 events.push(EventPluginized::now(OverlordEvent::ItemSold { item_id }));
493 state.character_state.character.character_experience +=
494 apply_buff_multiplier(removed_item.experience, buff_mult);
495
496 if state.character_state.character.auto_chest_enabled {
497 if state
498 .character_state
499 .inventory
500 .iter()
501 .any(|item| !item.is_equipped)
502 {
503 return EventHandleResult::ok_events(state, events);
504 };
505 events.push(EventPluginized::delayed(
506 OverlordEvent::AutoChestOpenItemCase {
507 batch_size: state.auto_chest.batch_size,
508 },
509 game_config.game_settings.auto_chest_pause_duration_ticks,
510 ));
511 }
512
513 EventHandleResult::ok_events(state, events)
514 }
515
516 pub fn handle_enable_auto_sell(
517 &self,
518 mut state: OverlordState,
519 ) -> EventHandleResult<OverlordEvent, OverlordState> {
520 state.character_state.character_settings.auto_sell_enabled = true;
521
522 EventHandleResult::ok(state)
523 }
524
525 pub fn handle_disable_auto_sell(
526 &self,
527 mut state: OverlordState,
528 ) -> EventHandleResult<OverlordEvent, OverlordState> {
529 state.character_state.character_settings.auto_sell_enabled = false;
530
531 EventHandleResult::ok(state)
532 }
533
534 pub fn handle_set_gear_override_enabled(
535 &self,
536 item_type: ItemType,
537 enabled: bool,
538 mut state: OverlordState,
539 ) -> EventHandleResult<OverlordEvent, OverlordState> {
540 let overrides = &mut state
541 .character_state
542 .character_settings
543 .gear_override_enabled_item_types;
544
545 if enabled {
546 if !overrides.contains(&item_type) {
547 overrides.push(item_type);
548 }
549 return EventHandleResult::ok(state);
550 }
551
552 overrides.retain(|t| *t != item_type);
553
554 let game_config = self.game_config.get();
555 let skin_ids: Vec<Uuid> = state
556 .character_state
557 .inventory
558 .iter()
559 .filter(|item| item.is_equipped && item.item_type == item_type)
560 .filter_map(|item| {
561 game_config
562 .item_template(item.item_template_id)
563 .and_then(|template| template.skin_id)
564 })
565 .collect();
566
567 if skin_ids.is_empty() {
568 return EventHandleResult::ok(state);
569 }
570
571 EventHandleResult::ok_events(
572 state,
573 vec![EventPluginized::now(OverlordEvent::EquipAndUnequipSkins {
574 equip_skin_ids: skin_ids,
575 unequip_skin_ids: vec![],
576 })],
577 )
578 }
579
580 pub fn handle_enable_case_upgrade_pop_up(
581 &self,
582 mut state: OverlordState,
583 ) -> EventHandleResult<OverlordEvent, OverlordState> {
584 state
585 .character_state
586 .character_settings
587 .dont_show_case_upgrade_popup_today = false;
588
589 EventHandleResult::ok(state)
590 }
591
592 pub fn handle_disable_case_upgrade_pop_up(
593 &self,
594 mut state: OverlordState,
595 ) -> EventHandleResult<OverlordEvent, OverlordState> {
596 state
597 .character_state
598 .character_settings
599 .dont_show_case_upgrade_popup_today = true;
600
601 EventHandleResult::ok(state)
602 }
603}