overlord_event_system/
entities.rs

1use configs::game_config;
2use essences::{
3    abilities, character_state,
4    entity::{self, EntityAction, EntityActionsQueue, EntityAttributes, EntityId, EntityState},
5    fighting,
6};
7use event_system::event::EventPluginized;
8
9use crate::attributes::{
10    AttributeDeltas, EntityStats, calculate_entity_stats,
11    calculate_player_entity_stats_without_zeroes,
12};
13use crate::game_config_helpers::GameConfigLookup;
14
15use super::{TICKER_UNIT_DURATION_MS, event::OverlordEvent, state::OverlordState};
16
17fn make_ability_deadline(cooldown: u64) -> chrono::DateTime<chrono::Utc> {
18    ::time::utc_now()
19        + chrono::TimeDelta::milliseconds((cooldown as u128 * TICKER_UNIT_DURATION_MS) as i64)
20}
21
22pub fn sort_active_abilities(abilities: &mut [abilities::ActiveAbility]) {
23    abilities.sort_by(|a, b| {
24        a.slot_id
25            .cmp(&b.slot_id)
26            .then(a.ability.template_id.cmp(&b.ability.template_id))
27    });
28}
29
30pub fn make_active_abilities_from_equipped(
31    equipped_abilities: &abilities::EquippedAbilities,
32    game_config: &game_config::GameConfig,
33    begin_in_cooldown: bool,
34) -> Vec<abilities::ActiveAbility> {
35    let deadline_for = |template_id| {
36        if begin_in_cooldown {
37            let cooldown = game_config
38                .ability_template(template_id)
39                .map(|t| t.cooldown)
40                .unwrap_or(0);
41            Some(make_ability_deadline(cooldown))
42        } else {
43            None
44        }
45    };
46
47    let mut abilities: Vec<abilities::ActiveAbility> =
48        equipped_abilities
49            .unslotted
50            .iter()
51            .map(|a| abilities::ActiveAbility {
52                deadline: deadline_for(a.template_id),
53                ability: a.clone(),
54                slot_id: None,
55            })
56            .chain(equipped_abilities.slotted.iter().map(|(&slot_id, a)| {
57                abilities::ActiveAbility {
58                    deadline: deadline_for(a.template_id),
59                    ability: a.clone(),
60                    slot_id: Some(slot_id),
61                }
62            }))
63            .collect();
64
65    sort_active_abilities(&mut abilities);
66
67    abilities
68}
69
70pub fn create_player_entity(
71    character_state: &character_state::CharacterState,
72    entity_id: uuid::Uuid,
73    game_config: &game_config::GameConfig,
74) -> anyhow::Result<entity::Entity> {
75    let entity_stats = calculate_player_entity_stats_without_zeroes(
76        &EntityState::Character(character_state),
77        game_config,
78    )?;
79
80    let mut entity_abilities = make_active_abilities_from_equipped(
81        &character_state.equipped_abilities,
82        game_config,
83        false,
84    );
85
86    // Add leader pet's active ability if present
87    if let Some(leader_pet) = character_state.equipped_pets.leader()
88        && let Some(ability_template) = leader_pet
89            .active_ability_id
90            .and_then(|id| game_config.ability_template(id))
91    {
92        let pet_ability = abilities::Ability::from_template(ability_template, None, None);
93        entity_abilities.push(abilities::ActiveAbility {
94            ability: pet_ability,
95            deadline: None,
96            slot_id: None,
97        });
98    }
99
100    Ok(entity::Entity {
101        id: entity_id,
102        max_hp: entity_stats.max_hp,
103        hp: entity_stats.max_hp,
104        abilities: entity_abilities,
105        actions_queue: EntityActionsQueue::new(entity_id),
106        attributes: entity_stats.attributes,
107        effect_ids: Default::default(),
108        coordinates: game_config.fight_settings.player_start_position.clone(),
109        move_target: None,
110        width: 1, // TODO idk
111        rewards: None,
112        class_id: Some(character_state.character.class),
113        team: fighting::EntityTeam::Ally,
114        has_big_hp_bar: false,
115        entity_template_id: None,
116    })
117}
118
119pub fn create_party_entity(
120    character_state: &character_state::CharacterState,
121    entity_id: uuid::Uuid,
122    game_config: &game_config::GameConfig,
123) -> anyhow::Result<entity::Entity> {
124    let mut entity = create_player_entity(character_state, entity_id, game_config)?;
125    entity.coordinates = game_config.fight_settings.party_start_position.clone();
126    Ok(entity)
127}
128
129pub fn create_pve_entity(
130    entity_id: uuid::Uuid,
131    fight_entity: &fighting::FightEntity,
132    game_config: &game_config::GameConfig,
133    entity_attributes: Option<EntityAttributes>,
134) -> anyhow::Result<entity::Entity> {
135    let entity_template_id = match fight_entity.entity_type {
136        fighting::EntityType::PVEEntity { entity_template_id } => entity_template_id,
137        fighting::EntityType::PVPEntity => {
138            anyhow::bail!(
139                "Wanted to create a PVE entity, but got a PVP entity = {:?}",
140                fight_entity
141            );
142        }
143    };
144
145    let Some(entity) = game_config.entity_template(entity_template_id).cloned() else {
146        anyhow::bail!(
147            "Failed to find entity_template with id={}",
148            entity_template_id
149        )
150    };
151
152    let entity_abilities = entity
153        .ability_ids
154        .iter()
155        .filter_map(|&ability_id| {
156            let Some(entity_ability_template) = game_config.ability_template(ability_id) else {
157                tracing::error!("Failed to get ability with ability_id={}", ability_id);
158                return None;
159            };
160
161            let enemy_ability = abilities::Ability::from_template(
162                entity_ability_template,
163                /*level=*/ None,
164                /*shards_amount=*/ None,
165            );
166
167            Some(abilities::ActiveAbility {
168                ability: enemy_ability,
169                deadline: None,
170                slot_id: None,
171            })
172        })
173        .collect();
174
175    let entity_stats = if let Some(attributes) = entity_attributes {
176        let mut max_hp = 0;
177        let Some(config_max_hp_attribute) =
178            game_config.attribute(game_config.game_settings.hp_attribute_id)
179        else {
180            anyhow::bail!(
181                "Couldn't find max_hp attribute with id = {}",
182                game_config.game_settings.hp_attribute_id
183            );
184        };
185
186        for (code, value) in &attributes.0 {
187            if *code == config_max_hp_attribute.code {
188                max_hp += *value as u64
189            }
190        }
191        EntityStats { attributes, max_hp }
192    } else {
193        let mut attributes_deltas = AttributeDeltas::new();
194        for attribute in entity.attributes {
195            *attributes_deltas.entry(attribute.attribute_id).or_insert(0) += attribute.value as i64;
196        }
197
198        calculate_entity_stats(game_config, attributes_deltas)?
199    };
200
201    Ok(entity::Entity {
202        id: entity_id,
203        max_hp: entity_stats.max_hp,
204        hp: entity_stats.max_hp,
205        abilities: entity_abilities,
206        actions_queue: EntityActionsQueue::new(entity_id),
207        attributes: entity_stats.attributes,
208        effect_ids: Default::default(),
209        coordinates: fight_entity.position.clone(),
210        move_target: None,
211        width: entity.width,
212        rewards: Some(entity.rewards),
213        class_id: None,
214        team: fight_entity.team.clone(),
215        has_big_hp_bar: fight_entity.has_big_hp_bar,
216        entity_template_id: Some(entity_template_id),
217    })
218}
219
220pub fn create_pvp_entity(
221    entity_state: &EntityState,
222    fight_entity: &fighting::FightEntity,
223    game_config: &game_config::GameConfig,
224) -> anyhow::Result<entity::Entity> {
225    if fight_entity.entity_type != fighting::EntityType::PVPEntity {
226        anyhow::bail!(
227            "Wanted to create a PVP entity, but got a PVE entity = {:?}",
228            fight_entity
229        );
230    };
231
232    let entity_stats = calculate_player_entity_stats_without_zeroes(entity_state, game_config)?;
233
234    Ok(entity::Entity {
235        id: entity_state.id(),
236        max_hp: entity_stats.max_hp,
237        hp: entity_stats.max_hp,
238        abilities: make_active_abilities_from_equipped(
239            entity_state.equipped_abilities(),
240            game_config,
241            false,
242        ),
243        actions_queue: EntityActionsQueue::new(entity_state.id()),
244        attributes: entity_stats.attributes,
245        effect_ids: Default::default(),
246        coordinates: fight_entity.position.clone(),
247        move_target: None,
248        width: 1, // TODO idk
249        rewards: None,
250        class_id: Some(entity_state.class()),
251        team: fight_entity.team.clone(),
252        has_big_hp_bar: fight_entity.has_big_hp_bar,
253        entity_template_id: None,
254    })
255}
256
257pub fn event_from_entity_action(
258    action: EntityAction,
259    entity_id: EntityId,
260) -> EventPluginized<OverlordEvent, OverlordState> {
261    match action {
262        EntityAction::CastEffect {
263            entity_id,
264            effect_id,
265        } => EventPluginized::now(OverlordEvent::CastEffect {
266            entity_id,
267            effect_id,
268        }),
269        EntityAction::CastAbility {
270            ability_id,
271            target_entity_id,
272        } => EventPluginized::now(OverlordEvent::CastAbility {
273            by_entity_id: entity_id,
274            to_entity_id: target_entity_id,
275            ability_id,
276        }),
277        EntityAction::CastBasicAbility {
278            ability_id,
279            target_entity_id,
280        } => EventPluginized::now(OverlordEvent::CastAbility {
281            by_entity_id: entity_id,
282            to_entity_id: target_entity_id,
283            ability_id,
284        }),
285        EntityAction::StartCastAbility {
286            ability_id,
287            by_entity_id,
288            pet_id,
289        } => EventPluginized::now(OverlordEvent::StartCastAbility {
290            by_entity_id,
291            ability_id,
292            pet_id,
293        }),
294    }
295}