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;
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 Add<Stats> for Stats {
199 type Output = Self;
200
201 fn add(self, other: Self) -> Self {
202 Self {
203 equip_time_secs: self.equip_time_secs + other.equip_time_secs,
204 power: self.power + other.power,
205 effect_power: self.effect_power + other.effect_power,
206 speed: self.speed + other.speed,
207 range: self.range + other.range,
208 energy_efficiency: self.energy_efficiency + other.energy_efficiency,
209 buff_strength: self.buff_strength + other.buff_strength,
210 }
211 }
212}
213
214impl AddAssign<Stats> for Stats {
215 fn add_assign(&mut self, other: Stats) { *self = *self + other; }
216}
217
218impl Sub<Stats> for Stats {
219 type Output = Self;
220
221 fn sub(self, other: Self) -> Self::Output {
222 Self {
223 equip_time_secs: self.equip_time_secs - other.equip_time_secs,
224 power: self.power - other.power,
225 effect_power: self.effect_power - other.effect_power,
226 speed: self.speed - other.speed,
227 range: self.range - other.range,
228 energy_efficiency: self.energy_efficiency - other.energy_efficiency,
229 buff_strength: self.buff_strength - other.buff_strength,
230 }
231 }
232}
233
234impl Mul<Stats> for Stats {
235 type Output = Self;
236
237 fn mul(self, other: Self) -> Self {
238 Self {
239 equip_time_secs: self.equip_time_secs * other.equip_time_secs,
240 power: self.power * other.power,
241 effect_power: self.effect_power * other.effect_power,
242 speed: self.speed * other.speed,
243 range: self.range * other.range,
244 energy_efficiency: self.energy_efficiency * other.energy_efficiency,
245 buff_strength: self.buff_strength * other.buff_strength,
246 }
247 }
248}
249
250impl MulAssign<Stats> for Stats {
251 fn mul_assign(&mut self, other: Stats) { *self = *self * other; }
252}
253
254impl Div<f32> for Stats {
255 type Output = Self;
256
257 fn div(self, scalar: f32) -> Self {
258 Self {
259 equip_time_secs: self.equip_time_secs / scalar,
260 power: self.power / scalar,
261 effect_power: self.effect_power / scalar,
262 speed: self.speed / scalar,
263 range: self.range / scalar,
264 energy_efficiency: self.energy_efficiency / scalar,
265 buff_strength: self.buff_strength / scalar,
266 }
267 }
268}
269
270impl Mul<DurabilityMultiplier> for Stats {
271 type Output = Self;
272
273 fn mul(self, value: DurabilityMultiplier) -> Self { self.with_durability_mult(value) }
274}
275
276#[derive(Clone, Debug, Serialize, Deserialize)]
277pub struct Tool {
278 pub kind: ToolKind,
279 pub hands: Hands,
280 stats: Stats,
281 }
283
284impl Tool {
285 pub fn new(kind: ToolKind, hands: Hands, stats: Stats) -> Self { Self { kind, hands, stats } }
288
289 pub fn empty() -> Self {
290 Self {
291 kind: ToolKind::Empty,
292 hands: Hands::One,
293 stats: Stats {
294 equip_time_secs: 0.0,
295 power: 1.00,
296 effect_power: 1.00,
297 speed: 1.00,
298 range: 1.0,
299 energy_efficiency: 1.0,
300 buff_strength: 1.0,
301 },
302 }
303 }
304
305 pub fn stats(&self, durability_multiplier: DurabilityMultiplier) -> Stats {
306 self.stats * durability_multiplier
307 }
308}
309
310#[derive(Clone, Debug, Serialize, Deserialize)]
311pub struct AbilitySet<T> {
312 pub guard: Option<AbilityKind<T>>,
313 pub primary: AbilityKind<T>,
314 pub secondary: AbilityKind<T>,
315 pub abilities: Vec<AbilityKind<T>>,
316}
317
318#[derive(Clone, Debug, Serialize, Deserialize)]
319pub enum AbilityKind<T> {
320 Simple(Option<Skill>, T),
321 Contextualized {
322 pseudo_id: String,
323 abilities: Vec<(AbilityContext, (Option<Skill>, T))>,
324 },
325}
326
327#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq)]
331pub struct ContextualIndex(pub usize);
332
333impl<T> AbilityKind<T> {
334 pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilityKind<U> {
335 match self {
336 Self::Simple(s, x) => AbilityKind::<U>::Simple(s, f(x)),
337 Self::Contextualized {
338 pseudo_id,
339 abilities,
340 } => AbilityKind::<U>::Contextualized {
341 pseudo_id,
342 abilities: abilities
343 .into_iter()
344 .map(|(c, (s, x))| (c, (s, f(x))))
345 .collect(),
346 },
347 }
348 }
349
350 pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilityKind<U> {
351 match self {
352 Self::Simple(s, x) => AbilityKind::<U>::Simple(*s, f(x)),
353 Self::Contextualized {
354 pseudo_id,
355 abilities,
356 } => AbilityKind::<U>::Contextualized {
357 pseudo_id: pseudo_id.clone(),
358 abilities: abilities
359 .iter()
360 .map(|(c, (s, x))| (*c, (*s, f(x))))
361 .collect(),
362 },
363 }
364 }
365
366 pub fn ability(
367 &self,
368 skillset: Option<&SkillSet>,
369 context: &AbilityContext,
370 ) -> Option<(&T, Option<ContextualIndex>)> {
371 let unlocked = |s: Option<Skill>, a| {
372 s.is_none_or(|s| skillset.is_some_and(|ss| ss.has_skill(s)))
375 .then_some(a)
376 };
377
378 match self {
379 AbilityKind::Simple(s, a) => unlocked(*s, a).map(|a| (a, None)),
380 AbilityKind::Contextualized {
381 pseudo_id: _,
382 abilities,
383 } => abilities
384 .iter()
385 .enumerate()
386 .filter_map(|(i, (req_contexts, (s, a)))| {
387 unlocked(*s, a).map(|a| (i, (req_contexts, a)))
388 })
389 .find_map(|(i, (req_context, a))| {
390 req_context
391 .fulfilled_by(context)
392 .then_some((a, Some(ContextualIndex(i))))
393 }),
394 }
395 }
396}
397
398#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash, Default)]
399pub struct AbilityContext {
400 pub stance: Option<Stance>,
404 #[serde(default)]
405 pub dual_wielding_same_kind: bool,
406 pub combo: Option<u32>,
407}
408
409impl AbilityContext {
410 pub fn from(stance: Option<&Stance>, inv: Option<&Inventory>, combo: Option<&Combo>) -> Self {
411 let stance = match stance {
412 Some(Stance::None) => None,
413 Some(stance) => Some(*stance),
414 None => None,
415 };
416 let dual_wielding_same_kind = if let Some(inv) = inv {
417 let tool_kind = |slot| {
418 inv.equipped(slot).and_then(|i| {
419 if let ItemKind::Tool(tool) = &*i.kind() {
420 Some(tool.kind)
421 } else {
422 None
423 }
424 })
425 };
426 tool_kind(EquipSlot::ActiveMainhand) == tool_kind(EquipSlot::ActiveOffhand)
427 } else {
428 false
429 };
430 let combo = combo.map(|c| c.counter());
431
432 AbilityContext {
433 stance,
434 dual_wielding_same_kind,
435 combo,
436 }
437 }
438
439 fn fulfilled_by(&self, context: &AbilityContext) -> bool {
440 let AbilityContext {
441 stance,
442 dual_wielding_same_kind,
443 combo,
444 } = self;
445 let stance_check = stance.is_none_or(|s| context.stance == Some(s));
447 let dual_wield_check = !dual_wielding_same_kind || context.dual_wielding_same_kind;
449 let combo_check = combo.is_none_or(|c| context.combo.is_some_and(|c_c| c_c >= c));
451
452 stance_check && dual_wield_check && combo_check
453 }
454}
455
456impl AbilitySet<AbilityItem> {
457 #[must_use]
458 pub fn modified_by_tool(
459 self,
460 tool: &Tool,
461 durability_multiplier: DurabilityMultiplier,
462 ) -> Self {
463 self.map(|a| AbilityItem {
464 id: a.id,
465 ability: a
466 .ability
467 .adjusted_by_stats(tool.stats(durability_multiplier)),
468 })
469 }
470}
471
472impl<T> AbilitySet<T> {
473 pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilitySet<U> {
474 AbilitySet {
475 guard: self.guard.map(|g| g.map(&mut f)),
476 primary: self.primary.map(&mut f),
477 secondary: self.secondary.map(&mut f),
478 abilities: self.abilities.into_iter().map(|x| x.map(&mut f)).collect(),
479 }
480 }
481
482 pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilitySet<U> {
483 AbilitySet {
484 guard: self.guard.as_ref().map(|g| g.map_ref(&mut f)),
485 primary: self.primary.map_ref(&mut f),
486 secondary: self.secondary.map_ref(&mut f),
487 abilities: self.abilities.iter().map(|x| x.map_ref(&mut f)).collect(),
488 }
489 }
490
491 pub fn guard(
492 &self,
493 skillset: Option<&SkillSet>,
494 context: &AbilityContext,
495 ) -> Option<(&T, Option<ContextualIndex>)> {
496 self.guard
497 .as_ref()
498 .and_then(|g| g.ability(skillset, context))
499 }
500
501 pub fn primary(
502 &self,
503 skillset: Option<&SkillSet>,
504 context: &AbilityContext,
505 ) -> Option<(&T, Option<ContextualIndex>)> {
506 self.primary.ability(skillset, context)
507 }
508
509 pub fn secondary(
510 &self,
511 skillset: Option<&SkillSet>,
512 context: &AbilityContext,
513 ) -> Option<(&T, Option<ContextualIndex>)> {
514 self.secondary.ability(skillset, context)
515 }
516
517 pub fn auxiliary(
518 &self,
519 index: usize,
520 skillset: Option<&SkillSet>,
521 context: &AbilityContext,
522 ) -> Option<(&T, Option<ContextualIndex>)> {
523 self.abilities
524 .get(index)
525 .and_then(|a| a.ability(skillset, context))
526 }
527}
528
529impl Default for AbilitySet<AbilityItem> {
530 fn default() -> Self {
531 AbilitySet {
532 guard: None,
533 primary: AbilityKind::Simple(None, AbilityItem {
534 id: String::new(),
535 ability: CharacterAbility::default(),
536 }),
537 secondary: AbilityKind::Simple(None, AbilityItem {
538 id: String::new(),
539 ability: CharacterAbility::default(),
540 }),
541 abilities: Vec::new(),
542 }
543 }
544}
545
546#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
547pub enum AbilitySpec {
548 Tool(ToolKind),
549 Custom(String),
550}
551
552#[derive(Clone, Debug, Serialize, Deserialize)]
553pub struct AbilityItem {
554 pub id: String,
555 pub ability: CharacterAbility,
556}
557
558#[derive(Clone, Debug, Serialize, Deserialize)]
559pub struct AbilityMap<T = AbilityItem>(HashMap<AbilitySpec, AbilitySet<T>>);
560
561impl AbilityMap {
562 pub fn load() -> AssetHandle<Self> {
563 Self::load_expect("common.abilities.ability_set_manifest")
564 }
565}
566
567impl<T> AbilityMap<T> {
568 pub fn get_ability_set(&self, key: &AbilitySpec) -> Option<&AbilitySet<T>> { self.0.get(key) }
569}
570
571impl Asset for AbilityMap {
572 fn load(cache: &AssetCache, specifier: &SharedString) -> Result<Self, BoxedError> {
573 let manifest = cache.load::<Ron<AbilityMap<String>>>(specifier)?.read();
574
575 Ok(AbilityMap(
576 manifest
577 .0
578 .0
579 .iter()
580 .map(|(kind, set)| {
581 (
582 kind.clone(),
583 set.map_ref(|s| AbilityItem {
586 id: s.clone(),
587 ability: cache
588 .load_expect::<Ron<CharacterAbility>>(s)
589 .cloned()
590 .into_inner(),
591 }),
592 )
593 })
594 .collect(),
595 ))
596 }
597}