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};
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 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    // TODO: item specific abilities
288}
289
290impl Tool {
291    // DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
292    // Added for CSV import of stats
293    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/// The contextual index indicates which entry in a contextual ability was used.
334/// This should only be necessary for the frontend to distinguish between the
335/// options when a contextual ability is used.
336#[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            // If there is a skill requirement and the skillset does not contain the
379            // required skill, return None
380            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    /// Note, in this context `Stance::None` isn't intended to be used. e.g. the
407    /// stance field should be `None` instead of `Some(Stance::None)` in the
408    /// ability map config files(s).
409    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        // Either stance not required or context is in the same stance
452        let stance_check = stance.is_none_or(|s| context.stance == Some(s));
453        // Either dual wield not required or context is dual wielding
454        let dual_wield_check = !dual_wielding_same_kind || context.dual_wielding_same_kind;
455        // Either no minimum combo needed or context has sufficient combo
456        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                        // expect cannot fail because CharacterAbility always
598                        // provides a default value in case of failure
599                        set.map_ref(|s| AbilityItem {
600                            id: s.clone(),
601                            ability: cache.load_expect(s).cloned(),
602                        }),
603                    )
604                })
605                .collect(),
606        ))
607    }
608}