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    Phoenix,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
47pub enum Utility {
48    Coins,
49    Collar,
50    Key,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55pub struct Lantern {
56    color: Rgb<u32>,
57    strength_thousandths: u32,
58    flicker_thousandths: u32,
59}
60
61impl Lantern {
62    pub fn strength(&self) -> f32 { self.strength_thousandths as f32 / 1000_f32 }
63
64    pub fn color(&self) -> Rgb<f32> { self.color.map(|c| c as f32 / 255.0) }
65
66    pub fn flicker(&self) -> f32 { self.flicker_thousandths as f32 / 1000_f32 }
67}
68
69#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
70pub enum Quality {
71    Low,       // Grey
72    Common,    // Light blue
73    Moderate,  // Green
74    High,      // Blue
75    Epic,      // Purple
76    Legendary, // Gold
77    Artifact,  // Orange
78    Debug,     // Red
79}
80
81impl Quality {
82    pub const MIN: Self = Self::Low;
83}
84
85pub trait TagExampleInfo {
86    fn name(&self) -> &str;
87    /// What item to show in the crafting hud if the player has nothing with the
88    /// tag
89    fn exemplar_identifier(&self) -> Option<&str>;
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr)]
93pub enum MaterialKind {
94    Metal,
95    Gem,
96    Wood,
97    Stone,
98    Cloth,
99    Hide,
100}
101
102#[derive(
103    Clone,
104    Copy,
105    Debug,
106    PartialEq,
107    Eq,
108    Hash,
109    Serialize,
110    Deserialize,
111    IntoStaticStr,
112    EnumString,
113    EnumIter,
114)]
115#[strum(serialize_all = "snake_case")]
116pub enum Material {
117    Bronze,
118    Iron,
119    Steel,
120    Cobalt,
121    Bloodsteel,
122    Silver,
123    Gold,
124    Orichalcum,
125    Topaz,
126    Emerald,
127    Sapphire,
128    Amethyst,
129    Ruby,
130    Diamond,
131    Twig,
132    PlantFiber,
133    Wood,
134    Bamboo,
135    Hardwood,
136    Ironwood,
137    Frostwood,
138    Eldwood,
139    Rock,
140    Granite,
141    Bone,
142    Basalt,
143    Obsidian,
144    Velorite,
145    Linen,
146    RedLinen,
147    Wool,
148    Silk,
149    Lifecloth,
150    Moonweave,
151    Sunsilk,
152    Rawhide,
153    Leather,
154    RigidLeather,
155    Scale,
156    Carapace,
157    Plate,
158    Dragonscale,
159}
160
161impl Material {
162    pub fn material_kind(&self) -> MaterialKind {
163        match self {
164            Material::Bronze
165            | Material::Iron
166            | Material::Steel
167            | Material::Cobalt
168            | Material::Bloodsteel
169            | Material::Silver
170            | Material::Gold
171            | Material::Orichalcum => MaterialKind::Metal,
172            Material::Topaz
173            | Material::Emerald
174            | Material::Sapphire
175            | Material::Amethyst
176            | Material::Ruby
177            | Material::Diamond => MaterialKind::Gem,
178            Material::Wood
179            | Material::Twig
180            | Material::PlantFiber
181            | Material::Bamboo
182            | Material::Hardwood
183            | Material::Ironwood
184            | Material::Frostwood
185            | Material::Eldwood => MaterialKind::Wood,
186            Material::Rock
187            | Material::Granite
188            | Material::Bone
189            | Material::Basalt
190            | Material::Obsidian
191            | Material::Velorite => MaterialKind::Stone,
192            Material::Linen
193            | Material::RedLinen
194            | Material::Wool
195            | Material::Silk
196            | Material::Lifecloth
197            | Material::Moonweave
198            | Material::Sunsilk => MaterialKind::Cloth,
199            Material::Rawhide
200            | Material::Leather
201            | Material::RigidLeather
202            | Material::Scale
203            | Material::Carapace
204            | Material::Plate
205            | Material::Dragonscale => MaterialKind::Hide,
206        }
207    }
208
209    pub fn asset_identifier(&self) -> Option<&'static str> {
210        match self {
211            Material::Bronze => Some("common.items.mineral.ingot.bronze"),
212            Material::Iron => Some("common.items.mineral.ingot.iron"),
213            Material::Steel => Some("common.items.mineral.ingot.steel"),
214            Material::Cobalt => Some("common.items.mineral.ingot.cobalt"),
215            Material::Bloodsteel => Some("common.items.mineral.ingot.bloodsteel"),
216            Material::Silver => Some("common.items.mineral.ingot.silver"),
217            Material::Gold => Some("common.items.mineral.ingot.gold"),
218            Material::Orichalcum => Some("common.items.mineral.ingot.orichalcum"),
219            Material::Topaz => Some("common.items.mineral.gem.topaz"),
220            Material::Emerald => Some("common.items.mineral.gem.emerald"),
221            Material::Sapphire => Some("common.items.mineral.gem.sapphire"),
222            Material::Amethyst => Some("common.items.mineral.gem.amethyst"),
223            Material::Ruby => Some("common.items.mineral.gem.ruby"),
224            Material::Diamond => Some("common.items.mineral.gem.diamond"),
225            Material::Twig => Some("common.items.crafting_ing.twigs"),
226            Material::PlantFiber => Some("common.items.flowers.plant_fiber"),
227            Material::Wood => Some("common.items.log.wood"),
228            Material::Bamboo => Some("common.items.log.bamboo"),
229            Material::Hardwood => Some("common.items.log.hardwood"),
230            Material::Ironwood => Some("common.items.log.ironwood"),
231            Material::Frostwood => Some("common.items.log.frostwood"),
232            Material::Eldwood => Some("common.items.log.eldwood"),
233            Material::Rock
234            | Material::Granite
235            | Material::Bone
236            | Material::Basalt
237            | Material::Obsidian
238            | Material::Velorite => None,
239            Material::Linen => Some("common.items.crafting_ing.cloth.linen"),
240            Material::RedLinen => Some("common.items.crafting_ing.cloth.linen_red"),
241            Material::Wool => Some("common.items.crafting_ing.cloth.wool"),
242            Material::Silk => Some("common.items.crafting_ing.cloth.silk"),
243            Material::Lifecloth => Some("common.items.crafting_ing.cloth.lifecloth"),
244            Material::Moonweave => Some("common.items.crafting_ing.cloth.moonweave"),
245            Material::Sunsilk => Some("common.items.crafting_ing.cloth.sunsilk"),
246            Material::Rawhide => Some("common.items.crafting_ing.leather.simple_leather"),
247            Material::Leather => Some("common.items.crafting_ing.leather.thick_leather"),
248            Material::RigidLeather => Some("common.items.crafting_ing.leather.rigid_leather"),
249            Material::Scale => Some("common.items.crafting_ing.hide.scales"),
250            Material::Carapace => Some("common.items.crafting_ing.hide.carapace"),
251            Material::Plate => Some("common.items.crafting_ing.hide.plate"),
252            Material::Dragonscale => Some("common.items.crafting_ing.hide.dragon_scale"),
253        }
254    }
255}
256
257impl TagExampleInfo for Material {
258    fn name(&self) -> &str { self.into() }
259
260    fn exemplar_identifier(&self) -> Option<&str> { self.asset_identifier() }
261}
262
263#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
264pub enum ItemTag {
265    /// Used to indicate that an item is composed of this material
266    Material(Material),
267    /// Used to indicate that an item is composed of this material kind
268    MaterialKind(MaterialKind),
269    Cultist,
270    Gnarling,
271    Potion,
272    Charm,
273    Food,
274    BaseMaterial, // Cloth-scraps, Leather...
275    CraftingTool, // Pickaxe, Craftsman-Hammer, Sewing-Set
276    Utility,
277    Bag,
278    SalvageInto(Material, u32),
279    Witch,
280    Pirate,
281}
282
283impl TagExampleInfo for ItemTag {
284    fn name(&self) -> &str {
285        match self {
286            ItemTag::Material(material) => material.name(),
287            ItemTag::MaterialKind(material_kind) => material_kind.into(),
288            ItemTag::Cultist => "cultist",
289            ItemTag::Gnarling => "gnarling",
290            ItemTag::Potion => "potion",
291            ItemTag::Charm => "charm",
292            ItemTag::Food => "food",
293            ItemTag::BaseMaterial => "basemat",
294            ItemTag::CraftingTool => "tool",
295            ItemTag::Utility => "utility",
296            ItemTag::Bag => "bag",
297            ItemTag::SalvageInto(_, _) => "salvage",
298            ItemTag::Witch => "witch",
299            ItemTag::Pirate => "pirate",
300        }
301    }
302
303    // TODO: Autogenerate these?
304    fn exemplar_identifier(&self) -> Option<&str> {
305        match self {
306            ItemTag::Material(material) => material.exemplar_identifier(),
307            ItemTag::Cultist => Some("common.items.tag_examples.cultist"),
308            ItemTag::Gnarling => Some("common.items.tag_examples.gnarling"),
309            ItemTag::Witch => Some("common.items.tag_examples.witch"),
310            ItemTag::Pirate => Some("common.items.tag_examples.pirate"),
311            ItemTag::MaterialKind(_)
312            | ItemTag::Potion
313            | ItemTag::Food
314            | ItemTag::Charm
315            | ItemTag::BaseMaterial
316            | ItemTag::CraftingTool
317            | ItemTag::Utility
318            | ItemTag::Bag
319            | ItemTag::SalvageInto(_, _) => None,
320        }
321    }
322}
323
324#[derive(Clone, Debug, Serialize, Deserialize)]
325pub enum Effects {
326    Any(Vec<Effect>),
327    All(Vec<Effect>),
328    One(Effect),
329}
330
331impl Effects {
332    pub fn effects(&self) -> &[Effect] {
333        match self {
334            Effects::Any(effects) => effects,
335            Effects::All(effects) => effects,
336            Effects::One(effect) => std::slice::from_ref(effect),
337        }
338    }
339}
340
341#[derive(Clone, Debug, Serialize, Deserialize)]
342#[serde(deny_unknown_fields)]
343pub enum ItemKind {
344    /// Something wieldable
345    Tool(Tool),
346    ModularComponent(ModularComponent),
347    Lantern(Lantern),
348    Armor(armor::Armor),
349    Glider,
350    Consumable {
351        kind: ConsumableKind,
352        effects: Effects,
353    },
354    Utility {
355        kind: Utility,
356    },
357    Ingredient {
358        /// Used to generate names for modular items composed of this ingredient
359        // I think we can actually remove it now?
360        #[deprecated = "part of non-localized name generation"]
361        descriptor: String,
362    },
363    TagExamples {
364        /// A list of item names to lookup the appearences of and animate
365        /// through
366        item_ids: Vec<String>,
367    },
368    RecipeGroup {
369        recipes: Vec<String>,
370    },
371}
372
373#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
374pub enum ConsumableKind {
375    Drink,
376    Food,
377    ComplexFood,
378    Charm,
379    Recipe,
380}
381
382impl ItemKind {
383    pub fn is_equippable(&self) -> bool {
384        matches!(
385            self,
386            ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Glider | ItemKind::Lantern(_)
387        )
388    }
389
390    // Used for inventory sorting, what comes before the first colon (:) is used as
391    // a broader category
392    pub fn get_itemkind_string(&self) -> String {
393        match self {
394            // Using tool and toolkind to sort tools by kind
395            ItemKind::Tool(tool) => format!("Tool: {:?}", tool.kind),
396            ItemKind::ModularComponent(modular_component) => {
397                format!("ModularComponent: {:?}", modular_component.toolkind())
398            },
399            ItemKind::Lantern(lantern) => format!("Lantern: {:?}", lantern),
400            ItemKind::Armor(armor) => format!("Armor: {:?}", armor.stats),
401            ItemKind::Glider => "Glider:".to_string(),
402            ItemKind::Consumable { kind, .. } => {
403                format!("Consumable: {:?}", kind)
404            },
405            ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
406            #[expect(deprecated)]
407            ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
408            ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids),
409            ItemKind::RecipeGroup { .. } => String::from("Recipes:"),
410        }
411    }
412
413    pub fn has_durability(&self) -> bool {
414        match self {
415            ItemKind::Tool(Tool { kind, .. }) => !matches!(kind, ToolKind::Throwable),
416            ItemKind::Armor(armor) => armor.kind.has_durability(),
417            ItemKind::ModularComponent(_)
418            | ItemKind::Lantern(_)
419            | ItemKind::Glider
420            | ItemKind::Consumable { .. }
421            | ItemKind::Utility { .. }
422            | ItemKind::Ingredient { .. }
423            | ItemKind::TagExamples { .. }
424            | ItemKind::RecipeGroup { .. } => false,
425        }
426    }
427}
428
429pub type ItemId = AtomicCell<Option<NonZeroU64>>;
430
431/* /// The only way to access an item id outside this module is to mutably, atomically update it using
432/// this structure.  It has a single method, `try_assign_id`, which attempts to set the id if and
433/// only if it's not already set.
434pub struct CreateDatabaseItemId {
435    item_id: Arc<ItemId>,
436}*/
437
438/// NOTE: Do not call `Item::clone` without consulting the core devs!  It only
439/// exists due to being required for message serialization at the moment, and
440/// should not be used for any other purpose.
441///
442/// FIXME: Turn on a Clippy lint forbidding the use of `Item::clone` using the
443/// `disallowed_method` feature.
444#[derive(Clone, Debug, Serialize, Deserialize)]
445pub struct Item {
446    /// item_id is hidden because it represents the persistent, storage entity
447    /// ID for any item that has been saved to the database.  Additionally,
448    /// it (currently) holds interior mutable state, making it very
449    /// dangerous to expose.  We will work to eliminate this issue soon; for
450    /// now, we try to make the system as foolproof as possible by greatly
451    /// restricting opportunities for cloning the item_id.
452    #[serde(skip)]
453    item_id: Arc<ItemId>,
454    /// item_def is hidden because changing the item definition for an item
455    /// could change invariants like whether it was stackable (invalidating
456    /// the amount).
457    item_base: ItemBase,
458    /// components is hidden to maintain the following invariants:
459    /// - It should only contain modular components (and enhancements, once they
460    ///   exist)
461    /// - Enhancements (once they exist) should be compatible with the available
462    ///   slot shapes
463    /// - Modular components should agree with the tool kind
464    /// - There should be exactly one damage component and exactly one held
465    ///   component for modular weapons
466    components: Vec<Item>,
467    /// amount is hidden because it needs to maintain the invariant that only
468    /// stackable items can have > 1 amounts.
469    amount: NonZeroU32,
470    /// The slots for items that this item has
471    slots: Vec<InvSlot>,
472    item_config: Option<Box<ItemConfig>>,
473    hash: u64,
474    /// Tracks how many deaths occurred while item was equipped, which is
475    /// converted into the items durability. Only tracked for tools and armor
476    /// currently.
477    durability_lost: Option<u32>,
478}
479
480/// Newtype around [`Item`] used for frontend events to prevent it accidentally
481/// being used for anything other than frontend events
482#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
483pub struct FrontendItem(Item);
484
485// An item that is dropped into the world an can be picked up. It can stack with
486// other items of the same type regardless of the stack limit, when picked up
487// the last item from the list is popped
488//
489// NOTE: Never call PickupItem::clone, it is only used for network
490// synchronization
491//
492// Invariants:
493//  - Any item that is not the last one must have an amount equal to its
494//    `max_amount()`
495//  - All items must be equal and have a zero amount of slots
496//  - The Item list must not be empty
497#[derive(Debug, Clone, Serialize, Deserialize)]
498pub struct PickupItem {
499    items: Vec<Item>,
500    /// This [`ProgramTime`] only makes sense on the server
501    created_at: ProgramTime,
502    /// This [`ProgramTime`] only makes sense on the server
503    next_merge_check: ProgramTime,
504    /// When set to `true`, this item will actively try to be merged into nearby
505    /// items of the same kind (see [`Item::can_merge`]). Currently only used
506    /// for inventory dropped items to prevent entity DoS.
507    pub should_merge: bool,
508}
509
510/// Newtype around [`Item`] so that thrown projectiles can track which item
511/// they represent
512#[derive(Debug, Clone, Serialize, Deserialize)]
513pub struct ThrownItem(pub Item);
514
515use std::hash::{Hash, Hasher};
516
517// Used to find inventory item corresponding to hotbar slot
518impl Hash for Item {
519    fn hash<H: Hasher>(&self, state: &mut H) {
520        self.item_definition_id().hash(state);
521        self.components.iter().for_each(|comp| comp.hash(state));
522    }
523}
524
525// at the time of writing, we use Fluent, which supports attributes
526// and we can get both name and description using them
527type I18nId = String;
528
529#[derive(Clone, Debug, Serialize, Deserialize)]
530// TODO: probably make a Resource if used outside of voxygen
531// TODO: add hot-reloading similar to how ItemImgs does it?
532// TODO: make it work with plugins (via Concatenate?)
533/// To be used with ItemDesc::i18n
534///
535/// NOTE: there is a limitation to this manifest, as it uses ItemKey and
536/// ItemKey isn't uniquely identifies Item, when it comes to modular items.
537///
538/// If modular weapon has the same primary component and the same hand-ness,
539/// we use the same model EVEN IF it has different secondary components, like
540/// Staff with Heavy core or Light core.
541///
542/// Translations currently do the same, but *maybe* they shouldn't in which case
543/// we should either extend ItemKey or use new identifier. We could use
544/// ItemDefinitionId, but it's very generic and cumbersome.
545pub struct ItemI18n {
546    /// maps ItemKey to i18n identifier
547    map: HashMap<ItemKey, I18nId>,
548}
549
550impl assets::Asset for ItemI18n {
551    type Loader = assets::RonLoader;
552
553    const EXTENSION: &'static str = "ron";
554}
555
556impl ItemI18n {
557    pub fn new_expect() -> Self {
558        ItemI18n::load_expect("common.item_i18n_manifest")
559            .read()
560            .clone()
561    }
562
563    /// Returns (name, description) in Content form.
564    // TODO: after we remove legacy text from ItemDef, consider making this
565    // function non-fallible?
566    fn item_text_opt(&self, mut item_key: ItemKey) -> Option<(Content, Content)> {
567        // we don't put TagExamples into manifest
568        if let ItemKey::TagExamples(_, id) = item_key {
569            item_key = ItemKey::Simple(id.to_string());
570        }
571
572        let key = self.map.get(&item_key);
573        key.map(|key| {
574            (
575                Content::Key(key.to_owned()),
576                Content::Attr(key.to_owned(), "desc".to_owned()),
577            )
578        })
579    }
580}
581
582#[derive(Clone, Debug)]
583pub enum ItemBase {
584    Simple(Arc<ItemDef>),
585    Modular(ModularBase),
586}
587
588impl Serialize for ItemBase {
589    // Custom serialization for ItemDef, we only want to send the item_definition_id
590    // over the network, the client will use deserialize_item_def to fetch the
591    // ItemDef from assets.
592    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
593    where
594        S: Serializer,
595    {
596        serializer.serialize_str(&self.serialization_item_id())
597    }
598}
599
600impl<'de> Deserialize<'de> for ItemBase {
601    // Custom de-serialization for ItemBase to retrieve the ItemBase from assets
602    // using its asset specifier (item_definition_id)
603    fn deserialize<D>(deserializer: D) -> Result<ItemBase, D::Error>
604    where
605        D: de::Deserializer<'de>,
606    {
607        struct ItemBaseStringVisitor;
608
609        impl de::Visitor<'_> for ItemBaseStringVisitor {
610            type Value = ItemBase;
611
612            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
613                formatter.write_str("item def string")
614            }
615
616            fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
617            where
618                E: de::Error,
619            {
620                ItemBase::from_item_id_string(serialized_item_base)
621                    .map_err(|err| E::custom(err.to_string()))
622            }
623        }
624
625        deserializer.deserialize_str(ItemBaseStringVisitor)
626    }
627}
628
629impl ItemBase {
630    fn num_slots(&self) -> u16 {
631        match self {
632            ItemBase::Simple(item_def) => item_def.num_slots(),
633            ItemBase::Modular(_) => 0,
634        }
635    }
636
637    // Should be kept the same as the persistence_item_id function in Item
638    // TODO: Maybe use Cow?
639    fn serialization_item_id(&self) -> String {
640        match &self {
641            ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
642            ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
643        }
644    }
645
646    fn from_item_id_string(item_id_string: &str) -> Result<Self, Error> {
647        if item_id_string.starts_with(crate::modular_item_id_prefix!()) {
648            Ok(ItemBase::Modular(ModularBase::load_from_pseudo_id(
649                item_id_string,
650            )))
651        } else {
652            Ok(ItemBase::Simple(Arc::<ItemDef>::load_cloned(
653                item_id_string,
654            )?))
655        }
656    }
657}
658
659// TODO: could this theorectically hold a ref to the actual components and
660// lazily get their IDs for hash/partialeq/debug/to_owned/etc? (i.e. eliminating
661// `Vec`s)
662#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
663pub enum ItemDefinitionId<'a> {
664    Simple(Cow<'a, str>),
665    Modular {
666        pseudo_base: &'a str,
667        components: Vec<ItemDefinitionId<'a>>,
668    },
669    Compound {
670        simple_base: &'a str,
671        components: Vec<ItemDefinitionId<'a>>,
672    },
673}
674
675#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
676pub enum ItemDefinitionIdOwned {
677    Simple(String),
678    Modular {
679        pseudo_base: String,
680        components: Vec<ItemDefinitionIdOwned>,
681    },
682    Compound {
683        simple_base: String,
684        components: Vec<ItemDefinitionIdOwned>,
685    },
686}
687
688impl ItemDefinitionIdOwned {
689    pub fn as_ref(&self) -> ItemDefinitionId<'_> {
690        match *self {
691            Self::Simple(ref id) => ItemDefinitionId::Simple(Cow::Borrowed(id)),
692            Self::Modular {
693                ref pseudo_base,
694                ref components,
695            } => ItemDefinitionId::Modular {
696                pseudo_base,
697                components: components.iter().map(|comp| comp.as_ref()).collect(),
698            },
699            Self::Compound {
700                ref simple_base,
701                ref components,
702            } => ItemDefinitionId::Compound {
703                simple_base,
704                components: components.iter().map(|comp| comp.as_ref()).collect(),
705            },
706        }
707    }
708}
709
710impl ItemDefinitionId<'_> {
711    pub fn itemdef_id(&self) -> Option<&str> {
712        match self {
713            Self::Simple(id) => Some(id),
714            Self::Modular { .. } => None,
715            Self::Compound { simple_base, .. } => Some(simple_base),
716        }
717    }
718
719    pub fn to_owned(&self) -> ItemDefinitionIdOwned {
720        match self {
721            Self::Simple(id) => ItemDefinitionIdOwned::Simple(String::from(&**id)),
722            Self::Modular {
723                pseudo_base,
724                components,
725            } => ItemDefinitionIdOwned::Modular {
726                pseudo_base: String::from(*pseudo_base),
727                components: components.iter().map(|comp| comp.to_owned()).collect(),
728            },
729            Self::Compound {
730                simple_base,
731                components,
732            } => ItemDefinitionIdOwned::Compound {
733                simple_base: String::from(*simple_base),
734                components: components.iter().map(|comp| comp.to_owned()).collect(),
735            },
736        }
737    }
738}
739
740#[derive(Debug, Serialize, Deserialize)]
741pub struct ItemDef {
742    #[serde(default)]
743    /// The string that refers to the filepath to the asset, relative to the
744    /// assets folder, which the ItemDef is loaded from. The name space
745    /// prepended with `veloren.core` is reserved for veloren functions.
746    item_definition_id: String,
747    #[deprecated = "since item i18n"]
748    name: String,
749    #[deprecated = "since item i18n"]
750    description: String,
751    pub kind: ItemKind,
752    pub quality: Quality,
753    pub tags: Vec<ItemTag>,
754    #[serde(default)]
755    pub slots: u16,
756    /// Used to specify a custom ability set for a weapon. Leave None (or don't
757    /// include field in ItemDef) to use default ability set for weapon kind.
758    pub ability_spec: Option<AbilitySpec>,
759}
760
761impl PartialEq for ItemDef {
762    fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
763}
764
765// TODO: Look into removing ItemConfig and just using AbilitySet
766#[derive(Clone, Debug, Serialize, Deserialize)]
767pub struct ItemConfig {
768    pub abilities: AbilitySet<tool::AbilityItem>,
769}
770
771#[derive(Debug)]
772pub enum ItemConfigError {
773    BadItemKind,
774}
775
776impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
777    type Error = ItemConfigError;
778
779    fn try_from(
780        // TODO: Either remove msm or use it as argument in fn kind
781        (item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
782    ) -> Result<Self, Self::Error> {
783        match &*item.kind() {
784            ItemKind::Tool(tool) => {
785                // If no custom ability set is specified, fall back to abilityset of tool kind.
786                let tool_default = |tool_kind| {
787                    let key = &AbilitySpec::Tool(tool_kind);
788                    ability_map.get_ability_set(key)
789                };
790                let abilities = if let Some(set_key) = item.ability_spec() {
791                    if let Some(set) = ability_map.get_ability_set(&set_key) {
792                        set.clone()
793                            .modified_by_tool(tool, item.stats_durability_multiplier())
794                    } else {
795                        error!(
796                            "Custom ability set: {:?} references non-existent set, falling back \
797                             to default ability set.",
798                            set_key
799                        );
800                        tool_default(tool.kind).cloned().unwrap_or_default()
801                    }
802                } else if let Some(set) = tool_default(tool.kind) {
803                    set.clone()
804                        .modified_by_tool(tool, item.stats_durability_multiplier())
805                } else {
806                    error!(
807                        "No ability set defined for tool: {:?}, falling back to default ability \
808                         set.",
809                        tool.kind
810                    );
811                    Default::default()
812                };
813
814                Ok(ItemConfig { abilities })
815            },
816            ItemKind::Glider => item
817                .ability_spec()
818                .and_then(|set_key| ability_map.get_ability_set(&set_key))
819                .map(|abilities| ItemConfig {
820                    abilities: abilities.clone(),
821                })
822                .ok_or(ItemConfigError::BadItemKind),
823            _ => Err(ItemConfigError::BadItemKind),
824        }
825    }
826}
827
828impl ItemDef {
829    pub fn is_stackable(&self) -> bool {
830        matches!(
831            self.kind,
832            ItemKind::Consumable { .. }
833                | ItemKind::Ingredient { .. }
834                | ItemKind::Utility { .. }
835                | ItemKind::Tool(Tool {
836                    kind: ToolKind::Throwable,
837                    ..
838                })
839        )
840    }
841
842    /// NOTE: invariant that amount() ≤ max_amount(), 1 ≤ max_amount(),
843    /// and if !self.is_stackable(), self.max_amount() = 1.
844    pub fn max_amount(&self) -> u32 { if self.is_stackable() { u32::MAX } else { 1 } }
845
846    // currently needed by trade_pricing
847    pub fn id(&self) -> &str { &self.item_definition_id }
848
849    #[cfg(test)]
850    pub fn new_test(
851        item_definition_id: String,
852        kind: ItemKind,
853        quality: Quality,
854        tags: Vec<ItemTag>,
855        slots: u16,
856    ) -> Self {
857        #[expect(deprecated)]
858        Self {
859            item_definition_id,
860            name: "test item name".to_owned(),
861            description: "test item description".to_owned(),
862            kind,
863            quality,
864            tags,
865            slots,
866            ability_spec: None,
867        }
868    }
869
870    #[cfg(test)]
871    pub fn create_test_itemdef_from_kind(kind: ItemKind) -> Self {
872        #[expect(deprecated)]
873        Self {
874            item_definition_id: "test.item".to_string(),
875            name: "test item name".to_owned(),
876            description: "test item description".to_owned(),
877            kind,
878            quality: Quality::Common,
879            tags: vec![],
880            slots: 0,
881            ability_spec: None,
882        }
883    }
884}
885
886/// NOTE: This PartialEq instance is pretty broken!  It doesn't check item
887/// amount or any child items (and, arguably, doing so should be able to ignore
888/// things like item order within the main inventory or within each bag, and
889/// possibly even coalesce amounts, though these may be more controversial).
890/// Until such time as we find an actual need for a proper PartialEq instance,
891/// please don't rely on this for anything!
892impl PartialEq for Item {
893    fn eq(&self, other: &Self) -> bool {
894        (match (&self.item_base, &other.item_base) {
895            (ItemBase::Simple(our_def), ItemBase::Simple(other_def)) => {
896                our_def.item_definition_id == other_def.item_definition_id
897            },
898            (ItemBase::Modular(our_base), ItemBase::Modular(other_base)) => our_base == other_base,
899            _ => false,
900        }) && self.components() == other.components()
901    }
902}
903
904impl assets::Compound for ItemDef {
905    fn load(cache: assets::AnyCache, specifier: &assets::SharedString) -> Result<Self, BoxedError> {
906        if specifier.starts_with("veloren.core.") {
907            return Err(format!(
908                "Attempted to load an asset from a specifier reserved for core veloren functions. \
909                 Specifier: {}",
910                specifier
911            )
912            .into());
913        }
914
915        let RawItemDef {
916            legacy_name,
917            legacy_description,
918            kind,
919            quality,
920            tags,
921            slots,
922            ability_spec,
923        } = cache.load::<RawItemDef>(specifier)?.cloned();
924
925        // Some commands like /give_item provide the asset specifier separated with \
926        // instead of .
927        //
928        // TODO: This probably does not belong here
929        let item_definition_id = specifier.replace('\\', ".");
930
931        #[expect(deprecated)]
932        Ok(ItemDef {
933            item_definition_id,
934            name: legacy_name,
935            description: legacy_description,
936            kind,
937            quality,
938            tags,
939            slots,
940            ability_spec,
941        })
942    }
943}
944
945#[derive(Clone, Debug, Serialize, Deserialize)]
946#[serde(rename = "ItemDef", deny_unknown_fields)]
947struct RawItemDef {
948    legacy_name: String,
949    legacy_description: String,
950    kind: ItemKind,
951    quality: Quality,
952    tags: Vec<ItemTag>,
953    #[serde(default)]
954    slots: u16,
955    ability_spec: Option<AbilitySpec>,
956}
957
958impl assets::Asset for RawItemDef {
959    type Loader = assets::RonLoader;
960
961    const EXTENSION: &'static str = "ron";
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::<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            if *durability_lost < Self::MAX_DURABILITY {
1465                *durability_lost += 1;
1466            }
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::<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::<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            drop(item)
2109        }
2110    }
2111
2112    #[test]
2113    fn test_item_i18n() { let _ = ItemI18n::new_expect(); }
2114
2115    #[test]
2116    // Probably can't fail, but better safe than crashing production server
2117    fn test_all_items() { let _ = all_items_expect(); }
2118
2119    #[test]
2120    // All items in Veloren should have localization.
2121    // If no, add some common dummy i18n id.
2122    fn ensure_item_localization() {
2123        let manifest = ItemI18n::new_expect();
2124        let items = all_items_expect();
2125        let mut errs = vec![];
2126        for item in items {
2127            let item_key: ItemKey = (&item).into();
2128            if manifest.item_text_opt(item_key.clone()).is_none() {
2129                errs.push(item_key)
2130            }
2131        }
2132        if !errs.is_empty() {
2133            panic!("item i18n manifest misses translation-id for following items {errs:#?}")
2134        }
2135    }
2136}