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::fighting::ActivePetAbility;
12use essences::pets::{EquippedPets, PetId, PetSlotId};
13use event_system::system::EventHandleResult;
14
15impl OverlordLogic {
16    pub fn handle_equip_pet(
17        &self,
18        slot_id: u64,
19        pet_id: PetId,
20        current_tick: u64,
21        mut state: OverlordState,
22    ) -> EventHandleResult<OverlordEvent, OverlordState> {
23        let game_config = self.game_config.get();
24
25        // Check that the pet is not already equipped in another slot
26        if let Some((existing_slot, _)) = state
27            .character_state
28            .equipped_pets
29            .slotted
30            .iter()
31            .find(|(_, p)| p.template_id == pet_id)
32        {
33            tracing::error!("Pet_id = {pet_id} is already equipped in slot_id={existing_slot}");
34            return EventHandleResult::fail(state);
35        }
36
37        // Find the pet in all_pets
38        let Some(pet) = state
39            .character_state
40            .all_pets
41            .iter()
42            .find(|p| p.template_id == pet_id)
43            .cloned()
44        else {
45            tracing::error!("No pet with id = {} in state", pet_id);
46            return EventHandleResult::fail(state);
47        };
48
49        // Check that the slot is unlocked
50        let Some(pet_slots) = game_config
51            .pet_slots_for_chapter_level(state.character_state.character.current_chapter_level)
52        else {
53            tracing::error!(
54                "No pet slots for current_chapter_level = {}",
55                state.character_state.character.current_chapter_level
56            );
57            return EventHandleResult::fail(state);
58        };
59
60        if slot_id >= pet_slots {
61            tracing::error!(
62                "Slot_id is too high = {}, current max pet slots = {}",
63                slot_id,
64                pet_slots,
65            );
66            return EventHandleResult::fail(state);
67        }
68
69        state
70            .character_state
71            .equipped_pets
72            .slotted
73            .insert(slot_id as PetSlotId, pet);
74
75        self.refresh_player_entity_in_active_fight(&mut state, current_tick);
76
77        EventHandleResult::ok(state)
78    }
79
80    pub fn build_pet_combat_state(
81        &self,
82        state: &OverlordState,
83    ) -> (Option<ActivePetAbility>, Option<PetId>) {
84        let leader = state.character_state.equipped_pets.leader();
85        match leader {
86            Some(pet) => {
87                let game_config = self.game_config.get();
88                let template = game_config.pet_template(pet.template_id);
89                let pet_combat_state = template.and_then(|t| {
90                    t.active_ability_id.map(|ability_id| ActivePetAbility {
91                        pet_template_id: pet.template_id,
92                        ability_id,
93                        charge: 0,
94                        max_charge: t.max_charge,
95                    })
96                });
97                (pet_combat_state, Some(pet.template_id))
98            }
99            None => (None, None),
100        }
101    }
102
103    pub fn handle_unequip_pet(
104        &self,
105        slot_id: PetSlotId,
106        current_tick: u64,
107        mut state: OverlordState,
108    ) -> EventHandleResult<OverlordEvent, OverlordState> {
109        state.character_state.equipped_pets.slotted.remove(&slot_id);
110
111        self.refresh_player_entity_in_active_fight(&mut state, current_tick);
112
113        EventHandleResult::ok(state)
114    }
115
116    pub fn handle_equip_pets(
117        &self,
118        equipped_pets: EquippedPets,
119        current_tick: u64,
120        mut state: OverlordState,
121    ) -> EventHandleResult<OverlordEvent, OverlordState> {
122        state.character_state.equipped_pets = equipped_pets;
123
124        self.refresh_player_entity_in_active_fight(&mut state, current_tick);
125
126        EventHandleResult::ok(state)
127    }
128
129    /// Recalculates the player entity's attributes and pet combat state
130    /// in the active fight to reflect the current equipped pets.
131    fn refresh_player_entity_in_active_fight(&self, state: &mut OverlordState, current_tick: u64) {
132        if state.active_fight.is_none() {
133            return;
134        }
135
136        let game_config = self.game_config.get();
137
138        // Recalculate player entity stats with updated pet equipment
139        let entity_stats = match calculate_player_entity_stats_without_zeroes(
140            &EntityState::Character(&state.character_state),
141            &game_config,
142        ) {
143            Ok(stats) => stats,
144            Err(err) => {
145                tracing::error!("Failed to recalculate player stats after pet change: {err}");
146                return;
147            }
148        };
149
150        // Build new abilities list
151        let mut new_abilities = make_active_abilities_from_equipped(
152            &state.character_state.equipped_abilities,
153            &game_config,
154            true,
155        );
156        if let Some(leader_pet) = state.character_state.equipped_pets.leader()
157            && let Some(ability_template) = leader_pet
158                .active_ability_id
159                .and_then(|id| game_config.ability_template(id))
160        {
161            let pet_ability =
162                essences::abilities::Ability::from_template(ability_template, None, None);
163            new_abilities.push(essences::abilities::ActiveAbility {
164                ability: pet_ability,
165                deadline: None,
166                slot_id: None,
167            });
168        }
169        sort_active_abilities(&mut new_abilities);
170
171        // Rebuild pet combat state
172        let (pet_combat_state, leader_pet_template_id) = self.build_pet_combat_state(state);
173
174        // Apply all changes to active fight
175        let active_fight = state.active_fight.as_mut().unwrap();
176        if let Some(player) = active_fight
177            .entities
178            .iter_mut()
179            .find(|e| e.id == active_fight.player_id)
180        {
181            let hp_delta = entity_stats.max_hp as i64 - player.max_hp as i64;
182            player.max_hp = entity_stats.max_hp;
183            player.hp = (player.hp as i64 + hp_delta).max(1) as u64;
184            player.attributes = entity_stats.attributes;
185
186            // Remove old ability actions from the queue
187            for ability in &player.abilities {
188                player
189                    .actions_queue
190                    .remove_start_cast_ability_action(ability.ability.template_id);
191            }
192
193            player.abilities = new_abilities;
194
195            // Push new ability actions into the queue
196            for ability in &player.abilities {
197                let Some(ability_template) =
198                    game_config.ability_template(ability.ability.template_id)
199                else {
200                    tracing::error!(
201                        "Couldn't find template for ability_id = {}",
202                        ability.ability.template_id
203                    );
204                    continue;
205                };
206                player.actions_queue.push(&ActionWithDeadline {
207                    action: self
208                        .make_start_cast_ability_action(player.id, ability.ability.template_id),
209                    deadline_tick: current_tick + ability_template.cooldown,
210                });
211            }
212        }
213
214        active_fight.pet_combat_state = pet_combat_state;
215        active_fight.leader_pet_template_id = leader_pet_template_id;
216    }
217}