overlord_event_system/logic/
pets.rs

1use crate::{
2    attributes::calculate_player_entity_stats_without_zeroes,
3    entities::{make_active_abilities_from_equipped, sort_active_abilities},
4    event::OverlordEvent,
5    game_config_helpers::GameConfigLookup,
6    logic::handler::OverlordLogic,
7    state::OverlordState,
8};
9
10use essences::entity::{ActionWithDeadline, EntityState};
11use essences::pets::{EquippedPets, PetId, PetSlotId};
12use event_system::system::EventHandleResult;
13
14impl OverlordLogic {
15    pub fn handle_equip_pet(
16        &self,
17        slot_id: u64,
18        pet_id: PetId,
19        current_tick: u64,
20        mut state: OverlordState,
21    ) -> EventHandleResult<OverlordEvent, OverlordState> {
22        let game_config = self.game_config.get();
23
24        // Check that the pet is not already equipped in another slot
25        if let Some((existing_slot, _)) = state
26            .character_state
27            .equipped_pets
28            .slotted
29            .iter()
30            .find(|(_, p)| p.template_id == pet_id)
31        {
32            tracing::error!("Pet_id = {pet_id} is already equipped in slot_id={existing_slot}");
33            return EventHandleResult::fail(state);
34        }
35
36        // Find the pet in all_pets
37        let Some(pet) = state
38            .character_state
39            .all_pets
40            .iter()
41            .find(|p| p.template_id == pet_id)
42            .cloned()
43        else {
44            tracing::error!("No pet with id = {} in state", pet_id);
45            return EventHandleResult::fail(state);
46        };
47
48        // Check that the slot is unlocked
49        let Some(pet_slots) = game_config
50            .pet_slots_for_chapter_level(state.character_state.character.current_chapter_level)
51        else {
52            tracing::error!(
53                "No pet slots for current_chapter_level = {}",
54                state.character_state.character.current_chapter_level
55            );
56            return EventHandleResult::fail(state);
57        };
58
59        if slot_id >= pet_slots {
60            tracing::error!(
61                "Slot_id is too high = {}, current max pet slots = {}",
62                slot_id,
63                pet_slots,
64            );
65            return EventHandleResult::fail(state);
66        }
67
68        state
69            .character_state
70            .equipped_pets
71            .slotted
72            .insert(slot_id as PetSlotId, pet);
73
74        self.refresh_player_entity_in_active_fight(&mut state, current_tick);
75
76        EventHandleResult::ok(state)
77    }
78
79    pub fn handle_unequip_pet(
80        &self,
81        slot_id: PetSlotId,
82        current_tick: u64,
83        mut state: OverlordState,
84    ) -> EventHandleResult<OverlordEvent, OverlordState> {
85        state.character_state.equipped_pets.slotted.remove(&slot_id);
86
87        self.refresh_player_entity_in_active_fight(&mut state, current_tick);
88
89        EventHandleResult::ok(state)
90    }
91
92    pub fn handle_equip_pets(
93        &self,
94        equipped_pets: EquippedPets,
95        current_tick: u64,
96        mut state: OverlordState,
97    ) -> EventHandleResult<OverlordEvent, OverlordState> {
98        state.character_state.equipped_pets = equipped_pets;
99
100        self.refresh_player_entity_in_active_fight(&mut state, current_tick);
101
102        EventHandleResult::ok(state)
103    }
104
105    /// Recalculates the player entity's attributes and pet combat state
106    /// in the active fight to reflect the current equipped pets.
107    fn refresh_player_entity_in_active_fight(&self, state: &mut OverlordState, current_tick: u64) {
108        if state.active_fight.is_none() {
109            return;
110        }
111
112        let game_config = self.game_config.get();
113
114        // Recalculate player entity stats with updated pet equipment
115        let entity_stats = match calculate_player_entity_stats_without_zeroes(
116            &EntityState::Character(&state.character_state),
117            &game_config,
118        ) {
119            Ok(stats) => stats,
120            Err(err) => {
121                tracing::error!("Failed to recalculate player stats after pet change: {err}");
122                return;
123            }
124        };
125
126        // Build new abilities list
127        let mut new_abilities = make_active_abilities_from_equipped(
128            &state.character_state.equipped_abilities,
129            &game_config,
130            true,
131        );
132        sort_active_abilities(&mut new_abilities);
133
134        // Apply all changes to active fight
135        let active_fight = state.active_fight.as_mut().unwrap();
136        if let Some(player) = active_fight
137            .entities
138            .iter_mut()
139            .find(|e| e.id == active_fight.player_id)
140        {
141            let hp_delta = entity_stats.max_hp as i64 - player.max_hp as i64;
142            player.max_hp = entity_stats.max_hp;
143            player.hp = (player.hp as i64 + hp_delta).max(1) as u64;
144            player.attributes = entity_stats.attributes;
145
146            // Remove old ability actions from the queue
147            for ability in &player.abilities {
148                player
149                    .actions_queue
150                    .remove_start_cast_ability_action(ability.ability.template_id);
151            }
152
153            player.abilities = new_abilities;
154
155            // Push new ability actions into the queue.
156            for ability in &player.abilities {
157                let Some(ability_template) =
158                    game_config.ability_template(ability.ability.template_id)
159                else {
160                    tracing::error!(
161                        "Couldn't find template for ability_id = {}",
162                        ability.ability.template_id
163                    );
164                    continue;
165                };
166                player.actions_queue.push(&ActionWithDeadline::core(
167                    self.make_start_cast_ability_action(player.id, ability.ability.template_id),
168                    current_tick + ability_template.cooldown,
169                ));
170            }
171        }
172    }
173}