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