veloren_common/comp/
ability.rs

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