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