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::{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    // weapons
27    Sword,
28    Axe,
29    Hammer,
30    Bow,
31    Staff,
32    Sceptre,
33    // future weapons
34    Dagger,
35    Shield,
36    Spear,
37    Blowgun,
38    // tools
39    Debug,
40    Farming,
41    Pick,
42    Shovel,
43    /// Music Instruments
44    Instrument,
45    /// Throwable item
46    Throwable,
47    // npcs
48    /// Intended for invisible weapons (e.g. a creature using its claws or
49    /// biting)
50    Natural,
51    /// This is an placeholder item, it is used by non-humanoid npcs to attack
52    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    /// Calculates a diminished buff strength where the buff strength is clamped
173    /// by the power, and then excess buff strength above the power is added
174    /// with diminishing returns.
175    // TODO: Remove this later when there are more varied high tier materials.
176    // Mainly exists for now as a hack to allow some progression in strength of
177    // directly applied buffs.
178    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    // TODO: item specific abilities
282}
283
284impl Tool {
285    // DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
286    // Added for CSV import of stats
287    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/// The contextual index indicates which entry in a contextual ability was used.
328/// This should only be necessary for the frontend to distinguish between the
329/// options when a contextual ability is used.
330#[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            // If there is a skill requirement and the skillset does not contain the
373            // required skill, return None
374            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    /// Note, in this context `Stance::None` isn't intended to be used. e.g. the
401    /// stance field should be `None` instead of `Some(Stance::None)` in the
402    /// ability map config files(s).
403    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        // Either stance not required or context is in the same stance
446        let stance_check = stance.is_none_or(|s| context.stance == Some(s));
447        // Either dual wield not required or context is dual wielding
448        let dual_wield_check = !dual_wielding_same_kind || context.dual_wielding_same_kind;
449        // Either no minimum combo needed or context has sufficient combo
450        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                        // expect cannot fail because CharacterAbility always
584                        // provides a default value in case of failure
585                        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}