1use crate::{
5 assets::{Asset, AssetCache, AssetExt, AssetHandle, BoxedError, Ron, SharedString},
6 comp::{
7 CharacterAbility, Combo, SkillSet,
8 ability::Stance,
9 buff::{BuffKind, Buffs},
10 inventory::{
11 Inventory,
12 item::{DurabilityMultiplier, ItemKind},
13 slot::EquipSlot,
14 },
15 skills::Skill,
16 },
17};
18use hashbrown::HashMap;
19use serde::{Deserialize, Serialize};
20use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub};
21use strum::EnumIter;
22use tracing::warn;
23
24#[derive(
25 Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd, EnumIter,
26)]
27pub enum ToolKind {
28 Sword,
30 Axe,
31 Hammer,
32 Bow,
33 Staff,
34 Sceptre,
35 Dagger,
37 Shield,
38 Spear,
39 Blowgun,
40 Debug,
42 Farming,
43 Pick,
44 Shovel,
45 Instrument,
47 Throwable,
49 Natural,
53 Empty,
55}
56
57impl ToolKind {
58 pub fn identifier_name(&self) -> &'static str {
59 match self {
60 ToolKind::Sword => "sword",
61 ToolKind::Axe => "axe",
62 ToolKind::Hammer => "hammer",
63 ToolKind::Bow => "bow",
64 ToolKind::Dagger => "dagger",
65 ToolKind::Staff => "staff",
66 ToolKind::Spear => "spear",
67 ToolKind::Blowgun => "blowgun",
68 ToolKind::Sceptre => "sceptre",
69 ToolKind::Shield => "shield",
70 ToolKind::Natural => "natural",
71 ToolKind::Debug => "debug",
72 ToolKind::Farming => "farming",
73 ToolKind::Pick => "pickaxe",
74 ToolKind::Shovel => "shovel",
75 ToolKind::Instrument => "instrument",
76 ToolKind::Throwable => "throwable",
77 ToolKind::Empty => "empty",
78 }
79 }
80
81 pub fn gains_combat_xp(&self) -> bool {
82 matches!(
83 self,
84 ToolKind::Sword
85 | ToolKind::Axe
86 | ToolKind::Hammer
87 | ToolKind::Bow
88 | ToolKind::Dagger
89 | ToolKind::Staff
90 | ToolKind::Spear
91 | ToolKind::Blowgun
92 | ToolKind::Sceptre
93 | ToolKind::Shield
94 )
95 }
96
97 pub fn can_block(&self) -> bool {
98 matches!(
99 self,
100 ToolKind::Sword
101 | ToolKind::Axe
102 | ToolKind::Hammer
103 | ToolKind::Shield
104 | ToolKind::Dagger
105 )
106 }
107
108 pub fn block_priority(&self) -> i32 {
109 match self {
110 ToolKind::Debug => 0,
111 ToolKind::Blowgun => 1,
112 ToolKind::Bow => 2,
113 ToolKind::Staff => 3,
114 ToolKind::Sceptre => 4,
115 ToolKind::Empty => 5,
116 ToolKind::Natural => 6,
117 ToolKind::Throwable => 7,
118 ToolKind::Instrument => 8,
119 ToolKind::Farming => 9,
120 ToolKind::Shovel => 10,
121 ToolKind::Pick => 11,
122 ToolKind::Dagger => 12,
123 ToolKind::Spear => 13,
124 ToolKind::Hammer => 14,
125 ToolKind::Axe => 15,
126 ToolKind::Sword => 16,
127 ToolKind::Shield => 17,
128 }
129 }
130}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
133pub enum Hands {
134 One,
135 Two,
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
139pub struct Stats {
140 pub equip_time_secs: f32,
141 pub power: f32,
142 pub effect_power: f32,
143 pub speed: f32,
144 pub range: f32,
145 pub energy_efficiency: f32,
146 pub buff_strength: f32,
147}
148
149impl Stats {
150 pub fn zero() -> Stats {
151 Stats {
152 equip_time_secs: 0.0,
153 power: 0.0,
154 effect_power: 0.0,
155 speed: 0.0,
156 range: 0.0,
157 energy_efficiency: 0.0,
158 buff_strength: 0.0,
159 }
160 }
161
162 pub fn one() -> Stats {
163 Stats {
164 equip_time_secs: 1.0,
165 power: 1.0,
166 effect_power: 1.0,
167 speed: 1.0,
168 range: 1.0,
169 energy_efficiency: 1.0,
170 buff_strength: 1.0,
171 }
172 }
173
174 pub fn diminished_buff_strength(&self) -> f32 {
181 let base = self.buff_strength.clamp(0.0, self.power);
182 let diminished = (self.buff_strength - base + 1.0).log(5.0);
183 base + diminished
184 }
185
186 pub fn with_durability_mult(&self, dur_mult: DurabilityMultiplier) -> Self {
187 let less_scaled = dur_mult.0 * 0.5 + 0.5;
188 Self {
189 equip_time_secs: self.equip_time_secs / less_scaled.max(0.01),
190 power: self.power * dur_mult.0,
191 effect_power: self.effect_power * dur_mult.0,
192 speed: self.speed * less_scaled,
193 range: self.range * less_scaled,
194 energy_efficiency: self.energy_efficiency * less_scaled,
195 buff_strength: self.buff_strength * dur_mult.0,
196 }
197 }
198}
199
200impl Add<Stats> for Stats {
201 type Output = Self;
202
203 fn add(self, other: Self) -> Self {
204 Self {
205 equip_time_secs: self.equip_time_secs + other.equip_time_secs,
206 power: self.power + other.power,
207 effect_power: self.effect_power + other.effect_power,
208 speed: self.speed + other.speed,
209 range: self.range + other.range,
210 energy_efficiency: self.energy_efficiency + other.energy_efficiency,
211 buff_strength: self.buff_strength + other.buff_strength,
212 }
213 }
214}
215
216impl AddAssign<Stats> for Stats {
217 fn add_assign(&mut self, other: Stats) { *self = *self + other; }
218}
219
220impl Sub<Stats> for Stats {
221 type Output = Self;
222
223 fn sub(self, other: Self) -> Self::Output {
224 Self {
225 equip_time_secs: self.equip_time_secs - other.equip_time_secs,
226 power: self.power - other.power,
227 effect_power: self.effect_power - other.effect_power,
228 speed: self.speed - other.speed,
229 range: self.range - other.range,
230 energy_efficiency: self.energy_efficiency - other.energy_efficiency,
231 buff_strength: self.buff_strength - other.buff_strength,
232 }
233 }
234}
235
236impl Mul<Stats> for Stats {
237 type Output = Self;
238
239 fn mul(self, other: Self) -> Self {
240 Self {
241 equip_time_secs: self.equip_time_secs * other.equip_time_secs,
242 power: self.power * other.power,
243 effect_power: self.effect_power * other.effect_power,
244 speed: self.speed * other.speed,
245 range: self.range * other.range,
246 energy_efficiency: self.energy_efficiency * other.energy_efficiency,
247 buff_strength: self.buff_strength * other.buff_strength,
248 }
249 }
250}
251
252impl MulAssign<Stats> for Stats {
253 fn mul_assign(&mut self, other: Stats) { *self = *self * other; }
254}
255
256impl Div<f32> for Stats {
257 type Output = Self;
258
259 fn div(self, scalar: f32) -> Self {
260 Self {
261 equip_time_secs: self.equip_time_secs / scalar,
262 power: self.power / scalar,
263 effect_power: self.effect_power / scalar,
264 speed: self.speed / scalar,
265 range: self.range / scalar,
266 energy_efficiency: self.energy_efficiency / scalar,
267 buff_strength: self.buff_strength / scalar,
268 }
269 }
270}
271
272impl Mul<DurabilityMultiplier> for Stats {
273 type Output = Self;
274
275 fn mul(self, value: DurabilityMultiplier) -> Self { self.with_durability_mult(value) }
276}
277
278#[derive(Clone, Debug, Serialize, Deserialize)]
279pub struct Tool {
280 pub kind: ToolKind,
281 pub hands: Hands,
282 stats: Stats,
283 }
285
286impl Tool {
287 pub fn new(kind: ToolKind, hands: Hands, stats: Stats) -> Self { Self { kind, hands, stats } }
290
291 pub fn empty() -> Self {
292 Self {
293 kind: ToolKind::Empty,
294 hands: Hands::One,
295 stats: Stats {
296 equip_time_secs: 0.0,
297 power: 1.00,
298 effect_power: 1.00,
299 speed: 1.00,
300 range: 1.0,
301 energy_efficiency: 1.0,
302 buff_strength: 1.0,
303 },
304 }
305 }
306
307 pub fn stats(&self, durability_multiplier: DurabilityMultiplier) -> Stats {
308 self.stats * durability_multiplier
309 }
310}
311
312#[derive(Clone, Debug, Serialize, Deserialize)]
313pub struct AbilitySet<T> {
314 pub guard: Option<AbilityKind<T>>,
315 pub primary: AbilityKind<T>,
316 pub secondary: AbilityKind<T>,
317 pub abilities: Vec<AbilityKind<T>>,
318}
319
320#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
321pub enum AbilityKind<T> {
322 Simple(Option<Skill>, T),
323 Contextualized {
324 pseudo_id: String,
325 abilities: Vec<(AbilityContext, (Option<Skill>, T))>,
326 },
327}
328
329#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq)]
333pub struct ContextualIndex(pub usize);
334
335impl<T> AbilityKind<T> {
336 pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilityKind<U> {
337 match self {
338 Self::Simple(s, x) => AbilityKind::<U>::Simple(s, f(x)),
339 Self::Contextualized {
340 pseudo_id,
341 abilities,
342 } => AbilityKind::<U>::Contextualized {
343 pseudo_id,
344 abilities: abilities
345 .into_iter()
346 .map(|(c, (s, x))| (c, (s, f(x))))
347 .collect(),
348 },
349 }
350 }
351
352 pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilityKind<U> {
353 match self {
354 Self::Simple(s, x) => AbilityKind::<U>::Simple(*s, f(x)),
355 Self::Contextualized {
356 pseudo_id,
357 abilities,
358 } => AbilityKind::<U>::Contextualized {
359 pseudo_id: pseudo_id.clone(),
360 abilities: abilities
361 .iter()
362 .map(|(c, (s, x))| (*c, (*s, f(x))))
363 .collect(),
364 },
365 }
366 }
367
368 pub fn ability(
369 &self,
370 skillset: Option<&SkillSet>,
371 stance: Option<&Stance>,
372 inv: Option<&Inventory>,
373 combo: Option<&Combo>,
374 buffs: Option<&Buffs>,
375 ) -> Option<(&T, Option<ContextualIndex>)> {
376 let unlocked = |s: Option<Skill>, a| {
377 s.is_none_or(|s| skillset.is_some_and(|ss| ss.has_skill(s)))
380 .then_some(a)
381 };
382
383 match self {
384 AbilityKind::Simple(s, a) => unlocked(*s, a).map(|a| (a, None)),
385 AbilityKind::Contextualized {
386 pseudo_id: _,
387 abilities,
388 } => abilities
389 .iter()
390 .enumerate()
391 .filter_map(|(i, (req_contexts, (s, a)))| {
392 unlocked(*s, a).map(|a| (i, (req_contexts, a)))
393 })
394 .find_map(|(i, (req_context, a))| {
395 req_context
396 .fulfilled_by(stance, inv, combo, buffs)
397 .then_some((a, Some(ContextualIndex(i))))
398 }),
399 }
400 }
401}
402
403#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash, Default)]
404pub struct AbilityContext {
405 pub stance: Option<Stance>,
409 #[serde(default)]
410 pub dual_wielding_same_kind: bool,
411 pub combo: Option<u32>,
412 pub buff: Option<BuffKind>,
413}
414
415impl AbilityContext {
416 fn fulfilled_by(
417 &self,
418 stance: Option<&Stance>,
419 inv: Option<&Inventory>,
420 combo: Option<&Combo>,
421 buffs: Option<&Buffs>,
422 ) -> bool {
423 let dual_wielding_same_kind = if let Some(inv) = inv {
424 let tool_kind = |slot| {
425 inv.equipped(slot).and_then(|i| {
426 if let ItemKind::Tool(tool) = &*i.kind() {
427 Some(tool.kind)
428 } else {
429 None
430 }
431 })
432 };
433 tool_kind(EquipSlot::ActiveMainhand) == tool_kind(EquipSlot::ActiveOffhand)
434 } else {
435 false
436 };
437
438 let stance_check = self.stance.is_none_or(|s| stance.copied() == Some(s));
440 let dual_wield_check = !self.dual_wielding_same_kind || dual_wielding_same_kind;
442 let combo_check = self
444 .combo
445 .is_none_or(|c_req| combo.is_some_and(|c| c.counter() >= c_req));
446 let buff_check = self
448 .buff
449 .is_none_or(|b| buffs.is_some_and(|buffs| buffs.contains(b)));
450
451 stance_check && dual_wield_check && combo_check && buff_check
452 }
453}
454
455impl AbilitySet<AbilityItem> {
456 #[must_use]
457 pub fn modified_by_tool(
458 self,
459 tool: &Tool,
460 durability_multiplier: DurabilityMultiplier,
461 ) -> Self {
462 self.map(|a| AbilityItem {
463 id: a.id,
464 ability: a
465 .ability
466 .adjusted_by_stats(tool.stats(durability_multiplier)),
467 })
468 }
469}
470
471impl<T> AbilitySet<T> {
472 pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilitySet<U> {
473 AbilitySet {
474 guard: self.guard.map(|g| g.map(&mut f)),
475 primary: self.primary.map(&mut f),
476 secondary: self.secondary.map(&mut f),
477 abilities: self.abilities.into_iter().map(|x| x.map(&mut f)).collect(),
478 }
479 }
480
481 pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilitySet<U> {
482 AbilitySet {
483 guard: self.guard.as_ref().map(|g| g.map_ref(&mut f)),
484 primary: self.primary.map_ref(&mut f),
485 secondary: self.secondary.map_ref(&mut f),
486 abilities: self.abilities.iter().map(|x| x.map_ref(&mut f)).collect(),
487 }
488 }
489
490 pub fn guard(
491 &self,
492 skillset: Option<&SkillSet>,
493 stance: Option<&Stance>,
494 inv: Option<&Inventory>,
495 combo: Option<&Combo>,
496 buffs: Option<&Buffs>,
497 ) -> Option<(&T, Option<ContextualIndex>)> {
498 self.guard
499 .as_ref()
500 .and_then(|g| g.ability(skillset, stance, inv, combo, buffs))
501 }
502
503 pub fn primary(
504 &self,
505 skillset: Option<&SkillSet>,
506 stance: Option<&Stance>,
507 inv: Option<&Inventory>,
508 combo: Option<&Combo>,
509 buffs: Option<&Buffs>,
510 ) -> Option<(&T, Option<ContextualIndex>)> {
511 self.primary.ability(skillset, stance, inv, combo, buffs)
512 }
513
514 pub fn secondary(
515 &self,
516 skillset: Option<&SkillSet>,
517 stance: Option<&Stance>,
518 inv: Option<&Inventory>,
519 combo: Option<&Combo>,
520 buffs: Option<&Buffs>,
521 ) -> Option<(&T, Option<ContextualIndex>)> {
522 self.secondary.ability(skillset, stance, inv, combo, buffs)
523 }
524
525 pub fn auxiliary(
526 &self,
527 index: usize,
528 skillset: Option<&SkillSet>,
529 stance: Option<&Stance>,
530 inv: Option<&Inventory>,
531 combo: Option<&Combo>,
532 buffs: Option<&Buffs>,
533 ) -> Option<(&T, Option<ContextualIndex>)> {
534 self.abilities
535 .get(index)
536 .and_then(|a| a.ability(skillset, stance, inv, combo, buffs))
537 }
538}
539
540impl Default for AbilitySet<AbilityItem> {
541 fn default() -> Self {
542 AbilitySet {
543 guard: None,
544 primary: AbilityKind::Simple(None, AbilityItem {
545 id: String::new(),
546 ability: CharacterAbility::default(),
547 }),
548 secondary: AbilityKind::Simple(None, AbilityItem {
549 id: String::new(),
550 ability: CharacterAbility::default(),
551 }),
552 abilities: Vec::new(),
553 }
554 }
555}
556
557#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
558pub enum AbilitySpec {
559 Tool(ToolKind),
560 Custom(String),
561}
562
563#[derive(Clone, Debug, Serialize, Deserialize)]
564pub struct AbilityItem {
565 pub id: String,
566 pub ability: CharacterAbility,
567}
568
569#[derive(Clone, Debug, Serialize, Deserialize)]
570pub enum AbilityMapEntry<T = AbilityItem> {
571 AbilitySet(AbilitySet<T>),
572 AbilitySetOverride {
573 parent: AbilitySpec,
574 guard: Option<AbilityKind<T>>,
575 primary: Option<AbilityKind<T>>,
576 secondary: Option<AbilityKind<T>>,
577 added_abilities: Vec<AbilityKind<T>>,
578 removed_abilities: Vec<AbilityKind<T>>,
579 },
580}
581
582impl<T: Clone + Eq> AbilityMapEntry<T> {
583 pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilityMapEntry<U> {
584 match self {
585 AbilityMapEntry::AbilitySet(ability_set) => {
586 AbilityMapEntry::AbilitySet(ability_set.map_ref(f))
587 },
588 AbilityMapEntry::AbilitySetOverride {
589 parent,
590 guard,
591 primary,
592 secondary,
593 added_abilities,
594 removed_abilities,
595 } => AbilityMapEntry::AbilitySetOverride {
596 parent: parent.clone(),
597 guard: guard.as_ref().map(|g| g.map_ref(&mut f)),
598 primary: primary.as_ref().map(|p| p.map_ref(&mut f)),
599 secondary: secondary.as_ref().map(|s| s.map_ref(&mut f)),
600 added_abilities: added_abilities.iter().map(|x| x.map_ref(&mut f)).collect(),
601 removed_abilities: removed_abilities
602 .iter()
603 .map(|x| x.map_ref(&mut f))
604 .collect(),
605 },
606 }
607 }
608
609 pub fn inherit(self, parent: &Self) -> Self {
610 match self {
611 AbilityMapEntry::AbilitySet(_) => self,
612 AbilityMapEntry::AbilitySetOverride {
613 guard,
614 primary,
615 secondary,
616 mut added_abilities,
617 mut removed_abilities,
618 ..
619 } => match parent {
620 AbilityMapEntry::AbilitySet(parent) => {
621 added_abilities.extend(
622 parent
623 .abilities
624 .iter()
625 .filter(|x| !removed_abilities.contains(x))
626 .cloned(),
627 );
628
629 AbilityMapEntry::AbilitySet(AbilitySet {
630 guard: guard.or(parent.guard.clone()),
631 primary: primary.unwrap_or(parent.primary.clone()),
632 secondary: secondary.unwrap_or(parent.secondary.clone()),
633 abilities: added_abilities,
634 })
635 },
636 AbilityMapEntry::AbilitySetOverride {
637 parent: p_parent,
638 guard: p_guard,
639 primary: p_primary,
640 secondary: p_secondary,
641 added_abilities: p_added_abilities,
642 removed_abilities: p_removed_abilities,
643 } => {
644 added_abilities.extend(
645 p_added_abilities
646 .iter()
647 .filter(|x| !removed_abilities.contains(x))
648 .cloned(),
649 );
650 removed_abilities.extend(
651 p_removed_abilities
652 .iter()
653 .filter(|x| !added_abilities.contains(x))
654 .cloned(),
655 );
656
657 AbilityMapEntry::AbilitySetOverride {
658 parent: p_parent.clone(),
659 guard: guard.or(p_guard.clone()),
660 primary: primary.or(p_primary.clone()),
661 secondary: secondary.or(p_secondary.clone()),
662 added_abilities,
663 removed_abilities,
664 }
665 },
666 },
667 }
668 }
669}
670
671#[derive(Clone, Debug, Serialize, Deserialize)]
672pub struct AbilityMap<T = AbilityItem>(HashMap<AbilitySpec, AbilityMapEntry<T>>);
673
674impl AbilityMap {
675 pub fn load() -> AssetHandle<Self> {
676 Self::load_expect("common.abilities.ability_set_manifest")
677 }
678}
679
680impl<T> AbilityMap<T> {
681 pub fn get_ability_set(&self, key: &AbilitySpec) -> Option<&AbilitySet<T>> {
682 self.0.get(key).and_then(|entry| match entry {
683 AbilityMapEntry::AbilitySet(ability_set) => Some(ability_set),
684 AbilityMapEntry::AbilitySetOverride { .. } => None,
685 })
686 }
687}
688
689impl Asset for AbilityMap {
690 fn load(cache: &AssetCache, specifier: &SharedString) -> Result<Self, BoxedError> {
691 let mut ability_map = cache
692 .load::<Ron<AbilityMap<String>>>(specifier)?
693 .read()
694 .0
695 .0
696 .clone();
697
698 while let Some((spec, mut entry)) = {
700 let spec = ability_map
701 .iter()
702 .find(|(_, entry)| matches!(entry, AbilityMapEntry::AbilitySetOverride { .. }))
703 .map(|(spec, _)| spec.clone());
704
705 spec.and_then(|spec| ability_map.remove_entry(&spec))
706 } {
707 let parent = if let AbilityMapEntry::AbilitySetOverride { parent, .. } = &entry {
708 Some(parent)
709 } else {
710 None
711 }
712 .and_then(|parent| ability_map.get(parent));
713
714 if let Some(parent) = parent {
715 entry = entry.inherit(parent);
716 }
717
718 ability_map.insert(spec, entry);
719 }
720
721 Ok(AbilityMap(
722 ability_map
723 .into_iter()
724 .map(|(kind, set)| {
725 (
726 kind.clone(),
727 set.map_ref(|s| AbilityItem {
728 id: s.clone(),
729 ability: if let Ok(handle) = cache.load::<Ron<CharacterAbility>>(s) {
730 handle.cloned().into_inner()
731 } else {
732 warn!(?s, "missing specified ability file");
733 CharacterAbility::default()
734 },
735 }),
736 )
737 })
738 .collect::<HashMap<_, _>>(),
739 ))
740 }
741}