veloren_common/comp/
ability.rs

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