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