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(
88 Debug,
89 Clone,
90 Copy,
91 EnumString,
92 Sequence,
93 Display,
94 Deserialize,
95 Serialize,
96 Hash,
97 Eq,
98 PartialEq,
99 EnumIter,
100 Default,
101 JsonSchema,
102 Tsify,
103)]
104#[tsify(namespace)]
105pub enum AbilityTargetType {
106 #[default]
107 Enemy,
108 Ally,
109 #[serde(rename = "Self")]
111 #[strum(serialize = "Self")]
112 Zelf,
113}
114
115impl AbilityTargetType {
116 pub fn as_str(&self) -> &'static str {
119 match self {
120 AbilityTargetType::Enemy => "Enemy",
121 AbilityTargetType::Ally => "Ally",
122 AbilityTargetType::Zelf => "Self",
123 }
124 }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Tsify)]
129#[tsify(namespace)]
130pub enum AbilityCaseRollType {
131 Small,
132 Big,
133}
134
135#[derive(Default, PartialEq, Debug, Clone, Serialize, Deserialize, Tsify, JsonSchema)]
138pub struct AbilityRarity {
139 #[schemars(schema_with = "id_schema")]
140 pub id: AbilityRarityId,
141
142 #[schemars(title = "Название редкости")]
143 pub name: i18n::I18nString,
144
145 #[schemars(title = "Сортировка")]
146 pub order: u64,
147
148 #[schemars(title = "Цвет редкости", schema_with = "color_schema")]
149 pub color: String,
150
151 #[schemars(title = "Цвет заднего фона редкости", schema_with = "color_schema")]
152 pub bg_color: String,
153
154 #[schemars(title = "Иконка рамки", schema_with = "webp_url_schema")]
155 pub icon_url: String,
156
157 #[schemars(title = "Рамка", schema_with = "asset_ability_rarity_icon_schema")]
158 pub icon_path: String,
159
160 #[schemars(
161 title = "Квадратная рамка",
162 schema_with = "asset_ability_rarity_square_icon_schema"
163 )]
164 pub square_icon_path: String,
165
166 #[schemars(
167 title = "Эффективность редкости (eff)",
168 description = "Множитель урона способностей этой редкости (ability_rarity_eff). Используется в balance::ability_eff."
169 )]
170 #[serde(default)]
171 pub eff: f64,
172}
173
174#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema, Default)]
175#[tsify(from_wasm_abi)]
176pub struct AbilityTemplate {
177 #[schemars(schema_with = "id_schema")]
178 pub id: AbilityId,
179
180 #[schemars(title = "Видимость в UI боя")]
181 pub is_fight_ui_visible: bool,
182
183 #[schemars(title = "Тип видимости в UI боя")]
184 pub fight_ui_visibility: AbilityFightUiVisibility,
185
186 #[schemars(title = "Имя способности")]
187 pub name: i18n::I18nString,
188
189 #[schemars(
190 title = "Нативная функция старта каста",
191 description = "Имя нативной функции категории start_cast_ability, выполняющей старт каста способности.",
192 schema_with = "start_cast_ability_ref_schema"
193 )]
194 #[serde(default)]
195 pub start_behavior: Option<String>,
196
197 #[schemars(
198 title = "Нативная функция каста способности",
199 description = "Имя нативной функции категории cast_ability, выполняющей каст способности.",
200 schema_with = "cast_ability_ref_schema"
201 )]
202 #[serde(default)]
203 pub behavior: Option<String>,
204
205 #[schemars(
206 title = "Ссылка на иконку способности",
207 schema_with = "webp_url_schema"
208 )]
209 pub icon_url: String,
210
211 #[schemars(title = "Иконка", schema_with = "asset_ability_icon_schema")]
212 pub icon_path: String,
213
214 #[schemars(title = "Перезарядка способности")]
215 pub cooldown: u64,
216
217 #[schemars(title = "Показывается в окне гачи, умеет выпадать из гачи")]
218 pub is_gacha_ability: bool,
219
220 #[schemars(title = "Доступна ботам (для генерации оппонентов)")]
221 #[serde(default)]
222 pub available_to_bots: bool,
223
224 #[schemars(title = "Id редкости", schema_with = "ability_rarity_link_id_schema")]
225 pub rarity_id: AbilityRarityId,
226
227 #[schemars(title = "Описание способности")]
228 pub description: i18n::I18nString,
229
230 #[schemars(
231 title = "Скрипт, возвращающий значения для подстановки в описание",
232 schema_with = "option_script_schema"
233 )]
234 pub description_values_script: Option<String>,
235
236 #[schemars(title = "VFX", schema_with = "asset_vfx_object_schema")]
237 pub vfx_object_path: String,
238
239 #[schemars(title = "Тип каста")]
240 pub cast_type: AbilityCastType,
241
242 #[schemars(title = "Тип цели (Enemy / Ally / Self)")]
243 #[serde(default)]
244 pub target_type: AbilityTargetType,
245
246 #[schemars(title = "Дальность каста (в клетках)")]
247 #[serde(default)]
248 pub range: i64,
249}
250
251#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
252#[tsify(from_wasm_abi)]
253pub struct Ability {
254 pub template_id: AbilityId,
255 pub level: i64,
256 pub shards_amount: i64,
257}
258
259impl Ability {
260 pub fn from_template(
261 ability_template: &AbilityTemplate,
262 level: Option<i64>,
263 shards_amount: Option<i64>,
264 ) -> Self {
265 Ability {
266 template_id: ability_template.id,
267 level: level.unwrap_or(1),
268 shards_amount: shards_amount.unwrap_or(0),
269 }
270 }
271}
272
273#[derive(Clone, Debug, Serialize, Deserialize, Eq, JsonSchema, Tsify)]
274pub struct ActiveAbility {
275 pub ability: Ability,
276 #[cfg_attr(target_arch = "wasm32", schemars(skip))]
277 pub deadline: Option<chrono::DateTime<chrono::Utc>>,
278 pub slot_id: Option<AbilitySlotId>,
279}
280
281impl PartialEq for ActiveAbility {
282 fn eq(&self, other: &Self) -> bool {
283 self.ability == other.ability
284 && self.slot_id == other.slot_id
285 && self.deadline.zip(other.deadline).map_or(
286 self.deadline.is_none() && other.deadline.is_none(),
287 |(a, b)| a.signed_duration_since(b).abs() <= chrono::TimeDelta::milliseconds(250),
288 )
289 }
290}
291
292#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
293pub struct UpgradedAbilitiesMap(pub HashMap<AbilityId, (i64, i64)>);
294
295impl UpgradedAbilitiesMap {
296 pub fn upgrade(&mut self, id: AbilityId, level: i64) {
297 self.0
298 .entry(id)
299 .and_modify(|(_, max)| {
300 *max += 1;
301 })
302 .or_insert((level, level + 1));
303 }
304
305 pub fn insert(&mut self, id: AbilityId, levels: (i64, i64)) {
306 self.0.insert(id, levels);
307 }
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
311pub struct AbilityShard {
312 pub ability_id: AbilityId,
313 pub shards_amount: i64,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
317pub struct AbilityDrop {
318 pub template: AbilityTemplate,
319 pub is_new: bool,
320 pub evolved_from: Option<AbilityTemplate>,
321 pub is_checkpoint: bool,
322}
323
324#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify, Default)]
325#[tsify(from_wasm_abi)]
326pub struct EquippedAbilities {
327 pub slotted: BTreeMap<AbilitySlotId, Ability>,
328 pub unslotted: Vec<Ability>,
329}
330
331impl EquippedAbilities {
332 pub fn new() -> Self {
333 Self {
334 slotted: BTreeMap::new(),
335 unslotted: Vec::new(),
336 }
337 }
338
339 pub fn add(&mut self, ability: Ability, slot_id: Option<AbilitySlotId>) {
340 if let Some(slot_id) = slot_id {
341 self.slotted.insert(slot_id, ability);
342 } else {
343 self.unslotted.push(ability);
344 }
345 }
346
347 pub fn to_vec(&self) -> Vec<&Ability> {
348 self.unslotted.iter().chain(self.slotted.values()).collect()
349 }
350
351 pub fn to_vec_mut(&mut self) -> Vec<&mut Ability> {
352 self.unslotted
353 .iter_mut()
354 .chain(self.slotted.values_mut())
355 .collect()
356 }
357
358 pub fn get_by_slot_id(&self, slot_id: AbilitySlotId) -> Option<&Ability> {
359 self.slotted.get(&slot_id)
360 }
361
362 pub fn has_ability(&self, ability_id: AbilityId) -> bool {
363 self.slotted.values().any(|a| a.template_id == ability_id)
364 || self.unslotted.iter().any(|a| a.template_id == ability_id)
365 }
366
367 pub fn get_mut_by_id(&mut self, ability_id: AbilityId) -> Option<&mut Ability> {
368 self.slotted
369 .values_mut()
370 .find(|a| a.template_id == ability_id)
371 .or_else(|| {
372 self.unslotted
373 .iter_mut()
374 .find(|a| a.template_id == ability_id)
375 })
376 }
377
378 pub fn remove_by_slot_id(&mut self, slot_id: AbilitySlotId) -> Option<Ability> {
379 self.slotted.remove(&slot_id)
380 }
381
382 pub fn len(&self) -> usize {
383 self.slotted.len() + self.unslotted.len()
384 }
385
386 pub fn is_empty(&self) -> bool {
387 self.slotted.is_empty() && self.unslotted.is_empty()
388 }
389
390 pub fn clear(&mut self) {
391 self.slotted.clear();
392 self.unslotted.clear();
393 }
394}