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