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