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