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