veloren_common/comp/
ability.rs

1use crate::{
2    combat::{self, CombatEffect, DamageKind, Knockback, ScalingKind},
3    comp::{
4        self, Body, CharacterState, LightEmitter, StateUpdate, aura, beam, buff,
5        character_state::AttackFilters,
6        inventory::{
7            Inventory,
8            item::{
9                ItemDefinitionIdOwned, 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, ProjectileSpread,
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    RapidRanged,
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::RapidRanged(_) => Self::RapidRanged,
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(_)
740            | CharacterState::LeapRanged(_)
741            | CharacterState::Simple(_) => Self::Other,
742        }
743    }
744}
745
746#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
747pub enum Dodgeable {
748    #[default]
749    Roll,
750    Jump,
751    No,
752}
753
754#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
755pub enum Amount {
756    PerHead(u32),
757    Value(u32),
758}
759
760impl Amount {
761    pub fn add(&mut self, value: u32) {
762        match self {
763            Self::PerHead(v) | Self::Value(v) => *v += value,
764        }
765    }
766
767    pub fn compute(&self, heads: u32) -> u32 {
768        match self {
769            Amount::PerHead(v) => v * heads,
770            Amount::Value(v) => *v,
771        }
772    }
773}
774
775impl Default for Amount {
776    fn default() -> Self { Self::Value(1) }
777}
778
779#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
780#[serde(deny_unknown_fields)]
781/// For documentation on individual fields, see the corresponding character
782/// state file in 'common/src/states/'
783pub enum CharacterAbility {
784    BasicMelee {
785        energy_cost: f32,
786        buildup_duration: f32,
787        swing_duration: f32,
788        hit_timing: f32,
789        recover_duration: f32,
790        melee_constructor: MeleeConstructor,
791        #[serde(default)]
792        movement_modifier: MovementModifier,
793        #[serde(default)]
794        ori_modifier: OrientationModifier,
795        frontend_specifier: Option<basic_melee::FrontendSpecifier>,
796        #[serde(default)]
797        meta: AbilityMeta,
798    },
799    BasicRanged {
800        energy_cost: f32,
801        buildup_duration: f32,
802        recover_duration: f32,
803        projectile: ProjectileConstructor,
804        projectile_body: Body,
805        projectile_light: Option<LightEmitter>,
806        projectile_speed: f32,
807        #[serde(default)]
808        vertical_angle_offset: f32,
809        #[serde(default)]
810        num_projectiles: Amount,
811        projectile_spread: Option<ProjectileSpread>,
812        #[serde(default)]
813        auto_aim: bool,
814        #[serde(default)]
815        movement_modifier: MovementModifier,
816        #[serde(default)]
817        ori_modifier: OrientationModifier,
818        #[serde(default)]
819        marker: Option<comp::FrontendMarker>,
820        #[serde(default)]
821        meta: AbilityMeta,
822    },
823    RapidRanged {
824        #[serde(default)]
825        initial_energy: f32,
826        #[serde(default)]
827        energy_cost: f32,
828        buildup_duration: f32,
829        shoot_duration: f32,
830        recover_duration: f32,
831        options: rapid_ranged::Options,
832        projectile: ProjectileConstructor,
833        projectile_body: Body,
834        projectile_light: Option<LightEmitter>,
835        projectile_speed: f32,
836        specifier: Option<rapid_ranged::FrontendSpecifier>,
837        #[serde(default)]
838        meta: AbilityMeta,
839    },
840    Boost {
841        movement_duration: f32,
842        only_up: bool,
843        speed: f32,
844        max_exit_velocity: f32,
845        #[serde(default)]
846        meta: AbilityMeta,
847    },
848    GlideBoost {
849        booster: glide::Boost,
850        #[serde(default)]
851        meta: AbilityMeta,
852    },
853    DashMelee {
854        energy_cost: f32,
855        energy_drain: f32,
856        forward_speed: f32,
857        buildup_duration: f32,
858        charge_duration: f32,
859        swing_duration: f32,
860        recover_duration: f32,
861        melee_constructor: MeleeConstructor,
862        ori_modifier: f32,
863        auto_charge: bool,
864        #[serde(default)]
865        charge_through: bool,
866        #[serde(default)]
867        frontend_specifier: Option<dash_melee::FrontendSpecifier>,
868        #[serde(default)]
869        meta: AbilityMeta,
870    },
871    BasicBlock {
872        buildup_duration: f32,
873        recover_duration: f32,
874        max_angle: f32,
875        block_strength: f32,
876        parry_window: basic_block::ParryWindow,
877        energy_cost: f32,
878        energy_regen: f32,
879        can_hold: bool,
880        blocked_attacks: AttackFilters,
881        #[serde(default)]
882        meta: AbilityMeta,
883    },
884    Roll {
885        energy_cost: f32,
886        buildup_duration: f32,
887        movement_duration: f32,
888        recover_duration: f32,
889        roll_strength: f32,
890        attack_immunities: AttackFilters,
891        was_cancel: bool,
892        #[serde(default)]
893        meta: AbilityMeta,
894    },
895    ComboMelee2 {
896        strikes: Vec<combo_melee2::Strike<f32>>,
897        energy_cost_per_strike: f32,
898        specifier: Option<combo_melee2::FrontendSpecifier>,
899        #[serde(default)]
900        auto_progress: bool,
901        #[serde(default)]
902        meta: AbilityMeta,
903    },
904    LeapExplosionShockwave {
905        energy_cost: f32,
906        buildup_duration: f32,
907        movement_duration: f32,
908        swing_duration: f32,
909        recover_duration: f32,
910        forward_leap_strength: f32,
911        vertical_leap_strength: f32,
912        explosion_damage: f32,
913        explosion_poise: f32,
914        explosion_knockback: Knockback,
915        explosion_radius: f32,
916        min_falloff: f32,
917        #[serde(default)]
918        explosion_dodgeable: Dodgeable,
919        #[serde(default)]
920        destroy_terrain: Option<(f32, ColorPreset)>,
921        #[serde(default)]
922        replace_terrain: Option<(f32, TerrainReplacementPreset)>,
923        #[serde(default)]
924        eye_height: bool,
925        #[serde(default)]
926        reagent: Option<Reagent>,
927        shockwave_damage: f32,
928        shockwave_poise: f32,
929        shockwave_knockback: Knockback,
930        shockwave_angle: f32,
931        shockwave_vertical_angle: f32,
932        shockwave_speed: f32,
933        shockwave_duration: f32,
934        #[serde(default)]
935        shockwave_dodgeable: Dodgeable,
936        #[serde(default)]
937        shockwave_damage_effect: Option<CombatEffect>,
938        shockwave_damage_kind: DamageKind,
939        shockwave_specifier: comp::shockwave::FrontendSpecifier,
940        move_efficiency: f32,
941        #[serde(default)]
942        meta: AbilityMeta,
943    },
944    LeapMelee {
945        energy_cost: f32,
946        buildup_duration: f32,
947        movement_duration: f32,
948        swing_duration: f32,
949        recover_duration: f32,
950        melee_constructor: MeleeConstructor,
951        forward_leap_strength: f32,
952        vertical_leap_strength: f32,
953        specifier: Option<leap_melee::FrontendSpecifier>,
954        #[serde(default)]
955        meta: AbilityMeta,
956    },
957    LeapShockwave {
958        energy_cost: f32,
959        buildup_duration: f32,
960        movement_duration: f32,
961        swing_duration: f32,
962        recover_duration: f32,
963        damage: f32,
964        poise_damage: f32,
965        knockback: Knockback,
966        shockwave_angle: f32,
967        shockwave_vertical_angle: f32,
968        shockwave_speed: f32,
969        shockwave_duration: f32,
970        dodgeable: Dodgeable,
971        move_efficiency: f32,
972        damage_kind: DamageKind,
973        specifier: comp::shockwave::FrontendSpecifier,
974        damage_effect: Option<CombatEffect>,
975        forward_leap_strength: f32,
976        vertical_leap_strength: f32,
977        #[serde(default)]
978        meta: AbilityMeta,
979    },
980    ChargedMelee {
981        energy_cost: f32,
982        energy_drain: f32,
983        buildup_strike: Option<(f32, MeleeConstructor)>,
984        charge_duration: f32,
985        swing_duration: f32,
986        hit_timing: f32,
987        recover_duration: f32,
988        melee_constructor: MeleeConstructor,
989        specifier: Option<charged_melee::FrontendSpecifier>,
990        #[serde(default)]
991        custom_combo: CustomCombo,
992        #[serde(default)]
993        meta: AbilityMeta,
994        #[serde(default)]
995        movement_modifier: MovementModifier,
996        #[serde(default)]
997        ori_modifier: OrientationModifier,
998    },
999    ChargedRanged {
1000        energy_cost: f32,
1001        energy_drain: f32,
1002        idle_drain: f32,
1003        projectile: ProjectileConstructor,
1004        buildup_duration: f32,
1005        charge_duration: f32,
1006        recover_duration: f32,
1007        projectile_body: Body,
1008        projectile_light: Option<LightEmitter>,
1009        initial_projectile_speed: f32,
1010        scaled_projectile_speed: f32,
1011        projectile_spread: Option<ProjectileSpread>,
1012        #[serde(default)]
1013        num_projectiles: Amount,
1014        marker: Option<comp::FrontendMarker>,
1015        move_speed: f32,
1016        #[serde(default)]
1017        meta: AbilityMeta,
1018    },
1019    Throw {
1020        energy_cost: f32,
1021        energy_drain: f32,
1022        buildup_duration: f32,
1023        charge_duration: f32,
1024        throw_duration: f32,
1025        recover_duration: f32,
1026        projectile: ProjectileConstructor,
1027        projectile_light: Option<LightEmitter>,
1028        projectile_dir: throw::ProjectileDir,
1029        initial_projectile_speed: f32,
1030        scaled_projectile_speed: f32,
1031        damage_effect: Option<CombatEffect>,
1032        move_speed: f32,
1033        #[serde(default)]
1034        meta: AbilityMeta,
1035    },
1036    Shockwave {
1037        energy_cost: f32,
1038        buildup_duration: f32,
1039        swing_duration: f32,
1040        recover_duration: f32,
1041        damage: f32,
1042        poise_damage: f32,
1043        knockback: Knockback,
1044        shockwave_angle: f32,
1045        shockwave_vertical_angle: f32,
1046        shockwave_speed: f32,
1047        shockwave_duration: f32,
1048        dodgeable: Dodgeable,
1049        move_efficiency: f32,
1050        damage_kind: DamageKind,
1051        specifier: comp::shockwave::FrontendSpecifier,
1052        ori_rate: f32,
1053        damage_effect: Option<CombatEffect>,
1054        timing: shockwave::Timing,
1055        emit_outcome: bool,
1056        minimum_combo: Option<u32>,
1057        #[serde(default)]
1058        combo_consumption: ComboConsumption,
1059        #[serde(default)]
1060        meta: AbilityMeta,
1061    },
1062    Explosion {
1063        energy_cost: f32,
1064        buildup_duration: f32,
1065        action_duration: f32,
1066        recover_duration: f32,
1067        damage: f32,
1068        poise: f32,
1069        knockback: Knockback,
1070        radius: f32,
1071        min_falloff: f32,
1072        #[serde(default)]
1073        dodgeable: Dodgeable,
1074        #[serde(default)]
1075        destroy_terrain: Option<(f32, ColorPreset)>,
1076        #[serde(default)]
1077        replace_terrain: Option<(f32, TerrainReplacementPreset)>,
1078        #[serde(default)]
1079        eye_height: bool,
1080        #[serde(default)]
1081        reagent: Option<Reagent>,
1082        #[serde(default)]
1083        movement_modifier: MovementModifier,
1084        #[serde(default)]
1085        ori_modifier: OrientationModifier,
1086        #[serde(default)]
1087        meta: AbilityMeta,
1088    },
1089    BasicBeam {
1090        buildup_duration: f32,
1091        recover_duration: f32,
1092        beam_duration: f64,
1093        damage: f32,
1094        tick_rate: f32,
1095        range: f32,
1096        #[serde(default)]
1097        dodgeable: Dodgeable,
1098        #[serde(default = "default_true")]
1099        blockable: bool,
1100        max_angle: f32,
1101        damage_effect: Option<CombatEffect>,
1102        energy_regen: f32,
1103        energy_drain: f32,
1104        ori_rate: f32,
1105        move_efficiency: f32,
1106        specifier: beam::FrontendSpecifier,
1107        #[serde(default)]
1108        meta: AbilityMeta,
1109    },
1110    BasicAura {
1111        buildup_duration: f32,
1112        cast_duration: f32,
1113        recover_duration: f32,
1114        targets: combat::GroupTarget,
1115        auras: Vec<aura::AuraBuffConstructor>,
1116        aura_duration: Option<Secs>,
1117        range: f32,
1118        energy_cost: f32,
1119        scales_with_combo: bool,
1120        specifier: Option<aura::Specifier>,
1121        #[serde(default)]
1122        meta: AbilityMeta,
1123    },
1124    StaticAura {
1125        buildup_duration: f32,
1126        cast_duration: f32,
1127        recover_duration: f32,
1128        energy_cost: f32,
1129        targets: combat::GroupTarget,
1130        auras: Vec<aura::AuraBuffConstructor>,
1131        aura_duration: Option<Secs>,
1132        range: f32,
1133        sprite_info: Option<static_aura::SpriteInfo>,
1134        #[serde(default)]
1135        meta: AbilityMeta,
1136    },
1137    Blink {
1138        buildup_duration: f32,
1139        recover_duration: f32,
1140        max_range: f32,
1141        frontend_specifier: Option<blink::FrontendSpecifier>,
1142        #[serde(default)]
1143        meta: AbilityMeta,
1144    },
1145    BasicSummon {
1146        buildup_duration: f32,
1147        cast_duration: f32,
1148        recover_duration: f32,
1149        summon_info: basic_summon::SummonInfo,
1150        #[serde(default)]
1151        movement_modifier: MovementModifier,
1152        #[serde(default)]
1153        ori_modifier: OrientationModifier,
1154        #[serde(default)]
1155        meta: AbilityMeta,
1156    },
1157    SelfBuff {
1158        buildup_duration: f32,
1159        cast_duration: f32,
1160        recover_duration: f32,
1161        buffs: Vec<self_buff::BuffDesc>,
1162        #[serde(default)]
1163        use_raw_buff_strength: bool,
1164        buff_cat: Option<buff::BuffCategory>,
1165        energy_cost: f32,
1166        #[serde(default = "default_true")]
1167        enforced_limit: bool,
1168        #[serde(default)]
1169        combo_cost: u32,
1170        combo_scaling: Option<ScalingKind>,
1171        #[serde(default)]
1172        meta: AbilityMeta,
1173        specifier: Option<self_buff::FrontendSpecifier>,
1174    },
1175    SpriteSummon {
1176        buildup_duration: f32,
1177        cast_duration: f32,
1178        recover_duration: f32,
1179        sprite: SpriteKind,
1180        del_timeout: Option<(f32, f32)>,
1181        summon_distance: (f32, f32),
1182        sparseness: f64,
1183        angle: f32,
1184        #[serde(default)]
1185        anchor: SpriteSummonAnchor,
1186        #[serde(default)]
1187        move_efficiency: f32,
1188        ori_modifier: f32,
1189        #[serde(default)]
1190        meta: AbilityMeta,
1191    },
1192    Music {
1193        play_duration: f32,
1194        ori_modifier: f32,
1195        #[serde(default)]
1196        meta: AbilityMeta,
1197    },
1198    FinisherMelee {
1199        energy_cost: f32,
1200        buildup_duration: f32,
1201        swing_duration: f32,
1202        recover_duration: f32,
1203        melee_constructor: MeleeConstructor,
1204        minimum_combo: u32,
1205        scaling: Option<finisher_melee::Scaling>,
1206        #[serde(default)]
1207        combo_consumption: ComboConsumption,
1208        #[serde(default)]
1209        meta: AbilityMeta,
1210    },
1211    DiveMelee {
1212        energy_cost: f32,
1213        vertical_speed: f32,
1214        buildup_duration: Option<f32>,
1215        movement_duration: f32,
1216        swing_duration: f32,
1217        recover_duration: f32,
1218        melee_constructor: MeleeConstructor,
1219        max_scaling: f32,
1220        #[serde(default)]
1221        meta: AbilityMeta,
1222    },
1223    RiposteMelee {
1224        energy_cost: f32,
1225        buildup_duration: f32,
1226        swing_duration: f32,
1227        recover_duration: f32,
1228        whiffed_recover_duration: f32,
1229        block_strength: f32,
1230        melee_constructor: MeleeConstructor,
1231        #[serde(default)]
1232        meta: AbilityMeta,
1233    },
1234    RapidMelee {
1235        buildup_duration: f32,
1236        swing_duration: f32,
1237        recover_duration: f32,
1238        energy_cost: f32,
1239        max_strikes: Option<u32>,
1240        melee_constructor: MeleeConstructor,
1241        move_modifier: f32,
1242        ori_modifier: f32,
1243        frontend_specifier: Option<rapid_melee::FrontendSpecifier>,
1244        #[serde(default)]
1245        minimum_combo: u32,
1246        #[serde(default)]
1247        meta: AbilityMeta,
1248    },
1249    Transform {
1250        buildup_duration: f32,
1251        recover_duration: f32,
1252        target: String,
1253        #[serde(default)]
1254        specifier: Option<transform::FrontendSpecifier>,
1255        /// Only set to `true` for admin only abilities since this disables
1256        /// persistence and is not intended to be used by regular players
1257        #[serde(default)]
1258        allow_players: bool,
1259        #[serde(default)]
1260        meta: AbilityMeta,
1261    },
1262    RegrowHead {
1263        buildup_duration: f32,
1264        recover_duration: f32,
1265        energy_cost: f32,
1266        #[serde(default)]
1267        specifier: Option<regrow_head::FrontendSpecifier>,
1268        #[serde(default)]
1269        meta: AbilityMeta,
1270    },
1271    LeapRanged {
1272        energy_cost: f32,
1273        buildup_duration: f32,
1274        buildup_melee_timing: f32,
1275        movement_duration: f32,
1276        movement_ranged_timing: f32,
1277        land_timeout: f32,
1278        recover_duration: f32,
1279        melee: Option<MeleeConstructor>,
1280        melee_required: bool,
1281        projectile: ProjectileConstructor,
1282        projectile_body: Body,
1283        projectile_light: Option<LightEmitter>,
1284        projectile_speed: f32,
1285        horiz_leap_strength: f32,
1286        vert_leap_strength: f32,
1287        #[serde(default)]
1288        meta: AbilityMeta,
1289    },
1290    Simple {
1291        energy_cost: f32,
1292        combo_cost: u32,
1293        buildup_duration: f32,
1294        #[serde(default)]
1295        meta: AbilityMeta,
1296    },
1297}
1298
1299impl Default for CharacterAbility {
1300    fn default() -> Self {
1301        CharacterAbility::BasicMelee {
1302            energy_cost: 0.0,
1303            buildup_duration: 0.25,
1304            swing_duration: 0.25,
1305            hit_timing: 0.5,
1306            recover_duration: 0.5,
1307            melee_constructor: MeleeConstructor {
1308                kind: MeleeConstructorKind::Slash {
1309                    damage: 1.0,
1310                    knockback: 0.0,
1311                    poise: 0.0,
1312                    energy_regen: 0.0,
1313                },
1314                scaled: None,
1315                range: 3.5,
1316                angle: 15.0,
1317                multi_target: None,
1318                damage_effect: None,
1319                attack_effect: None,
1320                simultaneous_hits: 1,
1321                custom_combo: CustomCombo {
1322                    base: None,
1323                    conditional: None,
1324                },
1325                dodgeable: Dodgeable::Roll,
1326                blockable: true,
1327                precision_flank_multipliers: Default::default(),
1328                precision_flank_invert: false,
1329            },
1330            movement_modifier: Default::default(),
1331            ori_modifier: Default::default(),
1332            frontend_specifier: None,
1333            meta: Default::default(),
1334        }
1335    }
1336}
1337
1338impl CharacterAbility {
1339    /// Attempts to fulfill requirements, mutating `update` (taking energy) if
1340    /// applicable.
1341    pub fn requirements_paid(&self, data: &JoinData, update: &mut StateUpdate) -> bool {
1342        let from_meta = {
1343            let AbilityMeta { requirements, .. } = self.ability_meta();
1344            requirements.requirements_met(data.stance, data.inventory)
1345        };
1346        from_meta
1347            && match self {
1348                CharacterAbility::Roll { energy_cost, .. }
1349                | CharacterAbility::StaticAura {
1350                    energy_cost,
1351                    sprite_info: Some(_),
1352                    ..
1353                } => {
1354                    data.physics.on_ground.is_some()
1355                        && update.energy.try_change_by(-*energy_cost).is_ok()
1356                },
1357                CharacterAbility::DashMelee { energy_cost, .. }
1358                | CharacterAbility::BasicMelee { energy_cost, .. }
1359                | CharacterAbility::BasicRanged { energy_cost, .. }
1360                | CharacterAbility::ChargedRanged { energy_cost, .. }
1361                | CharacterAbility::Throw { energy_cost, .. }
1362                | CharacterAbility::ChargedMelee { energy_cost, .. }
1363                | CharacterAbility::BasicBlock { energy_cost, .. }
1364                | CharacterAbility::RiposteMelee { energy_cost, .. }
1365                | CharacterAbility::ComboMelee2 {
1366                    energy_cost_per_strike: energy_cost,
1367                    ..
1368                }
1369                | CharacterAbility::StaticAura {
1370                    energy_cost,
1371                    sprite_info: None,
1372                    ..
1373                }
1374                | CharacterAbility::RegrowHead { energy_cost, .. } => {
1375                    update.energy.try_change_by(-*energy_cost).is_ok()
1376                },
1377                // Also can consume energy within state, so value checked before entering state too
1378                CharacterAbility::RapidRanged {
1379                    initial_energy,
1380                    energy_cost,
1381                    ..
1382                } => {
1383                    update.energy.current() >= *energy_cost + *initial_energy
1384                        && update.energy.try_change_by(-*initial_energy).is_ok()
1385                },
1386                CharacterAbility::LeapExplosionShockwave { energy_cost, .. }
1387                | CharacterAbility::LeapMelee { energy_cost, .. }
1388                | CharacterAbility::LeapShockwave { energy_cost, .. }
1389                | CharacterAbility::LeapRanged { energy_cost, .. } => {
1390                    update.vel.0.z >= 0.0 && update.energy.try_change_by(-*energy_cost).is_ok()
1391                },
1392                CharacterAbility::BasicAura {
1393                    energy_cost,
1394                    scales_with_combo,
1395                    ..
1396                } => {
1397                    ((*scales_with_combo && data.combo.is_some_and(|c| c.counter() > 0))
1398                        | !*scales_with_combo)
1399                        && update.energy.try_change_by(-*energy_cost).is_ok()
1400                },
1401                CharacterAbility::FinisherMelee {
1402                    energy_cost,
1403                    minimum_combo,
1404                    ..
1405                }
1406                | CharacterAbility::RapidMelee {
1407                    energy_cost,
1408                    minimum_combo,
1409                    ..
1410                }
1411                | CharacterAbility::SelfBuff {
1412                    energy_cost,
1413                    combo_cost: minimum_combo,
1414                    ..
1415                }
1416                | CharacterAbility::Simple {
1417                    energy_cost,
1418                    combo_cost: minimum_combo,
1419                    ..
1420                } => {
1421                    data.combo.is_some_and(|c| c.counter() >= *minimum_combo)
1422                        && update.energy.try_change_by(-*energy_cost).is_ok()
1423                },
1424                CharacterAbility::Shockwave {
1425                    energy_cost,
1426                    minimum_combo,
1427                    ..
1428                } => {
1429                    data.combo
1430                        .is_some_and(|c| c.counter() >= minimum_combo.unwrap_or(0))
1431                        && update.energy.try_change_by(-*energy_cost).is_ok()
1432                },
1433                CharacterAbility::Explosion { energy_cost, .. } => {
1434                    update.energy.try_change_by(-*energy_cost).is_ok()
1435                },
1436                CharacterAbility::DiveMelee {
1437                    buildup_duration,
1438                    energy_cost,
1439                    ..
1440                } => {
1441                    // If either in the air or is on ground and able to be activated from
1442                    // ground.
1443                    //
1444                    // NOTE: there is a check in CharacterState::try_from below that must be kept in
1445                    // sync with the conditions here (it determines whether this starts in a
1446                    // movement or buildup stage).
1447                    (data.physics.on_ground.is_none() || buildup_duration.is_some())
1448                        && update.energy.try_change_by(-*energy_cost).is_ok()
1449                },
1450                CharacterAbility::Boost { .. }
1451                | CharacterAbility::GlideBoost { .. }
1452                | CharacterAbility::BasicBeam { .. }
1453                | CharacterAbility::Blink { .. }
1454                | CharacterAbility::Music { .. }
1455                | CharacterAbility::BasicSummon { .. }
1456                | CharacterAbility::SpriteSummon { .. }
1457                | CharacterAbility::Transform { .. } => true,
1458            }
1459    }
1460
1461    pub fn default_roll(current_state: Option<&CharacterState>) -> CharacterAbility {
1462        let remaining_duration = current_state
1463            .and_then(|char_state| {
1464                char_state.timer().zip(
1465                    char_state
1466                        .durations()
1467                        .zip(char_state.stage_section())
1468                        .and_then(|(durations, stage_section)| match stage_section {
1469                            StageSection::Buildup => durations.buildup,
1470                            StageSection::Recover => durations.recover,
1471                            _ => None,
1472                        }),
1473                )
1474            })
1475            .map_or(0.0, |(timer, duration)| {
1476                duration.as_secs_f32() - timer.as_secs_f32()
1477            })
1478            .max(0.0);
1479
1480        CharacterAbility::Roll {
1481            // Energy cost increased by remaining duration
1482            energy_cost: 10.0 + 100.0 * remaining_duration,
1483            buildup_duration: 0.05,
1484            movement_duration: 0.36,
1485            recover_duration: 0.125,
1486            roll_strength: 3.3075,
1487            attack_immunities: AttackFilters {
1488                melee: true,
1489                projectiles: false,
1490                beams: true,
1491                ground_shockwaves: false,
1492                air_shockwaves: true,
1493                explosions: true,
1494                arcs: true,
1495                pools: true,
1496            },
1497            was_cancel: remaining_duration > 0.0,
1498            meta: Default::default(),
1499        }
1500    }
1501
1502    #[must_use]
1503    pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
1504        use CharacterAbility::*;
1505        match self {
1506            BasicMelee {
1507                ref mut energy_cost,
1508                ref mut buildup_duration,
1509                ref mut swing_duration,
1510                ref mut recover_duration,
1511                ref mut melee_constructor,
1512                movement_modifier: _,
1513                ori_modifier: _,
1514                hit_timing: _,
1515                frontend_specifier: _,
1516                meta: _,
1517            } => {
1518                *buildup_duration /= stats.speed;
1519                *swing_duration /= stats.speed;
1520                *recover_duration /= stats.speed;
1521                *energy_cost /= stats.energy_efficiency;
1522                *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
1523            },
1524            BasicRanged {
1525                ref mut energy_cost,
1526                ref mut buildup_duration,
1527                ref mut recover_duration,
1528                ref mut projectile,
1529                projectile_body: _,
1530                projectile_light: _,
1531                ref mut projectile_speed,
1532                vertical_angle_offset: _,
1533                num_projectiles: _,
1534                projectile_spread: _,
1535                auto_aim: _,
1536                movement_modifier: _,
1537                ori_modifier: _,
1538                marker: _,
1539                meta: _,
1540            } => {
1541                *buildup_duration /= stats.speed;
1542                *recover_duration /= stats.speed;
1543                *projectile = projectile.clone().adjusted_by_stats(stats);
1544                *projectile_speed *= stats.range;
1545                *energy_cost /= stats.energy_efficiency;
1546            },
1547            RapidRanged {
1548                ref mut initial_energy,
1549                ref mut energy_cost,
1550                ref mut buildup_duration,
1551                ref mut shoot_duration,
1552                ref mut recover_duration,
1553                options: _,
1554                ref mut projectile,
1555                projectile_body: _,
1556                projectile_light: _,
1557                ref mut projectile_speed,
1558                specifier: _,
1559                meta: _,
1560            } => {
1561                *buildup_duration /= stats.speed;
1562                *shoot_duration /= stats.speed;
1563                *recover_duration /= stats.speed;
1564                *projectile = projectile.clone().adjusted_by_stats(stats);
1565                *projectile_speed *= stats.range;
1566                *initial_energy /= stats.energy_efficiency;
1567                *energy_cost /= stats.energy_efficiency;
1568            },
1569            Boost {
1570                ref mut movement_duration,
1571                only_up: _,
1572                speed: ref mut boost_speed,
1573                max_exit_velocity: _,
1574                meta: _,
1575            } => {
1576                *movement_duration /= stats.speed;
1577                *boost_speed *= stats.power;
1578            },
1579            DashMelee {
1580                ref mut energy_cost,
1581                ref mut energy_drain,
1582                forward_speed: _,
1583                ref mut buildup_duration,
1584                charge_duration: _,
1585                ref mut swing_duration,
1586                ref mut recover_duration,
1587                ref mut melee_constructor,
1588                ori_modifier: _,
1589                auto_charge: _,
1590                charge_through: _,
1591                frontend_specifier: _,
1592                meta: _,
1593            } => {
1594                *buildup_duration /= stats.speed;
1595                *swing_duration /= stats.speed;
1596                *recover_duration /= stats.speed;
1597                *energy_cost /= stats.energy_efficiency;
1598                *energy_drain /= stats.energy_efficiency;
1599                *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
1600            },
1601            BasicBlock {
1602                ref mut buildup_duration,
1603                ref mut recover_duration,
1604                // Do we want angle to be adjusted by range?
1605                max_angle: _,
1606                ref mut block_strength,
1607                parry_window: _,
1608                ref mut energy_cost,
1609                energy_regen: _,
1610                can_hold: _,
1611                blocked_attacks: _,
1612                meta: _,
1613            } => {
1614                *buildup_duration /= stats.speed;
1615                *recover_duration /= stats.speed;
1616                *energy_cost /= stats.energy_efficiency;
1617                *block_strength *= stats.power;
1618            },
1619            Roll {
1620                ref mut energy_cost,
1621                ref mut buildup_duration,
1622                ref mut movement_duration,
1623                ref mut recover_duration,
1624                roll_strength: _,
1625                attack_immunities: _,
1626                was_cancel: _,
1627                meta: _,
1628            } => {
1629                *buildup_duration /= stats.speed;
1630                *movement_duration /= stats.speed;
1631                *recover_duration /= stats.speed;
1632                *energy_cost /= stats.energy_efficiency;
1633            },
1634            ComboMelee2 {
1635                ref mut strikes,
1636                ref mut energy_cost_per_strike,
1637                specifier: _,
1638                auto_progress: _,
1639                meta: _,
1640            } => {
1641                *energy_cost_per_strike /= stats.energy_efficiency;
1642                *strikes = strikes
1643                    .iter_mut()
1644                    .map(|s| s.clone().adjusted_by_stats(stats))
1645                    .collect();
1646            },
1647            LeapExplosionShockwave {
1648                ref mut energy_cost,
1649                ref mut buildup_duration,
1650                ref mut movement_duration,
1651                ref mut swing_duration,
1652                ref mut recover_duration,
1653                forward_leap_strength: _,
1654                vertical_leap_strength: _,
1655                ref mut explosion_damage,
1656                ref mut explosion_poise,
1657                ref mut explosion_knockback,
1658                ref mut explosion_radius,
1659                min_falloff: _,
1660                explosion_dodgeable: _,
1661                destroy_terrain: _,
1662                replace_terrain: _,
1663                eye_height: _,
1664                reagent: _,
1665                ref mut shockwave_damage,
1666                ref mut shockwave_poise,
1667                ref mut shockwave_knockback,
1668                shockwave_angle: _,
1669                shockwave_vertical_angle: _,
1670                shockwave_speed: _,
1671                ref mut shockwave_duration,
1672                shockwave_dodgeable: _,
1673                ref mut shockwave_damage_effect,
1674                shockwave_damage_kind: _,
1675                shockwave_specifier: _,
1676                move_efficiency: _,
1677                meta: _,
1678            } => {
1679                *energy_cost /= stats.energy_efficiency;
1680                *buildup_duration /= stats.speed;
1681                *movement_duration /= stats.speed;
1682                *swing_duration /= stats.speed;
1683                *recover_duration /= stats.speed;
1684
1685                *explosion_damage *= stats.power;
1686                *explosion_poise *= stats.effect_power;
1687                explosion_knockback.strength *= stats.effect_power;
1688                *explosion_radius *= stats.range;
1689
1690                *shockwave_damage *= stats.power;
1691                *shockwave_poise *= stats.effect_power;
1692                shockwave_knockback.strength *= stats.effect_power;
1693                *shockwave_duration *= stats.range;
1694                if let Some(CombatEffect::Buff(combat::CombatBuff {
1695                    kind: _,
1696                    dur_secs: _,
1697                    strength,
1698                    chance: _,
1699                })) = shockwave_damage_effect
1700                {
1701                    *strength *= stats.buff_strength;
1702                }
1703            },
1704            LeapMelee {
1705                ref mut energy_cost,
1706                ref mut buildup_duration,
1707                movement_duration: _,
1708                ref mut swing_duration,
1709                ref mut recover_duration,
1710                ref mut melee_constructor,
1711                forward_leap_strength: _,
1712                vertical_leap_strength: _,
1713                specifier: _,
1714                meta: _,
1715            } => {
1716                *buildup_duration /= stats.speed;
1717                *swing_duration /= stats.speed;
1718                *recover_duration /= stats.speed;
1719                *energy_cost /= stats.energy_efficiency;
1720                *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats)
1721            },
1722            LeapShockwave {
1723                ref mut energy_cost,
1724                ref mut buildup_duration,
1725                movement_duration: _,
1726                ref mut swing_duration,
1727                ref mut recover_duration,
1728                ref mut damage,
1729                ref mut poise_damage,
1730                knockback: _,
1731                shockwave_angle: _,
1732                shockwave_vertical_angle: _,
1733                shockwave_speed: _,
1734                ref mut shockwave_duration,
1735                dodgeable: _,
1736                move_efficiency: _,
1737                damage_kind: _,
1738                specifier: _,
1739                ref mut damage_effect,
1740                forward_leap_strength: _,
1741                vertical_leap_strength: _,
1742                meta: _,
1743            } => {
1744                *buildup_duration /= stats.speed;
1745                *swing_duration /= stats.speed;
1746                *recover_duration /= stats.speed;
1747                *damage *= stats.power;
1748                *poise_damage *= stats.effect_power;
1749                *shockwave_duration *= stats.range;
1750                *energy_cost /= stats.energy_efficiency;
1751                if let Some(CombatEffect::Buff(combat::CombatBuff {
1752                    kind: _,
1753                    dur_secs: _,
1754                    strength,
1755                    chance: _,
1756                })) = damage_effect
1757                {
1758                    *strength *= stats.buff_strength;
1759                }
1760            },
1761            ChargedMelee {
1762                ref mut energy_cost,
1763                ref mut energy_drain,
1764                ref mut buildup_strike,
1765                ref mut charge_duration,
1766                ref mut swing_duration,
1767                hit_timing: _,
1768                ref mut recover_duration,
1769                ref mut melee_constructor,
1770                specifier: _,
1771                meta: _,
1772                custom_combo: _,
1773                movement_modifier: _,
1774                ori_modifier: _,
1775            } => {
1776                *swing_duration /= stats.speed;
1777                *buildup_strike = buildup_strike
1778                    .as_ref()
1779                    .cloned()
1780                    .map(|(dur, strike)| (dur / stats.speed, strike.adjusted_by_stats(stats)));
1781                *charge_duration /= stats.speed;
1782                *recover_duration /= stats.speed;
1783                *energy_cost /= stats.energy_efficiency;
1784                *energy_drain *= stats.speed / stats.energy_efficiency;
1785                *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
1786            },
1787            ChargedRanged {
1788                ref mut energy_cost,
1789                ref mut energy_drain,
1790                ref mut idle_drain,
1791                ref mut projectile,
1792                ref mut buildup_duration,
1793                ref mut charge_duration,
1794                ref mut recover_duration,
1795                projectile_body: _,
1796                projectile_light: _,
1797                ref mut initial_projectile_speed,
1798                ref mut scaled_projectile_speed,
1799                projectile_spread: _,
1800                num_projectiles: _,
1801                marker: _,
1802                move_speed: _,
1803                meta: _,
1804            } => {
1805                *projectile = projectile.clone().adjusted_by_stats(stats);
1806                *buildup_duration /= stats.speed;
1807                *charge_duration /= stats.speed;
1808                *recover_duration /= stats.speed;
1809                *initial_projectile_speed *= stats.range;
1810                *scaled_projectile_speed *= stats.range;
1811                *energy_cost /= stats.energy_efficiency;
1812                *energy_drain *= stats.speed / stats.energy_efficiency;
1813                *idle_drain /= stats.energy_efficiency;
1814            },
1815            Throw {
1816                ref mut energy_cost,
1817                ref mut energy_drain,
1818                ref mut buildup_duration,
1819                ref mut charge_duration,
1820                ref mut throw_duration,
1821                ref mut recover_duration,
1822                ref mut projectile,
1823                projectile_light: _,
1824                projectile_dir: _,
1825                ref mut initial_projectile_speed,
1826                ref mut scaled_projectile_speed,
1827                damage_effect: _,
1828                move_speed: _,
1829                meta: _,
1830            } => {
1831                *projectile = projectile.clone().adjusted_by_stats(stats);
1832                *energy_cost /= stats.energy_efficiency;
1833                *energy_drain *= stats.speed / stats.energy_efficiency;
1834                *buildup_duration /= stats.speed;
1835                *charge_duration /= stats.speed;
1836                *throw_duration /= stats.speed;
1837                *recover_duration /= stats.speed;
1838                *initial_projectile_speed *= stats.range;
1839                *scaled_projectile_speed *= stats.range;
1840            },
1841            Shockwave {
1842                ref mut energy_cost,
1843                ref mut buildup_duration,
1844                ref mut swing_duration,
1845                ref mut recover_duration,
1846                ref mut damage,
1847                ref mut poise_damage,
1848                knockback: _,
1849                shockwave_angle: _,
1850                shockwave_vertical_angle: _,
1851                shockwave_speed: _,
1852                ref mut shockwave_duration,
1853                dodgeable: _,
1854                move_efficiency: _,
1855                damage_kind: _,
1856                specifier: _,
1857                ori_rate: _,
1858                ref mut damage_effect,
1859                timing: _,
1860                emit_outcome: _,
1861                minimum_combo: _,
1862                combo_consumption: _,
1863                meta: _,
1864            } => {
1865                *buildup_duration /= stats.speed;
1866                *swing_duration /= stats.speed;
1867                *recover_duration /= stats.speed;
1868                *damage *= stats.power;
1869                *poise_damage *= stats.effect_power;
1870                *shockwave_duration *= stats.range;
1871                *energy_cost /= stats.energy_efficiency;
1872                *damage_effect = damage_effect
1873                    .as_ref()
1874                    .cloned()
1875                    .map(|de| de.adjusted_by_stats(stats));
1876            },
1877            Explosion {
1878                ref mut energy_cost,
1879                ref mut buildup_duration,
1880                ref mut action_duration,
1881                ref mut recover_duration,
1882                ref mut damage,
1883                poise: ref mut poise_damage,
1884                ref mut knockback,
1885                ref mut radius,
1886                min_falloff: _,
1887                dodgeable: _,
1888                destroy_terrain: _,
1889                replace_terrain: _,
1890                eye_height: _,
1891                reagent: _,
1892                movement_modifier: _,
1893                ori_modifier: _,
1894                meta: _,
1895            } => {
1896                *energy_cost /= stats.energy_efficiency;
1897                *buildup_duration /= stats.speed;
1898                *action_duration /= stats.speed;
1899                *recover_duration /= stats.speed;
1900                *damage *= stats.power;
1901                *poise_damage *= stats.effect_power;
1902                knockback.strength *= stats.effect_power;
1903                *radius *= stats.range;
1904            },
1905            BasicBeam {
1906                ref mut buildup_duration,
1907                ref mut recover_duration,
1908                ref mut beam_duration,
1909                ref mut damage,
1910                ref mut tick_rate,
1911                ref mut range,
1912                dodgeable: _,
1913                blockable: _,
1914                max_angle: _,
1915                ref mut damage_effect,
1916                energy_regen: _,
1917                ref mut energy_drain,
1918                move_efficiency: _,
1919                ori_rate: _,
1920                specifier: _,
1921                meta: _,
1922            } => {
1923                *buildup_duration /= stats.speed;
1924                *recover_duration /= stats.speed;
1925                *damage *= stats.power;
1926                *tick_rate *= stats.speed;
1927                *range *= stats.range;
1928                // Duration modified to keep velocity constant
1929                *beam_duration *= stats.range as f64;
1930                *energy_drain /= stats.energy_efficiency;
1931                *damage_effect = damage_effect
1932                    .as_ref()
1933                    .cloned()
1934                    .map(|de| de.adjusted_by_stats(stats));
1935            },
1936            BasicAura {
1937                ref mut buildup_duration,
1938                ref mut cast_duration,
1939                ref mut recover_duration,
1940                targets: _,
1941                ref mut auras,
1942                aura_duration: _,
1943                ref mut range,
1944                ref mut energy_cost,
1945                scales_with_combo: _,
1946                specifier: _,
1947                meta: _,
1948            } => {
1949                *buildup_duration /= stats.speed;
1950                *cast_duration /= stats.speed;
1951                *recover_duration /= stats.speed;
1952                auras.iter_mut().for_each(
1953                    |aura::AuraBuffConstructor {
1954                         kind: _,
1955                         strength,
1956                         duration: _,
1957                         category: _,
1958                     }| {
1959                        *strength *= stats.diminished_buff_strength();
1960                    },
1961                );
1962                *range *= stats.range;
1963                *energy_cost /= stats.energy_efficiency;
1964            },
1965            StaticAura {
1966                ref mut buildup_duration,
1967                ref mut cast_duration,
1968                ref mut recover_duration,
1969                targets: _,
1970                ref mut auras,
1971                aura_duration: _,
1972                ref mut range,
1973                ref mut energy_cost,
1974                ref mut sprite_info,
1975                meta: _,
1976            } => {
1977                *buildup_duration /= stats.speed;
1978                *cast_duration /= stats.speed;
1979                *recover_duration /= stats.speed;
1980                auras.iter_mut().for_each(
1981                    |aura::AuraBuffConstructor {
1982                         kind: _,
1983                         strength,
1984                         duration: _,
1985                         category: _,
1986                     }| {
1987                        *strength *= stats.diminished_buff_strength();
1988                    },
1989                );
1990                *range *= stats.range;
1991                *energy_cost /= stats.energy_efficiency;
1992                *sprite_info = sprite_info.map(|mut si| {
1993                    si.summon_distance.0 *= stats.range;
1994                    si.summon_distance.1 *= stats.range;
1995                    si
1996                });
1997            },
1998            Blink {
1999                ref mut buildup_duration,
2000                ref mut recover_duration,
2001                ref mut max_range,
2002                frontend_specifier: _,
2003                meta: _,
2004            } => {
2005                *buildup_duration /= stats.speed;
2006                *recover_duration /= stats.speed;
2007                *max_range *= stats.range;
2008            },
2009            BasicSummon {
2010                ref mut buildup_duration,
2011                ref mut cast_duration,
2012                ref mut recover_duration,
2013                ref mut summon_info,
2014                movement_modifier: _,
2015                ori_modifier: _,
2016                meta: _,
2017            } => {
2018                // TODO: Figure out how/if power should affect this
2019                *buildup_duration /= stats.speed;
2020                *cast_duration /= stats.speed;
2021                *recover_duration /= stats.speed;
2022                summon_info.scale_range(stats.range);
2023            },
2024            SelfBuff {
2025                ref mut buildup_duration,
2026                ref mut cast_duration,
2027                ref mut recover_duration,
2028                ref mut buffs,
2029                use_raw_buff_strength,
2030                buff_cat: _,
2031                ref mut energy_cost,
2032                enforced_limit: _,
2033                combo_cost: _,
2034                combo_scaling: _,
2035                meta: _,
2036                specifier: _,
2037            } => {
2038                for buff in buffs.iter_mut() {
2039                    buff.data.strength *= if use_raw_buff_strength {
2040                        stats.buff_strength
2041                    } else {
2042                        stats.diminished_buff_strength()
2043                    };
2044                }
2045                *buildup_duration /= stats.speed;
2046                *cast_duration /= stats.speed;
2047                *recover_duration /= stats.speed;
2048                *energy_cost /= stats.energy_efficiency;
2049            },
2050            SpriteSummon {
2051                ref mut buildup_duration,
2052                ref mut cast_duration,
2053                ref mut recover_duration,
2054                sprite: _,
2055                del_timeout: _,
2056                summon_distance: (ref mut inner_dist, ref mut outer_dist),
2057                sparseness: _,
2058                angle: _,
2059                anchor: _,
2060                move_efficiency: _,
2061                ori_modifier: _,
2062                meta: _,
2063            } => {
2064                // TODO: Figure out how/if power should affect this
2065                *buildup_duration /= stats.speed;
2066                *cast_duration /= stats.speed;
2067                *recover_duration /= stats.speed;
2068                *inner_dist *= stats.range;
2069                *outer_dist *= stats.range;
2070            },
2071            Music {
2072                ref mut play_duration,
2073                ori_modifier: _,
2074                meta: _,
2075            } => {
2076                *play_duration /= stats.speed;
2077            },
2078            FinisherMelee {
2079                ref mut energy_cost,
2080                ref mut buildup_duration,
2081                ref mut swing_duration,
2082                ref mut recover_duration,
2083                ref mut melee_constructor,
2084                minimum_combo: _,
2085                scaling: _,
2086                combo_consumption: _,
2087                meta: _,
2088            } => {
2089                *buildup_duration /= stats.speed;
2090                *swing_duration /= stats.speed;
2091                *recover_duration /= stats.speed;
2092                *energy_cost /= stats.energy_efficiency;
2093                *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
2094            },
2095            DiveMelee {
2096                ref mut energy_cost,
2097                vertical_speed: _,
2098                movement_duration: _,
2099                ref mut buildup_duration,
2100                ref mut swing_duration,
2101                ref mut recover_duration,
2102                ref mut melee_constructor,
2103                max_scaling: _,
2104                meta: _,
2105            } => {
2106                *buildup_duration = buildup_duration.map(|b| b / stats.speed);
2107                *swing_duration /= stats.speed;
2108                *recover_duration /= stats.speed;
2109                *energy_cost /= stats.energy_efficiency;
2110                *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
2111            },
2112            RiposteMelee {
2113                ref mut energy_cost,
2114                ref mut buildup_duration,
2115                ref mut swing_duration,
2116                ref mut recover_duration,
2117                ref mut whiffed_recover_duration,
2118                ref mut block_strength,
2119                ref mut melee_constructor,
2120                meta: _,
2121            } => {
2122                *buildup_duration /= stats.speed;
2123                *swing_duration /= stats.speed;
2124                *recover_duration /= stats.speed;
2125                *whiffed_recover_duration /= stats.speed;
2126                *energy_cost /= stats.energy_efficiency;
2127                *block_strength *= stats.power;
2128                *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
2129            },
2130            RapidMelee {
2131                ref mut buildup_duration,
2132                ref mut swing_duration,
2133                ref mut recover_duration,
2134                ref mut energy_cost,
2135                ref mut melee_constructor,
2136                max_strikes: _,
2137                move_modifier: _,
2138                ori_modifier: _,
2139                minimum_combo: _,
2140                frontend_specifier: _,
2141                meta: _,
2142            } => {
2143                *buildup_duration /= stats.speed;
2144                *swing_duration /= stats.speed;
2145                *recover_duration /= stats.speed;
2146                *energy_cost /= stats.energy_efficiency;
2147                *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
2148            },
2149            Transform {
2150                ref mut buildup_duration,
2151                ref mut recover_duration,
2152                target: _,
2153                specifier: _,
2154                allow_players: _,
2155                meta: _,
2156            } => {
2157                *buildup_duration /= stats.speed;
2158                *recover_duration /= stats.speed;
2159            },
2160            GlideBoost { .. } => {},
2161            RegrowHead {
2162                ref mut buildup_duration,
2163                ref mut recover_duration,
2164                ref mut energy_cost,
2165                specifier: _,
2166                meta: _,
2167            } => {
2168                *buildup_duration /= stats.speed;
2169                *recover_duration /= stats.speed;
2170                *energy_cost /= stats.energy_efficiency;
2171            },
2172            LeapRanged {
2173                ref mut energy_cost,
2174                ref mut buildup_duration,
2175                buildup_melee_timing: _,
2176                movement_duration: _,
2177                movement_ranged_timing: _,
2178                land_timeout: _,
2179                ref mut recover_duration,
2180                ref mut melee,
2181                melee_required: _,
2182                ref mut projectile,
2183                projectile_body: _,
2184                projectile_light: _,
2185                ref mut projectile_speed,
2186                horiz_leap_strength: _,
2187                vert_leap_strength: _,
2188                meta: _,
2189            } => {
2190                *energy_cost /= stats.energy_efficiency;
2191                *buildup_duration /= stats.speed;
2192                *recover_duration /= stats.speed;
2193                *melee = melee.as_ref().cloned().map(|m| m.adjusted_by_stats(stats));
2194                *projectile = projectile.clone().adjusted_by_stats(stats);
2195                *projectile_speed *= stats.range;
2196            },
2197            Simple {
2198                ref mut energy_cost,
2199                combo_cost: _,
2200                ref mut buildup_duration,
2201                meta: _,
2202            } => {
2203                *energy_cost /= stats.energy_efficiency;
2204                *buildup_duration /= stats.speed;
2205            },
2206        }
2207        self
2208    }
2209
2210    pub fn energy_cost(&self) -> f32 {
2211        use CharacterAbility::*;
2212        match self {
2213            BasicMelee { energy_cost, .. }
2214            | BasicRanged { energy_cost, .. }
2215            | RapidRanged { energy_cost, .. }
2216            | DashMelee { energy_cost, .. }
2217            | Roll { energy_cost, .. }
2218            | LeapExplosionShockwave { energy_cost, .. }
2219            | LeapMelee { energy_cost, .. }
2220            | LeapShockwave { energy_cost, .. }
2221            | ChargedMelee { energy_cost, .. }
2222            | ChargedRanged { energy_cost, .. }
2223            | Throw { energy_cost, .. }
2224            | Shockwave { energy_cost, .. }
2225            | Explosion { energy_cost, .. }
2226            | BasicAura { energy_cost, .. }
2227            | BasicBlock { energy_cost, .. }
2228            | SelfBuff { energy_cost, .. }
2229            | FinisherMelee { energy_cost, .. }
2230            | ComboMelee2 {
2231                energy_cost_per_strike: energy_cost,
2232                ..
2233            }
2234            | DiveMelee { energy_cost, .. }
2235            | RiposteMelee { energy_cost, .. }
2236            | RapidMelee { energy_cost, .. }
2237            | StaticAura { energy_cost, .. }
2238            | RegrowHead { energy_cost, .. }
2239            | LeapRanged { energy_cost, .. }
2240            | Simple { energy_cost, .. } => *energy_cost,
2241            BasicBeam { energy_drain, .. } => {
2242                if *energy_drain > f32::EPSILON {
2243                    1.0
2244                } else {
2245                    0.0
2246                }
2247            },
2248            Boost { .. }
2249            | GlideBoost { .. }
2250            | Blink { .. }
2251            | Music { .. }
2252            | BasicSummon { .. }
2253            | SpriteSummon { .. }
2254            | Transform { .. } => 0.0,
2255        }
2256    }
2257
2258    #[expect(clippy::bool_to_int_with_if)]
2259    pub fn combo_cost(&self) -> u32 {
2260        use CharacterAbility::*;
2261        match self {
2262            BasicAura {
2263                scales_with_combo, ..
2264            } => {
2265                if *scales_with_combo {
2266                    1
2267                } else {
2268                    0
2269                }
2270            },
2271            FinisherMelee {
2272                minimum_combo: combo,
2273                ..
2274            }
2275            | RapidMelee {
2276                minimum_combo: combo,
2277                ..
2278            }
2279            | SelfBuff {
2280                combo_cost: combo, ..
2281            }
2282            | Simple {
2283                combo_cost: combo, ..
2284            } => *combo,
2285            Shockwave {
2286                minimum_combo: combo,
2287                ..
2288            } => combo.unwrap_or(0),
2289            BasicMelee { .. }
2290            | BasicRanged { .. }
2291            | RapidRanged { .. }
2292            | DashMelee { .. }
2293            | Roll { .. }
2294            | LeapExplosionShockwave { .. }
2295            | LeapMelee { .. }
2296            | LeapShockwave { .. }
2297            | Explosion { .. }
2298            | ChargedMelee { .. }
2299            | ChargedRanged { .. }
2300            | Throw { .. }
2301            | BasicBlock { .. }
2302            | ComboMelee2 { .. }
2303            | DiveMelee { .. }
2304            | RiposteMelee { .. }
2305            | BasicBeam { .. }
2306            | Boost { .. }
2307            | GlideBoost { .. }
2308            | Blink { .. }
2309            | Music { .. }
2310            | BasicSummon { .. }
2311            | SpriteSummon { .. }
2312            | Transform { .. }
2313            | StaticAura { .. }
2314            | RegrowHead { .. }
2315            | LeapRanged { .. } => 0,
2316        }
2317    }
2318
2319    // TODO: Maybe consider making CharacterAbility a struct at some point?
2320    pub fn ability_meta(&self) -> AbilityMeta {
2321        use CharacterAbility::*;
2322        match self {
2323            BasicMelee { meta, .. }
2324            | BasicRanged { meta, .. }
2325            | RapidRanged { meta, .. }
2326            | DashMelee { meta, .. }
2327            | Roll { meta, .. }
2328            | LeapExplosionShockwave { meta, .. }
2329            | LeapMelee { meta, .. }
2330            | LeapShockwave { meta, .. }
2331            | ChargedMelee { meta, .. }
2332            | ChargedRanged { meta, .. }
2333            | Throw { meta, .. }
2334            | Shockwave { meta, .. }
2335            | Explosion { meta, .. }
2336            | BasicAura { meta, .. }
2337            | BasicBlock { meta, .. }
2338            | SelfBuff { meta, .. }
2339            | BasicBeam { meta, .. }
2340            | Boost { meta, .. }
2341            | GlideBoost { meta, .. }
2342            | ComboMelee2 { meta, .. }
2343            | Blink { meta, .. }
2344            | BasicSummon { meta, .. }
2345            | SpriteSummon { meta, .. }
2346            | FinisherMelee { meta, .. }
2347            | Music { meta, .. }
2348            | DiveMelee { meta, .. }
2349            | RiposteMelee { meta, .. }
2350            | RapidMelee { meta, .. }
2351            | Transform { meta, .. }
2352            | StaticAura { meta, .. }
2353            | RegrowHead { meta, .. }
2354            | LeapRanged { meta, .. }
2355            | Simple { meta, .. } => *meta,
2356        }
2357    }
2358
2359    #[must_use = "method returns new ability and doesn't mutate the original value"]
2360    pub fn adjusted_by_skills(mut self, skillset: &SkillSet, tool: Option<ToolKind>) -> Self {
2361        match tool {
2362            Some(ToolKind::Sceptre) => self.adjusted_by_sceptre_skills(skillset),
2363            Some(ToolKind::Pick) => self.adjusted_by_mining_skills(skillset),
2364            None | Some(_) => {},
2365        }
2366        self
2367    }
2368
2369    fn adjusted_by_mining_skills(&mut self, skillset: &SkillSet) {
2370        use skills::MiningSkill::Speed;
2371
2372        if let CharacterAbility::BasicMelee {
2373            buildup_duration,
2374            swing_duration,
2375            recover_duration,
2376            ..
2377        } = self
2378            && let Ok(level) = skillset.skill_level(Skill::Pick(Speed))
2379        {
2380            let modifiers = SKILL_MODIFIERS.mining_tree;
2381
2382            let speed = modifiers.speed.powi(level.into());
2383            *buildup_duration /= speed;
2384            *swing_duration /= speed;
2385            *recover_duration /= speed;
2386        }
2387    }
2388
2389    fn adjusted_by_sceptre_skills(&mut self, skillset: &SkillSet) {
2390        use skills::{SceptreSkill::*, Skill::Sceptre};
2391
2392        match self {
2393            CharacterAbility::BasicBeam {
2394                damage,
2395                range,
2396                beam_duration,
2397                damage_effect,
2398                energy_regen,
2399                ..
2400            } => {
2401                let modifiers = SKILL_MODIFIERS.sceptre_tree.beam;
2402                if let Ok(level) = skillset.skill_level(Sceptre(LDamage)) {
2403                    *damage *= modifiers.damage.powi(level.into());
2404                }
2405                if let Ok(level) = skillset.skill_level(Sceptre(LRange)) {
2406                    let range_mod = modifiers.range.powi(level.into());
2407                    *range *= range_mod;
2408                    // Duration modified to keep velocity constant
2409                    *beam_duration *= range_mod as f64;
2410                }
2411                if let Ok(level) = skillset.skill_level(Sceptre(LRegen)) {
2412                    *energy_regen *= modifiers.energy_regen.powi(level.into());
2413                }
2414                if let (Ok(level), Some(CombatEffect::Lifesteal(lifesteal))) =
2415                    (skillset.skill_level(Sceptre(LLifesteal)), damage_effect)
2416                {
2417                    *lifesteal *= modifiers.lifesteal.powi(level.into());
2418                }
2419            },
2420            CharacterAbility::BasicAura {
2421                auras,
2422                range,
2423                energy_cost,
2424                specifier: Some(aura::Specifier::HealingAura),
2425                ..
2426            } => {
2427                let modifiers = SKILL_MODIFIERS.sceptre_tree.healing_aura;
2428                if let Ok(level) = skillset.skill_level(Sceptre(HHeal)) {
2429                    auras.iter_mut().for_each(|ref mut aura| {
2430                        aura.strength *= modifiers.strength.powi(level.into());
2431                    });
2432                }
2433                if let Ok(level) = skillset.skill_level(Sceptre(HDuration)) {
2434                    auras.iter_mut().for_each(|ref mut aura| {
2435                        if let Some(ref mut duration) = aura.duration {
2436                            *duration *= modifiers.duration.powi(level.into()) as f64;
2437                        }
2438                    });
2439                }
2440                if let Ok(level) = skillset.skill_level(Sceptre(HRange)) {
2441                    *range *= modifiers.range.powi(level.into());
2442                }
2443                if let Ok(level) = skillset.skill_level(Sceptre(HCost)) {
2444                    *energy_cost *= modifiers.energy_cost.powi(level.into());
2445                }
2446            },
2447            CharacterAbility::BasicAura {
2448                auras,
2449                range,
2450                energy_cost,
2451                specifier: Some(aura::Specifier::WardingAura),
2452                ..
2453            } => {
2454                let modifiers = SKILL_MODIFIERS.sceptre_tree.warding_aura;
2455                if let Ok(level) = skillset.skill_level(Sceptre(AStrength)) {
2456                    auras.iter_mut().for_each(|ref mut aura| {
2457                        aura.strength *= modifiers.strength.powi(level.into());
2458                    });
2459                }
2460                if let Ok(level) = skillset.skill_level(Sceptre(ADuration)) {
2461                    auras.iter_mut().for_each(|ref mut aura| {
2462                        if let Some(ref mut duration) = aura.duration {
2463                            *duration *= modifiers.duration.powi(level.into()) as f64;
2464                        }
2465                    });
2466                }
2467                if let Ok(level) = skillset.skill_level(Sceptre(ARange)) {
2468                    *range *= modifiers.range.powi(level.into());
2469                }
2470                if let Ok(level) = skillset.skill_level(Sceptre(ACost)) {
2471                    *energy_cost *= modifiers.energy_cost.powi(level.into());
2472                }
2473            },
2474            _ => {},
2475        }
2476    }
2477}
2478
2479/// Small helper for #[serde(default)] booleans
2480fn default_true() -> bool { true }
2481
2482#[derive(Debug)]
2483pub enum CharacterStateCreationError {
2484    MissingHandInfo,
2485    MissingItem,
2486    InvalidItemKind,
2487}
2488
2489impl TryFrom<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
2490    type Error = CharacterStateCreationError;
2491
2492    fn try_from(
2493        (ability, ability_info, data): (&CharacterAbility, AbilityInfo, &JoinData),
2494    ) -> Result<Self, Self::Error> {
2495        Ok(match ability {
2496            CharacterAbility::BasicMelee {
2497                buildup_duration,
2498                swing_duration,
2499                hit_timing,
2500                recover_duration,
2501                melee_constructor,
2502                movement_modifier,
2503                ori_modifier,
2504                frontend_specifier,
2505                energy_cost: _,
2506                meta: _,
2507            } => CharacterState::BasicMelee(basic_melee::Data {
2508                static_data: basic_melee::StaticData {
2509                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2510                    swing_duration: Duration::from_secs_f32(*swing_duration),
2511                    hit_timing: hit_timing.clamp(0.0, 1.0),
2512                    recover_duration: Duration::from_secs_f32(*recover_duration),
2513                    melee_constructor: melee_constructor.clone(),
2514                    movement_modifier: *movement_modifier,
2515                    ori_modifier: *ori_modifier,
2516                    frontend_specifier: *frontend_specifier,
2517                    ability_info,
2518                },
2519                timer: Duration::default(),
2520                stage_section: StageSection::Buildup,
2521                exhausted: false,
2522                movement_modifier: movement_modifier.buildup,
2523                ori_modifier: ori_modifier.buildup,
2524            }),
2525            CharacterAbility::BasicRanged {
2526                buildup_duration,
2527                recover_duration,
2528                projectile,
2529                projectile_body,
2530                projectile_light,
2531                projectile_speed,
2532                vertical_angle_offset,
2533                energy_cost: _,
2534                num_projectiles,
2535                projectile_spread,
2536                auto_aim,
2537                movement_modifier,
2538                ori_modifier,
2539                marker,
2540                meta: _,
2541            } => CharacterState::BasicRanged(basic_ranged::Data {
2542                static_data: basic_ranged::StaticData {
2543                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2544                    recover_duration: Duration::from_secs_f32(*recover_duration),
2545                    projectile: projectile.clone(),
2546                    projectile_body: *projectile_body,
2547                    projectile_light: *projectile_light,
2548                    projectile_speed: *projectile_speed,
2549                    vertical_angle_offset: *vertical_angle_offset,
2550                    num_projectiles: *num_projectiles,
2551                    projectile_spread: *projectile_spread,
2552                    auto_aim: *auto_aim,
2553                    ability_info,
2554                    movement_modifier: *movement_modifier,
2555                    ori_modifier: *ori_modifier,
2556                    marker: *marker,
2557                },
2558                timer: Duration::default(),
2559                stage_section: StageSection::Buildup,
2560                exhausted: false,
2561                movement_modifier: movement_modifier.buildup,
2562                ori_modifier: ori_modifier.buildup,
2563            }),
2564            CharacterAbility::Boost {
2565                movement_duration,
2566                only_up,
2567                speed,
2568                max_exit_velocity,
2569                meta: _,
2570            } => CharacterState::Boost(boost::Data {
2571                static_data: boost::StaticData {
2572                    movement_duration: Duration::from_secs_f32(*movement_duration),
2573                    only_up: *only_up,
2574                    speed: *speed,
2575                    max_exit_velocity: *max_exit_velocity,
2576                    ability_info,
2577                },
2578                timer: Duration::default(),
2579            }),
2580            CharacterAbility::GlideBoost { booster, meta: _ } => {
2581                let scale = data.body.dimensions().z.sqrt();
2582                let mut glide_data = glide::Data::new(scale * 4.5, scale, *data.ori);
2583                glide_data.booster = Some(*booster);
2584
2585                CharacterState::Glide(glide_data)
2586            },
2587            CharacterAbility::DashMelee {
2588                energy_cost: _,
2589                energy_drain,
2590                forward_speed,
2591                buildup_duration,
2592                charge_duration,
2593                swing_duration,
2594                recover_duration,
2595                melee_constructor,
2596                ori_modifier,
2597                auto_charge,
2598                charge_through,
2599                frontend_specifier,
2600                meta: _,
2601            } => CharacterState::DashMelee(dash_melee::Data {
2602                static_data: dash_melee::StaticData {
2603                    energy_drain: *energy_drain,
2604                    forward_speed: *forward_speed,
2605                    auto_charge: *auto_charge,
2606                    charge_through: *charge_through,
2607                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2608                    charge_duration: Duration::from_secs_f32(*charge_duration),
2609                    swing_duration: Duration::from_secs_f32(*swing_duration),
2610                    recover_duration: Duration::from_secs_f32(*recover_duration),
2611                    melee_constructor: melee_constructor.clone(),
2612                    ori_modifier: *ori_modifier,
2613                    frontend_specifier: *frontend_specifier,
2614                    ability_info,
2615                },
2616                auto_charge: false,
2617                timer: Duration::default(),
2618                stage_section: StageSection::Buildup,
2619            }),
2620            CharacterAbility::BasicBlock {
2621                buildup_duration,
2622                recover_duration,
2623                max_angle,
2624                block_strength,
2625                parry_window,
2626                energy_cost,
2627                energy_regen,
2628                can_hold,
2629                blocked_attacks,
2630                meta: _,
2631            } => CharacterState::BasicBlock(basic_block::Data {
2632                static_data: basic_block::StaticData {
2633                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2634                    recover_duration: Duration::from_secs_f32(*recover_duration),
2635                    max_angle: *max_angle,
2636                    block_strength: *block_strength,
2637                    parry_window: *parry_window,
2638                    energy_cost: *energy_cost,
2639                    energy_regen: *energy_regen,
2640                    can_hold: *can_hold,
2641                    blocked_attacks: *blocked_attacks,
2642                    ability_info,
2643                },
2644                timer: Duration::default(),
2645                stage_section: StageSection::Buildup,
2646                is_parry: false,
2647            }),
2648            CharacterAbility::Roll {
2649                energy_cost: _,
2650                buildup_duration,
2651                movement_duration,
2652                recover_duration,
2653                roll_strength,
2654                attack_immunities,
2655                was_cancel,
2656                meta: _,
2657            } => CharacterState::Roll(roll::Data {
2658                static_data: roll::StaticData {
2659                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2660                    movement_duration: Duration::from_secs_f32(*movement_duration),
2661                    recover_duration: Duration::from_secs_f32(*recover_duration),
2662                    roll_strength: *roll_strength,
2663                    attack_immunities: *attack_immunities,
2664                    was_cancel: *was_cancel,
2665                    ability_info,
2666                },
2667                timer: Duration::default(),
2668                stage_section: StageSection::Buildup,
2669                was_wielded: false, // false by default. utils might set it to true
2670                prev_aimed_dir: None,
2671                is_sneaking: false,
2672            }),
2673            CharacterAbility::ComboMelee2 {
2674                strikes,
2675                energy_cost_per_strike,
2676                specifier,
2677                auto_progress,
2678                meta: _,
2679            } => CharacterState::ComboMelee2(combo_melee2::Data {
2680                static_data: combo_melee2::StaticData {
2681                    strikes: strikes.iter().cloned().map(|s| s.to_duration()).collect(),
2682                    energy_cost_per_strike: *energy_cost_per_strike,
2683                    specifier: *specifier,
2684                    auto_progress: *auto_progress,
2685                    ability_info,
2686                },
2687                exhausted: false,
2688                start_next_strike: false,
2689                timer: Duration::default(),
2690                stage_section: StageSection::Buildup,
2691                completed_strikes: 0,
2692                movement_modifier: strikes.first().and_then(|s| s.movement_modifier.buildup),
2693                ori_modifier: strikes.first().and_then(|s| s.ori_modifier.buildup),
2694            }),
2695            CharacterAbility::LeapExplosionShockwave {
2696                energy_cost: _,
2697                buildup_duration,
2698                movement_duration,
2699                swing_duration,
2700                recover_duration,
2701                forward_leap_strength,
2702                vertical_leap_strength,
2703                explosion_damage,
2704                explosion_poise,
2705                explosion_knockback,
2706                explosion_radius,
2707                min_falloff,
2708                explosion_dodgeable,
2709                destroy_terrain,
2710                replace_terrain,
2711                eye_height,
2712                reagent,
2713                shockwave_damage,
2714                shockwave_poise,
2715                shockwave_knockback,
2716                shockwave_angle,
2717                shockwave_vertical_angle,
2718                shockwave_speed,
2719                shockwave_duration,
2720                shockwave_dodgeable,
2721                shockwave_damage_effect,
2722                shockwave_damage_kind,
2723                shockwave_specifier,
2724                move_efficiency,
2725                meta: _,
2726            } => CharacterState::LeapExplosionShockwave(leap_explosion_shockwave::Data {
2727                static_data: leap_explosion_shockwave::StaticData {
2728                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2729                    movement_duration: Duration::from_secs_f32(*movement_duration),
2730                    swing_duration: Duration::from_secs_f32(*swing_duration),
2731                    recover_duration: Duration::from_secs_f32(*recover_duration),
2732                    forward_leap_strength: *forward_leap_strength,
2733                    vertical_leap_strength: *vertical_leap_strength,
2734                    explosion_damage: *explosion_damage,
2735                    explosion_poise: *explosion_poise,
2736                    explosion_knockback: *explosion_knockback,
2737                    explosion_radius: *explosion_radius,
2738                    min_falloff: *min_falloff,
2739                    explosion_dodgeable: *explosion_dodgeable,
2740                    destroy_terrain: *destroy_terrain,
2741                    replace_terrain: *replace_terrain,
2742                    eye_height: *eye_height,
2743                    reagent: *reagent,
2744                    shockwave_damage: *shockwave_damage,
2745                    shockwave_poise: *shockwave_poise,
2746                    shockwave_knockback: *shockwave_knockback,
2747                    shockwave_angle: *shockwave_angle,
2748                    shockwave_vertical_angle: *shockwave_vertical_angle,
2749                    shockwave_speed: *shockwave_speed,
2750                    shockwave_duration: Duration::from_secs_f32(*shockwave_duration),
2751                    shockwave_dodgeable: *shockwave_dodgeable,
2752                    shockwave_damage_effect: shockwave_damage_effect.clone(),
2753                    shockwave_damage_kind: *shockwave_damage_kind,
2754                    shockwave_specifier: *shockwave_specifier,
2755                    move_efficiency: *move_efficiency,
2756                    ability_info,
2757                },
2758                timer: Duration::default(),
2759                stage_section: StageSection::Buildup,
2760                exhausted: false,
2761            }),
2762            CharacterAbility::LeapMelee {
2763                energy_cost: _,
2764                buildup_duration,
2765                movement_duration,
2766                swing_duration,
2767                recover_duration,
2768                melee_constructor,
2769                forward_leap_strength,
2770                vertical_leap_strength,
2771                specifier,
2772                meta: _,
2773            } => CharacterState::LeapMelee(leap_melee::Data {
2774                static_data: leap_melee::StaticData {
2775                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2776                    movement_duration: Duration::from_secs_f32(*movement_duration),
2777                    swing_duration: Duration::from_secs_f32(*swing_duration),
2778                    recover_duration: Duration::from_secs_f32(*recover_duration),
2779                    melee_constructor: melee_constructor.clone(),
2780                    forward_leap_strength: *forward_leap_strength,
2781                    vertical_leap_strength: *vertical_leap_strength,
2782                    ability_info,
2783                    specifier: *specifier,
2784                },
2785                timer: Duration::default(),
2786                stage_section: StageSection::Buildup,
2787                exhausted: false,
2788            }),
2789            CharacterAbility::LeapShockwave {
2790                energy_cost: _,
2791                buildup_duration,
2792                movement_duration,
2793                swing_duration,
2794                recover_duration,
2795                damage,
2796                poise_damage,
2797                knockback,
2798                shockwave_angle,
2799                shockwave_vertical_angle,
2800                shockwave_speed,
2801                shockwave_duration,
2802                dodgeable,
2803                move_efficiency,
2804                damage_kind,
2805                specifier,
2806                damage_effect,
2807                forward_leap_strength,
2808                vertical_leap_strength,
2809                meta: _,
2810            } => CharacterState::LeapShockwave(leap_shockwave::Data {
2811                static_data: leap_shockwave::StaticData {
2812                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2813                    movement_duration: Duration::from_secs_f32(*movement_duration),
2814                    swing_duration: Duration::from_secs_f32(*swing_duration),
2815                    recover_duration: Duration::from_secs_f32(*recover_duration),
2816                    damage: *damage,
2817                    poise_damage: *poise_damage,
2818                    knockback: *knockback,
2819                    shockwave_angle: *shockwave_angle,
2820                    shockwave_vertical_angle: *shockwave_vertical_angle,
2821                    shockwave_speed: *shockwave_speed,
2822                    shockwave_duration: Duration::from_secs_f32(*shockwave_duration),
2823                    dodgeable: *dodgeable,
2824                    move_efficiency: *move_efficiency,
2825                    damage_kind: *damage_kind,
2826                    specifier: *specifier,
2827                    damage_effect: damage_effect.clone(),
2828                    forward_leap_strength: *forward_leap_strength,
2829                    vertical_leap_strength: *vertical_leap_strength,
2830                    ability_info,
2831                },
2832                timer: Duration::default(),
2833                stage_section: StageSection::Buildup,
2834                exhausted: false,
2835            }),
2836            CharacterAbility::ChargedMelee {
2837                energy_cost,
2838                energy_drain,
2839                buildup_strike,
2840                charge_duration,
2841                swing_duration,
2842                hit_timing,
2843                recover_duration,
2844                melee_constructor,
2845                specifier,
2846                custom_combo,
2847                meta: _,
2848                movement_modifier,
2849                ori_modifier,
2850            } => CharacterState::ChargedMelee(charged_melee::Data {
2851                static_data: charged_melee::StaticData {
2852                    energy_cost: *energy_cost,
2853                    energy_drain: *energy_drain,
2854                    buildup_strike: buildup_strike
2855                        .as_ref()
2856                        .map(|(dur, strike)| (Duration::from_secs_f32(*dur), strike.clone())),
2857                    charge_duration: Duration::from_secs_f32(*charge_duration),
2858                    swing_duration: Duration::from_secs_f32(*swing_duration),
2859                    hit_timing: *hit_timing,
2860                    recover_duration: Duration::from_secs_f32(*recover_duration),
2861                    melee_constructor: melee_constructor.clone(),
2862                    ability_info,
2863                    specifier: *specifier,
2864                    custom_combo: *custom_combo,
2865                    movement_modifier: *movement_modifier,
2866                    ori_modifier: *ori_modifier,
2867                },
2868                stage_section: if buildup_strike.is_some() {
2869                    StageSection::Buildup
2870                } else {
2871                    StageSection::Charge
2872                },
2873                timer: Duration::default(),
2874                exhausted: false,
2875                charge_amount: 0.0,
2876                movement_modifier: movement_modifier.buildup,
2877                ori_modifier: ori_modifier.buildup,
2878            }),
2879            CharacterAbility::ChargedRanged {
2880                energy_cost: _,
2881                energy_drain,
2882                idle_drain,
2883                projectile,
2884                buildup_duration,
2885                charge_duration,
2886                recover_duration,
2887                projectile_body,
2888                projectile_light,
2889                initial_projectile_speed,
2890                scaled_projectile_speed,
2891                projectile_spread,
2892                num_projectiles,
2893                marker,
2894                move_speed,
2895                meta: _,
2896            } => CharacterState::ChargedRanged(charged_ranged::Data {
2897                static_data: charged_ranged::StaticData {
2898                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2899                    charge_duration: Duration::from_secs_f32(*charge_duration),
2900                    recover_duration: Duration::from_secs_f32(*recover_duration),
2901                    energy_drain: *energy_drain,
2902                    idle_drain: *idle_drain,
2903                    projectile: projectile.clone(),
2904                    projectile_body: *projectile_body,
2905                    projectile_light: *projectile_light,
2906                    initial_projectile_speed: *initial_projectile_speed,
2907                    scaled_projectile_speed: *scaled_projectile_speed,
2908                    projectile_spread: *projectile_spread,
2909                    num_projectiles: *num_projectiles,
2910                    marker: *marker,
2911                    move_speed: *move_speed,
2912                    ability_info,
2913                },
2914                timer: Duration::default(),
2915                stage_section: StageSection::Buildup,
2916                exhausted: false,
2917            }),
2918            CharacterAbility::RapidRanged {
2919                initial_energy: _,
2920                energy_cost,
2921                buildup_duration,
2922                shoot_duration,
2923                recover_duration,
2924                options,
2925                projectile,
2926                projectile_body,
2927                projectile_light,
2928                projectile_speed,
2929                specifier,
2930                meta: _,
2931            } => CharacterState::RapidRanged(rapid_ranged::Data {
2932                static_data: rapid_ranged::StaticData {
2933                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
2934                    shoot_duration: Duration::from_secs_f32(*shoot_duration),
2935                    recover_duration: Duration::from_secs_f32(*recover_duration),
2936                    energy_cost: *energy_cost,
2937                    options: *options,
2938                    projectile: projectile.clone(),
2939                    projectile_body: *projectile_body,
2940                    projectile_light: *projectile_light,
2941                    projectile_speed: *projectile_speed,
2942                    ability_info,
2943                    specifier: *specifier,
2944                },
2945                timer: Duration::default(),
2946                stage_section: StageSection::Buildup,
2947                projectiles_fired: 0,
2948                speed: 1.0,
2949            }),
2950            CharacterAbility::Throw {
2951                energy_cost: _,
2952                energy_drain,
2953                buildup_duration,
2954                charge_duration,
2955                throw_duration,
2956                recover_duration,
2957                projectile,
2958                projectile_light,
2959                projectile_dir,
2960                initial_projectile_speed,
2961                scaled_projectile_speed,
2962                damage_effect,
2963                move_speed,
2964                meta: _,
2965            } => {
2966                let hand_info = if let Some(hand_info) = ability_info.hand {
2967                    hand_info
2968                } else {
2969                    return Err(CharacterStateCreationError::MissingHandInfo);
2970                };
2971
2972                let equip_slot = hand_info.to_equip_slot();
2973
2974                let equipped_item =
2975                    if let Some(item) = data.inventory.and_then(|inv| inv.equipped(equip_slot)) {
2976                        item
2977                    } else {
2978                        return Err(CharacterStateCreationError::MissingItem);
2979                    };
2980
2981                let item_hash = equipped_item.item_hash();
2982
2983                let tool_kind = if let ItemKind::Tool(Tool { kind, .. }) = *equipped_item.kind() {
2984                    kind
2985                } else {
2986                    return Err(CharacterStateCreationError::InvalidItemKind);
2987                };
2988
2989                CharacterState::Throw(throw::Data {
2990                    static_data: throw::StaticData {
2991                        buildup_duration: Duration::from_secs_f32(*buildup_duration),
2992                        charge_duration: Duration::from_secs_f32(*charge_duration),
2993                        throw_duration: Duration::from_secs_f32(*throw_duration),
2994                        recover_duration: Duration::from_secs_f32(*recover_duration),
2995                        energy_drain: *energy_drain,
2996                        projectile: projectile.clone(),
2997                        projectile_light: *projectile_light,
2998                        projectile_dir: *projectile_dir,
2999                        initial_projectile_speed: *initial_projectile_speed,
3000                        scaled_projectile_speed: *scaled_projectile_speed,
3001                        move_speed: *move_speed,
3002                        ability_info,
3003                        damage_effect: damage_effect.clone(),
3004                        equip_slot,
3005                        item_hash,
3006                        hand_info,
3007                        tool_kind,
3008                    },
3009                    timer: Duration::default(),
3010                    stage_section: StageSection::Buildup,
3011                    exhausted: false,
3012                })
3013            },
3014            CharacterAbility::Shockwave {
3015                energy_cost: _,
3016                buildup_duration,
3017                swing_duration,
3018                recover_duration,
3019                damage,
3020                poise_damage,
3021                knockback,
3022                shockwave_angle,
3023                shockwave_vertical_angle,
3024                shockwave_speed,
3025                shockwave_duration,
3026                dodgeable,
3027                move_efficiency,
3028                damage_kind,
3029                specifier,
3030                ori_rate,
3031                damage_effect,
3032                timing,
3033                emit_outcome,
3034                minimum_combo,
3035                combo_consumption,
3036                meta: _,
3037            } => CharacterState::Shockwave(shockwave::Data {
3038                static_data: shockwave::StaticData {
3039                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3040                    swing_duration: Duration::from_secs_f32(*swing_duration),
3041                    recover_duration: Duration::from_secs_f32(*recover_duration),
3042                    damage: *damage,
3043                    poise_damage: *poise_damage,
3044                    knockback: *knockback,
3045                    shockwave_angle: *shockwave_angle,
3046                    shockwave_vertical_angle: *shockwave_vertical_angle,
3047                    shockwave_speed: *shockwave_speed,
3048                    shockwave_duration: Duration::from_secs_f32(*shockwave_duration),
3049                    dodgeable: *dodgeable,
3050                    move_efficiency: *move_efficiency,
3051                    damage_effect: damage_effect.clone(),
3052                    ability_info,
3053                    damage_kind: *damage_kind,
3054                    specifier: *specifier,
3055                    ori_rate: *ori_rate,
3056                    timing: *timing,
3057                    emit_outcome: *emit_outcome,
3058                    minimum_combo: *minimum_combo,
3059                    combo_on_use: data.combo.map_or(0, |c| c.counter()),
3060                    combo_consumption: *combo_consumption,
3061                },
3062                timer: Duration::default(),
3063                stage_section: StageSection::Buildup,
3064            }),
3065            CharacterAbility::Explosion {
3066                energy_cost: _,
3067                buildup_duration,
3068                action_duration,
3069                recover_duration,
3070                damage,
3071                poise,
3072                knockback,
3073                radius,
3074                min_falloff,
3075                dodgeable,
3076                destroy_terrain,
3077                replace_terrain,
3078                eye_height,
3079                reagent,
3080                movement_modifier,
3081                ori_modifier,
3082                meta: _,
3083            } => CharacterState::Explosion(explosion::Data {
3084                static_data: explosion::StaticData {
3085                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3086                    action_duration: Duration::from_secs_f32(*action_duration),
3087                    recover_duration: Duration::from_secs_f32(*recover_duration),
3088                    damage: *damage,
3089                    poise: *poise,
3090                    knockback: *knockback,
3091                    radius: *radius,
3092                    min_falloff: *min_falloff,
3093                    dodgeable: *dodgeable,
3094                    destroy_terrain: *destroy_terrain,
3095                    replace_terrain: *replace_terrain,
3096                    eye_height: *eye_height,
3097                    reagent: *reagent,
3098                    movement_modifier: *movement_modifier,
3099                    ori_modifier: *ori_modifier,
3100                    ability_info,
3101                },
3102                timer: Duration::default(),
3103                stage_section: StageSection::Buildup,
3104                movement_modifier: movement_modifier.buildup,
3105                ori_modifier: ori_modifier.buildup,
3106            }),
3107            CharacterAbility::BasicBeam {
3108                buildup_duration,
3109                recover_duration,
3110                beam_duration,
3111                damage,
3112                tick_rate,
3113                range,
3114                dodgeable,
3115                blockable,
3116                max_angle,
3117                damage_effect,
3118                energy_regen,
3119                energy_drain,
3120                move_efficiency,
3121                ori_rate,
3122                specifier,
3123                meta: _,
3124            } => CharacterState::BasicBeam(basic_beam::Data {
3125                static_data: basic_beam::StaticData {
3126                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3127                    recover_duration: Duration::from_secs_f32(*recover_duration),
3128                    beam_duration: Secs(*beam_duration),
3129                    damage: *damage,
3130                    tick_rate: *tick_rate,
3131                    range: *range,
3132                    dodgeable: *dodgeable,
3133                    blockable: *blockable,
3134                    end_radius: max_angle.to_radians().tan() * *range,
3135                    damage_effect: damage_effect.clone(),
3136                    energy_regen: *energy_regen,
3137                    energy_drain: *energy_drain,
3138                    ability_info,
3139                    move_efficiency: *move_efficiency,
3140                    ori_rate: *ori_rate,
3141                    specifier: *specifier,
3142                },
3143                timer: Duration::default(),
3144                stage_section: StageSection::Buildup,
3145                aim_dir: data.ori.look_dir(),
3146                beam_offset: data.pos.0,
3147            }),
3148            CharacterAbility::BasicAura {
3149                buildup_duration,
3150                cast_duration,
3151                recover_duration,
3152                targets,
3153                auras,
3154                aura_duration,
3155                range,
3156                energy_cost: _,
3157                scales_with_combo,
3158                specifier,
3159                meta: _,
3160            } => CharacterState::BasicAura(basic_aura::Data {
3161                static_data: basic_aura::StaticData {
3162                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3163                    cast_duration: Duration::from_secs_f32(*cast_duration),
3164                    recover_duration: Duration::from_secs_f32(*recover_duration),
3165                    targets: *targets,
3166                    auras: auras.clone(),
3167                    aura_duration: *aura_duration,
3168                    range: *range,
3169                    ability_info,
3170                    scales_with_combo: *scales_with_combo,
3171                    combo_at_cast: data.combo.map_or(0, |c| c.counter()),
3172                    specifier: *specifier,
3173                },
3174                timer: Duration::default(),
3175                stage_section: StageSection::Buildup,
3176            }),
3177            CharacterAbility::StaticAura {
3178                buildup_duration,
3179                cast_duration,
3180                recover_duration,
3181                targets,
3182                auras,
3183                aura_duration,
3184                range,
3185                energy_cost: _,
3186                sprite_info,
3187                meta: _,
3188            } => CharacterState::StaticAura(static_aura::Data {
3189                static_data: static_aura::StaticData {
3190                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3191                    cast_duration: Duration::from_secs_f32(*cast_duration),
3192                    recover_duration: Duration::from_secs_f32(*recover_duration),
3193                    targets: *targets,
3194                    auras: auras.clone(),
3195                    aura_duration: *aura_duration,
3196                    range: *range,
3197                    ability_info,
3198                    sprite_info: *sprite_info,
3199                },
3200                timer: Duration::default(),
3201                stage_section: StageSection::Buildup,
3202                achieved_radius: sprite_info.map(|si| si.summon_distance.0.floor() as i32 - 1),
3203            }),
3204            CharacterAbility::Blink {
3205                buildup_duration,
3206                recover_duration,
3207                max_range,
3208                frontend_specifier,
3209                meta: _,
3210            } => CharacterState::Blink(blink::Data {
3211                static_data: blink::StaticData {
3212                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3213                    recover_duration: Duration::from_secs_f32(*recover_duration),
3214                    max_range: *max_range,
3215                    frontend_specifier: *frontend_specifier,
3216                    ability_info,
3217                },
3218                timer: Duration::default(),
3219                stage_section: StageSection::Buildup,
3220            }),
3221            CharacterAbility::BasicSummon {
3222                buildup_duration,
3223                cast_duration,
3224                recover_duration,
3225                summon_info,
3226                movement_modifier,
3227                ori_modifier,
3228                meta: _,
3229            } => CharacterState::BasicSummon(basic_summon::Data {
3230                static_data: basic_summon::StaticData {
3231                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3232                    cast_duration: Duration::from_secs_f32(*cast_duration),
3233                    recover_duration: Duration::from_secs_f32(*recover_duration),
3234                    summon_info: summon_info.clone(),
3235                    movement_modifier: *movement_modifier,
3236                    ori_modifier: *ori_modifier,
3237                    ability_info,
3238                },
3239                summon_count: 0,
3240                timer: Duration::default(),
3241                stage_section: StageSection::Buildup,
3242                movement_modifier: movement_modifier.buildup,
3243                ori_modifier: ori_modifier.buildup,
3244            }),
3245            CharacterAbility::SelfBuff {
3246                buildup_duration,
3247                cast_duration,
3248                recover_duration,
3249                buffs,
3250                use_raw_buff_strength: _,
3251                buff_cat,
3252                energy_cost: _,
3253                combo_cost,
3254                combo_scaling,
3255                enforced_limit,
3256                meta: _,
3257                specifier,
3258            } => CharacterState::SelfBuff(self_buff::Data {
3259                static_data: self_buff::StaticData {
3260                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3261                    cast_duration: Duration::from_secs_f32(*cast_duration),
3262                    recover_duration: Duration::from_secs_f32(*recover_duration),
3263                    buffs: buffs.clone(),
3264                    buff_cat: buff_cat.clone(),
3265                    combo_cost: *combo_cost,
3266                    combo_scaling: *combo_scaling,
3267                    combo_on_use: data.combo.map_or(0, |c| c.counter()),
3268                    enforced_limit: *enforced_limit,
3269                    ability_info,
3270                    specifier: *specifier,
3271                },
3272                timer: Duration::default(),
3273                stage_section: StageSection::Buildup,
3274            }),
3275            CharacterAbility::SpriteSummon {
3276                buildup_duration,
3277                cast_duration,
3278                recover_duration,
3279                sprite,
3280                del_timeout,
3281                summon_distance,
3282                sparseness,
3283                angle,
3284                anchor,
3285                move_efficiency,
3286                ori_modifier,
3287                meta: _,
3288            } => CharacterState::SpriteSummon(sprite_summon::Data {
3289                static_data: sprite_summon::StaticData {
3290                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3291                    cast_duration: Duration::from_secs_f32(*cast_duration),
3292                    recover_duration: Duration::from_secs_f32(*recover_duration),
3293                    sprite: *sprite,
3294                    del_timeout: *del_timeout,
3295                    summon_distance: *summon_distance,
3296                    sparseness: *sparseness,
3297                    angle: *angle,
3298                    anchor: *anchor,
3299                    move_efficiency: *move_efficiency,
3300                    ori_modifier: *ori_modifier,
3301                    ability_info,
3302                },
3303                timer: Duration::default(),
3304                stage_section: StageSection::Buildup,
3305                achieved_radius: summon_distance.0.floor() as i32 - 1,
3306            }),
3307            CharacterAbility::Music {
3308                play_duration,
3309                ori_modifier,
3310                meta: _,
3311            } => CharacterState::Music(music::Data {
3312                static_data: music::StaticData {
3313                    play_duration: Duration::from_secs_f32(*play_duration),
3314                    ori_modifier: *ori_modifier,
3315                    ability_info,
3316                },
3317                timer: Duration::default(),
3318                stage_section: StageSection::Action,
3319                exhausted: false,
3320            }),
3321            CharacterAbility::FinisherMelee {
3322                energy_cost: _,
3323                buildup_duration,
3324                swing_duration,
3325                recover_duration,
3326                melee_constructor,
3327                minimum_combo,
3328                scaling,
3329                combo_consumption,
3330                meta: _,
3331            } => CharacterState::FinisherMelee(finisher_melee::Data {
3332                static_data: finisher_melee::StaticData {
3333                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3334                    swing_duration: Duration::from_secs_f32(*swing_duration),
3335                    recover_duration: Duration::from_secs_f32(*recover_duration),
3336                    melee_constructor: melee_constructor.clone(),
3337                    scaling: *scaling,
3338                    minimum_combo: *minimum_combo,
3339                    combo_on_use: data.combo.map_or(0, |c| c.counter()),
3340                    combo_consumption: *combo_consumption,
3341                    ability_info,
3342                },
3343                timer: Duration::default(),
3344                stage_section: StageSection::Buildup,
3345                exhausted: false,
3346            }),
3347            CharacterAbility::DiveMelee {
3348                buildup_duration,
3349                movement_duration,
3350                swing_duration,
3351                recover_duration,
3352                melee_constructor,
3353                energy_cost: _,
3354                vertical_speed,
3355                max_scaling,
3356                meta: _,
3357            } => CharacterState::DiveMelee(dive_melee::Data {
3358                static_data: dive_melee::StaticData {
3359                    buildup_duration: buildup_duration.map(Duration::from_secs_f32),
3360                    movement_duration: Duration::from_secs_f32(*movement_duration),
3361                    swing_duration: Duration::from_secs_f32(*swing_duration),
3362                    recover_duration: Duration::from_secs_f32(*recover_duration),
3363                    vertical_speed: *vertical_speed,
3364                    melee_constructor: melee_constructor.clone(),
3365                    max_scaling: *max_scaling,
3366                    ability_info,
3367                },
3368                timer: Duration::default(),
3369                stage_section: if data.physics.on_ground.is_none() || buildup_duration.is_none() {
3370                    StageSection::Movement
3371                } else {
3372                    StageSection::Buildup
3373                },
3374                exhausted: false,
3375                max_vertical_speed: 0.0,
3376            }),
3377            CharacterAbility::RiposteMelee {
3378                energy_cost: _,
3379                buildup_duration,
3380                swing_duration,
3381                recover_duration,
3382                whiffed_recover_duration,
3383                block_strength,
3384                melee_constructor,
3385                meta: _,
3386            } => CharacterState::RiposteMelee(riposte_melee::Data {
3387                static_data: riposte_melee::StaticData {
3388                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3389                    swing_duration: Duration::from_secs_f32(*swing_duration),
3390                    recover_duration: Duration::from_secs_f32(*recover_duration),
3391                    whiffed_recover_duration: Duration::from_secs_f32(*whiffed_recover_duration),
3392                    block_strength: *block_strength,
3393                    melee_constructor: melee_constructor.clone(),
3394                    ability_info,
3395                },
3396                timer: Duration::default(),
3397                stage_section: StageSection::Buildup,
3398                exhausted: false,
3399                whiffed: true,
3400            }),
3401            CharacterAbility::RapidMelee {
3402                buildup_duration,
3403                swing_duration,
3404                recover_duration,
3405                melee_constructor,
3406                energy_cost,
3407                max_strikes,
3408                move_modifier,
3409                ori_modifier,
3410                minimum_combo,
3411                frontend_specifier,
3412                meta: _,
3413            } => CharacterState::RapidMelee(rapid_melee::Data {
3414                static_data: rapid_melee::StaticData {
3415                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3416                    swing_duration: Duration::from_secs_f32(*swing_duration),
3417                    recover_duration: Duration::from_secs_f32(*recover_duration),
3418                    melee_constructor: melee_constructor.clone(),
3419                    energy_cost: *energy_cost,
3420                    max_strikes: *max_strikes,
3421                    move_modifier: *move_modifier,
3422                    ori_modifier: *ori_modifier,
3423                    minimum_combo: *minimum_combo,
3424                    frontend_specifier: *frontend_specifier,
3425                    ability_info,
3426                },
3427                timer: Duration::default(),
3428                current_strike: 1,
3429                stage_section: StageSection::Buildup,
3430                exhausted: false,
3431            }),
3432            CharacterAbility::Transform {
3433                buildup_duration,
3434                recover_duration,
3435                target,
3436                specifier,
3437                allow_players,
3438                meta: _,
3439            } => CharacterState::Transform(transform::Data {
3440                static_data: transform::StaticData {
3441                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3442                    recover_duration: Duration::from_secs_f32(*recover_duration),
3443                    specifier: *specifier,
3444                    allow_players: *allow_players,
3445                    target: target.to_owned(),
3446                    ability_info,
3447                },
3448                timer: Duration::default(),
3449                stage_section: StageSection::Buildup,
3450            }),
3451            CharacterAbility::RegrowHead {
3452                buildup_duration,
3453                recover_duration,
3454                energy_cost,
3455                specifier,
3456                meta: _,
3457            } => CharacterState::RegrowHead(regrow_head::Data {
3458                static_data: regrow_head::StaticData {
3459                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3460                    recover_duration: Duration::from_secs_f32(*recover_duration),
3461                    specifier: *specifier,
3462                    energy_cost: *energy_cost,
3463                    ability_info,
3464                },
3465                timer: Duration::default(),
3466                stage_section: StageSection::Buildup,
3467            }),
3468            CharacterAbility::LeapRanged {
3469                energy_cost: _,
3470                buildup_duration,
3471                buildup_melee_timing,
3472                movement_duration,
3473                movement_ranged_timing,
3474                land_timeout,
3475                recover_duration,
3476                melee,
3477                melee_required,
3478                projectile,
3479                projectile_body,
3480                projectile_light,
3481                projectile_speed,
3482                horiz_leap_strength,
3483                vert_leap_strength,
3484                meta: _,
3485            } => CharacterState::LeapRanged(leap_ranged::Data {
3486                static_data: leap_ranged::StaticData {
3487                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3488                    buildup_melee_timing: *buildup_melee_timing,
3489                    movement_duration: Duration::from_secs_f32(*movement_duration),
3490                    movement_ranged_timing: *movement_ranged_timing,
3491                    land_timeout: Duration::from_secs_f32(*land_timeout),
3492                    recover_duration: Duration::from_secs_f32(*recover_duration),
3493                    melee: melee.clone(),
3494                    melee_required: *melee_required,
3495                    projectile: projectile.clone(),
3496                    projectile_body: *projectile_body,
3497                    projectile_light: *projectile_light,
3498                    projectile_speed: *projectile_speed,
3499                    horiz_leap_strength: *horiz_leap_strength,
3500                    vert_leap_strength: *vert_leap_strength,
3501                    ability_info,
3502                },
3503                timer: Duration::default(),
3504                stage_section: StageSection::Buildup,
3505                melee_done: false,
3506                ranged_done: false,
3507            }),
3508            CharacterAbility::Simple {
3509                energy_cost: _,
3510                combo_cost: _,
3511                buildup_duration,
3512                meta: _,
3513            } => CharacterState::Simple(simple::Data {
3514                static_data: simple::StaticData {
3515                    buildup_duration: Duration::from_secs_f32(*buildup_duration),
3516                    ability_info,
3517                },
3518                timer: Duration::default(),
3519                stage_section: StageSection::Buildup,
3520            }),
3521        })
3522    }
3523}
3524
3525#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
3526#[serde(deny_unknown_fields)]
3527pub struct AbilityMeta {
3528    #[serde(default)]
3529    pub capabilities: Capability,
3530    #[serde(default)]
3531    /// This is an event that gets emitted when the ability is first activated
3532    pub init_event: Option<AbilityInitEvent>,
3533    #[serde(default)]
3534    pub requirements: AbilityRequirements,
3535    /// Adjusts stats of ability when activated based on context.
3536    // If we ever add more, I guess change to a vec? Or maybe just an array if we want to keep
3537    // AbilityMeta small?
3538    pub contextual_stats: Option<StatAdj>,
3539    /// If provided, multiplies the precision power from armor for this ability
3540    pub precision_power_mult: Option<f32>,
3541}
3542
3543impl StatAdj {
3544    pub fn equivalent_stats(&self, data: &JoinData) -> Stats {
3545        let mut stats = Stats::one();
3546        let add = match self.context {
3547            StatContext::PoiseResilience(base) => {
3548                let poise_res = combat::compute_poise_resilience(data.inventory, data.msm);
3549                poise_res.unwrap_or(0.0) / base.max(0.1)
3550            },
3551            StatContext::Stealth(base) => {
3552                let stealth = combat::compute_stealth(data.inventory, data.msm);
3553                stealth / base.max(0.1)
3554            },
3555        };
3556        match self.field {
3557            StatField::EffectPower => {
3558                stats.effect_power += add;
3559            },
3560            StatField::BuffStrength => {
3561                stats.buff_strength += add;
3562            },
3563            StatField::Power => {
3564                stats.power += add;
3565            },
3566        }
3567        stats
3568    }
3569}
3570
3571#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3572pub struct StatAdj {
3573    /// If this much of the stat is achieved, 1.0 will be added to the affected
3574    /// stat
3575    pub context: StatContext,
3576    pub field: StatField,
3577}
3578
3579#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3580pub enum StatContext {
3581    PoiseResilience(f32),
3582    Stealth(f32),
3583}
3584
3585#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3586pub enum StatField {
3587    EffectPower,
3588    BuffStrength,
3589    Power,
3590}
3591
3592#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
3593pub enum AbilityReqItem {
3594    Firedrop,
3595    PoisonClot,
3596    GelidGel,
3597    LevinDust,
3598}
3599
3600impl AbilityReqItem {
3601    pub fn item_def_id(&self) -> ItemDefinitionIdOwned {
3602        match self {
3603            Self::Firedrop => {
3604                ItemDefinitionIdOwned::Simple(String::from("common.items.consumable.firedrop"))
3605            },
3606            Self::PoisonClot => {
3607                ItemDefinitionIdOwned::Simple(String::from("common.items.consumable.poison_clot"))
3608            },
3609            Self::GelidGel => {
3610                ItemDefinitionIdOwned::Simple(String::from("common.items.consumable.gelid_gel"))
3611            },
3612            Self::LevinDust => {
3613                ItemDefinitionIdOwned::Simple(String::from("common.items.consumable.levin_dust"))
3614            },
3615        }
3616    }
3617}
3618
3619// TODO: Later move over things like energy and combo into here
3620#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
3621pub struct AbilityRequirements {
3622    pub stance: Option<Stance>,
3623    pub item: Option<AbilityReqItem>,
3624}
3625
3626impl AbilityRequirements {
3627    pub fn requirements_met(&self, stance: Option<&Stance>, inv: Option<&Inventory>) -> bool {
3628        let AbilityRequirements {
3629            stance: req_stance,
3630            item,
3631        } = self;
3632        let stance_met = req_stance
3633            .is_none_or(|req_stance| stance.is_some_and(|char_stance| req_stance == *char_stance));
3634        let item_met = item.is_none_or(|item| {
3635            inv.is_some_and(|inv| {
3636                inv.get_slot_of_item_by_def_id(&item.item_def_id())
3637                    .is_some()
3638            })
3639        });
3640        stance_met && item_met
3641    }
3642}
3643
3644bitflags::bitflags! {
3645    #[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
3646    // If more are ever needed, first check if any not used anymore, as some were only used in intermediary stages so may be free
3647    pub struct Capability: u8 {
3648        // The ability will parry all blockable attacks in the buildup portion
3649        const PARRIES             = 0b00000001;
3650        // Allows blocking to interrupt the ability at any point
3651        const BLOCK_INTERRUPT     = 0b00000010;
3652        // The ability will block melee attacks in the buildup portion
3653        const BLOCKS              = 0b00000100;
3654        // When in the ability, an entity only receives half as much poise damage
3655        const POISE_RESISTANT     = 0b00001000;
3656        // WHen in the ability, an entity only receives half as much knockback
3657        const KNOCKBACK_RESISTANT = 0b00010000;
3658        // The ability will parry melee attacks in the buildup portion
3659        const PARRIES_MELEE       = 0b00100000;
3660    }
3661}
3662
3663#[derive(
3664    Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord, Default,
3665)]
3666pub enum Stance {
3667    #[default]
3668    None,
3669    Sword(SwordStance),
3670    Bow(BowStance),
3671}
3672
3673impl Stance {
3674    pub fn pseudo_ability_id(&self) -> &str {
3675        match self {
3676            Stance::Sword(SwordStance::Heavy) => "veloren.core.pseudo_abilities.sword.heavy_stance",
3677            Stance::Sword(SwordStance::Agile) => "veloren.core.pseudo_abilities.sword.agile_stance",
3678            Stance::Sword(SwordStance::Defensive) => {
3679                "veloren.core.pseudo_abilities.sword.defensive_stance"
3680            },
3681            Stance::Sword(SwordStance::Crippling) => {
3682                "veloren.core.pseudo_abilities.sword.crippling_stance"
3683            },
3684            Stance::Sword(SwordStance::Cleaving) => {
3685                "veloren.core.pseudo_abilities.sword.cleaving_stance"
3686            },
3687            Stance::Bow(BowStance::Barrage) => "common.abilities.bow.barrage",
3688            Stance::Bow(BowStance::Scatterburst) => "common.abilities.bow.scatterburst",
3689            Stance::Bow(BowStance::IgniteArrow) => "common.abilities.bow.ignite_arrow",
3690            Stance::Bow(BowStance::DrenchArrow) => "common.abilities.bow.drench_arrow",
3691            Stance::Bow(BowStance::FreezeArrow) => "common.abilities.bow.freeze_arrow",
3692            Stance::Bow(BowStance::JoltArrow) => "common.abilities.bow.jolt_arrow",
3693            Stance::Bow(BowStance::PiercingGale) => "common.abilities.bow.piercing_gale",
3694            Stance::Bow(BowStance::Hawkstrike) => "common.abilities.bow.hawkstrike",
3695            Stance::Bow(BowStance::Fusillade) => "common.abilities.bow.fusillade",
3696            Stance::Bow(BowStance::DeathVolley) => "common.abilities.bow.death_volley",
3697            Stance::None => "veloren.core.pseudo_abilities.no_stance",
3698        }
3699    }
3700}
3701
3702#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
3703pub enum SwordStance {
3704    Crippling,
3705    Cleaving,
3706    Defensive,
3707    Heavy,
3708    Agile,
3709}
3710
3711#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
3712pub enum BowStance {
3713    Barrage,
3714    Scatterburst,
3715    IgniteArrow,
3716    DrenchArrow,
3717    FreezeArrow,
3718    JoltArrow,
3719    PiercingGale,
3720    Hawkstrike,
3721    Fusillade,
3722    DeathVolley,
3723}
3724
3725#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3726pub enum AbilityInitEvent {
3727    EnterStance(Stance),
3728    GainBuff {
3729        kind: buff::BuffKind,
3730        strength: f32,
3731        duration: Option<Secs>,
3732    },
3733}
3734
3735impl Component for Stance {
3736    type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
3737}