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