overlord_event_system/logic/
abilities.rs

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