veloren_common/comp/inventory/item/
tool.rs

1// Note: If you changes here "break" old character saves you can change the
2// version in voxygen\src\meta.rs in order to reset save files to being empty
3
4use 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    // weapons
24    Sword,
25    Axe,
26    Hammer,
27    Bow,
28    Staff,
29    Sceptre,
30    // future weapons
31    Dagger,
32    Shield,
33    Spear,
34    Blowgun,
35    // tools
36    Debug,
37    Farming,
38    Pick,
39    Shovel,
40    /// Music Instruments
41    Instrument,
42    // npcs
43    /// Intended for invisible weapons (e.g. a creature using its claws or
44    /// biting)
45    Natural,
46    /// This is an placeholder item, it is used by non-humanoid npcs to attack
47    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    /// Calculates a diminished buff strength where the buff strength is clamped
166    /// by the power, and then excess buff strength above the power is added
167    /// with diminishing returns.
168    // TODO: Remove this later when there are more varied high tier materials.
169    // Mainly exists for now as a hack to allow some progression in strength of
170    // directly applied buffs.
171    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    // TODO: item specific abilities
281}
282
283impl Tool {
284    // DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
285    // Added for CSV import of stats
286    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/// The contextual index indicates which entry in a contextual ability was used.
327/// This should only be necessary for the frontend to distinguish between the
328/// options when a contextual ability is used.
329#[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            // If there is a skill requirement and the skillset does not contain the
372            // required skill, return None
373            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    /// Note, in this context `Stance::None` isn't intended to be used. e.g. the
400    /// stance field should be `None` instead of `Some(Stance::None)` in the
401    /// ability map config files(s).
402    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        // Either stance not required or context is in the same stance
445        let stance_check = stance.is_none_or(|s| context.stance == Some(s));
446        // Either dual wield not required or context is dual wielding
447        let dual_wield_check = !dual_wielding_same_kind || context.dual_wielding_same_kind;
448        // Either no minimum combo needed or context has sufficient combo
449        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                        // expect cannot fail because CharacterAbility always
591                        // provides a default value in case of failure
592                        set.map_ref(|s| AbilityItem {
593                            id: s.clone(),
594                            ability: cache.load_expect(s).cloned(),
595                        }),
596                    )
597                })
598                .collect(),
599        ))
600    }
601}