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;
21use tracing::warn;
22
23#[derive(
24    Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd, EnumIter,
25)]
26pub enum ToolKind {
27    // weapons
28    Sword,
29    Axe,
30    Hammer,
31    Bow,
32    Staff,
33    Sceptre,
34    // future weapons
35    Dagger,
36    Shield,
37    Spear,
38    Blowgun,
39    // tools
40    Debug,
41    Farming,
42    Pick,
43    Shovel,
44    /// Music Instruments
45    Instrument,
46    /// Throwable item
47    Throwable,
48    // npcs
49    /// Intended for invisible weapons (e.g. a creature using its claws or
50    /// biting)
51    Natural,
52    /// This is an placeholder item, it is used by non-humanoid npcs to attack
53    Empty,
54}
55
56impl ToolKind {
57    pub fn identifier_name(&self) -> &'static str {
58        match self {
59            ToolKind::Sword => "sword",
60            ToolKind::Axe => "axe",
61            ToolKind::Hammer => "hammer",
62            ToolKind::Bow => "bow",
63            ToolKind::Dagger => "dagger",
64            ToolKind::Staff => "staff",
65            ToolKind::Spear => "spear",
66            ToolKind::Blowgun => "blowgun",
67            ToolKind::Sceptre => "sceptre",
68            ToolKind::Shield => "shield",
69            ToolKind::Natural => "natural",
70            ToolKind::Debug => "debug",
71            ToolKind::Farming => "farming",
72            ToolKind::Pick => "pickaxe",
73            ToolKind::Shovel => "shovel",
74            ToolKind::Instrument => "instrument",
75            ToolKind::Throwable => "throwable",
76            ToolKind::Empty => "empty",
77        }
78    }
79
80    pub fn gains_combat_xp(&self) -> bool {
81        matches!(
82            self,
83            ToolKind::Sword
84                | ToolKind::Axe
85                | ToolKind::Hammer
86                | ToolKind::Bow
87                | ToolKind::Dagger
88                | ToolKind::Staff
89                | ToolKind::Spear
90                | ToolKind::Blowgun
91                | ToolKind::Sceptre
92                | ToolKind::Shield
93        )
94    }
95
96    pub fn can_block(&self) -> bool {
97        matches!(
98            self,
99            ToolKind::Sword
100                | ToolKind::Axe
101                | ToolKind::Hammer
102                | ToolKind::Shield
103                | ToolKind::Dagger
104        )
105    }
106
107    pub fn block_priority(&self) -> i32 {
108        match self {
109            ToolKind::Debug => 0,
110            ToolKind::Blowgun => 1,
111            ToolKind::Bow => 2,
112            ToolKind::Staff => 3,
113            ToolKind::Sceptre => 4,
114            ToolKind::Empty => 5,
115            ToolKind::Natural => 6,
116            ToolKind::Throwable => 7,
117            ToolKind::Instrument => 8,
118            ToolKind::Farming => 9,
119            ToolKind::Shovel => 10,
120            ToolKind::Pick => 11,
121            ToolKind::Dagger => 12,
122            ToolKind::Spear => 13,
123            ToolKind::Hammer => 14,
124            ToolKind::Axe => 15,
125            ToolKind::Sword => 16,
126            ToolKind::Shield => 17,
127        }
128    }
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
132pub enum Hands {
133    One,
134    Two,
135}
136
137#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
138pub struct Stats {
139    pub equip_time_secs: f32,
140    pub power: f32,
141    pub effect_power: f32,
142    pub speed: f32,
143    pub range: f32,
144    pub energy_efficiency: f32,
145    pub buff_strength: f32,
146}
147
148impl Stats {
149    pub fn zero() -> Stats {
150        Stats {
151            equip_time_secs: 0.0,
152            power: 0.0,
153            effect_power: 0.0,
154            speed: 0.0,
155            range: 0.0,
156            energy_efficiency: 0.0,
157            buff_strength: 0.0,
158        }
159    }
160
161    pub fn one() -> Stats {
162        Stats {
163            equip_time_secs: 1.0,
164            power: 1.0,
165            effect_power: 1.0,
166            speed: 1.0,
167            range: 1.0,
168            energy_efficiency: 1.0,
169            buff_strength: 1.0,
170        }
171    }
172
173    /// Calculates a diminished buff strength where the buff strength is clamped
174    /// by the power, and then excess buff strength above the power is added
175    /// with diminishing returns.
176    // TODO: Remove this later when there are more varied high tier materials.
177    // Mainly exists for now as a hack to allow some progression in strength of
178    // directly applied buffs.
179    pub fn diminished_buff_strength(&self) -> f32 {
180        let base = self.buff_strength.clamp(0.0, self.power);
181        let diminished = (self.buff_strength - base + 1.0).log(5.0);
182        base + diminished
183    }
184
185    pub fn with_durability_mult(&self, dur_mult: DurabilityMultiplier) -> Self {
186        let less_scaled = dur_mult.0 * 0.5 + 0.5;
187        Self {
188            equip_time_secs: self.equip_time_secs / less_scaled.max(0.01),
189            power: self.power * dur_mult.0,
190            effect_power: self.effect_power * dur_mult.0,
191            speed: self.speed * less_scaled,
192            range: self.range * less_scaled,
193            energy_efficiency: self.energy_efficiency * less_scaled,
194            buff_strength: self.buff_strength * dur_mult.0,
195        }
196    }
197}
198
199impl Add<Stats> for Stats {
200    type Output = Self;
201
202    fn add(self, other: Self) -> Self {
203        Self {
204            equip_time_secs: self.equip_time_secs + other.equip_time_secs,
205            power: self.power + other.power,
206            effect_power: self.effect_power + other.effect_power,
207            speed: self.speed + other.speed,
208            range: self.range + other.range,
209            energy_efficiency: self.energy_efficiency + other.energy_efficiency,
210            buff_strength: self.buff_strength + other.buff_strength,
211        }
212    }
213}
214
215impl AddAssign<Stats> for Stats {
216    fn add_assign(&mut self, other: Stats) { *self = *self + other; }
217}
218
219impl Sub<Stats> for Stats {
220    type Output = Self;
221
222    fn sub(self, other: Self) -> Self::Output {
223        Self {
224            equip_time_secs: self.equip_time_secs - other.equip_time_secs,
225            power: self.power - other.power,
226            effect_power: self.effect_power - other.effect_power,
227            speed: self.speed - other.speed,
228            range: self.range - other.range,
229            energy_efficiency: self.energy_efficiency - other.energy_efficiency,
230            buff_strength: self.buff_strength - other.buff_strength,
231        }
232    }
233}
234
235impl Mul<Stats> for Stats {
236    type Output = Self;
237
238    fn mul(self, other: Self) -> Self {
239        Self {
240            equip_time_secs: self.equip_time_secs * other.equip_time_secs,
241            power: self.power * other.power,
242            effect_power: self.effect_power * other.effect_power,
243            speed: self.speed * other.speed,
244            range: self.range * other.range,
245            energy_efficiency: self.energy_efficiency * other.energy_efficiency,
246            buff_strength: self.buff_strength * other.buff_strength,
247        }
248    }
249}
250
251impl MulAssign<Stats> for Stats {
252    fn mul_assign(&mut self, other: Stats) { *self = *self * other; }
253}
254
255impl Div<f32> for Stats {
256    type Output = Self;
257
258    fn div(self, scalar: f32) -> Self {
259        Self {
260            equip_time_secs: self.equip_time_secs / scalar,
261            power: self.power / scalar,
262            effect_power: self.effect_power / scalar,
263            speed: self.speed / scalar,
264            range: self.range / scalar,
265            energy_efficiency: self.energy_efficiency / scalar,
266            buff_strength: self.buff_strength / scalar,
267        }
268    }
269}
270
271impl Mul<DurabilityMultiplier> for Stats {
272    type Output = Self;
273
274    fn mul(self, value: DurabilityMultiplier) -> Self { self.with_durability_mult(value) }
275}
276
277#[derive(Clone, Debug, Serialize, Deserialize)]
278pub struct Tool {
279    pub kind: ToolKind,
280    pub hands: Hands,
281    stats: Stats,
282    // TODO: item specific abilities
283}
284
285impl Tool {
286    // DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
287    // Added for CSV import of stats
288    pub fn new(kind: ToolKind, hands: Hands, stats: Stats) -> Self { Self { kind, hands, stats } }
289
290    pub fn empty() -> Self {
291        Self {
292            kind: ToolKind::Empty,
293            hands: Hands::One,
294            stats: Stats {
295                equip_time_secs: 0.0,
296                power: 1.00,
297                effect_power: 1.00,
298                speed: 1.00,
299                range: 1.0,
300                energy_efficiency: 1.0,
301                buff_strength: 1.0,
302            },
303        }
304    }
305
306    pub fn stats(&self, durability_multiplier: DurabilityMultiplier) -> Stats {
307        self.stats * durability_multiplier
308    }
309}
310
311#[derive(Clone, Debug, Serialize, Deserialize)]
312pub struct AbilitySet<T> {
313    pub guard: Option<AbilityKind<T>>,
314    pub primary: AbilityKind<T>,
315    pub secondary: AbilityKind<T>,
316    pub abilities: Vec<AbilityKind<T>>,
317}
318
319#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
320pub enum AbilityKind<T> {
321    Simple(Option<Skill>, T),
322    Contextualized {
323        pseudo_id: String,
324        abilities: Vec<(AbilityContext, (Option<Skill>, T))>,
325    },
326}
327
328/// The contextual index indicates which entry in a contextual ability was used.
329/// This should only be necessary for the frontend to distinguish between the
330/// options when a contextual ability is used.
331#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq)]
332pub struct ContextualIndex(pub usize);
333
334impl<T> AbilityKind<T> {
335    pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilityKind<U> {
336        match self {
337            Self::Simple(s, x) => AbilityKind::<U>::Simple(s, f(x)),
338            Self::Contextualized {
339                pseudo_id,
340                abilities,
341            } => AbilityKind::<U>::Contextualized {
342                pseudo_id,
343                abilities: abilities
344                    .into_iter()
345                    .map(|(c, (s, x))| (c, (s, f(x))))
346                    .collect(),
347            },
348        }
349    }
350
351    pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilityKind<U> {
352        match self {
353            Self::Simple(s, x) => AbilityKind::<U>::Simple(*s, f(x)),
354            Self::Contextualized {
355                pseudo_id,
356                abilities,
357            } => AbilityKind::<U>::Contextualized {
358                pseudo_id: pseudo_id.clone(),
359                abilities: abilities
360                    .iter()
361                    .map(|(c, (s, x))| (*c, (*s, f(x))))
362                    .collect(),
363            },
364        }
365    }
366
367    pub fn ability(
368        &self,
369        skillset: Option<&SkillSet>,
370        context: &AbilityContext,
371    ) -> Option<(&T, Option<ContextualIndex>)> {
372        let unlocked = |s: Option<Skill>, a| {
373            // If there is a skill requirement and the skillset does not contain the
374            // required skill, return None
375            s.is_none_or(|s| skillset.is_some_and(|ss| ss.has_skill(s)))
376                .then_some(a)
377        };
378
379        match self {
380            AbilityKind::Simple(s, a) => unlocked(*s, a).map(|a| (a, None)),
381            AbilityKind::Contextualized {
382                pseudo_id: _,
383                abilities,
384            } => abilities
385                .iter()
386                .enumerate()
387                .filter_map(|(i, (req_contexts, (s, a)))| {
388                    unlocked(*s, a).map(|a| (i, (req_contexts, a)))
389                })
390                .find_map(|(i, (req_context, a))| {
391                    req_context
392                        .fulfilled_by(context)
393                        .then_some((a, Some(ContextualIndex(i))))
394                }),
395        }
396    }
397}
398
399#[derive(Clone, Debug, Serialize, Deserialize, Copy, Eq, PartialEq, Hash, Default)]
400pub struct AbilityContext {
401    /// Note, in this context `Stance::None` isn't intended to be used. e.g. the
402    /// stance field should be `None` instead of `Some(Stance::None)` in the
403    /// ability map config files(s).
404    pub stance: Option<Stance>,
405    #[serde(default)]
406    pub dual_wielding_same_kind: bool,
407    pub combo: Option<u32>,
408}
409
410impl AbilityContext {
411    pub fn from(stance: Option<&Stance>, inv: Option<&Inventory>, combo: Option<&Combo>) -> Self {
412        let stance = match stance {
413            Some(Stance::None) => None,
414            Some(stance) => Some(*stance),
415            None => None,
416        };
417        let dual_wielding_same_kind = if let Some(inv) = inv {
418            let tool_kind = |slot| {
419                inv.equipped(slot).and_then(|i| {
420                    if let ItemKind::Tool(tool) = &*i.kind() {
421                        Some(tool.kind)
422                    } else {
423                        None
424                    }
425                })
426            };
427            tool_kind(EquipSlot::ActiveMainhand) == tool_kind(EquipSlot::ActiveOffhand)
428        } else {
429            false
430        };
431        let combo = combo.map(|c| c.counter());
432
433        AbilityContext {
434            stance,
435            dual_wielding_same_kind,
436            combo,
437        }
438    }
439
440    fn fulfilled_by(&self, context: &AbilityContext) -> bool {
441        let AbilityContext {
442            stance,
443            dual_wielding_same_kind,
444            combo,
445        } = self;
446        // Either stance not required or context is in the same stance
447        let stance_check = stance.is_none_or(|s| context.stance == Some(s));
448        // Either dual wield not required or context is dual wielding
449        let dual_wield_check = !dual_wielding_same_kind || context.dual_wielding_same_kind;
450        // Either no minimum combo needed or context has sufficient combo
451        let combo_check = combo.is_none_or(|c| context.combo.is_some_and(|c_c| c_c >= c));
452
453        stance_check && dual_wield_check && combo_check
454    }
455}
456
457impl AbilitySet<AbilityItem> {
458    #[must_use]
459    pub fn modified_by_tool(
460        self,
461        tool: &Tool,
462        durability_multiplier: DurabilityMultiplier,
463    ) -> Self {
464        self.map(|a| AbilityItem {
465            id: a.id,
466            ability: a
467                .ability
468                .adjusted_by_stats(tool.stats(durability_multiplier)),
469        })
470    }
471}
472
473impl<T> AbilitySet<T> {
474    pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilitySet<U> {
475        AbilitySet {
476            guard: self.guard.map(|g| g.map(&mut f)),
477            primary: self.primary.map(&mut f),
478            secondary: self.secondary.map(&mut f),
479            abilities: self.abilities.into_iter().map(|x| x.map(&mut f)).collect(),
480        }
481    }
482
483    pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilitySet<U> {
484        AbilitySet {
485            guard: self.guard.as_ref().map(|g| g.map_ref(&mut f)),
486            primary: self.primary.map_ref(&mut f),
487            secondary: self.secondary.map_ref(&mut f),
488            abilities: self.abilities.iter().map(|x| x.map_ref(&mut f)).collect(),
489        }
490    }
491
492    pub fn guard(
493        &self,
494        skillset: Option<&SkillSet>,
495        context: &AbilityContext,
496    ) -> Option<(&T, Option<ContextualIndex>)> {
497        self.guard
498            .as_ref()
499            .and_then(|g| g.ability(skillset, context))
500    }
501
502    pub fn primary(
503        &self,
504        skillset: Option<&SkillSet>,
505        context: &AbilityContext,
506    ) -> Option<(&T, Option<ContextualIndex>)> {
507        self.primary.ability(skillset, context)
508    }
509
510    pub fn secondary(
511        &self,
512        skillset: Option<&SkillSet>,
513        context: &AbilityContext,
514    ) -> Option<(&T, Option<ContextualIndex>)> {
515        self.secondary.ability(skillset, context)
516    }
517
518    pub fn auxiliary(
519        &self,
520        index: usize,
521        skillset: Option<&SkillSet>,
522        context: &AbilityContext,
523    ) -> Option<(&T, Option<ContextualIndex>)> {
524        self.abilities
525            .get(index)
526            .and_then(|a| a.ability(skillset, context))
527    }
528}
529
530impl Default for AbilitySet<AbilityItem> {
531    fn default() -> Self {
532        AbilitySet {
533            guard: None,
534            primary: AbilityKind::Simple(None, AbilityItem {
535                id: String::new(),
536                ability: CharacterAbility::default(),
537            }),
538            secondary: AbilityKind::Simple(None, AbilityItem {
539                id: String::new(),
540                ability: CharacterAbility::default(),
541            }),
542            abilities: Vec::new(),
543        }
544    }
545}
546
547#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
548pub enum AbilitySpec {
549    Tool(ToolKind),
550    Custom(String),
551}
552
553#[derive(Clone, Debug, Serialize, Deserialize)]
554pub struct AbilityItem {
555    pub id: String,
556    pub ability: CharacterAbility,
557}
558
559#[derive(Clone, Debug, Serialize, Deserialize)]
560pub enum AbilityMapEntry<T = AbilityItem> {
561    AbilitySet(AbilitySet<T>),
562    AbilitySetOverride {
563        parent: AbilitySpec,
564        guard: Option<AbilityKind<T>>,
565        primary: Option<AbilityKind<T>>,
566        secondary: Option<AbilityKind<T>>,
567        added_abilities: Vec<AbilityKind<T>>,
568        removed_abilities: Vec<AbilityKind<T>>,
569    },
570}
571
572impl<T: Clone + Eq> AbilityMapEntry<T> {
573    pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilityMapEntry<U> {
574        match self {
575            AbilityMapEntry::AbilitySet(ability_set) => {
576                AbilityMapEntry::AbilitySet(ability_set.map_ref(f))
577            },
578            AbilityMapEntry::AbilitySetOverride {
579                parent,
580                guard,
581                primary,
582                secondary,
583                added_abilities,
584                removed_abilities,
585            } => AbilityMapEntry::AbilitySetOverride {
586                parent: parent.clone(),
587                guard: guard.as_ref().map(|g| g.map_ref(&mut f)),
588                primary: primary.as_ref().map(|p| p.map_ref(&mut f)),
589                secondary: secondary.as_ref().map(|s| s.map_ref(&mut f)),
590                added_abilities: added_abilities.iter().map(|x| x.map_ref(&mut f)).collect(),
591                removed_abilities: removed_abilities
592                    .iter()
593                    .map(|x| x.map_ref(&mut f))
594                    .collect(),
595            },
596        }
597    }
598
599    pub fn inherit(self, parent: &Self) -> Self {
600        match self {
601            AbilityMapEntry::AbilitySet(_) => self,
602            AbilityMapEntry::AbilitySetOverride {
603                guard,
604                primary,
605                secondary,
606                mut added_abilities,
607                mut removed_abilities,
608                ..
609            } => match parent {
610                AbilityMapEntry::AbilitySet(parent) => {
611                    added_abilities.extend(
612                        parent
613                            .abilities
614                            .iter()
615                            .filter(|x| !removed_abilities.contains(x))
616                            .cloned(),
617                    );
618
619                    AbilityMapEntry::AbilitySet(AbilitySet {
620                        guard: guard.or(parent.guard.clone()),
621                        primary: primary.unwrap_or(parent.primary.clone()),
622                        secondary: secondary.unwrap_or(parent.secondary.clone()),
623                        abilities: added_abilities,
624                    })
625                },
626                AbilityMapEntry::AbilitySetOverride {
627                    parent: p_parent,
628                    guard: p_guard,
629                    primary: p_primary,
630                    secondary: p_secondary,
631                    added_abilities: p_added_abilities,
632                    removed_abilities: p_removed_abilities,
633                } => {
634                    added_abilities.extend(
635                        p_added_abilities
636                            .iter()
637                            .filter(|x| !removed_abilities.contains(x))
638                            .cloned(),
639                    );
640                    removed_abilities.extend(
641                        p_removed_abilities
642                            .iter()
643                            .filter(|x| !added_abilities.contains(x))
644                            .cloned(),
645                    );
646
647                    AbilityMapEntry::AbilitySetOverride {
648                        parent: p_parent.clone(),
649                        guard: guard.or(p_guard.clone()),
650                        primary: primary.or(p_primary.clone()),
651                        secondary: secondary.or(p_secondary.clone()),
652                        added_abilities,
653                        removed_abilities,
654                    }
655                },
656            },
657        }
658    }
659}
660
661#[derive(Clone, Debug, Serialize, Deserialize)]
662pub struct AbilityMap<T = AbilityItem>(HashMap<AbilitySpec, AbilityMapEntry<T>>);
663
664impl AbilityMap {
665    pub fn load() -> AssetHandle<Self> {
666        Self::load_expect("common.abilities.ability_set_manifest")
667    }
668}
669
670impl<T> AbilityMap<T> {
671    pub fn get_ability_set(&self, key: &AbilitySpec) -> Option<&AbilitySet<T>> {
672        self.0.get(key).and_then(|entry| match entry {
673            AbilityMapEntry::AbilitySet(ability_set) => Some(ability_set),
674            AbilityMapEntry::AbilitySetOverride { .. } => None,
675        })
676    }
677}
678
679impl Asset for AbilityMap {
680    fn load(cache: &AssetCache, specifier: &SharedString) -> Result<Self, BoxedError> {
681        let mut ability_map = cache
682            .load::<Ron<AbilityMap<String>>>(specifier)?
683            .read()
684            .0
685            .0
686            .clone();
687
688        // Find child entries and inherit from their parent
689        while let Some((spec, mut entry)) = {
690            let spec = ability_map
691                .iter()
692                .find(|(_, entry)| matches!(entry, AbilityMapEntry::AbilitySetOverride { .. }))
693                .map(|(spec, _)| spec.clone());
694
695            spec.and_then(|spec| ability_map.remove_entry(&spec))
696        } {
697            let parent = if let AbilityMapEntry::AbilitySetOverride { parent, .. } = &entry {
698                Some(parent)
699            } else {
700                None
701            }
702            .and_then(|parent| ability_map.get(parent));
703
704            if let Some(parent) = parent {
705                entry = entry.inherit(parent);
706            }
707
708            ability_map.insert(spec, entry);
709        }
710
711        Ok(AbilityMap(
712            ability_map
713                .into_iter()
714                .map(|(kind, set)| {
715                    (
716                        kind.clone(),
717                        set.map_ref(|s| AbilityItem {
718                            id: s.clone(),
719                            ability: if let Ok(handle) = cache.load::<Ron<CharacterAbility>>(s) {
720                                handle.cloned().into_inner()
721                            } else {
722                                warn!(?s, "missing specified ability file");
723                                CharacterAbility::default()
724                            },
725                        }),
726                    )
727                })
728                .collect::<HashMap<_, _>>(),
729        ))
730    }
731}