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