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