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#[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 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 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 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 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, 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}