veloren_common/comp/inventory/item/
mod.rs

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