1pub mod armor;
2pub mod item_key;
3pub mod modular;
4pub mod tool;
5
6pub 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, Common, Moderate, High, Epic, Legendary, Artifact, Debug, }
83
84impl Quality {
85 pub const MIN: Self = Self::Low;
86}
87
88pub trait TagExampleInfo {
89 fn name(&self) -> &str;
90 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 Material(Material),
276 MaterialKind(MaterialKind),
278 Cultist,
279 Gnarling,
280 Potion,
281 Charm,
282 Food,
283 BaseMaterial, CraftingTool, 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 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 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 #[deprecated = "part of non-localized name generation"]
372 descriptor: String,
373 },
374 TagExamples {
375 item_ids: Vec<String>,
378 },
379 RecipeGroup {
380 recipes: Vec<String>,
381 },
382}
383
384#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
385pub enum ConsumableKind {
386 Drink,
387 Food,
388 ComplexFood,
389 Charm,
390 Recipe,
391}
392
393impl ItemKind {
394 pub fn is_equippable(&self) -> bool {
395 matches!(
396 self,
397 ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Glider | ItemKind::Lantern(_)
398 )
399 }
400
401 pub fn get_itemkind_string(&self) -> String {
404 match self {
405 ItemKind::Tool(tool) => format!("Tool: {:?}", tool.kind),
407 ItemKind::ModularComponent(modular_component) => {
408 format!("ModularComponent: {:?}", modular_component.toolkind())
409 },
410 ItemKind::Lantern(lantern) => format!("Lantern: {:?}", lantern),
411 ItemKind::Armor(armor) => format!("Armor: {:?}", armor.stats),
412 ItemKind::Glider => "Glider:".to_string(),
413 ItemKind::Consumable { kind, .. } => {
414 format!("Consumable: {:?}", kind)
415 },
416 ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
417 #[expect(deprecated)]
418 ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
419 ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids),
420 ItemKind::RecipeGroup { .. } => String::from("Recipes:"),
421 }
422 }
423
424 pub fn has_durability(&self) -> bool {
425 match self {
426 ItemKind::Tool(Tool { kind, .. }) => !matches!(kind, ToolKind::Throwable),
427 ItemKind::Armor(armor) => armor.kind.has_durability(),
428 ItemKind::ModularComponent(_)
429 | ItemKind::Lantern(_)
430 | ItemKind::Glider
431 | ItemKind::Consumable { .. }
432 | ItemKind::Utility { .. }
433 | ItemKind::Ingredient { .. }
434 | ItemKind::TagExamples { .. }
435 | ItemKind::RecipeGroup { .. } => false,
436 }
437 }
438}
439
440pub type ItemId = AtomicCell<Option<NonZeroU64>>;
441
442#[derive(Clone, Debug, Serialize, Deserialize)]
456pub struct Item {
457 #[serde(skip)]
464 item_id: Arc<ItemId>,
465 item_base: ItemBase,
469 components: Vec<Item>,
478 amount: NonZeroU32,
481 slots: Vec<InvSlot>,
483 item_config: Option<Box<ItemConfig>>,
484 hash: u64,
485 durability_lost: Option<u32>,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
494pub struct FrontendItem(Item);
495
496#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct PickupItem {
510 items: Vec<Item>,
511 created_at: ProgramTime,
513 next_merge_check: ProgramTime,
515 pub should_merge: bool,
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct ThrownItem(pub Item);
525
526use std::hash::{Hash, Hasher};
527
528impl Hash for Item {
530 fn hash<H: Hasher>(&self, state: &mut H) {
531 self.item_definition_id().hash(state);
532 self.components.iter().for_each(|comp| comp.hash(state));
533 }
534}
535
536type I18nId = String;
539
540#[derive(Clone, Debug, Serialize, Deserialize)]
541pub struct ItemI18n {
557 map: HashMap<ItemKey, I18nId>,
559}
560
561impl ItemI18n {
562 pub fn new_expect() -> Self {
563 Ron::load_expect("common.item_i18n_manifest")
564 .read()
565 .clone()
566 .into_inner()
567 }
568
569 fn item_text_opt(&self, mut item_key: ItemKey) -> Option<(Content, Content)> {
573 if let ItemKey::TagExamples(_, id) = item_key {
575 item_key = ItemKey::Simple(id.to_string());
576 }
577
578 let key = self.map.get(&item_key);
579 key.map(|key| {
580 (
581 Content::Key(key.to_owned()),
582 Content::Attr(key.to_owned(), "desc".to_owned()),
583 )
584 })
585 }
586}
587
588#[derive(Clone, Debug)]
589pub enum ItemBase {
590 Simple(Arc<ItemDef>),
591 Modular(ModularBase),
592}
593
594impl Serialize for ItemBase {
595 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
599 where
600 S: Serializer,
601 {
602 serializer.serialize_str(&self.serialization_item_id())
603 }
604}
605
606impl<'de> Deserialize<'de> for ItemBase {
607 fn deserialize<D>(deserializer: D) -> Result<ItemBase, D::Error>
610 where
611 D: de::Deserializer<'de>,
612 {
613 struct ItemBaseStringVisitor;
614
615 impl de::Visitor<'_> for ItemBaseStringVisitor {
616 type Value = ItemBase;
617
618 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
619 formatter.write_str("item def string")
620 }
621
622 fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
623 where
624 E: de::Error,
625 {
626 ItemBase::from_item_id_string(serialized_item_base)
627 .map_err(|err| E::custom(err.to_string()))
628 }
629 }
630
631 deserializer.deserialize_str(ItemBaseStringVisitor)
632 }
633}
634
635impl ItemBase {
636 fn num_slots(&self) -> u16 {
637 match self {
638 ItemBase::Simple(item_def) => item_def.num_slots(),
639 ItemBase::Modular(_) => 0,
640 }
641 }
642
643 fn serialization_item_id(&self) -> String {
646 match &self {
647 ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
648 ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
649 }
650 }
651
652 fn from_item_id_string(item_id_string: &str) -> Result<Self, Error> {
653 if item_id_string.starts_with(crate::modular_item_id_prefix!()) {
654 Ok(ItemBase::Modular(ModularBase::load_from_pseudo_id(
655 item_id_string,
656 )))
657 } else {
658 Ok(ItemBase::Simple(Arc::<ItemDef>::load_cloned(
659 item_id_string,
660 )?))
661 }
662 }
663}
664
665#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
669pub enum ItemDefinitionId<'a> {
670 Simple(Cow<'a, str>),
671 Modular {
672 pseudo_base: &'a str,
673 components: Vec<ItemDefinitionId<'a>>,
674 },
675 Compound {
676 simple_base: &'a str,
677 components: Vec<ItemDefinitionId<'a>>,
678 },
679}
680
681#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
682pub enum ItemDefinitionIdOwned {
683 Simple(String),
684 Modular {
685 pseudo_base: String,
686 components: Vec<ItemDefinitionIdOwned>,
687 },
688 Compound {
689 simple_base: String,
690 components: Vec<ItemDefinitionIdOwned>,
691 },
692}
693
694impl ItemDefinitionIdOwned {
695 pub fn as_ref(&self) -> ItemDefinitionId<'_> {
696 match *self {
697 Self::Simple(ref id) => ItemDefinitionId::Simple(Cow::Borrowed(id)),
698 Self::Modular {
699 ref pseudo_base,
700 ref components,
701 } => ItemDefinitionId::Modular {
702 pseudo_base,
703 components: components.iter().map(|comp| comp.as_ref()).collect(),
704 },
705 Self::Compound {
706 ref simple_base,
707 ref components,
708 } => ItemDefinitionId::Compound {
709 simple_base,
710 components: components.iter().map(|comp| comp.as_ref()).collect(),
711 },
712 }
713 }
714}
715
716impl ItemDefinitionId<'_> {
717 pub fn itemdef_id(&self) -> Option<&str> {
718 match self {
719 Self::Simple(id) => Some(id),
720 Self::Modular { .. } => None,
721 Self::Compound { simple_base, .. } => Some(simple_base),
722 }
723 }
724
725 pub fn to_owned(&self) -> ItemDefinitionIdOwned {
726 match self {
727 Self::Simple(id) => ItemDefinitionIdOwned::Simple(String::from(&**id)),
728 Self::Modular {
729 pseudo_base,
730 components,
731 } => ItemDefinitionIdOwned::Modular {
732 pseudo_base: String::from(*pseudo_base),
733 components: components.iter().map(|comp| comp.to_owned()).collect(),
734 },
735 Self::Compound {
736 simple_base,
737 components,
738 } => ItemDefinitionIdOwned::Compound {
739 simple_base: String::from(*simple_base),
740 components: components.iter().map(|comp| comp.to_owned()).collect(),
741 },
742 }
743 }
744}
745
746#[derive(Debug, Serialize, Deserialize)]
747pub struct ItemDef {
748 #[serde(default)]
749 item_definition_id: String,
753 #[deprecated = "since item i18n"]
754 name: String,
755 #[deprecated = "since item i18n"]
756 description: String,
757 pub kind: ItemKind,
758 pub quality: Quality,
759 pub tags: Vec<ItemTag>,
760 #[serde(default)]
761 pub slots: u16,
762 pub ability_spec: Option<AbilitySpec>,
765}
766
767impl PartialEq for ItemDef {
768 fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
769}
770
771#[derive(Clone, Debug, Serialize, Deserialize)]
773pub struct ItemConfig {
774 pub abilities: AbilitySet<tool::AbilityItem>,
775}
776
777#[derive(Debug)]
778pub enum ItemConfigError {
779 BadItemKind,
780}
781
782impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
783 type Error = ItemConfigError;
784
785 fn try_from(
786 (item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
788 ) -> Result<Self, Self::Error> {
789 match &*item.kind() {
790 ItemKind::Tool(tool) => {
791 let tool_default = |tool_kind| {
793 let key = &AbilitySpec::Tool(tool_kind);
794 ability_map.get_ability_set(key)
795 };
796 let abilities = if let Some(set_key) = item.ability_spec() {
797 if let Some(set) = ability_map.get_ability_set(&set_key) {
798 set.clone()
799 .modified_by_tool(tool, item.stats_durability_multiplier())
800 } else {
801 error!(
802 "Custom ability set: {:?} references non-existent set, falling back \
803 to default ability set.",
804 set_key
805 );
806 tool_default(tool.kind).cloned().unwrap_or_default()
807 }
808 } else if let Some(set) = tool_default(tool.kind) {
809 set.clone()
810 .modified_by_tool(tool, item.stats_durability_multiplier())
811 } else {
812 error!(
813 "No ability set defined for tool: {:?}, falling back to default ability \
814 set.",
815 tool.kind
816 );
817 Default::default()
818 };
819
820 Ok(ItemConfig { abilities })
821 },
822 ItemKind::Glider => item
823 .ability_spec()
824 .and_then(|set_key| ability_map.get_ability_set(&set_key))
825 .map(|abilities| ItemConfig {
826 abilities: abilities.clone(),
827 })
828 .ok_or(ItemConfigError::BadItemKind),
829 _ => Err(ItemConfigError::BadItemKind),
830 }
831 }
832}
833
834impl ItemDef {
835 pub fn is_stackable(&self) -> bool {
836 matches!(
837 self.kind,
838 ItemKind::Consumable { .. }
839 | ItemKind::Ingredient { .. }
840 | ItemKind::Utility { .. }
841 | ItemKind::Tool(Tool {
842 kind: ToolKind::Throwable,
843 ..
844 })
845 )
846 }
847
848 pub fn max_amount(&self) -> u32 { if self.is_stackable() { u32::MAX } else { 1 } }
851
852 pub fn id(&self) -> &str { &self.item_definition_id }
854
855 #[cfg(test)]
856 pub fn new_test(
857 item_definition_id: String,
858 kind: ItemKind,
859 quality: Quality,
860 tags: Vec<ItemTag>,
861 slots: u16,
862 ) -> Self {
863 #[expect(deprecated)]
864 Self {
865 item_definition_id,
866 name: "test item name".to_owned(),
867 description: "test item description".to_owned(),
868 kind,
869 quality,
870 tags,
871 slots,
872 ability_spec: None,
873 }
874 }
875
876 #[cfg(test)]
877 pub fn create_test_itemdef_from_kind(kind: ItemKind) -> Self {
878 #[expect(deprecated)]
879 Self {
880 item_definition_id: "test.item".to_string(),
881 name: "test item name".to_owned(),
882 description: "test item description".to_owned(),
883 kind,
884 quality: Quality::Common,
885 tags: vec![],
886 slots: 0,
887 ability_spec: None,
888 }
889 }
890}
891
892impl PartialEq for Item {
899 fn eq(&self, other: &Self) -> bool {
900 (match (&self.item_base, &other.item_base) {
901 (ItemBase::Simple(our_def), ItemBase::Simple(other_def)) => {
902 our_def.item_definition_id == other_def.item_definition_id
903 },
904 (ItemBase::Modular(our_base), ItemBase::Modular(other_base)) => our_base == other_base,
905 _ => false,
906 }) && self.components() == other.components()
907 }
908}
909
910impl Asset for ItemDef {
911 fn load(cache: &AssetCache, specifier: &SharedString) -> Result<Self, BoxedError> {
912 if specifier.starts_with("veloren.core.") {
913 return Err(format!(
914 "Attempted to load an asset from a specifier reserved for core veloren functions. \
915 Specifier: {}",
916 specifier
917 )
918 .into());
919 }
920
921 let RawItemDef {
922 legacy_name,
923 legacy_description,
924 kind,
925 quality,
926 tags,
927 slots,
928 ability_spec,
929 } = cache.load::<Ron<_>>(specifier)?.cloned().into_inner();
930
931 let item_definition_id = specifier.replace('\\', ".");
936
937 #[expect(deprecated)]
938 Ok(ItemDef {
939 item_definition_id,
940 name: legacy_name,
941 description: legacy_description,
942 kind,
943 quality,
944 tags,
945 slots,
946 ability_spec,
947 })
948 }
949}
950
951#[derive(Clone, Debug, Serialize, Deserialize)]
952#[serde(rename = "ItemDef", deny_unknown_fields)]
953struct RawItemDef {
954 legacy_name: String,
955 legacy_description: String,
956 kind: ItemKind,
957 quality: Quality,
958 tags: Vec<ItemTag>,
959 #[serde(default)]
960 slots: u16,
961 ability_spec: Option<AbilitySpec>,
962}
963
964#[derive(Debug)]
965pub struct OperationFailure;
966
967impl Item {
968 pub const MAX_DURABILITY: u32 = 12;
969
970 pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
973
974 pub fn new_from_item_base(
975 inner_item: ItemBase,
976 components: Vec<Item>,
977 ability_map: &AbilityMap,
978 msm: &MaterialStatManifest,
979 ) -> Self {
980 let mut item = Item {
981 item_id: Arc::new(AtomicCell::new(None)),
982 amount: NonZeroU32::new(1).unwrap(),
983 components,
984 slots: vec![None; inner_item.num_slots() as usize],
985 item_base: inner_item,
986 item_config: None,
988 hash: 0,
989 durability_lost: None,
990 };
991 item.durability_lost = item.has_durability().then_some(0);
992 item.update_item_state(ability_map, msm);
993 item
994 }
995
996 pub fn new_from_item_definition_id(
997 item_definition_id: ItemDefinitionId<'_>,
998 ability_map: &AbilityMap,
999 msm: &MaterialStatManifest,
1000 ) -> Result<Self, Error> {
1001 let (base, components) = match item_definition_id {
1002 ItemDefinitionId::Simple(spec) => {
1003 let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(&spec)?);
1004 (base, Vec::new())
1005 },
1006 ItemDefinitionId::Modular {
1007 pseudo_base,
1008 components,
1009 } => {
1010 let base = ItemBase::Modular(ModularBase::load_from_pseudo_id(pseudo_base));
1011 let components = components
1012 .into_iter()
1013 .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1014 .collect::<Result<Vec<_>, _>>()?;
1015 (base, components)
1016 },
1017 ItemDefinitionId::Compound {
1018 simple_base,
1019 components,
1020 } => {
1021 let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(simple_base)?);
1022 let components = components
1023 .into_iter()
1024 .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1025 .collect::<Result<Vec<_>, _>>()?;
1026 (base, components)
1027 },
1028 };
1029 Ok(Item::new_from_item_base(base, components, ability_map, msm))
1030 }
1031
1032 pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
1035 Item::new_from_asset(asset_specifier).unwrap_or_else(|err| {
1036 panic!(
1037 "Expected asset to exist: {}, instead got error {:?}",
1038 asset_specifier, err
1039 );
1040 })
1041 }
1042
1043 pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
1046 let specifier = asset_glob.strip_suffix(".*").unwrap_or(asset_glob);
1047 let defs = assets::load_rec_dir::<Ron<RawItemDef>>(specifier)?;
1048 defs.read()
1049 .ids()
1050 .map(|id| Item::new_from_asset(id))
1051 .collect()
1052 }
1053
1054 pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
1057 let inner_item = ItemBase::from_item_id_string(asset)?;
1058 let msm = &MaterialStatManifest::load().read();
1060 let ability_map = &AbilityMap::load().read();
1061 Ok(Item::new_from_item_base(
1062 inner_item,
1063 Vec::new(),
1064 ability_map,
1065 msm,
1066 ))
1067 }
1068
1069 #[must_use]
1071 pub fn frontend_item(
1072 &self,
1073 ability_map: &AbilityMap,
1074 msm: &MaterialStatManifest,
1075 ) -> FrontendItem {
1076 FrontendItem(self.duplicate(ability_map, msm))
1077 }
1078
1079 #[must_use]
1081 pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1082 let duplicated_components = self
1083 .components
1084 .iter()
1085 .map(|comp| comp.duplicate(ability_map, msm))
1086 .collect();
1087 let mut new_item = Item::new_from_item_base(
1088 match &self.item_base {
1089 ItemBase::Simple(item_def) => ItemBase::Simple(Arc::clone(item_def)),
1090 ItemBase::Modular(mod_base) => ItemBase::Modular(mod_base.clone()),
1091 },
1092 duplicated_components,
1093 ability_map,
1094 msm,
1095 );
1096 new_item.set_amount(self.amount()).expect(
1097 "`new_item` has the same `item_def` and as an invariant, \
1098 self.set_amount(self.amount()) should always succeed.",
1099 );
1100 new_item.slots_mut().iter_mut().zip(self.slots()).for_each(
1101 |(new_item_slot, old_item_slot)| {
1102 *new_item_slot = old_item_slot
1103 .as_ref()
1104 .map(|old_item| old_item.duplicate(ability_map, msm));
1105 },
1106 );
1107 new_item
1108 }
1109
1110 pub fn stacked_duplicates<'a>(
1111 &'a self,
1112 ability_map: &'a AbilityMap,
1113 msm: &'a MaterialStatManifest,
1114 count: u32,
1115 ) -> impl Iterator<Item = Self> + 'a {
1116 let max_stack_count = count / self.max_amount();
1117 let rest = count % self.max_amount();
1118
1119 (0..max_stack_count)
1120 .map(|_| {
1121 let mut item = self.duplicate(ability_map, msm);
1122
1123 item.set_amount(item.max_amount())
1124 .expect("max_amount() is always a valid amount.");
1125
1126 item
1127 })
1128 .chain((rest > 0).then(move || {
1129 let mut item = self.duplicate(ability_map, msm);
1130
1131 item.set_amount(rest)
1132 .expect("anything less than max_amount() is always a valid amount.");
1133
1134 item
1135 }))
1136 }
1137
1138 #[doc(hidden)]
1146 pub fn get_item_id_for_database(&self) -> Arc<ItemId> { Arc::clone(&self.item_id) }
1147
1148 fn reset_item_id(&mut self) {
1158 if let Some(item_id) = Arc::get_mut(&mut self.item_id) {
1159 *item_id = AtomicCell::new(None);
1160 } else {
1161 self.item_id = Arc::new(AtomicCell::new(None));
1162 }
1163 for component in self.components.iter_mut() {
1165 component.reset_item_id();
1166 }
1167 }
1168
1169 pub fn put_in_world(&mut self) { self.reset_item_id() }
1174
1175 pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), OperationFailure> {
1176 let amount = u32::from(self.amount);
1177 self.amount = amount
1178 .checked_add(increase_by)
1179 .filter(|&amount| amount <= self.max_amount())
1180 .and_then(NonZeroU32::new)
1181 .ok_or(OperationFailure)?;
1182 Ok(())
1183 }
1184
1185 pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), OperationFailure> {
1186 let amount = u32::from(self.amount);
1187 self.amount = amount
1188 .checked_sub(decrease_by)
1189 .and_then(NonZeroU32::new)
1190 .ok_or(OperationFailure)?;
1191 Ok(())
1192 }
1193
1194 pub fn set_amount(&mut self, give_amount: u32) -> Result<(), OperationFailure> {
1195 if give_amount <= self.max_amount() {
1196 self.amount = NonZeroU32::new(give_amount).ok_or(OperationFailure)?;
1197 Ok(())
1198 } else {
1199 Err(OperationFailure)
1200 }
1201 }
1202
1203 pub fn persistence_access_add_component(&mut self, component: Item) {
1204 self.components.push(component);
1205 }
1206
1207 pub fn persistence_access_mutable_component(&mut self, index: usize) -> Option<&mut Self> {
1208 self.components.get_mut(index)
1209 }
1210
1211 pub fn update_item_state(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1215 if let Ok(item_config) = ItemConfig::try_from((&*self, ability_map, msm)) {
1217 self.item_config = Some(Box::new(item_config));
1218 }
1219 self.hash = {
1221 let mut s = DefaultHasher::new();
1222 self.hash(&mut s);
1223 s.finish()
1224 };
1225 }
1226
1227 pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
1229 self.slots.iter_mut().filter_map(mem::take)
1230 }
1231
1232 pub fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1233 match &self.item_base {
1234 ItemBase::Simple(item_def) => {
1235 if self.components.is_empty() {
1236 ItemDefinitionId::Simple(Cow::Borrowed(&item_def.item_definition_id))
1237 } else {
1238 ItemDefinitionId::Compound {
1239 simple_base: &item_def.item_definition_id,
1240 components: self
1241 .components
1242 .iter()
1243 .map(|item| item.item_definition_id())
1244 .collect(),
1245 }
1246 }
1247 },
1248 ItemBase::Modular(mod_base) => ItemDefinitionId::Modular {
1249 pseudo_base: mod_base.pseudo_item_id(),
1250 components: self
1251 .components
1252 .iter()
1253 .map(|item| item.item_definition_id())
1254 .collect(),
1255 },
1256 }
1257 }
1258
1259 pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
1260 if let ItemBase::Simple(self_def) = &self.item_base {
1261 self_def.item_definition_id == item_def.item_definition_id
1262 } else {
1263 false
1264 }
1265 }
1266
1267 pub fn matches_recipe_input(&self, recipe_input: &RecipeInput, amount: u32) -> bool {
1268 match recipe_input {
1269 RecipeInput::Item(item_def) => self.is_same_item_def(item_def),
1270 RecipeInput::Tag(tag) => self.tags().contains(tag),
1271 RecipeInput::TagSameItem(tag) => {
1272 self.tags().contains(tag) && u32::from(self.amount) >= amount
1273 },
1274 RecipeInput::ListSameItem(item_defs) => item_defs.iter().any(|item_def| {
1275 self.is_same_item_def(item_def) && u32::from(self.amount) >= amount
1276 }),
1277 }
1278 }
1279
1280 pub fn is_salvageable(&self) -> bool {
1281 self.tags()
1282 .iter()
1283 .any(|tag| matches!(tag, ItemTag::SalvageInto(_, _)))
1284 }
1285
1286 pub fn salvage_output(&self) -> impl Iterator<Item = (&str, u32)> {
1287 self.tags().into_iter().filter_map(|tag| {
1288 if let ItemTag::SalvageInto(material, quantity) = tag {
1289 material
1290 .asset_identifier()
1291 .map(|material_id| (material_id, quantity))
1292 } else {
1293 None
1294 }
1295 })
1296 }
1297
1298 #[deprecated = "since item i18n"]
1299 pub fn name(&self) -> Cow<'_, str> {
1300 match &self.item_base {
1301 ItemBase::Simple(item_def) => {
1302 if self.components.is_empty() {
1303 #[expect(deprecated)]
1304 Cow::Borrowed(&item_def.name)
1305 } else {
1306 #[expect(deprecated)]
1307 modular::modify_name(&item_def.name, self)
1308 }
1309 },
1310 ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()),
1311 }
1312 }
1313
1314 #[deprecated = "since item i18n"]
1315 pub fn description(&self) -> &str {
1316 match &self.item_base {
1317 #[expect(deprecated)]
1318 ItemBase::Simple(item_def) => &item_def.description,
1319 ItemBase::Modular(_) => "",
1321 }
1322 }
1323
1324 pub fn kind(&self) -> Cow<'_, ItemKind> {
1325 match &self.item_base {
1326 ItemBase::Simple(item_def) => Cow::Borrowed(&item_def.kind),
1327 ItemBase::Modular(mod_base) => {
1328 let msm = &MaterialStatManifest::load().read();
1330 mod_base.kind(self.components(), msm, self.stats_durability_multiplier())
1331 },
1332 }
1333 }
1334
1335 pub fn amount(&self) -> u32 { u32::from(self.amount) }
1336
1337 pub fn is_stackable(&self) -> bool {
1338 match &self.item_base {
1339 ItemBase::Simple(item_def) => item_def.is_stackable(),
1340 ItemBase::Modular(_) => false,
1342 }
1343 }
1344
1345 pub fn max_amount(&self) -> u32 {
1348 match &self.item_base {
1349 ItemBase::Simple(item_def) => item_def.max_amount(),
1350 ItemBase::Modular(_) => {
1351 debug_assert!(!self.is_stackable());
1352 1
1353 },
1354 }
1355 }
1356
1357 pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
1358
1359 pub fn quality(&self) -> Quality {
1360 match &self.item_base {
1361 ItemBase::Simple(item_def) => item_def.quality.max(
1362 self.components
1363 .iter()
1364 .fold(Quality::MIN, |a, b| a.max(b.quality())),
1365 ),
1366 ItemBase::Modular(mod_base) => mod_base.compute_quality(self.components()),
1367 }
1368 }
1369
1370 pub fn components(&self) -> &[Item] { &self.components }
1371
1372 pub fn slots(&self) -> &[InvSlot] { &self.slots }
1373
1374 pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
1375
1376 pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() }
1377
1378 pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }
1379
1380 pub fn populated_slots(&self) -> usize { self.slots().len().saturating_sub(self.free_slots()) }
1381
1382 pub fn slot(&self, slot: usize) -> Option<&InvSlot> { self.slots.get(slot) }
1383
1384 pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
1385
1386 pub fn try_reclaim_from_block(
1387 block: Block,
1388 sprite_cfg: Option<&SpriteCfg>,
1389 ) -> Option<Vec<(u32, Self)>> {
1390 if let Some(loot_spec) = sprite_cfg.and_then(|sprite_cfg| sprite_cfg.loot_table.as_ref()) {
1391 LootSpec::LootTable(loot_spec).to_items()
1392 } else {
1393 block.get_sprite()?.default_loot_spec()??.to_items()
1394 }
1395 }
1396
1397 pub fn ability_spec(&self) -> Option<Cow<'_, AbilitySpec>> {
1398 match &self.item_base {
1399 ItemBase::Simple(item_def) => {
1400 item_def.ability_spec.as_ref().map(Cow::Borrowed).or({
1401 if let ItemKind::Tool(tool) = &item_def.kind {
1404 Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
1405 } else {
1406 None
1407 }
1408 })
1409 },
1410 ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
1411 }
1412 }
1413
1414 pub fn tags(&self) -> Vec<ItemTag> {
1417 match &self.item_base {
1418 ItemBase::Simple(item_def) => item_def.tags.to_vec(),
1419 ItemBase::Modular(mod_base) => mod_base.generate_tags(self.components()),
1421 }
1422 }
1423
1424 pub fn is_modular(&self) -> bool {
1425 match &self.item_base {
1426 ItemBase::Simple(_) => false,
1427 ItemBase::Modular(_) => true,
1428 }
1429 }
1430
1431 pub fn item_hash(&self) -> u64 { self.hash }
1432
1433 pub fn persistence_item_id(&self) -> String {
1434 match &self.item_base {
1435 ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
1436 ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
1437 }
1438 }
1439
1440 pub fn durability_lost(&self) -> Option<u32> {
1441 self.durability_lost.map(|x| x.min(Self::MAX_DURABILITY))
1442 }
1443
1444 pub fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1445 let durability_lost = self.durability_lost.unwrap_or(0);
1446 debug_assert!(durability_lost <= Self::MAX_DURABILITY);
1447 const DURABILITY_THRESHOLD: u32 = 9;
1449 const MIN_FRAC: f32 = 0.25;
1450 let mult = (1.0
1451 - durability_lost.saturating_sub(DURABILITY_THRESHOLD) as f32
1452 / (Self::MAX_DURABILITY - DURABILITY_THRESHOLD) as f32)
1453 * (1.0 - MIN_FRAC)
1454 + MIN_FRAC;
1455 DurabilityMultiplier(mult)
1456 }
1457
1458 pub fn has_durability(&self) -> bool {
1459 self.kind().has_durability() && self.quality() != Quality::Debug
1460 }
1461
1462 pub fn increment_damage(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1463 if let Some(durability_lost) = &mut self.durability_lost
1464 && *durability_lost < Self::MAX_DURABILITY
1465 {
1466 *durability_lost += 1;
1467 }
1468 self.update_item_state(ability_map, msm);
1471 }
1472
1473 pub fn persistence_durability(&self) -> Option<NonZeroU32> {
1474 self.durability_lost.and_then(NonZeroU32::new)
1475 }
1476
1477 pub fn persistence_set_durability(&mut self, value: Option<NonZeroU32>) {
1478 if !self.has_durability() {
1481 self.durability_lost = None;
1482 } else {
1483 self.durability_lost = Some(value.map_or(0, NonZeroU32::get));
1486 }
1487 }
1488
1489 pub fn reset_durability(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1490 self.durability_lost = self.has_durability().then_some(0);
1491 self.update_item_state(ability_map, msm);
1494 }
1495
1496 #[must_use = "Returned items will be lost if not used"]
1500 pub fn take_amount(
1501 &mut self,
1502 ability_map: &AbilityMap,
1503 msm: &MaterialStatManifest,
1504 returning_amount: u32,
1505 ) -> Option<Item> {
1506 if self.is_stackable() && self.amount() > 1 && returning_amount < self.amount() {
1507 let mut return_item = self.duplicate(ability_map, msm);
1508 self.decrease_amount(returning_amount).ok()?;
1509 return_item.set_amount(returning_amount).expect(
1510 "return_item.amount() = returning_amount < self.amount() (since self.amount() ≥ \
1511 1) ≤ self.max_amount() = return_item.max_amount(), since return_item is a \
1512 duplicate of item",
1513 );
1514 Some(return_item)
1515 } else {
1516 None
1517 }
1518 }
1519
1520 #[must_use = "Returned items will be lost if not used"]
1524 pub fn take_half(
1525 &mut self,
1526 ability_map: &AbilityMap,
1527 msm: &MaterialStatManifest,
1528 ) -> Option<Item> {
1529 self.take_amount(ability_map, msm, self.amount() / 2)
1530 }
1531
1532 #[cfg(test)]
1533 pub fn create_test_item_from_kind(kind: ItemKind) -> Self {
1534 let ability_map = &AbilityMap::load().read();
1535 let msm = &MaterialStatManifest::load().read();
1536 Self::new_from_item_base(
1537 ItemBase::Simple(Arc::new(ItemDef::create_test_itemdef_from_kind(kind))),
1538 Vec::new(),
1539 ability_map,
1540 msm,
1541 )
1542 }
1543
1544 pub fn can_merge(&self, other: &Self) -> bool {
1549 if self.amount() > self.max_amount() || other.amount() > other.max_amount() {
1550 error!("An item amount is over max_amount!");
1551 return false;
1552 }
1553
1554 (self == other)
1555 && self.slots().iter().all(Option::is_none)
1556 && other.slots().iter().all(Option::is_none)
1557 && self.durability_lost() == other.durability_lost()
1558 }
1559
1560 pub fn try_merge(&mut self, mut other: Self) -> Result<Option<Self>, Self> {
1570 if self.can_merge(&other) {
1571 let max_amount = self.max_amount();
1572 debug_assert_eq!(
1573 max_amount,
1574 other.max_amount(),
1575 "Mergeable items must have the same max_amount()"
1576 );
1577
1578 let to_fill_self = max_amount
1581 .checked_sub(self.amount())
1582 .expect("can_merge should ensure that amount() <= max_amount()");
1583
1584 if let Some(remainder) = other.amount().checked_sub(to_fill_self).filter(|r| *r > 0) {
1585 self.set_amount(max_amount)
1586 .expect("max_amount() is always a valid amount.");
1587 other.set_amount(remainder).expect(
1588 "We know remainder is more than 0 and less than or equal to max_amount()",
1589 );
1590 Ok(Some(other))
1591 } else {
1592 self.increase_amount(other.amount())
1594 .expect("We know that we can at least add other.amount() to this item");
1595 drop(other);
1596 Ok(None)
1597 }
1598 } else {
1599 Err(other)
1600 }
1601 }
1602
1603 pub fn persistence_item_base(&self) -> &ItemBase { &self.item_base }
1606}
1607
1608impl FrontendItem {
1609 #[must_use]
1612 pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1613 FrontendItem(self.0.duplicate(ability_map, msm))
1614 }
1615
1616 pub fn set_amount(&mut self, amount: u32) -> Result<(), OperationFailure> {
1617 self.0.set_amount(amount)
1618 }
1619}
1620
1621impl PickupItem {
1622 pub fn new(item: Item, time: ProgramTime, should_merge: bool) -> Self {
1623 Self {
1624 items: vec![item],
1625 created_at: time,
1626 next_merge_check: time,
1627 should_merge,
1628 }
1629 }
1630
1631 pub fn item(&self) -> &Item {
1635 self.items
1636 .last()
1637 .expect("PickupItem without at least one item is an invariant")
1638 }
1639
1640 pub fn created(&self) -> ProgramTime { self.created_at }
1641
1642 pub fn next_merge_check(&self) -> ProgramTime { self.next_merge_check }
1643
1644 pub fn next_merge_check_mut(&mut self) -> &mut ProgramTime { &mut self.next_merge_check }
1645
1646 pub fn amount(&self) -> u32 {
1648 self.items
1649 .iter()
1650 .map(Item::amount)
1651 .fold(0, |total, amount| total.saturating_add(amount))
1652 }
1653
1654 pub fn remove_debug_items(&mut self) {
1657 for item in self.items.iter_mut() {
1658 item.slots_mut().iter_mut().for_each(|container_slot| {
1659 container_slot
1660 .take_if(|contained_item| matches!(contained_item.quality(), Quality::Debug));
1661 });
1662 }
1663 }
1664
1665 pub fn can_merge(&self, other: &PickupItem) -> bool {
1666 let self_item = self.item();
1667 let other_item = other.item();
1668
1669 self.should_merge && other.should_merge && self_item.can_merge(other_item)
1670 }
1671
1672 pub fn try_merge(&mut self, mut other: PickupItem) -> Result<(), PickupItem> {
1675 if self.can_merge(&other) {
1676 let mut self_last = self
1679 .items
1680 .pop()
1681 .expect("PickupItem without at least one item is an invariant");
1682 let other_last = other
1683 .items
1684 .pop()
1685 .expect("PickupItem without at least one item is an invariant");
1686
1687 let merged = self_last
1689 .try_merge(other_last)
1690 .expect("We know these items can be merged");
1691
1692 debug_assert!(
1693 other
1694 .items
1695 .iter()
1696 .chain(self.items.iter())
1697 .all(|item| item.amount() == item.max_amount()),
1698 "All items before the last in `PickupItem` should have a full amount"
1699 );
1700
1701 self.items.append(&mut other.items);
1704
1705 debug_assert!(
1706 merged.is_none() || self_last.amount() == self_last.max_amount(),
1707 "Merged can only be `Some` if the origin was set to `max_amount()`"
1708 );
1709
1710 self.items.push(self_last);
1712
1713 if let Some(remainder) = merged {
1716 self.items.push(remainder);
1717 }
1718
1719 Ok(())
1720 } else {
1721 Err(other)
1722 }
1723 }
1724
1725 pub fn pick_up(mut self) -> (Item, Option<Self>) {
1726 (
1727 self.items
1728 .pop()
1729 .expect("PickupItem without at least one item is an invariant"),
1730 (!self.items.is_empty()).then_some(self),
1731 )
1732 }
1733}
1734
1735pub fn flatten_counted_items<'a>(
1736 items: &'a [(u32, Item)],
1737 ability_map: &'a AbilityMap,
1738 msm: &'a MaterialStatManifest,
1739) -> impl Iterator<Item = Item> + 'a {
1740 items
1741 .iter()
1742 .flat_map(|(count, item)| item.stacked_duplicates(ability_map, msm, *count))
1743}
1744
1745pub trait ItemDesc {
1748 #[deprecated = "since item i18n"]
1749 fn description(&self) -> &str;
1750 #[deprecated = "since item i18n"]
1751 fn name(&self) -> Cow<'_, str>;
1752 fn kind(&self) -> Cow<'_, ItemKind>;
1753 fn amount(&self) -> NonZeroU32;
1754 fn quality(&self) -> Quality;
1755 fn num_slots(&self) -> u16;
1756 fn item_definition_id(&self) -> ItemDefinitionId<'_>;
1757 fn tags(&self) -> Vec<ItemTag>;
1758 fn is_modular(&self) -> bool;
1759 fn components(&self) -> &[Item];
1760 fn has_durability(&self) -> bool;
1761 fn durability_lost(&self) -> Option<u32>;
1762 fn stats_durability_multiplier(&self) -> DurabilityMultiplier;
1763
1764 fn tool_info(&self) -> Option<ToolKind> {
1765 if let ItemKind::Tool(tool) = &*self.kind() {
1766 Some(tool.kind)
1767 } else {
1768 None
1769 }
1770 }
1771
1772 fn i18n(&self, i18n: &ItemI18n) -> (Content, Content) {
1774 let item_key: ItemKey = self.into();
1775
1776 #[expect(deprecated)]
1777 i18n.item_text_opt(item_key).unwrap_or_else(|| {
1778 (
1779 Content::Plain(self.name().to_string()),
1780 Content::Plain(self.description().to_string()),
1781 )
1782 })
1783 }
1784}
1785
1786impl ItemDesc for Item {
1787 fn description(&self) -> &str {
1788 #[expect(deprecated)]
1789 self.description()
1790 }
1791
1792 fn name(&self) -> Cow<'_, str> {
1793 #[expect(deprecated)]
1794 self.name()
1795 }
1796
1797 fn kind(&self) -> Cow<'_, ItemKind> { self.kind() }
1798
1799 fn amount(&self) -> NonZeroU32 { self.amount }
1800
1801 fn quality(&self) -> Quality { self.quality() }
1802
1803 fn num_slots(&self) -> u16 { self.num_slots() }
1804
1805 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item_definition_id() }
1806
1807 fn tags(&self) -> Vec<ItemTag> { self.tags() }
1808
1809 fn is_modular(&self) -> bool { self.is_modular() }
1810
1811 fn components(&self) -> &[Item] { self.components() }
1812
1813 fn has_durability(&self) -> bool { self.has_durability() }
1814
1815 fn durability_lost(&self) -> Option<u32> { self.durability_lost() }
1816
1817 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1818 self.stats_durability_multiplier()
1819 }
1820}
1821
1822impl ItemDesc for FrontendItem {
1823 fn description(&self) -> &str {
1824 #[expect(deprecated)]
1825 self.0.description()
1826 }
1827
1828 fn name(&self) -> Cow<'_, str> {
1829 #[expect(deprecated)]
1830 self.0.name()
1831 }
1832
1833 fn kind(&self) -> Cow<'_, ItemKind> { self.0.kind() }
1834
1835 fn amount(&self) -> NonZeroU32 { self.0.amount }
1836
1837 fn quality(&self) -> Quality { self.0.quality() }
1838
1839 fn num_slots(&self) -> u16 { self.0.num_slots() }
1840
1841 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.0.item_definition_id() }
1842
1843 fn tags(&self) -> Vec<ItemTag> { self.0.tags() }
1844
1845 fn is_modular(&self) -> bool { self.0.is_modular() }
1846
1847 fn components(&self) -> &[Item] { self.0.components() }
1848
1849 fn has_durability(&self) -> bool { self.0.has_durability() }
1850
1851 fn durability_lost(&self) -> Option<u32> { self.0.durability_lost() }
1852
1853 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1854 self.0.stats_durability_multiplier()
1855 }
1856}
1857
1858impl ItemDesc for ItemDef {
1859 fn description(&self) -> &str {
1860 #[expect(deprecated)]
1861 &self.description
1862 }
1863
1864 fn name(&self) -> Cow<'_, str> {
1865 #[expect(deprecated)]
1866 Cow::Borrowed(&self.name)
1867 }
1868
1869 fn kind(&self) -> Cow<'_, ItemKind> { Cow::Borrowed(&self.kind) }
1870
1871 fn amount(&self) -> NonZeroU32 { NonZeroU32::new(1).unwrap() }
1872
1873 fn quality(&self) -> Quality { self.quality }
1874
1875 fn num_slots(&self) -> u16 { self.slots }
1876
1877 fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1878 ItemDefinitionId::Simple(Cow::Borrowed(&self.item_definition_id))
1879 }
1880
1881 fn tags(&self) -> Vec<ItemTag> { self.tags.to_vec() }
1882
1883 fn is_modular(&self) -> bool { false }
1884
1885 fn components(&self) -> &[Item] { &[] }
1886
1887 fn has_durability(&self) -> bool {
1888 self.kind().has_durability() && self.quality != Quality::Debug
1889 }
1890
1891 fn durability_lost(&self) -> Option<u32> { None }
1892
1893 fn stats_durability_multiplier(&self) -> DurabilityMultiplier { DurabilityMultiplier(1.0) }
1894}
1895
1896impl ItemDesc for PickupItem {
1897 fn description(&self) -> &str {
1898 #[expect(deprecated)]
1899 self.item().description()
1900 }
1901
1902 fn name(&self) -> Cow<'_, str> {
1903 #[expect(deprecated)]
1904 self.item().name()
1905 }
1906
1907 fn kind(&self) -> Cow<'_, ItemKind> { self.item().kind() }
1908
1909 fn amount(&self) -> NonZeroU32 {
1910 NonZeroU32::new(self.amount()).expect("Item having amount of 0 is invariant")
1911 }
1912
1913 fn quality(&self) -> Quality { self.item().quality() }
1914
1915 fn num_slots(&self) -> u16 { self.item().num_slots() }
1916
1917 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item().item_definition_id() }
1918
1919 fn tags(&self) -> Vec<ItemTag> { self.item().tags() }
1920
1921 fn is_modular(&self) -> bool { self.item().is_modular() }
1922
1923 fn components(&self) -> &[Item] { self.item().components() }
1924
1925 fn has_durability(&self) -> bool { self.item().has_durability() }
1926
1927 fn durability_lost(&self) -> Option<u32> { self.item().durability_lost() }
1928
1929 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1930 self.item().stats_durability_multiplier()
1931 }
1932}
1933
1934#[derive(Clone, Debug, Serialize, Deserialize)]
1935pub struct ItemDrops(pub Vec<(u32, Item)>);
1936
1937impl Component for ItemDrops {
1938 type Storage = DenseVecStorage<Self>;
1939}
1940
1941impl Component for PickupItem {
1942 type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1943}
1944
1945impl Component for ThrownItem {
1946 type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1947}
1948
1949#[derive(Copy, Clone, Debug)]
1950pub struct DurabilityMultiplier(pub f32);
1951
1952impl<T: ItemDesc + ?Sized> ItemDesc for &T {
1953 fn description(&self) -> &str {
1954 #[expect(deprecated)]
1955 (*self).description()
1956 }
1957
1958 fn name(&self) -> Cow<'_, str> {
1959 #[expect(deprecated)]
1960 (*self).name()
1961 }
1962
1963 fn kind(&self) -> Cow<'_, ItemKind> { (*self).kind() }
1964
1965 fn amount(&self) -> NonZeroU32 { (*self).amount() }
1966
1967 fn quality(&self) -> Quality { (*self).quality() }
1968
1969 fn num_slots(&self) -> u16 { (*self).num_slots() }
1970
1971 fn item_definition_id(&self) -> ItemDefinitionId<'_> { (*self).item_definition_id() }
1972
1973 fn tags(&self) -> Vec<ItemTag> { (*self).tags() }
1974
1975 fn is_modular(&self) -> bool { (*self).is_modular() }
1976
1977 fn components(&self) -> &[Item] { (*self).components() }
1978
1979 fn has_durability(&self) -> bool { (*self).has_durability() }
1980
1981 fn durability_lost(&self) -> Option<u32> { (*self).durability_lost() }
1982
1983 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1984 (*self).stats_durability_multiplier()
1985 }
1986}
1987
1988pub fn all_item_defs_expect() -> Vec<String> {
1992 try_all_item_defs().expect("Failed to access items directory")
1993}
1994
1995pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
1997 let defs = assets::load_rec_dir::<Ron<RawItemDef>>("common.items")?;
1998 Ok(defs.read().ids().map(|id| id.to_string()).collect())
1999}
2000
2001pub fn all_items_expect() -> Vec<Item> {
2004 let defs = assets::load_rec_dir::<Ron<RawItemDef>>("common.items")
2005 .expect("failed to load item asset directory");
2006
2007 let mut asset_items: Vec<Item> = defs
2009 .read()
2010 .ids()
2011 .map(|id| Item::new_from_asset_expect(id))
2012 .collect();
2013
2014 let mut material_parse_table = HashMap::new();
2015 for mat in Material::iter() {
2016 if let Some(id) = mat.asset_identifier() {
2017 material_parse_table.insert(id.to_owned(), mat);
2018 }
2019 }
2020
2021 let primary_comp_pool = modular::PRIMARY_COMPONENT_POOL.clone();
2022
2023 let mut primary_comps: Vec<Item> = primary_comp_pool
2025 .values()
2026 .flatten()
2027 .map(|(item, _hand_rules)| item.clone())
2028 .collect();
2029
2030 let mut modular_items: Vec<Item> = primary_comp_pool
2032 .keys()
2033 .flat_map(|(tool, mat_id)| {
2034 let mat = material_parse_table
2035 .get(mat_id)
2036 .expect("unexpected material ident");
2037
2038 modular::generate_weapons(*tool, *mat, None)
2040 .expect("failure during modular weapon generation")
2041 })
2042 .collect();
2043
2044 let mut all = Vec::new();
2053 all.append(&mut asset_items);
2054 all.append(&mut primary_comps);
2055 all.append(&mut modular_items);
2056
2057 all
2058}
2059
2060impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2061 fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
2062 use ItemDefinitionId as DefId;
2063 match self {
2064 Self::Simple(simple) => {
2065 matches!(other, DefId::Simple(other_simple) if simple == other_simple)
2066 },
2067 Self::Modular {
2068 pseudo_base,
2069 components,
2070 } => matches!(
2071 other,
2072 DefId::Modular { pseudo_base: other_base, components: other_comps }
2073 if pseudo_base == other_base && components == other_comps
2074 ),
2075 Self::Compound {
2076 simple_base,
2077 components,
2078 } => matches!(
2079 other,
2080 DefId::Compound { simple_base: other_base, components: other_comps }
2081 if simple_base == other_base && components == other_comps
2082 ),
2083 }
2084 }
2085}
2086
2087impl PartialEq<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2088 #[inline]
2089 fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self }
2090}
2091
2092impl Equivalent<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2093 fn equivalent(&self, key: &ItemDefinitionIdOwned) -> bool { self == key }
2094}
2095
2096impl From<&ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2097 fn from(value: &ItemDefinitionId<'_>) -> Self { value.to_owned() }
2098}
2099
2100#[cfg(test)]
2101mod tests {
2102 use super::*;
2103
2104 #[test]
2105 fn test_assets_items() {
2106 let ids = all_item_defs_expect();
2107 for item in ids.iter().map(|id| Item::new_from_asset_expect(id)) {
2108 if let ItemKind::Consumable {
2109 container: Some(container),
2110 ..
2111 } = item.kind().as_ref()
2112 {
2113 Item::new_from_item_definition_id(
2114 container.as_ref(),
2115 &AbilityMap::load().read(),
2116 &MaterialStatManifest::load().read(),
2117 )
2118 .unwrap();
2119 }
2120 drop(item)
2121 }
2122 }
2123
2124 #[test]
2125 fn test_item_i18n() { let _ = ItemI18n::new_expect(); }
2126
2127 #[test]
2128 fn test_all_items() { let _ = all_items_expect(); }
2130
2131 #[test]
2132 fn ensure_item_localization() {
2135 let manifest = ItemI18n::new_expect();
2136 let items = all_items_expect();
2137 let mut errs = vec![];
2138 for item in items {
2139 let item_key: ItemKey = (&item).into();
2140 if manifest.item_text_opt(item_key.clone()).is_none() {
2141 errs.push(item_key)
2142 }
2143 }
2144 if !errs.is_empty() {
2145 panic!("item i18n manifest misses translation-id for following items {errs:#?}")
2146 }
2147 }
2148}