veloren_common/comp/
ability.rs

1use crate::{
2    assets::{self, Asset},
3    combat::{self, CombatEffect, DamageKind, Knockback},
4    comp::{
5        self, Body, CharacterState, LightEmitter, StateUpdate, aura, beam, buff,
6        character_state::AttackFilters,
7        inventory::{
8            Inventory,
9            item::{
10                ItemKind, Tool,
11                tool::{
12                    AbilityContext, AbilityItem, AbilityKind, ContextualIndex, Stats, ToolKind,
13                },
14            },
15            slot::EquipSlot,
16        },
17        melee::{CustomCombo, MeleeConstructor, MeleeConstructorKind},
18        projectile::ProjectileConstructor,
19        skillset::{
20            SkillSet,
21            skills::{self, SKILL_MODIFIERS, Skill},
22        },
23    },
24    resources::Secs,
25    states::{
26        behavior::JoinData,
27        sprite_summon::SpriteSummonAnchor,
28        utils::{
29            AbilityInfo, ComboConsumption, MovementModifier, OrientationModifier, ScalingKind,
30            StageSection,
31        },
32        *,
33    },
34    terrain::SpriteKind,
35};
36use hashbrown::HashMap;
37use serde::{Deserialize, Serialize};
38use specs::{Component, DerefFlaggedStorage};
39use std::{borrow::Cow, time::Duration};
40
41pub const BASE_ABILITY_LIMIT: usize = 5;
42
43// NOTE: different AbilitySpec on same ToolKind share the same key
44/// Descriptor to pick the right (auxiliary) ability set
45pub type AuxiliaryKey = (Option<ToolKind>, Option<ToolKind>);
46
47// TODO: Potentially look into storing previous ability sets for weapon
48// combinations and automatically reverting back to them on switching to that
49// set of weapons. Consider after UI is set up and people weigh in on memory
50// considerations.
51#[derive(Serialize, Deserialize, Debug, Clone)]
52pub struct ActiveAbilities {
53    pub guard: GuardAbility,
54    pub primary: PrimaryAbility,
55    pub secondary: SecondaryAbility,
56    pub movement: MovementAbility,
57    pub limit: Option<usize>,
58    pub auxiliary_sets: HashMap<AuxiliaryKey, Vec<AuxiliaryAbility>>,
59}
60
61impl Component for ActiveAbilities {
62    type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
63}
64
65impl Default for ActiveAbilities {
66    fn default() -> Self {
67        Self {
68            guard: GuardAbility::Tool,
69            primary: PrimaryAbility::Tool,
70            secondary: SecondaryAbility::Tool,
71            movement: MovementAbility::Species,
72            limit: None,
73            auxiliary_sets: HashMap::new(),
74        }
75    }
76}
77
78// make it pub, for UI stuff, if you want
79enum AbilitySource {
80    Weapons,
81    Glider,
82}
83
84impl AbilitySource {
85    // Get all needed data here and pick the right ability source
86    //
87    // make it pub, for UI stuff, if you want
88    fn determine(char_state: Option<&CharacterState>) -> Self {
89        if char_state.is_some_and(|c| c.is_glide_wielded()) {
90            Self::Glider
91        } else {
92            Self::Weapons
93        }
94    }
95}
96
97impl ActiveAbilities {
98    pub fn from_auxiliary(
99        auxiliary_sets: HashMap<AuxiliaryKey, Vec<AuxiliaryAbility>>,
100        limit: Option<usize>,
101    ) -> Self {
102        // Discard any sets that exceed the limit
103        ActiveAbilities {
104            auxiliary_sets: auxiliary_sets
105                .into_iter()
106                .filter(|(_, set)| limit.is_none_or(|limit| set.len() == limit))
107                .collect(),
108            limit,
109            ..Self::default()
110        }
111    }
112
113    pub fn default_limited(limit: usize) -> Self {
114        ActiveAbilities {
115            limit: Some(limit),
116            ..Default::default()
117        }
118    }
119
120    pub fn change_ability(
121        &mut self,
122        slot: usize,
123        auxiliary_key: AuxiliaryKey,
124        new_ability: AuxiliaryAbility,
125        inventory: Option<&Inventory>,
126        skill_set: Option<&SkillSet>,
127    ) {
128        let auxiliary_set = self
129            .auxiliary_sets
130            .entry(auxiliary_key)
131            .or_insert(Self::default_ability_set(inventory, skill_set, self.limit));
132        if let Some(ability) = auxiliary_set.get_mut(slot) {
133            *ability = new_ability;
134        }
135    }
136
137    pub fn active_auxiliary_key(inv: Option<&Inventory>) -> AuxiliaryKey {
138        let tool_kind = |slot| {
139            inv.and_then(|inv| inv.equipped(slot))
140                .and_then(|item| match &*item.kind() {
141                    ItemKind::Tool(tool) => Some(tool.kind),
142                    _ => None,
143                })
144        };
145
146        (
147            tool_kind(EquipSlot::ActiveMainhand),
148            tool_kind(EquipSlot::ActiveOffhand),
149        )
150    }
151
152    pub fn auxiliary_set(
153        &self,
154        inv: Option<&Inventory>,
155        skill_set: Option<&SkillSet>,
156    ) -> Cow<Vec<AuxiliaryAbility>> {
157        let aux_key = Self::active_auxiliary_key(inv);
158
159        self.auxiliary_sets
160            .get(&aux_key)
161            .map(Cow::Borrowed)
162            .unwrap_or_else(|| Cow::Owned(Self::default_ability_set(inv, skill_set, self.limit)))
163    }
164
165    pub fn get_ability(
166        &self,
167        input: AbilityInput,
168        inventory: Option<&Inventory>,
169        skill_set: Option<&SkillSet>,
170        stats: Option<&comp::Stats>,
171    ) -> Ability {
172        match input {
173            AbilityInput::Guard => self.guard.into(),
174            AbilityInput::Primary => self.primary.into(),
175            AbilityInput::Secondary => self.secondary.into(),
176            AbilityInput::Movement => self.movement.into(),
177            AbilityInput::Auxiliary(index) => {
178                if stats.is_some_and(|s| s.disable_auxiliary_abilities) {
179                    Ability::Empty
180                } else {
181                    self.auxiliary_set(inventory, skill_set)
182                        .get(index)
183                        .copied()
184                        .map(|a| a.into())
185                        .unwrap_or(Ability::Empty)
186                }
187            },
188        }
189    }
190
191    /// Returns the CharacterAbility from an ability input, and also whether the
192    /// ability was from a weapon wielded in the offhand
193    pub fn activate_ability(
194        &self,
195        input: AbilityInput,
196        inv: Option<&Inventory>,
197        skill_set: &SkillSet,
198        body: Option<&Body>,
199        char_state: Option<&CharacterState>,
200        context: &AbilityContext,
201        stats: Option<&comp::Stats>,
202        // bool is from_offhand
203    ) -> Option<(CharacterAbility, bool, SpecifiedAbility)> {
204        let ability = self.get_ability(input, inv, Some(skill_set), stats);
205
206        let ability_set = |equip_slot| {
207            inv.and_then(|inv| inv.equipped(equip_slot))
208                .and_then(|i| i.item_config().map(|c| &c.abilities))
209        };
210
211        let scale_ability = |ability: CharacterAbility, equip_slot| {
212            let tool_kind = inv
213                .and_then(|inv| inv.equipped(equip_slot))
214                .and_then(|item| match &*item.kind() {
215                    ItemKind::Tool(tool) => Some(tool.kind),
216                    _ => None,
217                });
218            ability.adjusted_by_skills(skill_set, tool_kind)
219        };
220
221        let spec_ability = |context_index| SpecifiedAbility {
222            ability,
223            context_index,
224        };
225
226        // This function is an attempt to generalize ability handling
227        let inst_ability = |slot: EquipSlot, offhand: bool| {
228            ability_set(slot).and_then(|abilities| {
229                // We use AbilityInput here as an object to match on, which
230                // roughly corresponds to all needed data we need to know about
231                // ability.
232                use AbilityInput as I;
233
234                // Also we don't provide `ability`, nor `ability_input` as an
235                // argument to the closure, and that wins us a bit of code
236                // duplication we would need to do otherwise, but it's
237                // important that we can and do re-create all needed Ability
238                // information here to make decisions.
239                //
240                // For example, we should't take `input` argument provided to
241                // activate_abilities, because in case of Auxiliary abilities,
242                // it has wrong index.
243                //
244                // We could alternatively just take `ability`, but it works too.
245                let dispatched = match ability.try_ability_set_key()? {
246                    I::Guard => abilities.guard(Some(skill_set), context),
247                    I::Primary => abilities.primary(Some(skill_set), context),
248                    I::Secondary => abilities.secondary(Some(skill_set), context),
249                    I::Auxiliary(index) => abilities.auxiliary(index, Some(skill_set), context),
250                    I::Movement => return None,
251                };
252
253                dispatched
254                    .map(|(a, i)| (a.ability.clone(), i))
255                    .map(|(a, i)| (scale_ability(a, slot), offhand, spec_ability(i)))
256            })
257        };
258
259        let source = AbilitySource::determine(char_state);
260
261        match ability {
262            Ability::ToolGuard => match source {
263                AbilitySource::Weapons => {
264                    let equip_slot = combat::get_equip_slot_by_block_priority(inv);
265                    inst_ability(equip_slot, matches!(equip_slot, EquipSlot::ActiveOffhand))
266                },
267                AbilitySource::Glider => None,
268            },
269            Ability::ToolPrimary => match source {
270                AbilitySource::Weapons => inst_ability(EquipSlot::ActiveMainhand, false),
271                AbilitySource::Glider => inst_ability(EquipSlot::Glider, false),
272            },
273            Ability::ToolSecondary => match source {
274                AbilitySource::Weapons => inst_ability(EquipSlot::ActiveOffhand, true)
275                    .or_else(|| inst_ability(EquipSlot::ActiveMainhand, false)),
276                AbilitySource::Glider => inst_ability(EquipSlot::Glider, false),
277            },
278            Ability::MainWeaponAux(_) => inst_ability(EquipSlot::ActiveMainhand, false),
279            Ability::OffWeaponAux(_) => inst_ability(EquipSlot::ActiveOffhand, true),
280            Ability::GliderAux(_) => inst_ability(EquipSlot::Glider, false),
281            Ability::Empty => None,
282            Ability::SpeciesMovement => matches!(body, Some(Body::Humanoid(_)))
283                .then(|| CharacterAbility::default_roll(char_state))
284                .map(|ability| {
285                    (
286                        ability.adjusted_by_skills(skill_set, None),
287                        false,
288                        spec_ability(None),
289                    )
290                }),
291        }
292    }
293
294    pub fn iter_available_abilities_on<'a>(
295        inv: Option<&'a Inventory>,
296        skill_set: Option<&'a SkillSet>,
297        equip_slot: EquipSlot,
298    ) -> impl Iterator<Item = usize> + 'a {
299        inv.and_then(|inv| inv.equipped(equip_slot).and_then(|i| i.item_config()))
300            .into_iter()
301            .flat_map(|config| &config.abilities.abilities)
302            .enumerate()
303            .filter_map(move |(i, a)| match a {
304                AbilityKind::Simple(skill, _) => skill
305                    .is_none_or(|s| skill_set.is_some_and(|ss| ss.has_skill(s)))
306                    .then_some(i),
307                AbilityKind::Contextualized {
308                    pseudo_id: _,
309                    abilities,
310                } => abilities
311                    .iter()
312                    .any(|(_contexts, (skill, _))| {
313                        skill.is_none_or(|s| skill_set.is_some_and(|ss| ss.has_skill(s)))
314                    })
315                    .then_some(i),
316            })
317    }
318
319    pub fn all_available_abilities(
320        inv: Option<&Inventory>,
321        skill_set: Option<&SkillSet>,
322    ) -> Vec<AuxiliaryAbility> {
323        let mut ability_buff = vec![];
324        // Check if uses combo of two "equal" weapons
325        let paired = inv
326            .and_then(|inv| {
327                let a = inv.equipped(EquipSlot::ActiveMainhand)?;
328                let b = inv.equipped(EquipSlot::ActiveOffhand)?;
329
330                if let (ItemKind::Tool(tool_a), ItemKind::Tool(tool_b)) = (&*a.kind(), &*b.kind()) {
331                    Some((a.ability_spec(), tool_a.kind, b.ability_spec(), tool_b.kind))
332                } else {
333                    None
334                }
335            })
336            .is_some_and(|(a_spec, a_kind, b_spec, b_kind)| (a_spec, a_kind) == (b_spec, b_kind));
337
338        // Push main weapon abilities
339        Self::iter_available_abilities_on(inv, skill_set, EquipSlot::ActiveMainhand)
340            .map(AuxiliaryAbility::MainWeapon)
341            .for_each(|a| ability_buff.push(a));
342
343        // Push secondary weapon abilities, if different
344        // If equal, just take the first
345        if !paired {
346            Self::iter_available_abilities_on(inv, skill_set, EquipSlot::ActiveOffhand)
347                .map(AuxiliaryAbility::OffWeapon)
348                .for_each(|a| ability_buff.push(a));
349        }
350        // Push glider abilities
351        Self::iter_available_abilities_on(inv, skill_set, EquipSlot::Glider)
352            .map(AuxiliaryAbility::Glider)
353            .for_each(|a| ability_buff.push(a));
354
355        ability_buff
356    }
357
358    fn default_ability_set<'a>(
359        inv: Option<&'a Inventory>,
360        skill_set: Option<&'a SkillSet>,
361        limit: Option<usize>,
362    ) -> Vec<AuxiliaryAbility> {
363        let mut iter = Self::iter_available_abilities_on(inv, skill_set, EquipSlot::ActiveMainhand)
364            .map(AuxiliaryAbility::MainWeapon)
365            .chain(
366                Self::iter_available_abilities_on(inv, skill_set, EquipSlot::ActiveOffhand)
367                    .map(AuxiliaryAbility::OffWeapon),
368            );
369
370        if let Some(limit) = limit {
371            (0..limit)
372                .map(|_| iter.next().unwrap_or(AuxiliaryAbility::Empty))
373                .collect()
374        } else {
375            iter.collect()
376        }
377    }
378}
379
380#[derive(Debug, Copy, Clone)]
381pub enum AbilityInput {
382    Guard,
383    Primary,
384    Secondary,
385    Movement,
386    Auxiliary(usize),
387}
388
389#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
390pub enum Ability {
391    ToolGuard,
392    ToolPrimary,
393    ToolSecondary,
394    SpeciesMovement,
395    MainWeaponAux(usize),
396    OffWeaponAux(usize),
397    GliderAux(usize),
398    Empty,
399    /* For future use
400     * ArmorAbility(usize), */
401}
402
403impl Ability {
404    // Used for generic ability dispatch (inst_ability) in this file
405    //
406    // It does use AbilityInput to avoid creating just another enum, but it is
407    // semantically different.
408    fn try_ability_set_key(&self) -> Option<AbilityInput> {
409        let input = match self {
410            Self::ToolGuard => AbilityInput::Guard,
411            Self::ToolPrimary => AbilityInput::Primary,
412            Self::ToolSecondary => AbilityInput::Secondary,
413            Self::SpeciesMovement => AbilityInput::Movement,
414            Self::GliderAux(idx) | Self::OffWeaponAux(idx) | Self::MainWeaponAux(idx) => {
415                AbilityInput::Auxiliary(*idx)
416            },
417            Self::Empty => return None,
418        };
419
420        Some(input)
421    }
422
423    pub fn ability_id<'a>(
424        self,
425        char_state: Option<&CharacterState>,
426        inv: Option<&'a Inventory>,
427        skill_set: Option<&'a SkillSet>,
428        context: &AbilityContext,
429    ) -> Option<&'a str> {
430        let ability_set = |equip_slot| {
431            inv.and_then(|inv| inv.equipped(equip_slot))
432                .and_then(|i| i.item_config().map(|c| &c.abilities))
433        };
434
435        let contextual_id = |kind: Option<&'a AbilityKind<_>>| -> Option<&'a str> {
436            if let Some(AbilityKind::Contextualized {
437                pseudo_id,
438                abilities: _,
439            }) = kind
440            {
441                Some(pseudo_id.as_str())
442            } else {
443                None
444            }
445        };
446
447        let inst_ability = |slot: EquipSlot| {
448            ability_set(slot).and_then(|abilities| {
449                use AbilityInput as I;
450
451                let dispatched = match self.try_ability_set_key()? {
452                    I::Guard => abilities.guard(skill_set, context),
453                    I::Primary => abilities.primary(skill_set, context),
454                    I::Secondary => abilities.secondary(skill_set, context),
455                    I::Auxiliary(index) => abilities.auxiliary(index, skill_set, context),
456                    I::Movement => return None,
457                };
458
459                dispatched.map(|(a, _)| a.id.as_str()).or_else(|| {
460                    match self.try_ability_set_key()? {
461                        I::Guard => abilities
462                            .guard
463                            .as_ref()
464                            .and_then(|g| contextual_id(Some(g))),
465                        I::Primary => contextual_id(Some(&abilities.primary)),
466                        I::Secondary => contextual_id(Some(&abilities.secondary)),
467                        I::Auxiliary(index) => contextual_id(abilities.abilities.get(index)),
468                        I::Movement => None,
469                    }
470                })
471            })
472        };
473
474        let source = AbilitySource::determine(char_state);
475        match source {
476            AbilitySource::Glider => match self {
477                Ability::ToolGuard => None,
478                Ability::ToolPrimary => inst_ability(EquipSlot::Glider),
479                Ability::ToolSecondary => inst_ability(EquipSlot::Glider),
480                Ability::SpeciesMovement => None, // TODO: Make not None
481                Ability::MainWeaponAux(_) => inst_ability(EquipSlot::ActiveMainhand),
482                Ability::OffWeaponAux(_) => inst_ability(EquipSlot::ActiveOffhand),
483                Ability::GliderAux(_) => inst_ability(EquipSlot::Glider),
484                Ability::Empty => None,
485            },
486            AbilitySource::Weapons => match self {
487                Ability::ToolGuard => {
488                    let equip_slot = combat::get_equip_slot_by_block_priority(inv);
489                    inst_ability(equip_slot)
490                },
491                Ability::ToolPrimary => inst_ability(EquipSlot::ActiveMainhand),
492                Ability::ToolSecondary => inst_ability(EquipSlot::ActiveOffhand)
493                    .or_else(|| inst_ability(EquipSlot::ActiveMainhand)),
494                Ability::SpeciesMovement => None, // TODO: Make not None
495                Ability::MainWeaponAux(_) => inst_ability(EquipSlot::ActiveMainhand),
496                Ability::OffWeaponAux(_) => inst_ability(EquipSlot::ActiveOffhand),
497                Ability::GliderAux(_) => inst_ability(EquipSlot::Glider),
498                Ability::Empty => None,
499            },
500        }
501    }
502
503    pub fn is_from_wielded(&self) -> bool {
504        match self {
505            Ability::ToolPrimary
506            | Ability::ToolSecondary
507            | Ability::MainWeaponAux(_)
508            | Ability::GliderAux(_)
509            | Ability::OffWeaponAux(_)
510            | Ability::ToolGuard => true,
511            Ability::SpeciesMovement | Ability::Empty => false,
512        }
513    }
514}
515
516#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
517pub enum GuardAbility {
518    Tool,
519    Empty,
520}
521
522impl From<GuardAbility> for Ability {
523    fn from(guard: GuardAbility) -> Self {
524        match guard {
525            GuardAbility::Tool => Ability::ToolGuard,
526            GuardAbility::Empty => Ability::Empty,
527        }
528    }
529}
530
531#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
532pub struct SpecifiedAbility {
533    pub ability: Ability,
534    pub context_index: Option<ContextualIndex>,
535}
536
537impl SpecifiedAbility {
538    pub fn ability_id<'a>(
539        self,
540        char_state: Option<&CharacterState>,
541        inv: Option<&'a Inventory>,
542    ) -> Option<&'a str> {
543        let ability_set = |equip_slot| {
544            inv.and_then(|inv| inv.equipped(equip_slot))
545                .and_then(|i| i.item_config().map(|c| &c.abilities))
546        };
547
548        fn ability_id(spec_ability: SpecifiedAbility, ability: &AbilityKind<AbilityItem>) -> &str {
549            match ability {
550                AbilityKind::Simple(_, a) => a.id.as_str(),
551                AbilityKind::Contextualized {
552                    pseudo_id,
553                    abilities,
554                } => spec_ability
555                    .context_index
556                    .and_then(|i| abilities.get(i.0))
557                    .map_or(pseudo_id.as_str(), |(_, (_, a))| a.id.as_str()),
558            }
559        }
560
561        let inst_ability = |slot: EquipSlot| {
562            ability_set(slot).and_then(|abilities| {
563                use AbilityInput as I;
564
565                let dispatched = match self.ability.try_ability_set_key()? {
566                    I::Guard => abilities.guard.as_ref(),
567                    I::Primary => Some(&abilities.primary),
568                    I::Secondary => Some(&abilities.secondary),
569                    I::Auxiliary(index) => abilities.abilities.get(index),
570                    I::Movement => return None,
571                };
572                dispatched.map(|a| ability_id(self, a))
573            })
574        };
575
576        let source = AbilitySource::determine(char_state);
577        match source {
578            AbilitySource::Glider => match self.ability {
579                Ability::ToolGuard => None,
580                Ability::ToolPrimary => inst_ability(EquipSlot::Glider),
581                Ability::ToolSecondary => inst_ability(EquipSlot::Glider),
582                Ability::SpeciesMovement => None,
583                Ability::MainWeaponAux(_) => inst_ability(EquipSlot::ActiveMainhand),
584                Ability::OffWeaponAux(_) => inst_ability(EquipSlot::ActiveOffhand),
585                Ability::GliderAux(_) => inst_ability(EquipSlot::Glider),
586                Ability::Empty => None,
587            },
588            AbilitySource::Weapons => match self.ability {
589                Ability::ToolGuard => inst_ability(combat::get_equip_slot_by_block_priority(inv)),
590                Ability::ToolPrimary => inst_ability(EquipSlot::ActiveMainhand),
591                Ability::ToolSecondary => inst_ability(EquipSlot::ActiveOffhand)
592                    .or_else(|| inst_ability(EquipSlot::ActiveMainhand)),
593                Ability::SpeciesMovement => None, // TODO: Make not None
594                Ability::MainWeaponAux(_) => inst_ability(EquipSlot::ActiveMainhand),
595                Ability::OffWeaponAux(_) => inst_ability(EquipSlot::ActiveOffhand),
596                Ability::GliderAux(_) => inst_ability(EquipSlot::Glider),
597                Ability::Empty => None,
598            },
599        }
600    }
601}
602
603#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
604pub enum PrimaryAbility {
605    Tool,
606    Empty,
607}
608
609impl From<PrimaryAbility> for Ability {
610    fn from(primary: PrimaryAbility) -> Self {
611        match primary {
612            PrimaryAbility::Tool => Ability::ToolPrimary,
613            PrimaryAbility::Empty => Ability::Empty,
614        }
615    }
616}
617
618#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
619pub enum SecondaryAbility {
620    Tool,
621    Empty,
622}
623
624impl From<SecondaryAbility> for Ability {
625    fn from(primary: SecondaryAbility) -> Self {
626        match primary {
627            SecondaryAbility::Tool => Ability::ToolSecondary,
628            SecondaryAbility::Empty => Ability::Empty,
629        }
630    }
631}
632
633#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
634pub enum MovementAbility {
635    Species,
636    Empty,
637}
638
639impl From<MovementAbility> for Ability {
640    fn from(primary: MovementAbility) -> Self {
641        match primary {
642            MovementAbility::Species => Ability::SpeciesMovement,
643            MovementAbility::Empty => Ability::Empty,
644        }
645    }
646}
647
648#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
649pub enum AuxiliaryAbility {
650    MainWeapon(usize),
651    OffWeapon(usize),
652    Glider(usize),
653    Empty,
654}
655
656impl From<AuxiliaryAbility> for Ability {
657    fn from(primary: AuxiliaryAbility) -> Self {
658        match primary {
659            AuxiliaryAbility::MainWeapon(i) => Ability::MainWeaponAux(i),
660            AuxiliaryAbility::OffWeapon(i) => Ability::OffWeaponAux(i),
661            AuxiliaryAbility::Glider(i) => Ability::GliderAux(i),
662            AuxiliaryAbility::Empty => Ability::Empty,
663        }
664    }
665}
666
667/// A lighter form of character state to pass around as needed for frontend
668/// purposes
669// Only add to this enum as needed for frontends, not necessary to immediately
670// add a variant here when adding a new character state
671#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
672pub enum CharacterAbilityType {
673    BasicMelee(StageSection),
674    BasicRanged,
675    Boost,
676    ChargedMelee(StageSection),
677    ChargedRanged,
678    DashMelee(StageSection),
679    BasicBlock,
680    ComboMelee2(StageSection),
681    FinisherMelee(StageSection),
682    DiveMelee(StageSection),
683    RiposteMelee(StageSection),
684    RapidMelee(StageSection),
685    LeapMelee(StageSection),
686    LeapShockwave(StageSection),
687    Music(StageSection),
688    Shockwave,
689    BasicBeam,
690    RepeaterRanged,
691    BasicAura,
692    SelfBuff,
693    Other,
694}
695
696impl From<&CharacterState> for CharacterAbilityType {
697    fn from(state: &CharacterState) -> Self {
698        match state {
699            CharacterState::BasicMelee(data) => Self::BasicMelee(data.stage_section),
700            CharacterState::BasicRanged(_) => Self::BasicRanged,
701            CharacterState::Boost(_) => Self::Boost,
702            CharacterState::DashMelee(data) => Self::DashMelee(data.stage_section),
703            CharacterState::BasicBlock(_) => Self::BasicBlock,
704            CharacterState::LeapMelee(data) => Self::LeapMelee(data.stage_section),
705            CharacterState::LeapShockwave(data) => Self::LeapShockwave(data.stage_section),
706            CharacterState::ComboMelee2(data) => Self::ComboMelee2(data.stage_section),
707            CharacterState::FinisherMelee(data) => Self::FinisherMelee(data.stage_section),
708            CharacterState::DiveMelee(data) => Self::DiveMelee(data.stage_section),
709            CharacterState::RiposteMelee(data) => Self::RiposteMelee(data.stage_section),
710            CharacterState::RapidMelee(data) => Self::RapidMelee(data.stage_section),
711            CharacterState::ChargedMelee(data) => Self::ChargedMelee(data.stage_section),
712            CharacterState::ChargedRanged(_) => Self::ChargedRanged,
713            CharacterState::Shockwave(_) => Self::Shockwave,
714            CharacterState::BasicBeam(_) => Self::BasicBeam,
715            CharacterState::RepeaterRanged(_) => Self::RepeaterRanged,
716            CharacterState::BasicAura(_) => Self::BasicAura,
717            CharacterState::SelfBuff(_) => Self::SelfBuff,
718            CharacterState::Music(data) => Self::Music(data.stage_section),
719            CharacterState::Idle(_)
720            | CharacterState::Crawl
721            | CharacterState::Climb(_)
722            | CharacterState::Sit
723            | CharacterState::Dance
724            | CharacterState::Talk(_)
725            | CharacterState::Glide(_)
726            | CharacterState::GlideWield(_)
727            | CharacterState::Stunned(_)
728            | CharacterState::Equipping(_)
729            | CharacterState::Wielding(_)
730            | CharacterState::Roll(_)
731            | CharacterState::Blink(_)
732            | CharacterState::BasicSummon(_)
733            | CharacterState::SpriteSummon(_)
734            | CharacterState::UseItem(_)
735            | CharacterState::Interact(_)
736            | CharacterState::Skate(_)
737            | CharacterState::Transform(_)
738            | CharacterState::RegrowHead(_)
739            | CharacterState::Wallrun(_)
740            | CharacterState::StaticAura(_)
741            | CharacterState::Throw(_) => Self::Other,
742        }
743    }
744}
745
746#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
747pub enum Dodgeable {
748    #[default]
749    Roll,
750    Jump,
751    No,
752}
753
754#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
755pub enum Amount {
756    PerHead(u32),
757    Value(u32),
758}
759
760impl Amount {
761    pub fn add(&mut self, value: u32) {
762        match self {
763            Self::PerHead(v) | Self::Value(v) => *v += value,
764        }
765    }
766
767    pub fn compute(&self, heads: u32) -> u32 {
768        match self {
769            Amount::PerHead(v) => v * heads,
770            Amount::Value(v) => *v,
771        }
772    }
773}
774
775#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
776#[serde(deny_unknown_fields)]
777/// For documentation on individual fields, see the corresponding character
778/// state file in 'common/src/states/'
779pub enum CharacterAbility {
780    BasicMelee {
781        energy_cost: f32,
782        buildup_duration: f32,
783        swing_duration: f32,
784        hit_timing: f32,
785        recover_duration: f32,
786        melee_constructor: MeleeConstructor,
787        #[serde(default)]
788        movement_modifier: MovementModifier,
789        #[serde(default)]
790        ori_modifier: OrientationModifier,
791        frontend_specifier: Option<basic_melee::FrontendSpecifier>,
792        #[serde(default)]
793        meta: AbilityMeta,
794    },
795    BasicRanged {
796        energy_cost: f32,
797        buildup_duration: f32,
798        recover_duration: f32,
799        projectile: ProjectileConstructor,
800        projectile_body: Body,
801        projectile_light: Option<LightEmitter>,
802        projectile_speed: f32,
803        num_projectiles: Amount,
804        projectile_spread: f32,
805        damage_effect: Option<CombatEffect>,
806        #[serde(default)]
807        movement_modifier: MovementModifier,
808        #[serde(default)]
809        ori_modifier: OrientationModifier,
810        #[serde(default)]
811        meta: AbilityMeta,
812    },
813    RepeaterRanged {
814        energy_cost: f32,
815        buildup_duration: f32,
816        shoot_duration: f32,
817        recover_duration: f32,
818        max_speed: f32,
819        half_speed_at: u32,
820        projectile: ProjectileConstructor,
821        projectile_body: Body,
822        projectile_light: Option<LightEmitter>,
823        projectile_speed: f32,
824        damage_effect: Option<CombatEffect>,
825        properties_of_aoe: Option<repeater_ranged::ProjectileOffset>,
826        specifier: Option<repeater_ranged::FrontendSpecifier>,
827        #[serde(default)]
828        meta: AbilityMeta,
829    },
830    Boost {
831        movement_duration: f32,
832        only_up: bool,
833        speed: f32,
834        max_exit_velocity: f32,
835        #[serde(default)]
836        meta: AbilityMeta,
837    },
838    GlideBoost {
839        booster: glide::Boost,
840        #[serde(default)]
841        meta: AbilityMeta,
842    },
843    DashMelee {
844        energy_cost: f32,
845        energy_drain: f32,
846        forward_speed: f32,
847        buildup_duration: f32,
848        charge_duration: f32,
849        swing_duration: f32,
850        recover_duration: f32,
851        melee_constructor: MeleeConstructor,
852        ori_modifier: f32,
853        auto_charge: bool,
854        #[serde(default)]
855        meta: AbilityMeta,
856    },
857    BasicBlock {
858        buildup_duration: f32,
859        recover_duration: f32,
860        max_angle: f32,
861        block_strength: f32,
862        parry_window: basic_block::ParryWindow,
863        energy_cost: f32,
864        energy_regen: f32,
865        can_hold: bool,
866        blocked_attacks: AttackFilters,
867        #[serde(default)]
868        meta: AbilityMeta,
869    },
870    Roll {
871        energy_cost: f32,
872        buildup_duration: f32,
873        movement_duration: f32,
874        recover_duration: f32,
875        roll_strength: f32,
876        attack_immunities: AttackFilters,
877        was_cancel: bool,
878        #[serde(default)]
879        meta: AbilityMeta,
880    },
881    ComboMelee2 {
882        strikes: Vec<combo_melee2::Strike<f32>>,
883        energy_cost_per_strike: f32,
884        specifier: Option<combo_melee2::FrontendSpecifier>,
885        #[serde(default)]
886        auto_progress: bool,
887        #[serde(default)]
888        meta: AbilityMeta,
889    },
890    LeapMelee {
891        energy_cost: f32,
892        buildup_duration: f32,
893        movement_duration: f32,
894        swing_duration: f32,
895        recover_duration: f32,
896        melee_constructor: MeleeConstructor,
897        forward_leap_strength: f32,
898        vertical_leap_strength: f32,
899        damage_effect: Option<CombatEffect>,
900        specifier: Option<leap_melee::FrontendSpecifier>,
901        #[serde(default)]
902        meta: AbilityMeta,
903    },
904    LeapShockwave {
905        energy_cost: f32,
906        buildup_duration: f32,
907        movement_duration: f32,
908        swing_duration: f32,
909        recover_duration: f32,
910        damage: f32,
911        poise_damage: f32,
912        knockback: Knockback,
913        shockwave_angle: f32,
914        shockwave_vertical_angle: f32,
915        shockwave_speed: f32,
916        shockwave_duration: f32,
917        dodgeable: Dodgeable,
918        move_efficiency: f32,
919        damage_kind: DamageKind,
920        specifier: comp::shockwave::FrontendSpecifier,
921        damage_effect: Option<CombatEffect>,
922        forward_leap_strength: f32,
923        vertical_leap_strength: f32,
924        #[serde(default)]
925        meta: AbilityMeta,
926    },
927    ChargedMelee {
928        energy_cost: f32,
929        energy_drain: f32,
930        buildup_strike: Option<(f32, MeleeConstructor)>,
931        charge_duration: f32,
932        swing_duration: f32,
933        hit_timing: f32,
934        recover_duration: f32,
935        melee_constructor: MeleeConstructor,
936        specifier: Option<charged_melee::FrontendSpecifier>,
937        damage_effect: Option<CombatEffect>,
938        #[serde(default)]
939        custom_combo: CustomCombo,
940        #[serde(default)]
941        meta: AbilityMeta,
942        #[serde(default)]
943        movement_modifier: MovementModifier,
944        #[serde(default)]
945        ori_modifier: OrientationModifier,
946    },
947    ChargedRanged {
948        energy_cost: f32,
949        energy_drain: f32,
950        projectile: ProjectileConstructor,
951        buildup_duration: f32,
952        charge_duration: f32,
953        recover_duration: f32,
954        projectile_body: Body,
955        projectile_light: Option<LightEmitter>,
956        initial_projectile_speed: f32,
957        scaled_projectile_speed: f32,
958        damage_effect: Option<CombatEffect>,
959        move_speed: f32,
960        #[serde(default)]
961        meta: AbilityMeta,
962    },
963    Throw {
964        energy_cost: f32,
965        energy_drain: f32,
966        buildup_duration: f32,
967        charge_duration: f32,
968        throw_duration: f32,
969        recover_duration: f32,
970        projectile: ProjectileConstructor,
971        projectile_light: Option<LightEmitter>,
972        projectile_dir: throw::ProjectileDir,
973        initial_projectile_speed: f32,
974        scaled_projectile_speed: f32,
975        damage_effect: Option<CombatEffect>,
976        move_speed: f32,
977        #[serde(default)]
978        meta: AbilityMeta,
979    },
980    Shockwave {
981        energy_cost: f32,
982        buildup_duration: f32,
983        swing_duration: f32,
984        recover_duration: f32,
985        damage: f32,
986        poise_damage: f32,
987        knockback: Knockback,
988        shockwave_angle: f32,
989        shockwave_vertical_angle: f32,
990        shockwave_speed: f32,
991        shockwave_duration: f32,
992        dodgeable: Dodgeable,
993        move_efficiency: f32,
994        damage_kind: DamageKind,
995        specifier: comp::shockwave::FrontendSpecifier,
996        ori_rate: f32,
997        damage_effect: Option<CombatEffect>,
998        timing: shockwave::Timing,
999        emit_outcome: bool,
1000        minimum_combo: Option<u32>,
1001        #[serde(default)]
1002        combo_consumption: ComboConsumption,
1003        #[serde(default)]
1004        meta: AbilityMeta,
1005    },
1006    BasicBeam {
1007        buildup_duration: f32,
1008        recover_duration: f32,
1009        beam_duration: f64,
1010        damage: f32,
1011        tick_rate: f32,
1012        range: f32,
1013        max_angle: f32,
1014        damage_effect: Option<CombatEffect>,
1015        energy_regen: f32,
1016        energy_drain: f32,
1017        ori_rate: f32,
1018        specifier: beam::FrontendSpecifier,
1019        #[serde(default)]
1020        meta: AbilityMeta,
1021    },
1022    BasicAura {
1023        buildup_duration: f32,
1024        cast_duration: f32,
1025        recover_duration: f32,
1026        targets: combat::GroupTarget,
1027        auras: Vec<aura::AuraBuffConstructor>,
1028        aura_duration: Option<Secs>,
1029        range: f32,
1030        energy_cost: f32,
1031        scales_with_combo: bool,
1032        specifier: Option<aura::Specifier>,
1033        #[serde(default)]
1034        meta: AbilityMeta,
1035    },
1036    StaticAura {
1037        buildup_duration: f32,
1038        cast_duration: f32,
1039        recover_duration: f32,
1040        energy_cost: f32,
1041        targets: combat::GroupTarget,
1042        auras: Vec<aura::AuraBuffConstructor>,
1043        aura_duration: Option<Secs>,
1044        range: f32,
1045        sprite_info: Option<static_aura::SpriteInfo>,
1046        #[serde(default)]
1047        meta: AbilityMeta,
1048    },
1049    Blink {
1050        buildup_duration: f32,
1051        recover_duration: f32,
1052        max_range: f32,
1053        frontend_specifier: Option<blink::FrontendSpecifier>,
1054        #[serde(default)]
1055        meta: AbilityMeta,
1056    },
1057    BasicSummon {
1058        buildup_duration: f32,
1059        cast_duration: f32,
1060        recover_duration: f32,
1061        summon_amount: u32,
1062        summon_distance: (f32, f32),
1063        summon_info: basic_summon::SummonInfo,
1064        duration: Option<Duration>,
1065        #[serde(default)]
1066        meta: AbilityMeta,
1067    },
1068    SelfBuff {
1069        buildup_duration: f32,
1070        cast_duration: f32,
1071        recover_duration: f32,
1072        buff_kind: buff::BuffKind,
1073        buff_strength: f32,
1074        buff_duration: Option<Secs>,
1075        energy_cost: f32,
1076        #[serde(default = "default_true")]
1077        enforced_limit: bool,
1078        #[serde(default)]
1079        combo_cost: u32,
1080        combo_scaling: Option<ScalingKind>,
1081        #[serde(default)]
1082        meta: AbilityMeta,
1083        specifier: Option<self_buff::FrontendSpecifier>,
1084    },
1085    SpriteSummon {
1086        buildup_duration: f32,
1087        cast_duration: f32,
1088        recover_duration: f32,
1089        sprite: SpriteKind,
1090        del_timeout: Option<(f32, f32)>,
1091        summon_distance: (f32, f32),
1092        sparseness: f64,
1093        angle: f32,
1094        #[serde(default)]
1095        anchor: SpriteSummonAnchor,
1096        #[serde(default)]
1097        move_efficiency: f32,
1098        #[serde(default)]
1099        meta: AbilityMeta,
1100    },
1101    Music {
1102        play_duration: f32,
1103        ori_modifier: f32,
1104        #[serde(default)]
1105        meta: AbilityMeta,
1106    },
1107    FinisherMelee {
1108        energy_cost: f32,
1109        buildup_duration: f32,
1110        swing_duration: f32,
1111        recover_duration: f32,
1112        melee_constructor: MeleeConstructor,
1113        minimum_combo: u32,
1114        scaling: Option<finisher_melee::Scaling>,
1115        #[serde(default)]
1116        combo_consumption: ComboConsumption,
1117        #[serde(default)]
1118        meta: AbilityMeta,
1119    },
1120    DiveMelee {
1121        energy_cost: f32,
1122        vertical_speed: f32,
1123        buildup_duration: Option<f32>,
1124        movement_duration: f32,
1125        swing_duration: f32,
1126        recover_duration: f32,
1127        melee_constructor: MeleeConstructor,
1128        max_scaling: f32,
1129        #[serde(default)]
1130        meta: AbilityMeta,
1131    },
1132    RiposteMelee {
1133        energy_cost: f32,
1134        buildup_duration: f32,
1135        swing_duration: f32,
1136        recover_duration: f32,
1137        whiffed_recover_duration: f32,
1138        block_strength: f32,
1139        melee_constructor: MeleeConstructor,
1140        #[serde(default)]
1141        meta: AbilityMeta,
1142    },
1143    RapidMelee {
1144        buildup_duration: f32,
1145        swing_duration: f32,
1146        recover_duration: f32,
1147        energy_cost: f32,
1148        max_strikes: Option<u32>,
1149        melee_constructor: MeleeConstructor,
1150        move_modifier: f32,
1151        ori_modifier: f32,
1152        frontend_specifier: Option<rapid_melee::FrontendSpecifier>,
1153        #[serde(default)]
1154        minimum_combo: u32,
1155        #[serde(default)]
1156        meta: AbilityMeta,
1157    },
1158    Transform {
1159        buildup_duration: f32,
1160        recover_duration: f32,
1161        target: String,
1162        #[serde(default)]
1163        specifier: Option<transform::FrontendSpecifier>,
1164        /// Only set to `true` for admin only abilities since this disables
1165        /// persistence and is not intended to be used by regular players
1166        #[serde(default)]
1167        allow_players: bool,
1168        #[serde(default)]
1169        meta: AbilityMeta,
1170    },
1171    RegrowHead {
1172        buildup_duration: f32,
1173        recover_duration: f32,
1174        energy_cost: f32,
1175        #[serde(default)]
1176        specifier: Option<regrow_head::FrontendSpecifier>,
1177        #[serde(default)]
1178        meta: AbilityMeta,
1179    },
1180}
1181
1182impl Default for CharacterAbility {
1183    fn default() -> Self {
1184        CharacterAbility::BasicMelee {
1185            energy_cost: 0.0,
1186            buildup_duration: 0.25,
1187            swing_duration: 0.25,
1188            hit_timing: 0.5,
1189            recover_duration: 0.5,
1190            melee_constructor: MeleeConstructor {
1191                kind: MeleeConstructorKind::Slash {
1192                    damage: 1.0,
1193                    knockback: 0.0,
1194                    poise: 0.0,
1195                    energy_regen: 0.0,
1196                },
1197                scaled: None,
1198                range: 3.5,
1199                angle: 15.0,
1200                multi_target: None,
1201                damage_effect: None,
1202                attack_effect: None,
1203                simultaneous_hits: 1,
1204                custom_combo: CustomCombo {
1205                    base: None,
1206                    conditional: None,
1207                },
1208                dodgeable: Dodgeable::Roll,
1209                precision_flank_multipliers: Default::default(),
1210                precision_flank_invert: false,
1211            },
1212            movement_modifier: Default::default(),
1213            ori_modifier: Default::default(),
1214            frontend_specifier: None,
1215            meta: Default::default(),
1216        }
1217    }
1218}
1219
1220impl Asset for CharacterAbility {
1221    type Loader = assets::RonLoader;
1222
1223    const EXTENSION: &'static str = "ron";
1224}
1225
1226impl CharacterAbility {
1227    /// Attempts to fulfill requirements, mutating `update` (taking energy) if
1228    /// applicable.
1229    pub fn requirements_paid(&self, data: &JoinData, update: &mut StateUpdate) -> bool {
1230        let from_meta = {
1231            let AbilityMeta { requirements, .. } = self.ability_meta();
1232            requirements.requirements_met(data.stance)
1233        };
1234        from_meta
1235            && match self {
1236                CharacterAbility::Roll { energy_cost, .. }
1237                | CharacterAbility::StaticAura {
1238                    energy_cost,
1239                    sprite_info: Some(_),
1240                    ..
1241                } => {
1242                    data.physics.on_ground.is_some()
1243                        && update.energy.try_change_by(-*energy_cost).is_ok()
1244                },
1245                CharacterAbility::DashMelee { energy_cost, .. }
1246                | CharacterAbility::BasicMelee { energy_cost, .. }
1247                | CharacterAbility::BasicRanged { energy_cost, .. }
1248                | CharacterAbility::ChargedRanged { energy_cost, .. }
1249                | CharacterAbility::Throw { energy_cost, .. }
1250                | CharacterAbility::ChargedMelee { energy_cost, .. }
1251                | CharacterAbility::BasicBlock { energy_cost, .. }
1252                | CharacterAbility::RiposteMelee { energy_cost, .. }
1253                | CharacterAbility::ComboMelee2 {
1254                    energy_cost_per_strike: energy_cost,
1255                    ..
1256                }
1257                | CharacterAbility::StaticAura {
1258                    energy_cost,
1259                    sprite_info: None,
1260                    ..
1261                }
1262                | CharacterAbility::RegrowHead { energy_cost, .. } => {
1263                    update.energy.try_change_by(-*energy_cost).is_ok()
1264                },
1265                // Consumes energy within state, so value only checked before entering state
1266                CharacterAbility::RepeaterRanged { energy_cost, .. } => {
1267                    update.energy.current() >= *energy_cost
1268                },
1269                CharacterAbility::LeapMelee { energy_cost, .. }
1270                | CharacterAbility::LeapShockwave { energy_cost, .. } => {
1271                    update.vel.0.z >= 0.0 && update.energy.try_change_by(-*energy_cost).is_ok()
1272                },
1273                CharacterAbility::BasicAura {
1274                    energy_cost,
1275                    scales_with_combo,
1276                    ..
1277                } => {
1278                    ((*scales_with_combo && data.combo.is_some_and(|c| c.counter() > 0))
1279                        | !*scales_with_combo)
1280                        && update.energy.try_change_by(-*energy_cost).is_ok()
1281                },
1282                CharacterAbility::FinisherMelee {
1283                    energy_cost,
1284                    minimum_combo,
1285                    ..
1286                }
1287                | CharacterAbility::RapidMelee {
1288                    energy_cost,
1289                    minimum_combo,
1290                    ..
1291                }
1292                | CharacterAbility::SelfBuff {
1293                    energy_cost,
1294                    combo_cost: minimum_combo,
1295                    ..
1296                } => {
1297                    data.combo.is_some_and(|c| c.counter() >= *minimum_combo)
1298                        && update.energy.try_change_by(-*energy_cost).is_ok()
1299                },
1300                CharacterAbility::Shockwave {
1301                    energy_cost,
1302                    minimum_combo,
1303                    ..
1304                } => {
1305                    data.combo
1306                        .is_some_and(|c| c.counter() >= minimum_combo.unwrap_or(0))
1307                        && update.energy.try_change_by(-*energy_cost).is_ok()
1308                },
1309                CharacterAbility::DiveMelee {
1310                    buildup_duration,
1311                    energy_cost,
1312                    ..
1313                } => {
1314                    // If either in the air or is on ground and able to be activated from
1315                    // ground.
1316                    //
1317                    // NOTE: there is a check in CharacterState::try_from below that must be kept in
1318                    // sync with the conditions here (it determines whether this starts in a
1319                    // movement or buildup stage).
1320                    (data.physics.on_ground.is_none() || buildup_duration.is_some())
1321                        && update.energy.try_change_by(-*energy_cost).is_ok()
1322                },
1323                CharacterAbility::Boost { .. }
1324                | CharacterAbility::GlideBoost { .. }
1325                | CharacterAbility::BasicBeam { .. }
1326                | CharacterAbility::Blink { .. }
1327                | CharacterAbility::Music { .. }
1328                | CharacterAbility::BasicSummon { .. }
1329                | CharacterAbility::SpriteSummon { .. }
1330                | CharacterAbility::Transform { .. } => true,
1331            }
1332    }
1333
1334    pub fn default_roll(current_state: Option<&CharacterState>) -> CharacterAbility {
1335        let remaining_duration = current_state
1336            .and_then(|char_state| {
1337                char_state.timer().zip(
1338                    char_state
1339                        .durations()
1340                        .zip(char_state.stage_section())
1341                        .and_then(|(durations, stage_section)| match stage_section {
1342                            StageSection::Buildup => durations.buildup,
1343                            StageSection::Recover => durations.recover,
1344                            _ => None,
1345                        }),
1346                )
1347            })
1348            .map_or(0.0, |(timer, duration)| {
1349                duration.as_secs_f32() - timer.as_secs_f32()
1350            })
1351            .max(0.0);
1352
1353        CharacterAbility::Roll {
1354            // Energy cost increased by remaining duration
1355            energy_cost: 10.0 + 100.0 * remaining_duration,
1356            buildup_duration: 0.05,
1357            movement_duration: 0.36,
1358            recover_duration: 0.125,
1359            roll_strength: 3.3075,
1360            attack_immunities: AttackFilters {
1361                melee: true,
1362                projectiles: false,
1363                beams: true,
1364                ground_shockwaves: false,
1365                air_shockwaves: true,
1366                explosions: true,
1367            },
1368            was_cancel: remaining_duration > 0.0,
1369            meta: Default::default(),
1370        }
1371    }
1372
1373    #[must_use]
1374    pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
1375        use CharacterAbility::*;
1376        match self {
1377            BasicMelee {
1378                ref mut energy_cost,
1379                ref mut buildup_duration,
1380                ref mut swing_duration,
1381                ref mut recover_duration,
1382                ref mut melee_constructor,
1383                movement_modifier: _,
1384                ori_modifier: _,
1385                hit_timing: _,
1386                frontend_specifier: _,
1387                meta: _,
1388            } => {
1389                *buildup_duration /= stats.speed;
1390                *swing_duration /= stats.speed;
1391                *recover_duration /= stats.speed;
1392                *energy_cost /= stats.energy_efficiency;
1393                *melee_constructor = melee_constructor.adjusted_by_stats(stats);
1394            },
1395            BasicRanged {
1396                ref mut energy_cost,
1397                ref mut buildup_duration,
1398                ref mut recover_duration,
1399                ref mut projectile,
1400                projectile_body: _,
1401                projectile_light: _,
1402                ref mut projectile_speed,
1403                num_projectiles: _,
1404                projectile_spread: _,
1405                damage_effect: _,
1406                movement_modifier: _,
1407                ori_modifier: _,
1408                meta: _,
1409            } => {
1410                *buildup_duration /= stats.speed;
1411                *recover_duration /= stats.speed;
1412                *projectile = projectile.adjusted_by_stats(stats);
1413                *projectile_speed *= stats.range;
1414                *energy_cost /= stats.energy_efficiency;
1415            },
1416            RepeaterRanged {
1417                ref mut energy_cost,
1418                ref mut buildup_duration,
1419                ref mut shoot_duration,
1420                ref mut recover_duration,
1421                max_speed: _,
1422                half_speed_at: _,
1423                ref mut projectile,
1424                projectile_body: _,
1425                projectile_light: _,
1426                ref mut projectile_speed,
1427                damage_effect: _,
1428                properties_of_aoe: _,
1429                specifier: _,
1430                meta: _,
1431            } => {
1432                *buildup_duration /= stats.speed;
1433                *shoot_duration /= stats.speed;
1434                *recover_duration /= stats.speed;
1435                *projectile = projectile.adjusted_by_stats(stats);
1436                *projectile_speed *= stats.range;
1437                *energy_cost /= stats.energy_efficiency;
1438            },
1439            Boost {
1440                ref mut movement_duration,
1441                only_up: _,
1442                speed: ref mut boost_speed,
1443                max_exit_velocity: _,
1444                meta: _,
1445            } => {
1446                *movement_duration /= stats.speed;
1447                *boost_speed *= stats.power;
1448            },
1449            DashMelee {
1450                ref mut energy_cost,
1451                ref mut energy_drain,
1452                forward_speed: _,
1453                ref mut buildup_duration,
1454                charge_duration: _,
1455                ref mut swing_duration,
1456                ref mut recover_duration,
1457                ref mut melee_constructor,
1458                ori_modifier: _,
1459                auto_charge: _,
1460                meta: _,
1461            } => {
1462                *buildup_duration /= stats.speed;
1463                *swing_duration /= stats.speed;
1464                *recover_duration /= stats.speed;
1465                *energy_cost /= stats.energy_efficiency;
1466                *energy_drain /= stats.energy_efficiency;
1467                *melee_constructor = melee_constructor.adjusted_by_stats(stats);
1468            },
1469            BasicBlock {
1470                ref mut buildup_duration,
1471                ref mut recover_duration,
1472                // Do we want angle to be adjusted by range?
1473                max_angle: _,
1474                ref mut block_strength,
1475                parry_window: _,
1476                ref mut energy_cost,
1477                energy_regen: _,
1478                can_hold: _,
1479                blocked_attacks: _,
1480                meta: _,
1481            } => {
1482                *buildup_duration /= stats.speed;
1483                *recover_duration /= stats.speed;
1484                *energy_cost /= stats.energy_efficiency;
1485                *block_strength *= stats.power;
1486            },
1487            Roll {
1488                ref mut energy_cost,
1489                ref mut buildup_duration,
1490                ref mut movement_duration,
1491                ref mut recover_duration,
1492                roll_strength: _,
1493                attack_immunities: _,
1494                was_cancel: _,
1495                meta: _,
1496            } => {
1497                *buildup_duration /= stats.speed;
1498                *movement_duration /= stats.speed;
1499                *recover_duration /= stats.speed;
1500                *energy_cost /= stats.energy_efficiency;
1501            },
1502            ComboMelee2 {
1503                ref mut strikes,
1504                ref mut energy_cost_per_strike,
1505                specifier: _,
1506                auto_progress: _,
1507                meta: _,
1508            } => {
1509                *energy_cost_per_strike /= stats.energy_efficiency;
1510                *strikes = strikes
1511                    .iter_mut()
1512                    .map(|s| s.adjusted_by_stats(stats))
1513                    .collect();
1514            },
1515            LeapMelee {
1516                ref mut energy_cost,
1517                ref mut buildup_duration,
1518                movement_duration: _,
1519                ref mut swing_duration,
1520                ref mut recover_duration,
1521                ref mut melee_constructor,
1522                forward_leap_strength: _,
1523                vertical_leap_strength: _,
1524                ref mut damage_effect,
1525                specifier: _,
1526                meta: _,
1527            } => {
1528                *buildup_duration /= stats.speed;
1529                *swing_duration /= stats.speed;
1530                *recover_duration /= stats.speed;
1531                *energy_cost /= stats.energy_efficiency;
1532                *melee_constructor = melee_constructor.adjusted_by_stats(stats);
1533                if let Some(CombatEffect::Buff(combat::CombatBuff {
1534                    kind: _,
1535                    dur_secs: _,
1536                    strength,
1537                    chance: _,
1538                })) = damage_effect
1539                {
1540                    *strength *= stats.buff_strength;
1541                }
1542            },
1543            LeapShockwave {
1544                ref mut energy_cost,
1545                ref mut buildup_duration,
1546                movement_duration: _,
1547                ref mut swing_duration,
1548                ref mut recover_duration,
1549                ref mut damage,
1550                ref mut poise_damage,
1551                knockback: _,
1552                shockwave_angle: _,
1553                shockwave_vertical_angle: _,
1554                shockwave_speed: _,
1555                ref mut shockwave_duration,
1556                dodgeable: _,
1557                move_efficiency: _,
1558                damage_kind: _,
1559                specifier: _,
1560                ref mut damage_effect,
1561                forward_leap_strength: _,
1562                vertical_leap_strength: _,
1563                meta: _,
1564            } => {
1565                *buildup_duration /= stats.speed;
1566                *swing_duration /= stats.speed;
1567                *recover_duration /= stats.speed;
1568                *damage *= stats.power;
1569                *poise_damage *= stats.effect_power;
1570                *shockwave_duration *= stats.range;
1571                *energy_cost /= stats.energy_efficiency;
1572                if let Some(CombatEffect::Buff(combat::CombatBuff {
1573                    kind: _,
1574                    dur_secs: _,
1575                    strength,
1576                    chance: _,
1577                })) = damage_effect
1578                {
1579                    *strength *= stats.buff_strength;
1580                }
1581            },
1582            ChargedMelee {
1583                ref mut energy_cost,
1584                ref mut energy_drain,
1585                ref mut buildup_strike,
1586                ref mut charge_duration,
1587                ref mut swing_duration,
1588                hit_timing: _,
1589                ref mut recover_duration,
1590                ref mut melee_constructor,
1591                specifier: _,
1592                ref mut damage_effect,
1593                meta: _,
1594                custom_combo: _,
1595                movement_modifier: _,
1596                ori_modifier: _,
1597            } => {
1598                *swing_duration /= stats.speed;
1599                *buildup_strike = buildup_strike
1600                    .map(|(dur, strike)| (dur / stats.speed, strike.adjusted_by_stats(stats)));
1601                *charge_duration /= stats.speed;
1602                *recover_duration /= stats.speed;
1603                *energy_cost /= stats.energy_efficiency;
1604                *energy_drain *= stats.speed / stats.energy_efficiency;
1605                *melee_constructor = melee_constructor.adjusted_by_stats(stats);
1606                if let Some(CombatEffect::Buff(combat::CombatBuff {
1607                    kind: _,
1608                    dur_secs: _,
1609                    strength,
1610                    chance: _,
1611                })) = damage_effect
1612                {
1613                    *strength *= stats.buff_strength;
1614                }
1615            },
1616            ChargedRanged {
1617                ref mut energy_cost,
1618                ref mut energy_drain,
1619                ref mut projectile,
1620                ref mut buildup_duration,
1621                ref mut charge_duration,
1622                ref mut recover_duration,
1623                projectile_body: _,
1624                projectile_light: _,
1625                ref mut initial_projectile_speed,
1626                ref mut scaled_projectile_speed,
1627                damage_effect: _,
1628                move_speed: _,
1629                meta: _,
1630            } => {
1631                *projectile = projectile.adjusted_by_stats(stats);
1632                *buildup_duration /= stats.speed;
1633                *charge_duration /= stats.speed;
1634                *recover_duration /= stats.speed;
1635                *initial_projectile_speed *= stats.range;
1636                *scaled_projectile_speed *= stats.range;
1637                *energy_cost /= stats.energy_efficiency;
1638                *energy_drain *= stats.speed / stats.energy_efficiency;
1639            },
1640            Throw {
1641                ref mut energy_cost,
1642                ref mut energy_drain,
1643                ref mut buildup_duration,
1644                ref mut charge_duration,
1645                ref mut throw_duration,
1646                ref mut recover_duration,
1647                ref mut projectile,
1648                projectile_light: _,
1649                projectile_dir: _,
1650                ref mut initial_projectile_speed,
1651                ref mut scaled_projectile_speed,
1652                damage_effect: _,
1653                move_speed: _,
1654                meta: _,
1655            } => {
1656                *projectile = projectile.adjusted_by_stats(stats);
1657                *energy_cost /= stats.energy_efficiency;
1658                *energy_drain *= stats.speed / stats.energy_efficiency;
1659                *buildup_duration /= stats.speed;
1660                *charge_duration /= stats.speed;
1661                *throw_duration /= stats.speed;
1662                *recover_duration /= stats.speed;
1663                *initial_projectile_speed *= stats.range;
1664                *scaled_projectile_speed *= stats.range;
1665            },
1666            Shockwave {
1667                ref mut energy_cost,
1668                ref mut buildup_duration,
1669                ref mut swing_duration,
1670                ref mut recover_duration,
1671                ref mut damage,
1672                ref mut poise_damage,
1673                knockback: _,
1674                shockwave_angle: _,
1675                shockwave_vertical_angle: _,
1676                shockwave_speed: _,
1677                ref mut shockwave_duration,
1678                dodgeable: _,
1679                move_efficiency: _,
1680                damage_kind: _,
1681                specifier: _,
1682                ori_rate: _,
1683                ref mut damage_effect,
1684                timing: _,
1685                emit_outcome: _,
1686                minimum_combo: _,
1687                combo_consumption: _,
1688                meta: _,
1689            } => {
1690                *buildup_duration /= stats.speed;
1691                *swing_duration /= stats.speed;
1692                *recover_duration /= stats.speed;
1693                *damage *= stats.power;
1694                *poise_damage *= stats.effect_power;
1695                *shockwave_duration *= stats.range;
1696                *energy_cost /= stats.energy_efficiency;
1697                *damage_effect = damage_effect.map(|de| de.adjusted_by_stats(stats));
1698            },
1699            BasicBeam {
1700                ref mut buildup_duration,
1701                ref mut recover_duration,
1702                ref mut beam_duration,
1703                ref mut damage,
1704                ref mut tick_rate,
1705                ref mut range,
1706                max_angle: _,
1707                ref mut damage_effect,
1708                energy_regen: _,
1709                ref mut energy_drain,
1710                ori_rate: _,
1711                specifier: _,
1712                meta: _,
1713            } => {
1714                *buildup_duration /= stats.speed;
1715                *recover_duration /= stats.speed;
1716                *damage *= stats.power;
1717                *tick_rate *= stats.speed;
1718                *range *= stats.range;
1719                // Duration modified to keep velocity constant
1720                *beam_duration *= stats.range as f64;
1721                *energy_drain /= stats.energy_efficiency;
1722                *damage_effect = damage_effect.map(|de| de.adjusted_by_stats(stats));
1723            },
1724            BasicAura {
1725                ref mut buildup_duration,
1726                ref mut cast_duration,
1727                ref mut recover_duration,
1728                targets: _,
1729                ref mut auras,
1730                aura_duration: _,
1731                ref mut range,
1732                ref mut energy_cost,
1733                scales_with_combo: _,
1734                specifier: _,
1735                meta: _,
1736            } => {
1737                *buildup_duration /= stats.speed;
1738                *cast_duration /= stats.speed;
1739                *recover_duration /= stats.speed;
1740                auras.iter_mut().for_each(
1741                    |aura::AuraBuffConstructor {
1742                         kind: _,
1743                         strength,
1744                         duration: _,
1745                         category: _,
1746                     }| {
1747                        *strength *= stats.diminished_buff_strength();
1748                    },
1749                );
1750                *range *= stats.range;
1751                *energy_cost /= stats.energy_efficiency;
1752            },
1753            StaticAura {
1754                ref mut buildup_duration,
1755                ref mut cast_duration,
1756                ref mut recover_duration,
1757                targets: _,
1758                ref mut auras,
1759                aura_duration: _,
1760                ref mut range,
1761                ref mut energy_cost,
1762                ref mut sprite_info,
1763                meta: _,
1764            } => {
1765                *buildup_duration /= stats.speed;
1766                *cast_duration /= stats.speed;
1767                *recover_duration /= stats.speed;
1768                auras.iter_mut().for_each(
1769                    |aura::AuraBuffConstructor {
1770                         kind: _,
1771                         strength,
1772                         duration: _,
1773                         category: _,
1774                     }| {
1775                        *strength *= stats.diminished_buff_strength();
1776                    },
1777                );
1778                *range *= stats.range;
1779                *energy_cost /= stats.energy_efficiency;
1780                *sprite_info = sprite_info.map(|mut si| {
1781                    si.summon_distance.0 *= stats.range;
1782                    si.summon_distance.1 *= stats.range;
1783                    si
1784                });
1785            },
1786            Blink {
1787                ref mut buildup_duration,
1788                ref mut recover_duration,
1789                ref mut max_range,
1790                frontend_specifier: _,
1791                meta: _,
1792            } => {
1793                *buildup_duration /= stats.speed;
1794                *recover_duration /= stats.speed;
1795                *max_range *= stats.range;
1796            },
1797            BasicSummon {
1798                ref mut buildup_duration,
1799                ref mut cast_duration,
1800                ref mut recover_duration,
1801                summon_amount: _,
1802                summon_distance: (ref mut inner_dist, ref mut outer_dist),
1803                summon_info: _,
1804                duration: _,
1805                meta: _,
1806            } => {
1807                // TODO: Figure out how/if power should affect this
1808                *buildup_duration /= stats.speed;
1809                *cast_duration /= stats.speed;
1810                *recover_duration /= stats.speed;
1811                *inner_dist *= stats.range;
1812                *outer_dist *= stats.range;
1813            },
1814            SelfBuff {
1815                ref mut buildup_duration,
1816                ref mut cast_duration,
1817                ref mut recover_duration,
1818                buff_kind: _,
1819                ref mut buff_strength,
1820                buff_duration: _,
1821                ref mut energy_cost,
1822                enforced_limit: _,
1823                combo_cost: _,
1824                combo_scaling: _,
1825                meta: _,
1826                specifier: _,
1827            } => {
1828                *buff_strength *= stats.diminished_buff_strength();
1829                *buildup_duration /= stats.speed;
1830                *cast_duration /= stats.speed;
1831                *recover_duration /= stats.speed;
1832                *energy_cost /= stats.energy_efficiency;
1833            },
1834            SpriteSummon {
1835                ref mut buildup_duration,
1836                ref mut cast_duration,
1837                ref mut recover_duration,
1838                sprite: _,
1839                del_timeout: _,
1840                summon_distance: (ref mut inner_dist, ref mut outer_dist),
1841                sparseness: _,
1842                angle: _,
1843                anchor: _,
1844                move_efficiency: _,
1845                meta: _,
1846            } => {
1847                // TODO: Figure out how/if power should affect this
1848                *buildup_duration /= stats.speed;
1849                *cast_duration /= stats.speed;
1850                *recover_duration /= stats.speed;
1851                *inner_dist *= stats.range;
1852                *outer_dist *= stats.range;
1853            },
1854            Music {
1855                ref mut play_duration,
1856                ori_modifier: _,
1857                meta: _,
1858            } => {
1859                *play_duration /= stats.speed;
1860            },
1861            FinisherMelee {
1862                ref mut energy_cost,
1863                ref mut buildup_duration,
1864                ref mut swing_duration,
1865                ref mut recover_duration,
1866                ref mut melee_constructor,
1867                minimum_combo: _,
1868                scaling: _,
1869                combo_consumption: _,
1870                meta: _,
1871            } => {
1872                *buildup_duration /= stats.speed;
1873                *swing_duration /= stats.speed;
1874                *recover_duration /= stats.speed;
1875                *energy_cost /= stats.energy_efficiency;
1876                *melee_constructor = melee_constructor.adjusted_by_stats(stats);
1877            },
1878            DiveMelee {
1879                ref mut energy_cost,
1880                vertical_speed: _,
1881                movement_duration: _,
1882                ref mut buildup_duration,
1883                ref mut swing_duration,
1884                ref mut recover_duration,
1885                ref mut melee_constructor,
1886                max_scaling: _,
1887                meta: _,
1888            } => {
1889                *buildup_duration = buildup_duration.map(|b| b / stats.speed);
1890                *swing_duration /= stats.speed;
1891                *recover_duration /= stats.speed;
1892                *energy_cost /= stats.energy_efficiency;
1893                *melee_constructor = melee_constructor.adjusted_by_stats(stats);
1894            },
1895            RiposteMelee {
1896                ref mut energy_cost,
1897                ref mut buildup_duration,
1898                ref mut swing_duration,
1899                ref mut recover_duration,
1900                ref mut whiffed_recover_duration,
1901                ref mut block_strength,
1902                ref mut melee_constructor,
1903                meta: _,
1904            } => {
1905                *buildup_duration /= stats.speed;
1906                *swing_duration /= stats.speed;
1907                *recover_duration /= stats.speed;
1908                *whiffed_recover_duration /= stats.speed;
1909                *energy_cost /= stats.energy_efficiency;
1910                *block_strength *= stats.power;
1911                *melee_constructor = melee_constructor.adjusted_by_stats(stats);
1912            },
1913            RapidMelee {
1914                ref mut buildup_duration,
1915                ref mut swing_duration,
1916                ref mut recover_duration,
1917                ref mut energy_cost,
1918                ref mut melee_constructor,
1919                max_strikes: _,
1920                move_modifier: _,
1921                ori_modifier: _,
1922                minimum_combo: _,
1923                frontend_specifier: _,
1924                meta: _,
1925            } => {
1926                *buildup_duration /= stats.speed;
1927                *swing_duration /= stats.speed;
1928                *recover_duration /= stats.speed;
1929                *energy_cost /= stats.energy_efficiency;
1930                *melee_constructor = melee_constructor.adjusted_by_stats(stats);
1931            },
1932            Transform {
1933                ref mut buildup_duration,
1934                ref mut recover_duration,
1935                target: _,
1936                specifier: _,
1937                allow_players: _,
1938                meta: _,
1939            } => {
1940                *buildup_duration /= stats.speed;
1941                *recover_duration /= stats.speed;
1942            },
1943            GlideBoost { .. } => {},
1944            RegrowHead {
1945                ref mut buildup_duration,
1946                ref mut recover_duration,
1947                ref mut energy_cost,
1948                specifier: _,
1949                meta: _,
1950            } => {
1951                *buildup_duration /= stats.speed;
1952                *recover_duration /= stats.speed;
1953                *energy_cost /= stats.energy_efficiency;
1954            },
1955        }
1956        self
1957    }
1958
1959    pub fn energy_cost(&self) -> f32 {
1960        use CharacterAbility::*;
1961        match self {
1962            BasicMelee { energy_cost, .. }
1963            | BasicRanged { energy_cost, .. }
1964            | RepeaterRanged { energy_cost, .. }
1965            | DashMelee { energy_cost, .. }
1966            | Roll { energy_cost, .. }
1967            | LeapMelee { energy_cost, .. }
1968            | LeapShockwave { energy_cost, .. }
1969            | ChargedMelee { energy_cost, .. }
1970            | ChargedRanged { energy_cost, .. }
1971            | Throw { energy_cost, .. }
1972            | Shockwave { energy_cost, .. }
1973            | BasicAura { energy_cost, .. }
1974            | BasicBlock { energy_cost, .. }
1975            | SelfBuff { energy_cost, .. }
1976            | FinisherMelee { energy_cost, .. }
1977            | ComboMelee2 {
1978                energy_cost_per_strike: energy_cost,
1979                ..
1980            }
1981            | DiveMelee { energy_cost, .. }
1982            | RiposteMelee { energy_cost, .. }
1983            | RapidMelee { energy_cost, .. }
1984            | StaticAura { energy_cost, .. }
1985            | RegrowHead { energy_cost, .. } => *energy_cost,
1986            BasicBeam { energy_drain, .. } => {
1987                if *energy_drain > f32::EPSILON {
1988                    1.0
1989                } else {
1990                    0.0
1991                }
1992            },
1993            Boost { .. }
1994            | GlideBoost { .. }
1995            | Blink { .. }
1996            | Music { .. }
1997            | BasicSummon { .. }
1998            | SpriteSummon { .. }
1999            | Transform { .. } => 0.0,
2000        }
2001    }
2002
2003    #[expect(clippy::bool_to_int_with_if)]
2004    pub fn combo_cost(&self) -> u32 {
2005        use CharacterAbility::*;
2006        match self {
2007            BasicAura {
2008                scales_with_combo, ..
2009            } => {
2010                if *scales_with_combo {
2011                    1
2012                } else {
2013                    0
2014                }
2015            },
2016            FinisherMelee {
2017                minimum_combo: combo,
2018                ..
2019            }
2020            | RapidMelee {
2021                minimum_combo: combo,
2022                ..
2023            }
2024            | SelfBuff {
2025                combo_cost: combo, ..
2026            } => *combo,
2027            Shockwave {
2028                minimum_combo: combo,
2029                ..
2030            } => combo.unwrap_or(0),
2031            BasicMelee { .. }
2032            | BasicRanged { .. }
2033            | RepeaterRanged { .. }
2034            | DashMelee { .. }
2035            | Roll { .. }
2036            | LeapMelee { .. }
2037            | LeapShockwave { .. }
2038            | ChargedMelee { .. }
2039            | ChargedRanged { .. }
2040            | Throw { .. }
2041            | BasicBlock { .. }
2042            | ComboMelee2 { .. }
2043            | DiveMelee { .. }
2044            | RiposteMelee { .. }
2045            | BasicBeam { .. }
2046            | Boost { .. }
2047            | GlideBoost { .. }
2048            | Blink { .. }
2049            | Music { .. }
2050            | BasicSummon { .. }
2051            | SpriteSummon { .. }
2052            | Transform { .. }
2053            | StaticAura { .. }
2054            | RegrowHead { .. } => 0,
2055        }
2056    }
2057
2058    // TODO: Maybe consider making CharacterAbility a struct at some point?
2059    pub fn ability_meta(&self) -> AbilityMeta {
2060        use CharacterAbility::*;
2061        match self {
2062            BasicMelee { meta, .. }
2063            | BasicRanged { meta, .. }
2064            | RepeaterRanged { meta, .. }
2065            | DashMelee { meta, .. }
2066            | Roll { meta, .. }
2067            | LeapMelee { meta, .. }
2068            | LeapShockwave { meta, .. }
2069            | ChargedMelee { meta, .. }
2070            | ChargedRanged { meta, .. }
2071            | Throw { meta, .. }
2072            | Shockwave { meta, .. }
2073            | BasicAura { meta, .. }
2074            | BasicBlock { meta, .. }
2075            | SelfBuff { meta, .. }
2076            | BasicBeam { meta, .. }
2077            | Boost { meta, .. }
2078            | GlideBoost { meta, .. }
2079            | ComboMelee2 { meta, .. }
2080            | Blink { meta, .. }
2081            | BasicSummon { meta, .. }
2082            | SpriteSummon { meta, .. }
2083            | FinisherMelee { meta, .. }
2084            | Music { meta, .. }
2085            | DiveMelee { meta, .. }
2086            | RiposteMelee { meta, .. }
2087            | RapidMelee { meta, .. }
2088            | Transform { meta, .. }
2089            | StaticAura { meta, .. }
2090            | RegrowHead { meta, .. } => *meta,
2091        }
2092    }
2093
2094    #[must_use = "method returns new ability and doesn't mutate the original value"]
2095    pub fn adjusted_by_skills(mut self, skillset: &SkillSet, tool: Option<ToolKind>) -> Self {
2096        match tool {
2097            Some(ToolKind::Bow) => self.adjusted_by_bow_skills(skillset),
2098            Some(ToolKind::Staff) => self.adjusted_by_staff_skills(skillset),
2099            Some(ToolKind::Sceptre) => self.adjusted_by_sceptre_skills(skillset),
2100            Some(ToolKind::Pick) => self.adjusted_by_mining_skills(skillset),
2101            None | Some(_) => {},
2102        }
2103        self
2104    }
2105
2106    fn adjusted_by_mining_skills(&mut self, skillset: &SkillSet) {
2107        use skills::MiningSkill::Speed;
2108
2109        if let CharacterAbility::BasicMelee {
2110            buildup_duration,
2111            swing_duration,
2112            recover_duration,
2113            ..
2114        } = self
2115        {
2116            if let Ok(level) = skillset.skill_level(Skill::Pick(Speed)) {
2117                let modifiers = SKILL_MODIFIERS.mining_tree;
2118
2119                let speed = modifiers.speed.powi(level.into());
2120                *buildup_duration /= speed;
2121                *swing_duration /= speed;
2122                *recover_duration /= speed;
2123            }
2124        }
2125    }
2126
2127    fn adjusted_by_bow_skills(&mut self, skillset: &SkillSet) {
2128        use skills::{BowSkill::*, Skill::Bow};
2129
2130        let projectile_speed_modifier = SKILL_MODIFIERS.bow_tree.universal.projectile_speed;
2131        match self {
2132            CharacterAbility::ChargedRanged {
2133                projectile,
2134                move_speed,
2135                initial_projectile_speed,
2136                scaled_projectile_speed,
2137                charge_duration,
2138                ..
2139            } => {
2140                let modifiers = SKILL_MODIFIERS.bow_tree.charged;
2141                if let Ok(level) = skillset.skill_level(Bow(ProjSpeed)) {
2142                    let projectile_speed_scaling = projectile_speed_modifier.powi(level.into());
2143                    *initial_projectile_speed *= projectile_speed_scaling;
2144                    *scaled_projectile_speed *= projectile_speed_scaling;
2145                }
2146                if let Ok(level) = skillset.skill_level(Bow(CDamage)) {
2147                    let power = modifiers.damage_scaling.powi(level.into());
2148                    *projectile = projectile.legacy_modified_by_skills(power, 1_f32, 1_f32, 1_f32);
2149                }
2150                if let Ok(level) = skillset.skill_level(Bow(CRegen)) {
2151                    let regen = modifiers.regen_scaling.powi(level.into());
2152                    *projectile = projectile.legacy_modified_by_skills(1_f32, regen, 1_f32, 1_f32);
2153                }
2154                if let Ok(level) = skillset.skill_level(Bow(CKnockback)) {
2155                    let kb = modifiers.knockback_scaling.powi(level.into());
2156                    *projectile = projectile.legacy_modified_by_skills(1_f32, 1_f32, 1_f32, kb);
2157                }
2158                if let Ok(level) = skillset.skill_level(Bow(CSpeed)) {
2159                    let charge_time = 1.0 / modifiers.charge_rate;
2160                    *charge_duration *= charge_time.powi(level.into());
2161                }
2162                if let Ok(level) = skillset.skill_level(Bow(CMove)) {
2163                    *move_speed *= modifiers.move_speed.powi(level.into());
2164                }
2165            },
2166            CharacterAbility::RepeaterRanged {
2167                energy_cost,
2168                projectile,
2169                max_speed,
2170                projectile_speed,
2171                ..
2172            } => {
2173                let modifiers = SKILL_MODIFIERS.bow_tree.repeater;
2174                if let Ok(level) = skillset.skill_level(Bow(ProjSpeed)) {
2175                    *projectile_speed *= projectile_speed_modifier.powi(level.into());
2176                }
2177                if let Ok(level) = skillset.skill_level(Bow(RDamage)) {
2178                    let power = modifiers.power.powi(level.into());
2179                    *projectile = projectile.legacy_modified_by_skills(power, 1_f32, 1_f32, 1_f32);
2180                }
2181                if let Ok(level) = skillset.skill_level(Bow(RCost)) {
2182                    *energy_cost *= modifiers.energy_cost.powi(level.into());
2183                }
2184                if let Ok(level) = skillset.skill_level(Bow(RSpeed)) {
2185                    *max_speed *= modifiers.max_speed.powi(level.into());
2186                }
2187            },
2188            CharacterAbility::BasicRanged {
2189                projectile,
2190                energy_cost,
2191                num_projectiles,
2192                projectile_spread,
2193                projectile_speed,
2194                ..
2195            } => {
2196                let modifiers = SKILL_MODIFIERS.bow_tree.shotgun;
2197                if let Ok(level) = skillset.skill_level(Bow(ProjSpeed)) {
2198                    *projectile_speed *= projectile_speed_modifier.powi(level.into());
2199                }
2200                if let Ok(level) = skillset.skill_level(Bow(SDamage)) {
2201                    let power = modifiers.power.powi(level.into());
2202                    *projectile = projectile.legacy_modified_by_skills(power, 1_f32, 1_f32, 1_f32);
2203                }
2204                if let Ok(level) = skillset.skill_level(Bow(SCost)) {
2205                    *energy_cost *= modifiers.energy_cost.powi(level.into());
2206                }
2207                if let Ok(level) = skillset.skill_level(Bow(SArrows)) {
2208                    num_projectiles.add(u32::from(level) * modifiers.num_projectiles);
2209                }
2210                if let Ok(level) = skillset.skill_level(Bow(SSpread)) {
2211                    *projectile_spread *= modifiers.spread.powi(level.into());
2212                }
2213            },
2214            _ => {},
2215        }
2216    }
2217
2218    fn adjusted_by_staff_skills(&mut self, skillset: &SkillSet) {
2219        use skills::{Skill::Staff, StaffSkill::*};
2220
2221        match self {
2222            CharacterAbility::BasicRanged { projectile, .. } => {
2223                let modifiers = SKILL_MODIFIERS.staff_tree.fireball;
2224                let damage_level = skillset.skill_level(Staff(BDamage)).unwrap_or(0);
2225                let regen_level = skillset.skill_level(Staff(BRegen)).unwrap_or(0);
2226                let range_level = skillset.skill_level(Staff(BRadius)).unwrap_or(0);
2227                let power = modifiers.power.powi(damage_level.into());
2228                let regen = modifiers.regen.powi(regen_level.into());
2229                let range = modifiers.range.powi(range_level.into());
2230                *projectile = projectile.legacy_modified_by_skills(power, regen, range, 1_f32);
2231            },
2232            CharacterAbility::BasicBeam {
2233                damage,
2234                range,
2235                energy_drain,
2236                beam_duration,
2237                ..
2238            } => {
2239                let modifiers = SKILL_MODIFIERS.staff_tree.flamethrower;
2240                if let Ok(level) = skillset.skill_level(Staff(FDamage)) {
2241                    *damage *= modifiers.damage.powi(level.into());
2242                }
2243                if let Ok(level) = skillset.skill_level(Staff(FRange)) {
2244                    let range_mod = modifiers.range.powi(level.into());
2245                    *range *= range_mod;
2246                    // Duration modified to keep velocity constant
2247                    *beam_duration *= range_mod as f64;
2248                }
2249                if let Ok(level) = skillset.skill_level(Staff(FDrain)) {
2250                    *energy_drain *= modifiers.energy_drain.powi(level.into());
2251                }
2252                if let Ok(level) = skillset.skill_level(Staff(FVelocity)) {
2253                    let velocity_increase = modifiers.velocity.powi(level.into());
2254                    let duration_mod = 1.0 / (1.0 + velocity_increase);
2255                    *beam_duration *= duration_mod as f64;
2256                }
2257            },
2258            CharacterAbility::Shockwave {
2259                damage,
2260                knockback,
2261                shockwave_duration,
2262                energy_cost,
2263                ..
2264            } => {
2265                let modifiers = SKILL_MODIFIERS.staff_tree.shockwave;
2266                if let Ok(level) = skillset.skill_level(Staff(SDamage)) {
2267                    *damage *= modifiers.damage.powi(level.into());
2268                }
2269                if let Ok(level) = skillset.skill_level(Staff(SKnockback)) {
2270                    let knockback_mod = modifiers.knockback.powi(level.into());
2271                    *knockback = knockback.modify_strength(knockback_mod);
2272                }
2273                if let Ok(level) = skillset.skill_level(Staff(SRange)) {
2274                    *shockwave_duration *= modifiers.duration.powi(level.into());
2275                }
2276                if let Ok(level) = skillset.skill_level(Staff(SCost)) {
2277                    *energy_cost *= modifiers.energy_cost.powi(level.into());
2278                }
2279            },
2280            _ => {},
2281        }
2282    }
2283
2284    fn adjusted_by_sceptre_skills(&mut self, skillset: &SkillSet) {
2285        use skills::{SceptreSkill::*, Skill::Sceptre};
2286
2287        match self {
2288            CharacterAbility::BasicBeam {
2289                damage,
2290                range,
2291                beam_duration,
2292                damage_effect,
2293                energy_regen,
2294                ..
2295            } => {
2296                let modifiers = SKILL_MODIFIERS.sceptre_tree.beam;
2297                if let Ok(level) = skillset.skill_level(Sceptre(LDamage)) {
2298                    *damage *= modifiers.damage.powi(level.into());
2299                }
2300                if let Ok(level) = skillset.skill_level(Sceptre(LRange)) {
2301                    let range_mod = modifiers.range.powi(level.into());
2302                    *range *= range_mod;
2303                    // Duration modified to keep velocity constant
2304                    *beam_duration *= range_mod as f64;
2305                }
2306                if let Ok(level) = skillset.skill_level(Sceptre(LRegen)) {
2307                    *energy_regen *= modifiers.energy_regen.powi(level.into());
2308                }
2309                if let (Ok(level), Some(CombatEffect::Lifesteal(lifesteal))) =
2310                    (skillset.skill_level(Sceptre(LLifesteal)), damage_effect)
2311                {
2312                    *lifesteal *= modifiers.lifesteal.powi(level.into());
2313                }
2314            },
2315            CharacterAbility::BasicAura {
2316                auras,
2317                range,
2318                energy_cost,
2319                specifier: Some(aura::Specifier::HealingAura),
2320                ..
2321            } => {
2322                let modifiers = SKILL_MODIFIERS.sceptre_tree.healing_aura;
2323                if let Ok(level) = skillset.skill_level(Sceptre(HHeal)) {
2324                    auras.iter_mut().for_each(|ref mut aura| {
2325                        aura.strength *= modifiers.strength.powi(level.into());
2326                    });
2327                }
2328                if let Ok(level) = skillset.skill_level(Sceptre(HDuration)) {
2329                    auras.iter_mut().for_each(|ref mut aura| {
2330                        if let Some(ref mut duration) = aura.duration {
2331                            *duration *= modifiers.duration.powi(level.into()) as f64;
2332                        }
2333                    });
2334                }
2335                if let Ok(level) = skillset.skill_level(Sceptre(HRange)) {
2336                    *range *= modifiers.range.powi(level.into());
2337                }
2338                if let Ok(level) = skillset.skill_level(Sceptre(HCost)) {
2339                    *energy_cost *= modifiers.energy_cost.powi(level.into());
2340                }
2341            },
2342            CharacterAbility::BasicAura {
2343                auras,
2344                range,
2345                energy_cost,
2346                specifier: Some(aura::Specifier::WardingAura),
2347                ..
2348            } => {
2349                let modifiers = SKILL_MODIFIERS.sceptre_tree.warding_aura;
2350                if let Ok(level) = skillset.skill_level(Sceptre(AStrength)) {
2351                    auras.iter_mut().for_each(|ref mut aura| {
2352                        aura.strength *= modifiers.strength.powi(level.into());
2353                    });
2354                }
2355                if let Ok(level) = skillset.skill_level(Sceptre(ADuration)) {
2356                    auras.iter_mut().for_each(|ref mut aura| {
2357                        if let Some(ref mut duration) = aura.duration {
2358                            *duration *= modifiers.duration.powi(level.into()) as f64;
2359                        }
2360                    });
2361                }
2362                if let Ok(level) = skillset.skill_level(Sceptre(ARange)) {
2363                    *range *= modifiers.range.powi(level.into());
2364                }
2365                if let Ok(level) = skillset.skill_level(Sceptre(ACost)) {
2366                    *energy_cost *= modifiers.energy_cost.powi(level.into());
2367                }
2368            },
2369            _ => {},
2370        }
2371    }
2372}
2373
2374/// Small helper for #[serde(default)] booleans
2375fn default_true() -> bool { true }
2376
2377#[derive(Debug)]
2378pub enum CharacterStateCreationError {
2379    MissingHandInfo,
2380    MissingItem,
2381    InvalidItemKind,
2382}
2383
2384impl TryFrom<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
2385    type Error = CharacterStateCreationError;
2386
2387    fn try_from(
2388        (ability, ability_info, data): (&CharacterAbility, AbilityInfo, &JoinData),
2389    ) -> Result<Self, Self::Error> {
2390        Ok(match ability {
2391            CharacterAbility::BasicMelee {
2392                buildup_duration,
2393                swing_duration,
2394                hit_timing,
2395                recover_duration,
2396                melee_constructor,
2397                movement_modifier,
2398                ori_modifier,
2399                frontend_specifier,
2400                energy_cost: _,
2401                meta: _,
2402            } => CharacterState::BasicMelee(basic_melee::Data {
2403                static_data: basic_melee::StaticData {
2404                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2405                    swing_duration: Duration::from_secs_f32(*swing_duration),
2406                    hit_timing: hit_timing.clamp(0.0, 1.0),
2407                    recover_duration: Duration::from_secs_f32(*recover_duration),
2408                    melee_constructor: *melee_constructor,
2409                    movement_modifier: *movement_modifier,
2410                    ori_modifier: *ori_modifier,
2411                    frontend_specifier: *frontend_specifier,
2412                    ability_info,
2413                },
2414                timer: Duration::default(),
2415                stage_section: StageSection::Buildup,
2416                exhausted: false,
2417                movement_modifier: movement_modifier.buildup,
2418                ori_modifier: ori_modifier.buildup,
2419            }),
2420            CharacterAbility::BasicRanged {
2421                buildup_duration,
2422                recover_duration,
2423                projectile,
2424                projectile_body,
2425                projectile_light,
2426                projectile_speed,
2427                energy_cost: _,
2428                num_projectiles,
2429                projectile_spread,
2430                damage_effect,
2431                movement_modifier,
2432                ori_modifier,
2433                meta: _,
2434            } => CharacterState::BasicRanged(basic_ranged::Data {
2435                static_data: basic_ranged::StaticData {
2436                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2437                    recover_duration: Duration::from_secs_f32(*recover_duration),
2438                    projectile: *projectile,
2439                    projectile_body: *projectile_body,
2440                    projectile_light: *projectile_light,
2441                    projectile_speed: *projectile_speed,
2442                    num_projectiles: *num_projectiles,
2443                    projectile_spread: *projectile_spread,
2444                    ability_info,
2445                    damage_effect: *damage_effect,
2446                    movement_modifier: *movement_modifier,
2447                    ori_modifier: *ori_modifier,
2448                },
2449                timer: Duration::default(),
2450                stage_section: StageSection::Buildup,
2451                exhausted: false,
2452                movement_modifier: movement_modifier.buildup,
2453                ori_modifier: ori_modifier.buildup,
2454            }),
2455            CharacterAbility::Boost {
2456                movement_duration,
2457                only_up,
2458                speed,
2459                max_exit_velocity,
2460                meta: _,
2461            } => CharacterState::Boost(boost::Data {
2462                static_data: boost::StaticData {
2463                    movement_duration: Duration::from_secs_f32(*movement_duration),
2464                    only_up: *only_up,
2465                    speed: *speed,
2466                    max_exit_velocity: *max_exit_velocity,
2467                    ability_info,
2468                },
2469                timer: Duration::default(),
2470            }),
2471            CharacterAbility::GlideBoost { booster, meta: _ } => {
2472                let scale = data.body.dimensions().z.sqrt();
2473                let mut glide_data = glide::Data::new(scale * 4.5, scale, *data.ori);
2474                glide_data.booster = Some(*booster);
2475
2476                CharacterState::Glide(glide_data)
2477            },
2478            CharacterAbility::DashMelee {
2479                energy_cost: _,
2480                energy_drain,
2481                forward_speed,
2482                buildup_duration,
2483                charge_duration,
2484                swing_duration,
2485                recover_duration,
2486                melee_constructor,
2487                ori_modifier,
2488                auto_charge,
2489                meta: _,
2490            } => CharacterState::DashMelee(dash_melee::Data {
2491                static_data: dash_melee::StaticData {
2492                    energy_drain: *energy_drain,
2493                    forward_speed: *forward_speed,
2494                    auto_charge: *auto_charge,
2495                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2496                    charge_duration: Duration::from_secs_f32(*charge_duration),
2497                    swing_duration: Duration::from_secs_f32(*swing_duration),
2498                    recover_duration: Duration::from_secs_f32(*recover_duration),
2499                    melee_constructor: *melee_constructor,
2500                    ori_modifier: *ori_modifier,
2501                    ability_info,
2502                },
2503                auto_charge: false,
2504                timer: Duration::default(),
2505                stage_section: StageSection::Buildup,
2506            }),
2507            CharacterAbility::BasicBlock {
2508                buildup_duration,
2509                recover_duration,
2510                max_angle,
2511                block_strength,
2512                parry_window,
2513                energy_cost,
2514                energy_regen,
2515                can_hold,
2516                blocked_attacks,
2517                meta: _,
2518            } => CharacterState::BasicBlock(basic_block::Data {
2519                static_data: basic_block::StaticData {
2520                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2521                    recover_duration: Duration::from_secs_f32(*recover_duration),
2522                    max_angle: *max_angle,
2523                    block_strength: *block_strength,
2524                    parry_window: *parry_window,
2525                    energy_cost: *energy_cost,
2526                    energy_regen: *energy_regen,
2527                    can_hold: *can_hold,
2528                    blocked_attacks: *blocked_attacks,
2529                    ability_info,
2530                },
2531                timer: Duration::default(),
2532                stage_section: StageSection::Buildup,
2533                is_parry: false,
2534            }),
2535            CharacterAbility::Roll {
2536                energy_cost: _,
2537                buildup_duration,
2538                movement_duration,
2539                recover_duration,
2540                roll_strength,
2541                attack_immunities,
2542                was_cancel,
2543                meta: _,
2544            } => CharacterState::Roll(roll::Data {
2545                static_data: roll::StaticData {
2546                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2547                    movement_duration: Duration::from_secs_f32(*movement_duration),
2548                    recover_duration: Duration::from_secs_f32(*recover_duration),
2549                    roll_strength: *roll_strength,
2550                    attack_immunities: *attack_immunities,
2551                    was_cancel: *was_cancel,
2552                    ability_info,
2553                },
2554                timer: Duration::default(),
2555                stage_section: StageSection::Buildup,
2556                was_wielded: false, // false by default. utils might set it to true
2557                prev_aimed_dir: None,
2558                is_sneaking: false,
2559            }),
2560            CharacterAbility::ComboMelee2 {
2561                strikes,
2562                energy_cost_per_strike,
2563                specifier,
2564                auto_progress,
2565                meta: _,
2566            } => CharacterState::ComboMelee2(combo_melee2::Data {
2567                static_data: combo_melee2::StaticData {
2568                    strikes: strikes.iter().map(|s| s.to_duration()).collect(),
2569                    energy_cost_per_strike: *energy_cost_per_strike,
2570                    specifier: *specifier,
2571                    auto_progress: *auto_progress,
2572                    ability_info,
2573                },
2574                exhausted: false,
2575                start_next_strike: false,
2576                timer: Duration::default(),
2577                stage_section: StageSection::Buildup,
2578                completed_strikes: 0,
2579                movement_modifier: strikes.first().and_then(|s| s.movement_modifier.buildup),
2580                ori_modifier: strikes.first().and_then(|s| s.ori_modifier.buildup),
2581            }),
2582            CharacterAbility::LeapMelee {
2583                energy_cost: _,
2584                buildup_duration,
2585                movement_duration,
2586                swing_duration,
2587                recover_duration,
2588                melee_constructor,
2589                forward_leap_strength,
2590                vertical_leap_strength,
2591                damage_effect,
2592                specifier,
2593                meta: _,
2594            } => CharacterState::LeapMelee(leap_melee::Data {
2595                static_data: leap_melee::StaticData {
2596                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2597                    movement_duration: Duration::from_secs_f32(*movement_duration),
2598                    swing_duration: Duration::from_secs_f32(*swing_duration),
2599                    recover_duration: Duration::from_secs_f32(*recover_duration),
2600                    melee_constructor: *melee_constructor,
2601                    forward_leap_strength: *forward_leap_strength,
2602                    vertical_leap_strength: *vertical_leap_strength,
2603                    ability_info,
2604                    damage_effect: *damage_effect,
2605                    specifier: *specifier,
2606                },
2607                timer: Duration::default(),
2608                stage_section: StageSection::Buildup,
2609                exhausted: false,
2610            }),
2611            CharacterAbility::LeapShockwave {
2612                energy_cost: _,
2613                buildup_duration,
2614                movement_duration,
2615                swing_duration,
2616                recover_duration,
2617                damage,
2618                poise_damage,
2619                knockback,
2620                shockwave_angle,
2621                shockwave_vertical_angle,
2622                shockwave_speed,
2623                shockwave_duration,
2624                dodgeable,
2625                move_efficiency,
2626                damage_kind,
2627                specifier,
2628                damage_effect,
2629                forward_leap_strength,
2630                vertical_leap_strength,
2631                meta: _,
2632            } => CharacterState::LeapShockwave(leap_shockwave::Data {
2633                static_data: leap_shockwave::StaticData {
2634                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2635                    movement_duration: Duration::from_secs_f32(*movement_duration),
2636                    swing_duration: Duration::from_secs_f32(*swing_duration),
2637                    recover_duration: Duration::from_secs_f32(*recover_duration),
2638                    damage: *damage,
2639                    poise_damage: *poise_damage,
2640                    knockback: *knockback,
2641                    shockwave_angle: *shockwave_angle,
2642                    shockwave_vertical_angle: *shockwave_vertical_angle,
2643                    shockwave_speed: *shockwave_speed,
2644                    shockwave_duration: Duration::from_secs_f32(*shockwave_duration),
2645                    dodgeable: *dodgeable,
2646                    move_efficiency: *move_efficiency,
2647                    damage_kind: *damage_kind,
2648                    specifier: *specifier,
2649                    damage_effect: *damage_effect,
2650                    forward_leap_strength: *forward_leap_strength,
2651                    vertical_leap_strength: *vertical_leap_strength,
2652                    ability_info,
2653                },
2654                timer: Duration::default(),
2655                stage_section: StageSection::Buildup,
2656                exhausted: false,
2657            }),
2658            CharacterAbility::ChargedMelee {
2659                energy_cost,
2660                energy_drain,
2661                buildup_strike,
2662                charge_duration,
2663                swing_duration,
2664                hit_timing,
2665                recover_duration,
2666                melee_constructor,
2667                specifier,
2668                damage_effect,
2669                custom_combo,
2670                meta: _,
2671                movement_modifier,
2672                ori_modifier,
2673            } => CharacterState::ChargedMelee(charged_melee::Data {
2674                static_data: charged_melee::StaticData {
2675                    energy_cost: *energy_cost,
2676                    energy_drain: *energy_drain,
2677                    buildup_strike: buildup_strike
2678                        .map(|(dur, strike)| (Duration::from_secs_f32(dur), strike)),
2679                    charge_duration: Duration::from_secs_f32(*charge_duration),
2680                    swing_duration: Duration::from_secs_f32(*swing_duration),
2681                    hit_timing: *hit_timing,
2682                    recover_duration: Duration::from_secs_f32(*recover_duration),
2683                    melee_constructor: *melee_constructor,
2684                    ability_info,
2685                    specifier: *specifier,
2686                    damage_effect: *damage_effect,
2687                    custom_combo: *custom_combo,
2688                    movement_modifier: *movement_modifier,
2689                    ori_modifier: *ori_modifier,
2690                },
2691                stage_section: if buildup_strike.is_some() {
2692                    StageSection::Buildup
2693                } else {
2694                    StageSection::Charge
2695                },
2696                timer: Duration::default(),
2697                exhausted: false,
2698                charge_amount: 0.0,
2699                movement_modifier: movement_modifier.buildup,
2700                ori_modifier: ori_modifier.buildup,
2701            }),
2702            CharacterAbility::ChargedRanged {
2703                energy_cost: _,
2704                energy_drain,
2705                projectile,
2706                buildup_duration,
2707                charge_duration,
2708                recover_duration,
2709                projectile_body,
2710                projectile_light,
2711                initial_projectile_speed,
2712                scaled_projectile_speed,
2713                damage_effect,
2714                move_speed,
2715                meta: _,
2716            } => CharacterState::ChargedRanged(charged_ranged::Data {
2717                static_data: charged_ranged::StaticData {
2718                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2719                    charge_duration: Duration::from_secs_f32(*charge_duration),
2720                    recover_duration: Duration::from_secs_f32(*recover_duration),
2721                    energy_drain: *energy_drain,
2722                    projectile: *projectile,
2723                    projectile_body: *projectile_body,
2724                    projectile_light: *projectile_light,
2725                    initial_projectile_speed: *initial_projectile_speed,
2726                    scaled_projectile_speed: *scaled_projectile_speed,
2727                    move_speed: *move_speed,
2728                    ability_info,
2729                    damage_effect: *damage_effect,
2730                },
2731                timer: Duration::default(),
2732                stage_section: StageSection::Buildup,
2733                exhausted: false,
2734            }),
2735            CharacterAbility::RepeaterRanged {
2736                energy_cost,
2737                buildup_duration,
2738                shoot_duration,
2739                recover_duration,
2740                max_speed,
2741                half_speed_at,
2742                projectile,
2743                projectile_body,
2744                projectile_light,
2745                projectile_speed,
2746                damage_effect,
2747                properties_of_aoe,
2748                specifier,
2749                meta: _,
2750            } => CharacterState::RepeaterRanged(repeater_ranged::Data {
2751                static_data: repeater_ranged::StaticData {
2752                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2753                    shoot_duration: Duration::from_secs_f32(*shoot_duration),
2754                    recover_duration: Duration::from_secs_f32(*recover_duration),
2755                    energy_cost: *energy_cost,
2756                    // 1.0 is subtracted as 1.0 is added in state file
2757                    max_speed: *max_speed - 1.0,
2758                    half_speed_at: *half_speed_at,
2759                    projectile: *projectile,
2760                    projectile_body: *projectile_body,
2761                    projectile_light: *projectile_light,
2762                    projectile_speed: *projectile_speed,
2763                    ability_info,
2764                    damage_effect: *damage_effect,
2765                    properties_of_aoe: *properties_of_aoe,
2766                    specifier: *specifier,
2767                },
2768                timer: Duration::default(),
2769                stage_section: StageSection::Buildup,
2770                projectiles_fired: 0,
2771                speed: 1.0,
2772            }),
2773            CharacterAbility::Throw {
2774                energy_cost: _,
2775                energy_drain,
2776                buildup_duration,
2777                charge_duration,
2778                throw_duration,
2779                recover_duration,
2780                projectile,
2781                projectile_light,
2782                projectile_dir,
2783                initial_projectile_speed,
2784                scaled_projectile_speed,
2785                damage_effect,
2786                move_speed,
2787                meta: _,
2788            } => {
2789                let hand_info = if let Some(hand_info) = ability_info.hand {
2790                    hand_info
2791                } else {
2792                    return Err(CharacterStateCreationError::MissingHandInfo);
2793                };
2794
2795                let equip_slot = hand_info.to_equip_slot();
2796
2797                let equipped_item =
2798                    if let Some(item) = data.inventory.and_then(|inv| inv.equipped(equip_slot)) {
2799                        item
2800                    } else {
2801                        return Err(CharacterStateCreationError::MissingItem);
2802                    };
2803
2804                let item_hash = equipped_item.item_hash();
2805
2806                let tool_kind = if let ItemKind::Tool(Tool { kind, .. }) = *equipped_item.kind() {
2807                    kind
2808                } else {
2809                    return Err(CharacterStateCreationError::InvalidItemKind);
2810                };
2811
2812                CharacterState::Throw(throw::Data {
2813                    static_data: throw::StaticData {
2814                        buildup_duration: Duration::from_secs_f32(*buildup_duration),
2815                        charge_duration: Duration::from_secs_f32(*charge_duration),
2816                        throw_duration: Duration::from_secs_f32(*throw_duration),
2817                        recover_duration: Duration::from_secs_f32(*recover_duration),
2818                        energy_drain: *energy_drain,
2819                        projectile: *projectile,
2820                        projectile_light: *projectile_light,
2821                        projectile_dir: *projectile_dir,
2822                        initial_projectile_speed: *initial_projectile_speed,
2823                        scaled_projectile_speed: *scaled_projectile_speed,
2824                        move_speed: *move_speed,
2825                        ability_info,
2826                        damage_effect: *damage_effect,
2827                        equip_slot,
2828                        item_hash,
2829                        hand_info,
2830                        tool_kind,
2831                    },
2832                    timer: Duration::default(),
2833                    stage_section: StageSection::Buildup,
2834                    exhausted: false,
2835                })
2836            },
2837            CharacterAbility::Shockwave {
2838                energy_cost: _,
2839                buildup_duration,
2840                swing_duration,
2841                recover_duration,
2842                damage,
2843                poise_damage,
2844                knockback,
2845                shockwave_angle,
2846                shockwave_vertical_angle,
2847                shockwave_speed,
2848                shockwave_duration,
2849                dodgeable,
2850                move_efficiency,
2851                damage_kind,
2852                specifier,
2853                ori_rate,
2854                damage_effect,
2855                timing,
2856                emit_outcome,
2857                minimum_combo,
2858                combo_consumption,
2859                meta: _,
2860            } => CharacterState::Shockwave(shockwave::Data {
2861                static_data: shockwave::StaticData {
2862                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2863                    swing_duration: Duration::from_secs_f32(*swing_duration),
2864                    recover_duration: Duration::from_secs_f32(*recover_duration),
2865                    damage: *damage,
2866                    poise_damage: *poise_damage,
2867                    knockback: *knockback,
2868                    shockwave_angle: *shockwave_angle,
2869                    shockwave_vertical_angle: *shockwave_vertical_angle,
2870                    shockwave_speed: *shockwave_speed,
2871                    shockwave_duration: Duration::from_secs_f32(*shockwave_duration),
2872                    dodgeable: *dodgeable,
2873                    move_efficiency: *move_efficiency,
2874                    damage_effect: *damage_effect,
2875                    ability_info,
2876                    damage_kind: *damage_kind,
2877                    specifier: *specifier,
2878                    ori_rate: *ori_rate,
2879                    timing: *timing,
2880                    emit_outcome: *emit_outcome,
2881                    minimum_combo: *minimum_combo,
2882                    combo_on_use: data.combo.map_or(0, |c| c.counter()),
2883                    combo_consumption: *combo_consumption,
2884                },
2885                timer: Duration::default(),
2886                stage_section: StageSection::Buildup,
2887            }),
2888            CharacterAbility::BasicBeam {
2889                buildup_duration,
2890                recover_duration,
2891                beam_duration,
2892                damage,
2893                tick_rate,
2894                range,
2895                max_angle,
2896                damage_effect,
2897                energy_regen,
2898                energy_drain,
2899                ori_rate,
2900                specifier,
2901                meta: _,
2902            } => CharacterState::BasicBeam(basic_beam::Data {
2903                static_data: basic_beam::StaticData {
2904                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2905                    recover_duration: Duration::from_secs_f32(*recover_duration),
2906                    beam_duration: Secs(*beam_duration),
2907                    damage: *damage,
2908                    tick_rate: *tick_rate,
2909                    range: *range,
2910                    end_radius: max_angle.to_radians().tan() * *range,
2911                    damage_effect: *damage_effect,
2912                    energy_regen: *energy_regen,
2913                    energy_drain: *energy_drain,
2914                    ability_info,
2915                    ori_rate: *ori_rate,
2916                    specifier: *specifier,
2917                },
2918                timer: Duration::default(),
2919                stage_section: StageSection::Buildup,
2920                aim_dir: data.ori.look_dir(),
2921                beam_offset: data.pos.0,
2922            }),
2923            CharacterAbility::BasicAura {
2924                buildup_duration,
2925                cast_duration,
2926                recover_duration,
2927                targets,
2928                auras,
2929                aura_duration,
2930                range,
2931                energy_cost: _,
2932                scales_with_combo,
2933                specifier,
2934                meta: _,
2935            } => CharacterState::BasicAura(basic_aura::Data {
2936                static_data: basic_aura::StaticData {
2937                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2938                    cast_duration: Duration::from_secs_f32(*cast_duration),
2939                    recover_duration: Duration::from_secs_f32(*recover_duration),
2940                    targets: *targets,
2941                    auras: auras.clone(),
2942                    aura_duration: *aura_duration,
2943                    range: *range,
2944                    ability_info,
2945                    scales_with_combo: *scales_with_combo,
2946                    combo_at_cast: data.combo.map_or(0, |c| c.counter()),
2947                    specifier: *specifier,
2948                },
2949                timer: Duration::default(),
2950                stage_section: StageSection::Buildup,
2951            }),
2952            CharacterAbility::StaticAura {
2953                buildup_duration,
2954                cast_duration,
2955                recover_duration,
2956                targets,
2957                auras,
2958                aura_duration,
2959                range,
2960                energy_cost: _,
2961                sprite_info,
2962                meta: _,
2963            } => CharacterState::StaticAura(static_aura::Data {
2964                static_data: static_aura::StaticData {
2965                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2966                    cast_duration: Duration::from_secs_f32(*cast_duration),
2967                    recover_duration: Duration::from_secs_f32(*recover_duration),
2968                    targets: *targets,
2969                    auras: auras.clone(),
2970                    aura_duration: *aura_duration,
2971                    range: *range,
2972                    ability_info,
2973                    sprite_info: *sprite_info,
2974                },
2975                timer: Duration::default(),
2976                stage_section: StageSection::Buildup,
2977                achieved_radius: sprite_info.map(|si| si.summon_distance.0.floor() as i32 - 1),
2978            }),
2979            CharacterAbility::Blink {
2980                buildup_duration,
2981                recover_duration,
2982                max_range,
2983                frontend_specifier,
2984                meta: _,
2985            } => CharacterState::Blink(blink::Data {
2986                static_data: blink::StaticData {
2987                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2988                    recover_duration: Duration::from_secs_f32(*recover_duration),
2989                    max_range: *max_range,
2990                    frontend_specifier: *frontend_specifier,
2991                    ability_info,
2992                },
2993                timer: Duration::default(),
2994                stage_section: StageSection::Buildup,
2995            }),
2996            CharacterAbility::BasicSummon {
2997                buildup_duration,
2998                cast_duration,
2999                recover_duration,
3000                summon_amount,
3001                summon_distance,
3002                summon_info,
3003                duration,
3004                meta: _,
3005            } => CharacterState::BasicSummon(basic_summon::Data {
3006                static_data: basic_summon::StaticData {
3007                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3008                    cast_duration: Duration::from_secs_f32(*cast_duration),
3009                    recover_duration: Duration::from_secs_f32(*recover_duration),
3010                    summon_amount: *summon_amount,
3011                    summon_distance: *summon_distance,
3012                    summon_info: *summon_info,
3013                    ability_info,
3014                    duration: *duration,
3015                },
3016                summon_count: 0,
3017                timer: Duration::default(),
3018                stage_section: StageSection::Buildup,
3019            }),
3020            CharacterAbility::SelfBuff {
3021                buildup_duration,
3022                cast_duration,
3023                recover_duration,
3024                buff_kind,
3025                buff_strength,
3026                buff_duration,
3027                energy_cost: _,
3028                combo_cost,
3029                combo_scaling,
3030                enforced_limit,
3031                meta: _,
3032                specifier,
3033            } => CharacterState::SelfBuff(self_buff::Data {
3034                static_data: self_buff::StaticData {
3035                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3036                    cast_duration: Duration::from_secs_f32(*cast_duration),
3037                    recover_duration: Duration::from_secs_f32(*recover_duration),
3038                    buff_kind: *buff_kind,
3039                    buff_strength: *buff_strength,
3040                    buff_duration: *buff_duration,
3041                    combo_cost: *combo_cost,
3042                    combo_scaling: *combo_scaling,
3043                    combo_on_use: data.combo.map_or(0, |c| c.counter()),
3044                    enforced_limit: *enforced_limit,
3045                    ability_info,
3046                    specifier: *specifier,
3047                },
3048                timer: Duration::default(),
3049                stage_section: StageSection::Buildup,
3050            }),
3051            CharacterAbility::SpriteSummon {
3052                buildup_duration,
3053                cast_duration,
3054                recover_duration,
3055                sprite,
3056                del_timeout,
3057                summon_distance,
3058                sparseness,
3059                angle,
3060                anchor,
3061                move_efficiency,
3062                meta: _,
3063            } => CharacterState::SpriteSummon(sprite_summon::Data {
3064                static_data: sprite_summon::StaticData {
3065                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3066                    cast_duration: Duration::from_secs_f32(*cast_duration),
3067                    recover_duration: Duration::from_secs_f32(*recover_duration),
3068                    sprite: *sprite,
3069                    del_timeout: *del_timeout,
3070                    summon_distance: *summon_distance,
3071                    sparseness: *sparseness,
3072                    angle: *angle,
3073                    anchor: *anchor,
3074                    move_efficiency: *move_efficiency,
3075                    ability_info,
3076                },
3077                timer: Duration::default(),
3078                stage_section: StageSection::Buildup,
3079                achieved_radius: summon_distance.0.floor() as i32 - 1,
3080            }),
3081            CharacterAbility::Music {
3082                play_duration,
3083                ori_modifier,
3084                meta: _,
3085            } => CharacterState::Music(music::Data {
3086                static_data: music::StaticData {
3087                    play_duration: Duration::from_secs_f32(*play_duration),
3088                    ori_modifier: *ori_modifier,
3089                    ability_info,
3090                },
3091                timer: Duration::default(),
3092                stage_section: StageSection::Action,
3093                exhausted: false,
3094            }),
3095            CharacterAbility::FinisherMelee {
3096                energy_cost: _,
3097                buildup_duration,
3098                swing_duration,
3099                recover_duration,
3100                melee_constructor,
3101                minimum_combo,
3102                scaling,
3103                combo_consumption,
3104                meta: _,
3105            } => CharacterState::FinisherMelee(finisher_melee::Data {
3106                static_data: finisher_melee::StaticData {
3107                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3108                    swing_duration: Duration::from_secs_f32(*swing_duration),
3109                    recover_duration: Duration::from_secs_f32(*recover_duration),
3110                    melee_constructor: *melee_constructor,
3111                    scaling: *scaling,
3112                    minimum_combo: *minimum_combo,
3113                    combo_on_use: data.combo.map_or(0, |c| c.counter()),
3114                    combo_consumption: *combo_consumption,
3115                    ability_info,
3116                },
3117                timer: Duration::default(),
3118                stage_section: StageSection::Buildup,
3119                exhausted: false,
3120            }),
3121            CharacterAbility::DiveMelee {
3122                buildup_duration,
3123                movement_duration,
3124                swing_duration,
3125                recover_duration,
3126                melee_constructor,
3127                energy_cost: _,
3128                vertical_speed,
3129                max_scaling,
3130                meta: _,
3131            } => CharacterState::DiveMelee(dive_melee::Data {
3132                static_data: dive_melee::StaticData {
3133                    buildup_duration: buildup_duration.map(Duration::from_secs_f32),
3134                    movement_duration: Duration::from_secs_f32(*movement_duration),
3135                    swing_duration: Duration::from_secs_f32(*swing_duration),
3136                    recover_duration: Duration::from_secs_f32(*recover_duration),
3137                    vertical_speed: *vertical_speed,
3138                    melee_constructor: *melee_constructor,
3139                    max_scaling: *max_scaling,
3140                    ability_info,
3141                },
3142                timer: Duration::default(),
3143                stage_section: if data.physics.on_ground.is_none() || buildup_duration.is_none() {
3144                    StageSection::Movement
3145                } else {
3146                    StageSection::Buildup
3147                },
3148                exhausted: false,
3149                max_vertical_speed: 0.0,
3150            }),
3151            CharacterAbility::RiposteMelee {
3152                energy_cost: _,
3153                buildup_duration,
3154                swing_duration,
3155                recover_duration,
3156                whiffed_recover_duration,
3157                block_strength,
3158                melee_constructor,
3159                meta: _,
3160            } => CharacterState::RiposteMelee(riposte_melee::Data {
3161                static_data: riposte_melee::StaticData {
3162                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3163                    swing_duration: Duration::from_secs_f32(*swing_duration),
3164                    recover_duration: Duration::from_secs_f32(*recover_duration),
3165                    whiffed_recover_duration: Duration::from_secs_f32(*whiffed_recover_duration),
3166                    block_strength: *block_strength,
3167                    melee_constructor: *melee_constructor,
3168                    ability_info,
3169                },
3170                timer: Duration::default(),
3171                stage_section: StageSection::Buildup,
3172                exhausted: false,
3173                whiffed: true,
3174            }),
3175            CharacterAbility::RapidMelee {
3176                buildup_duration,
3177                swing_duration,
3178                recover_duration,
3179                melee_constructor,
3180                energy_cost,
3181                max_strikes,
3182                move_modifier,
3183                ori_modifier,
3184                minimum_combo,
3185                frontend_specifier,
3186                meta: _,
3187            } => CharacterState::RapidMelee(rapid_melee::Data {
3188                static_data: rapid_melee::StaticData {
3189                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3190                    swing_duration: Duration::from_secs_f32(*swing_duration),
3191                    recover_duration: Duration::from_secs_f32(*recover_duration),
3192                    melee_constructor: *melee_constructor,
3193                    energy_cost: *energy_cost,
3194                    max_strikes: *max_strikes,
3195                    move_modifier: *move_modifier,
3196                    ori_modifier: *ori_modifier,
3197                    minimum_combo: *minimum_combo,
3198                    frontend_specifier: *frontend_specifier,
3199                    ability_info,
3200                },
3201                timer: Duration::default(),
3202                current_strike: 1,
3203                stage_section: StageSection::Buildup,
3204                exhausted: false,
3205            }),
3206            CharacterAbility::Transform {
3207                buildup_duration,
3208                recover_duration,
3209                target,
3210                specifier,
3211                allow_players,
3212                meta: _,
3213            } => CharacterState::Transform(transform::Data {
3214                static_data: transform::StaticData {
3215                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3216                    recover_duration: Duration::from_secs_f32(*recover_duration),
3217                    specifier: *specifier,
3218                    allow_players: *allow_players,
3219                    target: target.to_owned(),
3220                    ability_info,
3221                },
3222                timer: Duration::default(),
3223                stage_section: StageSection::Buildup,
3224            }),
3225            CharacterAbility::RegrowHead {
3226                buildup_duration,
3227                recover_duration,
3228                energy_cost,
3229                specifier,
3230                meta: _,
3231            } => CharacterState::RegrowHead(regrow_head::Data {
3232                static_data: regrow_head::StaticData {
3233                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3234                    recover_duration: Duration::from_secs_f32(*recover_duration),
3235                    specifier: *specifier,
3236                    energy_cost: *energy_cost,
3237                    ability_info,
3238                },
3239                timer: Duration::default(),
3240                stage_section: StageSection::Buildup,
3241            }),
3242        })
3243    }
3244}
3245
3246#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
3247#[serde(deny_unknown_fields)]
3248pub struct AbilityMeta {
3249    #[serde(default)]
3250    pub capabilities: Capability,
3251    #[serde(default)]
3252    /// This is an event that gets emitted when the ability is first activated
3253    pub init_event: Option<AbilityInitEvent>,
3254    #[serde(default)]
3255    pub requirements: AbilityRequirements,
3256    /// Adjusts stats of ability when activated based on context.
3257    // If we ever add more, I guess change to a vec? Or maybe just an array if we want to keep
3258    // AbilityMeta small?
3259    pub contextual_stats: Option<StatAdj>,
3260}
3261
3262impl StatAdj {
3263    pub fn equivalent_stats(&self, data: &JoinData) -> Stats {
3264        let mut stats = Stats::one();
3265        let add = match self.context {
3266            StatContext::PoiseResilience(base) => {
3267                let poise_res = combat::compute_poise_resilience(data.inventory, data.msm);
3268                poise_res.unwrap_or(0.0) / base
3269            },
3270        };
3271        match self.field {
3272            StatField::EffectPower => {
3273                stats.effect_power += add;
3274            },
3275            StatField::BuffStrength => {
3276                stats.buff_strength += add;
3277            },
3278            StatField::Power => {
3279                stats.power += add;
3280            },
3281        }
3282        stats
3283    }
3284}
3285
3286#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3287pub struct StatAdj {
3288    pub context: StatContext,
3289    pub field: StatField,
3290}
3291
3292#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3293pub enum StatContext {
3294    PoiseResilience(f32),
3295}
3296
3297#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3298pub enum StatField {
3299    EffectPower,
3300    BuffStrength,
3301    Power,
3302}
3303
3304// TODO: Later move over things like energy and combo into here
3305#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
3306pub struct AbilityRequirements {
3307    pub stance: Option<Stance>,
3308}
3309
3310impl AbilityRequirements {
3311    pub fn requirements_met(&self, stance: Option<&Stance>) -> bool {
3312        let AbilityRequirements { stance: req_stance } = self;
3313        req_stance
3314            .is_none_or(|req_stance| stance.is_some_and(|char_stance| req_stance == *char_stance))
3315    }
3316}
3317
3318#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
3319pub enum SwordStance {
3320    Crippling,
3321    Cleaving,
3322    Defensive,
3323    Heavy,
3324    Agile,
3325}
3326
3327bitflags::bitflags! {
3328    #[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
3329    // If more are ever needed, first check if any not used anymore, as some were only used in intermediary stages so may be free
3330    pub struct Capability: u8 {
3331        // The ability will parry all blockable attacks in the buildup portion
3332        const PARRIES             = 0b00000001;
3333        // Allows blocking to interrupt the ability at any point
3334        const BLOCK_INTERRUPT     = 0b00000010;
3335        // The ability will block melee attacks in the buildup portion
3336        const BLOCKS              = 0b00000100;
3337        // When in the ability, an entity only receives half as much poise damage
3338        const POISE_RESISTANT     = 0b00001000;
3339        // WHen in the ability, an entity only receives half as much knockback
3340        const KNOCKBACK_RESISTANT = 0b00010000;
3341        // The ability will parry melee attacks in the buildup portion
3342        const PARRIES_MELEE       = 0b00100000;
3343    }
3344}
3345
3346#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
3347pub enum Stance {
3348    None,
3349    Sword(SwordStance),
3350}
3351
3352impl Stance {
3353    pub fn pseudo_ability_id(&self) -> &str {
3354        match self {
3355            Stance::Sword(SwordStance::Heavy) => "veloren.core.pseudo_abilities.sword.heavy_stance",
3356            Stance::Sword(SwordStance::Agile) => "veloren.core.pseudo_abilities.sword.agile_stance",
3357            Stance::Sword(SwordStance::Defensive) => {
3358                "veloren.core.pseudo_abilities.sword.defensive_stance"
3359            },
3360            Stance::Sword(SwordStance::Crippling) => {
3361                "veloren.core.pseudo_abilities.sword.crippling_stance"
3362            },
3363            Stance::Sword(SwordStance::Cleaving) => {
3364                "veloren.core.pseudo_abilities.sword.cleaving_stance"
3365            },
3366            Stance::None => "veloren.core.pseudo_abilities.no_stance",
3367        }
3368    }
3369}
3370
3371#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3372pub enum AbilityInitEvent {
3373    EnterStance(Stance),
3374    GainBuff {
3375        kind: buff::BuffKind,
3376        strength: f32,
3377        duration: Option<Secs>,
3378    },
3379}
3380
3381impl Default for Stance {
3382    fn default() -> Self { Self::None }
3383}
3384
3385impl Component for Stance {
3386    type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
3387}