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 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 OverlordLogic {
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, rand_gen, 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 self.fight_clock.schedule(
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 self.fight_clock.schedule(
290 OverlordEvent::PrepareFight {
291 prepare_fight_type: PrepareFightType::SingleFight { fight_templated_id },
292 },
293 game_config
294 .fight_settings
295 .prepare_fight_win_delay_ticks_default,
296 );
297
298 EventHandleResult::ok(state)
299 }
300
301 #[allow(clippy::too_many_arguments)]
302 fn handle_cheat_spawn_entity(
303 &mut self,
304 entity_template_id: EntityTemplateId,
305 x: i64,
306 y: i64,
307 team: EntityTeam,
308 attributes: &Vec<(String, i64)>,
309 current_tick: u64,
310 mut state: OverlordState,
311 mut rand_gen: rand::rngs::StdRng,
312 ) -> EventHandleResult<OverlordEvent, OverlordState> {
313 let game_config = self.game_config.get();
314
315 let Some(active_fight) = &mut state.active_fight else {
316 return EventHandleResult::ok(state);
317 };
318
319 let entity_id = uuid::Builder::from_random_bytes(rand_gen.random()).into_uuid();
320
321 if active_fight
322 .entities
323 .iter()
324 .any(|entity| entity.id == entity_id)
325 {
326 tracing::error!("There is already an entity with id: {entity_id}");
327 return EventHandleResult::fail(state);
328 }
329
330 let Some(player) = active_fight.get_player() else {
331 tracing::error!("No player in fight");
332 return EventHandleResult::fail(state);
333 };
334
335 let position = Coordinates {
336 x: player.coordinates.x + x,
337 y: (player.coordinates.y + y).clamp(0, 2),
338 };
339
340 let fight_entity = FightEntity {
341 entity_type: EntityType::PVEEntity { entity_template_id },
342 position,
343 has_big_hp_bar: false,
344 team,
345 };
346
347 let mut entity_attributes = EntityAttributes::default();
348
349 for (key, value) in attributes {
350 entity_attributes.add(key, *value);
351 }
352
353 let mut created_entity = match create_pve_entity(
354 entity_id,
355 &fight_entity,
356 &game_config,
357 Some(entity_attributes),
358 ) {
359 Ok(entity) => entity,
360 Err(err) => {
361 tracing::error!("Couldn't create entity: {}", err.to_string());
362 return EventHandleResult::fail(state);
363 }
364 };
365
366 created_entity.abilities.iter().for_each(|ability| {
367 let cooldown = game_config
368 .ability_template(ability.ability.template_id)
369 .map(|t| t.cooldown)
370 .unwrap_or(0);
371 created_entity.actions_queue.push(&ActionWithDeadline {
372 action: self
373 .make_start_cast_ability_action(created_entity.id, ability.ability.template_id),
374 deadline_tick: current_tick + cooldown,
375 })
376 });
377
378 active_fight.entities.push(created_entity);
379
380 EventHandleResult::ok(state)
381 }
382
383 fn handle_cheat_wear_equipment_set(
384 &mut self,
385 items_ids: &Vec<ItemTemplateId>,
386 mut state: OverlordState,
387 mut rand_gen: rand::rngs::StdRng,
388 ) -> EventHandleResult<OverlordEvent, OverlordState> {
389 let game_config = self.game_config.get();
390
391 state.character_state.inventory.clear();
392
393 for item_id in items_ids {
394 let item_template = game_config
395 .require_item_template(*item_id)
396 .unwrap_or_else(|_| panic!("Failed to get item with id={item_id}"));
397
398 let rarity = game_config
399 .require_item_rarity(item_template.rarity_id)
400 .unwrap_or_else(|_| {
401 panic!("Failed to get rarity with id={}", item_template.rarity_id)
402 })
403 .clone();
404
405 let mut item = generate_item_from_template(
406 item_template,
407 rarity,
408 state.character_state.character.character_level,
409 &game_config,
410 &mut rand_gen,
411 );
412
413 let mut finalized_item =
414 match try_finalize_item(&mut item, &game_config, &self.behaviors) {
415 Ok(()) => item,
416 Err(e) => {
417 tracing::error!("Failed to finalize item: {}", e);
418 return EventHandleResult::fail(state);
419 }
420 };
421
422 finalized_item.is_equipped = true;
423
424 state.character_state.inventory.push(finalized_item);
425 }
426
427 EventHandleResult::ok(state)
428 }
429
430 fn handle_cheat_equip_skin(
431 &mut self,
432 skin_id: SkinId,
433 mut state: OverlordState,
434 ) -> EventHandleResult<OverlordEvent, OverlordState> {
435 let game_config = self.game_config.get();
436
437 let Some(config_skin) = game_config.skin(skin_id) else {
438 tracing::error!("Failed to find skin with id: {skin_id}");
439 return EventHandleResult::fail(state);
440 };
441
442 let new_skin_type = config_skin.skin_type;
443
444 if let Some(pos) = state
445 .character_state
446 .character_skins
447 .equipped
448 .iter()
449 .position(|&s| {
450 game_config
451 .skin(s)
452 .is_some_and(|cs| cs.skin_type == new_skin_type)
453 })
454 {
455 let old_skin_id = state.character_state.character_skins.equipped.remove(pos);
456 state
457 .character_state
458 .character_skins
459 .available
460 .push(old_skin_id);
461 }
462
463 state.character_state.character_skins.equipped.push(skin_id);
464
465 EventHandleResult::ok(state)
466 }
467
468 fn handle_cheat_unequip_skin(
469 &mut self,
470 skin_id: SkinId,
471 mut state: OverlordState,
472 ) -> EventHandleResult<OverlordEvent, OverlordState> {
473 if let Some(pos) = state
474 .character_state
475 .character_skins
476 .equipped
477 .iter()
478 .position(|&s| s == skin_id)
479 {
480 state.character_state.character_skins.equipped.remove(pos);
481 state
482 .character_state
483 .character_skins
484 .available
485 .push(skin_id);
486 }
487
488 EventHandleResult::ok(state)
489 }
490
491 fn handle_cheat_new_level(
492 &mut self,
493 level: i64,
494 mut state: OverlordState,
495 ) -> EventHandleResult<OverlordEvent, OverlordState> {
496 state.character_state.character.character_level = level;
497 EventHandleResult::ok(state)
498 }
499
500 fn handle_cheat_script(
508 &mut self,
509 script_id: CheatScriptId,
510 rand_gen: rand::rngs::StdRng,
511 state: OverlordState,
512 ) -> EventHandleResult<OverlordEvent, OverlordState> {
513 let game_config = self.game_config.get();
514
515 let Some(_cheat_script) = game_config.cheat_script(script_id) else {
516 tracing::error!("Failed to find cheat_script with id: {script_id}");
517 return EventHandleResult::fail(state);
518 };
519
520 let parse_uuid =
522 |s: &str| uuid::Uuid::parse_str(s).expect("valid cheat-script uuid literal");
523
524 let mut events: Vec<OverlordEvent> = Vec::new();
525
526 match script_id.to_string().as_str() {
527 "019be635-9bc8-7a67-b7df-2cc01c2001c5" => {
532 let quest_ids = [
533 "019a4b43-be66-76d6-838f-153a717f82ff",
534 "019a6ef3-740e-7150-b795-b0cbd681ceef",
535 "019b561c-4f34-76c2-8a53-7b55667e7aea",
536 "019b561e-5450-7d29-ae94-d2ddc340030d",
537 "019b561f-bea7-71cc-834f-0909dead9d4b",
538 "019b5621-6475-794b-b850-502d8e124422",
539 "019bc1e6-394d-7c7e-b70c-55764b7455cd",
540 "019bc29c-572e-7c7d-a9e3-8b10a6d28550",
541 "019bc29c-b647-7288-a6ba-668bd37e1c5b",
542 "019bdb53-7ed5-731c-a056-06df0b628065",
543 "019be138-4fee-7ad6-a1a3-7b1539a7bf69",
544 "019be13b-ba5f-71b1-8982-ca1734a7367b",
545 ]
546 .into_iter()
547 .map(parse_uuid)
548 .collect::<Vec<_>>();
549 events.push(OverlordEvent::NewQuests { quest_ids });
550
551 let rng = event_system::script::random::GameRng::new(rand_gen);
552 crate::mechanics::loop_tasks::prepare_loop(
553 &mut events,
554 &game_config,
555 &state.character_state,
556 &rng,
557 );
558 crate::mechanics::loop_tasks::advance_loop(
559 &mut events,
560 &game_config,
561 self.behaviors.lookups(),
562 &state.character_state,
563 );
564 }
565
566 "019bea59-c643-7a11-8985-f02ba314bfd6" => {
568 crate::mechanics::loop_tasks::advance_loop(
569 &mut events,
570 &game_config,
571 self.behaviors.lookups(),
572 &state.character_state,
573 );
574 }
575
576 "019bfca8-953e-76c5-8b33-d83662f9dbb5" => {
578 events.push(OverlordEvent::CustomEvent {
579 event_type: "CompleteAllLoopTasks".to_string(),
580 data: crate::event::CustomEventData(Default::default()),
581 });
582 }
583
584 "019c1f08-d84d-7c5f-b71d-1368ac514461" => {
586 events.push(OverlordEvent::UpdateActiveLoopTaskId {
587 quest_id: parse_uuid("019b561e-5450-7d29-ae94-d2ddc340030d"),
588 });
589 }
590
591 other => {
593 tracing::warn!(
594 "Cheat::Script (id={other}) has no native implementation; \
595 producing no events"
596 );
597 }
598 }
599
600 let events = events.into_iter().map(EventPluginized::now).collect();
601 EventHandleResult::ok_events(state, events)
602 }
603}