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