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