1use crate::{
2 cases::try_finalize_item,
3 entities::create_pve_entity,
4 event::{Cheat, OverlordEvent, PrepareFightType},
5 gacha::item_case::generate_item_from_template,
6 game_config_helpers::GameConfigLookup,
7 logic::handler::OverlordLogic,
8 state::OverlordState,
9};
10
11use configs::cheats::CheatScriptId;
12use essences::{
13 abilities::AbilityShard,
14 ability_stones::{AbilityStoneDrop, OwnedAbilityStone},
15 entity::{ActionWithDeadline, Coordinates, EntityAttributes},
16 fighting::{EntityType, FightEntity, FightTemplateId},
17 items::{Item, ItemTemplateId},
18 pets::{Pet, PetDrop},
19 ratings::{RatingRewardAvailability, RatingRewardPeriod, RatingType},
20 skins::SkinId,
21 stones::StoneTemplateId,
22};
23use essences::{
24 currency::{CurrencySource, CurrencyUnit, check_can_decrease_currencies},
25 fighting::EntityTeam,
26 game::EntityTemplateId,
27};
28use event_system::script::random::GameRng;
29use event_system::{event::EventPluginized, system::EventHandleResult};
30use rand::RngExt;
31
32const CHEAT_OPEN_ITEM_CASE_MAX_BATCH: i64 = 1000;
36
37impl OverlordLogic {
38 pub fn handle_run_cheat(
39 &mut self,
40 cheat: &Cheat,
41 current_tick: u64,
42 rand_gen: rand::rngs::StdRng,
43 state: OverlordState,
44 ) -> EventHandleResult<OverlordEvent, OverlordState> {
45 match cheat {
46 Cheat::PauseCombat => self.handle_cheat_pause_combat(state),
47 Cheat::UnpauseCombat => self.handle_cheat_unpause_combat(state),
48 Cheat::GodModeOn => self.handle_cheat_god_mode(state),
49 Cheat::GodModeOff => self.handle_cheat_god_mode_off(state),
50 Cheat::GetRich => self.handle_cheat_get_rich(state),
51 Cheat::StartFight { templated_id } => {
52 self.handle_cheat_start_fight(*templated_id, state)
53 }
54 Cheat::SpawnEntity {
55 entity_id,
56 x,
57 y,
58 team,
59 attributes,
60 } => self.handle_cheat_spawn_entity(
61 *entity_id,
62 *x,
63 *y,
64 team.clone(),
65 attributes,
66 current_tick,
67 state,
68 rand_gen,
69 ),
70 Cheat::WearItems { items_ids } => {
71 self.handle_cheat_wear_equipment_set(items_ids, state, rand_gen)
72 }
73 Cheat::ClearInventory => self.handle_cheat_clear_inventory(state),
74 Cheat::ClearSlot { item_id } => self.handle_cheat_clear_slot(*item_id, state),
75 Cheat::GetAllSpells => self.handle_cheat_get_all_spells(state),
76 Cheat::SetChapter { chapter } => self.handle_cheat_set_chapter(*chapter, state),
77 Cheat::EquipSkin { skin_id } => self.handle_cheat_equip_skin(*skin_id, state),
78 Cheat::UnequipSkin { skin_id } => self.handle_cheat_unequip_skin(*skin_id, state),
79 Cheat::NewLevel { level } => self.handle_cheat_new_level(*level, state),
80 Cheat::Script { script_id } => self.handle_cheat_script(*script_id, rand_gen, state),
81 Cheat::MakeDailyRatingRewardsReady => {
82 self.handle_cheat_make_rating_rewards_ready(RatingRewardPeriod::Daily, state)
83 }
84 Cheat::MakeWeeklyRatingRewardsReady => {
85 self.handle_cheat_make_rating_rewards_ready(RatingRewardPeriod::Weekly, state)
86 }
87 Cheat::GetAllStones => self.handle_cheat_get_all_stones(state),
88 Cheat::GetAllAbilityStones => self.handle_cheat_get_all_ability_stones(state),
89 Cheat::GetAllPets => self.handle_cheat_get_all_pets(state),
90 Cheat::GetAllArtifacts => self.handle_cheat_get_all_artifacts(state),
91 Cheat::GetAllLaws => self.handle_cheat_get_all_laws(state),
92 Cheat::OpenItemCase { batch_size } => {
93 self.handle_cheat_open_item_case(*batch_size, false, rand_gen, state)
94 }
95 Cheat::OpenItemCaseFree { batch_size } => {
96 self.handle_cheat_open_item_case(*batch_size, true, rand_gen, state)
97 }
98 }
99 }
100
101 fn handle_cheat_open_item_case(
117 &self,
118 batch_size: i64,
119 grant_keys: bool,
120 mut rng: rand::rngs::StdRng,
121 state: OverlordState,
122 ) -> EventHandleResult<OverlordEvent, OverlordState> {
123 if self.frontend {
124 return EventHandleResult::ok(state);
127 }
128
129 if batch_size <= 0 {
130 tracing::error!("Cheat OpenItemCase: batch_size must be positive, got {batch_size}");
131 return EventHandleResult::fail(state);
132 }
133 let batch_size = batch_size.min(CHEAT_OPEN_ITEM_CASE_MAX_BATCH);
138
139 let key_currency_id = self.game_config.get().game_settings.item_case_currency_id;
144 if !grant_keys {
145 let price = vec![CurrencyUnit {
146 currency_id: key_currency_id,
147 amount: batch_size,
148 }];
149 if !check_can_decrease_currencies(&state.character_state.currencies, &price) {
150 tracing::error!(
151 "Cheat OpenItemCase: not enough currency for {batch_size} opens, available = {:?}",
152 state.character_state.currencies
153 );
154 return EventHandleResult::fail(state);
155 }
156 }
157
158 let mut result = self.open_item_case(batch_size, batch_size, &mut rng, state);
161 if !result.success() {
162 return result;
163 }
164
165 let Some(items) = result
166 .events()
167 .iter()
168 .find_map(|event| match event.event() {
169 OverlordEvent::PlayerNewItems { items } => Some(items.clone()),
170 _ => None,
171 })
172 else {
173 return result;
174 };
175 if items.is_empty() {
176 return result;
177 }
178
179 let game_config = self.game_config.get();
186 let (state, events) = result.state_and_events_mut();
187 let mut paths = Vec::new();
188 events.retain(|event| match event.event() {
189 OverlordEvent::PlinkoBallDropped { path, .. } => {
190 paths.push(path.clone());
191 false
192 }
193 _ => true,
194 });
195 let chest_level = state.character_state.character.item_case_level;
196 for path in &paths {
197 crate::mechanics::plinko::apply_pin_bonuses(
198 &game_config.plinko_settings,
199 chest_level,
200 path,
201 &mut state.character_state.plinko_pin_bonuses,
202 );
203 }
204
205 if grant_keys {
212 let keys = vec![CurrencyUnit {
213 currency_id: key_currency_id,
214 amount: items.len() as i64,
215 }];
216 result
217 .events_mut()
218 .insert(0, Self::currency_increase(&keys, CurrencySource::Cheat));
219 }
220
221 if !result.state().character_state.character.auto_chest_enabled {
222 match self.cheat_batch_sell_events(&items, result.state()) {
223 Ok(sell_events) => result.events_mut().extend(sell_events),
224 Err(e) => {
225 tracing::error!("Cheat OpenItemCase: couldn't rank the batch: {e}");
228 }
229 }
230 }
231
232 result
233 }
234
235 fn cheat_batch_sell_events(
245 &self,
246 items: &[Item],
247 state: &OverlordState,
248 ) -> anyhow::Result<Vec<EventPluginized<OverlordEvent, OverlordState>>> {
249 let game_config = self.game_config.get();
250
251 let mut powers = Vec::with_capacity(items.len());
252 for item in items {
253 powers.push(state.calculate_power_with_candidate_equipped(
254 item,
255 &game_config,
256 &self.behaviors,
257 )?);
258 }
259
260 let mut events = Vec::new();
261 for (idx, item) in items.iter().enumerate() {
262 let key = item.equipment_slot_key();
263 let has_stronger_same_slot = items.iter().enumerate().any(|(other_idx, other)| {
264 other_idx != idx
265 && other.equipment_slot_key() == key
266 && powers[other_idx] > powers[idx]
267 });
268 if has_stronger_same_slot {
269 events.push(EventPluginized::now(OverlordEvent::SellItem {
270 item_id: item.id,
271 }));
272 }
273 }
274
275 Ok(events)
276 }
277
278 fn handle_cheat_get_all_artifacts(
282 &self,
283 state: OverlordState,
284 ) -> EventHandleResult<OverlordEvent, OverlordState> {
285 let game_config = self.game_config.get();
286 let artifacts: Vec<_> = game_config.artifacts.iter().map(|a| a.id).collect();
287 let artifact_stones: Vec<_> = game_config.artifact_stones.iter().map(|s| s.id).collect();
288 self.handle_player_new_artifacts(&artifacts, &artifact_stones, state)
289 }
290
291 fn handle_cheat_get_all_laws(
296 &self,
297 mut state: OverlordState,
298 ) -> EventHandleResult<OverlordEvent, OverlordState> {
299 let game_config = self.game_config.get();
300 let cores = &mut state.character_state.cores;
301 for law in &game_config.laws {
302 if cores.law(law.id).is_some() {
303 continue;
304 }
305 cores.laws.push(essences::cores::OwnedLaw {
306 template_id: law.id,
307 level: 1,
308 copies: 0,
309 slot_index: None,
310 });
311 }
312 EventHandleResult::ok(state)
313 }
314
315 fn handle_cheat_get_all_pets(
319 &mut self,
320 mut state: OverlordState,
321 ) -> EventHandleResult<OverlordEvent, OverlordState> {
322 let game_config = self.game_config.get();
323
324 let mut drops = Vec::with_capacity(game_config.pet_templates.len());
325 for template in &game_config.pet_templates {
326 let Some(rarity) = game_config.pet_rarity(template.rarity_id) else {
327 tracing::warn!(
328 pet = %template.id,
329 rarity = %template.rarity_id,
330 "Pet template references a rarity that is not in config — skipped"
331 );
332 continue;
333 };
334
335 let owned = state
336 .character_state
337 .all_pets
338 .iter_mut()
339 .find(|owned| owned.template_id == template.id);
340
341 let is_new = owned.is_none();
342 match owned {
343 Some(owned) => owned.shards_amount += 1,
346 None => state.character_state.all_pets.push(Pet::from_template(
347 template,
348 rarity.clone(),
349 1,
350 0,
351 )),
352 }
353
354 drops.push(PetDrop {
355 template: template.clone(),
356 is_new,
357 is_checkpoint: false,
358 });
359 }
360
361 if drops.is_empty() {
362 return EventHandleResult::ok(state);
363 }
364
365 EventHandleResult::ok_events(
366 state,
367 vec![EventPluginized::now(OverlordEvent::NewPets { pets: drops })],
368 )
369 }
370
371 fn handle_cheat_get_all_ability_stones(
375 &mut self,
376 mut state: OverlordState,
377 ) -> EventHandleResult<OverlordEvent, OverlordState> {
378 let game_config = self.game_config.get();
379
380 let mut drops = Vec::with_capacity(game_config.ability_stones.len());
381 for template in &game_config.ability_stones {
382 let owned = state
383 .character_state
384 .ability_stones
385 .iter_mut()
386 .find(|owned| owned.template_id == template.id);
387
388 let is_new = owned.is_none();
389 match owned {
390 Some(owned) => owned.copies += 1,
391 None => {
392 let mut granted = OwnedAbilityStone::new(template.id);
393 granted.copies = 1;
394 state.character_state.ability_stones.push(granted);
395 }
396 }
397
398 drops.push(AbilityStoneDrop {
399 stone_id: template.id,
400 copies: 1,
401 is_new,
402 });
403 }
404
405 if drops.is_empty() {
406 return EventHandleResult::ok(state);
407 }
408
409 EventHandleResult::ok_events(
410 state,
411 vec![EventPluginized::now(OverlordEvent::NewAbilityStones {
412 stones: drops,
413 })],
414 )
415 }
416
417 fn handle_cheat_get_all_stones(
420 &mut self,
421 state: OverlordState,
422 ) -> EventHandleResult<OverlordEvent, OverlordState> {
423 let game_config = self.game_config.get();
424
425 let trigger_stones: Vec<StoneTemplateId> =
426 game_config.trigger_stones.iter().map(|s| s.id).collect();
427 let effect_stones: Vec<StoneTemplateId> =
428 game_config.effect_stones.iter().map(|s| s.id).collect();
429
430 self.handle_player_new_stones(&trigger_stones, &effect_stones, state)
431 }
432
433 fn handle_cheat_make_rating_rewards_ready(
434 &self,
435 period: RatingRewardPeriod,
436 mut state: OverlordState,
437 ) -> EventHandleResult<OverlordEvent, OverlordState> {
438 for rating_type in [RatingType::Arena, RatingType::PvE] {
439 let availability = state
440 .rating_reward_availability
441 .iter_mut()
442 .find(|availability| availability.rating_type == rating_type);
443
444 if let Some(availability) = availability {
445 match period {
446 RatingRewardPeriod::Daily => availability.daily_available = true,
447 RatingRewardPeriod::Weekly => availability.weekly_available = true,
448 }
449 } else {
450 state
451 .rating_reward_availability
452 .push(RatingRewardAvailability {
453 rating_type,
454 daily_available: period == RatingRewardPeriod::Daily,
455 weekly_available: period == RatingRewardPeriod::Weekly,
456 });
457 }
458 }
459
460 EventHandleResult::ok(state)
461 }
462
463 fn handle_cheat_pause_combat(
464 &self,
465 mut state: OverlordState,
466 ) -> EventHandleResult<OverlordEvent, OverlordState> {
467 if let Some(fight) = &mut state.active_fight {
468 fight.paused = true;
469 }
470
471 EventHandleResult::ok(state)
472 }
473
474 fn handle_cheat_unpause_combat(
475 &self,
476 mut state: OverlordState,
477 ) -> EventHandleResult<OverlordEvent, OverlordState> {
478 if let Some(fight) = &mut state.active_fight {
479 fight.paused = false;
480 }
481
482 EventHandleResult::ok(state)
483 }
484
485 fn handle_cheat_god_mode(
486 &mut self,
487 mut state: OverlordState,
488 ) -> EventHandleResult<OverlordEvent, OverlordState> {
489 let Some(active_fight) = &mut state.active_fight else {
490 return EventHandleResult::ok(state);
491 };
492
493 let Some(entity) = active_fight
494 .entities
495 .iter_mut()
496 .find(|e| e.id == active_fight.player_id)
497 else {
498 tracing::error!(
499 "Couldn't find player entity with id = {}",
500 active_fight.player_id
501 );
502 return EventHandleResult::fail(state);
503 };
504
505 entity.attributes.set("godmode", 1);
506
507 EventHandleResult::ok(state)
508 }
509
510 fn handle_cheat_god_mode_off(
511 &mut self,
512 mut state: OverlordState,
513 ) -> EventHandleResult<OverlordEvent, OverlordState> {
514 let Some(active_fight) = &mut state.active_fight else {
515 return EventHandleResult::ok(state);
516 };
517
518 let Some(entity) = active_fight
519 .entities
520 .iter_mut()
521 .find(|e| e.id == active_fight.player_id)
522 else {
523 tracing::error!(
524 "Couldn't find player entity with id = {}",
525 active_fight.player_id
526 );
527 return EventHandleResult::fail(state);
528 };
529
530 entity.attributes.set("godmode", 0);
531
532 EventHandleResult::ok(state)
533 }
534
535 fn handle_cheat_get_rich(
536 &mut self,
537 state: OverlordState,
538 ) -> EventHandleResult<OverlordEvent, OverlordState> {
539 let game_config = self.game_config.get();
540
541 let currencies: Vec<CurrencyUnit> = game_config
542 .currencies
543 .iter()
544 .map(|currency| CurrencyUnit {
545 currency_id: currency.id,
546 amount: 1000000,
547 })
548 .collect();
549
550 let events = vec![Self::currency_increase(¤cies, CurrencySource::Cheat)];
551 EventHandleResult::ok_events(state, events)
552 }
553
554 fn handle_cheat_clear_inventory(
555 &mut self,
556 mut state: OverlordState,
557 ) -> EventHandleResult<OverlordEvent, OverlordState> {
558 state.character_state.inventory.clear();
560
561 EventHandleResult::ok(state)
562 }
563
564 fn handle_cheat_clear_slot(
565 &mut self,
566 item_id: uuid::Uuid,
567 mut state: OverlordState,
568 ) -> EventHandleResult<OverlordEvent, OverlordState> {
569 let Some(inv_item_idx) = state
570 .character_state
571 .inventory
572 .iter()
573 .position(|x| x.id == item_id)
574 else {
575 tracing::error!("Tried clearing undefined item: item_id={}", item_id);
576 return EventHandleResult::fail(state);
577 };
578
579 state.character_state.inventory.remove(inv_item_idx);
580
581 EventHandleResult::ok(state)
582 }
583
584 fn handle_cheat_get_all_spells(
585 &mut self,
586 mut state: OverlordState,
587 ) -> EventHandleResult<OverlordEvent, OverlordState> {
588 let game_config = self.game_config.get();
589
590 let ability_shards: Vec<AbilityShard> = game_config
592 .abilities
593 .iter()
594 .filter(|ability| ability.is_gacha_ability)
595 .map(|ability| AbilityShard {
596 ability_id: ability.id,
597 shards_amount: 1,
598 })
599 .collect();
600
601 for shard in &ability_shards {
603 if let Some(existing_ability) = state
604 .character_state
605 .all_abilities
606 .iter_mut()
607 .find(|a| a.template_id == shard.ability_id)
608 {
609 existing_ability.shards_amount += shard.shards_amount;
611 } else {
612 let Some(template) = game_config.ability_template(shard.ability_id) else {
614 tracing::error!(
615 "Failed to find ability template with id={}",
616 shard.ability_id
617 );
618 continue;
619 };
620
621 let new_ability = essences::abilities::Ability::from_template(template, None, None);
622 state.character_state.all_abilities.push(new_ability);
623 }
624 }
625
626 EventHandleResult::ok(state)
627 }
628
629 fn handle_cheat_set_chapter(
630 &mut self,
631 chapter: i64,
632 mut state: OverlordState,
633 ) -> EventHandleResult<OverlordEvent, OverlordState> {
634 let game_config = self.game_config.get();
635
636 let prev_chapter_level = state.character_state.character.current_chapter_level;
637 state.character_state.character.current_chapter_level = chapter;
638 state.character_state.character.current_fight_number = 0;
639 state.character_state.character.last_boss_fight_won = true;
640
641 let Ok(current_chapter) = game_config
642 .require_chapter_by_level(state.character_state.character.current_chapter_level)
643 .cloned()
644 else {
645 tracing::error!(
646 "Failed to get chapter with chapter_level={}",
647 state.character_state.character.current_chapter_level
648 );
649 return EventHandleResult::fail(state);
650 };
651
652 let prepare_fight_delay_ticks =
653 self.get_prepare_fight_delay(true, ¤t_chapter, &state);
654
655 let mut events = vec![];
656 if self.should_reset_afk_timer_on_gating_unlock(prev_chapter_level, &state) {
657 events.push(EventPluginized::now(
658 OverlordEvent::AfkRewardsGatingUnlocked {},
659 ));
660 }
661 self.fight_clock.schedule(
662 OverlordEvent::PrepareFight {
663 prepare_fight_type: PrepareFightType::PVEFight,
664 },
665 prepare_fight_delay_ticks,
666 );
667
668 EventHandleResult::ok_events(state, events)
669 }
670
671 fn handle_cheat_start_fight(
672 &mut self,
673 fight_templated_id: FightTemplateId,
674 state: OverlordState,
675 ) -> EventHandleResult<OverlordEvent, OverlordState> {
676 let game_config = self.game_config.get();
677
678 self.fight_clock.schedule(
679 OverlordEvent::PrepareFight {
680 prepare_fight_type: PrepareFightType::SingleFight { fight_templated_id },
681 },
682 game_config
683 .fight_settings
684 .prepare_fight_win_delay_ticks_default,
685 );
686
687 EventHandleResult::ok(state)
688 }
689
690 #[allow(clippy::too_many_arguments)]
691 fn handle_cheat_spawn_entity(
692 &mut self,
693 entity_template_id: EntityTemplateId,
694 x: i64,
695 y: i64,
696 team: EntityTeam,
697 attributes: &Vec<(String, i64)>,
698 current_tick: u64,
699 mut state: OverlordState,
700 mut rand_gen: rand::rngs::StdRng,
701 ) -> EventHandleResult<OverlordEvent, OverlordState> {
702 let game_config = self.game_config.get();
703
704 let Some(active_fight) = &mut state.active_fight else {
705 return EventHandleResult::ok(state);
706 };
707
708 let entity_id = uuid::Builder::from_random_bytes(rand_gen.random()).into_uuid();
709
710 if active_fight
711 .entities
712 .iter()
713 .any(|entity| entity.id == entity_id)
714 {
715 tracing::error!("There is already an entity with id: {entity_id}");
716 return EventHandleResult::fail(state);
717 }
718
719 let Some(player) = active_fight.get_player() else {
720 tracing::error!("No player in fight");
721 return EventHandleResult::fail(state);
722 };
723
724 let position = Coordinates {
725 x: player.coordinates.x + x,
726 y: (player.coordinates.y + y).clamp(0, 2),
727 };
728
729 let fight_entity = FightEntity {
730 entity_type: EntityType::PVEEntity { entity_template_id },
731 position,
732 has_big_hp_bar: false,
733 team,
734 };
735
736 let mut entity_attributes = EntityAttributes::default();
737
738 for (key, value) in attributes {
739 entity_attributes.add(key, *value);
740 }
741
742 let mut created_entity = match create_pve_entity(
743 entity_id,
744 &fight_entity,
745 &game_config,
746 Some(entity_attributes),
747 ) {
748 Ok(entity) => entity,
749 Err(err) => {
750 tracing::error!("Couldn't create entity: {}", err.to_string());
751 return EventHandleResult::fail(state);
752 }
753 };
754
755 created_entity.abilities.iter().for_each(|ability| {
756 let cooldown = game_config
757 .ability_template(ability.ability.template_id)
758 .map(|t| t.cooldown)
759 .unwrap_or(0);
760 created_entity.actions_queue.push(&ActionWithDeadline::core(
761 self.make_start_cast_ability_action(created_entity.id, ability.ability.template_id),
762 current_tick + cooldown,
763 ))
764 });
765
766 active_fight.entities.push(created_entity);
767
768 EventHandleResult::ok(state)
769 }
770
771 fn handle_cheat_wear_equipment_set(
772 &mut self,
773 items_ids: &Vec<ItemTemplateId>,
774 mut state: OverlordState,
775 mut rand_gen: rand::rngs::StdRng,
776 ) -> EventHandleResult<OverlordEvent, OverlordState> {
777 let game_config = self.game_config.get();
778
779 state.character_state.inventory.clear();
780
781 for item_id in items_ids {
782 let item_template = game_config
783 .require_item_template(*item_id)
784 .unwrap_or_else(|_| panic!("Failed to get item with id={item_id}"));
785
786 let rarity = game_config
787 .require_item_rarity(item_template.rarity_id)
788 .unwrap_or_else(|_| {
789 panic!("Failed to get rarity with id={}", item_template.rarity_id)
790 })
791 .clone();
792
793 let mut item = generate_item_from_template(
794 item_template,
795 rarity,
796 state.character_state.character.character_level,
797 item_template.attributes_settings.optional_attributes_count,
799 &game_config,
800 &mut rand_gen,
801 );
802
803 let mut finalized_item = match try_finalize_item(
804 &mut item,
805 &game_config,
806 &self.behaviors,
807 &GameRng::from_entropy(),
808 ) {
809 Ok(()) => item,
810 Err(e) => {
811 tracing::error!("Failed to finalize item: {}", e);
812 return EventHandleResult::fail(state);
813 }
814 };
815
816 finalized_item.is_equipped = true;
817
818 state.character_state.inventory.push(finalized_item);
819 }
820
821 EventHandleResult::ok(state)
822 }
823
824 fn handle_cheat_equip_skin(
825 &mut self,
826 skin_id: SkinId,
827 mut state: OverlordState,
828 ) -> EventHandleResult<OverlordEvent, OverlordState> {
829 let game_config = self.game_config.get();
830
831 let Some(config_skin) = game_config.skin(skin_id) else {
832 tracing::error!("Failed to find skin with id: {skin_id}");
833 return EventHandleResult::fail(state);
834 };
835
836 let new_skin_type = config_skin.skin_type;
837
838 if let Some(pos) = state
839 .character_state
840 .character_skins
841 .equipped
842 .iter()
843 .position(|&s| {
844 game_config
845 .skin(s)
846 .is_some_and(|cs| cs.skin_type == new_skin_type)
847 })
848 {
849 let old_skin_id = state.character_state.character_skins.equipped.remove(pos);
850 state
851 .character_state
852 .character_skins
853 .available
854 .push(old_skin_id);
855 }
856
857 state.character_state.character_skins.equipped.push(skin_id);
858
859 EventHandleResult::ok(state)
860 }
861
862 fn handle_cheat_unequip_skin(
863 &mut self,
864 skin_id: SkinId,
865 mut state: OverlordState,
866 ) -> EventHandleResult<OverlordEvent, OverlordState> {
867 if let Some(pos) = state
868 .character_state
869 .character_skins
870 .equipped
871 .iter()
872 .position(|&s| s == skin_id)
873 {
874 state.character_state.character_skins.equipped.remove(pos);
875 state
876 .character_state
877 .character_skins
878 .available
879 .push(skin_id);
880 }
881
882 EventHandleResult::ok(state)
883 }
884
885 fn handle_cheat_new_level(
886 &mut self,
887 level: i64,
888 mut state: OverlordState,
889 ) -> EventHandleResult<OverlordEvent, OverlordState> {
890 state.character_state.character.character_level = level;
891 EventHandleResult::ok(state)
892 }
893
894 fn handle_cheat_script(
902 &mut self,
903 script_id: CheatScriptId,
904 rand_gen: rand::rngs::StdRng,
905 state: OverlordState,
906 ) -> EventHandleResult<OverlordEvent, OverlordState> {
907 let game_config = self.game_config.get();
908
909 let Some(_cheat_script) = game_config.cheat_script(script_id) else {
910 tracing::error!("Failed to find cheat_script with id: {script_id}");
911 return EventHandleResult::fail(state);
912 };
913
914 let parse_uuid =
916 |s: &str| uuid::Uuid::parse_str(s).expect("valid cheat-script uuid literal");
917
918 let mut events: Vec<OverlordEvent> = Vec::new();
919
920 match script_id.to_string().as_str() {
921 "019be635-9bc8-7a67-b7df-2cc01c2001c5" => {
926 let quest_ids = [
927 "019a4b43-be66-76d6-838f-153a717f82ff",
928 "019a6ef3-740e-7150-b795-b0cbd681ceef",
929 "019b561c-4f34-76c2-8a53-7b55667e7aea",
930 "019b561e-5450-7d29-ae94-d2ddc340030d",
931 "019b561f-bea7-71cc-834f-0909dead9d4b",
932 "019b5621-6475-794b-b850-502d8e124422",
933 "019bc1e6-394d-7c7e-b70c-55764b7455cd",
934 "019bc29c-572e-7c7d-a9e3-8b10a6d28550",
935 "019bc29c-b647-7288-a6ba-668bd37e1c5b",
936 "019bdb53-7ed5-731c-a056-06df0b628065",
937 "019be138-4fee-7ad6-a1a3-7b1539a7bf69",
938 "019be13b-ba5f-71b1-8982-ca1734a7367b",
939 ]
940 .into_iter()
941 .map(parse_uuid)
942 .collect::<Vec<_>>();
943 events.push(OverlordEvent::NewQuests { quest_ids });
944
945 let rng = event_system::script::random::GameRng::new(rand_gen);
946 crate::mechanics::loop_tasks::prepare_loop(
947 &mut events,
948 &game_config,
949 &state.character_state,
950 &rng,
951 );
952 crate::mechanics::loop_tasks::advance_loop(
953 &mut events,
954 &game_config,
955 self.behaviors.lookups(),
956 &state.character_state,
957 );
958 }
959
960 "019bea59-c643-7a11-8985-f02ba314bfd6" => {
962 crate::mechanics::loop_tasks::advance_loop(
963 &mut events,
964 &game_config,
965 self.behaviors.lookups(),
966 &state.character_state,
967 );
968 }
969
970 "019bfca8-953e-76c5-8b33-d83662f9dbb5" => {
972 events.push(OverlordEvent::CustomEvent {
973 event_type: "CompleteAllLoopTasks".to_string(),
974 data: crate::event::CustomEventData(Default::default()),
975 });
976 }
977
978 "019c1f08-d84d-7c5f-b71d-1368ac514461" => {
980 events.push(OverlordEvent::UpdateActiveLoopTaskId {
981 quest_id: parse_uuid("019b561e-5450-7d29-ae94-d2ddc340030d"),
982 });
983 }
984
985 other => {
987 tracing::warn!(
988 "Cheat::Script (id={other}) has no native implementation; \
989 producing no events"
990 );
991 }
992 }
993
994 let events = events.into_iter().map(EventPluginized::now).collect();
995 EventHandleResult::ok_events(state, events)
996 }
997}