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