Skip to main content

veloren_common/comp/inventory/item/
mod.rs

1pub mod armor;
2pub mod item_key;
3pub mod modular;
4pub mod tool;
5
6// Reexports
7pub use modular::{MaterialStatManifest, ModularBase, ModularComponent};
8pub use tool::{AbilityMap, AbilitySet, AbilitySpec, Hands, Tool, ToolKind};
9
10use crate::{
11    assets::{self, Asset, AssetCache, AssetExt, BoxedError, Error, Ron, SharedString},
12    comp::inventory::InvSlot,
13    effect::Effect,
14    lottery::LootSpec,
15    recipe::RecipeInput,
16    resources::ProgramTime,
17    terrain::{Block, sprite::SpriteCfg},
18};
19use common_i18n::Content;
20use core::{
21    convert::TryFrom,
22    mem,
23    num::{NonZeroU32, NonZeroU64},
24};
25use crossbeam_utils::atomic::AtomicCell;
26use hashbrown::{Equivalent, HashMap};
27use item_key::ItemKey;
28use serde::{Deserialize, Serialize, Serializer, de};
29use specs::{Component, DenseVecStorage, DerefFlaggedStorage};
30use std::{borrow::Cow, collections::hash_map::DefaultHasher, fmt, sync::Arc};
31use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
32use tracing::error;
33use vek::*;
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, strum::EnumString)]
36pub enum Reagent {
37    Blue,
38    Green,
39    Purple,
40    Red,
41    White,
42    Yellow,
43    FireRain,
44    FireGigas,
45    Earth,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
49pub enum Utility {
50    Coins,
51    Collar,
52    Key,
53    AbilityReq,
54}
55
56#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
57#[serde(deny_unknown_fields)]
58pub struct Lantern {
59    color: Rgb<u32>,
60    strength_thousandths: u32,
61    flicker_thousandths: u32,
62    pub dir: Option<(Vec3<f32>, f32)>,
63}
64
65impl Lantern {
66    pub fn strength(&self) -> f32 { self.strength_thousandths as f32 / 1000_f32 }
67
68    pub fn color(&self) -> Rgb<f32> { self.color.map(|c| c as f32 / 255.0) }
69
70    pub fn flicker(&self) -> f32 { self.flicker_thousandths as f32 / 1000_f32 }
71}
72
73#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
74pub enum Quality {
75    Low,       // Grey
76    Common,    // Light blue
77    Moderate,  // Green
78    High,      // Blue
79    Epic,      // Purple
80    Legendary, // Gold
81    Artifact,  // Orange
82    Debug,     // Red
83}
84
85impl Quality {
86    pub const MIN: Self = Self::Low;
87}
88
89pub trait TagExampleInfo {
90    fn name(&self) -> &str;
91    /// What item to show in the crafting hud if the player has nothing with the
92    /// tag
93    fn exemplar_identifier(&self) -> Option<&str>;
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr)]
97pub enum MaterialKind {
98    Metal,
99    Gem,
100    Wood,
101    Stone,
102    Cloth,
103    Hide,
104}
105
106#[derive(
107    Clone,
108    Copy,
109    Debug,
110    PartialEq,
111    Eq,
112    Hash,
113    Serialize,
114    Deserialize,
115    IntoStaticStr,
116    EnumString,
117    EnumIter,
118)]
119#[strum(serialize_all = "snake_case")]
120pub enum Material {
121    Bronze,
122    Iron,
123    Steel,
124    Cobalt,
125    Bloodsteel,
126    Silver,
127    Gold,
128    Orichalcum,
129    Topaz,
130    Emerald,
131    Sapphire,
132    Amethyst,
133    Ruby,
134    Diamond,
135    Twig,
136    PlantFiber,
137    Wood,
138    Bamboo,
139    Hardwood,
140    Ironwood,
141    Frostwood,
142    Eldwood,
143    Rock,
144    Granite,
145    Bone,
146    Basalt,
147    Obsidian,
148    Velorite,
149    Linen,
150    RedLinen,
151    Cotton,
152    Wool,
153    Silk,
154    Lifecloth,
155    Moonweave,
156    Sunsilk,
157    Rawhide,
158    Leather,
159    RigidLeather,
160    Scale,
161    Carapace,
162    Serpentscale,
163    Plate,
164    Dragonscale,
165}
166
167impl Material {
168    pub fn material_kind(&self) -> MaterialKind {
169        match self {
170            Material::Bronze
171            | Material::Iron
172            | Material::Steel
173            | Material::Cobalt
174            | Material::Bloodsteel
175            | Material::Silver
176            | Material::Gold
177            | Material::Orichalcum => MaterialKind::Metal,
178            Material::Topaz
179            | Material::Emerald
180            | Material::Sapphire
181            | Material::Amethyst
182            | Material::Ruby
183            | Material::Diamond => MaterialKind::Gem,
184            Material::Wood
185            | Material::Twig
186            | Material::PlantFiber
187            | Material::Bamboo
188            | Material::Hardwood
189            | Material::Ironwood
190            | Material::Frostwood
191            | Material::Eldwood => MaterialKind::Wood,
192            Material::Rock
193            | Material::Granite
194            | Material::Bone
195            | Material::Basalt
196            | Material::Obsidian
197            | Material::Velorite => MaterialKind::Stone,
198            Material::Linen
199            | Material::RedLinen
200            | Material::Cotton
201            | Material::Wool
202            | Material::Silk
203            | Material::Lifecloth
204            | Material::Moonweave
205            | Material::Sunsilk => MaterialKind::Cloth,
206            Material::Rawhide
207            | Material::Leather
208            | Material::RigidLeather
209            | Material::Scale
210            | Material::Carapace
211            | Material::Serpentscale
212            | Material::Plate
213            | Material::Dragonscale => MaterialKind::Hide,
214        }
215    }
216
217    pub fn asset_identifier(&self) -> Option<&'static str> {
218        match self {
219            Material::Bronze => Some("common.items.mineral.ingot.bronze"),
220            Material::Iron => Some("common.items.mineral.ingot.iron"),
221            Material::Steel => Some("common.items.mineral.ingot.steel"),
222            Material::Cobalt => Some("common.items.mineral.ingot.cobalt"),
223            Material::Bloodsteel => Some("common.items.mineral.ingot.bloodsteel"),
224            Material::Silver => Some("common.items.mineral.ingot.silver"),
225            Material::Gold => Some("common.items.mineral.ingot.gold"),
226            Material::Orichalcum => Some("common.items.mineral.ingot.orichalcum"),
227            Material::Topaz => Some("common.items.mineral.gem.topaz"),
228            Material::Emerald => Some("common.items.mineral.gem.emerald"),
229            Material::Sapphire => Some("common.items.mineral.gem.sapphire"),
230            Material::Amethyst => Some("common.items.mineral.gem.amethyst"),
231            Material::Ruby => Some("common.items.mineral.gem.ruby"),
232            Material::Diamond => Some("common.items.mineral.gem.diamond"),
233            Material::Twig => Some("common.items.crafting_ing.twigs"),
234            Material::PlantFiber => Some("common.items.flowers.plant_fiber"),
235            Material::Wood => Some("common.items.log.wood"),
236            Material::Bamboo => Some("common.items.log.bamboo"),
237            Material::Hardwood => Some("common.items.log.hardwood"),
238            Material::Ironwood => Some("common.items.log.ironwood"),
239            Material::Frostwood => Some("common.items.log.frostwood"),
240            Material::Eldwood => Some("common.items.log.eldwood"),
241            Material::Rock
242            | Material::Granite
243            | Material::Bone
244            | Material::Basalt
245            | Material::Obsidian
246            | Material::Velorite => None,
247            Material::Linen => Some("common.items.crafting_ing.cloth.linen"),
248            Material::RedLinen => Some("common.items.crafting_ing.cloth.linen_red"),
249            Material::Cotton => Some("common.items.crafting_ing.cloth.cotton"),
250            Material::Wool => Some("common.items.crafting_ing.cloth.wool"),
251            Material::Silk => Some("common.items.crafting_ing.cloth.silk"),
252            Material::Lifecloth => Some("common.items.crafting_ing.cloth.lifecloth"),
253            Material::Moonweave => Some("common.items.crafting_ing.cloth.moonweave"),
254            Material::Sunsilk => Some("common.items.crafting_ing.cloth.sunsilk"),
255            Material::Rawhide => Some("common.items.crafting_ing.leather.simple_leather"),
256            Material::Leather => Some("common.items.crafting_ing.leather.thick_leather"),
257            Material::RigidLeather => Some("common.items.crafting_ing.leather.rigid_leather"),
258            Material::Scale => Some("common.items.crafting_ing.hide.scales"),
259            Material::Carapace => Some("common.items.crafting_ing.hide.carapace"),
260            Material::Serpentscale => Some("common.items.crafting_ing.hide.serpent_scale"),
261            Material::Plate => Some("common.items.crafting_ing.hide.plate"),
262            Material::Dragonscale => Some("common.items.crafting_ing.hide.dragon_scale"),
263        }
264    }
265}
266
267impl TagExampleInfo for Material {
268    fn name(&self) -> &str { self.into() }
269
270    fn exemplar_identifier(&self) -> Option<&str> { self.asset_identifier() }
271}
272
273#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
274pub enum ItemTag {
275    /// Used to indicate that an item is composed of this material
276    Material(Material),
277    /// Used to indicate that an item is composed of this material kind
278    MaterialKind(MaterialKind),
279    Cultist,
280    Gnarling,
281    Potion,
282    Charm,
283    Food,
284    BaseMaterial, // Cloth-scraps, Leather...
285    CraftingTool, // Pickaxe, Craftsman-Hammer, Sewing-Set
286    Utility,
287    Bag,
288    SalvageInto(Material, u32),
289    Witch,
290    Pirate,
291}
292
293impl TagExampleInfo for ItemTag {
294    fn name(&self) -> &str {
295        match self {
296            ItemTag::Material(material) => material.name(),
297            ItemTag::MaterialKind(material_kind) => material_kind.into(),
298            ItemTag::Cultist => "cultist",
299            ItemTag::Gnarling => "gnarling",
300            ItemTag::Potion => "potion",
301            ItemTag::Charm => "charm",
302            ItemTag::Food => "food",
303            ItemTag::BaseMaterial => "basemat",
304            ItemTag::CraftingTool => "tool",
305            ItemTag::Utility => "utility",
306            ItemTag::Bag => "bag",
307            ItemTag::SalvageInto(_, _) => "salvage",
308            ItemTag::Witch => "witch",
309            ItemTag::Pirate => "pirate",
310        }
311    }
312
313    // TODO: Autogenerate these?
314    fn exemplar_identifier(&self) -> Option<&str> {
315        match self {
316            ItemTag::Material(material) => material.exemplar_identifier(),
317            ItemTag::Cultist => Some("common.items.tag_examples.cultist"),
318            ItemTag::Gnarling => Some("common.items.tag_examples.gnarling"),
319            ItemTag::Witch => Some("common.items.tag_examples.witch"),
320            ItemTag::Pirate => Some("common.items.tag_examples.pirate"),
321            ItemTag::MaterialKind(_)
322            | ItemTag::Potion
323            | ItemTag::Food
324            | ItemTag::Charm
325            | ItemTag::BaseMaterial
326            | ItemTag::CraftingTool
327            | ItemTag::Utility
328            | ItemTag::Bag
329            | ItemTag::SalvageInto(_, _) => None,
330        }
331    }
332}
333
334#[derive(Clone, Debug, Serialize, Deserialize)]
335pub enum Effects {
336    Any(Vec<Effect>),
337    All(Vec<Effect>),
338    One(Effect),
339}
340
341impl Effects {
342    pub fn effects(&self) -> &[Effect] {
343        match self {
344            Effects::Any(effects) => effects,
345            Effects::All(effects) => effects,
346            Effects::One(effect) => std::slice::from_ref(effect),
347        }
348    }
349}
350
351#[derive(Clone, Debug, Serialize, Deserialize)]
352#[serde(deny_unknown_fields)]
353pub enum ItemKind {
354    /// Something wieldable
355    Tool(Tool),
356    ModularComponent(ModularComponent),
357    Lantern(Lantern),
358    Armor(armor::Armor),
359    Glider,
360    Consumable {
361        kind: ConsumableKind,
362        effects: Effects,
363        #[serde(default)]
364        container: Option<ItemDefinitionIdOwned>,
365    },
366    Utility {
367        kind: Utility,
368    },
369    Ingredient {
370        /// Used to generate names for modular items composed of this ingredient
371        // I think we can actually remove it now?
372        #[deprecated = "since item i18n"]
373        descriptor: String,
374    },
375    TagExamples {
376        /// A list of item names to lookup the appearences of and animate
377        /// through
378        item_ids: Vec<String>,
379    },
380    RecipeGroup {
381        recipes: Vec<String>,
382    },
383    Quest,
384}
385
386#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
387pub enum ConsumableKind {
388    Drink,
389    Food,
390    ComplexFood,
391    Charm,
392    Recipe,
393}
394
395impl ItemKind {
396    pub fn is_equippable(&self) -> bool {
397        matches!(
398            self,
399            ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Glider | ItemKind::Lantern(_)
400        )
401    }
402
403    // Used for inventory sorting, what comes before the first colon (:) is used as
404    // a broader category
405    pub fn get_itemkind_string(&self) -> String {
406        match self {
407            // Using tool and toolkind to sort tools by kind
408            ItemKind::Tool(tool) => format!("Tool: {:?}", tool.kind),
409            ItemKind::ModularComponent(modular_component) => {
410                format!("ModularComponent: {:?}", modular_component.toolkind())
411            },
412            ItemKind::Lantern(lantern) => format!("Lantern: {:?}", lantern),
413            ItemKind::Armor(armor) => format!("Armor: {:?}", armor.stats),
414            ItemKind::Glider => "Glider:".to_string(),
415            ItemKind::Consumable { kind, .. } => {
416                format!("Consumable: {:?}", kind)
417            },
418            ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
419            #[expect(deprecated)]
420            ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
421            ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids),
422            ItemKind::RecipeGroup { .. } => String::from("Recipes:"),
423            ItemKind::Quest => String::from("Quest:"),
424        }
425    }
426
427    pub fn has_durability(&self) -> bool {
428        match self {
429            ItemKind::Tool(Tool { kind, .. }) => !matches!(kind, ToolKind::Throwable),
430            ItemKind::Armor(armor) => armor.kind.has_durability(),
431            ItemKind::ModularComponent(_)
432            | ItemKind::Lantern(_)
433            | ItemKind::Quest
434            | ItemKind::Glider
435            | ItemKind::Consumable { .. }
436            | ItemKind::Utility { .. }
437            | ItemKind::Ingredient { .. }
438            | ItemKind::TagExamples { .. }
439            | ItemKind::RecipeGroup { .. } => false,
440        }
441    }
442}
443
444pub type ItemId = AtomicCell<Option<NonZeroU64>>;
445
446/* /// The only way to access an item id outside this module is to mutably, atomically update it using
447/// this structure.  It has a single method, `try_assign_id`, which attempts to set the id if and
448/// only if it's not already set.
449pub struct CreateDatabaseItemId {
450    item_id: Arc<ItemId>,
451}*/
452
453/// NOTE: Do not call `Item::clone` without consulting the core devs!  It only
454/// exists due to being required for message serialization at the moment, and
455/// should not be used for any other purpose.
456///
457/// FIXME: Turn on a Clippy lint forbidding the use of `Item::clone` using the
458/// `disallowed_method` feature.
459#[derive(Clone, Debug, Serialize, Deserialize)]
460pub struct Item {
461    /// item_id is hidden because it represents the persistent, storage entity
462    /// ID for any item that has been saved to the database.  Additionally,
463    /// it (currently) holds interior mutable state, making it very
464    /// dangerous to expose.  We will work to eliminate this issue soon; for
465    /// now, we try to make the system as foolproof as possible by greatly
466    /// restricting opportunities for cloning the item_id.
467    #[serde(skip)]
468    item_id: Arc<ItemId>,
469    /// item_def is hidden because changing the item definition for an item
470    /// could change invariants like whether it was stackable (invalidating
471    /// the amount).
472    item_base: ItemBase,
473    /// components is hidden to maintain the following invariants:
474    /// - It should only contain modular components (and enhancements, once they
475    ///   exist)
476    /// - Enhancements (once they exist) should be compatible with the available
477    ///   slot shapes
478    /// - Modular components should agree with the tool kind
479    /// - There should be exactly one damage component and exactly one held
480    ///   component for modular weapons
481    components: Vec<Item>,
482    /// amount is hidden because it needs to maintain the invariant that only
483    /// stackable items can have > 1 amounts.
484    amount: NonZeroU32,
485    /// The slots for items that this item has
486    slots: Vec<InvSlot>,
487    item_config: Option<Box<ItemConfig>>,
488    hash: u64,
489    /// Tracks how many deaths occurred while item was equipped, which is
490    /// converted into the items durability. Only tracked for tools and armor
491    /// currently.
492    durability_lost: Option<u32>,
493}
494
495/// Newtype around [`Item`] used for frontend events to prevent it accidentally
496/// being used for anything other than frontend events
497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
498pub struct FrontendItem(Item);
499
500// An item that is dropped into the world an can be picked up. It can stack with
501// other items of the same type regardless of the stack limit, when picked up
502// the last item from the list is popped
503//
504// NOTE: Never call PickupItem::clone, it is only used for network
505// synchronization
506//
507// Invariants:
508//  - Any item that is not the last one must have an amount equal to its
509//    `max_amount()`
510//  - All items must be equal and have a zero amount of slots
511//  - The Item list must not be empty
512#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct PickupItem {
514    items: Vec<Item>,
515    /// This [`ProgramTime`] only makes sense on the server
516    created_at: ProgramTime,
517    /// This [`ProgramTime`] only makes sense on the server
518    next_merge_check: ProgramTime,
519    /// When set to `true`, this item will actively try to be merged into nearby
520    /// items of the same kind (see [`Item::can_merge`]). Currently only used
521    /// for inventory dropped items to prevent entity DoS.
522    pub should_merge: bool,
523}
524
525/// Newtype around [`Item`] so that thrown projectiles can track which item
526/// they represent
527#[derive(Debug, Clone, Serialize, Deserialize)]
528pub struct ThrownItem(pub Item);
529
530use std::hash::{Hash, Hasher};
531
532// Used to find inventory item corresponding to hotbar slot
533impl Hash for Item {
534    fn hash<H: Hasher>(&self, state: &mut H) {
535        self.item_definition_id().hash(state);
536        self.components.iter().for_each(|comp| comp.hash(state));
537    }
538}
539
540// at the time of writing, we use Fluent, which supports attributes
541// and we can get both name and description using them
542type I18nId = String;
543
544#[derive(Clone, Debug, Serialize, Deserialize)]
545// TODO: probably make a Resource if used outside of voxygen
546// TODO: add hot-reloading similar to how ItemImgs does it?
547// TODO: make it work with plugins (via Concatenate?)
548/// To be used with ItemDesc::i18n
549///
550/// NOTE: there is a limitation to this manifest, as it uses ItemKey and
551/// ItemKey isn't uniquely identifies Item, when it comes to modular items.
552///
553/// If modular weapon has the same primary component and the same hand-ness,
554/// we use the same model EVEN IF it has different secondary components, like
555/// Staff with Heavy core or Light core.
556///
557/// Translations currently do the same, but *maybe* they shouldn't in which case
558/// we should either extend ItemKey or use new identifier. We could use
559/// ItemDefinitionId, but it's very generic and cumbersome.
560pub struct ItemI18n {
561    /// maps ItemKey to i18n identifier
562    map: HashMap<ItemKey, I18nId>,
563    /// maps FragmentKey to i18n identifier
564    ///
565    /// Used for optional templating for languages that can stomach them
566    fragments: HashMap<FragmentKey, I18nId>,
567}
568
569#[derive(Hash, Eq, PartialEq, Debug, Clone, Deserialize, Serialize)]
570pub enum FragmentKey {
571    // path to ingredient
572    Ingredient(String),
573    // path to primary component and hand-ness required
574    WeaponPrimaryComponent(String, Hands),
575}
576
577impl ItemI18n {
578    pub fn new_expect() -> Self {
579        Ron::load_expect("common.item_i18n_manifest")
580            .read()
581            .clone()
582            .into_inner()
583    }
584
585    /// Returns (name, description) in Content form.
586    // TODO: after we remove legacy text from ItemDef, consider making this
587    // function non-fallible?
588    fn item_text_opt(&self, item_key: &ItemKey) -> Option<(Content, Content)> {
589        let key = self.try_key(item_key);
590        key.map(|key| {
591            (
592                Content::Key(key.to_owned()),
593                Content::Attr(key.to_owned(), "desc".to_owned()),
594            )
595        })
596    }
597
598    /// Tries to fetch a fragment from i18n manifest
599    // TODO: potentially should just return a string as well?
600    fn try_fragment(&self, fragment_key: &FragmentKey) -> Option<Content> {
601        self.fragments
602            .get(fragment_key)
603            .map(|key| Content::Key(key.to_owned()))
604    }
605
606    /// Tries to fetch a key from i18n manifest, returns a i18n string,
607    /// do with it what you need.
608    fn try_key(&self, item_key: &ItemKey) -> Option<&I18nId> {
609        // We don't put TagExamples into manifest.
610        // Instead they are marked as Simple.
611        let key;
612        let item_key = if let ItemKey::TagExamples(_, id) = item_key {
613            key = ItemKey::Simple(id.to_string());
614            &key
615        } else {
616            item_key
617        };
618
619        self.map.get(item_key)
620    }
621
622    /// Returns all fragments, mainly for testing
623    pub fn all_fragments(&self) -> impl Iterator<Item = (&FragmentKey, &I18nId)> {
624        self.fragments.iter()
625    }
626}
627
628#[derive(Clone, Debug)]
629pub enum ItemBase {
630    Simple(Arc<ItemDef>),
631    Modular(ModularBase),
632}
633
634impl Serialize for ItemBase {
635    // Custom serialization for ItemDef, we only want to send the item_definition_id
636    // over the network, the client will use deserialize_item_def to fetch the
637    // ItemDef from assets.
638    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
639    where
640        S: Serializer,
641    {
642        serializer.serialize_str(&self.serialization_item_id())
643    }
644}
645
646impl<'de> Deserialize<'de> for ItemBase {
647    // Custom de-serialization for ItemBase to retrieve the ItemBase from assets
648    // using its asset specifier (item_definition_id)
649    fn deserialize<D>(deserializer: D) -> Result<ItemBase, D::Error>
650    where
651        D: de::Deserializer<'de>,
652    {
653        struct ItemBaseStringVisitor;
654
655        impl de::Visitor<'_> for ItemBaseStringVisitor {
656            type Value = ItemBase;
657
658            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
659                formatter.write_str("item def string")
660            }
661
662            fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
663            where
664                E: de::Error,
665            {
666                ItemBase::from_item_id_string(serialized_item_base)
667                    .map_err(|err| E::custom(err.to_string()))
668            }
669        }
670
671        deserializer.deserialize_str(ItemBaseStringVisitor)
672    }
673}
674
675impl ItemBase {
676    fn num_slots(&self) -> u16 {
677        match self {
678            ItemBase::Simple(item_def) => item_def.num_slots(),
679            ItemBase::Modular(_) => 0,
680        }
681    }
682
683    // Should be kept the same as the persistence_item_id function in Item
684    // TODO: Maybe use Cow?
685    fn serialization_item_id(&self) -> String {
686        match &self {
687            ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
688            ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
689        }
690    }
691
692    fn from_item_id_string(item_id_string: &str) -> Result<Self, Error> {
693        if item_id_string.starts_with(crate::modular_item_id_prefix!()) {
694            Ok(ItemBase::Modular(ModularBase::load_from_pseudo_id(
695                item_id_string,
696            )))
697        } else {
698            Ok(ItemBase::Simple(Arc::<ItemDef>::load_cloned(
699                item_id_string,
700            )?))
701        }
702    }
703}
704
705// TODO: could this theorectically hold a ref to the actual components and
706// lazily get their IDs for hash/partialeq/debug/to_owned/etc? (i.e. eliminating
707// `Vec`s)
708#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
709pub enum ItemDefinitionId<'a> {
710    Simple(Cow<'a, str>),
711    Modular {
712        pseudo_base: &'a str,
713        components: Vec<ItemDefinitionId<'a>>,
714    },
715    Compound {
716        simple_base: &'a str,
717        components: Vec<ItemDefinitionId<'a>>,
718    },
719}
720
721#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
722pub enum ItemDefinitionIdOwned {
723    Simple(String),
724    Modular {
725        pseudo_base: String,
726        components: Vec<ItemDefinitionIdOwned>,
727    },
728    Compound {
729        simple_base: String,
730        components: Vec<ItemDefinitionIdOwned>,
731    },
732}
733
734impl ItemDefinitionIdOwned {
735    pub fn as_ref(&self) -> ItemDefinitionId<'_> {
736        match *self {
737            Self::Simple(ref id) => ItemDefinitionId::Simple(Cow::Borrowed(id)),
738            Self::Modular {
739                ref pseudo_base,
740                ref components,
741            } => ItemDefinitionId::Modular {
742                pseudo_base,
743                components: components.iter().map(|comp| comp.as_ref()).collect(),
744            },
745            Self::Compound {
746                ref simple_base,
747                ref components,
748            } => ItemDefinitionId::Compound {
749                simple_base,
750                components: components.iter().map(|comp| comp.as_ref()).collect(),
751            },
752        }
753    }
754}
755
756impl ItemDefinitionId<'_> {
757    pub fn itemdef_id(&self) -> Option<&str> {
758        match self {
759            Self::Simple(id) => Some(id),
760            Self::Modular { .. } => None,
761            Self::Compound { simple_base, .. } => Some(simple_base),
762        }
763    }
764
765    pub fn to_owned(&self) -> ItemDefinitionIdOwned {
766        match self {
767            Self::Simple(id) => ItemDefinitionIdOwned::Simple(String::from(&**id)),
768            Self::Modular {
769                pseudo_base,
770                components,
771            } => ItemDefinitionIdOwned::Modular {
772                pseudo_base: String::from(*pseudo_base),
773                components: components.iter().map(|comp| comp.to_owned()).collect(),
774            },
775            Self::Compound {
776                simple_base,
777                components,
778            } => ItemDefinitionIdOwned::Compound {
779                simple_base: String::from(*simple_base),
780                components: components.iter().map(|comp| comp.to_owned()).collect(),
781            },
782        }
783    }
784}
785
786#[derive(Debug, Serialize, Deserialize)]
787pub struct ItemDef {
788    #[serde(default)]
789    /// The string that refers to the filepath to the asset, relative to the
790    /// assets folder, which the ItemDef is loaded from. The name space
791    /// prepended with `veloren.core` is reserved for veloren functions.
792    item_definition_id: String,
793    #[deprecated = "since item i18n"]
794    legacy_name: String,
795    pub kind: ItemKind,
796    pub quality: Quality,
797    pub tags: Vec<ItemTag>,
798    #[serde(default)]
799    pub slots: u16,
800    /// Used to specify a custom ability set for a weapon. Leave None (or don't
801    /// include field in ItemDef) to use default ability set for weapon kind.
802    pub ability_spec: Option<AbilitySpec>,
803}
804
805impl PartialEq for ItemDef {
806    fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
807}
808
809// TODO: Look into removing ItemConfig and just using AbilitySet
810#[derive(Clone, Debug, Serialize, Deserialize)]
811pub struct ItemConfig {
812    pub abilities: AbilitySet<tool::AbilityItem>,
813}
814
815#[derive(Debug)]
816pub enum ItemConfigError {
817    BadItemKind,
818}
819
820impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
821    type Error = ItemConfigError;
822
823    fn try_from(
824        // TODO: Either remove msm or use it as argument in fn kind
825        (item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
826    ) -> Result<Self, Self::Error> {
827        match &*item.kind() {
828            ItemKind::Tool(tool) => {
829                // If no custom ability set is specified, fall back to abilityset of tool kind.
830                let tool_default = |tool_kind| {
831                    let key = &AbilitySpec::Tool(tool_kind);
832                    ability_map.get_ability_set(key)
833                };
834                let abilities = if let Some(set_key) = item.ability_spec() {
835                    if let Some(set) = ability_map.get_ability_set(&set_key) {
836                        set.clone()
837                            .modified_by_tool(tool, item.stats_durability_multiplier())
838                    } else {
839                        error!(
840                            "Custom ability set: {:?} references non-existent set, falling back \
841                             to default ability set.",
842                            set_key
843                        );
844                        tool_default(tool.kind).cloned().unwrap_or_default()
845                    }
846                } else if let Some(set) = tool_default(tool.kind) {
847                    set.clone()
848                        .modified_by_tool(tool, item.stats_durability_multiplier())
849                } else {
850                    error!(
851                        "No ability set defined for tool: {:?}, falling back to default ability \
852                         set.",
853                        tool.kind
854                    );
855                    Default::default()
856                };
857
858                Ok(ItemConfig { abilities })
859            },
860            ItemKind::Glider => item
861                .ability_spec()
862                .and_then(|set_key| ability_map.get_ability_set(&set_key))
863                .map(|abilities| ItemConfig {
864                    abilities: abilities.clone(),
865                })
866                .ok_or(ItemConfigError::BadItemKind),
867            _ => Err(ItemConfigError::BadItemKind),
868        }
869    }
870}
871
872impl ItemDef {
873    pub fn is_stackable(&self) -> bool {
874        matches!(
875            self.kind,
876            ItemKind::Consumable { .. }
877                | ItemKind::Quest
878                | ItemKind::Ingredient { .. }
879                | ItemKind::Utility { .. }
880                | ItemKind::Tool(Tool {
881                    kind: ToolKind::Throwable,
882                    ..
883                })
884        )
885    }
886
887    /// NOTE: invariant that amount() ≤ max_amount(), 1 ≤ max_amount(),
888    /// and if !self.is_stackable(), self.max_amount() = 1.
889    pub fn max_amount(&self) -> u32 { if self.is_stackable() { u32::MAX } else { 1 } }
890
891    // currently needed by trade_pricing
892    pub fn id(&self) -> &str { &self.item_definition_id }
893
894    #[cfg(test)]
895    pub fn new_test(
896        item_definition_id: String,
897        kind: ItemKind,
898        quality: Quality,
899        tags: Vec<ItemTag>,
900        slots: u16,
901    ) -> Self {
902        #[expect(deprecated)]
903        Self {
904            item_definition_id,
905            legacy_name: "test item name".to_owned(),
906            kind,
907            quality,
908            tags,
909            slots,
910            ability_spec: None,
911        }
912    }
913
914    #[cfg(test)]
915    pub fn create_test_itemdef_from_kind(kind: ItemKind) -> Self {
916        #[expect(deprecated)]
917        Self {
918            item_definition_id: "test.item".to_string(),
919            legacy_name: "test item name".to_owned(),
920            kind,
921            quality: Quality::Common,
922            tags: vec![],
923            slots: 0,
924            ability_spec: None,
925        }
926    }
927}
928
929/// NOTE: This PartialEq instance is pretty broken!  It doesn't check item
930/// amount or any child items (and, arguably, doing so should be able to ignore
931/// things like item order within the main inventory or within each bag, and
932/// possibly even coalesce amounts, though these may be more controversial).
933/// Until such time as we find an actual need for a proper PartialEq instance,
934/// please don't rely on this for anything!
935impl PartialEq for Item {
936    fn eq(&self, other: &Self) -> bool {
937        (match (&self.item_base, &other.item_base) {
938            (ItemBase::Simple(our_def), ItemBase::Simple(other_def)) => {
939                our_def.item_definition_id == other_def.item_definition_id
940            },
941            (ItemBase::Modular(our_base), ItemBase::Modular(other_base)) => our_base == other_base,
942            _ => false,
943        }) && self.components() == other.components()
944    }
945}
946
947impl Asset for ItemDef {
948    fn load(cache: &AssetCache, specifier: &SharedString) -> Result<Self, BoxedError> {
949        if specifier.starts_with("veloren.core.") {
950            return Err(format!(
951                "Attempted to load an asset from a specifier reserved for core veloren functions. \
952                 Specifier: {}",
953                specifier
954            )
955            .into());
956        }
957
958        let RawItemDef {
959            legacy_name,
960            legacy_description: _,
961            kind,
962            quality,
963            tags,
964            slots,
965            ability_spec,
966        } = cache.load::<Ron<_>>(specifier)?.cloned().into_inner();
967
968        // Some commands like /give_item provide the asset specifier separated with \
969        // instead of .
970        //
971        // TODO: This probably does not belong here
972        let item_definition_id = specifier.replace('\\', ".");
973
974        Ok(ItemDef {
975            item_definition_id,
976            #[expect(deprecated)]
977            legacy_name,
978            kind,
979            quality,
980            tags,
981            slots,
982            ability_spec,
983        })
984    }
985}
986
987#[derive(Clone, Debug, Serialize, Deserialize)]
988#[serde(rename = "ItemDef", deny_unknown_fields)]
989struct RawItemDef {
990    legacy_name: String,
991    legacy_description: String,
992    kind: ItemKind,
993    quality: Quality,
994    tags: Vec<ItemTag>,
995    #[serde(default)]
996    slots: u16,
997    ability_spec: Option<AbilitySpec>,
998}
999
1000#[derive(Debug)]
1001pub struct OperationFailure;
1002
1003impl Item {
1004    pub const MAX_DURABILITY: u32 = 12;
1005
1006    // TODO: consider alternatives such as default abilities that can be added to a
1007    // loadout when no weapon is present
1008    pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
1009
1010    pub fn new_from_item_base(
1011        inner_item: ItemBase,
1012        components: Vec<Item>,
1013        ability_map: &AbilityMap,
1014        msm: &MaterialStatManifest,
1015    ) -> Self {
1016        let mut item = Item {
1017            item_id: Arc::new(AtomicCell::new(None)),
1018            amount: NonZeroU32::new(1).unwrap(),
1019            components,
1020            slots: vec![None; inner_item.num_slots() as usize],
1021            item_base: inner_item,
1022            // These fields are updated immediately below
1023            item_config: None,
1024            hash: 0,
1025            durability_lost: None,
1026        };
1027        item.durability_lost = item.has_durability().then_some(0);
1028        item.update_item_state(ability_map, msm);
1029        item
1030    }
1031
1032    pub fn new_from_item_definition_id(
1033        item_definition_id: ItemDefinitionId<'_>,
1034        ability_map: &AbilityMap,
1035        msm: &MaterialStatManifest,
1036    ) -> Result<Self, Error> {
1037        let (base, components) = match item_definition_id {
1038            ItemDefinitionId::Simple(spec) => {
1039                let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(&spec)?);
1040                (base, Vec::new())
1041            },
1042            ItemDefinitionId::Modular {
1043                pseudo_base,
1044                components,
1045            } => {
1046                let base = ItemBase::Modular(ModularBase::load_from_pseudo_id(pseudo_base));
1047                let components = components
1048                    .into_iter()
1049                    .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1050                    .collect::<Result<Vec<_>, _>>()?;
1051                (base, components)
1052            },
1053            ItemDefinitionId::Compound {
1054                simple_base,
1055                components,
1056            } => {
1057                let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(simple_base)?);
1058                let components = components
1059                    .into_iter()
1060                    .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1061                    .collect::<Result<Vec<_>, _>>()?;
1062                (base, components)
1063            },
1064        };
1065        Ok(Item::new_from_item_base(base, components, ability_map, msm))
1066    }
1067
1068    /// Creates a new instance of an `Item` from the provided asset identifier
1069    /// Panics if the asset does not exist.
1070    pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
1071        Item::new_from_asset(asset_specifier).unwrap_or_else(|err| {
1072            panic!(
1073                "Expected asset to exist: {}, instead got error {:?}",
1074                asset_specifier, err
1075            );
1076        })
1077    }
1078
1079    /// Creates a Vec containing one of each item that matches the provided
1080    /// asset glob pattern
1081    pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
1082        let specifier = asset_glob.strip_suffix(".*").unwrap_or(asset_glob);
1083        let defs = assets::load_rec_dir::<Ron<RawItemDef>>(specifier)?;
1084        defs.read()
1085            .ids()
1086            .map(|id| Item::new_from_asset(id))
1087            .collect()
1088    }
1089
1090    /// Creates a new instance of an `Item from the provided asset identifier if
1091    /// it exists
1092    pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
1093        let inner_item = ItemBase::from_item_id_string(asset)?;
1094        // TODO: Get msm and ability_map less hackily
1095        let msm = &MaterialStatManifest::load().read();
1096        let ability_map = &AbilityMap::load().read();
1097        Ok(Item::new_from_item_base(
1098            inner_item,
1099            Vec::new(),
1100            ability_map,
1101            msm,
1102        ))
1103    }
1104
1105    /// Creates a [`FrontendItem`] out of this item for frontend use
1106    #[must_use]
1107    pub fn frontend_item(
1108        &self,
1109        ability_map: &AbilityMap,
1110        msm: &MaterialStatManifest,
1111    ) -> FrontendItem {
1112        FrontendItem(self.duplicate(ability_map, msm))
1113    }
1114
1115    /// Duplicates an item, creating an exact copy but with a new item ID
1116    #[must_use]
1117    pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1118        let duplicated_components = self
1119            .components
1120            .iter()
1121            .map(|comp| comp.duplicate(ability_map, msm))
1122            .collect();
1123        let mut new_item = Item::new_from_item_base(
1124            match &self.item_base {
1125                ItemBase::Simple(item_def) => ItemBase::Simple(Arc::clone(item_def)),
1126                ItemBase::Modular(mod_base) => ItemBase::Modular(mod_base.clone()),
1127            },
1128            duplicated_components,
1129            ability_map,
1130            msm,
1131        );
1132        new_item.set_amount(self.amount()).expect(
1133            "`new_item` has the same `item_def` and as an invariant, \
1134             self.set_amount(self.amount()) should always succeed.",
1135        );
1136        new_item.slots_mut().iter_mut().zip(self.slots()).for_each(
1137            |(new_item_slot, old_item_slot)| {
1138                *new_item_slot = old_item_slot
1139                    .as_ref()
1140                    .map(|old_item| old_item.duplicate(ability_map, msm));
1141            },
1142        );
1143        new_item
1144    }
1145
1146    pub fn stacked_duplicates<'a>(
1147        &'a self,
1148        ability_map: &'a AbilityMap,
1149        msm: &'a MaterialStatManifest,
1150        count: u32,
1151    ) -> impl Iterator<Item = Self> + 'a {
1152        let max_stack_count = count / self.max_amount();
1153        let rest = count % self.max_amount();
1154
1155        (0..max_stack_count)
1156            .map(|_| {
1157                let mut item = self.duplicate(ability_map, msm);
1158
1159                item.set_amount(item.max_amount())
1160                    .expect("max_amount() is always a valid amount.");
1161
1162                item
1163            })
1164            .chain((rest > 0).then(move || {
1165                let mut item = self.duplicate(ability_map, msm);
1166
1167                item.set_amount(rest)
1168                    .expect("anything less than max_amount() is always a valid amount.");
1169
1170                item
1171            }))
1172    }
1173
1174    /// FIXME: HACK: In order to set the entity ID asynchronously, we currently
1175    /// start it at None, and then atomically set it when it's saved for the
1176    /// first time in the database.  Because this requires shared mutable
1177    /// state if these aren't synchronized by the program structure,
1178    /// currently we use an Atomic inside an Arc; this is clearly very
1179    /// dangerous, so in the future we will hopefully have a better way of
1180    /// dealing with this.
1181    #[doc(hidden)]
1182    pub fn get_item_id_for_database(&self) -> Arc<ItemId> { Arc::clone(&self.item_id) }
1183
1184    /// Resets the item's item ID to None, giving it a new identity. Used when
1185    /// dropping items into the world so that a new database record is
1186    /// created when they are picked up again.
1187    ///
1188    /// NOTE: The creation of a new `Arc` when resetting the item ID is critical
1189    /// because every time a new `Item` instance is created, it is cloned from
1190    /// a single asset which results in an `Arc` pointing to the same value in
1191    /// memory. Therefore, every time an item instance is created this
1192    /// method must be called in order to give it a unique identity.
1193    fn reset_item_id(&mut self) {
1194        if let Some(item_id) = Arc::get_mut(&mut self.item_id) {
1195            *item_id = AtomicCell::new(None);
1196        } else {
1197            self.item_id = Arc::new(AtomicCell::new(None));
1198        }
1199        // Reset item id for every component of an item too
1200        for component in self.components.iter_mut() {
1201            component.reset_item_id();
1202        }
1203    }
1204
1205    /// Removes the unique identity of an item - used when dropping an item on
1206    /// the floor. In the future this will need to be changed if we want to
1207    /// maintain a unique ID for an item even when it's dropped and picked
1208    /// up by another player.
1209    pub fn put_in_world(&mut self) { self.reset_item_id() }
1210
1211    pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), OperationFailure> {
1212        let amount = u32::from(self.amount);
1213        self.amount = amount
1214            .checked_add(increase_by)
1215            .filter(|&amount| amount <= self.max_amount())
1216            .and_then(NonZeroU32::new)
1217            .ok_or(OperationFailure)?;
1218        Ok(())
1219    }
1220
1221    pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), OperationFailure> {
1222        let amount = u32::from(self.amount);
1223        self.amount = amount
1224            .checked_sub(decrease_by)
1225            .and_then(NonZeroU32::new)
1226            .ok_or(OperationFailure)?;
1227        Ok(())
1228    }
1229
1230    pub fn set_amount(&mut self, give_amount: u32) -> Result<(), OperationFailure> {
1231        if give_amount <= self.max_amount() {
1232            self.amount = NonZeroU32::new(give_amount).ok_or(OperationFailure)?;
1233            Ok(())
1234        } else {
1235            Err(OperationFailure)
1236        }
1237    }
1238
1239    pub fn persistence_access_add_component(&mut self, component: Item) {
1240        self.components.push(component);
1241    }
1242
1243    pub fn persistence_access_mutable_component(&mut self, index: usize) -> Option<&mut Self> {
1244        self.components.get_mut(index)
1245    }
1246
1247    /// Updates state of an item (important for creation of new items,
1248    /// persistence, and if components are ever added to items after initial
1249    /// creation)
1250    pub fn update_item_state(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1251        // Updates item config of an item
1252        if let Ok(item_config) = ItemConfig::try_from((&*self, ability_map, msm)) {
1253            self.item_config = Some(Box::new(item_config));
1254        }
1255        // Updates hash of an item
1256        self.hash = {
1257            let mut s = DefaultHasher::new();
1258            self.hash(&mut s);
1259            s.finish()
1260        };
1261    }
1262
1263    /// Returns an iterator that drains items contained within the item's slots
1264    pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
1265        self.slots.iter_mut().filter_map(mem::take)
1266    }
1267
1268    pub fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1269        match &self.item_base {
1270            ItemBase::Simple(item_def) => {
1271                if self.components.is_empty() {
1272                    ItemDefinitionId::Simple(Cow::Borrowed(&item_def.item_definition_id))
1273                } else {
1274                    ItemDefinitionId::Compound {
1275                        simple_base: &item_def.item_definition_id,
1276                        components: self
1277                            .components
1278                            .iter()
1279                            .map(|item| item.item_definition_id())
1280                            .collect(),
1281                    }
1282                }
1283            },
1284            ItemBase::Modular(mod_base) => ItemDefinitionId::Modular {
1285                pseudo_base: mod_base.pseudo_item_id(),
1286                components: self
1287                    .components
1288                    .iter()
1289                    .map(|item| item.item_definition_id())
1290                    .collect(),
1291            },
1292        }
1293    }
1294
1295    pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
1296        if let ItemBase::Simple(self_def) = &self.item_base {
1297            self_def.item_definition_id == item_def.item_definition_id
1298        } else {
1299            false
1300        }
1301    }
1302
1303    pub fn matches_recipe_input(&self, recipe_input: &RecipeInput, amount: u32) -> bool {
1304        match recipe_input {
1305            RecipeInput::Item(item_def) => self.is_same_item_def(item_def),
1306            RecipeInput::Tag(tag) => self.tags().contains(tag),
1307            RecipeInput::TagSameItem(tag) => {
1308                self.tags().contains(tag) && u32::from(self.amount) >= amount
1309            },
1310            RecipeInput::ListSameItem(item_defs) => item_defs.iter().any(|item_def| {
1311                self.is_same_item_def(item_def) && u32::from(self.amount) >= amount
1312            }),
1313        }
1314    }
1315
1316    pub fn is_salvageable(&self) -> bool {
1317        self.tags()
1318            .iter()
1319            .any(|tag| matches!(tag, ItemTag::SalvageInto(_, _)))
1320    }
1321
1322    pub fn salvage_output(&self) -> impl Iterator<Item = (&str, u32)> {
1323        self.tags().into_iter().filter_map(|tag| {
1324            if let ItemTag::SalvageInto(material, quantity) = tag {
1325                material
1326                    .asset_identifier()
1327                    .map(|material_id| (material_id, quantity))
1328            } else {
1329                None
1330            }
1331        })
1332    }
1333
1334    #[deprecated = "since item i18n"]
1335    pub fn legacy_name(&self) -> Cow<'_, str> {
1336        match &self.item_base {
1337            ItemBase::Simple(item_def) => {
1338                if self.components.is_empty() {
1339                    #[expect(deprecated)]
1340                    Cow::Borrowed(&item_def.legacy_name)
1341                } else {
1342                    #[expect(deprecated)]
1343                    modular::modify_name(&item_def.legacy_name, self)
1344                }
1345            },
1346            #[expect(deprecated, reason = "since item i18n")]
1347            ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()),
1348        }
1349    }
1350
1351    pub fn kind(&self) -> Cow<'_, ItemKind> {
1352        match &self.item_base {
1353            ItemBase::Simple(item_def) => Cow::Borrowed(&item_def.kind),
1354            ItemBase::Modular(mod_base) => {
1355                // TODO: Try to move further upward
1356                let msm = &MaterialStatManifest::load().read();
1357                mod_base.kind(self.components(), msm, self.stats_durability_multiplier())
1358            },
1359        }
1360    }
1361
1362    pub fn amount(&self) -> u32 { u32::from(self.amount) }
1363
1364    pub fn is_stackable(&self) -> bool {
1365        match &self.item_base {
1366            ItemBase::Simple(item_def) => item_def.is_stackable(),
1367            // TODO: Let whoever implements stackable modular items deal with this
1368            ItemBase::Modular(_) => false,
1369        }
1370    }
1371
1372    /// NOTE: invariant that amount() ≤ max_amount(), 1 ≤ max_amount(),
1373    /// and if !self.is_stackable(), self.max_amount() = 1.
1374    pub fn max_amount(&self) -> u32 {
1375        match &self.item_base {
1376            ItemBase::Simple(item_def) => item_def.max_amount(),
1377            ItemBase::Modular(_) => {
1378                debug_assert!(!self.is_stackable());
1379                1
1380            },
1381        }
1382    }
1383
1384    pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
1385
1386    pub fn quality(&self) -> Quality {
1387        match &self.item_base {
1388            ItemBase::Simple(item_def) => item_def.quality.max(
1389                self.components
1390                    .iter()
1391                    .fold(Quality::MIN, |a, b| a.max(b.quality())),
1392            ),
1393            ItemBase::Modular(mod_base) => mod_base.compute_quality(self.components()),
1394        }
1395    }
1396
1397    pub fn components(&self) -> &[Item] { &self.components }
1398
1399    pub fn slots(&self) -> &[InvSlot] { &self.slots }
1400
1401    pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
1402
1403    pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() }
1404
1405    pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }
1406
1407    pub fn populated_slots(&self) -> usize { self.slots().len().saturating_sub(self.free_slots()) }
1408
1409    pub fn slot(&self, slot: usize) -> Option<&InvSlot> { self.slots.get(slot) }
1410
1411    pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
1412
1413    pub fn try_reclaim_from_block(
1414        block: Block,
1415        sprite_cfg: Option<&SpriteCfg>,
1416    ) -> Option<Vec<(u32, Self)>> {
1417        if let Some(loot_spec) = sprite_cfg.and_then(|sprite_cfg| sprite_cfg.loot_table.as_ref()) {
1418            LootSpec::LootTable(loot_spec).to_items()
1419        } else {
1420            block.get_sprite()?.default_loot_spec()??.to_items()
1421        }
1422    }
1423
1424    pub fn ability_spec(&self) -> Option<Cow<'_, AbilitySpec>> {
1425        match &self.item_base {
1426            ItemBase::Simple(item_def) => {
1427                item_def.ability_spec.as_ref().map(Cow::Borrowed).or({
1428                    // If no custom ability set is specified, fall back to abilityset of tool
1429                    // kind.
1430                    if let ItemKind::Tool(tool) = &item_def.kind {
1431                        Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
1432                    } else {
1433                        None
1434                    }
1435                })
1436            },
1437            ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
1438        }
1439    }
1440
1441    // TODO: Maybe try to make slice again instead of vec? Could also try to make an
1442    // iterator?
1443    pub fn tags(&self) -> Vec<ItemTag> {
1444        match &self.item_base {
1445            ItemBase::Simple(item_def) => item_def.tags.to_vec(),
1446            // TODO: Do this properly. It'll probably be important at some point.
1447            ItemBase::Modular(mod_base) => mod_base.generate_tags(self.components()),
1448        }
1449    }
1450
1451    pub fn is_modular(&self) -> bool {
1452        match &self.item_base {
1453            ItemBase::Simple(_) => false,
1454            ItemBase::Modular(_) => true,
1455        }
1456    }
1457
1458    pub fn item_hash(&self) -> u64 { self.hash }
1459
1460    pub fn persistence_item_id(&self) -> String {
1461        match &self.item_base {
1462            ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
1463            ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
1464        }
1465    }
1466
1467    pub fn durability_lost(&self) -> Option<u32> {
1468        self.durability_lost.map(|x| x.min(Self::MAX_DURABILITY))
1469    }
1470
1471    pub fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1472        let durability_lost = self.durability_lost.unwrap_or(0);
1473        debug_assert!(durability_lost <= Self::MAX_DURABILITY);
1474        // How much durability must be lost before stats start to decay
1475        const DURABILITY_THRESHOLD: u32 = 9;
1476        const MIN_FRAC: f32 = 0.25;
1477        let mult = (1.0
1478            - durability_lost.saturating_sub(DURABILITY_THRESHOLD) as f32
1479                / (Self::MAX_DURABILITY - DURABILITY_THRESHOLD) as f32)
1480            * (1.0 - MIN_FRAC)
1481            + MIN_FRAC;
1482        DurabilityMultiplier(mult)
1483    }
1484
1485    pub fn has_durability(&self) -> bool {
1486        self.kind().has_durability() && self.quality() != Quality::Debug
1487    }
1488
1489    pub fn increment_damage(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1490        if let Some(durability_lost) = &mut self.durability_lost
1491            && *durability_lost < Self::MAX_DURABILITY
1492        {
1493            *durability_lost += 1;
1494        }
1495        // Update item state after applying durability because stats have potential to
1496        // change from different durability
1497        self.update_item_state(ability_map, msm);
1498    }
1499
1500    pub fn persistence_durability(&self) -> Option<NonZeroU32> {
1501        self.durability_lost.and_then(NonZeroU32::new)
1502    }
1503
1504    pub fn persistence_set_durability(&mut self, value: Option<NonZeroU32>) {
1505        // If changes have been made so that item no longer needs to track durability,
1506        // set to None
1507        if !self.has_durability() {
1508            self.durability_lost = None;
1509        } else {
1510            // Set durability to persisted value, and if item previously had no durability,
1511            // set to Some(0) so that durability will be tracked
1512            self.durability_lost = Some(value.map_or(0, NonZeroU32::get));
1513        }
1514    }
1515
1516    pub fn reset_durability(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1517        self.durability_lost = self.has_durability().then_some(0);
1518        // Update item state after applying durability because stats have potential to
1519        // change from different durability
1520        self.update_item_state(ability_map, msm);
1521    }
1522
1523    /// If an item is stackable and has an amount greater than the requested
1524    /// amount, decreases the amount of the original item by the same
1525    /// quantity and return a copy of the item with the taken amount.
1526    #[must_use = "Returned items will be lost if not used"]
1527    pub fn take_amount(
1528        &mut self,
1529        ability_map: &AbilityMap,
1530        msm: &MaterialStatManifest,
1531        returning_amount: u32,
1532    ) -> Option<Item> {
1533        if self.is_stackable() && self.amount() > 1 && returning_amount < self.amount() {
1534            let mut return_item = self.duplicate(ability_map, msm);
1535            self.decrease_amount(returning_amount).ok()?;
1536            return_item.set_amount(returning_amount).expect(
1537                "return_item.amount() = returning_amount < self.amount() (since self.amount() ≥ \
1538                 1) ≤ self.max_amount() = return_item.max_amount(), since return_item is a \
1539                 duplicate of item",
1540            );
1541            Some(return_item)
1542        } else {
1543            None
1544        }
1545    }
1546
1547    /// If an item is stackable and has an amount greater than 1, creates a new
1548    /// item with half the amount (rounded down), and decreases the amount of
1549    /// the original item by the same quantity.
1550    #[must_use = "Returned items will be lost if not used"]
1551    pub fn take_half(
1552        &mut self,
1553        ability_map: &AbilityMap,
1554        msm: &MaterialStatManifest,
1555    ) -> Option<Item> {
1556        self.take_amount(ability_map, msm, self.amount() / 2)
1557    }
1558
1559    #[cfg(test)]
1560    pub fn create_test_item_from_kind(kind: ItemKind) -> Self {
1561        let ability_map = &AbilityMap::load().read();
1562        let msm = &MaterialStatManifest::load().read();
1563        Self::new_from_item_base(
1564            ItemBase::Simple(Arc::new(ItemDef::create_test_itemdef_from_kind(kind))),
1565            Vec::new(),
1566            ability_map,
1567            msm,
1568        )
1569    }
1570
1571    /// Checks if this item and another are suitable for grouping into the same
1572    /// [`PickupItem`].
1573    ///
1574    /// Also see [`Item::try_merge`].
1575    pub fn can_merge(&self, other: &Self) -> bool {
1576        if self.amount() > self.max_amount() || other.amount() > other.max_amount() {
1577            error!("An item amount is over max_amount!");
1578            return false;
1579        }
1580
1581        (self == other)
1582            && self.slots().iter().all(Option::is_none)
1583            && other.slots().iter().all(Option::is_none)
1584            && self.durability_lost() == other.durability_lost()
1585    }
1586
1587    /// Checks if this item and another are suitable for grouping into the same
1588    /// [`PickupItem`] and combines stackable items if possible.
1589    ///
1590    /// If the sum of both amounts is larger than their max amount, a remainder
1591    /// item is returned as `Ok(Some(remainder))`. A remainder item will
1592    /// always be produced for non-stackable items.
1593    ///
1594    /// If the items are not suitable for grouping `Err(other)` will be
1595    /// returned.
1596    pub fn try_merge(&mut self, mut other: Self) -> Result<Option<Self>, Self> {
1597        if self.can_merge(&other) {
1598            let max_amount = self.max_amount();
1599            debug_assert_eq!(
1600                max_amount,
1601                other.max_amount(),
1602                "Mergeable items must have the same max_amount()"
1603            );
1604
1605            // Additional amount `self` can hold
1606            // For non-stackable items this is always zero
1607            let to_fill_self = max_amount
1608                .checked_sub(self.amount())
1609                .expect("can_merge should ensure that amount() <= max_amount()");
1610
1611            if let Some(remainder) = other.amount().checked_sub(to_fill_self).filter(|r| *r > 0) {
1612                self.set_amount(max_amount)
1613                    .expect("max_amount() is always a valid amount.");
1614                other.set_amount(remainder).expect(
1615                    "We know remainder is more than 0 and less than or equal to max_amount()",
1616                );
1617                Ok(Some(other))
1618            } else {
1619                // If there would be no remainder, add the amounts!
1620                self.increase_amount(other.amount())
1621                    .expect("We know that we can at least add other.amount() to this item");
1622                drop(other);
1623                Ok(None)
1624            }
1625        } else {
1626            Err(other)
1627        }
1628    }
1629
1630    // Probably doesn't need to be limited to persistence, but nothing else should
1631    // really need to look at item base
1632    pub fn persistence_item_base(&self) -> &ItemBase { &self.item_base }
1633}
1634
1635impl FrontendItem {
1636    /// See [`Item::duplicate`], the returned item will still be a
1637    /// [`FrontendItem`]
1638    #[must_use]
1639    pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1640        FrontendItem(self.0.duplicate(ability_map, msm))
1641    }
1642
1643    pub fn set_amount(&mut self, amount: u32) -> Result<(), OperationFailure> {
1644        self.0.set_amount(amount)
1645    }
1646}
1647
1648impl PickupItem {
1649    pub fn new(item: Item, time: ProgramTime, should_merge: bool) -> Self {
1650        Self {
1651            items: vec![item],
1652            created_at: time,
1653            next_merge_check: time,
1654            should_merge,
1655        }
1656    }
1657
1658    /// Get a reference to the last item in this stack
1659    ///
1660    /// The amount of this item should *not* be used.
1661    pub fn item(&self) -> &Item {
1662        self.items
1663            .last()
1664            .expect("PickupItem without at least one item is an invariant")
1665    }
1666
1667    pub fn created(&self) -> ProgramTime { self.created_at }
1668
1669    pub fn next_merge_check(&self) -> ProgramTime { self.next_merge_check }
1670
1671    pub fn next_merge_check_mut(&mut self) -> &mut ProgramTime { &mut self.next_merge_check }
1672
1673    // Get the total amount of items in here
1674    pub fn amount(&self) -> u32 {
1675        self.items
1676            .iter()
1677            .map(Item::amount)
1678            .fold(0, |total, amount| total.saturating_add(amount))
1679    }
1680
1681    /// Remove any debug items if this is a container, used before dropping an
1682    /// item from an inventory
1683    pub fn remove_debug_items(&mut self) {
1684        for item in self.items.iter_mut() {
1685            item.slots_mut().iter_mut().for_each(|container_slot| {
1686                container_slot
1687                    .take_if(|contained_item| matches!(contained_item.quality(), Quality::Debug));
1688            });
1689        }
1690    }
1691
1692    pub fn can_merge(&self, other: &PickupItem) -> bool {
1693        let self_item = self.item();
1694        let other_item = other.item();
1695
1696        self.should_merge && other.should_merge && self_item.can_merge(other_item)
1697    }
1698
1699    // Attempt to merge another PickupItem into this one, can only fail if
1700    // `can_merge` returns false
1701    pub fn try_merge(&mut self, mut other: PickupItem) -> Result<(), PickupItem> {
1702        if self.can_merge(&other) {
1703            // Pop the last item from `self` and `other` to merge them, as only the last
1704            // items can have an amount != max_amount()
1705            let mut self_last = self
1706                .items
1707                .pop()
1708                .expect("PickupItem without at least one item is an invariant");
1709            let other_last = other
1710                .items
1711                .pop()
1712                .expect("PickupItem without at least one item is an invariant");
1713
1714            // Merge other_last into self_last
1715            let merged = self_last
1716                .try_merge(other_last)
1717                .expect("We know these items can be merged");
1718
1719            debug_assert!(
1720                other
1721                    .items
1722                    .iter()
1723                    .chain(self.items.iter())
1724                    .all(|item| item.amount() == item.max_amount()),
1725                "All items before the last in `PickupItem` should have a full amount"
1726            );
1727
1728            // We know all items except the last have a full amount, so we can safely append
1729            // them here
1730            self.items.append(&mut other.items);
1731
1732            debug_assert!(
1733                merged.is_none() || self_last.amount() == self_last.max_amount(),
1734                "Merged can only be `Some` if the origin was set to `max_amount()`"
1735            );
1736
1737            // Push the potentially not fully-stacked item at the end
1738            self.items.push(self_last);
1739
1740            // Push the remainder, merged is only `Some` if self_last was set to
1741            // `max_amount()`
1742            if let Some(remainder) = merged {
1743                self.items.push(remainder);
1744            }
1745
1746            Ok(())
1747        } else {
1748            Err(other)
1749        }
1750    }
1751
1752    pub fn pick_up(mut self) -> (Item, Option<Self>) {
1753        (
1754            self.items
1755                .pop()
1756                .expect("PickupItem without at least one item is an invariant"),
1757            (!self.items.is_empty()).then_some(self),
1758        )
1759    }
1760}
1761
1762pub fn flatten_counted_items<'a>(
1763    items: &'a [(u32, Item)],
1764    ability_map: &'a AbilityMap,
1765    msm: &'a MaterialStatManifest,
1766) -> impl Iterator<Item = Item> + 'a {
1767    items
1768        .iter()
1769        .flat_map(|(count, item)| item.stacked_duplicates(ability_map, msm, *count))
1770}
1771
1772/// Provides common methods providing details about an item definition
1773/// for either an `Item` containing the definition, or the actual `ItemDef`
1774pub trait ItemDesc {
1775    #[deprecated = "since item i18n"]
1776    fn legacy_name(&self) -> Cow<'_, str>;
1777    fn kind(&self) -> Cow<'_, ItemKind>;
1778    fn amount(&self) -> NonZeroU32;
1779    fn quality(&self) -> Quality;
1780    fn num_slots(&self) -> u16;
1781    fn item_definition_id(&self) -> ItemDefinitionId<'_>;
1782    fn tags(&self) -> Vec<ItemTag>;
1783    fn is_modular(&self) -> bool;
1784    fn components(&self) -> &[Item];
1785    fn has_durability(&self) -> bool;
1786    fn durability_lost(&self) -> Option<u32>;
1787    fn stats_durability_multiplier(&self) -> DurabilityMultiplier;
1788
1789    fn tool_info(&self) -> Option<ToolKind> {
1790        if let ItemKind::Tool(tool) = &*self.kind() {
1791            Some(tool.kind)
1792        } else {
1793            None
1794        }
1795    }
1796
1797    /// Return name's and description's localization descriptors
1798    fn i18n(&self, i18n: &ItemI18n) -> (Content, Content) {
1799        let item_key: ItemKey = self.into();
1800
1801        let (name, description) = i18n.item_text_opt(&item_key).unwrap_or_else(|| {
1802            (
1803                #[expect(deprecated)]
1804                Content::Plain(self.legacy_name().to_string()),
1805                Content::Plain(String::new()),
1806            )
1807        });
1808
1809        let b = |x| Box::new(x);
1810        if let ItemKey::ModularWeapon((comp_id, ing_id, hands)) = item_key {
1811            // the name template
1812            let title_fallback = Content::localized("weapon-modular-fallback-template")
1813                .with_arg(
1814                    "material-fragment",
1815                    i18n.try_fragment(&FragmentKey::Ingredient(ing_id))
1816                        // use Key instead of Plain here, so it's marked as
1817                        // "dirty" during get_content() and attempts English
1818                        .unwrap_or_else(|| Content::Key("Modular".to_owned())),
1819                )
1820                .with_arg(
1821                    "weapon",
1822                    i18n.try_fragment(&FragmentKey::WeaponPrimaryComponent(comp_id, hands))
1823                        // use Key instead of Plain here, so it's marked as
1824                        // "dirty" during get_content() and attempts English
1825                        .unwrap_or_else(|| Content::Key("Weapon".to_owned())),
1826                );
1827
1828            (
1829                Content::WithFallback(b(name), b(title_fallback)),
1830                // no fallback for description, yet?
1831                description,
1832            )
1833        } else if let ItemKey::ModularWeaponComponent((comp_id, ing_id)) = item_key {
1834            // the name template
1835            let title_fallback = Content::localized("weapon-modular-comp-fallback-template")
1836                .with_arg(
1837                    "material-fragment",
1838                    i18n.try_fragment(&FragmentKey::Ingredient(ing_id))
1839                        // use Key instead of Plain here, so it's marked as
1840                        // "dirty" during get_content() and attempts English
1841                        .unwrap_or_else(|| Content::Key("Modular".to_owned())),
1842                )
1843                .with_arg(
1844                    "component",
1845                    i18n.try_key(&ItemKey::Simple(comp_id))
1846                        .map(|k| Content::Key(k.to_owned()))
1847                        // use Key instead of Plain here, so it's marked as
1848                        // "dirty" during get_content() and attempts English
1849                        .unwrap_or_else(|| Content::Key("Component".to_owned())),
1850                );
1851
1852            (
1853                Content::WithFallback(b(name), b(title_fallback)),
1854                // no fallback for description, yet?
1855                description,
1856            )
1857        } else {
1858            (name, description)
1859        }
1860    }
1861}
1862
1863impl ItemDesc for Item {
1864    fn legacy_name(&self) -> Cow<'_, str> {
1865        #[expect(deprecated)]
1866        self.legacy_name()
1867    }
1868
1869    fn kind(&self) -> Cow<'_, ItemKind> { self.kind() }
1870
1871    fn amount(&self) -> NonZeroU32 { self.amount }
1872
1873    fn quality(&self) -> Quality { self.quality() }
1874
1875    fn num_slots(&self) -> u16 { self.num_slots() }
1876
1877    fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item_definition_id() }
1878
1879    fn tags(&self) -> Vec<ItemTag> { self.tags() }
1880
1881    fn is_modular(&self) -> bool { self.is_modular() }
1882
1883    fn components(&self) -> &[Item] { self.components() }
1884
1885    fn has_durability(&self) -> bool { self.has_durability() }
1886
1887    fn durability_lost(&self) -> Option<u32> { self.durability_lost() }
1888
1889    fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1890        self.stats_durability_multiplier()
1891    }
1892}
1893
1894impl ItemDesc for FrontendItem {
1895    fn legacy_name(&self) -> Cow<'_, str> {
1896        #[expect(deprecated)]
1897        self.0.legacy_name()
1898    }
1899
1900    fn kind(&self) -> Cow<'_, ItemKind> { self.0.kind() }
1901
1902    fn amount(&self) -> NonZeroU32 { self.0.amount }
1903
1904    fn quality(&self) -> Quality { self.0.quality() }
1905
1906    fn num_slots(&self) -> u16 { self.0.num_slots() }
1907
1908    fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.0.item_definition_id() }
1909
1910    fn tags(&self) -> Vec<ItemTag> { self.0.tags() }
1911
1912    fn is_modular(&self) -> bool { self.0.is_modular() }
1913
1914    fn components(&self) -> &[Item] { self.0.components() }
1915
1916    fn has_durability(&self) -> bool { self.0.has_durability() }
1917
1918    fn durability_lost(&self) -> Option<u32> { self.0.durability_lost() }
1919
1920    fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1921        self.0.stats_durability_multiplier()
1922    }
1923}
1924
1925impl ItemDesc for ItemDef {
1926    fn legacy_name(&self) -> Cow<'_, str> {
1927        #[expect(deprecated)]
1928        Cow::Borrowed(&self.legacy_name)
1929    }
1930
1931    fn kind(&self) -> Cow<'_, ItemKind> { Cow::Borrowed(&self.kind) }
1932
1933    fn amount(&self) -> NonZeroU32 { NonZeroU32::new(1).unwrap() }
1934
1935    fn quality(&self) -> Quality { self.quality }
1936
1937    fn num_slots(&self) -> u16 { self.slots }
1938
1939    fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1940        ItemDefinitionId::Simple(Cow::Borrowed(&self.item_definition_id))
1941    }
1942
1943    fn tags(&self) -> Vec<ItemTag> { self.tags.to_vec() }
1944
1945    fn is_modular(&self) -> bool { false }
1946
1947    fn components(&self) -> &[Item] { &[] }
1948
1949    fn has_durability(&self) -> bool {
1950        self.kind().has_durability() && self.quality != Quality::Debug
1951    }
1952
1953    fn durability_lost(&self) -> Option<u32> { None }
1954
1955    fn stats_durability_multiplier(&self) -> DurabilityMultiplier { DurabilityMultiplier(1.0) }
1956}
1957
1958impl ItemDesc for PickupItem {
1959    fn legacy_name(&self) -> Cow<'_, str> {
1960        #[expect(deprecated)]
1961        self.item().legacy_name()
1962    }
1963
1964    fn kind(&self) -> Cow<'_, ItemKind> { self.item().kind() }
1965
1966    fn amount(&self) -> NonZeroU32 {
1967        NonZeroU32::new(self.amount()).expect("Item having amount of 0 is invariant")
1968    }
1969
1970    fn quality(&self) -> Quality { self.item().quality() }
1971
1972    fn num_slots(&self) -> u16 { self.item().num_slots() }
1973
1974    fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item().item_definition_id() }
1975
1976    fn tags(&self) -> Vec<ItemTag> { self.item().tags() }
1977
1978    fn is_modular(&self) -> bool { self.item().is_modular() }
1979
1980    fn components(&self) -> &[Item] { self.item().components() }
1981
1982    fn has_durability(&self) -> bool { self.item().has_durability() }
1983
1984    fn durability_lost(&self) -> Option<u32> { self.item().durability_lost() }
1985
1986    fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1987        self.item().stats_durability_multiplier()
1988    }
1989}
1990
1991#[derive(Clone, Debug, Serialize, Deserialize)]
1992pub struct ItemDrops(pub Vec<(u32, Item)>);
1993
1994impl Component for ItemDrops {
1995    type Storage = DenseVecStorage<Self>;
1996}
1997
1998impl Component for PickupItem {
1999    type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
2000}
2001
2002impl Component for ThrownItem {
2003    type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
2004}
2005
2006#[derive(Copy, Clone, Debug)]
2007pub struct DurabilityMultiplier(pub f32);
2008
2009impl<T: ItemDesc + ?Sized> ItemDesc for &T {
2010    fn legacy_name(&self) -> Cow<'_, str> {
2011        #[expect(deprecated)]
2012        (*self).legacy_name()
2013    }
2014
2015    fn kind(&self) -> Cow<'_, ItemKind> { (*self).kind() }
2016
2017    fn amount(&self) -> NonZeroU32 { (*self).amount() }
2018
2019    fn quality(&self) -> Quality { (*self).quality() }
2020
2021    fn num_slots(&self) -> u16 { (*self).num_slots() }
2022
2023    fn item_definition_id(&self) -> ItemDefinitionId<'_> { (*self).item_definition_id() }
2024
2025    fn tags(&self) -> Vec<ItemTag> { (*self).tags() }
2026
2027    fn is_modular(&self) -> bool { (*self).is_modular() }
2028
2029    fn components(&self) -> &[Item] { (*self).components() }
2030
2031    fn has_durability(&self) -> bool { (*self).has_durability() }
2032
2033    fn durability_lost(&self) -> Option<u32> { (*self).durability_lost() }
2034
2035    fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
2036        (*self).stats_durability_multiplier()
2037    }
2038}
2039
2040/// Returns all item asset specifiers
2041///
2042/// Panics in case of filesystem errors
2043pub fn all_item_defs_expect() -> Vec<String> {
2044    try_all_item_defs().expect("Failed to access items directory")
2045}
2046
2047/// Returns all item asset specifiers
2048pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
2049    let defs = assets::load_rec_dir::<Ron<RawItemDef>>("common.items")?;
2050    Ok(defs.read().ids().map(|id| id.to_string()).collect())
2051}
2052
2053/// Designed to return all possible items, including modulars.
2054/// And some impossible too, like ItemKind::TagExamples.
2055pub fn all_items_expect() -> Vec<Item> {
2056    let defs = assets::load_rec_dir::<Ron<RawItemDef>>("common.items")
2057        .expect("failed to load item asset directory");
2058
2059    // Grab all items from assets
2060    let mut asset_items: Vec<Item> = defs
2061        .read()
2062        .ids()
2063        .map(|id| Item::new_from_asset_expect(id))
2064        .collect();
2065
2066    let mut material_parse_table = HashMap::new();
2067    for mat in Material::iter() {
2068        if let Some(id) = mat.asset_identifier() {
2069            material_parse_table.insert(id.to_owned(), mat);
2070        }
2071    }
2072
2073    let primary_comp_pool = modular::PRIMARY_COMPONENT_POOL.clone();
2074
2075    // Grab weapon primary components
2076    let mut primary_comps: Vec<Item> = primary_comp_pool
2077        .values()
2078        .flatten()
2079        .map(|(item, _hand_rules)| item.clone())
2080        .collect();
2081
2082    // Grab modular weapons
2083    let mut modular_items: Vec<Item> = primary_comp_pool
2084        .keys()
2085        .flat_map(|(tool, mat_id)| {
2086            let mat = material_parse_table
2087                .get(mat_id)
2088                .expect("unexpected material ident");
2089
2090            // get all weapons without imposing additional hand restrictions
2091            modular::generate_weapons(*tool, *mat, None)
2092                .expect("failure during modular weapon generation")
2093        })
2094        .collect();
2095
2096    // 1. Append asset items, that should include pretty much everything,
2097    // except modular items
2098    // 2. Append primary weapon components, which are modular as well.
2099    // 3. Finally append modular weapons that are made from (1) and (2)
2100    // extend when we get some new exotic stuff
2101    //
2102    // P. s. I still can't wrap my head around the idea that you can put
2103    // tag example into your inventory.
2104    let mut all = Vec::new();
2105    all.append(&mut asset_items);
2106    all.append(&mut primary_comps);
2107    all.append(&mut modular_items);
2108
2109    all
2110}
2111
2112impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2113    fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
2114        use ItemDefinitionId as DefId;
2115        match self {
2116            Self::Simple(simple) => {
2117                matches!(other, DefId::Simple(other_simple) if simple == other_simple)
2118            },
2119            Self::Modular {
2120                pseudo_base,
2121                components,
2122            } => matches!(
2123                other,
2124                DefId::Modular { pseudo_base: other_base, components: other_comps }
2125                if pseudo_base == other_base && components == other_comps
2126            ),
2127            Self::Compound {
2128                simple_base,
2129                components,
2130            } => matches!(
2131                other,
2132                DefId::Compound { simple_base: other_base, components: other_comps }
2133                if simple_base == other_base && components == other_comps
2134            ),
2135        }
2136    }
2137}
2138
2139impl PartialEq<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2140    #[inline]
2141    fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self }
2142}
2143
2144impl Equivalent<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2145    fn equivalent(&self, key: &ItemDefinitionIdOwned) -> bool { self == key }
2146}
2147
2148impl From<&ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2149    fn from(value: &ItemDefinitionId<'_>) -> Self { value.to_owned() }
2150}
2151
2152#[cfg(test)]
2153mod tests {
2154    use super::*;
2155    use hashbrown::HashSet;
2156
2157    #[test]
2158    fn test_assets_items() {
2159        let ids = all_item_defs_expect();
2160        for item in ids.iter().map(|id| Item::new_from_asset_expect(id)) {
2161            if let ItemKind::Consumable {
2162                container: Some(container),
2163                ..
2164            } = item.kind().as_ref()
2165            {
2166                Item::new_from_item_definition_id(
2167                    container.as_ref(),
2168                    &AbilityMap::load().read(),
2169                    &MaterialStatManifest::load().read(),
2170                )
2171                .unwrap();
2172            }
2173            drop(item)
2174        }
2175    }
2176
2177    #[test]
2178    fn test_item_i18n() { let _ = ItemI18n::new_expect(); }
2179
2180    #[test]
2181    // Probably can't fail, but better safe than crashing production server
2182    fn test_all_items() { let _ = all_items_expect(); }
2183
2184    #[test]
2185    // All items in Veloren should have localization.
2186    // If no, add some common dummy i18n id.
2187    fn ensure_item_localization() {
2188        let manifest = ItemI18n::new_expect();
2189        let items = all_items_expect();
2190        let mut errs = vec![];
2191        for item in items {
2192            let item_key: ItemKey = (&item).into();
2193            if manifest.item_text_opt(&item_key.clone()).is_none() {
2194                errs.push(item_key)
2195            }
2196        }
2197        if !errs.is_empty() {
2198            panic!("item i18n manifest misses translation-id for following items {errs:#?}")
2199        }
2200    }
2201
2202    #[test]
2203    // This exists to make translators' lives easier when translating
2204    // modulars.
2205    fn ensure_modular_fragments() {
2206        let manifest = ItemI18n::new_expect();
2207        let items = all_items_expect();
2208        let mut errs = HashSet::new();
2209
2210        for item in items {
2211            let item_key: ItemKey = (&item).into();
2212            if let ItemKey::ModularWeapon((comp_id, ing_id, hands)) = item_key {
2213                if manifest
2214                    .try_fragment(&FragmentKey::Ingredient(ing_id.clone()))
2215                    .is_none()
2216                {
2217                    errs.insert(FragmentKey::Ingredient(ing_id));
2218                }
2219                if manifest
2220                    .try_fragment(&FragmentKey::WeaponPrimaryComponent(comp_id.clone(), hands))
2221                    .is_none()
2222                {
2223                    errs.insert(FragmentKey::WeaponPrimaryComponent(comp_id, hands));
2224                }
2225            }
2226        }
2227        if !errs.is_empty() {
2228            panic!("item i18n manifest missing fragment-id for following items {errs:#?}")
2229        }
2230    }
2231}