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, AssetExt, BoxedError, Error},
12 comp::inventory::InvSlot,
13 effect::Effect,
14 lottery::LootSpec,
15 recipe::RecipeInput,
16 resources::ProgramTime,
17 terrain::{Block, sprite::SpriteCfg},
18};
19use common_i18n::Content;
20use core::{
21 convert::TryFrom,
22 mem,
23 num::{NonZeroU32, NonZeroU64},
24};
25use crossbeam_utils::atomic::AtomicCell;
26use hashbrown::{Equivalent, HashMap};
27use item_key::ItemKey;
28use serde::{Deserialize, Serialize, Serializer, de};
29use specs::{Component, DenseVecStorage, DerefFlaggedStorage};
30use std::{borrow::Cow, collections::hash_map::DefaultHasher, fmt, sync::Arc};
31use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
32use tracing::error;
33use vek::Rgb;
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, strum::EnumString)]
36pub enum Reagent {
37 Blue,
38 Green,
39 Purple,
40 Red,
41 White,
42 Yellow,
43 FireRain,
44 FireGigas,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub enum Utility {
49 Coins,
50 Collar,
51 Key,
52}
53
54#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
55#[serde(deny_unknown_fields)]
56pub struct Lantern {
57 color: Rgb<u32>,
58 strength_thousandths: u32,
59 flicker_thousandths: u32,
60}
61
62impl Lantern {
63 pub fn strength(&self) -> f32 { self.strength_thousandths as f32 / 1000_f32 }
64
65 pub fn color(&self) -> Rgb<f32> { self.color.map(|c| c as f32 / 255.0) }
66
67 pub fn flicker(&self) -> f32 { self.flicker_thousandths as f32 / 1000_f32 }
68}
69
70#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
71pub enum Quality {
72 Low, Common, Moderate, High, Epic, Legendary, Artifact, Debug, }
81
82impl Quality {
83 pub const MIN: Self = Self::Low;
84}
85
86pub trait TagExampleInfo {
87 fn name(&self) -> &str;
88 fn exemplar_identifier(&self) -> Option<&str>;
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr)]
94pub enum MaterialKind {
95 Metal,
96 Gem,
97 Wood,
98 Stone,
99 Cloth,
100 Hide,
101}
102
103#[derive(
104 Clone,
105 Copy,
106 Debug,
107 PartialEq,
108 Eq,
109 Hash,
110 Serialize,
111 Deserialize,
112 IntoStaticStr,
113 EnumString,
114 EnumIter,
115)]
116#[strum(serialize_all = "snake_case")]
117pub enum Material {
118 Bronze,
119 Iron,
120 Steel,
121 Cobalt,
122 Bloodsteel,
123 Silver,
124 Gold,
125 Orichalcum,
126 Topaz,
127 Emerald,
128 Sapphire,
129 Amethyst,
130 Ruby,
131 Diamond,
132 Twig,
133 PlantFiber,
134 Wood,
135 Bamboo,
136 Hardwood,
137 Ironwood,
138 Frostwood,
139 Eldwood,
140 Rock,
141 Granite,
142 Bone,
143 Basalt,
144 Obsidian,
145 Velorite,
146 Linen,
147 RedLinen,
148 Wool,
149 Silk,
150 Lifecloth,
151 Moonweave,
152 Sunsilk,
153 Rawhide,
154 Leather,
155 RigidLeather,
156 Scale,
157 Carapace,
158 Plate,
159 Dragonscale,
160}
161
162impl Material {
163 pub fn material_kind(&self) -> MaterialKind {
164 match self {
165 Material::Bronze
166 | Material::Iron
167 | Material::Steel
168 | Material::Cobalt
169 | Material::Bloodsteel
170 | Material::Silver
171 | Material::Gold
172 | Material::Orichalcum => MaterialKind::Metal,
173 Material::Topaz
174 | Material::Emerald
175 | Material::Sapphire
176 | Material::Amethyst
177 | Material::Ruby
178 | Material::Diamond => MaterialKind::Gem,
179 Material::Wood
180 | Material::Twig
181 | Material::PlantFiber
182 | Material::Bamboo
183 | Material::Hardwood
184 | Material::Ironwood
185 | Material::Frostwood
186 | Material::Eldwood => MaterialKind::Wood,
187 Material::Rock
188 | Material::Granite
189 | Material::Bone
190 | Material::Basalt
191 | Material::Obsidian
192 | Material::Velorite => MaterialKind::Stone,
193 Material::Linen
194 | Material::RedLinen
195 | Material::Wool
196 | Material::Silk
197 | Material::Lifecloth
198 | Material::Moonweave
199 | Material::Sunsilk => MaterialKind::Cloth,
200 Material::Rawhide
201 | Material::Leather
202 | Material::RigidLeather
203 | Material::Scale
204 | Material::Carapace
205 | Material::Plate
206 | Material::Dragonscale => MaterialKind::Hide,
207 }
208 }
209
210 pub fn asset_identifier(&self) -> Option<&'static str> {
211 match self {
212 Material::Bronze => Some("common.items.mineral.ingot.bronze"),
213 Material::Iron => Some("common.items.mineral.ingot.iron"),
214 Material::Steel => Some("common.items.mineral.ingot.steel"),
215 Material::Cobalt => Some("common.items.mineral.ingot.cobalt"),
216 Material::Bloodsteel => Some("common.items.mineral.ingot.bloodsteel"),
217 Material::Silver => Some("common.items.mineral.ingot.silver"),
218 Material::Gold => Some("common.items.mineral.ingot.gold"),
219 Material::Orichalcum => Some("common.items.mineral.ingot.orichalcum"),
220 Material::Topaz => Some("common.items.mineral.gem.topaz"),
221 Material::Emerald => Some("common.items.mineral.gem.emerald"),
222 Material::Sapphire => Some("common.items.mineral.gem.sapphire"),
223 Material::Amethyst => Some("common.items.mineral.gem.amethyst"),
224 Material::Ruby => Some("common.items.mineral.gem.ruby"),
225 Material::Diamond => Some("common.items.mineral.gem.diamond"),
226 Material::Twig => Some("common.items.crafting_ing.twigs"),
227 Material::PlantFiber => Some("common.items.flowers.plant_fiber"),
228 Material::Wood => Some("common.items.log.wood"),
229 Material::Bamboo => Some("common.items.log.bamboo"),
230 Material::Hardwood => Some("common.items.log.hardwood"),
231 Material::Ironwood => Some("common.items.log.ironwood"),
232 Material::Frostwood => Some("common.items.log.frostwood"),
233 Material::Eldwood => Some("common.items.log.eldwood"),
234 Material::Rock
235 | Material::Granite
236 | Material::Bone
237 | Material::Basalt
238 | Material::Obsidian
239 | Material::Velorite => None,
240 Material::Linen => Some("common.items.crafting_ing.cloth.linen"),
241 Material::RedLinen => Some("common.items.crafting_ing.cloth.linen_red"),
242 Material::Wool => Some("common.items.crafting_ing.cloth.wool"),
243 Material::Silk => Some("common.items.crafting_ing.cloth.silk"),
244 Material::Lifecloth => Some("common.items.crafting_ing.cloth.lifecloth"),
245 Material::Moonweave => Some("common.items.crafting_ing.cloth.moonweave"),
246 Material::Sunsilk => Some("common.items.crafting_ing.cloth.sunsilk"),
247 Material::Rawhide => Some("common.items.crafting_ing.leather.simple_leather"),
248 Material::Leather => Some("common.items.crafting_ing.leather.thick_leather"),
249 Material::RigidLeather => Some("common.items.crafting_ing.leather.rigid_leather"),
250 Material::Scale => Some("common.items.crafting_ing.hide.scales"),
251 Material::Carapace => Some("common.items.crafting_ing.hide.carapace"),
252 Material::Plate => Some("common.items.crafting_ing.hide.plate"),
253 Material::Dragonscale => Some("common.items.crafting_ing.hide.dragon_scale"),
254 }
255 }
256}
257
258impl TagExampleInfo for Material {
259 fn name(&self) -> &str { self.into() }
260
261 fn exemplar_identifier(&self) -> Option<&str> { self.asset_identifier() }
262}
263
264#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
265pub enum ItemTag {
266 Material(Material),
268 MaterialKind(MaterialKind),
270 Cultist,
271 Gnarling,
272 Potion,
273 Charm,
274 Food,
275 BaseMaterial, CraftingTool, Utility,
278 Bag,
279 SalvageInto(Material, u32),
280 Witch,
281 Pirate,
282}
283
284impl TagExampleInfo for ItemTag {
285 fn name(&self) -> &str {
286 match self {
287 ItemTag::Material(material) => material.name(),
288 ItemTag::MaterialKind(material_kind) => material_kind.into(),
289 ItemTag::Cultist => "cultist",
290 ItemTag::Gnarling => "gnarling",
291 ItemTag::Potion => "potion",
292 ItemTag::Charm => "charm",
293 ItemTag::Food => "food",
294 ItemTag::BaseMaterial => "basemat",
295 ItemTag::CraftingTool => "tool",
296 ItemTag::Utility => "utility",
297 ItemTag::Bag => "bag",
298 ItemTag::SalvageInto(_, _) => "salvage",
299 ItemTag::Witch => "witch",
300 ItemTag::Pirate => "pirate",
301 }
302 }
303
304 fn exemplar_identifier(&self) -> Option<&str> {
306 match self {
307 ItemTag::Material(material) => material.exemplar_identifier(),
308 ItemTag::Cultist => Some("common.items.tag_examples.cultist"),
309 ItemTag::Gnarling => Some("common.items.tag_examples.gnarling"),
310 ItemTag::Witch => Some("common.items.tag_examples.witch"),
311 ItemTag::Pirate => Some("common.items.tag_examples.pirate"),
312 ItemTag::MaterialKind(_)
313 | ItemTag::Potion
314 | ItemTag::Food
315 | ItemTag::Charm
316 | ItemTag::BaseMaterial
317 | ItemTag::CraftingTool
318 | ItemTag::Utility
319 | ItemTag::Bag
320 | ItemTag::SalvageInto(_, _) => None,
321 }
322 }
323}
324
325#[derive(Clone, Debug, Serialize, Deserialize)]
326pub enum Effects {
327 Any(Vec<Effect>),
328 All(Vec<Effect>),
329 One(Effect),
330}
331
332impl Effects {
333 pub fn effects(&self) -> &[Effect] {
334 match self {
335 Effects::Any(effects) => effects,
336 Effects::All(effects) => effects,
337 Effects::One(effect) => std::slice::from_ref(effect),
338 }
339 }
340}
341
342#[derive(Clone, Debug, Serialize, Deserialize)]
343#[serde(deny_unknown_fields)]
344pub enum ItemKind {
345 Tool(Tool),
347 ModularComponent(ModularComponent),
348 Lantern(Lantern),
349 Armor(armor::Armor),
350 Glider,
351 Consumable {
352 kind: ConsumableKind,
353 effects: Effects,
354 #[serde(default)]
355 container: Option<ItemDefinitionIdOwned>,
356 },
357 Utility {
358 kind: Utility,
359 },
360 Ingredient {
361 #[deprecated = "part of non-localized name generation"]
364 descriptor: String,
365 },
366 TagExamples {
367 item_ids: Vec<String>,
370 },
371 RecipeGroup {
372 recipes: Vec<String>,
373 },
374}
375
376#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
377pub enum ConsumableKind {
378 Drink,
379 Food,
380 ComplexFood,
381 Charm,
382 Recipe,
383}
384
385impl ItemKind {
386 pub fn is_equippable(&self) -> bool {
387 matches!(
388 self,
389 ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Glider | ItemKind::Lantern(_)
390 )
391 }
392
393 pub fn get_itemkind_string(&self) -> String {
396 match self {
397 ItemKind::Tool(tool) => format!("Tool: {:?}", tool.kind),
399 ItemKind::ModularComponent(modular_component) => {
400 format!("ModularComponent: {:?}", modular_component.toolkind())
401 },
402 ItemKind::Lantern(lantern) => format!("Lantern: {:?}", lantern),
403 ItemKind::Armor(armor) => format!("Armor: {:?}", armor.stats),
404 ItemKind::Glider => "Glider:".to_string(),
405 ItemKind::Consumable { kind, .. } => {
406 format!("Consumable: {:?}", kind)
407 },
408 ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
409 #[expect(deprecated)]
410 ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
411 ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids),
412 ItemKind::RecipeGroup { .. } => String::from("Recipes:"),
413 }
414 }
415
416 pub fn has_durability(&self) -> bool {
417 match self {
418 ItemKind::Tool(Tool { kind, .. }) => !matches!(kind, ToolKind::Throwable),
419 ItemKind::Armor(armor) => armor.kind.has_durability(),
420 ItemKind::ModularComponent(_)
421 | ItemKind::Lantern(_)
422 | ItemKind::Glider
423 | ItemKind::Consumable { .. }
424 | ItemKind::Utility { .. }
425 | ItemKind::Ingredient { .. }
426 | ItemKind::TagExamples { .. }
427 | ItemKind::RecipeGroup { .. } => false,
428 }
429 }
430}
431
432pub type ItemId = AtomicCell<Option<NonZeroU64>>;
433
434#[derive(Clone, Debug, Serialize, Deserialize)]
448pub struct Item {
449 #[serde(skip)]
456 item_id: Arc<ItemId>,
457 item_base: ItemBase,
461 components: Vec<Item>,
470 amount: NonZeroU32,
473 slots: Vec<InvSlot>,
475 item_config: Option<Box<ItemConfig>>,
476 hash: u64,
477 durability_lost: Option<u32>,
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
486pub struct FrontendItem(Item);
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct PickupItem {
502 items: Vec<Item>,
503 created_at: ProgramTime,
505 next_merge_check: ProgramTime,
507 pub should_merge: bool,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize)]
516pub struct ThrownItem(pub Item);
517
518use std::hash::{Hash, Hasher};
519
520impl Hash for Item {
522 fn hash<H: Hasher>(&self, state: &mut H) {
523 self.item_definition_id().hash(state);
524 self.components.iter().for_each(|comp| comp.hash(state));
525 }
526}
527
528type I18nId = String;
531
532#[derive(Clone, Debug, Serialize, Deserialize)]
533pub struct ItemI18n {
549 map: HashMap<ItemKey, I18nId>,
551}
552
553impl assets::Asset for ItemI18n {
554 type Loader = assets::RonLoader;
555
556 const EXTENSION: &'static str = "ron";
557}
558
559impl ItemI18n {
560 pub fn new_expect() -> Self {
561 ItemI18n::load_expect("common.item_i18n_manifest")
562 .read()
563 .clone()
564 }
565
566 fn item_text_opt(&self, mut item_key: ItemKey) -> Option<(Content, Content)> {
570 if let ItemKey::TagExamples(_, id) = item_key {
572 item_key = ItemKey::Simple(id.to_string());
573 }
574
575 let key = self.map.get(&item_key);
576 key.map(|key| {
577 (
578 Content::Key(key.to_owned()),
579 Content::Attr(key.to_owned(), "desc".to_owned()),
580 )
581 })
582 }
583}
584
585#[derive(Clone, Debug)]
586pub enum ItemBase {
587 Simple(Arc<ItemDef>),
588 Modular(ModularBase),
589}
590
591impl Serialize for ItemBase {
592 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
596 where
597 S: Serializer,
598 {
599 serializer.serialize_str(&self.serialization_item_id())
600 }
601}
602
603impl<'de> Deserialize<'de> for ItemBase {
604 fn deserialize<D>(deserializer: D) -> Result<ItemBase, D::Error>
607 where
608 D: de::Deserializer<'de>,
609 {
610 struct ItemBaseStringVisitor;
611
612 impl de::Visitor<'_> for ItemBaseStringVisitor {
613 type Value = ItemBase;
614
615 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
616 formatter.write_str("item def string")
617 }
618
619 fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
620 where
621 E: de::Error,
622 {
623 ItemBase::from_item_id_string(serialized_item_base)
624 .map_err(|err| E::custom(err.to_string()))
625 }
626 }
627
628 deserializer.deserialize_str(ItemBaseStringVisitor)
629 }
630}
631
632impl ItemBase {
633 fn num_slots(&self) -> u16 {
634 match self {
635 ItemBase::Simple(item_def) => item_def.num_slots(),
636 ItemBase::Modular(_) => 0,
637 }
638 }
639
640 fn serialization_item_id(&self) -> String {
643 match &self {
644 ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
645 ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
646 }
647 }
648
649 fn from_item_id_string(item_id_string: &str) -> Result<Self, Error> {
650 if item_id_string.starts_with(crate::modular_item_id_prefix!()) {
651 Ok(ItemBase::Modular(ModularBase::load_from_pseudo_id(
652 item_id_string,
653 )))
654 } else {
655 Ok(ItemBase::Simple(Arc::<ItemDef>::load_cloned(
656 item_id_string,
657 )?))
658 }
659 }
660}
661
662#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
666pub enum ItemDefinitionId<'a> {
667 Simple(Cow<'a, str>),
668 Modular {
669 pseudo_base: &'a str,
670 components: Vec<ItemDefinitionId<'a>>,
671 },
672 Compound {
673 simple_base: &'a str,
674 components: Vec<ItemDefinitionId<'a>>,
675 },
676}
677
678#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
679pub enum ItemDefinitionIdOwned {
680 Simple(String),
681 Modular {
682 pseudo_base: String,
683 components: Vec<ItemDefinitionIdOwned>,
684 },
685 Compound {
686 simple_base: String,
687 components: Vec<ItemDefinitionIdOwned>,
688 },
689}
690
691impl ItemDefinitionIdOwned {
692 pub fn as_ref(&self) -> ItemDefinitionId<'_> {
693 match *self {
694 Self::Simple(ref id) => ItemDefinitionId::Simple(Cow::Borrowed(id)),
695 Self::Modular {
696 ref pseudo_base,
697 ref components,
698 } => ItemDefinitionId::Modular {
699 pseudo_base,
700 components: components.iter().map(|comp| comp.as_ref()).collect(),
701 },
702 Self::Compound {
703 ref simple_base,
704 ref components,
705 } => ItemDefinitionId::Compound {
706 simple_base,
707 components: components.iter().map(|comp| comp.as_ref()).collect(),
708 },
709 }
710 }
711}
712
713impl ItemDefinitionId<'_> {
714 pub fn itemdef_id(&self) -> Option<&str> {
715 match self {
716 Self::Simple(id) => Some(id),
717 Self::Modular { .. } => None,
718 Self::Compound { simple_base, .. } => Some(simple_base),
719 }
720 }
721
722 pub fn to_owned(&self) -> ItemDefinitionIdOwned {
723 match self {
724 Self::Simple(id) => ItemDefinitionIdOwned::Simple(String::from(&**id)),
725 Self::Modular {
726 pseudo_base,
727 components,
728 } => ItemDefinitionIdOwned::Modular {
729 pseudo_base: String::from(*pseudo_base),
730 components: components.iter().map(|comp| comp.to_owned()).collect(),
731 },
732 Self::Compound {
733 simple_base,
734 components,
735 } => ItemDefinitionIdOwned::Compound {
736 simple_base: String::from(*simple_base),
737 components: components.iter().map(|comp| comp.to_owned()).collect(),
738 },
739 }
740 }
741}
742
743#[derive(Debug, Serialize, Deserialize)]
744pub struct ItemDef {
745 #[serde(default)]
746 item_definition_id: String,
750 #[deprecated = "since item i18n"]
751 name: String,
752 #[deprecated = "since item i18n"]
753 description: String,
754 pub kind: ItemKind,
755 pub quality: Quality,
756 pub tags: Vec<ItemTag>,
757 #[serde(default)]
758 pub slots: u16,
759 pub ability_spec: Option<AbilitySpec>,
762}
763
764impl PartialEq for ItemDef {
765 fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
766}
767
768#[derive(Clone, Debug, Serialize, Deserialize)]
770pub struct ItemConfig {
771 pub abilities: AbilitySet<tool::AbilityItem>,
772}
773
774#[derive(Debug)]
775pub enum ItemConfigError {
776 BadItemKind,
777}
778
779impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
780 type Error = ItemConfigError;
781
782 fn try_from(
783 (item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
785 ) -> Result<Self, Self::Error> {
786 match &*item.kind() {
787 ItemKind::Tool(tool) => {
788 let tool_default = |tool_kind| {
790 let key = &AbilitySpec::Tool(tool_kind);
791 ability_map.get_ability_set(key)
792 };
793 let abilities = if let Some(set_key) = item.ability_spec() {
794 if let Some(set) = ability_map.get_ability_set(&set_key) {
795 set.clone()
796 .modified_by_tool(tool, item.stats_durability_multiplier())
797 } else {
798 error!(
799 "Custom ability set: {:?} references non-existent set, falling back \
800 to default ability set.",
801 set_key
802 );
803 tool_default(tool.kind).cloned().unwrap_or_default()
804 }
805 } else if let Some(set) = tool_default(tool.kind) {
806 set.clone()
807 .modified_by_tool(tool, item.stats_durability_multiplier())
808 } else {
809 error!(
810 "No ability set defined for tool: {:?}, falling back to default ability \
811 set.",
812 tool.kind
813 );
814 Default::default()
815 };
816
817 Ok(ItemConfig { abilities })
818 },
819 ItemKind::Glider => item
820 .ability_spec()
821 .and_then(|set_key| ability_map.get_ability_set(&set_key))
822 .map(|abilities| ItemConfig {
823 abilities: abilities.clone(),
824 })
825 .ok_or(ItemConfigError::BadItemKind),
826 _ => Err(ItemConfigError::BadItemKind),
827 }
828 }
829}
830
831impl ItemDef {
832 pub fn is_stackable(&self) -> bool {
833 matches!(
834 self.kind,
835 ItemKind::Consumable { .. }
836 | ItemKind::Ingredient { .. }
837 | ItemKind::Utility { .. }
838 | ItemKind::Tool(Tool {
839 kind: ToolKind::Throwable,
840 ..
841 })
842 )
843 }
844
845 pub fn max_amount(&self) -> u32 { if self.is_stackable() { u32::MAX } else { 1 } }
848
849 pub fn id(&self) -> &str { &self.item_definition_id }
851
852 #[cfg(test)]
853 pub fn new_test(
854 item_definition_id: String,
855 kind: ItemKind,
856 quality: Quality,
857 tags: Vec<ItemTag>,
858 slots: u16,
859 ) -> Self {
860 #[expect(deprecated)]
861 Self {
862 item_definition_id,
863 name: "test item name".to_owned(),
864 description: "test item description".to_owned(),
865 kind,
866 quality,
867 tags,
868 slots,
869 ability_spec: None,
870 }
871 }
872
873 #[cfg(test)]
874 pub fn create_test_itemdef_from_kind(kind: ItemKind) -> Self {
875 #[expect(deprecated)]
876 Self {
877 item_definition_id: "test.item".to_string(),
878 name: "test item name".to_owned(),
879 description: "test item description".to_owned(),
880 kind,
881 quality: Quality::Common,
882 tags: vec![],
883 slots: 0,
884 ability_spec: None,
885 }
886 }
887}
888
889impl PartialEq for Item {
896 fn eq(&self, other: &Self) -> bool {
897 (match (&self.item_base, &other.item_base) {
898 (ItemBase::Simple(our_def), ItemBase::Simple(other_def)) => {
899 our_def.item_definition_id == other_def.item_definition_id
900 },
901 (ItemBase::Modular(our_base), ItemBase::Modular(other_base)) => our_base == other_base,
902 _ => false,
903 }) && self.components() == other.components()
904 }
905}
906
907impl assets::Compound for ItemDef {
908 fn load(cache: assets::AnyCache, specifier: &assets::SharedString) -> Result<Self, BoxedError> {
909 if specifier.starts_with("veloren.core.") {
910 return Err(format!(
911 "Attempted to load an asset from a specifier reserved for core veloren functions. \
912 Specifier: {}",
913 specifier
914 )
915 .into());
916 }
917
918 let RawItemDef {
919 legacy_name,
920 legacy_description,
921 kind,
922 quality,
923 tags,
924 slots,
925 ability_spec,
926 } = cache.load::<RawItemDef>(specifier)?.cloned();
927
928 let item_definition_id = specifier.replace('\\', ".");
933
934 #[expect(deprecated)]
935 Ok(ItemDef {
936 item_definition_id,
937 name: legacy_name,
938 description: legacy_description,
939 kind,
940 quality,
941 tags,
942 slots,
943 ability_spec,
944 })
945 }
946}
947
948#[derive(Clone, Debug, Serialize, Deserialize)]
949#[serde(rename = "ItemDef", deny_unknown_fields)]
950struct RawItemDef {
951 legacy_name: String,
952 legacy_description: String,
953 kind: ItemKind,
954 quality: Quality,
955 tags: Vec<ItemTag>,
956 #[serde(default)]
957 slots: u16,
958 ability_spec: Option<AbilitySpec>,
959}
960
961impl assets::Asset for RawItemDef {
962 type Loader = assets::RonLoader;
963
964 const EXTENSION: &'static str = "ron";
965}
966
967#[derive(Debug)]
968pub struct OperationFailure;
969
970impl Item {
971 pub const MAX_DURABILITY: u32 = 12;
972
973 pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
976
977 pub fn new_from_item_base(
978 inner_item: ItemBase,
979 components: Vec<Item>,
980 ability_map: &AbilityMap,
981 msm: &MaterialStatManifest,
982 ) -> Self {
983 let mut item = Item {
984 item_id: Arc::new(AtomicCell::new(None)),
985 amount: NonZeroU32::new(1).unwrap(),
986 components,
987 slots: vec![None; inner_item.num_slots() as usize],
988 item_base: inner_item,
989 item_config: None,
991 hash: 0,
992 durability_lost: None,
993 };
994 item.durability_lost = item.has_durability().then_some(0);
995 item.update_item_state(ability_map, msm);
996 item
997 }
998
999 pub fn new_from_item_definition_id(
1000 item_definition_id: ItemDefinitionId<'_>,
1001 ability_map: &AbilityMap,
1002 msm: &MaterialStatManifest,
1003 ) -> Result<Self, Error> {
1004 let (base, components) = match item_definition_id {
1005 ItemDefinitionId::Simple(spec) => {
1006 let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(&spec)?);
1007 (base, Vec::new())
1008 },
1009 ItemDefinitionId::Modular {
1010 pseudo_base,
1011 components,
1012 } => {
1013 let base = ItemBase::Modular(ModularBase::load_from_pseudo_id(pseudo_base));
1014 let components = components
1015 .into_iter()
1016 .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1017 .collect::<Result<Vec<_>, _>>()?;
1018 (base, components)
1019 },
1020 ItemDefinitionId::Compound {
1021 simple_base,
1022 components,
1023 } => {
1024 let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(simple_base)?);
1025 let components = components
1026 .into_iter()
1027 .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1028 .collect::<Result<Vec<_>, _>>()?;
1029 (base, components)
1030 },
1031 };
1032 Ok(Item::new_from_item_base(base, components, ability_map, msm))
1033 }
1034
1035 pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
1038 Item::new_from_asset(asset_specifier).unwrap_or_else(|err| {
1039 panic!(
1040 "Expected asset to exist: {}, instead got error {:?}",
1041 asset_specifier, err
1042 );
1043 })
1044 }
1045
1046 pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
1049 let specifier = asset_glob.strip_suffix(".*").unwrap_or(asset_glob);
1050 let defs = assets::load_rec_dir::<RawItemDef>(specifier)?;
1051 defs.read()
1052 .ids()
1053 .map(|id| Item::new_from_asset(id))
1054 .collect()
1055 }
1056
1057 pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
1060 let inner_item = ItemBase::from_item_id_string(asset)?;
1061 let msm = &MaterialStatManifest::load().read();
1063 let ability_map = &AbilityMap::load().read();
1064 Ok(Item::new_from_item_base(
1065 inner_item,
1066 Vec::new(),
1067 ability_map,
1068 msm,
1069 ))
1070 }
1071
1072 #[must_use]
1074 pub fn frontend_item(
1075 &self,
1076 ability_map: &AbilityMap,
1077 msm: &MaterialStatManifest,
1078 ) -> FrontendItem {
1079 FrontendItem(self.duplicate(ability_map, msm))
1080 }
1081
1082 #[must_use]
1084 pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1085 let duplicated_components = self
1086 .components
1087 .iter()
1088 .map(|comp| comp.duplicate(ability_map, msm))
1089 .collect();
1090 let mut new_item = Item::new_from_item_base(
1091 match &self.item_base {
1092 ItemBase::Simple(item_def) => ItemBase::Simple(Arc::clone(item_def)),
1093 ItemBase::Modular(mod_base) => ItemBase::Modular(mod_base.clone()),
1094 },
1095 duplicated_components,
1096 ability_map,
1097 msm,
1098 );
1099 new_item.set_amount(self.amount()).expect(
1100 "`new_item` has the same `item_def` and as an invariant, \
1101 self.set_amount(self.amount()) should always succeed.",
1102 );
1103 new_item.slots_mut().iter_mut().zip(self.slots()).for_each(
1104 |(new_item_slot, old_item_slot)| {
1105 *new_item_slot = old_item_slot
1106 .as_ref()
1107 .map(|old_item| old_item.duplicate(ability_map, msm));
1108 },
1109 );
1110 new_item
1111 }
1112
1113 pub fn stacked_duplicates<'a>(
1114 &'a self,
1115 ability_map: &'a AbilityMap,
1116 msm: &'a MaterialStatManifest,
1117 count: u32,
1118 ) -> impl Iterator<Item = Self> + 'a {
1119 let max_stack_count = count / self.max_amount();
1120 let rest = count % self.max_amount();
1121
1122 (0..max_stack_count)
1123 .map(|_| {
1124 let mut item = self.duplicate(ability_map, msm);
1125
1126 item.set_amount(item.max_amount())
1127 .expect("max_amount() is always a valid amount.");
1128
1129 item
1130 })
1131 .chain((rest > 0).then(move || {
1132 let mut item = self.duplicate(ability_map, msm);
1133
1134 item.set_amount(rest)
1135 .expect("anything less than max_amount() is always a valid amount.");
1136
1137 item
1138 }))
1139 }
1140
1141 #[doc(hidden)]
1149 pub fn get_item_id_for_database(&self) -> Arc<ItemId> { Arc::clone(&self.item_id) }
1150
1151 fn reset_item_id(&mut self) {
1161 if let Some(item_id) = Arc::get_mut(&mut self.item_id) {
1162 *item_id = AtomicCell::new(None);
1163 } else {
1164 self.item_id = Arc::new(AtomicCell::new(None));
1165 }
1166 for component in self.components.iter_mut() {
1168 component.reset_item_id();
1169 }
1170 }
1171
1172 pub fn put_in_world(&mut self) { self.reset_item_id() }
1177
1178 pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), OperationFailure> {
1179 let amount = u32::from(self.amount);
1180 self.amount = amount
1181 .checked_add(increase_by)
1182 .filter(|&amount| amount <= self.max_amount())
1183 .and_then(NonZeroU32::new)
1184 .ok_or(OperationFailure)?;
1185 Ok(())
1186 }
1187
1188 pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), OperationFailure> {
1189 let amount = u32::from(self.amount);
1190 self.amount = amount
1191 .checked_sub(decrease_by)
1192 .and_then(NonZeroU32::new)
1193 .ok_or(OperationFailure)?;
1194 Ok(())
1195 }
1196
1197 pub fn set_amount(&mut self, give_amount: u32) -> Result<(), OperationFailure> {
1198 if give_amount <= self.max_amount() {
1199 self.amount = NonZeroU32::new(give_amount).ok_or(OperationFailure)?;
1200 Ok(())
1201 } else {
1202 Err(OperationFailure)
1203 }
1204 }
1205
1206 pub fn persistence_access_add_component(&mut self, component: Item) {
1207 self.components.push(component);
1208 }
1209
1210 pub fn persistence_access_mutable_component(&mut self, index: usize) -> Option<&mut Self> {
1211 self.components.get_mut(index)
1212 }
1213
1214 pub fn update_item_state(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1218 if let Ok(item_config) = ItemConfig::try_from((&*self, ability_map, msm)) {
1220 self.item_config = Some(Box::new(item_config));
1221 }
1222 self.hash = {
1224 let mut s = DefaultHasher::new();
1225 self.hash(&mut s);
1226 s.finish()
1227 };
1228 }
1229
1230 pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
1232 self.slots.iter_mut().filter_map(mem::take)
1233 }
1234
1235 pub fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1236 match &self.item_base {
1237 ItemBase::Simple(item_def) => {
1238 if self.components.is_empty() {
1239 ItemDefinitionId::Simple(Cow::Borrowed(&item_def.item_definition_id))
1240 } else {
1241 ItemDefinitionId::Compound {
1242 simple_base: &item_def.item_definition_id,
1243 components: self
1244 .components
1245 .iter()
1246 .map(|item| item.item_definition_id())
1247 .collect(),
1248 }
1249 }
1250 },
1251 ItemBase::Modular(mod_base) => ItemDefinitionId::Modular {
1252 pseudo_base: mod_base.pseudo_item_id(),
1253 components: self
1254 .components
1255 .iter()
1256 .map(|item| item.item_definition_id())
1257 .collect(),
1258 },
1259 }
1260 }
1261
1262 pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
1263 if let ItemBase::Simple(self_def) = &self.item_base {
1264 self_def.item_definition_id == item_def.item_definition_id
1265 } else {
1266 false
1267 }
1268 }
1269
1270 pub fn matches_recipe_input(&self, recipe_input: &RecipeInput, amount: u32) -> bool {
1271 match recipe_input {
1272 RecipeInput::Item(item_def) => self.is_same_item_def(item_def),
1273 RecipeInput::Tag(tag) => self.tags().contains(tag),
1274 RecipeInput::TagSameItem(tag) => {
1275 self.tags().contains(tag) && u32::from(self.amount) >= amount
1276 },
1277 RecipeInput::ListSameItem(item_defs) => item_defs.iter().any(|item_def| {
1278 self.is_same_item_def(item_def) && u32::from(self.amount) >= amount
1279 }),
1280 }
1281 }
1282
1283 pub fn is_salvageable(&self) -> bool {
1284 self.tags()
1285 .iter()
1286 .any(|tag| matches!(tag, ItemTag::SalvageInto(_, _)))
1287 }
1288
1289 pub fn salvage_output(&self) -> impl Iterator<Item = (&str, u32)> {
1290 self.tags().into_iter().filter_map(|tag| {
1291 if let ItemTag::SalvageInto(material, quantity) = tag {
1292 material
1293 .asset_identifier()
1294 .map(|material_id| (material_id, quantity))
1295 } else {
1296 None
1297 }
1298 })
1299 }
1300
1301 #[deprecated = "since item i18n"]
1302 pub fn name(&self) -> Cow<str> {
1303 match &self.item_base {
1304 ItemBase::Simple(item_def) => {
1305 if self.components.is_empty() {
1306 #[expect(deprecated)]
1307 Cow::Borrowed(&item_def.name)
1308 } else {
1309 #[expect(deprecated)]
1310 modular::modify_name(&item_def.name, self)
1311 }
1312 },
1313 ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()),
1314 }
1315 }
1316
1317 #[deprecated = "since item i18n"]
1318 pub fn description(&self) -> &str {
1319 match &self.item_base {
1320 #[expect(deprecated)]
1321 ItemBase::Simple(item_def) => &item_def.description,
1322 ItemBase::Modular(_) => "",
1324 }
1325 }
1326
1327 pub fn kind(&self) -> Cow<ItemKind> {
1328 match &self.item_base {
1329 ItemBase::Simple(item_def) => Cow::Borrowed(&item_def.kind),
1330 ItemBase::Modular(mod_base) => {
1331 let msm = MaterialStatManifest::load().read();
1333 mod_base.kind(self.components(), &msm, self.stats_durability_multiplier())
1334 },
1335 }
1336 }
1337
1338 pub fn amount(&self) -> u32 { u32::from(self.amount) }
1339
1340 pub fn is_stackable(&self) -> bool {
1341 match &self.item_base {
1342 ItemBase::Simple(item_def) => item_def.is_stackable(),
1343 ItemBase::Modular(_) => false,
1345 }
1346 }
1347
1348 pub fn max_amount(&self) -> u32 {
1351 match &self.item_base {
1352 ItemBase::Simple(item_def) => item_def.max_amount(),
1353 ItemBase::Modular(_) => {
1354 debug_assert!(!self.is_stackable());
1355 1
1356 },
1357 }
1358 }
1359
1360 pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
1361
1362 pub fn quality(&self) -> Quality {
1363 match &self.item_base {
1364 ItemBase::Simple(item_def) => item_def.quality.max(
1365 self.components
1366 .iter()
1367 .fold(Quality::MIN, |a, b| a.max(b.quality())),
1368 ),
1369 ItemBase::Modular(mod_base) => mod_base.compute_quality(self.components()),
1370 }
1371 }
1372
1373 pub fn components(&self) -> &[Item] { &self.components }
1374
1375 pub fn slots(&self) -> &[InvSlot] { &self.slots }
1376
1377 pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
1378
1379 pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() }
1380
1381 pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }
1382
1383 pub fn populated_slots(&self) -> usize { self.slots().len().saturating_sub(self.free_slots()) }
1384
1385 pub fn slot(&self, slot: usize) -> Option<&InvSlot> { self.slots.get(slot) }
1386
1387 pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
1388
1389 pub fn try_reclaim_from_block(
1390 block: Block,
1391 sprite_cfg: Option<&SpriteCfg>,
1392 ) -> Option<Vec<(u32, Self)>> {
1393 if let Some(loot_spec) = sprite_cfg.and_then(|sprite_cfg| sprite_cfg.loot_table.as_ref()) {
1394 LootSpec::LootTable(loot_spec).to_items()
1395 } else {
1396 block.get_sprite()?.default_loot_spec()??.to_items()
1397 }
1398 }
1399
1400 pub fn ability_spec(&self) -> Option<Cow<AbilitySpec>> {
1401 match &self.item_base {
1402 ItemBase::Simple(item_def) => {
1403 item_def.ability_spec.as_ref().map(Cow::Borrowed).or({
1404 if let ItemKind::Tool(tool) = &item_def.kind {
1407 Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
1408 } else {
1409 None
1410 }
1411 })
1412 },
1413 ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
1414 }
1415 }
1416
1417 pub fn tags(&self) -> Vec<ItemTag> {
1420 match &self.item_base {
1421 ItemBase::Simple(item_def) => item_def.tags.to_vec(),
1422 ItemBase::Modular(mod_base) => mod_base.generate_tags(self.components()),
1424 }
1425 }
1426
1427 pub fn is_modular(&self) -> bool {
1428 match &self.item_base {
1429 ItemBase::Simple(_) => false,
1430 ItemBase::Modular(_) => true,
1431 }
1432 }
1433
1434 pub fn item_hash(&self) -> u64 { self.hash }
1435
1436 pub fn persistence_item_id(&self) -> String {
1437 match &self.item_base {
1438 ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
1439 ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
1440 }
1441 }
1442
1443 pub fn durability_lost(&self) -> Option<u32> {
1444 self.durability_lost.map(|x| x.min(Self::MAX_DURABILITY))
1445 }
1446
1447 pub fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1448 let durability_lost = self.durability_lost.unwrap_or(0);
1449 debug_assert!(durability_lost <= Self::MAX_DURABILITY);
1450 const DURABILITY_THRESHOLD: u32 = 9;
1452 const MIN_FRAC: f32 = 0.25;
1453 let mult = (1.0
1454 - durability_lost.saturating_sub(DURABILITY_THRESHOLD) as f32
1455 / (Self::MAX_DURABILITY - DURABILITY_THRESHOLD) as f32)
1456 * (1.0 - MIN_FRAC)
1457 + MIN_FRAC;
1458 DurabilityMultiplier(mult)
1459 }
1460
1461 pub fn has_durability(&self) -> bool {
1462 self.kind().has_durability() && self.quality() != Quality::Debug
1463 }
1464
1465 pub fn increment_damage(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1466 if let Some(durability_lost) = &mut self.durability_lost {
1467 if *durability_lost < Self::MAX_DURABILITY {
1468 *durability_lost += 1;
1469 }
1470 }
1471 self.update_item_state(ability_map, msm);
1474 }
1475
1476 pub fn persistence_durability(&self) -> Option<NonZeroU32> {
1477 self.durability_lost.and_then(NonZeroU32::new)
1478 }
1479
1480 pub fn persistence_set_durability(&mut self, value: Option<NonZeroU32>) {
1481 if !self.has_durability() {
1484 self.durability_lost = None;
1485 } else {
1486 self.durability_lost = Some(value.map_or(0, NonZeroU32::get));
1489 }
1490 }
1491
1492 pub fn reset_durability(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1493 self.durability_lost = self.has_durability().then_some(0);
1494 self.update_item_state(ability_map, msm);
1497 }
1498
1499 #[must_use = "Returned items will be lost if not used"]
1503 pub fn take_amount(
1504 &mut self,
1505 ability_map: &AbilityMap,
1506 msm: &MaterialStatManifest,
1507 returning_amount: u32,
1508 ) -> Option<Item> {
1509 if self.is_stackable() && self.amount() > 1 && returning_amount < self.amount() {
1510 let mut return_item = self.duplicate(ability_map, msm);
1511 self.decrease_amount(returning_amount).ok()?;
1512 return_item.set_amount(returning_amount).expect(
1513 "return_item.amount() = returning_amount < self.amount() (since self.amount() ≥ \
1514 1) ≤ self.max_amount() = return_item.max_amount(), since return_item is a \
1515 duplicate of item",
1516 );
1517 Some(return_item)
1518 } else {
1519 None
1520 }
1521 }
1522
1523 #[must_use = "Returned items will be lost if not used"]
1527 pub fn take_half(
1528 &mut self,
1529 ability_map: &AbilityMap,
1530 msm: &MaterialStatManifest,
1531 ) -> Option<Item> {
1532 self.take_amount(ability_map, msm, self.amount() / 2)
1533 }
1534
1535 #[cfg(test)]
1536 pub fn create_test_item_from_kind(kind: ItemKind) -> Self {
1537 let ability_map = &AbilityMap::load().read();
1538 let msm = &MaterialStatManifest::load().read();
1539 Self::new_from_item_base(
1540 ItemBase::Simple(Arc::new(ItemDef::create_test_itemdef_from_kind(kind))),
1541 Vec::new(),
1542 ability_map,
1543 msm,
1544 )
1545 }
1546
1547 pub fn can_merge(&self, other: &Self) -> bool {
1552 if self.amount() > self.max_amount() || other.amount() > other.max_amount() {
1553 error!("An item amount is over max_amount!");
1554 return false;
1555 }
1556
1557 (self == other)
1558 && self.slots().iter().all(Option::is_none)
1559 && other.slots().iter().all(Option::is_none)
1560 && self.durability_lost() == other.durability_lost()
1561 }
1562
1563 pub fn try_merge(&mut self, mut other: Self) -> Result<Option<Self>, Self> {
1573 if self.can_merge(&other) {
1574 let max_amount = self.max_amount();
1575 debug_assert_eq!(
1576 max_amount,
1577 other.max_amount(),
1578 "Mergeable items must have the same max_amount()"
1579 );
1580
1581 let to_fill_self = max_amount
1584 .checked_sub(self.amount())
1585 .expect("can_merge should ensure that amount() <= max_amount()");
1586
1587 if let Some(remainder) = other.amount().checked_sub(to_fill_self).filter(|r| *r > 0) {
1588 self.set_amount(max_amount)
1589 .expect("max_amount() is always a valid amount.");
1590 other.set_amount(remainder).expect(
1591 "We know remainder is more than 0 and less than or equal to max_amount()",
1592 );
1593 Ok(Some(other))
1594 } else {
1595 self.increase_amount(other.amount())
1597 .expect("We know that we can at least add other.amount() to this item");
1598 drop(other);
1599 Ok(None)
1600 }
1601 } else {
1602 Err(other)
1603 }
1604 }
1605
1606 pub fn persistence_item_base(&self) -> &ItemBase { &self.item_base }
1609}
1610
1611impl FrontendItem {
1612 #[must_use]
1615 pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1616 FrontendItem(self.0.duplicate(ability_map, msm))
1617 }
1618
1619 pub fn set_amount(&mut self, amount: u32) -> Result<(), OperationFailure> {
1620 self.0.set_amount(amount)
1621 }
1622}
1623
1624impl PickupItem {
1625 pub fn new(item: Item, time: ProgramTime, should_merge: bool) -> Self {
1626 Self {
1627 items: vec![item],
1628 created_at: time,
1629 next_merge_check: time,
1630 should_merge,
1631 }
1632 }
1633
1634 pub fn item(&self) -> &Item {
1638 self.items
1639 .last()
1640 .expect("PickupItem without at least one item is an invariant")
1641 }
1642
1643 pub fn created(&self) -> ProgramTime { self.created_at }
1644
1645 pub fn next_merge_check(&self) -> ProgramTime { self.next_merge_check }
1646
1647 pub fn next_merge_check_mut(&mut self) -> &mut ProgramTime { &mut self.next_merge_check }
1648
1649 pub fn amount(&self) -> u32 {
1651 self.items
1652 .iter()
1653 .map(Item::amount)
1654 .fold(0, |total, amount| total.saturating_add(amount))
1655 }
1656
1657 pub fn remove_debug_items(&mut self) {
1660 for item in self.items.iter_mut() {
1661 item.slots_mut().iter_mut().for_each(|container_slot| {
1662 container_slot
1663 .take_if(|contained_item| matches!(contained_item.quality(), Quality::Debug));
1664 });
1665 }
1666 }
1667
1668 pub fn can_merge(&self, other: &PickupItem) -> bool {
1669 let self_item = self.item();
1670 let other_item = other.item();
1671
1672 self.should_merge && other.should_merge && self_item.can_merge(other_item)
1673 }
1674
1675 pub fn try_merge(&mut self, mut other: PickupItem) -> Result<(), PickupItem> {
1678 if self.can_merge(&other) {
1679 let mut self_last = self
1682 .items
1683 .pop()
1684 .expect("PickupItem without at least one item is an invariant");
1685 let other_last = other
1686 .items
1687 .pop()
1688 .expect("PickupItem without at least one item is an invariant");
1689
1690 let merged = self_last
1692 .try_merge(other_last)
1693 .expect("We know these items can be merged");
1694
1695 debug_assert!(
1696 other
1697 .items
1698 .iter()
1699 .chain(self.items.iter())
1700 .all(|item| item.amount() == item.max_amount()),
1701 "All items before the last in `PickupItem` should have a full amount"
1702 );
1703
1704 self.items.append(&mut other.items);
1707
1708 debug_assert!(
1709 merged.is_none() || self_last.amount() == self_last.max_amount(),
1710 "Merged can only be `Some` if the origin was set to `max_amount()`"
1711 );
1712
1713 self.items.push(self_last);
1715
1716 if let Some(remainder) = merged {
1719 self.items.push(remainder);
1720 }
1721
1722 Ok(())
1723 } else {
1724 Err(other)
1725 }
1726 }
1727
1728 pub fn pick_up(mut self) -> (Item, Option<Self>) {
1729 (
1730 self.items
1731 .pop()
1732 .expect("PickupItem without at least one item is an invariant"),
1733 (!self.items.is_empty()).then_some(self),
1734 )
1735 }
1736}
1737
1738pub fn flatten_counted_items<'a>(
1739 items: &'a [(u32, Item)],
1740 ability_map: &'a AbilityMap,
1741 msm: &'a MaterialStatManifest,
1742) -> impl Iterator<Item = Item> + 'a {
1743 items
1744 .iter()
1745 .flat_map(|(count, item)| item.stacked_duplicates(ability_map, msm, *count))
1746}
1747
1748pub trait ItemDesc {
1751 #[deprecated = "since item i18n"]
1752 fn description(&self) -> &str;
1753 #[deprecated = "since item i18n"]
1754 fn name(&self) -> Cow<str>;
1755 fn kind(&self) -> Cow<ItemKind>;
1756 fn amount(&self) -> NonZeroU32;
1757 fn quality(&self) -> Quality;
1758 fn num_slots(&self) -> u16;
1759 fn item_definition_id(&self) -> ItemDefinitionId<'_>;
1760 fn tags(&self) -> Vec<ItemTag>;
1761 fn is_modular(&self) -> bool;
1762 fn components(&self) -> &[Item];
1763 fn has_durability(&self) -> bool;
1764 fn durability_lost(&self) -> Option<u32>;
1765 fn stats_durability_multiplier(&self) -> DurabilityMultiplier;
1766
1767 fn tool_info(&self) -> Option<ToolKind> {
1768 if let ItemKind::Tool(tool) = &*self.kind() {
1769 Some(tool.kind)
1770 } else {
1771 None
1772 }
1773 }
1774
1775 fn i18n(&self, i18n: &ItemI18n) -> (Content, Content) {
1777 let item_key: ItemKey = self.into();
1778
1779 #[expect(deprecated)]
1780 i18n.item_text_opt(item_key).unwrap_or_else(|| {
1781 (
1782 Content::Plain(self.name().to_string()),
1783 Content::Plain(self.description().to_string()),
1784 )
1785 })
1786 }
1787}
1788
1789impl ItemDesc for Item {
1790 fn description(&self) -> &str {
1791 #[expect(deprecated)]
1792 self.description()
1793 }
1794
1795 fn name(&self) -> Cow<str> {
1796 #[expect(deprecated)]
1797 self.name()
1798 }
1799
1800 fn kind(&self) -> Cow<ItemKind> { self.kind() }
1801
1802 fn amount(&self) -> NonZeroU32 { self.amount }
1803
1804 fn quality(&self) -> Quality { self.quality() }
1805
1806 fn num_slots(&self) -> u16 { self.num_slots() }
1807
1808 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item_definition_id() }
1809
1810 fn tags(&self) -> Vec<ItemTag> { self.tags() }
1811
1812 fn is_modular(&self) -> bool { self.is_modular() }
1813
1814 fn components(&self) -> &[Item] { self.components() }
1815
1816 fn has_durability(&self) -> bool { self.has_durability() }
1817
1818 fn durability_lost(&self) -> Option<u32> { self.durability_lost() }
1819
1820 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1821 self.stats_durability_multiplier()
1822 }
1823}
1824
1825impl ItemDesc for FrontendItem {
1826 fn description(&self) -> &str {
1827 #[expect(deprecated)]
1828 self.0.description()
1829 }
1830
1831 fn name(&self) -> Cow<str> {
1832 #[expect(deprecated)]
1833 self.0.name()
1834 }
1835
1836 fn kind(&self) -> Cow<ItemKind> { self.0.kind() }
1837
1838 fn amount(&self) -> NonZeroU32 { self.0.amount }
1839
1840 fn quality(&self) -> Quality { self.0.quality() }
1841
1842 fn num_slots(&self) -> u16 { self.0.num_slots() }
1843
1844 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.0.item_definition_id() }
1845
1846 fn tags(&self) -> Vec<ItemTag> { self.0.tags() }
1847
1848 fn is_modular(&self) -> bool { self.0.is_modular() }
1849
1850 fn components(&self) -> &[Item] { self.0.components() }
1851
1852 fn has_durability(&self) -> bool { self.0.has_durability() }
1853
1854 fn durability_lost(&self) -> Option<u32> { self.0.durability_lost() }
1855
1856 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1857 self.0.stats_durability_multiplier()
1858 }
1859}
1860
1861impl ItemDesc for ItemDef {
1862 fn description(&self) -> &str {
1863 #[expect(deprecated)]
1864 &self.description
1865 }
1866
1867 fn name(&self) -> Cow<str> {
1868 #[expect(deprecated)]
1869 Cow::Borrowed(&self.name)
1870 }
1871
1872 fn kind(&self) -> Cow<ItemKind> { Cow::Borrowed(&self.kind) }
1873
1874 fn amount(&self) -> NonZeroU32 { NonZeroU32::new(1).unwrap() }
1875
1876 fn quality(&self) -> Quality { self.quality }
1877
1878 fn num_slots(&self) -> u16 { self.slots }
1879
1880 fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1881 ItemDefinitionId::Simple(Cow::Borrowed(&self.item_definition_id))
1882 }
1883
1884 fn tags(&self) -> Vec<ItemTag> { self.tags.to_vec() }
1885
1886 fn is_modular(&self) -> bool { false }
1887
1888 fn components(&self) -> &[Item] { &[] }
1889
1890 fn has_durability(&self) -> bool {
1891 self.kind().has_durability() && self.quality != Quality::Debug
1892 }
1893
1894 fn durability_lost(&self) -> Option<u32> { None }
1895
1896 fn stats_durability_multiplier(&self) -> DurabilityMultiplier { DurabilityMultiplier(1.0) }
1897}
1898
1899impl ItemDesc for PickupItem {
1900 fn description(&self) -> &str {
1901 #[expect(deprecated)]
1902 self.item().description()
1903 }
1904
1905 fn name(&self) -> Cow<str> {
1906 #[expect(deprecated)]
1907 self.item().name()
1908 }
1909
1910 fn kind(&self) -> Cow<ItemKind> { self.item().kind() }
1911
1912 fn amount(&self) -> NonZeroU32 {
1913 NonZeroU32::new(self.amount()).expect("Item having amount of 0 is invariant")
1914 }
1915
1916 fn quality(&self) -> Quality { self.item().quality() }
1917
1918 fn num_slots(&self) -> u16 { self.item().num_slots() }
1919
1920 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item().item_definition_id() }
1921
1922 fn tags(&self) -> Vec<ItemTag> { self.item().tags() }
1923
1924 fn is_modular(&self) -> bool { self.item().is_modular() }
1925
1926 fn components(&self) -> &[Item] { self.item().components() }
1927
1928 fn has_durability(&self) -> bool { self.item().has_durability() }
1929
1930 fn durability_lost(&self) -> Option<u32> { self.item().durability_lost() }
1931
1932 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1933 self.item().stats_durability_multiplier()
1934 }
1935}
1936
1937#[derive(Clone, Debug, Serialize, Deserialize)]
1938pub struct ItemDrops(pub Vec<(u32, Item)>);
1939
1940impl Component for ItemDrops {
1941 type Storage = DenseVecStorage<Self>;
1942}
1943
1944impl Component for PickupItem {
1945 type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1946}
1947
1948impl Component for ThrownItem {
1949 type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1950}
1951
1952#[derive(Copy, Clone, Debug)]
1953pub struct DurabilityMultiplier(pub f32);
1954
1955impl<T: ItemDesc + ?Sized> ItemDesc for &T {
1956 fn description(&self) -> &str {
1957 #[expect(deprecated)]
1958 (*self).description()
1959 }
1960
1961 fn name(&self) -> Cow<str> {
1962 #[expect(deprecated)]
1963 (*self).name()
1964 }
1965
1966 fn kind(&self) -> Cow<ItemKind> { (*self).kind() }
1967
1968 fn amount(&self) -> NonZeroU32 { (*self).amount() }
1969
1970 fn quality(&self) -> Quality { (*self).quality() }
1971
1972 fn num_slots(&self) -> u16 { (*self).num_slots() }
1973
1974 fn item_definition_id(&self) -> ItemDefinitionId<'_> { (*self).item_definition_id() }
1975
1976 fn tags(&self) -> Vec<ItemTag> { (*self).tags() }
1977
1978 fn is_modular(&self) -> bool { (*self).is_modular() }
1979
1980 fn components(&self) -> &[Item] { (*self).components() }
1981
1982 fn has_durability(&self) -> bool { (*self).has_durability() }
1983
1984 fn durability_lost(&self) -> Option<u32> { (*self).durability_lost() }
1985
1986 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1987 (*self).stats_durability_multiplier()
1988 }
1989}
1990
1991pub fn all_item_defs_expect() -> Vec<String> {
1995 try_all_item_defs().expect("Failed to access items directory")
1996}
1997
1998pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
2000 let defs = assets::load_rec_dir::<RawItemDef>("common.items")?;
2001 Ok(defs.read().ids().map(|id| id.to_string()).collect())
2002}
2003
2004pub fn all_items_expect() -> Vec<Item> {
2007 let defs = assets::load_rec_dir::<RawItemDef>("common.items")
2008 .expect("failed to load item asset directory");
2009
2010 let mut asset_items: Vec<Item> = defs
2012 .read()
2013 .ids()
2014 .map(|id| Item::new_from_asset_expect(id))
2015 .collect();
2016
2017 let mut material_parse_table = HashMap::new();
2018 for mat in Material::iter() {
2019 if let Some(id) = mat.asset_identifier() {
2020 material_parse_table.insert(id.to_owned(), mat);
2021 }
2022 }
2023
2024 let primary_comp_pool = modular::PRIMARY_COMPONENT_POOL.clone();
2025
2026 let mut primary_comps: Vec<Item> = primary_comp_pool
2028 .values()
2029 .flatten()
2030 .map(|(item, _hand_rules)| item.clone())
2031 .collect();
2032
2033 let mut modular_items: Vec<Item> = primary_comp_pool
2035 .keys()
2036 .flat_map(|(tool, mat_id)| {
2037 let mat = material_parse_table
2038 .get(mat_id)
2039 .expect("unexpected material ident");
2040
2041 modular::generate_weapons(*tool, *mat, None)
2043 .expect("failure during modular weapon generation")
2044 })
2045 .collect();
2046
2047 let mut all = Vec::new();
2056 all.append(&mut asset_items);
2057 all.append(&mut primary_comps);
2058 all.append(&mut modular_items);
2059
2060 all
2061}
2062
2063impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2064 fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
2065 use ItemDefinitionId as DefId;
2066 match self {
2067 Self::Simple(simple) => {
2068 matches!(other, DefId::Simple(other_simple) if simple == other_simple)
2069 },
2070 Self::Modular {
2071 pseudo_base,
2072 components,
2073 } => matches!(
2074 other,
2075 DefId::Modular { pseudo_base: other_base, components: other_comps }
2076 if pseudo_base == other_base && components == other_comps
2077 ),
2078 Self::Compound {
2079 simple_base,
2080 components,
2081 } => matches!(
2082 other,
2083 DefId::Compound { simple_base: other_base, components: other_comps }
2084 if simple_base == other_base && components == other_comps
2085 ),
2086 }
2087 }
2088}
2089
2090impl PartialEq<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2091 #[inline]
2092 fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self }
2093}
2094
2095impl Equivalent<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2096 fn equivalent(&self, key: &ItemDefinitionIdOwned) -> bool { self == key }
2097}
2098
2099impl From<&ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2100 fn from(value: &ItemDefinitionId<'_>) -> Self { value.to_owned() }
2101}
2102
2103#[cfg(test)]
2104mod tests {
2105 use super::*;
2106
2107 #[test]
2108 fn test_assets_items() {
2109 let ids = all_item_defs_expect();
2110 for item in ids.iter().map(|id| Item::new_from_asset_expect(id)) {
2111 if let ItemKind::Consumable {
2112 container: Some(container),
2113 ..
2114 } = item.kind().as_ref()
2115 {
2116 Item::new_from_item_definition_id(
2117 container.as_ref(),
2118 &AbilityMap::load().read(),
2119 &MaterialStatManifest::load().read(),
2120 )
2121 .unwrap();
2122 }
2123 drop(item)
2124 }
2125 }
2126
2127 #[test]
2128 fn test_item_i18n() { let _ = ItemI18n::new_expect(); }
2129
2130 #[test]
2131 fn test_all_items() { let _ = all_items_expect(); }
2133
2134 #[test]
2135 fn ensure_item_localization() {
2138 let manifest = ItemI18n::new_expect();
2139 let items = all_items_expect();
2140 let mut errs = vec![];
2141 for item in items {
2142 let item_key: ItemKey = (&item).into();
2143 if manifest.item_text_opt(item_key.clone()).is_none() {
2144 errs.push(item_key)
2145 }
2146 }
2147 if !errs.is_empty() {
2148 panic!("item i18n manifest misses translation-id for following items {errs:#?}")
2149 }
2150 }
2151}