overlord_event_system/async_handler/
pets.rs1use crate::{
2 async_handler::handler::OverlordAsyncEventHandler,
3 attributes::calculate_player_entity_stats_without_zeroes,
4 entities::{make_active_abilities_from_equipped, sort_active_abilities},
5 event::OverlordEvent,
6 game_config_helpers::GameConfigLookup,
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 OverlordAsyncEventHandler {
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 &self.script_runner,
143 ) {
144 Ok(stats) => stats,
145 Err(err) => {
146 tracing::error!("Failed to recalculate player stats after pet change: {err}");
147 return;
148 }
149 };
150
151 let mut new_abilities = make_active_abilities_from_equipped(
153 &state.character_state.equipped_abilities,
154 &game_config,
155 true,
156 );
157 if let Some(leader_pet) = state.character_state.equipped_pets.leader()
158 && let Some(ability_template) = leader_pet
159 .active_ability_id
160 .and_then(|id| game_config.ability_template(id))
161 {
162 let pet_ability =
163 essences::abilities::Ability::from_template(ability_template, None, None);
164 new_abilities.push(essences::abilities::ActiveAbility {
165 ability: pet_ability,
166 deadline: None,
167 slot_id: None,
168 });
169 }
170 sort_active_abilities(&mut new_abilities);
171
172 let (pet_combat_state, leader_pet_template_id) = self.build_pet_combat_state(state);
174
175 let active_fight = state.active_fight.as_mut().unwrap();
177 if let Some(player) = active_fight
178 .entities
179 .iter_mut()
180 .find(|e| e.id == active_fight.player_id)
181 {
182 let hp_delta = entity_stats.max_hp as i64 - player.max_hp as i64;
183 player.max_hp = entity_stats.max_hp;
184 player.hp = (player.hp as i64 + hp_delta).max(1) as u64;
185 player.attributes = entity_stats.attributes;
186
187 for ability in &player.abilities {
189 player
190 .actions_queue
191 .remove_start_cast_ability_action(ability.ability.template_id);
192 }
193
194 player.abilities = new_abilities;
195
196 for ability in &player.abilities {
198 let Some(ability_template) =
199 game_config.ability_template(ability.ability.template_id)
200 else {
201 tracing::error!(
202 "Couldn't find template for ability_id = {}",
203 ability.ability.template_id
204 );
205 continue;
206 };
207 player.actions_queue.push(&ActionWithDeadline {
208 action: self
209 .make_start_cast_ability_action(player.id, ability.ability.template_id),
210 deadline_tick: current_tick + ability_template.cooldown,
211 });
212 }
213 }
214
215 active_fight.pet_combat_state = pet_combat_state;
216 active_fight.leader_pet_template_id = leader_pet_template_id;
217 }
218}