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    /// Throwable item
43    Throwable,
44    // npcs
45    /// Intended for invisible weapons (e.g. a creature using its claws or
46    /// biting)
47    Natural,
48    /// This is an placeholder item, it is used by non-humanoid npcs to attack
49    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    /// Calculates a diminished buff strength where the buff strength is clamped
170    /// by the power, and then excess buff strength above the power is added
171    /// with diminishing returns.
172    // TODO: Remove this later when there are more varied high tier materials.
173    // Mainly exists for now as a hack to allow some progression in strength of
174    // directly applied buffs.
175    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    // TODO: item specific abilities
285}
286
287impl Tool {
288    // DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
289    // Added for CSV import of stats
290    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/// The contextual index indicates which entry in a contextual ability was used.
331/// This should only be necessary for the frontend to distinguish between the
332/// options when a contextual ability is used.
333#[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            // If there is a skill requirement and the skillset does not contain the
376            // required skill, return None
377            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    /// Note, in this context `Stance::None` isn't intended to be used. e.g. the
404    /// stance field should be `None` instead of `Some(Stance::None)` in the
405    /// ability map config files(s).
406    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        // Either stance not required or context is in the same stance
449        let stance_check = stance.is_none_or(|s| context.stance == Some(s));
450        // Either dual wield not required or context is dual wielding
451        let dual_wield_check = !dual_wielding_same_kind || context.dual_wielding_same_kind;
452        // Either no minimum combo needed or context has sufficient combo
453        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                        // expect cannot fail because CharacterAbility always
595                        // provides a default value in case of failure
596                        set.map_ref(|s| AbilityItem {
597                            id: s.clone(),
598                            ability: cache.load_expect(s).cloned(),
599                        }),
600                    )
601                })
602                .collect(),
603        ))
604    }
605}