veloren_common/comp/
ability.rs

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