overlord_event_system/async_handler/
abilities.rs

1use crate::{
2    TICKER_UNIT_DURATION_MS, async_handler::handler::OverlordAsyncEventHandler,
3    entities::make_active_abilities_from_equipped, event::OverlordEvent,
4    game_config_helpers::GameConfigLookup, state::OverlordState,
5};
6
7use essences::{
8    abilities::{Ability, AbilityId, AbilitySlotId, ActiveAbility, EquippedAbilities},
9    effect::EffectId,
10    entity::{ActionWithDeadline, EntityAction, EntityId},
11};
12use event_system::system::EventHandleResult;
13
14impl OverlordAsyncEventHandler {
15    pub fn handle_equip_ability(
16        &self,
17        slot_id: u64,
18        ability_id: AbilityId,
19        current_tick: u64,
20        mut state: OverlordState,
21    ) -> EventHandleResult<OverlordEvent, OverlordState> {
22        let game_config = self.game_config.get();
23
24        if let Some((slot_id, _)) = state
25            .character_state
26            .equipped_abilities
27            .slotted
28            .iter()
29            .find(|(_, a)| a.template_id == ability_id)
30        {
31            tracing::error!(
32                "Ability_id = {ability_id} is already equipped in another slot in state: slot_id={slot_id}"
33            );
34            return EventHandleResult::fail(state);
35        }
36
37        let Some(ability) = state
38            .character_state
39            .all_abilities
40            .iter()
41            .find(|ab| ab.template_id == ability_id)
42            .cloned()
43        else {
44            tracing::error!("No ability with id = {} state exists", ability_id);
45            return EventHandleResult::fail(state);
46        };
47
48        let Some(ability_template) = game_config.ability_template(ability_id) else {
49            tracing::error!("No template found for ability_id = {}", ability_id);
50            return EventHandleResult::fail(state);
51        };
52
53        if !ability_template.fight_ui_visibility.is_player_equippable() {
54            tracing::error!("Trying to equip not visible ability {}", ability_id);
55            return EventHandleResult::fail(state);
56        }
57
58        let Some(ability_slots) = game_config
59            .ability_slots_for_chapter_level(state.character_state.character.current_chapter_level)
60        else {
61            tracing::error!(
62                "No ability slots for current_chapter_level = {}",
63                state.character_state.character.current_chapter_level
64            );
65            return EventHandleResult::fail(state);
66        };
67
68        if slot_id >= ability_slots {
69            tracing::error!(
70                "Slot_id is too high = {}, current max slots = {}",
71                slot_id,
72                ability_slots,
73            );
74            return EventHandleResult::fail(state);
75        }
76
77        self.set_ability_in_slot(&mut state, &ability, slot_id as usize, current_tick);
78
79        EventHandleResult::ok(state)
80    }
81
82    pub fn handle_unequip_ability(
83        &self,
84        slot_id: AbilitySlotId,
85        mut state: OverlordState,
86    ) -> EventHandleResult<OverlordEvent, OverlordState> {
87        if let Some(player) = state.get_active_fight_player_mut()
88            && let Some(pos) = player
89                .abilities
90                .iter()
91                .position(|a| a.slot_id == Some(slot_id))
92        {
93            let active_ability = player.abilities.swap_remove(pos);
94
95            player
96                .actions_queue
97                .remove_start_cast_ability_action(active_ability.ability.template_id);
98        }
99
100        state
101            .character_state
102            .equipped_abilities
103            .slotted
104            .remove(&slot_id);
105
106        EventHandleResult::ok(state)
107    }
108
109    fn set_ability_in_slot(
110        &self,
111        state: &mut OverlordState,
112        ability: &Ability,
113        slot_id: AbilitySlotId,
114        current_tick: u64,
115    ) {
116        let game_config = self.game_config.get();
117        let cooldown = game_config
118            .ability_template(ability.template_id)
119            .map(|t| t.cooldown)
120            .unwrap_or(0);
121
122        if let Some(player) = state.get_active_fight_player_mut() {
123            if let Some(pos) = player
124                .abilities
125                .iter()
126                .position(|a| a.slot_id == Some(slot_id))
127            {
128                let active_ability = player.abilities.swap_remove(pos);
129
130                player
131                    .actions_queue
132                    .remove_start_cast_ability_action(active_ability.ability.template_id);
133            }
134
135            player
136                .abilities
137                .push(self.make_active_ability(ability, Some(slot_id)));
138
139            player.actions_queue.push(&ActionWithDeadline {
140                action: self.make_start_cast_ability_action(player.id, ability.template_id),
141                deadline_tick: current_tick + cooldown,
142            });
143        }
144
145        state
146            .character_state
147            .equipped_abilities
148            .slotted
149            .insert(slot_id, ability.clone());
150    }
151
152    pub fn handle_equip_abilities(
153        &self,
154        equipped_abilities: EquippedAbilities,
155        current_tick: u64,
156        mut state: OverlordState,
157    ) -> EventHandleResult<OverlordEvent, OverlordState> {
158        let game_config = self.game_config.get();
159
160        if let Some(player) = state.get_active_fight_player_mut() {
161            for ability in &player.abilities {
162                player
163                    .actions_queue
164                    .remove_start_cast_ability_action(ability.ability.template_id);
165            }
166
167            player.abilities =
168                make_active_abilities_from_equipped(&equipped_abilities, &game_config, true);
169
170            for ability in &player.abilities {
171                let cooldown = game_config
172                    .ability_template(ability.ability.template_id)
173                    .map(|t| t.cooldown)
174                    .unwrap_or(0);
175                player.actions_queue.push(&ActionWithDeadline {
176                    action: self
177                        .make_start_cast_ability_action(player.id, ability.ability.template_id),
178                    deadline_tick: current_tick + cooldown,
179                });
180            }
181        }
182
183        state.character_state.equipped_abilities = equipped_abilities;
184
185        EventHandleResult::ok(state)
186    }
187
188    pub fn make_cast_effect_action(
189        &self,
190        entity_id: EntityId,
191        effect_id: EffectId,
192    ) -> EntityAction {
193        EntityAction::CastEffect {
194            entity_id,
195            effect_id,
196        }
197    }
198
199    pub fn make_start_cast_ability_action(
200        &self,
201        by_entity_id: EntityId,
202        ability_id: AbilityId,
203    ) -> EntityAction {
204        EntityAction::StartCastAbility {
205            ability_id,
206            by_entity_id,
207            pet_id: None,
208        }
209    }
210
211    fn make_active_ability(
212        &self,
213        ability: &Ability,
214        slot_id: Option<AbilitySlotId>,
215    ) -> ActiveAbility {
216        let game_config = self.game_config.get();
217        let cooldown = game_config
218            .ability_template(ability.template_id)
219            .map(|t| t.cooldown)
220            .unwrap_or(0);
221
222        ActiveAbility {
223            ability: ability.clone(),
224            deadline: Some(
225                ::time::utc_now()
226                    + chrono::TimeDelta::milliseconds(
227                        (cooldown as u128 * TICKER_UNIT_DURATION_MS) as i64,
228                    ),
229            ),
230            slot_id,
231        }
232    }
233}