overlord_event_system/logic/
pets.rs1use 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 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 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 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 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 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 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 let (pet_combat_state, leader_pet_template_id) = self.build_pet_combat_state(state);
173
174 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 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 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}