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::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 ItemI18n {
557    pub fn new_expect() -> Self {
558        Ron::load_expect("common.item_i18n_manifest")
559            .read()
560            .clone()
561            .into_inner()
562    }
563
564    /// Returns (name, description) in Content form.
565    // TODO: after we remove legacy text from ItemDef, consider making this
566    // function non-fallible?
567    fn item_text_opt(&self, mut item_key: ItemKey) -> Option<(Content, Content)> {
568        // we don't put TagExamples into manifest
569        if let ItemKey::TagExamples(_, id) = item_key {
570            item_key = ItemKey::Simple(id.to_string());
571        }
572
573        let key = self.map.get(&item_key);
574        key.map(|key| {
575            (
576                Content::Key(key.to_owned()),
577                Content::Attr(key.to_owned(), "desc".to_owned()),
578            )
579        })
580    }
581}
582
583#[derive(Clone, Debug)]
584pub enum ItemBase {
585    Simple(Arc<ItemDef>),
586    Modular(ModularBase),
587}
588
589impl Serialize for ItemBase {
590    // Custom serialization for ItemDef, we only want to send the item_definition_id
591    // over the network, the client will use deserialize_item_def to fetch the
592    // ItemDef from assets.
593    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
594    where
595        S: Serializer,
596    {
597        serializer.serialize_str(&self.serialization_item_id())
598    }
599}
600
601impl<'de> Deserialize<'de> for ItemBase {
602    // Custom de-serialization for ItemBase to retrieve the ItemBase from assets
603    // using its asset specifier (item_definition_id)
604    fn deserialize<D>(deserializer: D) -> Result<ItemBase, D::Error>
605    where
606        D: de::Deserializer<'de>,
607    {
608        struct ItemBaseStringVisitor;
609
610        impl de::Visitor<'_> for ItemBaseStringVisitor {
611            type Value = ItemBase;
612
613            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
614                formatter.write_str("item def string")
615            }
616
617            fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
618            where
619                E: de::Error,
620            {
621                ItemBase::from_item_id_string(serialized_item_base)
622                    .map_err(|err| E::custom(err.to_string()))
623            }
624        }
625
626        deserializer.deserialize_str(ItemBaseStringVisitor)
627    }
628}
629
630impl ItemBase {
631    fn num_slots(&self) -> u16 {
632        match self {
633            ItemBase::Simple(item_def) => item_def.num_slots(),
634            ItemBase::Modular(_) => 0,
635        }
636    }
637
638    // Should be kept the same as the persistence_item_id function in Item
639    // TODO: Maybe use Cow?
640    fn serialization_item_id(&self) -> String {
641        match &self {
642            ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
643            ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
644        }
645    }
646
647    fn from_item_id_string(item_id_string: &str) -> Result<Self, Error> {
648        if item_id_string.starts_with(crate::modular_item_id_prefix!()) {
649            Ok(ItemBase::Modular(ModularBase::load_from_pseudo_id(
650                item_id_string,
651            )))
652        } else {
653            Ok(ItemBase::Simple(Arc::<ItemDef>::load_cloned(
654                item_id_string,
655            )?))
656        }
657    }
658}
659
660// TODO: could this theorectically hold a ref to the actual components and
661// lazily get their IDs for hash/partialeq/debug/to_owned/etc? (i.e. eliminating
662// `Vec`s)
663#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
664pub enum ItemDefinitionId<'a> {
665    Simple(Cow<'a, str>),
666    Modular {
667        pseudo_base: &'a str,
668        components: Vec<ItemDefinitionId<'a>>,
669    },
670    Compound {
671        simple_base: &'a str,
672        components: Vec<ItemDefinitionId<'a>>,
673    },
674}
675
676#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
677pub enum ItemDefinitionIdOwned {
678    Simple(String),
679    Modular {
680        pseudo_base: String,
681        components: Vec<ItemDefinitionIdOwned>,
682    },
683    Compound {
684        simple_base: String,
685        components: Vec<ItemDefinitionIdOwned>,
686    },
687}
688
689impl ItemDefinitionIdOwned {
690    pub fn as_ref(&self) -> ItemDefinitionId<'_> {
691        match *self {
692            Self::Simple(ref id) => ItemDefinitionId::Simple(Cow::Borrowed(id)),
693            Self::Modular {
694                ref pseudo_base,
695                ref components,
696            } => ItemDefinitionId::Modular {
697                pseudo_base,
698                components: components.iter().map(|comp| comp.as_ref()).collect(),
699            },
700            Self::Compound {
701                ref simple_base,
702                ref components,
703            } => ItemDefinitionId::Compound {
704                simple_base,
705                components: components.iter().map(|comp| comp.as_ref()).collect(),
706            },
707        }
708    }
709}
710
711impl ItemDefinitionId<'_> {
712    pub fn itemdef_id(&self) -> Option<&str> {
713        match self {
714            Self::Simple(id) => Some(id),
715            Self::Modular { .. } => None,
716            Self::Compound { simple_base, .. } => Some(simple_base),
717        }
718    }
719
720    pub fn to_owned(&self) -> ItemDefinitionIdOwned {
721        match self {
722            Self::Simple(id) => ItemDefinitionIdOwned::Simple(String::from(&**id)),
723            Self::Modular {
724                pseudo_base,
725                components,
726            } => ItemDefinitionIdOwned::Modular {
727                pseudo_base: String::from(*pseudo_base),
728                components: components.iter().map(|comp| comp.to_owned()).collect(),
729            },
730            Self::Compound {
731                simple_base,
732                components,
733            } => ItemDefinitionIdOwned::Compound {
734                simple_base: String::from(*simple_base),
735                components: components.iter().map(|comp| comp.to_owned()).collect(),
736            },
737        }
738    }
739}
740
741#[derive(Debug, Serialize, Deserialize)]
742pub struct ItemDef {
743    #[serde(default)]
744    /// The string that refers to the filepath to the asset, relative to the
745    /// assets folder, which the ItemDef is loaded from. The name space
746    /// prepended with `veloren.core` is reserved for veloren functions.
747    item_definition_id: String,
748    #[deprecated = "since item i18n"]
749    name: String,
750    #[deprecated = "since item i18n"]
751    description: String,
752    pub kind: ItemKind,
753    pub quality: Quality,
754    pub tags: Vec<ItemTag>,
755    #[serde(default)]
756    pub slots: u16,
757    /// Used to specify a custom ability set for a weapon. Leave None (or don't
758    /// include field in ItemDef) to use default ability set for weapon kind.
759    pub ability_spec: Option<AbilitySpec>,
760}
761
762impl PartialEq for ItemDef {
763    fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
764}
765
766// TODO: Look into removing ItemConfig and just using AbilitySet
767#[derive(Clone, Debug, Serialize, Deserialize)]
768pub struct ItemConfig {
769    pub abilities: AbilitySet<tool::AbilityItem>,
770}
771
772#[derive(Debug)]
773pub enum ItemConfigError {
774    BadItemKind,
775}
776
777impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
778    type Error = ItemConfigError;
779
780    fn try_from(
781        // TODO: Either remove msm or use it as argument in fn kind
782        (item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
783    ) -> Result<Self, Self::Error> {
784        match &*item.kind() {
785            ItemKind::Tool(tool) => {
786                // If no custom ability set is specified, fall back to abilityset of tool kind.
787                let tool_default = |tool_kind| {
788                    let key = &AbilitySpec::Tool(tool_kind);
789                    ability_map.get_ability_set(key)
790                };
791                let abilities = if let Some(set_key) = item.ability_spec() {
792                    if let Some(set) = ability_map.get_ability_set(&set_key) {
793                        set.clone()
794                            .modified_by_tool(tool, item.stats_durability_multiplier())
795                    } else {
796                        error!(
797                            "Custom ability set: {:?} references non-existent set, falling back \
798                             to default ability set.",
799                            set_key
800                        );
801                        tool_default(tool.kind).cloned().unwrap_or_default()
802                    }
803                } else if let Some(set) = tool_default(tool.kind) {
804                    set.clone()
805                        .modified_by_tool(tool, item.stats_durability_multiplier())
806                } else {
807                    error!(
808                        "No ability set defined for tool: {:?}, falling back to default ability \
809                         set.",
810                        tool.kind
811                    );
812                    Default::default()
813                };
814
815                Ok(ItemConfig { abilities })
816            },
817            ItemKind::Glider => item
818                .ability_spec()
819                .and_then(|set_key| ability_map.get_ability_set(&set_key))
820                .map(|abilities| ItemConfig {
821                    abilities: abilities.clone(),
822                })
823                .ok_or(ItemConfigError::BadItemKind),
824            _ => Err(ItemConfigError::BadItemKind),
825        }
826    }
827}
828
829impl ItemDef {
830    pub fn is_stackable(&self) -> bool {
831        matches!(
832            self.kind,
833            ItemKind::Consumable { .. }
834                | ItemKind::Ingredient { .. }
835                | ItemKind::Utility { .. }
836                | ItemKind::Tool(Tool {
837                    kind: ToolKind::Throwable,
838                    ..
839                })
840        )
841    }
842
843    /// NOTE: invariant that amount() ≤ max_amount(), 1 ≤ max_amount(),
844    /// and if !self.is_stackable(), self.max_amount() = 1.
845    pub fn max_amount(&self) -> u32 { if self.is_stackable() { u32::MAX } else { 1 } }
846
847    // currently needed by trade_pricing
848    pub fn id(&self) -> &str { &self.item_definition_id }
849
850    #[cfg(test)]
851    pub fn new_test(
852        item_definition_id: String,
853        kind: ItemKind,
854        quality: Quality,
855        tags: Vec<ItemTag>,
856        slots: u16,
857    ) -> Self {
858        #[expect(deprecated)]
859        Self {
860            item_definition_id,
861            name: "test item name".to_owned(),
862            description: "test item description".to_owned(),
863            kind,
864            quality,
865            tags,
866            slots,
867            ability_spec: None,
868        }
869    }
870
871    #[cfg(test)]
872    pub fn create_test_itemdef_from_kind(kind: ItemKind) -> Self {
873        #[expect(deprecated)]
874        Self {
875            item_definition_id: "test.item".to_string(),
876            name: "test item name".to_owned(),
877            description: "test item description".to_owned(),
878            kind,
879            quality: Quality::Common,
880            tags: vec![],
881            slots: 0,
882            ability_spec: None,
883        }
884    }
885}
886
887/// NOTE: This PartialEq instance is pretty broken!  It doesn't check item
888/// amount or any child items (and, arguably, doing so should be able to ignore
889/// things like item order within the main inventory or within each bag, and
890/// possibly even coalesce amounts, though these may be more controversial).
891/// Until such time as we find an actual need for a proper PartialEq instance,
892/// please don't rely on this for anything!
893impl PartialEq for Item {
894    fn eq(&self, other: &Self) -> bool {
895        (match (&self.item_base, &other.item_base) {
896            (ItemBase::Simple(our_def), ItemBase::Simple(other_def)) => {
897                our_def.item_definition_id == other_def.item_definition_id
898            },
899            (ItemBase::Modular(our_base), ItemBase::Modular(other_base)) => our_base == other_base,
900            _ => false,
901        }) && self.components() == other.components()
902    }
903}
904
905impl Asset for ItemDef {
906    fn load(cache: &AssetCache, specifier: &SharedString) -> Result<Self, BoxedError> {
907        if specifier.starts_with("veloren.core.") {
908            return Err(format!(
909                "Attempted to load an asset from a specifier reserved for core veloren functions. \
910                 Specifier: {}",
911                specifier
912            )
913            .into());
914        }
915
916        let RawItemDef {
917            legacy_name,
918            legacy_description,
919            kind,
920            quality,
921            tags,
922            slots,
923            ability_spec,
924        } = cache.load::<Ron<_>>(specifier)?.cloned().into_inner();
925
926        // Some commands like /give_item provide the asset specifier separated with \
927        // instead of .
928        //
929        // TODO: This probably does not belong here
930        let item_definition_id = specifier.replace('\\', ".");
931
932        #[expect(deprecated)]
933        Ok(ItemDef {
934            item_definition_id,
935            name: legacy_name,
936            description: legacy_description,
937            kind,
938            quality,
939            tags,
940            slots,
941            ability_spec,
942        })
943    }
944}
945
946#[derive(Clone, Debug, Serialize, Deserialize)]
947#[serde(rename = "ItemDef", deny_unknown_fields)]
948struct RawItemDef {
949    legacy_name: String,
950    legacy_description: String,
951    kind: ItemKind,
952    quality: Quality,
953    tags: Vec<ItemTag>,
954    #[serde(default)]
955    slots: u16,
956    ability_spec: Option<AbilitySpec>,
957}
958
959#[derive(Debug)]
960pub struct OperationFailure;
961
962impl Item {
963    pub const MAX_DURABILITY: u32 = 12;
964
965    // TODO: consider alternatives such as default abilities that can be added to a
966    // loadout when no weapon is present
967    pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
968
969    pub fn new_from_item_base(
970        inner_item: ItemBase,
971        components: Vec<Item>,
972        ability_map: &AbilityMap,
973        msm: &MaterialStatManifest,
974    ) -> Self {
975        let mut item = Item {
976            item_id: Arc::new(AtomicCell::new(None)),
977            amount: NonZeroU32::new(1).unwrap(),
978            components,
979            slots: vec![None; inner_item.num_slots() as usize],
980            item_base: inner_item,
981            // These fields are updated immediately below
982            item_config: None,
983            hash: 0,
984            durability_lost: None,
985        };
986        item.durability_lost = item.has_durability().then_some(0);
987        item.update_item_state(ability_map, msm);
988        item
989    }
990
991    pub fn new_from_item_definition_id(
992        item_definition_id: ItemDefinitionId<'_>,
993        ability_map: &AbilityMap,
994        msm: &MaterialStatManifest,
995    ) -> Result<Self, Error> {
996        let (base, components) = match item_definition_id {
997            ItemDefinitionId::Simple(spec) => {
998                let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(&spec)?);
999                (base, Vec::new())
1000            },
1001            ItemDefinitionId::Modular {
1002                pseudo_base,
1003                components,
1004            } => {
1005                let base = ItemBase::Modular(ModularBase::load_from_pseudo_id(pseudo_base));
1006                let components = components
1007                    .into_iter()
1008                    .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1009                    .collect::<Result<Vec<_>, _>>()?;
1010                (base, components)
1011            },
1012            ItemDefinitionId::Compound {
1013                simple_base,
1014                components,
1015            } => {
1016                let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(simple_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        };
1024        Ok(Item::new_from_item_base(base, components, ability_map, msm))
1025    }
1026
1027    /// Creates a new instance of an `Item` from the provided asset identifier
1028    /// Panics if the asset does not exist.
1029    pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
1030        Item::new_from_asset(asset_specifier).unwrap_or_else(|err| {
1031            panic!(
1032                "Expected asset to exist: {}, instead got error {:?}",
1033                asset_specifier, err
1034            );
1035        })
1036    }
1037
1038    /// Creates a Vec containing one of each item that matches the provided
1039    /// asset glob pattern
1040    pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
1041        let specifier = asset_glob.strip_suffix(".*").unwrap_or(asset_glob);
1042        let defs = assets::load_rec_dir::<Ron<RawItemDef>>(specifier)?;
1043        defs.read()
1044            .ids()
1045            .map(|id| Item::new_from_asset(id))
1046            .collect()
1047    }
1048
1049    /// Creates a new instance of an `Item from the provided asset identifier if
1050    /// it exists
1051    pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
1052        let inner_item = ItemBase::from_item_id_string(asset)?;
1053        // TODO: Get msm and ability_map less hackily
1054        let msm = &MaterialStatManifest::load().read();
1055        let ability_map = &AbilityMap::load().read();
1056        Ok(Item::new_from_item_base(
1057            inner_item,
1058            Vec::new(),
1059            ability_map,
1060            msm,
1061        ))
1062    }
1063
1064    /// Creates a [`FrontendItem`] out of this item for frontend use
1065    #[must_use]
1066    pub fn frontend_item(
1067        &self,
1068        ability_map: &AbilityMap,
1069        msm: &MaterialStatManifest,
1070    ) -> FrontendItem {
1071        FrontendItem(self.duplicate(ability_map, msm))
1072    }
1073
1074    /// Duplicates an item, creating an exact copy but with a new item ID
1075    #[must_use]
1076    pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1077        let duplicated_components = self
1078            .components
1079            .iter()
1080            .map(|comp| comp.duplicate(ability_map, msm))
1081            .collect();
1082        let mut new_item = Item::new_from_item_base(
1083            match &self.item_base {
1084                ItemBase::Simple(item_def) => ItemBase::Simple(Arc::clone(item_def)),
1085                ItemBase::Modular(mod_base) => ItemBase::Modular(mod_base.clone()),
1086            },
1087            duplicated_components,
1088            ability_map,
1089            msm,
1090        );
1091        new_item.set_amount(self.amount()).expect(
1092            "`new_item` has the same `item_def` and as an invariant, \
1093             self.set_amount(self.amount()) should always succeed.",
1094        );
1095        new_item.slots_mut().iter_mut().zip(self.slots()).for_each(
1096            |(new_item_slot, old_item_slot)| {
1097                *new_item_slot = old_item_slot
1098                    .as_ref()
1099                    .map(|old_item| old_item.duplicate(ability_map, msm));
1100            },
1101        );
1102        new_item
1103    }
1104
1105    pub fn stacked_duplicates<'a>(
1106        &'a self,
1107        ability_map: &'a AbilityMap,
1108        msm: &'a MaterialStatManifest,
1109        count: u32,
1110    ) -> impl Iterator<Item = Self> + 'a {
1111        let max_stack_count = count / self.max_amount();
1112        let rest = count % self.max_amount();
1113
1114        (0..max_stack_count)
1115            .map(|_| {
1116                let mut item = self.duplicate(ability_map, msm);
1117
1118                item.set_amount(item.max_amount())
1119                    .expect("max_amount() is always a valid amount.");
1120
1121                item
1122            })
1123            .chain((rest > 0).then(move || {
1124                let mut item = self.duplicate(ability_map, msm);
1125
1126                item.set_amount(rest)
1127                    .expect("anything less than max_amount() is always a valid amount.");
1128
1129                item
1130            }))
1131    }
1132
1133    /// FIXME: HACK: In order to set the entity ID asynchronously, we currently
1134    /// start it at None, and then atomically set it when it's saved for the
1135    /// first time in the database.  Because this requires shared mutable
1136    /// state if these aren't synchronized by the program structure,
1137    /// currently we use an Atomic inside an Arc; this is clearly very
1138    /// dangerous, so in the future we will hopefully have a better way of
1139    /// dealing with this.
1140    #[doc(hidden)]
1141    pub fn get_item_id_for_database(&self) -> Arc<ItemId> { Arc::clone(&self.item_id) }
1142
1143    /// Resets the item's item ID to None, giving it a new identity. Used when
1144    /// dropping items into the world so that a new database record is
1145    /// created when they are picked up again.
1146    ///
1147    /// NOTE: The creation of a new `Arc` when resetting the item ID is critical
1148    /// because every time a new `Item` instance is created, it is cloned from
1149    /// a single asset which results in an `Arc` pointing to the same value in
1150    /// memory. Therefore, every time an item instance is created this
1151    /// method must be called in order to give it a unique identity.
1152    fn reset_item_id(&mut self) {
1153        if let Some(item_id) = Arc::get_mut(&mut self.item_id) {
1154            *item_id = AtomicCell::new(None);
1155        } else {
1156            self.item_id = Arc::new(AtomicCell::new(None));
1157        }
1158        // Reset item id for every component of an item too
1159        for component in self.components.iter_mut() {
1160            component.reset_item_id();
1161        }
1162    }
1163
1164    /// Removes the unique identity of an item - used when dropping an item on
1165    /// the floor. In the future this will need to be changed if we want to
1166    /// maintain a unique ID for an item even when it's dropped and picked
1167    /// up by another player.
1168    pub fn put_in_world(&mut self) { self.reset_item_id() }
1169
1170    pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), OperationFailure> {
1171        let amount = u32::from(self.amount);
1172        self.amount = amount
1173            .checked_add(increase_by)
1174            .filter(|&amount| amount <= self.max_amount())
1175            .and_then(NonZeroU32::new)
1176            .ok_or(OperationFailure)?;
1177        Ok(())
1178    }
1179
1180    pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), OperationFailure> {
1181        let amount = u32::from(self.amount);
1182        self.amount = amount
1183            .checked_sub(decrease_by)
1184            .and_then(NonZeroU32::new)
1185            .ok_or(OperationFailure)?;
1186        Ok(())
1187    }
1188
1189    pub fn set_amount(&mut self, give_amount: u32) -> Result<(), OperationFailure> {
1190        if give_amount <= self.max_amount() {
1191            self.amount = NonZeroU32::new(give_amount).ok_or(OperationFailure)?;
1192            Ok(())
1193        } else {
1194            Err(OperationFailure)
1195        }
1196    }
1197
1198    pub fn persistence_access_add_component(&mut self, component: Item) {
1199        self.components.push(component);
1200    }
1201
1202    pub fn persistence_access_mutable_component(&mut self, index: usize) -> Option<&mut Self> {
1203        self.components.get_mut(index)
1204    }
1205
1206    /// Updates state of an item (important for creation of new items,
1207    /// persistence, and if components are ever added to items after initial
1208    /// creation)
1209    pub fn update_item_state(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1210        // Updates item config of an item
1211        if let Ok(item_config) = ItemConfig::try_from((&*self, ability_map, msm)) {
1212            self.item_config = Some(Box::new(item_config));
1213        }
1214        // Updates hash of an item
1215        self.hash = {
1216            let mut s = DefaultHasher::new();
1217            self.hash(&mut s);
1218            s.finish()
1219        };
1220    }
1221
1222    /// Returns an iterator that drains items contained within the item's slots
1223    pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
1224        self.slots.iter_mut().filter_map(mem::take)
1225    }
1226
1227    pub fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1228        match &self.item_base {
1229            ItemBase::Simple(item_def) => {
1230                if self.components.is_empty() {
1231                    ItemDefinitionId::Simple(Cow::Borrowed(&item_def.item_definition_id))
1232                } else {
1233                    ItemDefinitionId::Compound {
1234                        simple_base: &item_def.item_definition_id,
1235                        components: self
1236                            .components
1237                            .iter()
1238                            .map(|item| item.item_definition_id())
1239                            .collect(),
1240                    }
1241                }
1242            },
1243            ItemBase::Modular(mod_base) => ItemDefinitionId::Modular {
1244                pseudo_base: mod_base.pseudo_item_id(),
1245                components: self
1246                    .components
1247                    .iter()
1248                    .map(|item| item.item_definition_id())
1249                    .collect(),
1250            },
1251        }
1252    }
1253
1254    pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
1255        if let ItemBase::Simple(self_def) = &self.item_base {
1256            self_def.item_definition_id == item_def.item_definition_id
1257        } else {
1258            false
1259        }
1260    }
1261
1262    pub fn matches_recipe_input(&self, recipe_input: &RecipeInput, amount: u32) -> bool {
1263        match recipe_input {
1264            RecipeInput::Item(item_def) => self.is_same_item_def(item_def),
1265            RecipeInput::Tag(tag) => self.tags().contains(tag),
1266            RecipeInput::TagSameItem(tag) => {
1267                self.tags().contains(tag) && u32::from(self.amount) >= amount
1268            },
1269            RecipeInput::ListSameItem(item_defs) => item_defs.iter().any(|item_def| {
1270                self.is_same_item_def(item_def) && u32::from(self.amount) >= amount
1271            }),
1272        }
1273    }
1274
1275    pub fn is_salvageable(&self) -> bool {
1276        self.tags()
1277            .iter()
1278            .any(|tag| matches!(tag, ItemTag::SalvageInto(_, _)))
1279    }
1280
1281    pub fn salvage_output(&self) -> impl Iterator<Item = (&str, u32)> {
1282        self.tags().into_iter().filter_map(|tag| {
1283            if let ItemTag::SalvageInto(material, quantity) = tag {
1284                material
1285                    .asset_identifier()
1286                    .map(|material_id| (material_id, quantity))
1287            } else {
1288                None
1289            }
1290        })
1291    }
1292
1293    #[deprecated = "since item i18n"]
1294    pub fn name(&self) -> Cow<'_, str> {
1295        match &self.item_base {
1296            ItemBase::Simple(item_def) => {
1297                if self.components.is_empty() {
1298                    #[expect(deprecated)]
1299                    Cow::Borrowed(&item_def.name)
1300                } else {
1301                    #[expect(deprecated)]
1302                    modular::modify_name(&item_def.name, self)
1303                }
1304            },
1305            ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()),
1306        }
1307    }
1308
1309    #[deprecated = "since item i18n"]
1310    pub fn description(&self) -> &str {
1311        match &self.item_base {
1312            #[expect(deprecated)]
1313            ItemBase::Simple(item_def) => &item_def.description,
1314            // TODO: See if James wanted to make description, else leave with none
1315            ItemBase::Modular(_) => "",
1316        }
1317    }
1318
1319    pub fn kind(&self) -> Cow<'_, ItemKind> {
1320        match &self.item_base {
1321            ItemBase::Simple(item_def) => Cow::Borrowed(&item_def.kind),
1322            ItemBase::Modular(mod_base) => {
1323                // TODO: Try to move further upward
1324                let msm = &MaterialStatManifest::load().read();
1325                mod_base.kind(self.components(), msm, self.stats_durability_multiplier())
1326            },
1327        }
1328    }
1329
1330    pub fn amount(&self) -> u32 { u32::from(self.amount) }
1331
1332    pub fn is_stackable(&self) -> bool {
1333        match &self.item_base {
1334            ItemBase::Simple(item_def) => item_def.is_stackable(),
1335            // TODO: Let whoever implements stackable modular items deal with this
1336            ItemBase::Modular(_) => false,
1337        }
1338    }
1339
1340    /// NOTE: invariant that amount() ≤ max_amount(), 1 ≤ max_amount(),
1341    /// and if !self.is_stackable(), self.max_amount() = 1.
1342    pub fn max_amount(&self) -> u32 {
1343        match &self.item_base {
1344            ItemBase::Simple(item_def) => item_def.max_amount(),
1345            ItemBase::Modular(_) => {
1346                debug_assert!(!self.is_stackable());
1347                1
1348            },
1349        }
1350    }
1351
1352    pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
1353
1354    pub fn quality(&self) -> Quality {
1355        match &self.item_base {
1356            ItemBase::Simple(item_def) => item_def.quality.max(
1357                self.components
1358                    .iter()
1359                    .fold(Quality::MIN, |a, b| a.max(b.quality())),
1360            ),
1361            ItemBase::Modular(mod_base) => mod_base.compute_quality(self.components()),
1362        }
1363    }
1364
1365    pub fn components(&self) -> &[Item] { &self.components }
1366
1367    pub fn slots(&self) -> &[InvSlot] { &self.slots }
1368
1369    pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
1370
1371    pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() }
1372
1373    pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }
1374
1375    pub fn populated_slots(&self) -> usize { self.slots().len().saturating_sub(self.free_slots()) }
1376
1377    pub fn slot(&self, slot: usize) -> Option<&InvSlot> { self.slots.get(slot) }
1378
1379    pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
1380
1381    pub fn try_reclaim_from_block(
1382        block: Block,
1383        sprite_cfg: Option<&SpriteCfg>,
1384    ) -> Option<Vec<(u32, Self)>> {
1385        if let Some(loot_spec) = sprite_cfg.and_then(|sprite_cfg| sprite_cfg.loot_table.as_ref()) {
1386            LootSpec::LootTable(loot_spec).to_items()
1387        } else {
1388            block.get_sprite()?.default_loot_spec()??.to_items()
1389        }
1390    }
1391
1392    pub fn ability_spec(&self) -> Option<Cow<'_, AbilitySpec>> {
1393        match &self.item_base {
1394            ItemBase::Simple(item_def) => {
1395                item_def.ability_spec.as_ref().map(Cow::Borrowed).or({
1396                    // If no custom ability set is specified, fall back to abilityset of tool
1397                    // kind.
1398                    if let ItemKind::Tool(tool) = &item_def.kind {
1399                        Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
1400                    } else {
1401                        None
1402                    }
1403                })
1404            },
1405            ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
1406        }
1407    }
1408
1409    // TODO: Maybe try to make slice again instead of vec? Could also try to make an
1410    // iterator?
1411    pub fn tags(&self) -> Vec<ItemTag> {
1412        match &self.item_base {
1413            ItemBase::Simple(item_def) => item_def.tags.to_vec(),
1414            // TODO: Do this properly. It'll probably be important at some point.
1415            ItemBase::Modular(mod_base) => mod_base.generate_tags(self.components()),
1416        }
1417    }
1418
1419    pub fn is_modular(&self) -> bool {
1420        match &self.item_base {
1421            ItemBase::Simple(_) => false,
1422            ItemBase::Modular(_) => true,
1423        }
1424    }
1425
1426    pub fn item_hash(&self) -> u64 { self.hash }
1427
1428    pub fn persistence_item_id(&self) -> String {
1429        match &self.item_base {
1430            ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
1431            ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
1432        }
1433    }
1434
1435    pub fn durability_lost(&self) -> Option<u32> {
1436        self.durability_lost.map(|x| x.min(Self::MAX_DURABILITY))
1437    }
1438
1439    pub fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1440        let durability_lost = self.durability_lost.unwrap_or(0);
1441        debug_assert!(durability_lost <= Self::MAX_DURABILITY);
1442        // How much durability must be lost before stats start to decay
1443        const DURABILITY_THRESHOLD: u32 = 9;
1444        const MIN_FRAC: f32 = 0.25;
1445        let mult = (1.0
1446            - durability_lost.saturating_sub(DURABILITY_THRESHOLD) as f32
1447                / (Self::MAX_DURABILITY - DURABILITY_THRESHOLD) as f32)
1448            * (1.0 - MIN_FRAC)
1449            + MIN_FRAC;
1450        DurabilityMultiplier(mult)
1451    }
1452
1453    pub fn has_durability(&self) -> bool {
1454        self.kind().has_durability() && self.quality() != Quality::Debug
1455    }
1456
1457    pub fn increment_damage(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1458        if let Some(durability_lost) = &mut self.durability_lost
1459            && *durability_lost < Self::MAX_DURABILITY
1460        {
1461            *durability_lost += 1;
1462        }
1463        // Update item state after applying durability because stats have potential to
1464        // change from different durability
1465        self.update_item_state(ability_map, msm);
1466    }
1467
1468    pub fn persistence_durability(&self) -> Option<NonZeroU32> {
1469        self.durability_lost.and_then(NonZeroU32::new)
1470    }
1471
1472    pub fn persistence_set_durability(&mut self, value: Option<NonZeroU32>) {
1473        // If changes have been made so that item no longer needs to track durability,
1474        // set to None
1475        if !self.has_durability() {
1476            self.durability_lost = None;
1477        } else {
1478            // Set durability to persisted value, and if item previously had no durability,
1479            // set to Some(0) so that durability will be tracked
1480            self.durability_lost = Some(value.map_or(0, NonZeroU32::get));
1481        }
1482    }
1483
1484    pub fn reset_durability(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1485        self.durability_lost = self.has_durability().then_some(0);
1486        // Update item state after applying durability because stats have potential to
1487        // change from different durability
1488        self.update_item_state(ability_map, msm);
1489    }
1490
1491    /// If an item is stackable and has an amount greater than the requested
1492    /// amount, decreases the amount of the original item by the same
1493    /// quantity and return a copy of the item with the taken amount.
1494    #[must_use = "Returned items will be lost if not used"]
1495    pub fn take_amount(
1496        &mut self,
1497        ability_map: &AbilityMap,
1498        msm: &MaterialStatManifest,
1499        returning_amount: u32,
1500    ) -> Option<Item> {
1501        if self.is_stackable() && self.amount() > 1 && returning_amount < self.amount() {
1502            let mut return_item = self.duplicate(ability_map, msm);
1503            self.decrease_amount(returning_amount).ok()?;
1504            return_item.set_amount(returning_amount).expect(
1505                "return_item.amount() = returning_amount < self.amount() (since self.amount() ≥ \
1506                 1) ≤ self.max_amount() = return_item.max_amount(), since return_item is a \
1507                 duplicate of item",
1508            );
1509            Some(return_item)
1510        } else {
1511            None
1512        }
1513    }
1514
1515    /// If an item is stackable and has an amount greater than 1, creates a new
1516    /// item with half the amount (rounded down), and decreases the amount of
1517    /// the original item by the same quantity.
1518    #[must_use = "Returned items will be lost if not used"]
1519    pub fn take_half(
1520        &mut self,
1521        ability_map: &AbilityMap,
1522        msm: &MaterialStatManifest,
1523    ) -> Option<Item> {
1524        self.take_amount(ability_map, msm, self.amount() / 2)
1525    }
1526
1527    #[cfg(test)]
1528    pub fn create_test_item_from_kind(kind: ItemKind) -> Self {
1529        let ability_map = &AbilityMap::load().read();
1530        let msm = &MaterialStatManifest::load().read();
1531        Self::new_from_item_base(
1532            ItemBase::Simple(Arc::new(ItemDef::create_test_itemdef_from_kind(kind))),
1533            Vec::new(),
1534            ability_map,
1535            msm,
1536        )
1537    }
1538
1539    /// Checks if this item and another are suitable for grouping into the same
1540    /// [`PickupItem`].
1541    ///
1542    /// Also see [`Item::try_merge`].
1543    pub fn can_merge(&self, other: &Self) -> bool {
1544        if self.amount() > self.max_amount() || other.amount() > other.max_amount() {
1545            error!("An item amount is over max_amount!");
1546            return false;
1547        }
1548
1549        (self == other)
1550            && self.slots().iter().all(Option::is_none)
1551            && other.slots().iter().all(Option::is_none)
1552            && self.durability_lost() == other.durability_lost()
1553    }
1554
1555    /// Checks if this item and another are suitable for grouping into the same
1556    /// [`PickupItem`] and combines stackable items if possible.
1557    ///
1558    /// If the sum of both amounts is larger than their max amount, a remainder
1559    /// item is returned as `Ok(Some(remainder))`. A remainder item will
1560    /// always be produced for non-stackable items.
1561    ///
1562    /// If the items are not suitable for grouping `Err(other)` will be
1563    /// returned.
1564    pub fn try_merge(&mut self, mut other: Self) -> Result<Option<Self>, Self> {
1565        if self.can_merge(&other) {
1566            let max_amount = self.max_amount();
1567            debug_assert_eq!(
1568                max_amount,
1569                other.max_amount(),
1570                "Mergeable items must have the same max_amount()"
1571            );
1572
1573            // Additional amount `self` can hold
1574            // For non-stackable items this is always zero
1575            let to_fill_self = max_amount
1576                .checked_sub(self.amount())
1577                .expect("can_merge should ensure that amount() <= max_amount()");
1578
1579            if let Some(remainder) = other.amount().checked_sub(to_fill_self).filter(|r| *r > 0) {
1580                self.set_amount(max_amount)
1581                    .expect("max_amount() is always a valid amount.");
1582                other.set_amount(remainder).expect(
1583                    "We know remainder is more than 0 and less than or equal to max_amount()",
1584                );
1585                Ok(Some(other))
1586            } else {
1587                // If there would be no remainder, add the amounts!
1588                self.increase_amount(other.amount())
1589                    .expect("We know that we can at least add other.amount() to this item");
1590                drop(other);
1591                Ok(None)
1592            }
1593        } else {
1594            Err(other)
1595        }
1596    }
1597
1598    // Probably doesn't need to be limited to persistence, but nothing else should
1599    // really need to look at item base
1600    pub fn persistence_item_base(&self) -> &ItemBase { &self.item_base }
1601}
1602
1603impl FrontendItem {
1604    /// See [`Item::duplicate`], the returned item will still be a
1605    /// [`FrontendItem`]
1606    #[must_use]
1607    pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1608        FrontendItem(self.0.duplicate(ability_map, msm))
1609    }
1610
1611    pub fn set_amount(&mut self, amount: u32) -> Result<(), OperationFailure> {
1612        self.0.set_amount(amount)
1613    }
1614}
1615
1616impl PickupItem {
1617    pub fn new(item: Item, time: ProgramTime, should_merge: bool) -> Self {
1618        Self {
1619            items: vec![item],
1620            created_at: time,
1621            next_merge_check: time,
1622            should_merge,
1623        }
1624    }
1625
1626    /// Get a reference to the last item in this stack
1627    ///
1628    /// The amount of this item should *not* be used.
1629    pub fn item(&self) -> &Item {
1630        self.items
1631            .last()
1632            .expect("PickupItem without at least one item is an invariant")
1633    }
1634
1635    pub fn created(&self) -> ProgramTime { self.created_at }
1636
1637    pub fn next_merge_check(&self) -> ProgramTime { self.next_merge_check }
1638
1639    pub fn next_merge_check_mut(&mut self) -> &mut ProgramTime { &mut self.next_merge_check }
1640
1641    // Get the total amount of items in here
1642    pub fn amount(&self) -> u32 {
1643        self.items
1644            .iter()
1645            .map(Item::amount)
1646            .fold(0, |total, amount| total.saturating_add(amount))
1647    }
1648
1649    /// Remove any debug items if this is a container, used before dropping an
1650    /// item from an inventory
1651    pub fn remove_debug_items(&mut self) {
1652        for item in self.items.iter_mut() {
1653            item.slots_mut().iter_mut().for_each(|container_slot| {
1654                container_slot
1655                    .take_if(|contained_item| matches!(contained_item.quality(), Quality::Debug));
1656            });
1657        }
1658    }
1659
1660    pub fn can_merge(&self, other: &PickupItem) -> bool {
1661        let self_item = self.item();
1662        let other_item = other.item();
1663
1664        self.should_merge && other.should_merge && self_item.can_merge(other_item)
1665    }
1666
1667    // Attempt to merge another PickupItem into this one, can only fail if
1668    // `can_merge` returns false
1669    pub fn try_merge(&mut self, mut other: PickupItem) -> Result<(), PickupItem> {
1670        if self.can_merge(&other) {
1671            // Pop the last item from `self` and `other` to merge them, as only the last
1672            // items can have an amount != max_amount()
1673            let mut self_last = self
1674                .items
1675                .pop()
1676                .expect("PickupItem without at least one item is an invariant");
1677            let other_last = other
1678                .items
1679                .pop()
1680                .expect("PickupItem without at least one item is an invariant");
1681
1682            // Merge other_last into self_last
1683            let merged = self_last
1684                .try_merge(other_last)
1685                .expect("We know these items can be merged");
1686
1687            debug_assert!(
1688                other
1689                    .items
1690                    .iter()
1691                    .chain(self.items.iter())
1692                    .all(|item| item.amount() == item.max_amount()),
1693                "All items before the last in `PickupItem` should have a full amount"
1694            );
1695
1696            // We know all items except the last have a full amount, so we can safely append
1697            // them here
1698            self.items.append(&mut other.items);
1699
1700            debug_assert!(
1701                merged.is_none() || self_last.amount() == self_last.max_amount(),
1702                "Merged can only be `Some` if the origin was set to `max_amount()`"
1703            );
1704
1705            // Push the potentially not fully-stacked item at the end
1706            self.items.push(self_last);
1707
1708            // Push the remainder, merged is only `Some` if self_last was set to
1709            // `max_amount()`
1710            if let Some(remainder) = merged {
1711                self.items.push(remainder);
1712            }
1713
1714            Ok(())
1715        } else {
1716            Err(other)
1717        }
1718    }
1719
1720    pub fn pick_up(mut self) -> (Item, Option<Self>) {
1721        (
1722            self.items
1723                .pop()
1724                .expect("PickupItem without at least one item is an invariant"),
1725            (!self.items.is_empty()).then_some(self),
1726        )
1727    }
1728}
1729
1730pub fn flatten_counted_items<'a>(
1731    items: &'a [(u32, Item)],
1732    ability_map: &'a AbilityMap,
1733    msm: &'a MaterialStatManifest,
1734) -> impl Iterator<Item = Item> + 'a {
1735    items
1736        .iter()
1737        .flat_map(|(count, item)| item.stacked_duplicates(ability_map, msm, *count))
1738}
1739
1740/// Provides common methods providing details about an item definition
1741/// for either an `Item` containing the definition, or the actual `ItemDef`
1742pub trait ItemDesc {
1743    #[deprecated = "since item i18n"]
1744    fn description(&self) -> &str;
1745    #[deprecated = "since item i18n"]
1746    fn name(&self) -> Cow<'_, str>;
1747    fn kind(&self) -> Cow<'_, ItemKind>;
1748    fn amount(&self) -> NonZeroU32;
1749    fn quality(&self) -> Quality;
1750    fn num_slots(&self) -> u16;
1751    fn item_definition_id(&self) -> ItemDefinitionId<'_>;
1752    fn tags(&self) -> Vec<ItemTag>;
1753    fn is_modular(&self) -> bool;
1754    fn components(&self) -> &[Item];
1755    fn has_durability(&self) -> bool;
1756    fn durability_lost(&self) -> Option<u32>;
1757    fn stats_durability_multiplier(&self) -> DurabilityMultiplier;
1758
1759    fn tool_info(&self) -> Option<ToolKind> {
1760        if let ItemKind::Tool(tool) = &*self.kind() {
1761            Some(tool.kind)
1762        } else {
1763            None
1764        }
1765    }
1766
1767    /// Return name's and description's localization descriptors
1768    fn i18n(&self, i18n: &ItemI18n) -> (Content, Content) {
1769        let item_key: ItemKey = self.into();
1770
1771        #[expect(deprecated)]
1772        i18n.item_text_opt(item_key).unwrap_or_else(|| {
1773            (
1774                Content::Plain(self.name().to_string()),
1775                Content::Plain(self.description().to_string()),
1776            )
1777        })
1778    }
1779}
1780
1781impl ItemDesc for Item {
1782    fn description(&self) -> &str {
1783        #[expect(deprecated)]
1784        self.description()
1785    }
1786
1787    fn name(&self) -> Cow<'_, str> {
1788        #[expect(deprecated)]
1789        self.name()
1790    }
1791
1792    fn kind(&self) -> Cow<'_, ItemKind> { self.kind() }
1793
1794    fn amount(&self) -> NonZeroU32 { self.amount }
1795
1796    fn quality(&self) -> Quality { self.quality() }
1797
1798    fn num_slots(&self) -> u16 { self.num_slots() }
1799
1800    fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item_definition_id() }
1801
1802    fn tags(&self) -> Vec<ItemTag> { self.tags() }
1803
1804    fn is_modular(&self) -> bool { self.is_modular() }
1805
1806    fn components(&self) -> &[Item] { self.components() }
1807
1808    fn has_durability(&self) -> bool { self.has_durability() }
1809
1810    fn durability_lost(&self) -> Option<u32> { self.durability_lost() }
1811
1812    fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1813        self.stats_durability_multiplier()
1814    }
1815}
1816
1817impl ItemDesc for FrontendItem {
1818    fn description(&self) -> &str {
1819        #[expect(deprecated)]
1820        self.0.description()
1821    }
1822
1823    fn name(&self) -> Cow<'_, str> {
1824        #[expect(deprecated)]
1825        self.0.name()
1826    }
1827
1828    fn kind(&self) -> Cow<'_, ItemKind> { self.0.kind() }
1829
1830    fn amount(&self) -> NonZeroU32 { self.0.amount }
1831
1832    fn quality(&self) -> Quality { self.0.quality() }
1833
1834    fn num_slots(&self) -> u16 { self.0.num_slots() }
1835
1836    fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.0.item_definition_id() }
1837
1838    fn tags(&self) -> Vec<ItemTag> { self.0.tags() }
1839
1840    fn is_modular(&self) -> bool { self.0.is_modular() }
1841
1842    fn components(&self) -> &[Item] { self.0.components() }
1843
1844    fn has_durability(&self) -> bool { self.0.has_durability() }
1845
1846    fn durability_lost(&self) -> Option<u32> { self.0.durability_lost() }
1847
1848    fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1849        self.0.stats_durability_multiplier()
1850    }
1851}
1852
1853impl ItemDesc for ItemDef {
1854    fn description(&self) -> &str {
1855        #[expect(deprecated)]
1856        &self.description
1857    }
1858
1859    fn name(&self) -> Cow<'_, str> {
1860        #[expect(deprecated)]
1861        Cow::Borrowed(&self.name)
1862    }
1863
1864    fn kind(&self) -> Cow<'_, ItemKind> { Cow::Borrowed(&self.kind) }
1865
1866    fn amount(&self) -> NonZeroU32 { NonZeroU32::new(1).unwrap() }
1867
1868    fn quality(&self) -> Quality { self.quality }
1869
1870    fn num_slots(&self) -> u16 { self.slots }
1871
1872    fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1873        ItemDefinitionId::Simple(Cow::Borrowed(&self.item_definition_id))
1874    }
1875
1876    fn tags(&self) -> Vec<ItemTag> { self.tags.to_vec() }
1877
1878    fn is_modular(&self) -> bool { false }
1879
1880    fn components(&self) -> &[Item] { &[] }
1881
1882    fn has_durability(&self) -> bool {
1883        self.kind().has_durability() && self.quality != Quality::Debug
1884    }
1885
1886    fn durability_lost(&self) -> Option<u32> { None }
1887
1888    fn stats_durability_multiplier(&self) -> DurabilityMultiplier { DurabilityMultiplier(1.0) }
1889}
1890
1891impl ItemDesc for PickupItem {
1892    fn description(&self) -> &str {
1893        #[expect(deprecated)]
1894        self.item().description()
1895    }
1896
1897    fn name(&self) -> Cow<'_, str> {
1898        #[expect(deprecated)]
1899        self.item().name()
1900    }
1901
1902    fn kind(&self) -> Cow<'_, ItemKind> { self.item().kind() }
1903
1904    fn amount(&self) -> NonZeroU32 {
1905        NonZeroU32::new(self.amount()).expect("Item having amount of 0 is invariant")
1906    }
1907
1908    fn quality(&self) -> Quality { self.item().quality() }
1909
1910    fn num_slots(&self) -> u16 { self.item().num_slots() }
1911
1912    fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item().item_definition_id() }
1913
1914    fn tags(&self) -> Vec<ItemTag> { self.item().tags() }
1915
1916    fn is_modular(&self) -> bool { self.item().is_modular() }
1917
1918    fn components(&self) -> &[Item] { self.item().components() }
1919
1920    fn has_durability(&self) -> bool { self.item().has_durability() }
1921
1922    fn durability_lost(&self) -> Option<u32> { self.item().durability_lost() }
1923
1924    fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1925        self.item().stats_durability_multiplier()
1926    }
1927}
1928
1929#[derive(Clone, Debug, Serialize, Deserialize)]
1930pub struct ItemDrops(pub Vec<(u32, Item)>);
1931
1932impl Component for ItemDrops {
1933    type Storage = DenseVecStorage<Self>;
1934}
1935
1936impl Component for PickupItem {
1937    type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1938}
1939
1940impl Component for ThrownItem {
1941    type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1942}
1943
1944#[derive(Copy, Clone, Debug)]
1945pub struct DurabilityMultiplier(pub f32);
1946
1947impl<T: ItemDesc + ?Sized> ItemDesc for &T {
1948    fn description(&self) -> &str {
1949        #[expect(deprecated)]
1950        (*self).description()
1951    }
1952
1953    fn name(&self) -> Cow<'_, str> {
1954        #[expect(deprecated)]
1955        (*self).name()
1956    }
1957
1958    fn kind(&self) -> Cow<'_, ItemKind> { (*self).kind() }
1959
1960    fn amount(&self) -> NonZeroU32 { (*self).amount() }
1961
1962    fn quality(&self) -> Quality { (*self).quality() }
1963
1964    fn num_slots(&self) -> u16 { (*self).num_slots() }
1965
1966    fn item_definition_id(&self) -> ItemDefinitionId<'_> { (*self).item_definition_id() }
1967
1968    fn tags(&self) -> Vec<ItemTag> { (*self).tags() }
1969
1970    fn is_modular(&self) -> bool { (*self).is_modular() }
1971
1972    fn components(&self) -> &[Item] { (*self).components() }
1973
1974    fn has_durability(&self) -> bool { (*self).has_durability() }
1975
1976    fn durability_lost(&self) -> Option<u32> { (*self).durability_lost() }
1977
1978    fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1979        (*self).stats_durability_multiplier()
1980    }
1981}
1982
1983/// Returns all item asset specifiers
1984///
1985/// Panics in case of filesystem errors
1986pub fn all_item_defs_expect() -> Vec<String> {
1987    try_all_item_defs().expect("Failed to access items directory")
1988}
1989
1990/// Returns all item asset specifiers
1991pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
1992    let defs = assets::load_rec_dir::<Ron<RawItemDef>>("common.items")?;
1993    Ok(defs.read().ids().map(|id| id.to_string()).collect())
1994}
1995
1996/// Designed to return all possible items, including modulars.
1997/// And some impossible too, like ItemKind::TagExamples.
1998pub fn all_items_expect() -> Vec<Item> {
1999    let defs = assets::load_rec_dir::<Ron<RawItemDef>>("common.items")
2000        .expect("failed to load item asset directory");
2001
2002    // Grab all items from assets
2003    let mut asset_items: Vec<Item> = defs
2004        .read()
2005        .ids()
2006        .map(|id| Item::new_from_asset_expect(id))
2007        .collect();
2008
2009    let mut material_parse_table = HashMap::new();
2010    for mat in Material::iter() {
2011        if let Some(id) = mat.asset_identifier() {
2012            material_parse_table.insert(id.to_owned(), mat);
2013        }
2014    }
2015
2016    let primary_comp_pool = modular::PRIMARY_COMPONENT_POOL.clone();
2017
2018    // Grab weapon primary components
2019    let mut primary_comps: Vec<Item> = primary_comp_pool
2020        .values()
2021        .flatten()
2022        .map(|(item, _hand_rules)| item.clone())
2023        .collect();
2024
2025    // Grab modular weapons
2026    let mut modular_items: Vec<Item> = primary_comp_pool
2027        .keys()
2028        .flat_map(|(tool, mat_id)| {
2029            let mat = material_parse_table
2030                .get(mat_id)
2031                .expect("unexpected material ident");
2032
2033            // get all weapons without imposing additional hand restrictions
2034            modular::generate_weapons(*tool, *mat, None)
2035                .expect("failure during modular weapon generation")
2036        })
2037        .collect();
2038
2039    // 1. Append asset items, that should include pretty much everything,
2040    // except modular items
2041    // 2. Append primary weapon components, which are modular as well.
2042    // 3. Finally append modular weapons that are made from (1) and (2)
2043    // extend when we get some new exotic stuff
2044    //
2045    // P. s. I still can't wrap my head around the idea that you can put
2046    // tag example into your inventory.
2047    let mut all = Vec::new();
2048    all.append(&mut asset_items);
2049    all.append(&mut primary_comps);
2050    all.append(&mut modular_items);
2051
2052    all
2053}
2054
2055impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2056    fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
2057        use ItemDefinitionId as DefId;
2058        match self {
2059            Self::Simple(simple) => {
2060                matches!(other, DefId::Simple(other_simple) if simple == other_simple)
2061            },
2062            Self::Modular {
2063                pseudo_base,
2064                components,
2065            } => matches!(
2066                other,
2067                DefId::Modular { pseudo_base: other_base, components: other_comps }
2068                if pseudo_base == other_base && components == other_comps
2069            ),
2070            Self::Compound {
2071                simple_base,
2072                components,
2073            } => matches!(
2074                other,
2075                DefId::Compound { simple_base: other_base, components: other_comps }
2076                if simple_base == other_base && components == other_comps
2077            ),
2078        }
2079    }
2080}
2081
2082impl PartialEq<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2083    #[inline]
2084    fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self }
2085}
2086
2087impl Equivalent<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2088    fn equivalent(&self, key: &ItemDefinitionIdOwned) -> bool { self == key }
2089}
2090
2091impl From<&ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2092    fn from(value: &ItemDefinitionId<'_>) -> Self { value.to_owned() }
2093}
2094
2095#[cfg(test)]
2096mod tests {
2097    use super::*;
2098
2099    #[test]
2100    fn test_assets_items() {
2101        let ids = all_item_defs_expect();
2102        for item in ids.iter().map(|id| Item::new_from_asset_expect(id)) {
2103            if let ItemKind::Consumable {
2104                container: Some(container),
2105                ..
2106            } = item.kind().as_ref()
2107            {
2108                Item::new_from_item_definition_id(
2109                    container.as_ref(),
2110                    &AbilityMap::load().read(),
2111                    &MaterialStatManifest::load().read(),
2112                )
2113                .unwrap();
2114            }
2115            drop(item)
2116        }
2117    }
2118
2119    #[test]
2120    fn test_item_i18n() { let _ = ItemI18n::new_expect(); }
2121
2122    #[test]
2123    // Probably can't fail, but better safe than crashing production server
2124    fn test_all_items() { let _ = all_items_expect(); }
2125
2126    #[test]
2127    // All items in Veloren should have localization.
2128    // If no, add some common dummy i18n id.
2129    fn ensure_item_localization() {
2130        let manifest = ItemI18n::new_expect();
2131        let items = all_items_expect();
2132        let mut errs = vec![];
2133        for item in items {
2134            let item_key: ItemKey = (&item).into();
2135            if manifest.item_text_opt(item_key.clone()).is_none() {
2136                errs.push(item_key)
2137            }
2138        }
2139        if !errs.is_empty() {
2140            panic!("item i18n manifest misses translation-id for following items {errs:#?}")
2141        }
2142    }
2143}