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(¤cies, 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 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 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 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 existing_ability.shards_amount += shard.shards_amount;
222 } else {
223 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, ¤t_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}