essences/
entity.rs

1use std::collections::{BTreeMap, HashMap};
2
3use crate::abilities::{Ability, AbilityId, ActiveAbility, EquippedAbilities};
4use crate::character_state::CharacterState;
5use crate::class::ClassId;
6use crate::effect::EffectId;
7use crate::fighting::EntityTeam;
8use crate::game::{EnemyReward, EntityTemplateId};
9use crate::items::Item;
10use crate::opponents::OpponentState;
11use crate::pets::{EquippedPets, PetId};
12
13use crate::prelude::*;
14use strum::{EnumIter, IntoEnumIterator};
15
16#[declare]
17pub type EntityId = Uuid;
18
19#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
20pub struct EntityAttributes(pub BTreeMap<String, i64>);
21
22impl EntityAttributes {
23    pub fn add(&mut self, key: &str, delta: i64) {
24        let value = self
25            .0
26            .entry(key.to_owned())
27            .and_modify(|x| *x += delta)
28            .or_insert(delta);
29        if *value == 0 {
30            self.0.remove(key);
31        }
32    }
33
34    pub fn set(&mut self, key: &str, value: i64) {
35        let value = self
36            .0
37            .entry(key.to_owned())
38            .and_modify(|x| *x = value)
39            .or_insert(value);
40        if *value == 0 {
41            self.0.remove(key);
42        }
43    }
44
45    pub fn remove_zeroes(&mut self) {
46        self.0.retain(|_, v| *v != 0);
47    }
48}
49
50impl CustomType for EntityAttributes {
51    fn build(mut builder: TypeBuilder<Self>) {
52        builder
53            .with_name("EntityAttributes")
54            .with_indexer_get(|ea: &mut EntityAttributes, idx: String| -> rhai::Dynamic {
55                match ea.0.get(&idx) {
56                    Some(value) => (*value).into(),
57                    None => ().into(),
58                }
59            })
60            .with_fn("add", EntityAttributes::add)
61            .with_fn("EntityAttributes", Self::default);
62    }
63}
64
65#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
66pub struct Coordinates {
67    #[schemars(title = "Координата по х")]
68    pub x: i64,
69    #[schemars(title = "Координата по у")]
70    pub y: i64,
71}
72
73impl CustomType for Coordinates {
74    fn build(mut builder: TypeBuilder<Self>) {
75        builder
76            .with_fn("Coordinates", |x: i64, y: i64| Self { x, y })
77            .with_get("x", |c: &mut Self| c.x)
78            .with_get("y", |c: &mut Self| c.y);
79    }
80}
81
82#[derive(Clone, Default, Debug, Copy, Serialize, Hash, Deserialize, PartialEq, Eq, EnumIter)]
83pub enum ActionPriority {
84    #[default]
85    First,
86    Second,
87    Third,
88    Fourth,
89}
90
91// This is a copy of CustomEventData from OES/script, because it cant be imported here and also needs to be defined in OES
92#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
93pub struct EssencesCustomEventData(pub BTreeMap<String, i64>);
94
95#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
96pub enum EntityAction {
97    CastEffect {
98        entity_id: Uuid,
99        effect_id: Uuid,
100    },
101    CastAbility {
102        ability_id: AbilityId,
103        target_entity_id: EntityId,
104    },
105    CastBasicAbility {
106        ability_id: AbilityId,
107        target_entity_id: EntityId,
108    },
109    StartCastAbility {
110        ability_id: AbilityId,
111        by_entity_id: EntityId,
112        pet_id: Option<PetId>,
113    },
114}
115
116impl EntityAction {
117    fn get_action_priority(action: &EntityAction) -> ActionPriority {
118        match action {
119            EntityAction::CastEffect { .. } => ActionPriority::First,
120            EntityAction::CastAbility { .. } => ActionPriority::Second,
121            EntityAction::CastBasicAbility { .. } => ActionPriority::Third,
122            EntityAction::StartCastAbility { .. } => ActionPriority::Fourth,
123        }
124    }
125
126    fn get_actions_priorities(actions: &[Self]) -> Vec<ActionPriority> {
127        actions.iter().map(Self::get_action_priority).collect()
128    }
129
130    fn get_casting_spell_priorities() -> Vec<ActionPriority> {
131        Self::get_actions_priorities(&[
132            Self::CastAbility {
133                ability_id: Uuid::nil(),
134                target_entity_id: Uuid::nil(),
135            },
136            Self::CastBasicAbility {
137                ability_id: Uuid::nil(),
138                target_entity_id: Uuid::nil(),
139            },
140        ])
141    }
142
143    pub fn get_cast_ability_priority() -> ActionPriority {
144        Self::get_action_priority(&Self::CastAbility {
145            ability_id: Uuid::nil(),
146            target_entity_id: Uuid::nil(),
147        })
148    }
149
150    pub fn get_cast_basic_ability_priority() -> ActionPriority {
151        Self::get_action_priority(&Self::CastBasicAbility {
152            ability_id: Uuid::nil(),
153            target_entity_id: Uuid::nil(),
154        })
155    }
156
157    pub fn get_starting_cast_priority() -> ActionPriority {
158        Self::get_action_priority(&Self::StartCastAbility {
159            ability_id: Uuid::nil(),
160            by_entity_id: Uuid::nil(),
161            pet_id: None,
162        })
163    }
164
165    pub fn get_cast_effect_priority() -> ActionPriority {
166        Self::get_action_priority(&Self::CastEffect {
167            entity_id: Uuid::nil(),
168            effect_id: Uuid::nil(),
169        })
170    }
171}
172
173#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
174pub struct ActionWithDeadline {
175    pub action: EntityAction,
176    pub deadline_tick: u64,
177}
178
179#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
180pub struct EntityActionsQueue {
181    action_queues: HashMap<ActionPriority, Vec<ActionWithDeadline>>,
182    entity_id: EntityId,
183}
184
185impl EntityActionsQueue {
186    pub fn new(entity_id: EntityId) -> Self {
187        Self {
188            action_queues: HashMap::new(),
189            entity_id,
190        }
191    }
192
193    // Push signle action
194    pub fn push(&mut self, action_with_deadline: &ActionWithDeadline) {
195        let priority = EntityAction::get_action_priority(&action_with_deadline.action);
196
197        self.action_queues
198            .entry(priority)
199            .or_default()
200            .push(ActionWithDeadline {
201                action: action_with_deadline.action.clone(),
202                deadline_tick: action_with_deadline.deadline_tick,
203            });
204    }
205
206    // Add StartCastAbilityAction, depending on what action was triggered
207    fn add_start_cast_ability(
208        &mut self,
209        action_with_deadline: Option<&ActionWithDeadline>,
210        current_tick: u64,
211        ability_id: AbilityId,
212        ability_cooldown: u64,
213    ) {
214        if let Some(action_with_deadline) = action_with_deadline {
215            match action_with_deadline.action {
216                EntityAction::CastAbility { .. } => {
217                    if ability_cooldown != 0 {
218                        self.push(&ActionWithDeadline {
219                            action: EntityAction::StartCastAbility {
220                                ability_id,
221                                by_entity_id: self.entity_id,
222                                pet_id: None,
223                            },
224                            deadline_tick: current_tick + ability_cooldown,
225                        })
226                    }
227                }
228                EntityAction::CastBasicAbility { .. } => {
229                    if ability_cooldown != 0 {
230                        self.push(&ActionWithDeadline {
231                            action: EntityAction::StartCastAbility {
232                                ability_id,
233                                by_entity_id: self.entity_id,
234                                pet_id: None,
235                            },
236                            deadline_tick: current_tick + ability_cooldown,
237                        })
238                    }
239                }
240                EntityAction::StartCastAbility { .. } => {}
241                EntityAction::CastEffect { .. } => {}
242            }
243        } else {
244            self.push(&ActionWithDeadline {
245                action: EntityAction::StartCastAbility {
246                    ability_id,
247                    by_entity_id: self.entity_id,
248                    pet_id: None,
249                },
250                deadline_tick: current_tick,
251            });
252        }
253    }
254
255    // Append multiple actions(casts or move) + add start_cast_ability, depending on action
256    pub fn append_start_cast_ability_result_actions(
257        &mut self,
258        actions_with_deadlines: &Vec<ActionWithDeadline>,
259        current_tick: u64,
260        ability_id: AbilityId,
261        ability_cooldown: u64,
262    ) {
263        for action_with_deadline in actions_with_deadlines {
264            self.push(action_with_deadline);
265        }
266
267        // TODO
268        // If move -> actions_with_deadlines should be empty -> Add StartCastAbility without increased deadline. If CastAbility -> increase deadline by ability cooldown.
269        self.add_start_cast_ability(
270            actions_with_deadlines.first(),
271            current_tick,
272            ability_id,
273            ability_cooldown,
274        );
275    }
276
277    fn is_casting_spell(&self) -> bool {
278        for priority in EntityAction::get_casting_spell_priorities() {
279            if let Some(queue) = self.action_queues.get(&priority)
280                && !queue.is_empty()
281            {
282                return true;
283            }
284        }
285        false
286    }
287
288    fn check_action_is_available(&self, action_priority: &ActionPriority) -> bool {
289        match action_priority {
290            ActionPriority::First => true,
291            ActionPriority::Second => true,
292            ActionPriority::Third => true,
293            ActionPriority::Fourth => !self.is_casting_spell(),
294        }
295    }
296
297    pub fn pop(&mut self, current_tick: u64) -> Option<EntityAction> {
298        for priority in ActionPriority::iter() {
299            if !self.check_action_is_available(&priority) {
300                continue;
301            }
302
303            if let Some(queue) = self.action_queues.get_mut(&priority)
304                && let Some((idx, action_with_deadline)) = queue
305                    .iter()
306                    .enumerate()
307                    .min_by_key(|(_, action_with_deadline)| action_with_deadline.deadline_tick)
308                && action_with_deadline.deadline_tick <= current_tick
309            {
310                let action = queue.remove(idx);
311                return Some(action.action);
312            }
313        }
314
315        None
316    }
317
318    pub fn remove_start_cast_ability_action(&mut self, ability_id_to_remove: AbilityId) {
319        if let Some(queue) = self.action_queues.get_mut(&EntityAction::get_starting_cast_priority()) && let Some(pos) = queue.iter().position(|action_with_deadline| {
320            matches!(
321                action_with_deadline.action,
322                EntityAction::StartCastAbility { ability_id, .. } if ability_id == ability_id_to_remove
323            )
324        }) {
325            queue.remove(pos);
326        }
327    }
328
329    pub fn remove_cast_effect_action(&mut self, effect_id_to_remove: EffectId) {
330        if let Some(queue) = self
331            .action_queues
332            .get_mut(&EntityAction::get_cast_effect_priority())
333            && let Some(pos) = queue.iter().position(|action_with_deadline| {
334                matches!(
335                    action_with_deadline.action,
336                    EntityAction::CastEffect { effect_id, .. } if effect_id == effect_id_to_remove
337                )
338            })
339        {
340            queue.remove(pos);
341        }
342    }
343
344    pub fn get_closest_start_cast_action_deadline(&self) -> Option<u64> {
345        if let Some(queue) = self
346            .action_queues
347            .get(&EntityAction::get_starting_cast_priority())
348        {
349            return queue.iter().map(|action| action.deadline_tick).min();
350        }
351
352        None
353    }
354
355    pub fn view(&self) -> HashMap<ActionPriority, Vec<ActionWithDeadline>> {
356        self.action_queues.clone()
357    }
358}
359
360#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
361#[tsify(from_wasm_abi, into_wasm_abi)]
362pub struct Entity {
363    pub id: EntityId,
364    pub max_hp: u64,
365    pub hp: u64,
366    pub abilities: Vec<ActiveAbility>,
367    #[schemars(skip)]
368    pub actions_queue: EntityActionsQueue,
369    pub attributes: EntityAttributes,
370    pub effect_ids: Vec<EffectId>,
371    pub coordinates: Coordinates,
372    pub being_moved: bool,
373    pub width: i8, // not ENUM because JsonSchema and Tsify can't be friends // TODO I am not sure anymore
374    pub rewards: Option<Vec<EnemyReward>>,
375    pub class_id: Option<ClassId>,
376    pub team: EntityTeam,
377    pub has_big_hp_bar: bool,
378    pub entity_template_id: Option<EntityTemplateId>,
379}
380
381impl CustomType for Entity {
382    fn build(mut builder: TypeBuilder<Self>) {
383        builder
384            .with_name("Entity")
385            .with_get("id", |ent: &mut Self| ent.id)
386            .with_get("max_hp", |ent: &mut Self| ent.max_hp)
387            .with_get("hp", |ent: &mut Self| ent.hp)
388            .with_get("abilities", |ent: &mut Self| {
389                ent.abilities
390                    .iter()
391                    .map(|x| x.ability.clone())
392                    .collect::<Vec<Ability>>()
393            })
394            .with_get("attributes", |ent: &mut Self| ent.attributes.clone())
395            .with_get("effect_ids", |ent: &mut Self| ent.effect_ids.clone())
396            .with_get("coordinates", |ent: &mut Self| ent.coordinates.clone())
397            .with_get("width", |ent: &mut Self| ent.width)
398            .with_get("being_moved", |ent: &mut Self| ent.being_moved)
399            .with_get("rewards", |ent: &mut Self| ent.rewards.clone())
400            .with_get("team", |ent: &mut Self| ent.team.clone())
401            .with_get("class_id", |ent: &mut Self| {
402                ent.class_id.map(Dynamic::from).unwrap_or(Dynamic::UNIT)
403            })
404            .with_get("entity_template_id", |ent: &mut Self| {
405                ent.entity_template_id
406                    .map(Dynamic::from)
407                    .unwrap_or(Dynamic::UNIT)
408            });
409    }
410}
411
412#[derive(Debug, Clone, Eq, PartialEq)]
413pub enum EntityState<'a> {
414    Character(&'a CharacterState),
415    Opponent(&'a OpponentState),
416}
417
418impl<'a> EntityState<'a> {
419    pub fn id(&self) -> uuid::Uuid {
420        match self {
421            EntityState::Character(character_state) => character_state.character.id,
422            EntityState::Opponent(opponent_state) => opponent_state.id(),
423        }
424    }
425
426    pub fn level(&self) -> i64 {
427        match self {
428            EntityState::Character(character_state) => character_state.character.character_level,
429            EntityState::Opponent(opponent_state) => opponent_state.level(),
430        }
431    }
432
433    pub fn inventory(&self) -> &Vec<Item> {
434        match self {
435            EntityState::Character(character_state) => &character_state.inventory,
436            EntityState::Opponent(opponent_state) => opponent_state.inventory(),
437        }
438    }
439
440    pub fn class(&self) -> ClassId {
441        match self {
442            EntityState::Character(character_state) => character_state.character.class,
443            EntityState::Opponent(opponent_state) => opponent_state.class(),
444        }
445    }
446
447    pub fn equipped_abilities(&self) -> &EquippedAbilities {
448        match self {
449            EntityState::Character(character_state) => &character_state.equipped_abilities,
450            EntityState::Opponent(opponent_state) => opponent_state.equipped_abilities(),
451        }
452    }
453
454    pub fn equipped_pets(&self) -> Option<&EquippedPets> {
455        match self {
456            EntityState::Character(character_state) => Some(&character_state.equipped_pets),
457            EntityState::Opponent(opponent_state) => opponent_state.equipped_pets(),
458        }
459    }
460}