overlord_event_system/async_handler/
cheats.rs

1use crate::{
2    async_handler::handler::OverlordAsyncEventHandler,
3    cases::try_finalize_item,
4    entities::create_pve_entity,
5    event::{Cheat, OverlordEvent, PrepareFightType},
6    gacha::item_case::generate_item_from_template,
7    game_config_helpers::GameConfigLookup,
8    state::OverlordState,
9};
10
11use configs::cheats::CheatScriptId;
12use essences::{
13    abilities::AbilityShard,
14    entity::{ActionWithDeadline, Coordinates, EntityAttributes},
15    fighting::{EntityType, FightEntity, FightTemplateId},
16    items::ItemTemplateId,
17    skins::SkinId,
18};
19use essences::{
20    currency::{CurrencySource, CurrencyUnit},
21    fighting::EntityTeam,
22    game::EntityTemplateId,
23};
24use event_system::{event::EventPluginized, system::EventHandleResult};
25use rand::Rng;
26
27impl OverlordAsyncEventHandler {
28    pub fn handle_run_cheat(
29        &mut self,
30        cheat: &Cheat,
31        current_tick: u64,
32        rand_gen: rand::rngs::StdRng,
33        state: OverlordState,
34    ) -> EventHandleResult<OverlordEvent, OverlordState> {
35        match cheat {
36            Cheat::PauseCombat => self.handle_cheat_pause_combat(state),
37            Cheat::UnpauseCombat => self.handle_cheat_unpause_combat(state),
38            Cheat::GodModeOn => self.handle_cheat_god_mode(state),
39            Cheat::GodModeOff => self.handle_cheat_god_mode_off(state),
40            Cheat::GetRich => self.handle_cheat_get_rich(state),
41            Cheat::StartFight { templated_id } => {
42                self.handle_cheat_start_fight(*templated_id, state)
43            }
44            Cheat::SpawnEntity {
45                entity_id,
46                x,
47                y,
48                team,
49                attributes,
50            } => self.handle_cheat_spawn_entity(
51                *entity_id,
52                *x,
53                *y,
54                team.clone(),
55                attributes,
56                current_tick,
57                state,
58                rand_gen,
59            ),
60            Cheat::WearItems { items_ids } => {
61                self.handle_cheat_wear_equipment_set(items_ids, state, rand_gen)
62            }
63            Cheat::ClearInventory => self.handle_cheat_clear_inventory(state),
64            Cheat::ClearSlot { item_id } => self.handle_cheat_clear_slot(*item_id, state),
65            Cheat::GetAllSpells => self.handle_cheat_get_all_spells(state),
66            Cheat::SetChapter { chapter } => self.handle_cheat_set_chapter(*chapter, state),
67            Cheat::EquipSkin { skin_id } => self.handle_cheat_equip_skin(*skin_id, state),
68            Cheat::UnequipSkin { skin_id } => self.handle_cheat_unequip_skin(*skin_id, state),
69            Cheat::NewLevel { level } => self.handle_cheat_new_level(*level, state),
70            Cheat::Script { script_id } => self.handle_cheat_script(*script_id, state),
71        }
72    }
73
74    fn handle_cheat_pause_combat(
75        &self,
76        mut state: OverlordState,
77    ) -> EventHandleResult<OverlordEvent, OverlordState> {
78        if let Some(fight) = &mut state.active_fight {
79            fight.paused = true;
80        }
81
82        EventHandleResult::ok(state)
83    }
84
85    fn handle_cheat_unpause_combat(
86        &self,
87        mut state: OverlordState,
88    ) -> EventHandleResult<OverlordEvent, OverlordState> {
89        if let Some(fight) = &mut state.active_fight {
90            fight.paused = false;
91        }
92
93        EventHandleResult::ok(state)
94    }
95
96    fn handle_cheat_god_mode(
97        &mut self,
98        mut state: OverlordState,
99    ) -> EventHandleResult<OverlordEvent, OverlordState> {
100        let Some(active_fight) = &mut state.active_fight else {
101            return EventHandleResult::ok(state);
102        };
103
104        let Some(entity) = active_fight
105            .entities
106            .iter_mut()
107            .find(|e| e.id == active_fight.player_id)
108        else {
109            tracing::error!(
110                "Couldn't find player entity with id = {}",
111                active_fight.player_id
112            );
113            return EventHandleResult::fail(state);
114        };
115
116        entity.attributes.set("godmode", 1);
117
118        EventHandleResult::ok(state)
119    }
120
121    fn handle_cheat_god_mode_off(
122        &mut self,
123        mut state: OverlordState,
124    ) -> EventHandleResult<OverlordEvent, OverlordState> {
125        let Some(active_fight) = &mut state.active_fight else {
126            return EventHandleResult::ok(state);
127        };
128
129        let Some(entity) = active_fight
130            .entities
131            .iter_mut()
132            .find(|e| e.id == active_fight.player_id)
133        else {
134            tracing::error!(
135                "Couldn't find player entity with id = {}",
136                active_fight.player_id
137            );
138            return EventHandleResult::fail(state);
139        };
140
141        entity.attributes.set("godmode", 0);
142
143        EventHandleResult::ok(state)
144    }
145
146    fn handle_cheat_get_rich(
147        &mut self,
148        state: OverlordState,
149    ) -> EventHandleResult<OverlordEvent, OverlordState> {
150        let game_config = self.game_config.get();
151
152        let currencies: Vec<CurrencyUnit> = game_config
153            .currencies
154            .iter()
155            .map(|currency| CurrencyUnit {
156                currency_id: currency.id,
157                amount: 1000000,
158            })
159            .collect();
160
161        let events = vec![Self::currency_increase(&currencies, CurrencySource::Cheat)];
162        EventHandleResult::ok_events(state, events)
163    }
164
165    fn handle_cheat_clear_inventory(
166        &mut self,
167        mut state: OverlordState,
168    ) -> EventHandleResult<OverlordEvent, OverlordState> {
169        // Remove all items from inventory
170        state.character_state.inventory.clear();
171
172        EventHandleResult::ok(state)
173    }
174
175    fn handle_cheat_clear_slot(
176        &mut self,
177        item_id: uuid::Uuid,
178        mut state: OverlordState,
179    ) -> EventHandleResult<OverlordEvent, OverlordState> {
180        let Some(inv_item_idx) = state
181            .character_state
182            .inventory
183            .iter()
184            .position(|x| x.id == item_id)
185        else {
186            tracing::error!("Tried clearing undefined item: item_id={}", item_id);
187            return EventHandleResult::fail(state);
188        };
189
190        state.character_state.inventory.remove(inv_item_idx);
191
192        EventHandleResult::ok(state)
193    }
194
195    fn handle_cheat_get_all_spells(
196        &mut self,
197        mut state: OverlordState,
198    ) -> EventHandleResult<OverlordEvent, OverlordState> {
199        let game_config = self.game_config.get();
200
201        // Collect all gacha abilities with 1 shard each
202        let ability_shards: Vec<AbilityShard> = game_config
203            .abilities
204            .iter()
205            .filter(|ability| ability.is_gacha_ability)
206            .map(|ability| AbilityShard {
207                ability_id: ability.id,
208                shards_amount: 1,
209            })
210            .collect();
211
212        // Update existing abilities or add new ones
213        for shard in &ability_shards {
214            if let Some(existing_ability) = state
215                .character_state
216                .all_abilities
217                .iter_mut()
218                .find(|a| a.template_id == shard.ability_id)
219            {
220                // Add shards to existing ability
221                existing_ability.shards_amount += shard.shards_amount;
222            } else {
223                // This is a new ability, we need to create it
224                let Some(template) = game_config.ability_template(shard.ability_id) else {
225                    tracing::error!(
226                        "Failed to find ability template with id={}",
227                        shard.ability_id
228                    );
229                    continue;
230                };
231
232                let new_ability = essences::abilities::Ability::from_template(template, None, None);
233                state.character_state.all_abilities.push(new_ability);
234            }
235        }
236
237        EventHandleResult::ok(state)
238    }
239
240    fn handle_cheat_set_chapter(
241        &mut self,
242        chapter: i64,
243        mut state: OverlordState,
244    ) -> EventHandleResult<OverlordEvent, OverlordState> {
245        let game_config = self.game_config.get();
246
247        let prev_chapter_level = state.character_state.character.current_chapter_level;
248        state.character_state.character.current_chapter_level = chapter;
249        state.character_state.character.current_fight_number = 0;
250        state.character_state.character.last_boss_fight_won = true;
251
252        let Ok(current_chapter) = game_config
253            .require_chapter_by_level(state.character_state.character.current_chapter_level)
254            .cloned()
255        else {
256            tracing::error!(
257                "Failed to get chapter with chapter_level={}",
258                state.character_state.character.current_chapter_level
259            );
260            return EventHandleResult::fail(state);
261        };
262
263        let prepare_fight_delay_ticks =
264            self.get_prepare_fight_delay(true, &current_chapter, &state);
265
266        let mut events = vec![];
267        if self.should_reset_afk_timer_on_gating_unlock(prev_chapter_level, &state) {
268            events.push(EventPluginized::now(
269                OverlordEvent::AfkRewardsGatingUnlocked {},
270            ));
271        }
272        events.push(EventPluginized::delayed(
273            OverlordEvent::PrepareFight {
274                prepare_fight_type: PrepareFightType::PVEFight,
275            },
276            prepare_fight_delay_ticks,
277        ));
278
279        EventHandleResult::ok_events(state, events)
280    }
281
282    fn handle_cheat_start_fight(
283        &mut self,
284        fight_templated_id: FightTemplateId,
285        state: OverlordState,
286    ) -> EventHandleResult<OverlordEvent, OverlordState> {
287        let game_config = self.game_config.get();
288
289        EventHandleResult::ok_events(
290            state,
291            vec![EventPluginized::delayed(
292                OverlordEvent::PrepareFight {
293                    prepare_fight_type: PrepareFightType::SingleFight { fight_templated_id },
294                },
295                game_config
296                    .fight_settings
297                    .prepare_fight_win_delay_ticks_default,
298            )],
299        )
300    }
301
302    #[allow(clippy::too_many_arguments)]
303    fn handle_cheat_spawn_entity(
304        &mut self,
305        entity_template_id: EntityTemplateId,
306        x: i64,
307        y: i64,
308        team: EntityTeam,
309        attributes: &Vec<(String, i64)>,
310        current_tick: u64,
311        mut state: OverlordState,
312        mut rand_gen: rand::rngs::StdRng,
313    ) -> EventHandleResult<OverlordEvent, OverlordState> {
314        let game_config = self.game_config.get();
315
316        let Some(active_fight) = &mut state.active_fight else {
317            return EventHandleResult::ok(state);
318        };
319
320        let entity_id = uuid::Builder::from_random_bytes(rand_gen.random()).into_uuid();
321
322        if active_fight
323            .entities
324            .iter()
325            .any(|entity| entity.id == entity_id)
326        {
327            tracing::error!("There is already an entity with id: {entity_id}");
328            return EventHandleResult::fail(state);
329        }
330
331        let Some(player) = active_fight.get_player() else {
332            tracing::error!("No player in fight");
333            return EventHandleResult::fail(state);
334        };
335
336        let position = Coordinates {
337            x: player.coordinates.x + x,
338            y: (player.coordinates.y + y).clamp(0, 2),
339        };
340
341        let fight_entity = FightEntity {
342            entity_type: EntityType::PVEEntity { entity_template_id },
343            position,
344            has_big_hp_bar: false,
345            team,
346        };
347
348        let mut entity_attributes = EntityAttributes::default();
349
350        for (key, value) in attributes {
351            entity_attributes.add(key, *value);
352        }
353
354        let mut created_entity = match create_pve_entity(
355            entity_id,
356            &fight_entity,
357            &game_config,
358            Some(entity_attributes),
359        ) {
360            Ok(entity) => entity,
361            Err(err) => {
362                tracing::error!("Couldn't create entity: {}", err.to_string());
363                return EventHandleResult::fail(state);
364            }
365        };
366
367        created_entity.abilities.iter().for_each(|ability| {
368            let cooldown = game_config
369                .ability_template(ability.ability.template_id)
370                .map(|t| t.cooldown)
371                .unwrap_or(0);
372            created_entity.actions_queue.push(&ActionWithDeadline {
373                action: self
374                    .make_start_cast_ability_action(created_entity.id, ability.ability.template_id),
375                deadline_tick: current_tick + cooldown,
376            })
377        });
378
379        active_fight.entities.push(created_entity);
380
381        EventHandleResult::ok(state)
382    }
383
384    fn handle_cheat_wear_equipment_set(
385        &mut self,
386        items_ids: &Vec<ItemTemplateId>,
387        mut state: OverlordState,
388        mut rand_gen: rand::rngs::StdRng,
389    ) -> EventHandleResult<OverlordEvent, OverlordState> {
390        let game_config = self.game_config.get();
391
392        state.character_state.inventory.clear();
393
394        for item_id in items_ids {
395            let item_template = game_config
396                .require_item_template(*item_id)
397                .unwrap_or_else(|_| panic!("Failed to get item with id={item_id}"));
398
399            let rarity = game_config
400                .require_item_rarity(item_template.rarity_id)
401                .unwrap_or_else(|_| {
402                    panic!("Failed to get rarity with id={}", item_template.rarity_id)
403                })
404                .clone();
405
406            let mut item = generate_item_from_template(
407                item_template,
408                rarity,
409                state.character_state.character.character_level,
410                &game_config,
411                &mut rand_gen,
412            );
413
414            let mut finalized_item =
415                match try_finalize_item(&mut item, &game_config, &self.script_runner) {
416                    Ok(()) => item,
417                    Err(e) => {
418                        tracing::error!("Failed to finalize item: {}", e);
419                        return EventHandleResult::fail(state);
420                    }
421                };
422
423            finalized_item.is_equipped = true;
424
425            state.character_state.inventory.push(finalized_item);
426        }
427
428        EventHandleResult::ok(state)
429    }
430
431    fn handle_cheat_equip_skin(
432        &mut self,
433        skin_id: SkinId,
434        mut state: OverlordState,
435    ) -> EventHandleResult<OverlordEvent, OverlordState> {
436        let game_config = self.game_config.get();
437
438        let Some(config_skin) = game_config.skin(skin_id) else {
439            tracing::error!("Failed to find skin with id: {skin_id}");
440            return EventHandleResult::fail(state);
441        };
442
443        let new_skin_type = config_skin.skin_type;
444
445        if let Some(pos) = state
446            .character_state
447            .character_skins
448            .equipped
449            .iter()
450            .position(|&s| {
451                game_config
452                    .skin(s)
453                    .is_some_and(|cs| cs.skin_type == new_skin_type)
454            })
455        {
456            let old_skin_id = state.character_state.character_skins.equipped.remove(pos);
457            state
458                .character_state
459                .character_skins
460                .available
461                .push(old_skin_id);
462        }
463
464        state.character_state.character_skins.equipped.push(skin_id);
465
466        EventHandleResult::ok(state)
467    }
468
469    fn handle_cheat_unequip_skin(
470        &mut self,
471        skin_id: SkinId,
472        mut state: OverlordState,
473    ) -> EventHandleResult<OverlordEvent, OverlordState> {
474        if let Some(pos) = state
475            .character_state
476            .character_skins
477            .equipped
478            .iter()
479            .position(|&s| s == skin_id)
480        {
481            state.character_state.character_skins.equipped.remove(pos);
482            state
483                .character_state
484                .character_skins
485                .available
486                .push(skin_id);
487        }
488
489        EventHandleResult::ok(state)
490    }
491
492    fn handle_cheat_new_level(
493        &mut self,
494        level: i64,
495        mut state: OverlordState,
496    ) -> EventHandleResult<OverlordEvent, OverlordState> {
497        state.character_state.character.character_level = level;
498        EventHandleResult::ok(state)
499    }
500
501    fn handle_cheat_script(
502        &mut self,
503        script_id: CheatScriptId,
504        state: OverlordState,
505    ) -> EventHandleResult<OverlordEvent, OverlordState> {
506        let game_config = self.game_config.get();
507
508        let Some(cheat_script) = game_config.cheat_script(script_id) else {
509            tracing::error!("Failed to find cheat_script with id: {script_id}");
510            return EventHandleResult::fail(state);
511        };
512
513        let mut events = vec![];
514
515        match self.script_runner.run_event(
516            |mut scope_setter| {
517                scope_setter.set_const("CharacterState", state.character_state.clone());
518                scope_setter
519            },
520            &state,
521            &cheat_script.script,
522        ) {
523            Ok(script_events) => {
524                events.append(
525                    &mut script_events
526                        .into_iter()
527                        .map(EventPluginized::now)
528                        .collect(),
529                );
530            }
531            Err(err) => {
532                tracing::error!("Cheat script failed with error: {err:?}");
533                return EventHandleResult::fail(state);
534            }
535        }
536
537        EventHandleResult::ok_events(state, events)
538    }
539}