1use crate::{
5 assets::{self, Asset, AssetExt, AssetHandle},
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};
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd)]
22pub enum ToolKind {
23 Sword,
25 Axe,
26 Hammer,
27 Bow,
28 Staff,
29 Sceptre,
30 Dagger,
32 Shield,
33 Spear,
34 Blowgun,
35 Debug,
37 Farming,
38 Pick,
39 Shovel,
40 Instrument,
42 Natural,
46 Empty,
48}
49
50impl ToolKind {
51 pub fn identifier_name(&self) -> &'static str {
52 match self {
53 ToolKind::Sword => "sword",
54 ToolKind::Axe => "axe",
55 ToolKind::Hammer => "hammer",
56 ToolKind::Bow => "bow",
57 ToolKind::Dagger => "dagger",
58 ToolKind::Staff => "staff",
59 ToolKind::Spear => "spear",
60 ToolKind::Blowgun => "blowgun",
61 ToolKind::Sceptre => "sceptre",
62 ToolKind::Shield => "shield",
63 ToolKind::Natural => "natural",
64 ToolKind::Debug => "debug",
65 ToolKind::Farming => "farming",
66 ToolKind::Pick => "pickaxe",
67 ToolKind::Shovel => "shovel",
68 ToolKind::Instrument => "instrument",
69 ToolKind::Empty => "empty",
70 }
71 }
72
73 pub fn gains_combat_xp(&self) -> bool {
74 matches!(
75 self,
76 ToolKind::Sword
77 | ToolKind::Axe
78 | ToolKind::Hammer
79 | ToolKind::Bow
80 | ToolKind::Dagger
81 | ToolKind::Staff
82 | ToolKind::Spear
83 | ToolKind::Blowgun
84 | ToolKind::Sceptre
85 | ToolKind::Shield
86 )
87 }
88
89 pub fn can_block(&self) -> bool {
90 matches!(
91 self,
92 ToolKind::Sword
93 | ToolKind::Axe
94 | ToolKind::Hammer
95 | ToolKind::Shield
96 | ToolKind::Dagger
97 )
98 }
99
100 pub fn block_priority(&self) -> i32 {
101 match self {
102 ToolKind::Debug => 0,
103 ToolKind::Blowgun => 1,
104 ToolKind::Bow => 2,
105 ToolKind::Staff => 3,
106 ToolKind::Sceptre => 4,
107 ToolKind::Empty => 5,
108 ToolKind::Natural => 6,
109 ToolKind::Instrument => 7,
110 ToolKind::Farming => 8,
111 ToolKind::Shovel => 9,
112 ToolKind::Pick => 10,
113 ToolKind::Dagger => 11,
114 ToolKind::Spear => 12,
115 ToolKind::Hammer => 13,
116 ToolKind::Axe => 14,
117 ToolKind::Sword => 15,
118 ToolKind::Shield => 16,
119 }
120 }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
124pub enum Hands {
125 One,
126 Two,
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
130pub struct Stats {
131 pub equip_time_secs: f32,
132 pub power: f32,
133 pub effect_power: f32,
134 pub speed: f32,
135 pub range: f32,
136 pub energy_efficiency: f32,
137 pub buff_strength: f32,
138}
139
140impl Stats {
141 pub fn zero() -> Stats {
142 Stats {
143 equip_time_secs: 0.0,
144 power: 0.0,
145 effect_power: 0.0,
146 speed: 0.0,
147 range: 0.0,
148 energy_efficiency: 0.0,
149 buff_strength: 0.0,
150 }
151 }
152
153 pub fn one() -> Stats {
154 Stats {
155 equip_time_secs: 1.0,
156 power: 1.0,
157 effect_power: 1.0,
158 speed: 1.0,
159 range: 1.0,
160 energy_efficiency: 1.0,
161 buff_strength: 1.0,
162 }
163 }
164
165 pub fn diminished_buff_strength(&self) -> f32 {
172 let base = self.buff_strength.clamp(0.0, self.power);
173 let diminished = (self.buff_strength - base + 1.0).log(5.0);
174 base + diminished
175 }
176
177 pub fn with_durability_mult(&self, dur_mult: DurabilityMultiplier) -> Self {
178 let less_scaled = dur_mult.0 * 0.5 + 0.5;
179 Self {
180 equip_time_secs: self.equip_time_secs / less_scaled.max(0.01),
181 power: self.power * dur_mult.0,
182 effect_power: self.effect_power * dur_mult.0,
183 speed: self.speed * less_scaled,
184 range: self.range * less_scaled,
185 energy_efficiency: self.energy_efficiency * less_scaled,
186 buff_strength: self.buff_strength * dur_mult.0,
187 }
188 }
189}
190
191impl Asset for Stats {
192 type Loader = assets::RonLoader;
193
194 const EXTENSION: &'static str = "ron";
195}
196
197impl Add<Stats> for Stats {
198 type Output = Self;
199
200 fn add(self, other: Self) -> Self {
201 Self {
202 equip_time_secs: self.equip_time_secs + other.equip_time_secs,
203 power: self.power + other.power,
204 effect_power: self.effect_power + other.effect_power,
205 speed: self.speed + other.speed,
206 range: self.range + other.range,
207 energy_efficiency: self.energy_efficiency + other.energy_efficiency,
208 buff_strength: self.buff_strength + other.buff_strength,
209 }
210 }
211}
212
213impl AddAssign<Stats> for Stats {
214 fn add_assign(&mut self, other: Stats) { *self = *self + other; }
215}
216
217impl Sub<Stats> for Stats {
218 type Output = Self;
219
220 fn sub(self, other: Self) -> Self::Output {
221 Self {
222 equip_time_secs: self.equip_time_secs - other.equip_time_secs,
223 power: self.power - other.power,
224 effect_power: self.effect_power - other.effect_power,
225 speed: self.speed - other.speed,
226 range: self.range - other.range,
227 energy_efficiency: self.energy_efficiency - other.energy_efficiency,
228 buff_strength: self.buff_strength - other.buff_strength,
229 }
230 }
231}
232
233impl Mul<Stats> for Stats {
234 type Output = Self;
235
236 fn mul(self, other: Self) -> Self {
237 Self {
238 equip_time_secs: self.equip_time_secs * other.equip_time_secs,
239 power: self.power * other.power,
240 effect_power: self.effect_power * other.effect_power,
241 speed: self.speed * other.speed,
242 range: self.range * other.range,
243 energy_efficiency: self.energy_efficiency * other.energy_efficiency,
244 buff_strength: self.buff_strength * other.buff_strength,
245 }
246 }
247}
248
249impl MulAssign<Stats> for Stats {
250 fn mul_assign(&mut self, other: Stats) { *self = *self * other; }
251}
252
253impl Div<f32> for Stats {
254 type Output = Self;
255
256 fn div(self, scalar: f32) -> Self {
257 Self {
258 equip_time_secs: self.equip_time_secs / scalar,
259 power: self.power / scalar,
260 effect_power: self.effect_power / scalar,
261 speed: self.speed / scalar,
262 range: self.range / scalar,
263 energy_efficiency: self.energy_efficiency / scalar,
264 buff_strength: self.buff_strength / scalar,
265 }
266 }
267}
268
269impl Mul<DurabilityMultiplier> for Stats {
270 type Output = Self;
271
272 fn mul(self, value: DurabilityMultiplier) -> Self { self.with_durability_mult(value) }
273}
274
275#[derive(Clone, Debug, Serialize, Deserialize)]
276pub struct Tool {
277 pub kind: ToolKind,
278 pub hands: Hands,
279 stats: Stats,
280 }
282
283impl Tool {
284 pub fn new(kind: ToolKind, hands: Hands, stats: Stats) -> Self { Self { kind, hands, stats } }
287
288 pub fn empty() -> Self {
289 Self {
290 kind: ToolKind::Empty,
291 hands: Hands::One,
292 stats: Stats {
293 equip_time_secs: 0.0,
294 power: 1.00,
295 effect_power: 1.00,
296 speed: 1.00,
297 range: 1.0,
298 energy_efficiency: 1.0,
299 buff_strength: 1.0,
300 },
301 }
302 }
303
304 pub fn stats(&self, durability_multiplier: DurabilityMultiplier) -> Stats {
305 self.stats * durability_multiplier
306 }
307}
308
309#[derive(Clone, Debug, Serialize, Deserialize)]
310pub struct AbilitySet<T> {
311 pub guard: Option<AbilityKind<T>>,
312 pub primary: AbilityKind<T>,
313 pub secondary: AbilityKind<T>,
314 pub abilities: Vec<AbilityKind<T>>,
315}
316
317#[derive(Clone, Debug, Serialize, Deserialize)]
318pub enum AbilityKind<T> {
319 Simple(Option<Skill>, T),
320 Contextualized {
321 pseudo_id: String,
322 abilities: Vec<(AbilityContext, (Option<Skill>, T))>,
323 },
324}
325
326#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq)]
330pub struct ContextualIndex(pub usize);
331
332impl<T> AbilityKind<T> {
333 pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilityKind<U> {
334 match self {
335 Self::Simple(s, x) => AbilityKind::<U>::Simple(s, f(x)),
336 Self::Contextualized {
337 pseudo_id,
338 abilities,
339 } => AbilityKind::<U>::Contextualized {
340 pseudo_id,
341 abilities: abilities
342 .into_iter()
343 .map(|(c, (s, x))| (c, (s, f(x))))
344 .collect(),
345 },
346 }
347 }
348
349 pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilityKind<U> {
350 match self {
351 Self::Simple(s, x) => AbilityKind::<U>::Simple(*s, f(x)),
352 Self::Contextualized {
353 pseudo_id,
354 abilities,
355 } => AbilityKind::<U>::Contextualized {
356 pseudo_id: pseudo_id.clone(),
357 abilities: abilities
358 .iter()
359 .map(|(c, (s, x))| (*c, (*s, f(x))))
360 .collect(),
361 },
362 }
363 }
364
365 pub fn ability(
366 &self,
367 skillset: Option<&SkillSet>,
368 context: &AbilityContext,
369 ) -> Option<(&T, Option<ContextualIndex>)> {
370 let unlocked = |s: Option<Skill>, a| {
371 s.is_none_or(|s| skillset.is_some_and(|ss| ss.has_skill(s)))
374 .then_some(a)
375 };
376
377 match self {
378 AbilityKind::Simple(s, a) => unlocked(*s, a).map(|a| (a, None)),
379 AbilityKind::Contextualized {
380 pseudo_id: _,
381 abilities,
382 } => abilities
383 .iter()
384 .enumerate()
385 .filter_map(|(i, (req_contexts, (s, a)))| {
386 unlocked(*s, a).map(|a| (i, (req_contexts, a)))
387 })
388 .find_map(|(i, (req_context, a))| {
389 req_context
390 .fulfilled_by(context)
391 .then_some((a, Some(ContextualIndex(i))))
392 }),
393 }
394 }
395}
396
397#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash, Default)]
398pub struct AbilityContext {
399 pub stance: Option<Stance>,
403 #[serde(default)]
404 pub dual_wielding_same_kind: bool,
405 pub combo: Option<u32>,
406}
407
408impl AbilityContext {
409 pub fn from(stance: Option<&Stance>, inv: Option<&Inventory>, combo: Option<&Combo>) -> Self {
410 let stance = match stance {
411 Some(Stance::None) => None,
412 Some(stance) => Some(*stance),
413 None => None,
414 };
415 let dual_wielding_same_kind = if let Some(inv) = inv {
416 let tool_kind = |slot| {
417 inv.equipped(slot).and_then(|i| {
418 if let ItemKind::Tool(tool) = &*i.kind() {
419 Some(tool.kind)
420 } else {
421 None
422 }
423 })
424 };
425 tool_kind(EquipSlot::ActiveMainhand) == tool_kind(EquipSlot::ActiveOffhand)
426 } else {
427 false
428 };
429 let combo = combo.map(|c| c.counter());
430
431 AbilityContext {
432 stance,
433 dual_wielding_same_kind,
434 combo,
435 }
436 }
437
438 fn fulfilled_by(&self, context: &AbilityContext) -> bool {
439 let AbilityContext {
440 stance,
441 dual_wielding_same_kind,
442 combo,
443 } = self;
444 let stance_check = stance.is_none_or(|s| context.stance == Some(s));
446 let dual_wield_check = !dual_wielding_same_kind || context.dual_wielding_same_kind;
448 let combo_check = combo.is_none_or(|c| context.combo.is_some_and(|c_c| c_c >= c));
450
451 stance_check && dual_wield_check && combo_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 context: &AbilityContext,
494 ) -> Option<(&T, Option<ContextualIndex>)> {
495 self.guard
496 .as_ref()
497 .and_then(|g| g.ability(skillset, context))
498 }
499
500 pub fn primary(
501 &self,
502 skillset: Option<&SkillSet>,
503 context: &AbilityContext,
504 ) -> Option<(&T, Option<ContextualIndex>)> {
505 self.primary.ability(skillset, context)
506 }
507
508 pub fn secondary(
509 &self,
510 skillset: Option<&SkillSet>,
511 context: &AbilityContext,
512 ) -> Option<(&T, Option<ContextualIndex>)> {
513 self.secondary.ability(skillset, context)
514 }
515
516 pub fn auxiliary(
517 &self,
518 index: usize,
519 skillset: Option<&SkillSet>,
520 context: &AbilityContext,
521 ) -> Option<(&T, Option<ContextualIndex>)> {
522 self.abilities
523 .get(index)
524 .and_then(|a| a.ability(skillset, context))
525 }
526}
527
528impl Default for AbilitySet<AbilityItem> {
529 fn default() -> Self {
530 AbilitySet {
531 guard: None,
532 primary: AbilityKind::Simple(None, AbilityItem {
533 id: String::new(),
534 ability: CharacterAbility::default(),
535 }),
536 secondary: AbilityKind::Simple(None, AbilityItem {
537 id: String::new(),
538 ability: CharacterAbility::default(),
539 }),
540 abilities: Vec::new(),
541 }
542 }
543}
544
545#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
546pub enum AbilitySpec {
547 Tool(ToolKind),
548 Custom(String),
549}
550
551#[derive(Clone, Debug, Serialize, Deserialize)]
552pub struct AbilityItem {
553 pub id: String,
554 pub ability: CharacterAbility,
555}
556
557#[derive(Clone, Debug, Serialize, Deserialize)]
558pub struct AbilityMap<T = AbilityItem>(HashMap<AbilitySpec, AbilitySet<T>>);
559
560impl AbilityMap {
561 pub fn load() -> AssetHandle<Self> {
562 Self::load_expect("common.abilities.ability_set_manifest")
563 }
564}
565
566impl<T> AbilityMap<T> {
567 pub fn get_ability_set(&self, key: &AbilitySpec) -> Option<&AbilitySet<T>> { self.0.get(key) }
568}
569
570impl Asset for AbilityMap<String> {
571 type Loader = assets::RonLoader;
572
573 const EXTENSION: &'static str = "ron";
574}
575
576impl assets::Compound for AbilityMap {
577 fn load(
578 cache: assets::AnyCache,
579 specifier: &assets::SharedString,
580 ) -> Result<Self, assets::BoxedError> {
581 let manifest = cache.load::<AbilityMap<String>>(specifier)?.read();
582
583 Ok(AbilityMap(
584 manifest
585 .0
586 .iter()
587 .map(|(kind, set)| {
588 (
589 kind.clone(),
590 set.map_ref(|s| AbilityItem {
593 id: s.clone(),
594 ability: cache.load_expect(s).cloned(),
595 }),
596 )
597 })
598 .collect(),
599 ))
600 }
601}