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