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 vertical_angle_offset: f32,
809 #[serde(default)]
810 num_projectiles: Amount,
811 projectile_spread: Option<ProjectileSpread>,
812 #[serde(default)]
813 auto_aim: bool,
814 #[serde(default)]
815 movement_modifier: MovementModifier,
816 #[serde(default)]
817 ori_modifier: OrientationModifier,
818 #[serde(default)]
819 marker: Option<comp::FrontendMarker>,
820 #[serde(default)]
821 meta: AbilityMeta,
822 },
823 RapidRanged {
824 #[serde(default)]
825 initial_energy: f32,
826 #[serde(default)]
827 energy_cost: f32,
828 buildup_duration: f32,
829 shoot_duration: f32,
830 recover_duration: f32,
831 options: rapid_ranged::Options,
832 projectile: ProjectileConstructor,
833 projectile_body: Body,
834 projectile_light: Option<LightEmitter>,
835 projectile_speed: f32,
836 specifier: Option<rapid_ranged::FrontendSpecifier>,
837 #[serde(default)]
838 meta: AbilityMeta,
839 },
840 Boost {
841 movement_duration: f32,
842 only_up: bool,
843 speed: f32,
844 max_exit_velocity: f32,
845 #[serde(default)]
846 meta: AbilityMeta,
847 },
848 GlideBoost {
849 booster: glide::Boost,
850 #[serde(default)]
851 meta: AbilityMeta,
852 },
853 DashMelee {
854 energy_cost: f32,
855 energy_drain: f32,
856 forward_speed: f32,
857 buildup_duration: f32,
858 charge_duration: f32,
859 swing_duration: f32,
860 recover_duration: f32,
861 melee_constructor: MeleeConstructor,
862 ori_modifier: f32,
863 auto_charge: bool,
864 #[serde(default)]
865 charge_through: bool,
866 #[serde(default)]
867 frontend_specifier: Option<dash_melee::FrontendSpecifier>,
868 #[serde(default)]
869 meta: AbilityMeta,
870 },
871 BasicBlock {
872 buildup_duration: f32,
873 recover_duration: f32,
874 max_angle: f32,
875 block_strength: f32,
876 parry_window: basic_block::ParryWindow,
877 energy_cost: f32,
878 energy_regen: f32,
879 can_hold: bool,
880 blocked_attacks: AttackFilters,
881 #[serde(default)]
882 meta: AbilityMeta,
883 },
884 Roll {
885 energy_cost: f32,
886 buildup_duration: f32,
887 movement_duration: f32,
888 recover_duration: f32,
889 roll_strength: f32,
890 attack_immunities: AttackFilters,
891 was_cancel: bool,
892 #[serde(default)]
893 meta: AbilityMeta,
894 },
895 ComboMelee2 {
896 strikes: Vec<combo_melee2::Strike<f32>>,
897 energy_cost_per_strike: f32,
898 specifier: Option<combo_melee2::FrontendSpecifier>,
899 #[serde(default)]
900 auto_progress: bool,
901 #[serde(default)]
902 meta: AbilityMeta,
903 },
904 LeapExplosionShockwave {
905 energy_cost: f32,
906 buildup_duration: f32,
907 movement_duration: f32,
908 swing_duration: f32,
909 recover_duration: f32,
910 forward_leap_strength: f32,
911 vertical_leap_strength: f32,
912 explosion_damage: f32,
913 explosion_poise: f32,
914 explosion_knockback: Knockback,
915 explosion_radius: f32,
916 min_falloff: f32,
917 #[serde(default)]
918 explosion_dodgeable: Dodgeable,
919 #[serde(default)]
920 destroy_terrain: Option<(f32, ColorPreset)>,
921 #[serde(default)]
922 replace_terrain: Option<(f32, TerrainReplacementPreset)>,
923 #[serde(default)]
924 eye_height: bool,
925 #[serde(default)]
926 reagent: Option<Reagent>,
927 shockwave_damage: f32,
928 shockwave_poise: f32,
929 shockwave_knockback: Knockback,
930 shockwave_angle: f32,
931 shockwave_vertical_angle: f32,
932 shockwave_speed: f32,
933 shockwave_duration: f32,
934 #[serde(default)]
935 shockwave_dodgeable: Dodgeable,
936 #[serde(default)]
937 shockwave_damage_effect: Option<CombatEffect>,
938 shockwave_damage_kind: DamageKind,
939 shockwave_specifier: comp::shockwave::FrontendSpecifier,
940 move_efficiency: f32,
941 #[serde(default)]
942 meta: AbilityMeta,
943 },
944 LeapMelee {
945 energy_cost: f32,
946 buildup_duration: f32,
947 movement_duration: f32,
948 swing_duration: f32,
949 recover_duration: f32,
950 melee_constructor: MeleeConstructor,
951 forward_leap_strength: f32,
952 vertical_leap_strength: f32,
953 specifier: Option<leap_melee::FrontendSpecifier>,
954 #[serde(default)]
955 meta: AbilityMeta,
956 },
957 LeapShockwave {
958 energy_cost: f32,
959 buildup_duration: f32,
960 movement_duration: f32,
961 swing_duration: f32,
962 recover_duration: f32,
963 damage: f32,
964 poise_damage: f32,
965 knockback: Knockback,
966 shockwave_angle: f32,
967 shockwave_vertical_angle: f32,
968 shockwave_speed: f32,
969 shockwave_duration: f32,
970 dodgeable: Dodgeable,
971 move_efficiency: f32,
972 damage_kind: DamageKind,
973 specifier: comp::shockwave::FrontendSpecifier,
974 damage_effect: Option<CombatEffect>,
975 forward_leap_strength: f32,
976 vertical_leap_strength: f32,
977 #[serde(default)]
978 meta: AbilityMeta,
979 },
980 ChargedMelee {
981 energy_cost: f32,
982 energy_drain: f32,
983 buildup_strike: Option<(f32, MeleeConstructor)>,
984 charge_duration: f32,
985 swing_duration: f32,
986 hit_timing: f32,
987 recover_duration: f32,
988 melee_constructor: MeleeConstructor,
989 specifier: Option<charged_melee::FrontendSpecifier>,
990 #[serde(default)]
991 custom_combo: CustomCombo,
992 #[serde(default)]
993 meta: AbilityMeta,
994 #[serde(default)]
995 movement_modifier: MovementModifier,
996 #[serde(default)]
997 ori_modifier: OrientationModifier,
998 },
999 ChargedRanged {
1000 energy_cost: f32,
1001 energy_drain: f32,
1002 idle_drain: f32,
1003 projectile: ProjectileConstructor,
1004 buildup_duration: f32,
1005 charge_duration: f32,
1006 recover_duration: f32,
1007 projectile_body: Body,
1008 projectile_light: Option<LightEmitter>,
1009 initial_projectile_speed: f32,
1010 scaled_projectile_speed: f32,
1011 projectile_spread: Option<ProjectileSpread>,
1012 #[serde(default)]
1013 num_projectiles: Amount,
1014 marker: Option<comp::FrontendMarker>,
1015 move_speed: f32,
1016 #[serde(default)]
1017 meta: AbilityMeta,
1018 },
1019 Throw {
1020 energy_cost: f32,
1021 energy_drain: f32,
1022 buildup_duration: f32,
1023 charge_duration: f32,
1024 throw_duration: f32,
1025 recover_duration: f32,
1026 projectile: ProjectileConstructor,
1027 projectile_light: Option<LightEmitter>,
1028 projectile_dir: throw::ProjectileDir,
1029 initial_projectile_speed: f32,
1030 scaled_projectile_speed: f32,
1031 damage_effect: Option<CombatEffect>,
1032 move_speed: f32,
1033 #[serde(default)]
1034 meta: AbilityMeta,
1035 },
1036 Shockwave {
1037 energy_cost: f32,
1038 buildup_duration: f32,
1039 swing_duration: f32,
1040 recover_duration: f32,
1041 damage: f32,
1042 poise_damage: f32,
1043 knockback: Knockback,
1044 shockwave_angle: f32,
1045 shockwave_vertical_angle: f32,
1046 shockwave_speed: f32,
1047 shockwave_duration: f32,
1048 dodgeable: Dodgeable,
1049 move_efficiency: f32,
1050 damage_kind: DamageKind,
1051 specifier: comp::shockwave::FrontendSpecifier,
1052 ori_rate: f32,
1053 damage_effect: Option<CombatEffect>,
1054 timing: shockwave::Timing,
1055 emit_outcome: bool,
1056 minimum_combo: Option<u32>,
1057 #[serde(default)]
1058 combo_consumption: ComboConsumption,
1059 #[serde(default)]
1060 meta: AbilityMeta,
1061 },
1062 Explosion {
1063 energy_cost: f32,
1064 buildup_duration: f32,
1065 action_duration: f32,
1066 recover_duration: f32,
1067 damage: f32,
1068 poise: f32,
1069 knockback: Knockback,
1070 radius: f32,
1071 min_falloff: f32,
1072 #[serde(default)]
1073 dodgeable: Dodgeable,
1074 #[serde(default)]
1075 destroy_terrain: Option<(f32, ColorPreset)>,
1076 #[serde(default)]
1077 replace_terrain: Option<(f32, TerrainReplacementPreset)>,
1078 #[serde(default)]
1079 eye_height: bool,
1080 #[serde(default)]
1081 reagent: Option<Reagent>,
1082 #[serde(default)]
1083 movement_modifier: MovementModifier,
1084 #[serde(default)]
1085 ori_modifier: OrientationModifier,
1086 #[serde(default)]
1087 meta: AbilityMeta,
1088 },
1089 BasicBeam {
1090 buildup_duration: f32,
1091 recover_duration: f32,
1092 beam_duration: f64,
1093 damage: f32,
1094 tick_rate: f32,
1095 range: f32,
1096 #[serde(default)]
1097 dodgeable: Dodgeable,
1098 #[serde(default = "default_true")]
1099 blockable: bool,
1100 max_angle: f32,
1101 damage_effect: Option<CombatEffect>,
1102 energy_regen: f32,
1103 energy_drain: f32,
1104 ori_rate: f32,
1105 move_efficiency: f32,
1106 specifier: beam::FrontendSpecifier,
1107 #[serde(default)]
1108 meta: AbilityMeta,
1109 },
1110 BasicAura {
1111 buildup_duration: f32,
1112 cast_duration: f32,
1113 recover_duration: f32,
1114 targets: combat::GroupTarget,
1115 auras: Vec<aura::AuraBuffConstructor>,
1116 aura_duration: Option<Secs>,
1117 range: f32,
1118 energy_cost: f32,
1119 scales_with_combo: bool,
1120 specifier: Option<aura::Specifier>,
1121 #[serde(default)]
1122 meta: AbilityMeta,
1123 },
1124 StaticAura {
1125 buildup_duration: f32,
1126 cast_duration: f32,
1127 recover_duration: f32,
1128 energy_cost: f32,
1129 targets: combat::GroupTarget,
1130 auras: Vec<aura::AuraBuffConstructor>,
1131 aura_duration: Option<Secs>,
1132 range: f32,
1133 sprite_info: Option<static_aura::SpriteInfo>,
1134 #[serde(default)]
1135 meta: AbilityMeta,
1136 },
1137 Blink {
1138 buildup_duration: f32,
1139 recover_duration: f32,
1140 max_range: f32,
1141 frontend_specifier: Option<blink::FrontendSpecifier>,
1142 #[serde(default)]
1143 meta: AbilityMeta,
1144 },
1145 BasicSummon {
1146 buildup_duration: f32,
1147 cast_duration: f32,
1148 recover_duration: f32,
1149 summon_info: basic_summon::SummonInfo,
1150 #[serde(default)]
1151 movement_modifier: MovementModifier,
1152 #[serde(default)]
1153 ori_modifier: OrientationModifier,
1154 #[serde(default)]
1155 meta: AbilityMeta,
1156 },
1157 SelfBuff {
1158 buildup_duration: f32,
1159 cast_duration: f32,
1160 recover_duration: f32,
1161 buffs: Vec<self_buff::BuffDesc>,
1162 #[serde(default)]
1163 use_raw_buff_strength: bool,
1164 buff_cat: Option<buff::BuffCategory>,
1165 energy_cost: f32,
1166 #[serde(default = "default_true")]
1167 enforced_limit: bool,
1168 #[serde(default)]
1169 combo_cost: u32,
1170 combo_scaling: Option<ScalingKind>,
1171 #[serde(default)]
1172 meta: AbilityMeta,
1173 specifier: Option<self_buff::FrontendSpecifier>,
1174 },
1175 SpriteSummon {
1176 buildup_duration: f32,
1177 cast_duration: f32,
1178 recover_duration: f32,
1179 sprite: SpriteKind,
1180 del_timeout: Option<(f32, f32)>,
1181 summon_distance: (f32, f32),
1182 sparseness: f64,
1183 angle: f32,
1184 #[serde(default)]
1185 anchor: SpriteSummonAnchor,
1186 #[serde(default)]
1187 move_efficiency: f32,
1188 ori_modifier: f32,
1189 #[serde(default)]
1190 meta: AbilityMeta,
1191 },
1192 Music {
1193 play_duration: f32,
1194 ori_modifier: f32,
1195 #[serde(default)]
1196 meta: AbilityMeta,
1197 },
1198 FinisherMelee {
1199 energy_cost: f32,
1200 buildup_duration: f32,
1201 swing_duration: f32,
1202 recover_duration: f32,
1203 melee_constructor: MeleeConstructor,
1204 minimum_combo: u32,
1205 scaling: Option<finisher_melee::Scaling>,
1206 #[serde(default)]
1207 combo_consumption: ComboConsumption,
1208 #[serde(default)]
1209 meta: AbilityMeta,
1210 },
1211 DiveMelee {
1212 energy_cost: f32,
1213 vertical_speed: f32,
1214 buildup_duration: Option<f32>,
1215 movement_duration: f32,
1216 swing_duration: f32,
1217 recover_duration: f32,
1218 melee_constructor: MeleeConstructor,
1219 max_scaling: f32,
1220 #[serde(default)]
1221 meta: AbilityMeta,
1222 },
1223 RiposteMelee {
1224 energy_cost: f32,
1225 buildup_duration: f32,
1226 swing_duration: f32,
1227 recover_duration: f32,
1228 whiffed_recover_duration: f32,
1229 block_strength: f32,
1230 melee_constructor: MeleeConstructor,
1231 #[serde(default)]
1232 meta: AbilityMeta,
1233 },
1234 RapidMelee {
1235 buildup_duration: f32,
1236 swing_duration: f32,
1237 recover_duration: f32,
1238 energy_cost: f32,
1239 max_strikes: Option<u32>,
1240 melee_constructor: MeleeConstructor,
1241 move_modifier: f32,
1242 ori_modifier: f32,
1243 frontend_specifier: Option<rapid_melee::FrontendSpecifier>,
1244 #[serde(default)]
1245 minimum_combo: u32,
1246 #[serde(default)]
1247 meta: AbilityMeta,
1248 },
1249 Transform {
1250 buildup_duration: f32,
1251 recover_duration: f32,
1252 target: String,
1253 #[serde(default)]
1254 specifier: Option<transform::FrontendSpecifier>,
1255 #[serde(default)]
1258 allow_players: bool,
1259 #[serde(default)]
1260 meta: AbilityMeta,
1261 },
1262 RegrowHead {
1263 buildup_duration: f32,
1264 recover_duration: f32,
1265 energy_cost: f32,
1266 #[serde(default)]
1267 specifier: Option<regrow_head::FrontendSpecifier>,
1268 #[serde(default)]
1269 meta: AbilityMeta,
1270 },
1271 LeapRanged {
1272 energy_cost: f32,
1273 buildup_duration: f32,
1274 buildup_melee_timing: f32,
1275 movement_duration: f32,
1276 movement_ranged_timing: f32,
1277 land_timeout: f32,
1278 recover_duration: f32,
1279 melee: Option<MeleeConstructor>,
1280 melee_required: bool,
1281 projectile: ProjectileConstructor,
1282 projectile_body: Body,
1283 projectile_light: Option<LightEmitter>,
1284 projectile_speed: f32,
1285 horiz_leap_strength: f32,
1286 vert_leap_strength: f32,
1287 #[serde(default)]
1288 meta: AbilityMeta,
1289 },
1290 Simple {
1291 energy_cost: f32,
1292 combo_cost: u32,
1293 buildup_duration: f32,
1294 #[serde(default)]
1295 meta: AbilityMeta,
1296 },
1297}
1298
1299impl Default for CharacterAbility {
1300 fn default() -> Self {
1301 CharacterAbility::BasicMelee {
1302 energy_cost: 0.0,
1303 buildup_duration: 0.25,
1304 swing_duration: 0.25,
1305 hit_timing: 0.5,
1306 recover_duration: 0.5,
1307 melee_constructor: MeleeConstructor {
1308 kind: MeleeConstructorKind::Slash {
1309 damage: 1.0,
1310 knockback: 0.0,
1311 poise: 0.0,
1312 energy_regen: 0.0,
1313 },
1314 scaled: None,
1315 range: 3.5,
1316 angle: 15.0,
1317 multi_target: None,
1318 damage_effect: None,
1319 attack_effect: None,
1320 simultaneous_hits: 1,
1321 custom_combo: CustomCombo {
1322 base: None,
1323 conditional: None,
1324 },
1325 dodgeable: Dodgeable::Roll,
1326 blockable: true,
1327 precision_flank_multipliers: Default::default(),
1328 precision_flank_invert: false,
1329 },
1330 movement_modifier: Default::default(),
1331 ori_modifier: Default::default(),
1332 frontend_specifier: None,
1333 meta: Default::default(),
1334 }
1335 }
1336}
1337
1338impl CharacterAbility {
1339 pub fn requirements_paid(&self, data: &JoinData, update: &mut StateUpdate) -> bool {
1342 let from_meta = {
1343 let AbilityMeta { requirements, .. } = self.ability_meta();
1344 requirements.requirements_met(data.stance, data.inventory)
1345 };
1346 from_meta
1347 && match self {
1348 CharacterAbility::Roll { energy_cost, .. }
1349 | CharacterAbility::StaticAura {
1350 energy_cost,
1351 sprite_info: Some(_),
1352 ..
1353 } => {
1354 data.physics.on_ground.is_some()
1355 && update.energy.try_change_by(-*energy_cost).is_ok()
1356 },
1357 CharacterAbility::DashMelee { energy_cost, .. }
1358 | CharacterAbility::BasicMelee { energy_cost, .. }
1359 | CharacterAbility::BasicRanged { energy_cost, .. }
1360 | CharacterAbility::ChargedRanged { energy_cost, .. }
1361 | CharacterAbility::Throw { energy_cost, .. }
1362 | CharacterAbility::ChargedMelee { energy_cost, .. }
1363 | CharacterAbility::BasicBlock { energy_cost, .. }
1364 | CharacterAbility::RiposteMelee { energy_cost, .. }
1365 | CharacterAbility::ComboMelee2 {
1366 energy_cost_per_strike: energy_cost,
1367 ..
1368 }
1369 | CharacterAbility::StaticAura {
1370 energy_cost,
1371 sprite_info: None,
1372 ..
1373 }
1374 | CharacterAbility::RegrowHead { energy_cost, .. } => {
1375 update.energy.try_change_by(-*energy_cost).is_ok()
1376 },
1377 CharacterAbility::RapidRanged {
1379 initial_energy,
1380 energy_cost,
1381 ..
1382 } => {
1383 update.energy.current() >= *energy_cost + *initial_energy
1384 && update.energy.try_change_by(-*initial_energy).is_ok()
1385 },
1386 CharacterAbility::LeapExplosionShockwave { energy_cost, .. }
1387 | CharacterAbility::LeapMelee { energy_cost, .. }
1388 | CharacterAbility::LeapShockwave { energy_cost, .. }
1389 | CharacterAbility::LeapRanged { energy_cost, .. } => {
1390 update.vel.0.z >= 0.0 && update.energy.try_change_by(-*energy_cost).is_ok()
1391 },
1392 CharacterAbility::BasicAura {
1393 energy_cost,
1394 scales_with_combo,
1395 ..
1396 } => {
1397 ((*scales_with_combo && data.combo.is_some_and(|c| c.counter() > 0))
1398 | !*scales_with_combo)
1399 && update.energy.try_change_by(-*energy_cost).is_ok()
1400 },
1401 CharacterAbility::FinisherMelee {
1402 energy_cost,
1403 minimum_combo,
1404 ..
1405 }
1406 | CharacterAbility::RapidMelee {
1407 energy_cost,
1408 minimum_combo,
1409 ..
1410 }
1411 | CharacterAbility::SelfBuff {
1412 energy_cost,
1413 combo_cost: minimum_combo,
1414 ..
1415 }
1416 | CharacterAbility::Simple {
1417 energy_cost,
1418 combo_cost: minimum_combo,
1419 ..
1420 } => {
1421 data.combo.is_some_and(|c| c.counter() >= *minimum_combo)
1422 && update.energy.try_change_by(-*energy_cost).is_ok()
1423 },
1424 CharacterAbility::Shockwave {
1425 energy_cost,
1426 minimum_combo,
1427 ..
1428 } => {
1429 data.combo
1430 .is_some_and(|c| c.counter() >= minimum_combo.unwrap_or(0))
1431 && update.energy.try_change_by(-*energy_cost).is_ok()
1432 },
1433 CharacterAbility::Explosion { energy_cost, .. } => {
1434 update.energy.try_change_by(-*energy_cost).is_ok()
1435 },
1436 CharacterAbility::DiveMelee {
1437 buildup_duration,
1438 energy_cost,
1439 ..
1440 } => {
1441 (data.physics.on_ground.is_none() || buildup_duration.is_some())
1448 && update.energy.try_change_by(-*energy_cost).is_ok()
1449 },
1450 CharacterAbility::Boost { .. }
1451 | CharacterAbility::GlideBoost { .. }
1452 | CharacterAbility::BasicBeam { .. }
1453 | CharacterAbility::Blink { .. }
1454 | CharacterAbility::Music { .. }
1455 | CharacterAbility::BasicSummon { .. }
1456 | CharacterAbility::SpriteSummon { .. }
1457 | CharacterAbility::Transform { .. } => true,
1458 }
1459 }
1460
1461 pub fn default_roll(current_state: Option<&CharacterState>) -> CharacterAbility {
1462 let remaining_duration = current_state
1463 .and_then(|char_state| {
1464 char_state.timer().zip(
1465 char_state
1466 .durations()
1467 .zip(char_state.stage_section())
1468 .and_then(|(durations, stage_section)| match stage_section {
1469 StageSection::Buildup => durations.buildup,
1470 StageSection::Recover => durations.recover,
1471 _ => None,
1472 }),
1473 )
1474 })
1475 .map_or(0.0, |(timer, duration)| {
1476 duration.as_secs_f32() - timer.as_secs_f32()
1477 })
1478 .max(0.0);
1479
1480 CharacterAbility::Roll {
1481 energy_cost: 10.0 + 100.0 * remaining_duration,
1483 buildup_duration: 0.05,
1484 movement_duration: 0.36,
1485 recover_duration: 0.125,
1486 roll_strength: 3.3075,
1487 attack_immunities: AttackFilters {
1488 melee: true,
1489 projectiles: false,
1490 beams: true,
1491 ground_shockwaves: false,
1492 air_shockwaves: true,
1493 explosions: true,
1494 arcs: true,
1495 pools: true,
1496 },
1497 was_cancel: remaining_duration > 0.0,
1498 meta: Default::default(),
1499 }
1500 }
1501
1502 #[must_use]
1503 pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
1504 use CharacterAbility::*;
1505 match self {
1506 BasicMelee {
1507 ref mut energy_cost,
1508 ref mut buildup_duration,
1509 ref mut swing_duration,
1510 ref mut recover_duration,
1511 ref mut melee_constructor,
1512 movement_modifier: _,
1513 ori_modifier: _,
1514 hit_timing: _,
1515 frontend_specifier: _,
1516 meta: _,
1517 } => {
1518 *buildup_duration /= stats.speed;
1519 *swing_duration /= stats.speed;
1520 *recover_duration /= stats.speed;
1521 *energy_cost /= stats.energy_efficiency;
1522 *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
1523 },
1524 BasicRanged {
1525 ref mut energy_cost,
1526 ref mut buildup_duration,
1527 ref mut recover_duration,
1528 ref mut projectile,
1529 projectile_body: _,
1530 projectile_light: _,
1531 ref mut projectile_speed,
1532 vertical_angle_offset: _,
1533 num_projectiles: _,
1534 projectile_spread: _,
1535 auto_aim: _,
1536 movement_modifier: _,
1537 ori_modifier: _,
1538 marker: _,
1539 meta: _,
1540 } => {
1541 *buildup_duration /= stats.speed;
1542 *recover_duration /= stats.speed;
1543 *projectile = projectile.clone().adjusted_by_stats(stats);
1544 *projectile_speed *= stats.range;
1545 *energy_cost /= stats.energy_efficiency;
1546 },
1547 RapidRanged {
1548 ref mut initial_energy,
1549 ref mut energy_cost,
1550 ref mut buildup_duration,
1551 ref mut shoot_duration,
1552 ref mut recover_duration,
1553 options: _,
1554 ref mut projectile,
1555 projectile_body: _,
1556 projectile_light: _,
1557 ref mut projectile_speed,
1558 specifier: _,
1559 meta: _,
1560 } => {
1561 *buildup_duration /= stats.speed;
1562 *shoot_duration /= stats.speed;
1563 *recover_duration /= stats.speed;
1564 *projectile = projectile.clone().adjusted_by_stats(stats);
1565 *projectile_speed *= stats.range;
1566 *initial_energy /= stats.energy_efficiency;
1567 *energy_cost /= stats.energy_efficiency;
1568 },
1569 Boost {
1570 ref mut movement_duration,
1571 only_up: _,
1572 speed: ref mut boost_speed,
1573 max_exit_velocity: _,
1574 meta: _,
1575 } => {
1576 *movement_duration /= stats.speed;
1577 *boost_speed *= stats.power;
1578 },
1579 DashMelee {
1580 ref mut energy_cost,
1581 ref mut energy_drain,
1582 forward_speed: _,
1583 ref mut buildup_duration,
1584 charge_duration: _,
1585 ref mut swing_duration,
1586 ref mut recover_duration,
1587 ref mut melee_constructor,
1588 ori_modifier: _,
1589 auto_charge: _,
1590 charge_through: _,
1591 frontend_specifier: _,
1592 meta: _,
1593 } => {
1594 *buildup_duration /= stats.speed;
1595 *swing_duration /= stats.speed;
1596 *recover_duration /= stats.speed;
1597 *energy_cost /= stats.energy_efficiency;
1598 *energy_drain /= stats.energy_efficiency;
1599 *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
1600 },
1601 BasicBlock {
1602 ref mut buildup_duration,
1603 ref mut recover_duration,
1604 max_angle: _,
1606 ref mut block_strength,
1607 parry_window: _,
1608 ref mut energy_cost,
1609 energy_regen: _,
1610 can_hold: _,
1611 blocked_attacks: _,
1612 meta: _,
1613 } => {
1614 *buildup_duration /= stats.speed;
1615 *recover_duration /= stats.speed;
1616 *energy_cost /= stats.energy_efficiency;
1617 *block_strength *= stats.power;
1618 },
1619 Roll {
1620 ref mut energy_cost,
1621 ref mut buildup_duration,
1622 ref mut movement_duration,
1623 ref mut recover_duration,
1624 roll_strength: _,
1625 attack_immunities: _,
1626 was_cancel: _,
1627 meta: _,
1628 } => {
1629 *buildup_duration /= stats.speed;
1630 *movement_duration /= stats.speed;
1631 *recover_duration /= stats.speed;
1632 *energy_cost /= stats.energy_efficiency;
1633 },
1634 ComboMelee2 {
1635 ref mut strikes,
1636 ref mut energy_cost_per_strike,
1637 specifier: _,
1638 auto_progress: _,
1639 meta: _,
1640 } => {
1641 *energy_cost_per_strike /= stats.energy_efficiency;
1642 *strikes = strikes
1643 .iter_mut()
1644 .map(|s| s.clone().adjusted_by_stats(stats))
1645 .collect();
1646 },
1647 LeapExplosionShockwave {
1648 ref mut energy_cost,
1649 ref mut buildup_duration,
1650 ref mut movement_duration,
1651 ref mut swing_duration,
1652 ref mut recover_duration,
1653 forward_leap_strength: _,
1654 vertical_leap_strength: _,
1655 ref mut explosion_damage,
1656 ref mut explosion_poise,
1657 ref mut explosion_knockback,
1658 ref mut explosion_radius,
1659 min_falloff: _,
1660 explosion_dodgeable: _,
1661 destroy_terrain: _,
1662 replace_terrain: _,
1663 eye_height: _,
1664 reagent: _,
1665 ref mut shockwave_damage,
1666 ref mut shockwave_poise,
1667 ref mut shockwave_knockback,
1668 shockwave_angle: _,
1669 shockwave_vertical_angle: _,
1670 shockwave_speed: _,
1671 ref mut shockwave_duration,
1672 shockwave_dodgeable: _,
1673 ref mut shockwave_damage_effect,
1674 shockwave_damage_kind: _,
1675 shockwave_specifier: _,
1676 move_efficiency: _,
1677 meta: _,
1678 } => {
1679 *energy_cost /= stats.energy_efficiency;
1680 *buildup_duration /= stats.speed;
1681 *movement_duration /= stats.speed;
1682 *swing_duration /= stats.speed;
1683 *recover_duration /= stats.speed;
1684
1685 *explosion_damage *= stats.power;
1686 *explosion_poise *= stats.effect_power;
1687 explosion_knockback.strength *= stats.effect_power;
1688 *explosion_radius *= stats.range;
1689
1690 *shockwave_damage *= stats.power;
1691 *shockwave_poise *= stats.effect_power;
1692 shockwave_knockback.strength *= stats.effect_power;
1693 *shockwave_duration *= stats.range;
1694 if let Some(CombatEffect::Buff(combat::CombatBuff {
1695 kind: _,
1696 dur_secs: _,
1697 strength,
1698 chance: _,
1699 })) = shockwave_damage_effect
1700 {
1701 *strength *= stats.buff_strength;
1702 }
1703 },
1704 LeapMelee {
1705 ref mut energy_cost,
1706 ref mut buildup_duration,
1707 movement_duration: _,
1708 ref mut swing_duration,
1709 ref mut recover_duration,
1710 ref mut melee_constructor,
1711 forward_leap_strength: _,
1712 vertical_leap_strength: _,
1713 specifier: _,
1714 meta: _,
1715 } => {
1716 *buildup_duration /= stats.speed;
1717 *swing_duration /= stats.speed;
1718 *recover_duration /= stats.speed;
1719 *energy_cost /= stats.energy_efficiency;
1720 *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats)
1721 },
1722 LeapShockwave {
1723 ref mut energy_cost,
1724 ref mut buildup_duration,
1725 movement_duration: _,
1726 ref mut swing_duration,
1727 ref mut recover_duration,
1728 ref mut damage,
1729 ref mut poise_damage,
1730 knockback: _,
1731 shockwave_angle: _,
1732 shockwave_vertical_angle: _,
1733 shockwave_speed: _,
1734 ref mut shockwave_duration,
1735 dodgeable: _,
1736 move_efficiency: _,
1737 damage_kind: _,
1738 specifier: _,
1739 ref mut damage_effect,
1740 forward_leap_strength: _,
1741 vertical_leap_strength: _,
1742 meta: _,
1743 } => {
1744 *buildup_duration /= stats.speed;
1745 *swing_duration /= stats.speed;
1746 *recover_duration /= stats.speed;
1747 *damage *= stats.power;
1748 *poise_damage *= stats.effect_power;
1749 *shockwave_duration *= stats.range;
1750 *energy_cost /= stats.energy_efficiency;
1751 if let Some(CombatEffect::Buff(combat::CombatBuff {
1752 kind: _,
1753 dur_secs: _,
1754 strength,
1755 chance: _,
1756 })) = damage_effect
1757 {
1758 *strength *= stats.buff_strength;
1759 }
1760 },
1761 ChargedMelee {
1762 ref mut energy_cost,
1763 ref mut energy_drain,
1764 ref mut buildup_strike,
1765 ref mut charge_duration,
1766 ref mut swing_duration,
1767 hit_timing: _,
1768 ref mut recover_duration,
1769 ref mut melee_constructor,
1770 specifier: _,
1771 meta: _,
1772 custom_combo: _,
1773 movement_modifier: _,
1774 ori_modifier: _,
1775 } => {
1776 *swing_duration /= stats.speed;
1777 *buildup_strike = buildup_strike
1778 .as_ref()
1779 .cloned()
1780 .map(|(dur, strike)| (dur / stats.speed, strike.adjusted_by_stats(stats)));
1781 *charge_duration /= stats.speed;
1782 *recover_duration /= stats.speed;
1783 *energy_cost /= stats.energy_efficiency;
1784 *energy_drain *= stats.speed / stats.energy_efficiency;
1785 *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
1786 },
1787 ChargedRanged {
1788 ref mut energy_cost,
1789 ref mut energy_drain,
1790 ref mut idle_drain,
1791 ref mut projectile,
1792 ref mut buildup_duration,
1793 ref mut charge_duration,
1794 ref mut recover_duration,
1795 projectile_body: _,
1796 projectile_light: _,
1797 ref mut initial_projectile_speed,
1798 ref mut scaled_projectile_speed,
1799 projectile_spread: _,
1800 num_projectiles: _,
1801 marker: _,
1802 move_speed: _,
1803 meta: _,
1804 } => {
1805 *projectile = projectile.clone().adjusted_by_stats(stats);
1806 *buildup_duration /= stats.speed;
1807 *charge_duration /= stats.speed;
1808 *recover_duration /= stats.speed;
1809 *initial_projectile_speed *= stats.range;
1810 *scaled_projectile_speed *= stats.range;
1811 *energy_cost /= stats.energy_efficiency;
1812 *energy_drain *= stats.speed / stats.energy_efficiency;
1813 *idle_drain /= stats.energy_efficiency;
1814 },
1815 Throw {
1816 ref mut energy_cost,
1817 ref mut energy_drain,
1818 ref mut buildup_duration,
1819 ref mut charge_duration,
1820 ref mut throw_duration,
1821 ref mut recover_duration,
1822 ref mut projectile,
1823 projectile_light: _,
1824 projectile_dir: _,
1825 ref mut initial_projectile_speed,
1826 ref mut scaled_projectile_speed,
1827 damage_effect: _,
1828 move_speed: _,
1829 meta: _,
1830 } => {
1831 *projectile = projectile.clone().adjusted_by_stats(stats);
1832 *energy_cost /= stats.energy_efficiency;
1833 *energy_drain *= stats.speed / stats.energy_efficiency;
1834 *buildup_duration /= stats.speed;
1835 *charge_duration /= stats.speed;
1836 *throw_duration /= stats.speed;
1837 *recover_duration /= stats.speed;
1838 *initial_projectile_speed *= stats.range;
1839 *scaled_projectile_speed *= stats.range;
1840 },
1841 Shockwave {
1842 ref mut energy_cost,
1843 ref mut buildup_duration,
1844 ref mut swing_duration,
1845 ref mut recover_duration,
1846 ref mut damage,
1847 ref mut poise_damage,
1848 knockback: _,
1849 shockwave_angle: _,
1850 shockwave_vertical_angle: _,
1851 shockwave_speed: _,
1852 ref mut shockwave_duration,
1853 dodgeable: _,
1854 move_efficiency: _,
1855 damage_kind: _,
1856 specifier: _,
1857 ori_rate: _,
1858 ref mut damage_effect,
1859 timing: _,
1860 emit_outcome: _,
1861 minimum_combo: _,
1862 combo_consumption: _,
1863 meta: _,
1864 } => {
1865 *buildup_duration /= stats.speed;
1866 *swing_duration /= stats.speed;
1867 *recover_duration /= stats.speed;
1868 *damage *= stats.power;
1869 *poise_damage *= stats.effect_power;
1870 *shockwave_duration *= stats.range;
1871 *energy_cost /= stats.energy_efficiency;
1872 *damage_effect = damage_effect
1873 .as_ref()
1874 .cloned()
1875 .map(|de| de.adjusted_by_stats(stats));
1876 },
1877 Explosion {
1878 ref mut energy_cost,
1879 ref mut buildup_duration,
1880 ref mut action_duration,
1881 ref mut recover_duration,
1882 ref mut damage,
1883 poise: ref mut poise_damage,
1884 ref mut knockback,
1885 ref mut radius,
1886 min_falloff: _,
1887 dodgeable: _,
1888 destroy_terrain: _,
1889 replace_terrain: _,
1890 eye_height: _,
1891 reagent: _,
1892 movement_modifier: _,
1893 ori_modifier: _,
1894 meta: _,
1895 } => {
1896 *energy_cost /= stats.energy_efficiency;
1897 *buildup_duration /= stats.speed;
1898 *action_duration /= stats.speed;
1899 *recover_duration /= stats.speed;
1900 *damage *= stats.power;
1901 *poise_damage *= stats.effect_power;
1902 knockback.strength *= stats.effect_power;
1903 *radius *= stats.range;
1904 },
1905 BasicBeam {
1906 ref mut buildup_duration,
1907 ref mut recover_duration,
1908 ref mut beam_duration,
1909 ref mut damage,
1910 ref mut tick_rate,
1911 ref mut range,
1912 dodgeable: _,
1913 blockable: _,
1914 max_angle: _,
1915 ref mut damage_effect,
1916 energy_regen: _,
1917 ref mut energy_drain,
1918 move_efficiency: _,
1919 ori_rate: _,
1920 specifier: _,
1921 meta: _,
1922 } => {
1923 *buildup_duration /= stats.speed;
1924 *recover_duration /= stats.speed;
1925 *damage *= stats.power;
1926 *tick_rate *= stats.speed;
1927 *range *= stats.range;
1928 *beam_duration *= stats.range as f64;
1930 *energy_drain /= stats.energy_efficiency;
1931 *damage_effect = damage_effect
1932 .as_ref()
1933 .cloned()
1934 .map(|de| de.adjusted_by_stats(stats));
1935 },
1936 BasicAura {
1937 ref mut buildup_duration,
1938 ref mut cast_duration,
1939 ref mut recover_duration,
1940 targets: _,
1941 ref mut auras,
1942 aura_duration: _,
1943 ref mut range,
1944 ref mut energy_cost,
1945 scales_with_combo: _,
1946 specifier: _,
1947 meta: _,
1948 } => {
1949 *buildup_duration /= stats.speed;
1950 *cast_duration /= stats.speed;
1951 *recover_duration /= stats.speed;
1952 auras.iter_mut().for_each(
1953 |aura::AuraBuffConstructor {
1954 kind: _,
1955 strength,
1956 duration: _,
1957 category: _,
1958 }| {
1959 *strength *= stats.diminished_buff_strength();
1960 },
1961 );
1962 *range *= stats.range;
1963 *energy_cost /= stats.energy_efficiency;
1964 },
1965 StaticAura {
1966 ref mut buildup_duration,
1967 ref mut cast_duration,
1968 ref mut recover_duration,
1969 targets: _,
1970 ref mut auras,
1971 aura_duration: _,
1972 ref mut range,
1973 ref mut energy_cost,
1974 ref mut sprite_info,
1975 meta: _,
1976 } => {
1977 *buildup_duration /= stats.speed;
1978 *cast_duration /= stats.speed;
1979 *recover_duration /= stats.speed;
1980 auras.iter_mut().for_each(
1981 |aura::AuraBuffConstructor {
1982 kind: _,
1983 strength,
1984 duration: _,
1985 category: _,
1986 }| {
1987 *strength *= stats.diminished_buff_strength();
1988 },
1989 );
1990 *range *= stats.range;
1991 *energy_cost /= stats.energy_efficiency;
1992 *sprite_info = sprite_info.map(|mut si| {
1993 si.summon_distance.0 *= stats.range;
1994 si.summon_distance.1 *= stats.range;
1995 si
1996 });
1997 },
1998 Blink {
1999 ref mut buildup_duration,
2000 ref mut recover_duration,
2001 ref mut max_range,
2002 frontend_specifier: _,
2003 meta: _,
2004 } => {
2005 *buildup_duration /= stats.speed;
2006 *recover_duration /= stats.speed;
2007 *max_range *= stats.range;
2008 },
2009 BasicSummon {
2010 ref mut buildup_duration,
2011 ref mut cast_duration,
2012 ref mut recover_duration,
2013 ref mut summon_info,
2014 movement_modifier: _,
2015 ori_modifier: _,
2016 meta: _,
2017 } => {
2018 *buildup_duration /= stats.speed;
2020 *cast_duration /= stats.speed;
2021 *recover_duration /= stats.speed;
2022 summon_info.scale_range(stats.range);
2023 },
2024 SelfBuff {
2025 ref mut buildup_duration,
2026 ref mut cast_duration,
2027 ref mut recover_duration,
2028 ref mut buffs,
2029 use_raw_buff_strength,
2030 buff_cat: _,
2031 ref mut energy_cost,
2032 enforced_limit: _,
2033 combo_cost: _,
2034 combo_scaling: _,
2035 meta: _,
2036 specifier: _,
2037 } => {
2038 for buff in buffs.iter_mut() {
2039 buff.data.strength *= if use_raw_buff_strength {
2040 stats.buff_strength
2041 } else {
2042 stats.diminished_buff_strength()
2043 };
2044 }
2045 *buildup_duration /= stats.speed;
2046 *cast_duration /= stats.speed;
2047 *recover_duration /= stats.speed;
2048 *energy_cost /= stats.energy_efficiency;
2049 },
2050 SpriteSummon {
2051 ref mut buildup_duration,
2052 ref mut cast_duration,
2053 ref mut recover_duration,
2054 sprite: _,
2055 del_timeout: _,
2056 summon_distance: (ref mut inner_dist, ref mut outer_dist),
2057 sparseness: _,
2058 angle: _,
2059 anchor: _,
2060 move_efficiency: _,
2061 ori_modifier: _,
2062 meta: _,
2063 } => {
2064 *buildup_duration /= stats.speed;
2066 *cast_duration /= stats.speed;
2067 *recover_duration /= stats.speed;
2068 *inner_dist *= stats.range;
2069 *outer_dist *= stats.range;
2070 },
2071 Music {
2072 ref mut play_duration,
2073 ori_modifier: _,
2074 meta: _,
2075 } => {
2076 *play_duration /= stats.speed;
2077 },
2078 FinisherMelee {
2079 ref mut energy_cost,
2080 ref mut buildup_duration,
2081 ref mut swing_duration,
2082 ref mut recover_duration,
2083 ref mut melee_constructor,
2084 minimum_combo: _,
2085 scaling: _,
2086 combo_consumption: _,
2087 meta: _,
2088 } => {
2089 *buildup_duration /= stats.speed;
2090 *swing_duration /= stats.speed;
2091 *recover_duration /= stats.speed;
2092 *energy_cost /= stats.energy_efficiency;
2093 *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
2094 },
2095 DiveMelee {
2096 ref mut energy_cost,
2097 vertical_speed: _,
2098 movement_duration: _,
2099 ref mut buildup_duration,
2100 ref mut swing_duration,
2101 ref mut recover_duration,
2102 ref mut melee_constructor,
2103 max_scaling: _,
2104 meta: _,
2105 } => {
2106 *buildup_duration = buildup_duration.map(|b| b / stats.speed);
2107 *swing_duration /= stats.speed;
2108 *recover_duration /= stats.speed;
2109 *energy_cost /= stats.energy_efficiency;
2110 *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
2111 },
2112 RiposteMelee {
2113 ref mut energy_cost,
2114 ref mut buildup_duration,
2115 ref mut swing_duration,
2116 ref mut recover_duration,
2117 ref mut whiffed_recover_duration,
2118 ref mut block_strength,
2119 ref mut melee_constructor,
2120 meta: _,
2121 } => {
2122 *buildup_duration /= stats.speed;
2123 *swing_duration /= stats.speed;
2124 *recover_duration /= stats.speed;
2125 *whiffed_recover_duration /= stats.speed;
2126 *energy_cost /= stats.energy_efficiency;
2127 *block_strength *= stats.power;
2128 *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
2129 },
2130 RapidMelee {
2131 ref mut buildup_duration,
2132 ref mut swing_duration,
2133 ref mut recover_duration,
2134 ref mut energy_cost,
2135 ref mut melee_constructor,
2136 max_strikes: _,
2137 move_modifier: _,
2138 ori_modifier: _,
2139 minimum_combo: _,
2140 frontend_specifier: _,
2141 meta: _,
2142 } => {
2143 *buildup_duration /= stats.speed;
2144 *swing_duration /= stats.speed;
2145 *recover_duration /= stats.speed;
2146 *energy_cost /= stats.energy_efficiency;
2147 *melee_constructor = melee_constructor.clone().adjusted_by_stats(stats);
2148 },
2149 Transform {
2150 ref mut buildup_duration,
2151 ref mut recover_duration,
2152 target: _,
2153 specifier: _,
2154 allow_players: _,
2155 meta: _,
2156 } => {
2157 *buildup_duration /= stats.speed;
2158 *recover_duration /= stats.speed;
2159 },
2160 GlideBoost { .. } => {},
2161 RegrowHead {
2162 ref mut buildup_duration,
2163 ref mut recover_duration,
2164 ref mut energy_cost,
2165 specifier: _,
2166 meta: _,
2167 } => {
2168 *buildup_duration /= stats.speed;
2169 *recover_duration /= stats.speed;
2170 *energy_cost /= stats.energy_efficiency;
2171 },
2172 LeapRanged {
2173 ref mut energy_cost,
2174 ref mut buildup_duration,
2175 buildup_melee_timing: _,
2176 movement_duration: _,
2177 movement_ranged_timing: _,
2178 land_timeout: _,
2179 ref mut recover_duration,
2180 ref mut melee,
2181 melee_required: _,
2182 ref mut projectile,
2183 projectile_body: _,
2184 projectile_light: _,
2185 ref mut projectile_speed,
2186 horiz_leap_strength: _,
2187 vert_leap_strength: _,
2188 meta: _,
2189 } => {
2190 *energy_cost /= stats.energy_efficiency;
2191 *buildup_duration /= stats.speed;
2192 *recover_duration /= stats.speed;
2193 *melee = melee.as_ref().cloned().map(|m| m.adjusted_by_stats(stats));
2194 *projectile = projectile.clone().adjusted_by_stats(stats);
2195 *projectile_speed *= stats.range;
2196 },
2197 Simple {
2198 ref mut energy_cost,
2199 combo_cost: _,
2200 ref mut buildup_duration,
2201 meta: _,
2202 } => {
2203 *energy_cost /= stats.energy_efficiency;
2204 *buildup_duration /= stats.speed;
2205 },
2206 }
2207 self
2208 }
2209
2210 pub fn energy_cost(&self) -> f32 {
2211 use CharacterAbility::*;
2212 match self {
2213 BasicMelee { energy_cost, .. }
2214 | BasicRanged { energy_cost, .. }
2215 | RapidRanged { energy_cost, .. }
2216 | DashMelee { energy_cost, .. }
2217 | Roll { energy_cost, .. }
2218 | LeapExplosionShockwave { energy_cost, .. }
2219 | LeapMelee { energy_cost, .. }
2220 | LeapShockwave { energy_cost, .. }
2221 | ChargedMelee { energy_cost, .. }
2222 | ChargedRanged { energy_cost, .. }
2223 | Throw { energy_cost, .. }
2224 | Shockwave { energy_cost, .. }
2225 | Explosion { energy_cost, .. }
2226 | BasicAura { energy_cost, .. }
2227 | BasicBlock { energy_cost, .. }
2228 | SelfBuff { energy_cost, .. }
2229 | FinisherMelee { energy_cost, .. }
2230 | ComboMelee2 {
2231 energy_cost_per_strike: energy_cost,
2232 ..
2233 }
2234 | DiveMelee { energy_cost, .. }
2235 | RiposteMelee { energy_cost, .. }
2236 | RapidMelee { energy_cost, .. }
2237 | StaticAura { energy_cost, .. }
2238 | RegrowHead { energy_cost, .. }
2239 | LeapRanged { energy_cost, .. }
2240 | Simple { energy_cost, .. } => *energy_cost,
2241 BasicBeam { energy_drain, .. } => {
2242 if *energy_drain > f32::EPSILON {
2243 1.0
2244 } else {
2245 0.0
2246 }
2247 },
2248 Boost { .. }
2249 | GlideBoost { .. }
2250 | Blink { .. }
2251 | Music { .. }
2252 | BasicSummon { .. }
2253 | SpriteSummon { .. }
2254 | Transform { .. } => 0.0,
2255 }
2256 }
2257
2258 #[expect(clippy::bool_to_int_with_if)]
2259 pub fn combo_cost(&self) -> u32 {
2260 use CharacterAbility::*;
2261 match self {
2262 BasicAura {
2263 scales_with_combo, ..
2264 } => {
2265 if *scales_with_combo {
2266 1
2267 } else {
2268 0
2269 }
2270 },
2271 FinisherMelee {
2272 minimum_combo: combo,
2273 ..
2274 }
2275 | RapidMelee {
2276 minimum_combo: combo,
2277 ..
2278 }
2279 | SelfBuff {
2280 combo_cost: combo, ..
2281 }
2282 | Simple {
2283 combo_cost: combo, ..
2284 } => *combo,
2285 Shockwave {
2286 minimum_combo: combo,
2287 ..
2288 } => combo.unwrap_or(0),
2289 BasicMelee { .. }
2290 | BasicRanged { .. }
2291 | RapidRanged { .. }
2292 | DashMelee { .. }
2293 | Roll { .. }
2294 | LeapExplosionShockwave { .. }
2295 | LeapMelee { .. }
2296 | LeapShockwave { .. }
2297 | Explosion { .. }
2298 | ChargedMelee { .. }
2299 | ChargedRanged { .. }
2300 | Throw { .. }
2301 | BasicBlock { .. }
2302 | ComboMelee2 { .. }
2303 | DiveMelee { .. }
2304 | RiposteMelee { .. }
2305 | BasicBeam { .. }
2306 | Boost { .. }
2307 | GlideBoost { .. }
2308 | Blink { .. }
2309 | Music { .. }
2310 | BasicSummon { .. }
2311 | SpriteSummon { .. }
2312 | Transform { .. }
2313 | StaticAura { .. }
2314 | RegrowHead { .. }
2315 | LeapRanged { .. } => 0,
2316 }
2317 }
2318
2319 pub fn ability_meta(&self) -> AbilityMeta {
2321 use CharacterAbility::*;
2322 match self {
2323 BasicMelee { meta, .. }
2324 | BasicRanged { meta, .. }
2325 | RapidRanged { meta, .. }
2326 | DashMelee { meta, .. }
2327 | Roll { meta, .. }
2328 | LeapExplosionShockwave { meta, .. }
2329 | LeapMelee { meta, .. }
2330 | LeapShockwave { meta, .. }
2331 | ChargedMelee { meta, .. }
2332 | ChargedRanged { meta, .. }
2333 | Throw { meta, .. }
2334 | Shockwave { meta, .. }
2335 | Explosion { meta, .. }
2336 | BasicAura { meta, .. }
2337 | BasicBlock { meta, .. }
2338 | SelfBuff { meta, .. }
2339 | BasicBeam { meta, .. }
2340 | Boost { meta, .. }
2341 | GlideBoost { meta, .. }
2342 | ComboMelee2 { meta, .. }
2343 | Blink { meta, .. }
2344 | BasicSummon { meta, .. }
2345 | SpriteSummon { meta, .. }
2346 | FinisherMelee { meta, .. }
2347 | Music { meta, .. }
2348 | DiveMelee { meta, .. }
2349 | RiposteMelee { meta, .. }
2350 | RapidMelee { meta, .. }
2351 | Transform { meta, .. }
2352 | StaticAura { meta, .. }
2353 | RegrowHead { meta, .. }
2354 | LeapRanged { meta, .. }
2355 | Simple { meta, .. } => *meta,
2356 }
2357 }
2358
2359 #[must_use = "method returns new ability and doesn't mutate the original value"]
2360 pub fn adjusted_by_skills(mut self, skillset: &SkillSet, tool: Option<ToolKind>) -> Self {
2361 match tool {
2362 Some(ToolKind::Sceptre) => self.adjusted_by_sceptre_skills(skillset),
2363 Some(ToolKind::Pick) => self.adjusted_by_mining_skills(skillset),
2364 None | Some(_) => {},
2365 }
2366 self
2367 }
2368
2369 fn adjusted_by_mining_skills(&mut self, skillset: &SkillSet) {
2370 use skills::MiningSkill::Speed;
2371
2372 if let CharacterAbility::BasicMelee {
2373 buildup_duration,
2374 swing_duration,
2375 recover_duration,
2376 ..
2377 } = self
2378 && let Ok(level) = skillset.skill_level(Skill::Pick(Speed))
2379 {
2380 let modifiers = SKILL_MODIFIERS.mining_tree;
2381
2382 let speed = modifiers.speed.powi(level.into());
2383 *buildup_duration /= speed;
2384 *swing_duration /= speed;
2385 *recover_duration /= speed;
2386 }
2387 }
2388
2389 fn adjusted_by_sceptre_skills(&mut self, skillset: &SkillSet) {
2390 use skills::{SceptreSkill::*, Skill::Sceptre};
2391
2392 match self {
2393 CharacterAbility::BasicBeam {
2394 damage,
2395 range,
2396 beam_duration,
2397 damage_effect,
2398 energy_regen,
2399 ..
2400 } => {
2401 let modifiers = SKILL_MODIFIERS.sceptre_tree.beam;
2402 if let Ok(level) = skillset.skill_level(Sceptre(LDamage)) {
2403 *damage *= modifiers.damage.powi(level.into());
2404 }
2405 if let Ok(level) = skillset.skill_level(Sceptre(LRange)) {
2406 let range_mod = modifiers.range.powi(level.into());
2407 *range *= range_mod;
2408 *beam_duration *= range_mod as f64;
2410 }
2411 if let Ok(level) = skillset.skill_level(Sceptre(LRegen)) {
2412 *energy_regen *= modifiers.energy_regen.powi(level.into());
2413 }
2414 if let (Ok(level), Some(CombatEffect::Lifesteal(lifesteal))) =
2415 (skillset.skill_level(Sceptre(LLifesteal)), damage_effect)
2416 {
2417 *lifesteal *= modifiers.lifesteal.powi(level.into());
2418 }
2419 },
2420 CharacterAbility::BasicAura {
2421 auras,
2422 range,
2423 energy_cost,
2424 specifier: Some(aura::Specifier::HealingAura),
2425 ..
2426 } => {
2427 let modifiers = SKILL_MODIFIERS.sceptre_tree.healing_aura;
2428 if let Ok(level) = skillset.skill_level(Sceptre(HHeal)) {
2429 auras.iter_mut().for_each(|ref mut aura| {
2430 aura.strength *= modifiers.strength.powi(level.into());
2431 });
2432 }
2433 if let Ok(level) = skillset.skill_level(Sceptre(HDuration)) {
2434 auras.iter_mut().for_each(|ref mut aura| {
2435 if let Some(ref mut duration) = aura.duration {
2436 *duration *= modifiers.duration.powi(level.into()) as f64;
2437 }
2438 });
2439 }
2440 if let Ok(level) = skillset.skill_level(Sceptre(HRange)) {
2441 *range *= modifiers.range.powi(level.into());
2442 }
2443 if let Ok(level) = skillset.skill_level(Sceptre(HCost)) {
2444 *energy_cost *= modifiers.energy_cost.powi(level.into());
2445 }
2446 },
2447 CharacterAbility::BasicAura {
2448 auras,
2449 range,
2450 energy_cost,
2451 specifier: Some(aura::Specifier::WardingAura),
2452 ..
2453 } => {
2454 let modifiers = SKILL_MODIFIERS.sceptre_tree.warding_aura;
2455 if let Ok(level) = skillset.skill_level(Sceptre(AStrength)) {
2456 auras.iter_mut().for_each(|ref mut aura| {
2457 aura.strength *= modifiers.strength.powi(level.into());
2458 });
2459 }
2460 if let Ok(level) = skillset.skill_level(Sceptre(ADuration)) {
2461 auras.iter_mut().for_each(|ref mut aura| {
2462 if let Some(ref mut duration) = aura.duration {
2463 *duration *= modifiers.duration.powi(level.into()) as f64;
2464 }
2465 });
2466 }
2467 if let Ok(level) = skillset.skill_level(Sceptre(ARange)) {
2468 *range *= modifiers.range.powi(level.into());
2469 }
2470 if let Ok(level) = skillset.skill_level(Sceptre(ACost)) {
2471 *energy_cost *= modifiers.energy_cost.powi(level.into());
2472 }
2473 },
2474 _ => {},
2475 }
2476 }
2477}
2478
2479fn default_true() -> bool { true }
2481
2482#[derive(Debug)]
2483pub enum CharacterStateCreationError {
2484 MissingHandInfo,
2485 MissingItem,
2486 InvalidItemKind,
2487}
2488
2489impl TryFrom<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
2490 type Error = CharacterStateCreationError;
2491
2492 fn try_from(
2493 (ability, ability_info, data): (&CharacterAbility, AbilityInfo, &JoinData),
2494 ) -> Result<Self, Self::Error> {
2495 Ok(match ability {
2496 CharacterAbility::BasicMelee {
2497 buildup_duration,
2498 swing_duration,
2499 hit_timing,
2500 recover_duration,
2501 melee_constructor,
2502 movement_modifier,
2503 ori_modifier,
2504 frontend_specifier,
2505 energy_cost: _,
2506 meta: _,
2507 } => CharacterState::BasicMelee(basic_melee::Data {
2508 static_data: basic_melee::StaticData {
2509 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2510 swing_duration: Duration::from_secs_f32(*swing_duration),
2511 hit_timing: hit_timing.clamp(0.0, 1.0),
2512 recover_duration: Duration::from_secs_f32(*recover_duration),
2513 melee_constructor: melee_constructor.clone(),
2514 movement_modifier: *movement_modifier,
2515 ori_modifier: *ori_modifier,
2516 frontend_specifier: *frontend_specifier,
2517 ability_info,
2518 },
2519 timer: Duration::default(),
2520 stage_section: StageSection::Buildup,
2521 exhausted: false,
2522 movement_modifier: movement_modifier.buildup,
2523 ori_modifier: ori_modifier.buildup,
2524 }),
2525 CharacterAbility::BasicRanged {
2526 buildup_duration,
2527 recover_duration,
2528 projectile,
2529 projectile_body,
2530 projectile_light,
2531 projectile_speed,
2532 vertical_angle_offset,
2533 energy_cost: _,
2534 num_projectiles,
2535 projectile_spread,
2536 auto_aim,
2537 movement_modifier,
2538 ori_modifier,
2539 marker,
2540 meta: _,
2541 } => CharacterState::BasicRanged(basic_ranged::Data {
2542 static_data: basic_ranged::StaticData {
2543 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2544 recover_duration: Duration::from_secs_f32(*recover_duration),
2545 projectile: projectile.clone(),
2546 projectile_body: *projectile_body,
2547 projectile_light: *projectile_light,
2548 projectile_speed: *projectile_speed,
2549 vertical_angle_offset: *vertical_angle_offset,
2550 num_projectiles: *num_projectiles,
2551 projectile_spread: *projectile_spread,
2552 auto_aim: *auto_aim,
2553 ability_info,
2554 movement_modifier: *movement_modifier,
2555 ori_modifier: *ori_modifier,
2556 marker: *marker,
2557 },
2558 timer: Duration::default(),
2559 stage_section: StageSection::Buildup,
2560 exhausted: false,
2561 movement_modifier: movement_modifier.buildup,
2562 ori_modifier: ori_modifier.buildup,
2563 }),
2564 CharacterAbility::Boost {
2565 movement_duration,
2566 only_up,
2567 speed,
2568 max_exit_velocity,
2569 meta: _,
2570 } => CharacterState::Boost(boost::Data {
2571 static_data: boost::StaticData {
2572 movement_duration: Duration::from_secs_f32(*movement_duration),
2573 only_up: *only_up,
2574 speed: *speed,
2575 max_exit_velocity: *max_exit_velocity,
2576 ability_info,
2577 },
2578 timer: Duration::default(),
2579 }),
2580 CharacterAbility::GlideBoost { booster, meta: _ } => {
2581 let scale = data.body.dimensions().z.sqrt();
2582 let mut glide_data = glide::Data::new(scale * 4.5, scale, *data.ori);
2583 glide_data.booster = Some(*booster);
2584
2585 CharacterState::Glide(glide_data)
2586 },
2587 CharacterAbility::DashMelee {
2588 energy_cost: _,
2589 energy_drain,
2590 forward_speed,
2591 buildup_duration,
2592 charge_duration,
2593 swing_duration,
2594 recover_duration,
2595 melee_constructor,
2596 ori_modifier,
2597 auto_charge,
2598 charge_through,
2599 frontend_specifier,
2600 meta: _,
2601 } => CharacterState::DashMelee(dash_melee::Data {
2602 static_data: dash_melee::StaticData {
2603 energy_drain: *energy_drain,
2604 forward_speed: *forward_speed,
2605 auto_charge: *auto_charge,
2606 charge_through: *charge_through,
2607 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2608 charge_duration: Duration::from_secs_f32(*charge_duration),
2609 swing_duration: Duration::from_secs_f32(*swing_duration),
2610 recover_duration: Duration::from_secs_f32(*recover_duration),
2611 melee_constructor: melee_constructor.clone(),
2612 ori_modifier: *ori_modifier,
2613 frontend_specifier: *frontend_specifier,
2614 ability_info,
2615 },
2616 auto_charge: false,
2617 timer: Duration::default(),
2618 stage_section: StageSection::Buildup,
2619 }),
2620 CharacterAbility::BasicBlock {
2621 buildup_duration,
2622 recover_duration,
2623 max_angle,
2624 block_strength,
2625 parry_window,
2626 energy_cost,
2627 energy_regen,
2628 can_hold,
2629 blocked_attacks,
2630 meta: _,
2631 } => CharacterState::BasicBlock(basic_block::Data {
2632 static_data: basic_block::StaticData {
2633 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2634 recover_duration: Duration::from_secs_f32(*recover_duration),
2635 max_angle: *max_angle,
2636 block_strength: *block_strength,
2637 parry_window: *parry_window,
2638 energy_cost: *energy_cost,
2639 energy_regen: *energy_regen,
2640 can_hold: *can_hold,
2641 blocked_attacks: *blocked_attacks,
2642 ability_info,
2643 },
2644 timer: Duration::default(),
2645 stage_section: StageSection::Buildup,
2646 is_parry: false,
2647 }),
2648 CharacterAbility::Roll {
2649 energy_cost: _,
2650 buildup_duration,
2651 movement_duration,
2652 recover_duration,
2653 roll_strength,
2654 attack_immunities,
2655 was_cancel,
2656 meta: _,
2657 } => CharacterState::Roll(roll::Data {
2658 static_data: roll::StaticData {
2659 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2660 movement_duration: Duration::from_secs_f32(*movement_duration),
2661 recover_duration: Duration::from_secs_f32(*recover_duration),
2662 roll_strength: *roll_strength,
2663 attack_immunities: *attack_immunities,
2664 was_cancel: *was_cancel,
2665 ability_info,
2666 },
2667 timer: Duration::default(),
2668 stage_section: StageSection::Buildup,
2669 was_wielded: false, prev_aimed_dir: None,
2671 is_sneaking: false,
2672 }),
2673 CharacterAbility::ComboMelee2 {
2674 strikes,
2675 energy_cost_per_strike,
2676 specifier,
2677 auto_progress,
2678 meta: _,
2679 } => CharacterState::ComboMelee2(combo_melee2::Data {
2680 static_data: combo_melee2::StaticData {
2681 strikes: strikes.iter().cloned().map(|s| s.to_duration()).collect(),
2682 energy_cost_per_strike: *energy_cost_per_strike,
2683 specifier: *specifier,
2684 auto_progress: *auto_progress,
2685 ability_info,
2686 },
2687 exhausted: false,
2688 start_next_strike: false,
2689 timer: Duration::default(),
2690 stage_section: StageSection::Buildup,
2691 completed_strikes: 0,
2692 movement_modifier: strikes.first().and_then(|s| s.movement_modifier.buildup),
2693 ori_modifier: strikes.first().and_then(|s| s.ori_modifier.buildup),
2694 }),
2695 CharacterAbility::LeapExplosionShockwave {
2696 energy_cost: _,
2697 buildup_duration,
2698 movement_duration,
2699 swing_duration,
2700 recover_duration,
2701 forward_leap_strength,
2702 vertical_leap_strength,
2703 explosion_damage,
2704 explosion_poise,
2705 explosion_knockback,
2706 explosion_radius,
2707 min_falloff,
2708 explosion_dodgeable,
2709 destroy_terrain,
2710 replace_terrain,
2711 eye_height,
2712 reagent,
2713 shockwave_damage,
2714 shockwave_poise,
2715 shockwave_knockback,
2716 shockwave_angle,
2717 shockwave_vertical_angle,
2718 shockwave_speed,
2719 shockwave_duration,
2720 shockwave_dodgeable,
2721 shockwave_damage_effect,
2722 shockwave_damage_kind,
2723 shockwave_specifier,
2724 move_efficiency,
2725 meta: _,
2726 } => CharacterState::LeapExplosionShockwave(leap_explosion_shockwave::Data {
2727 static_data: leap_explosion_shockwave::StaticData {
2728 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2729 movement_duration: Duration::from_secs_f32(*movement_duration),
2730 swing_duration: Duration::from_secs_f32(*swing_duration),
2731 recover_duration: Duration::from_secs_f32(*recover_duration),
2732 forward_leap_strength: *forward_leap_strength,
2733 vertical_leap_strength: *vertical_leap_strength,
2734 explosion_damage: *explosion_damage,
2735 explosion_poise: *explosion_poise,
2736 explosion_knockback: *explosion_knockback,
2737 explosion_radius: *explosion_radius,
2738 min_falloff: *min_falloff,
2739 explosion_dodgeable: *explosion_dodgeable,
2740 destroy_terrain: *destroy_terrain,
2741 replace_terrain: *replace_terrain,
2742 eye_height: *eye_height,
2743 reagent: *reagent,
2744 shockwave_damage: *shockwave_damage,
2745 shockwave_poise: *shockwave_poise,
2746 shockwave_knockback: *shockwave_knockback,
2747 shockwave_angle: *shockwave_angle,
2748 shockwave_vertical_angle: *shockwave_vertical_angle,
2749 shockwave_speed: *shockwave_speed,
2750 shockwave_duration: Duration::from_secs_f32(*shockwave_duration),
2751 shockwave_dodgeable: *shockwave_dodgeable,
2752 shockwave_damage_effect: shockwave_damage_effect.clone(),
2753 shockwave_damage_kind: *shockwave_damage_kind,
2754 shockwave_specifier: *shockwave_specifier,
2755 move_efficiency: *move_efficiency,
2756 ability_info,
2757 },
2758 timer: Duration::default(),
2759 stage_section: StageSection::Buildup,
2760 exhausted: false,
2761 }),
2762 CharacterAbility::LeapMelee {
2763 energy_cost: _,
2764 buildup_duration,
2765 movement_duration,
2766 swing_duration,
2767 recover_duration,
2768 melee_constructor,
2769 forward_leap_strength,
2770 vertical_leap_strength,
2771 specifier,
2772 meta: _,
2773 } => CharacterState::LeapMelee(leap_melee::Data {
2774 static_data: leap_melee::StaticData {
2775 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2776 movement_duration: Duration::from_secs_f32(*movement_duration),
2777 swing_duration: Duration::from_secs_f32(*swing_duration),
2778 recover_duration: Duration::from_secs_f32(*recover_duration),
2779 melee_constructor: melee_constructor.clone(),
2780 forward_leap_strength: *forward_leap_strength,
2781 vertical_leap_strength: *vertical_leap_strength,
2782 ability_info,
2783 specifier: *specifier,
2784 },
2785 timer: Duration::default(),
2786 stage_section: StageSection::Buildup,
2787 exhausted: false,
2788 }),
2789 CharacterAbility::LeapShockwave {
2790 energy_cost: _,
2791 buildup_duration,
2792 movement_duration,
2793 swing_duration,
2794 recover_duration,
2795 damage,
2796 poise_damage,
2797 knockback,
2798 shockwave_angle,
2799 shockwave_vertical_angle,
2800 shockwave_speed,
2801 shockwave_duration,
2802 dodgeable,
2803 move_efficiency,
2804 damage_kind,
2805 specifier,
2806 damage_effect,
2807 forward_leap_strength,
2808 vertical_leap_strength,
2809 meta: _,
2810 } => CharacterState::LeapShockwave(leap_shockwave::Data {
2811 static_data: leap_shockwave::StaticData {
2812 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2813 movement_duration: Duration::from_secs_f32(*movement_duration),
2814 swing_duration: Duration::from_secs_f32(*swing_duration),
2815 recover_duration: Duration::from_secs_f32(*recover_duration),
2816 damage: *damage,
2817 poise_damage: *poise_damage,
2818 knockback: *knockback,
2819 shockwave_angle: *shockwave_angle,
2820 shockwave_vertical_angle: *shockwave_vertical_angle,
2821 shockwave_speed: *shockwave_speed,
2822 shockwave_duration: Duration::from_secs_f32(*shockwave_duration),
2823 dodgeable: *dodgeable,
2824 move_efficiency: *move_efficiency,
2825 damage_kind: *damage_kind,
2826 specifier: *specifier,
2827 damage_effect: damage_effect.clone(),
2828 forward_leap_strength: *forward_leap_strength,
2829 vertical_leap_strength: *vertical_leap_strength,
2830 ability_info,
2831 },
2832 timer: Duration::default(),
2833 stage_section: StageSection::Buildup,
2834 exhausted: false,
2835 }),
2836 CharacterAbility::ChargedMelee {
2837 energy_cost,
2838 energy_drain,
2839 buildup_strike,
2840 charge_duration,
2841 swing_duration,
2842 hit_timing,
2843 recover_duration,
2844 melee_constructor,
2845 specifier,
2846 custom_combo,
2847 meta: _,
2848 movement_modifier,
2849 ori_modifier,
2850 } => CharacterState::ChargedMelee(charged_melee::Data {
2851 static_data: charged_melee::StaticData {
2852 energy_cost: *energy_cost,
2853 energy_drain: *energy_drain,
2854 buildup_strike: buildup_strike
2855 .as_ref()
2856 .map(|(dur, strike)| (Duration::from_secs_f32(*dur), strike.clone())),
2857 charge_duration: Duration::from_secs_f32(*charge_duration),
2858 swing_duration: Duration::from_secs_f32(*swing_duration),
2859 hit_timing: *hit_timing,
2860 recover_duration: Duration::from_secs_f32(*recover_duration),
2861 melee_constructor: melee_constructor.clone(),
2862 ability_info,
2863 specifier: *specifier,
2864 custom_combo: *custom_combo,
2865 movement_modifier: *movement_modifier,
2866 ori_modifier: *ori_modifier,
2867 },
2868 stage_section: if buildup_strike.is_some() {
2869 StageSection::Buildup
2870 } else {
2871 StageSection::Charge
2872 },
2873 timer: Duration::default(),
2874 exhausted: false,
2875 charge_amount: 0.0,
2876 movement_modifier: movement_modifier.buildup,
2877 ori_modifier: ori_modifier.buildup,
2878 }),
2879 CharacterAbility::ChargedRanged {
2880 energy_cost: _,
2881 energy_drain,
2882 idle_drain,
2883 projectile,
2884 buildup_duration,
2885 charge_duration,
2886 recover_duration,
2887 projectile_body,
2888 projectile_light,
2889 initial_projectile_speed,
2890 scaled_projectile_speed,
2891 projectile_spread,
2892 num_projectiles,
2893 marker,
2894 move_speed,
2895 meta: _,
2896 } => CharacterState::ChargedRanged(charged_ranged::Data {
2897 static_data: charged_ranged::StaticData {
2898 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2899 charge_duration: Duration::from_secs_f32(*charge_duration),
2900 recover_duration: Duration::from_secs_f32(*recover_duration),
2901 energy_drain: *energy_drain,
2902 idle_drain: *idle_drain,
2903 projectile: projectile.clone(),
2904 projectile_body: *projectile_body,
2905 projectile_light: *projectile_light,
2906 initial_projectile_speed: *initial_projectile_speed,
2907 scaled_projectile_speed: *scaled_projectile_speed,
2908 projectile_spread: *projectile_spread,
2909 num_projectiles: *num_projectiles,
2910 marker: *marker,
2911 move_speed: *move_speed,
2912 ability_info,
2913 },
2914 timer: Duration::default(),
2915 stage_section: StageSection::Buildup,
2916 exhausted: false,
2917 }),
2918 CharacterAbility::RapidRanged {
2919 initial_energy: _,
2920 energy_cost,
2921 buildup_duration,
2922 shoot_duration,
2923 recover_duration,
2924 options,
2925 projectile,
2926 projectile_body,
2927 projectile_light,
2928 projectile_speed,
2929 specifier,
2930 meta: _,
2931 } => CharacterState::RapidRanged(rapid_ranged::Data {
2932 static_data: rapid_ranged::StaticData {
2933 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2934 shoot_duration: Duration::from_secs_f32(*shoot_duration),
2935 recover_duration: Duration::from_secs_f32(*recover_duration),
2936 energy_cost: *energy_cost,
2937 options: *options,
2938 projectile: projectile.clone(),
2939 projectile_body: *projectile_body,
2940 projectile_light: *projectile_light,
2941 projectile_speed: *projectile_speed,
2942 ability_info,
2943 specifier: *specifier,
2944 },
2945 timer: Duration::default(),
2946 stage_section: StageSection::Buildup,
2947 projectiles_fired: 0,
2948 speed: 1.0,
2949 }),
2950 CharacterAbility::Throw {
2951 energy_cost: _,
2952 energy_drain,
2953 buildup_duration,
2954 charge_duration,
2955 throw_duration,
2956 recover_duration,
2957 projectile,
2958 projectile_light,
2959 projectile_dir,
2960 initial_projectile_speed,
2961 scaled_projectile_speed,
2962 damage_effect,
2963 move_speed,
2964 meta: _,
2965 } => {
2966 let hand_info = if let Some(hand_info) = ability_info.hand {
2967 hand_info
2968 } else {
2969 return Err(CharacterStateCreationError::MissingHandInfo);
2970 };
2971
2972 let equip_slot = hand_info.to_equip_slot();
2973
2974 let equipped_item =
2975 if let Some(item) = data.inventory.and_then(|inv| inv.equipped(equip_slot)) {
2976 item
2977 } else {
2978 return Err(CharacterStateCreationError::MissingItem);
2979 };
2980
2981 let item_hash = equipped_item.item_hash();
2982
2983 let tool_kind = if let ItemKind::Tool(Tool { kind, .. }) = *equipped_item.kind() {
2984 kind
2985 } else {
2986 return Err(CharacterStateCreationError::InvalidItemKind);
2987 };
2988
2989 CharacterState::Throw(throw::Data {
2990 static_data: throw::StaticData {
2991 buildup_duration: Duration::from_secs_f32(*buildup_duration),
2992 charge_duration: Duration::from_secs_f32(*charge_duration),
2993 throw_duration: Duration::from_secs_f32(*throw_duration),
2994 recover_duration: Duration::from_secs_f32(*recover_duration),
2995 energy_drain: *energy_drain,
2996 projectile: projectile.clone(),
2997 projectile_light: *projectile_light,
2998 projectile_dir: *projectile_dir,
2999 initial_projectile_speed: *initial_projectile_speed,
3000 scaled_projectile_speed: *scaled_projectile_speed,
3001 move_speed: *move_speed,
3002 ability_info,
3003 damage_effect: damage_effect.clone(),
3004 equip_slot,
3005 item_hash,
3006 hand_info,
3007 tool_kind,
3008 },
3009 timer: Duration::default(),
3010 stage_section: StageSection::Buildup,
3011 exhausted: false,
3012 })
3013 },
3014 CharacterAbility::Shockwave {
3015 energy_cost: _,
3016 buildup_duration,
3017 swing_duration,
3018 recover_duration,
3019 damage,
3020 poise_damage,
3021 knockback,
3022 shockwave_angle,
3023 shockwave_vertical_angle,
3024 shockwave_speed,
3025 shockwave_duration,
3026 dodgeable,
3027 move_efficiency,
3028 damage_kind,
3029 specifier,
3030 ori_rate,
3031 damage_effect,
3032 timing,
3033 emit_outcome,
3034 minimum_combo,
3035 combo_consumption,
3036 meta: _,
3037 } => CharacterState::Shockwave(shockwave::Data {
3038 static_data: shockwave::StaticData {
3039 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3040 swing_duration: Duration::from_secs_f32(*swing_duration),
3041 recover_duration: Duration::from_secs_f32(*recover_duration),
3042 damage: *damage,
3043 poise_damage: *poise_damage,
3044 knockback: *knockback,
3045 shockwave_angle: *shockwave_angle,
3046 shockwave_vertical_angle: *shockwave_vertical_angle,
3047 shockwave_speed: *shockwave_speed,
3048 shockwave_duration: Duration::from_secs_f32(*shockwave_duration),
3049 dodgeable: *dodgeable,
3050 move_efficiency: *move_efficiency,
3051 damage_effect: damage_effect.clone(),
3052 ability_info,
3053 damage_kind: *damage_kind,
3054 specifier: *specifier,
3055 ori_rate: *ori_rate,
3056 timing: *timing,
3057 emit_outcome: *emit_outcome,
3058 minimum_combo: *minimum_combo,
3059 combo_on_use: data.combo.map_or(0, |c| c.counter()),
3060 combo_consumption: *combo_consumption,
3061 },
3062 timer: Duration::default(),
3063 stage_section: StageSection::Buildup,
3064 }),
3065 CharacterAbility::Explosion {
3066 energy_cost: _,
3067 buildup_duration,
3068 action_duration,
3069 recover_duration,
3070 damage,
3071 poise,
3072 knockback,
3073 radius,
3074 min_falloff,
3075 dodgeable,
3076 destroy_terrain,
3077 replace_terrain,
3078 eye_height,
3079 reagent,
3080 movement_modifier,
3081 ori_modifier,
3082 meta: _,
3083 } => CharacterState::Explosion(explosion::Data {
3084 static_data: explosion::StaticData {
3085 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3086 action_duration: Duration::from_secs_f32(*action_duration),
3087 recover_duration: Duration::from_secs_f32(*recover_duration),
3088 damage: *damage,
3089 poise: *poise,
3090 knockback: *knockback,
3091 radius: *radius,
3092 min_falloff: *min_falloff,
3093 dodgeable: *dodgeable,
3094 destroy_terrain: *destroy_terrain,
3095 replace_terrain: *replace_terrain,
3096 eye_height: *eye_height,
3097 reagent: *reagent,
3098 movement_modifier: *movement_modifier,
3099 ori_modifier: *ori_modifier,
3100 ability_info,
3101 },
3102 timer: Duration::default(),
3103 stage_section: StageSection::Buildup,
3104 movement_modifier: movement_modifier.buildup,
3105 ori_modifier: ori_modifier.buildup,
3106 }),
3107 CharacterAbility::BasicBeam {
3108 buildup_duration,
3109 recover_duration,
3110 beam_duration,
3111 damage,
3112 tick_rate,
3113 range,
3114 dodgeable,
3115 blockable,
3116 max_angle,
3117 damage_effect,
3118 energy_regen,
3119 energy_drain,
3120 move_efficiency,
3121 ori_rate,
3122 specifier,
3123 meta: _,
3124 } => CharacterState::BasicBeam(basic_beam::Data {
3125 static_data: basic_beam::StaticData {
3126 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3127 recover_duration: Duration::from_secs_f32(*recover_duration),
3128 beam_duration: Secs(*beam_duration),
3129 damage: *damage,
3130 tick_rate: *tick_rate,
3131 range: *range,
3132 dodgeable: *dodgeable,
3133 blockable: *blockable,
3134 end_radius: max_angle.to_radians().tan() * *range,
3135 damage_effect: damage_effect.clone(),
3136 energy_regen: *energy_regen,
3137 energy_drain: *energy_drain,
3138 ability_info,
3139 move_efficiency: *move_efficiency,
3140 ori_rate: *ori_rate,
3141 specifier: *specifier,
3142 },
3143 timer: Duration::default(),
3144 stage_section: StageSection::Buildup,
3145 aim_dir: data.ori.look_dir(),
3146 beam_offset: data.pos.0,
3147 }),
3148 CharacterAbility::BasicAura {
3149 buildup_duration,
3150 cast_duration,
3151 recover_duration,
3152 targets,
3153 auras,
3154 aura_duration,
3155 range,
3156 energy_cost: _,
3157 scales_with_combo,
3158 specifier,
3159 meta: _,
3160 } => CharacterState::BasicAura(basic_aura::Data {
3161 static_data: basic_aura::StaticData {
3162 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3163 cast_duration: Duration::from_secs_f32(*cast_duration),
3164 recover_duration: Duration::from_secs_f32(*recover_duration),
3165 targets: *targets,
3166 auras: auras.clone(),
3167 aura_duration: *aura_duration,
3168 range: *range,
3169 ability_info,
3170 scales_with_combo: *scales_with_combo,
3171 combo_at_cast: data.combo.map_or(0, |c| c.counter()),
3172 specifier: *specifier,
3173 },
3174 timer: Duration::default(),
3175 stage_section: StageSection::Buildup,
3176 }),
3177 CharacterAbility::StaticAura {
3178 buildup_duration,
3179 cast_duration,
3180 recover_duration,
3181 targets,
3182 auras,
3183 aura_duration,
3184 range,
3185 energy_cost: _,
3186 sprite_info,
3187 meta: _,
3188 } => CharacterState::StaticAura(static_aura::Data {
3189 static_data: static_aura::StaticData {
3190 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3191 cast_duration: Duration::from_secs_f32(*cast_duration),
3192 recover_duration: Duration::from_secs_f32(*recover_duration),
3193 targets: *targets,
3194 auras: auras.clone(),
3195 aura_duration: *aura_duration,
3196 range: *range,
3197 ability_info,
3198 sprite_info: *sprite_info,
3199 },
3200 timer: Duration::default(),
3201 stage_section: StageSection::Buildup,
3202 achieved_radius: sprite_info.map(|si| si.summon_distance.0.floor() as i32 - 1),
3203 }),
3204 CharacterAbility::Blink {
3205 buildup_duration,
3206 recover_duration,
3207 max_range,
3208 frontend_specifier,
3209 meta: _,
3210 } => CharacterState::Blink(blink::Data {
3211 static_data: blink::StaticData {
3212 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3213 recover_duration: Duration::from_secs_f32(*recover_duration),
3214 max_range: *max_range,
3215 frontend_specifier: *frontend_specifier,
3216 ability_info,
3217 },
3218 timer: Duration::default(),
3219 stage_section: StageSection::Buildup,
3220 }),
3221 CharacterAbility::BasicSummon {
3222 buildup_duration,
3223 cast_duration,
3224 recover_duration,
3225 summon_info,
3226 movement_modifier,
3227 ori_modifier,
3228 meta: _,
3229 } => CharacterState::BasicSummon(basic_summon::Data {
3230 static_data: basic_summon::StaticData {
3231 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3232 cast_duration: Duration::from_secs_f32(*cast_duration),
3233 recover_duration: Duration::from_secs_f32(*recover_duration),
3234 summon_info: summon_info.clone(),
3235 movement_modifier: *movement_modifier,
3236 ori_modifier: *ori_modifier,
3237 ability_info,
3238 },
3239 summon_count: 0,
3240 timer: Duration::default(),
3241 stage_section: StageSection::Buildup,
3242 movement_modifier: movement_modifier.buildup,
3243 ori_modifier: ori_modifier.buildup,
3244 }),
3245 CharacterAbility::SelfBuff {
3246 buildup_duration,
3247 cast_duration,
3248 recover_duration,
3249 buffs,
3250 use_raw_buff_strength: _,
3251 buff_cat,
3252 energy_cost: _,
3253 combo_cost,
3254 combo_scaling,
3255 enforced_limit,
3256 meta: _,
3257 specifier,
3258 } => CharacterState::SelfBuff(self_buff::Data {
3259 static_data: self_buff::StaticData {
3260 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3261 cast_duration: Duration::from_secs_f32(*cast_duration),
3262 recover_duration: Duration::from_secs_f32(*recover_duration),
3263 buffs: buffs.clone(),
3264 buff_cat: buff_cat.clone(),
3265 combo_cost: *combo_cost,
3266 combo_scaling: *combo_scaling,
3267 combo_on_use: data.combo.map_or(0, |c| c.counter()),
3268 enforced_limit: *enforced_limit,
3269 ability_info,
3270 specifier: *specifier,
3271 },
3272 timer: Duration::default(),
3273 stage_section: StageSection::Buildup,
3274 }),
3275 CharacterAbility::SpriteSummon {
3276 buildup_duration,
3277 cast_duration,
3278 recover_duration,
3279 sprite,
3280 del_timeout,
3281 summon_distance,
3282 sparseness,
3283 angle,
3284 anchor,
3285 move_efficiency,
3286 ori_modifier,
3287 meta: _,
3288 } => CharacterState::SpriteSummon(sprite_summon::Data {
3289 static_data: sprite_summon::StaticData {
3290 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3291 cast_duration: Duration::from_secs_f32(*cast_duration),
3292 recover_duration: Duration::from_secs_f32(*recover_duration),
3293 sprite: *sprite,
3294 del_timeout: *del_timeout,
3295 summon_distance: *summon_distance,
3296 sparseness: *sparseness,
3297 angle: *angle,
3298 anchor: *anchor,
3299 move_efficiency: *move_efficiency,
3300 ori_modifier: *ori_modifier,
3301 ability_info,
3302 },
3303 timer: Duration::default(),
3304 stage_section: StageSection::Buildup,
3305 achieved_radius: summon_distance.0.floor() as i32 - 1,
3306 }),
3307 CharacterAbility::Music {
3308 play_duration,
3309 ori_modifier,
3310 meta: _,
3311 } => CharacterState::Music(music::Data {
3312 static_data: music::StaticData {
3313 play_duration: Duration::from_secs_f32(*play_duration),
3314 ori_modifier: *ori_modifier,
3315 ability_info,
3316 },
3317 timer: Duration::default(),
3318 stage_section: StageSection::Action,
3319 exhausted: false,
3320 }),
3321 CharacterAbility::FinisherMelee {
3322 energy_cost: _,
3323 buildup_duration,
3324 swing_duration,
3325 recover_duration,
3326 melee_constructor,
3327 minimum_combo,
3328 scaling,
3329 combo_consumption,
3330 meta: _,
3331 } => CharacterState::FinisherMelee(finisher_melee::Data {
3332 static_data: finisher_melee::StaticData {
3333 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3334 swing_duration: Duration::from_secs_f32(*swing_duration),
3335 recover_duration: Duration::from_secs_f32(*recover_duration),
3336 melee_constructor: melee_constructor.clone(),
3337 scaling: *scaling,
3338 minimum_combo: *minimum_combo,
3339 combo_on_use: data.combo.map_or(0, |c| c.counter()),
3340 combo_consumption: *combo_consumption,
3341 ability_info,
3342 },
3343 timer: Duration::default(),
3344 stage_section: StageSection::Buildup,
3345 exhausted: false,
3346 }),
3347 CharacterAbility::DiveMelee {
3348 buildup_duration,
3349 movement_duration,
3350 swing_duration,
3351 recover_duration,
3352 melee_constructor,
3353 energy_cost: _,
3354 vertical_speed,
3355 max_scaling,
3356 meta: _,
3357 } => CharacterState::DiveMelee(dive_melee::Data {
3358 static_data: dive_melee::StaticData {
3359 buildup_duration: buildup_duration.map(Duration::from_secs_f32),
3360 movement_duration: Duration::from_secs_f32(*movement_duration),
3361 swing_duration: Duration::from_secs_f32(*swing_duration),
3362 recover_duration: Duration::from_secs_f32(*recover_duration),
3363 vertical_speed: *vertical_speed,
3364 melee_constructor: melee_constructor.clone(),
3365 max_scaling: *max_scaling,
3366 ability_info,
3367 },
3368 timer: Duration::default(),
3369 stage_section: if data.physics.on_ground.is_none() || buildup_duration.is_none() {
3370 StageSection::Movement
3371 } else {
3372 StageSection::Buildup
3373 },
3374 exhausted: false,
3375 max_vertical_speed: 0.0,
3376 }),
3377 CharacterAbility::RiposteMelee {
3378 energy_cost: _,
3379 buildup_duration,
3380 swing_duration,
3381 recover_duration,
3382 whiffed_recover_duration,
3383 block_strength,
3384 melee_constructor,
3385 meta: _,
3386 } => CharacterState::RiposteMelee(riposte_melee::Data {
3387 static_data: riposte_melee::StaticData {
3388 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3389 swing_duration: Duration::from_secs_f32(*swing_duration),
3390 recover_duration: Duration::from_secs_f32(*recover_duration),
3391 whiffed_recover_duration: Duration::from_secs_f32(*whiffed_recover_duration),
3392 block_strength: *block_strength,
3393 melee_constructor: melee_constructor.clone(),
3394 ability_info,
3395 },
3396 timer: Duration::default(),
3397 stage_section: StageSection::Buildup,
3398 exhausted: false,
3399 whiffed: true,
3400 }),
3401 CharacterAbility::RapidMelee {
3402 buildup_duration,
3403 swing_duration,
3404 recover_duration,
3405 melee_constructor,
3406 energy_cost,
3407 max_strikes,
3408 move_modifier,
3409 ori_modifier,
3410 minimum_combo,
3411 frontend_specifier,
3412 meta: _,
3413 } => CharacterState::RapidMelee(rapid_melee::Data {
3414 static_data: rapid_melee::StaticData {
3415 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3416 swing_duration: Duration::from_secs_f32(*swing_duration),
3417 recover_duration: Duration::from_secs_f32(*recover_duration),
3418 melee_constructor: melee_constructor.clone(),
3419 energy_cost: *energy_cost,
3420 max_strikes: *max_strikes,
3421 move_modifier: *move_modifier,
3422 ori_modifier: *ori_modifier,
3423 minimum_combo: *minimum_combo,
3424 frontend_specifier: *frontend_specifier,
3425 ability_info,
3426 },
3427 timer: Duration::default(),
3428 current_strike: 1,
3429 stage_section: StageSection::Buildup,
3430 exhausted: false,
3431 }),
3432 CharacterAbility::Transform {
3433 buildup_duration,
3434 recover_duration,
3435 target,
3436 specifier,
3437 allow_players,
3438 meta: _,
3439 } => CharacterState::Transform(transform::Data {
3440 static_data: transform::StaticData {
3441 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3442 recover_duration: Duration::from_secs_f32(*recover_duration),
3443 specifier: *specifier,
3444 allow_players: *allow_players,
3445 target: target.to_owned(),
3446 ability_info,
3447 },
3448 timer: Duration::default(),
3449 stage_section: StageSection::Buildup,
3450 }),
3451 CharacterAbility::RegrowHead {
3452 buildup_duration,
3453 recover_duration,
3454 energy_cost,
3455 specifier,
3456 meta: _,
3457 } => CharacterState::RegrowHead(regrow_head::Data {
3458 static_data: regrow_head::StaticData {
3459 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3460 recover_duration: Duration::from_secs_f32(*recover_duration),
3461 specifier: *specifier,
3462 energy_cost: *energy_cost,
3463 ability_info,
3464 },
3465 timer: Duration::default(),
3466 stage_section: StageSection::Buildup,
3467 }),
3468 CharacterAbility::LeapRanged {
3469 energy_cost: _,
3470 buildup_duration,
3471 buildup_melee_timing,
3472 movement_duration,
3473 movement_ranged_timing,
3474 land_timeout,
3475 recover_duration,
3476 melee,
3477 melee_required,
3478 projectile,
3479 projectile_body,
3480 projectile_light,
3481 projectile_speed,
3482 horiz_leap_strength,
3483 vert_leap_strength,
3484 meta: _,
3485 } => CharacterState::LeapRanged(leap_ranged::Data {
3486 static_data: leap_ranged::StaticData {
3487 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3488 buildup_melee_timing: *buildup_melee_timing,
3489 movement_duration: Duration::from_secs_f32(*movement_duration),
3490 movement_ranged_timing: *movement_ranged_timing,
3491 land_timeout: Duration::from_secs_f32(*land_timeout),
3492 recover_duration: Duration::from_secs_f32(*recover_duration),
3493 melee: melee.clone(),
3494 melee_required: *melee_required,
3495 projectile: projectile.clone(),
3496 projectile_body: *projectile_body,
3497 projectile_light: *projectile_light,
3498 projectile_speed: *projectile_speed,
3499 horiz_leap_strength: *horiz_leap_strength,
3500 vert_leap_strength: *vert_leap_strength,
3501 ability_info,
3502 },
3503 timer: Duration::default(),
3504 stage_section: StageSection::Buildup,
3505 melee_done: false,
3506 ranged_done: false,
3507 }),
3508 CharacterAbility::Simple {
3509 energy_cost: _,
3510 combo_cost: _,
3511 buildup_duration,
3512 meta: _,
3513 } => CharacterState::Simple(simple::Data {
3514 static_data: simple::StaticData {
3515 buildup_duration: Duration::from_secs_f32(*buildup_duration),
3516 ability_info,
3517 },
3518 timer: Duration::default(),
3519 stage_section: StageSection::Buildup,
3520 }),
3521 })
3522 }
3523}
3524
3525#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
3526#[serde(deny_unknown_fields)]
3527pub struct AbilityMeta {
3528 #[serde(default)]
3529 pub capabilities: Capability,
3530 #[serde(default)]
3531 pub init_event: Option<AbilityInitEvent>,
3533 #[serde(default)]
3534 pub requirements: AbilityRequirements,
3535 pub contextual_stats: Option<StatAdj>,
3539 pub precision_power_mult: Option<f32>,
3541}
3542
3543impl StatAdj {
3544 pub fn equivalent_stats(&self, data: &JoinData) -> Stats {
3545 let mut stats = Stats::one();
3546 let add = match self.context {
3547 StatContext::PoiseResilience(base) => {
3548 let poise_res = combat::compute_poise_resilience(data.inventory, data.msm);
3549 poise_res.unwrap_or(0.0) / base.max(0.1)
3550 },
3551 StatContext::Stealth(base) => {
3552 let stealth = combat::compute_stealth(data.inventory, data.msm);
3553 stealth / base.max(0.1)
3554 },
3555 };
3556 match self.field {
3557 StatField::EffectPower => {
3558 stats.effect_power += add;
3559 },
3560 StatField::BuffStrength => {
3561 stats.buff_strength += add;
3562 },
3563 StatField::Power => {
3564 stats.power += add;
3565 },
3566 }
3567 stats
3568 }
3569}
3570
3571#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3572pub struct StatAdj {
3573 pub context: StatContext,
3576 pub field: StatField,
3577}
3578
3579#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3580pub enum StatContext {
3581 PoiseResilience(f32),
3582 Stealth(f32),
3583}
3584
3585#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3586pub enum StatField {
3587 EffectPower,
3588 BuffStrength,
3589 Power,
3590}
3591
3592#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
3593pub enum AbilityReqItem {
3594 Firedrop,
3595 PoisonClot,
3596 GelidGel,
3597 LevinDust,
3598}
3599
3600impl AbilityReqItem {
3601 pub fn item_def_id(&self) -> ItemDefinitionIdOwned {
3602 match self {
3603 Self::Firedrop => {
3604 ItemDefinitionIdOwned::Simple(String::from("common.items.consumable.firedrop"))
3605 },
3606 Self::PoisonClot => {
3607 ItemDefinitionIdOwned::Simple(String::from("common.items.consumable.poison_clot"))
3608 },
3609 Self::GelidGel => {
3610 ItemDefinitionIdOwned::Simple(String::from("common.items.consumable.gelid_gel"))
3611 },
3612 Self::LevinDust => {
3613 ItemDefinitionIdOwned::Simple(String::from("common.items.consumable.levin_dust"))
3614 },
3615 }
3616 }
3617}
3618
3619#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
3621pub struct AbilityRequirements {
3622 pub stance: Option<Stance>,
3623 pub item: Option<AbilityReqItem>,
3624}
3625
3626impl AbilityRequirements {
3627 pub fn requirements_met(&self, stance: Option<&Stance>, inv: Option<&Inventory>) -> bool {
3628 let AbilityRequirements {
3629 stance: req_stance,
3630 item,
3631 } = self;
3632 let stance_met = req_stance
3633 .is_none_or(|req_stance| stance.is_some_and(|char_stance| req_stance == *char_stance));
3634 let item_met = item.is_none_or(|item| {
3635 inv.is_some_and(|inv| {
3636 inv.get_slot_of_item_by_def_id(&item.item_def_id())
3637 .is_some()
3638 })
3639 });
3640 stance_met && item_met
3641 }
3642}
3643
3644bitflags::bitflags! {
3645 #[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
3646 pub struct Capability: u8 {
3648 const PARRIES = 0b00000001;
3650 const BLOCK_INTERRUPT = 0b00000010;
3652 const BLOCKS = 0b00000100;
3654 const POISE_RESISTANT = 0b00001000;
3656 const KNOCKBACK_RESISTANT = 0b00010000;
3658 const PARRIES_MELEE = 0b00100000;
3660 }
3661}
3662
3663#[derive(
3664 Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord, Default,
3665)]
3666pub enum Stance {
3667 #[default]
3668 None,
3669 Sword(SwordStance),
3670 Bow(BowStance),
3671}
3672
3673impl Stance {
3674 pub fn pseudo_ability_id(&self) -> &str {
3675 match self {
3676 Stance::Sword(SwordStance::Heavy) => "veloren.core.pseudo_abilities.sword.heavy_stance",
3677 Stance::Sword(SwordStance::Agile) => "veloren.core.pseudo_abilities.sword.agile_stance",
3678 Stance::Sword(SwordStance::Defensive) => {
3679 "veloren.core.pseudo_abilities.sword.defensive_stance"
3680 },
3681 Stance::Sword(SwordStance::Crippling) => {
3682 "veloren.core.pseudo_abilities.sword.crippling_stance"
3683 },
3684 Stance::Sword(SwordStance::Cleaving) => {
3685 "veloren.core.pseudo_abilities.sword.cleaving_stance"
3686 },
3687 Stance::Bow(BowStance::Barrage) => "common.abilities.bow.barrage",
3688 Stance::Bow(BowStance::Scatterburst) => "common.abilities.bow.scatterburst",
3689 Stance::Bow(BowStance::IgniteArrow) => "common.abilities.bow.ignite_arrow",
3690 Stance::Bow(BowStance::DrenchArrow) => "common.abilities.bow.drench_arrow",
3691 Stance::Bow(BowStance::FreezeArrow) => "common.abilities.bow.freeze_arrow",
3692 Stance::Bow(BowStance::JoltArrow) => "common.abilities.bow.jolt_arrow",
3693 Stance::Bow(BowStance::PiercingGale) => "common.abilities.bow.piercing_gale",
3694 Stance::Bow(BowStance::Hawkstrike) => "common.abilities.bow.hawkstrike",
3695 Stance::Bow(BowStance::Fusillade) => "common.abilities.bow.fusillade",
3696 Stance::Bow(BowStance::DeathVolley) => "common.abilities.bow.death_volley",
3697 Stance::None => "veloren.core.pseudo_abilities.no_stance",
3698 }
3699 }
3700}
3701
3702#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
3703pub enum SwordStance {
3704 Crippling,
3705 Cleaving,
3706 Defensive,
3707 Heavy,
3708 Agile,
3709}
3710
3711#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord)]
3712pub enum BowStance {
3713 Barrage,
3714 Scatterburst,
3715 IgniteArrow,
3716 DrenchArrow,
3717 FreezeArrow,
3718 JoltArrow,
3719 PiercingGale,
3720 Hawkstrike,
3721 Fusillade,
3722 DeathVolley,
3723}
3724
3725#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
3726pub enum AbilityInitEvent {
3727 EnterStance(Stance),
3728 GainBuff {
3729 kind: buff::BuffKind,
3730 strength: f32,
3731 duration: Option<Secs>,
3732 },
3733}
3734
3735impl Component for Stance {
3736 type Storage = DerefFlaggedStorage<Self, specs::VecStorage<Self>>;
3737}