essences/
abilities.rs

1use crate::prelude::*;
2
3use enum_iterator::Sequence;
4use strum_macros::{Display, EnumIter, EnumString};
5
6use std::collections::{BTreeMap, HashMap};
7
8#[declare]
9pub type AbilityId = Uuid;
10
11#[declare]
12pub type AbilitySlotId = usize;
13
14#[declare]
15pub type AbilityRarityId = Uuid;
16
17#[derive(
18    Debug,
19    Clone,
20    Copy,
21    EnumString,
22    Sequence,
23    Display,
24    Deserialize,
25    Serialize,
26    Hash,
27    Eq,
28    PartialEq,
29    EnumIter,
30    Default,
31    JsonSchema,
32    Tsify,
33)]
34#[tsify(namespace)]
35pub enum AbilityFightUiVisibility {
36    #[default]
37    Slotted,
38    Class,
39    Hidden,
40}
41
42impl AbilityFightUiVisibility {
43    pub fn is_player_equippable(&self) -> bool {
44        match self {
45            AbilityFightUiVisibility::Slotted => true,
46            AbilityFightUiVisibility::Class => false,
47            AbilityFightUiVisibility::Hidden => false,
48        }
49    }
50
51    pub fn is_slotted(&self) -> bool {
52        match self {
53            AbilityFightUiVisibility::Slotted => true,
54            AbilityFightUiVisibility::Class => false,
55            AbilityFightUiVisibility::Hidden => false,
56        }
57    }
58}
59
60#[derive(
61    Debug,
62    Clone,
63    Copy,
64    EnumString,
65    Sequence,
66    Display,
67    Deserialize,
68    Serialize,
69    Hash,
70    Eq,
71    PartialEq,
72    EnumIter,
73    Default,
74    JsonSchema,
75    Tsify,
76)]
77#[tsify(namespace)]
78pub enum AbilityCastType {
79    #[default]
80    NoAnimation,
81    Basic,
82    Spell,
83}
84
85/// Тип крутки гачи способностей.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Tsify)]
87#[tsify(namespace)]
88pub enum AbilityCaseRollType {
89    Small,
90    Big,
91}
92
93#[derive(
94    Default,
95    PartialEq,
96    Eq,
97    Hash,
98    Debug,
99    Clone,
100    Serialize,
101    Deserialize,
102    Tsify,
103    JsonSchema,
104    CustomType,
105)]
106pub struct AbilityRarity {
107    #[schemars(schema_with = "id_schema")]
108    pub id: AbilityRarityId,
109
110    #[schemars(title = "Название редкости")]
111    pub name: i18n::I18nString,
112
113    #[schemars(title = "Сортировка")]
114    pub order: u64,
115
116    #[schemars(title = "Цвет редкости", schema_with = "color_schema")]
117    pub color: String,
118
119    #[schemars(title = "Цвет заднего фона редкости", schema_with = "color_schema")]
120    pub bg_color: String,
121
122    #[schemars(title = "Иконка рамки", schema_with = "webp_url_schema")]
123    pub icon_url: String,
124
125    #[schemars(title = "Рамка", schema_with = "asset_ability_rarity_icon_schema")]
126    pub icon_path: String,
127
128    #[schemars(
129        title = "Квадратная рамка",
130        schema_with = "asset_ability_rarity_square_icon_schema"
131    )]
132    pub square_icon_path: String,
133}
134
135#[derive(
136    Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema, CustomType, Default,
137)]
138#[tsify(from_wasm_abi)]
139pub struct AbilityTemplate {
140    #[schemars(schema_with = "id_schema")]
141    pub id: AbilityId,
142
143    #[schemars(title = "Видимость в UI боя")]
144    pub is_fight_ui_visible: bool,
145
146    #[schemars(title = "Тип видимости в UI боя")]
147    pub fight_ui_visibility: AbilityFightUiVisibility,
148
149    #[schemars(title = "Имя способности")]
150    pub name: i18n::I18nString,
151
152    #[schemars(title = "Скрипт каста способности", schema_with = "script_schema")]
153    pub start_script: String,
154
155    #[schemars(title = "Скрипт способности", schema_with = "script_schema")]
156    pub script: String,
157
158    #[schemars(
159        title = "Ссылка на иконку способности",
160        schema_with = "webp_url_schema"
161    )]
162    pub icon_url: String,
163
164    #[schemars(title = "Иконка", schema_with = "asset_ability_icon_schema")]
165    pub icon_path: String,
166
167    #[schemars(title = "Перезарядка способности")]
168    pub cooldown: u64,
169
170    #[schemars(title = "Показывается в окне гачи, умеет выпадать из гачи")]
171    pub is_gacha_ability: bool,
172
173    #[schemars(title = "Id редкости", schema_with = "ability_rarity_link_id_schema")]
174    pub rarity_id: AbilityRarityId,
175
176    #[schemars(title = "Описание способности")]
177    pub description: i18n::I18nString,
178
179    #[schemars(
180        title = "Скрипт, возвращающий значения для подстановки в описание",
181        schema_with = "option_script_schema"
182    )]
183    pub description_values_script: Option<String>,
184
185    #[schemars(title = "VFX", schema_with = "asset_vfx_object_schema")]
186    pub vfx_object_path: String,
187
188    #[schemars(title = "Тип каста")]
189    pub cast_type: AbilityCastType,
190}
191
192#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, CustomType)]
193#[tsify(from_wasm_abi)]
194pub struct Ability {
195    pub template_id: AbilityId,
196    pub level: i64,
197    pub shards_amount: i64,
198}
199
200impl Ability {
201    pub fn from_template(
202        ability_template: &AbilityTemplate,
203        level: Option<i64>,
204        shards_amount: Option<i64>,
205    ) -> Self {
206        Ability {
207            template_id: ability_template.id,
208            level: level.unwrap_or(1),
209            shards_amount: shards_amount.unwrap_or(0),
210        }
211    }
212}
213
214#[derive(Clone, Debug, Serialize, Deserialize, Eq, JsonSchema, Tsify, CustomType)]
215pub struct ActiveAbility {
216    pub ability: Ability,
217    #[cfg_attr(target_arch = "wasm32", schemars(skip))]
218    pub deadline: Option<chrono::DateTime<chrono::Utc>>,
219    pub slot_id: Option<AbilitySlotId>,
220}
221
222impl PartialEq for ActiveAbility {
223    fn eq(&self, other: &Self) -> bool {
224        self.ability == other.ability
225            && self.slot_id == other.slot_id
226            && self.deadline.zip(other.deadline).map_or(
227                self.deadline.is_none() && other.deadline.is_none(),
228                |(a, b)| a.signed_duration_since(b).abs() <= chrono::TimeDelta::milliseconds(250),
229            )
230    }
231}
232
233#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
234pub struct UpgradedAbilitiesMap(pub HashMap<AbilityId, (i64, i64)>);
235
236impl UpgradedAbilitiesMap {
237    pub fn iter(&self) -> rhai::Array {
238        self.0
239            .iter()
240            .map(|(id, (a, b))| {
241                rhai::Dynamic::from(rhai::Array::from(vec![
242                    id.to_string().into(),
243                    (*a).into(),
244                    (*b).into(),
245                ]))
246            })
247            .collect()
248    }
249
250    pub fn upgrade(&mut self, id: AbilityId, level: i64) {
251        self.0
252            .entry(id)
253            .and_modify(|(_, max)| {
254                *max += 1;
255            })
256            .or_insert((level, level + 1));
257    }
258
259    pub fn insert(&mut self, id: AbilityId, levels: (i64, i64)) {
260        self.0.insert(id, levels);
261    }
262}
263
264impl CustomType for UpgradedAbilitiesMap {
265    fn build(mut builder: TypeBuilder<Self>) {
266        builder
267            .with_name("UpgradedAbilitiesMap")
268            .with_indexer_get(
269                |ea: &mut UpgradedAbilitiesMap, idx: Uuid| -> rhai::Dynamic {
270                    match ea.0.get(&idx) {
271                        Some((a, b)) => rhai::Array::from(vec![(*a).into(), (*b).into()]).into(),
272                        None => ().into(),
273                    }
274                },
275            )
276            .with_fn("UpgradedAbilitiesMap", Self::default)
277            .with_fn("iter", |map: &mut UpgradedAbilitiesMap| -> rhai::Array {
278                map.iter()
279            });
280    }
281}
282
283#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct AbilityShard {
285    pub ability_id: AbilityId,
286    pub shards_amount: i64,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, CustomType)]
290pub struct AbilityDrop {
291    pub template: AbilityTemplate,
292    pub is_new: bool,
293    pub evolved_from: Option<AbilityTemplate>,
294    pub is_checkpoint: bool,
295}
296
297#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, Default)]
298#[tsify(from_wasm_abi)]
299pub struct EquippedAbilities {
300    pub slotted: BTreeMap<AbilitySlotId, Ability>,
301    pub unslotted: Vec<Ability>,
302}
303
304impl CustomType for EquippedAbilities {
305    fn build(mut builder: TypeBuilder<Self>) {
306        builder
307            .with_name("EquippedAbilities")
308            .with_get("slotted", |e: &mut EquippedAbilities| -> Array {
309                e.slotted
310                    .values()
311                    .cloned()
312                    .map(Dynamic::from)
313                    .collect::<Array>()
314            })
315            .with_get("unslotted", |e: &mut EquippedAbilities| e.unslotted.clone());
316    }
317}
318
319impl EquippedAbilities {
320    pub fn new() -> Self {
321        Self {
322            slotted: BTreeMap::new(),
323            unslotted: Vec::new(),
324        }
325    }
326
327    pub fn add(&mut self, ability: Ability, slot_id: Option<AbilitySlotId>) {
328        if let Some(slot_id) = slot_id {
329            self.slotted.insert(slot_id, ability);
330        } else {
331            self.unslotted.push(ability);
332        }
333    }
334
335    pub fn to_vec(&self) -> Vec<&Ability> {
336        self.unslotted.iter().chain(self.slotted.values()).collect()
337    }
338
339    pub fn to_vec_mut(&mut self) -> Vec<&mut Ability> {
340        self.unslotted
341            .iter_mut()
342            .chain(self.slotted.values_mut())
343            .collect()
344    }
345
346    pub fn get_by_slot_id(&self, slot_id: AbilitySlotId) -> Option<&Ability> {
347        self.slotted.get(&slot_id)
348    }
349
350    pub fn has_ability(&self, ability_id: AbilityId) -> bool {
351        self.slotted.values().any(|a| a.template_id == ability_id)
352            || self.unslotted.iter().any(|a| a.template_id == ability_id)
353    }
354
355    pub fn get_mut_by_id(&mut self, ability_id: AbilityId) -> Option<&mut Ability> {
356        self.slotted
357            .values_mut()
358            .find(|a| a.template_id == ability_id)
359            .or_else(|| {
360                self.unslotted
361                    .iter_mut()
362                    .find(|a| a.template_id == ability_id)
363            })
364    }
365
366    pub fn remove_by_slot_id(&mut self, slot_id: AbilitySlotId) -> Option<Ability> {
367        self.slotted.remove(&slot_id)
368    }
369
370    pub fn len(&self) -> usize {
371        self.slotted.len() + self.unslotted.len()
372    }
373
374    pub fn is_empty(&self) -> bool {
375        self.slotted.is_empty() && self.unslotted.is_empty()
376    }
377
378    pub fn clear(&mut self) {
379        self.slotted.clear();
380        self.unslotted.clear();
381    }
382}