Skip to main content

veloren_common/comp/
ability.rs

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