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