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