veloren_common/comp/inventory/item/
mod.rs

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