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