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