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