veloren_common/comp/inventory/item/
mod.rs

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