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