veloren_common/comp/inventory/item/
mod.rs

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