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 },
355 Utility {
356 kind: Utility,
357 },
358 Ingredient {
359 #[deprecated = "part of non-localized name generation"]
362 descriptor: String,
363 },
364 TagExamples {
365 item_ids: Vec<String>,
368 },
369 RecipeGroup {
370 recipes: Vec<String>,
371 },
372}
373
374#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
375pub enum ConsumableKind {
376 Drink,
377 Food,
378 ComplexFood,
379 Charm,
380 Recipe,
381}
382
383impl ItemKind {
384 pub fn is_equippable(&self) -> bool {
385 matches!(
386 self,
387 ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Glider | ItemKind::Lantern(_)
388 )
389 }
390
391 pub fn get_itemkind_string(&self) -> String {
394 match self {
395 ItemKind::Tool(tool) => format!("Tool: {:?}", tool.kind),
397 ItemKind::ModularComponent(modular_component) => {
398 format!("ModularComponent: {:?}", modular_component.toolkind())
399 },
400 ItemKind::Lantern(lantern) => format!("Lantern: {:?}", lantern),
401 ItemKind::Armor(armor) => format!("Armor: {:?}", armor.stats),
402 ItemKind::Glider => "Glider:".to_string(),
403 ItemKind::Consumable { kind, .. } => {
404 format!("Consumable: {:?}", kind)
405 },
406 ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
407 #[expect(deprecated)]
408 ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
409 ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids),
410 ItemKind::RecipeGroup { .. } => String::from("Recipes:"),
411 }
412 }
413
414 pub fn has_durability(&self) -> bool {
415 match self {
416 ItemKind::Tool(Tool { kind, .. }) => !matches!(kind, ToolKind::Throwable),
417 ItemKind::Armor(armor) => armor.kind.has_durability(),
418 ItemKind::ModularComponent(_)
419 | ItemKind::Lantern(_)
420 | ItemKind::Glider
421 | ItemKind::Consumable { .. }
422 | ItemKind::Utility { .. }
423 | ItemKind::Ingredient { .. }
424 | ItemKind::TagExamples { .. }
425 | ItemKind::RecipeGroup { .. } => false,
426 }
427 }
428}
429
430pub type ItemId = AtomicCell<Option<NonZeroU64>>;
431
432#[derive(Clone, Debug, Serialize, Deserialize)]
446pub struct Item {
447 #[serde(skip)]
454 item_id: Arc<ItemId>,
455 item_base: ItemBase,
459 components: Vec<Item>,
468 amount: NonZeroU32,
471 slots: Vec<InvSlot>,
473 item_config: Option<Box<ItemConfig>>,
474 hash: u64,
475 durability_lost: Option<u32>,
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
484pub struct FrontendItem(Item);
485
486#[derive(Debug, Clone, Serialize, Deserialize)]
499pub struct PickupItem {
500 items: Vec<Item>,
501 created_at: ProgramTime,
503 next_merge_check: ProgramTime,
505 pub should_merge: bool,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
514pub struct ThrownItem(pub Item);
515
516use std::hash::{Hash, Hasher};
517
518impl Hash for Item {
520 fn hash<H: Hasher>(&self, state: &mut H) {
521 self.item_definition_id().hash(state);
522 self.components.iter().for_each(|comp| comp.hash(state));
523 }
524}
525
526type I18nId = String;
529
530#[derive(Clone, Debug, Serialize, Deserialize)]
531pub struct ItemI18n {
547 map: HashMap<ItemKey, I18nId>,
549}
550
551impl assets::Asset for ItemI18n {
552 type Loader = assets::RonLoader;
553
554 const EXTENSION: &'static str = "ron";
555}
556
557impl ItemI18n {
558 pub fn new_expect() -> Self {
559 ItemI18n::load_expect("common.item_i18n_manifest")
560 .read()
561 .clone()
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 assets::Compound for ItemDef {
906 fn load(cache: assets::AnyCache, specifier: &assets::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::<RawItemDef>(specifier)?.cloned();
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
959impl assets::Asset for RawItemDef {
960 type Loader = assets::RonLoader;
961
962 const EXTENSION: &'static str = "ron";
963}
964
965#[derive(Debug)]
966pub struct OperationFailure;
967
968impl Item {
969 pub const MAX_DURABILITY: u32 = 12;
970
971 pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
974
975 pub fn new_from_item_base(
976 inner_item: ItemBase,
977 components: Vec<Item>,
978 ability_map: &AbilityMap,
979 msm: &MaterialStatManifest,
980 ) -> Self {
981 let mut item = Item {
982 item_id: Arc::new(AtomicCell::new(None)),
983 amount: NonZeroU32::new(1).unwrap(),
984 components,
985 slots: vec![None; inner_item.num_slots() as usize],
986 item_base: inner_item,
987 item_config: None,
989 hash: 0,
990 durability_lost: None,
991 };
992 item.durability_lost = item.has_durability().then_some(0);
993 item.update_item_state(ability_map, msm);
994 item
995 }
996
997 pub fn new_from_item_definition_id(
998 item_definition_id: ItemDefinitionId<'_>,
999 ability_map: &AbilityMap,
1000 msm: &MaterialStatManifest,
1001 ) -> Result<Self, Error> {
1002 let (base, components) = match item_definition_id {
1003 ItemDefinitionId::Simple(spec) => {
1004 let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(&spec)?);
1005 (base, Vec::new())
1006 },
1007 ItemDefinitionId::Modular {
1008 pseudo_base,
1009 components,
1010 } => {
1011 let base = ItemBase::Modular(ModularBase::load_from_pseudo_id(pseudo_base));
1012 let components = components
1013 .into_iter()
1014 .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1015 .collect::<Result<Vec<_>, _>>()?;
1016 (base, components)
1017 },
1018 ItemDefinitionId::Compound {
1019 simple_base,
1020 components,
1021 } => {
1022 let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(simple_base)?);
1023 let components = components
1024 .into_iter()
1025 .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1026 .collect::<Result<Vec<_>, _>>()?;
1027 (base, components)
1028 },
1029 };
1030 Ok(Item::new_from_item_base(base, components, ability_map, msm))
1031 }
1032
1033 pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
1036 Item::new_from_asset(asset_specifier).unwrap_or_else(|err| {
1037 panic!(
1038 "Expected asset to exist: {}, instead got error {:?}",
1039 asset_specifier, err
1040 );
1041 })
1042 }
1043
1044 pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
1047 let specifier = asset_glob.strip_suffix(".*").unwrap_or(asset_glob);
1048 let defs = assets::load_rec_dir::<RawItemDef>(specifier)?;
1049 defs.read()
1050 .ids()
1051 .map(|id| Item::new_from_asset(id))
1052 .collect()
1053 }
1054
1055 pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
1058 let inner_item = ItemBase::from_item_id_string(asset)?;
1059 let msm = &MaterialStatManifest::load().read();
1061 let ability_map = &AbilityMap::load().read();
1062 Ok(Item::new_from_item_base(
1063 inner_item,
1064 Vec::new(),
1065 ability_map,
1066 msm,
1067 ))
1068 }
1069
1070 #[must_use]
1072 pub fn frontend_item(
1073 &self,
1074 ability_map: &AbilityMap,
1075 msm: &MaterialStatManifest,
1076 ) -> FrontendItem {
1077 FrontendItem(self.duplicate(ability_map, msm))
1078 }
1079
1080 #[must_use]
1082 pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1083 let duplicated_components = self
1084 .components
1085 .iter()
1086 .map(|comp| comp.duplicate(ability_map, msm))
1087 .collect();
1088 let mut new_item = Item::new_from_item_base(
1089 match &self.item_base {
1090 ItemBase::Simple(item_def) => ItemBase::Simple(Arc::clone(item_def)),
1091 ItemBase::Modular(mod_base) => ItemBase::Modular(mod_base.clone()),
1092 },
1093 duplicated_components,
1094 ability_map,
1095 msm,
1096 );
1097 new_item.set_amount(self.amount()).expect(
1098 "`new_item` has the same `item_def` and as an invariant, \
1099 self.set_amount(self.amount()) should always succeed.",
1100 );
1101 new_item.slots_mut().iter_mut().zip(self.slots()).for_each(
1102 |(new_item_slot, old_item_slot)| {
1103 *new_item_slot = old_item_slot
1104 .as_ref()
1105 .map(|old_item| old_item.duplicate(ability_map, msm));
1106 },
1107 );
1108 new_item
1109 }
1110
1111 pub fn stacked_duplicates<'a>(
1112 &'a self,
1113 ability_map: &'a AbilityMap,
1114 msm: &'a MaterialStatManifest,
1115 count: u32,
1116 ) -> impl Iterator<Item = Self> + 'a {
1117 let max_stack_count = count / self.max_amount();
1118 let rest = count % self.max_amount();
1119
1120 (0..max_stack_count)
1121 .map(|_| {
1122 let mut item = self.duplicate(ability_map, msm);
1123
1124 item.set_amount(item.max_amount())
1125 .expect("max_amount() is always a valid amount.");
1126
1127 item
1128 })
1129 .chain((rest > 0).then(move || {
1130 let mut item = self.duplicate(ability_map, msm);
1131
1132 item.set_amount(rest)
1133 .expect("anything less than max_amount() is always a valid amount.");
1134
1135 item
1136 }))
1137 }
1138
1139 #[doc(hidden)]
1147 pub fn get_item_id_for_database(&self) -> Arc<ItemId> { Arc::clone(&self.item_id) }
1148
1149 fn reset_item_id(&mut self) {
1159 if let Some(item_id) = Arc::get_mut(&mut self.item_id) {
1160 *item_id = AtomicCell::new(None);
1161 } else {
1162 self.item_id = Arc::new(AtomicCell::new(None));
1163 }
1164 for component in self.components.iter_mut() {
1166 component.reset_item_id();
1167 }
1168 }
1169
1170 pub fn put_in_world(&mut self) { self.reset_item_id() }
1175
1176 pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), OperationFailure> {
1177 let amount = u32::from(self.amount);
1178 self.amount = amount
1179 .checked_add(increase_by)
1180 .filter(|&amount| amount <= self.max_amount())
1181 .and_then(NonZeroU32::new)
1182 .ok_or(OperationFailure)?;
1183 Ok(())
1184 }
1185
1186 pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), OperationFailure> {
1187 let amount = u32::from(self.amount);
1188 self.amount = amount
1189 .checked_sub(decrease_by)
1190 .and_then(NonZeroU32::new)
1191 .ok_or(OperationFailure)?;
1192 Ok(())
1193 }
1194
1195 pub fn set_amount(&mut self, give_amount: u32) -> Result<(), OperationFailure> {
1196 if give_amount <= self.max_amount() {
1197 self.amount = NonZeroU32::new(give_amount).ok_or(OperationFailure)?;
1198 Ok(())
1199 } else {
1200 Err(OperationFailure)
1201 }
1202 }
1203
1204 pub fn persistence_access_add_component(&mut self, component: Item) {
1205 self.components.push(component);
1206 }
1207
1208 pub fn persistence_access_mutable_component(&mut self, index: usize) -> Option<&mut Self> {
1209 self.components.get_mut(index)
1210 }
1211
1212 pub fn update_item_state(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1216 if let Ok(item_config) = ItemConfig::try_from((&*self, ability_map, msm)) {
1218 self.item_config = Some(Box::new(item_config));
1219 }
1220 self.hash = {
1222 let mut s = DefaultHasher::new();
1223 self.hash(&mut s);
1224 s.finish()
1225 };
1226 }
1227
1228 pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
1230 self.slots.iter_mut().filter_map(mem::take)
1231 }
1232
1233 pub fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1234 match &self.item_base {
1235 ItemBase::Simple(item_def) => {
1236 if self.components.is_empty() {
1237 ItemDefinitionId::Simple(Cow::Borrowed(&item_def.item_definition_id))
1238 } else {
1239 ItemDefinitionId::Compound {
1240 simple_base: &item_def.item_definition_id,
1241 components: self
1242 .components
1243 .iter()
1244 .map(|item| item.item_definition_id())
1245 .collect(),
1246 }
1247 }
1248 },
1249 ItemBase::Modular(mod_base) => ItemDefinitionId::Modular {
1250 pseudo_base: mod_base.pseudo_item_id(),
1251 components: self
1252 .components
1253 .iter()
1254 .map(|item| item.item_definition_id())
1255 .collect(),
1256 },
1257 }
1258 }
1259
1260 pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
1261 if let ItemBase::Simple(self_def) = &self.item_base {
1262 self_def.item_definition_id == item_def.item_definition_id
1263 } else {
1264 false
1265 }
1266 }
1267
1268 pub fn matches_recipe_input(&self, recipe_input: &RecipeInput, amount: u32) -> bool {
1269 match recipe_input {
1270 RecipeInput::Item(item_def) => self.is_same_item_def(item_def),
1271 RecipeInput::Tag(tag) => self.tags().contains(tag),
1272 RecipeInput::TagSameItem(tag) => {
1273 self.tags().contains(tag) && u32::from(self.amount) >= amount
1274 },
1275 RecipeInput::ListSameItem(item_defs) => item_defs.iter().any(|item_def| {
1276 self.is_same_item_def(item_def) && u32::from(self.amount) >= amount
1277 }),
1278 }
1279 }
1280
1281 pub fn is_salvageable(&self) -> bool {
1282 self.tags()
1283 .iter()
1284 .any(|tag| matches!(tag, ItemTag::SalvageInto(_, _)))
1285 }
1286
1287 pub fn salvage_output(&self) -> impl Iterator<Item = (&str, u32)> {
1288 self.tags().into_iter().filter_map(|tag| {
1289 if let ItemTag::SalvageInto(material, quantity) = tag {
1290 material
1291 .asset_identifier()
1292 .map(|material_id| (material_id, quantity))
1293 } else {
1294 None
1295 }
1296 })
1297 }
1298
1299 #[deprecated = "since item i18n"]
1300 pub fn name(&self) -> Cow<str> {
1301 match &self.item_base {
1302 ItemBase::Simple(item_def) => {
1303 if self.components.is_empty() {
1304 #[expect(deprecated)]
1305 Cow::Borrowed(&item_def.name)
1306 } else {
1307 #[expect(deprecated)]
1308 modular::modify_name(&item_def.name, self)
1309 }
1310 },
1311 ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()),
1312 }
1313 }
1314
1315 #[deprecated = "since item i18n"]
1316 pub fn description(&self) -> &str {
1317 match &self.item_base {
1318 #[expect(deprecated)]
1319 ItemBase::Simple(item_def) => &item_def.description,
1320 ItemBase::Modular(_) => "",
1322 }
1323 }
1324
1325 pub fn kind(&self) -> Cow<ItemKind> {
1326 match &self.item_base {
1327 ItemBase::Simple(item_def) => Cow::Borrowed(&item_def.kind),
1328 ItemBase::Modular(mod_base) => {
1329 let msm = MaterialStatManifest::load().read();
1331 mod_base.kind(self.components(), &msm, self.stats_durability_multiplier())
1332 },
1333 }
1334 }
1335
1336 pub fn amount(&self) -> u32 { u32::from(self.amount) }
1337
1338 pub fn is_stackable(&self) -> bool {
1339 match &self.item_base {
1340 ItemBase::Simple(item_def) => item_def.is_stackable(),
1341 ItemBase::Modular(_) => false,
1343 }
1344 }
1345
1346 pub fn max_amount(&self) -> u32 {
1349 match &self.item_base {
1350 ItemBase::Simple(item_def) => item_def.max_amount(),
1351 ItemBase::Modular(_) => {
1352 debug_assert!(!self.is_stackable());
1353 1
1354 },
1355 }
1356 }
1357
1358 pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
1359
1360 pub fn quality(&self) -> Quality {
1361 match &self.item_base {
1362 ItemBase::Simple(item_def) => item_def.quality.max(
1363 self.components
1364 .iter()
1365 .fold(Quality::MIN, |a, b| a.max(b.quality())),
1366 ),
1367 ItemBase::Modular(mod_base) => mod_base.compute_quality(self.components()),
1368 }
1369 }
1370
1371 pub fn components(&self) -> &[Item] { &self.components }
1372
1373 pub fn slots(&self) -> &[InvSlot] { &self.slots }
1374
1375 pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
1376
1377 pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() }
1378
1379 pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }
1380
1381 pub fn populated_slots(&self) -> usize { self.slots().len().saturating_sub(self.free_slots()) }
1382
1383 pub fn slot(&self, slot: usize) -> Option<&InvSlot> { self.slots.get(slot) }
1384
1385 pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
1386
1387 pub fn try_reclaim_from_block(
1388 block: Block,
1389 sprite_cfg: Option<&SpriteCfg>,
1390 ) -> Option<Vec<(u32, Self)>> {
1391 if let Some(loot_spec) = sprite_cfg.and_then(|sprite_cfg| sprite_cfg.loot_table.as_ref()) {
1392 LootSpec::LootTable(loot_spec).to_items()
1393 } else {
1394 block.get_sprite()?.default_loot_spec()??.to_items()
1395 }
1396 }
1397
1398 pub fn ability_spec(&self) -> Option<Cow<AbilitySpec>> {
1399 match &self.item_base {
1400 ItemBase::Simple(item_def) => {
1401 item_def.ability_spec.as_ref().map(Cow::Borrowed).or({
1402 if let ItemKind::Tool(tool) = &item_def.kind {
1405 Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
1406 } else {
1407 None
1408 }
1409 })
1410 },
1411 ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
1412 }
1413 }
1414
1415 pub fn tags(&self) -> Vec<ItemTag> {
1418 match &self.item_base {
1419 ItemBase::Simple(item_def) => item_def.tags.to_vec(),
1420 ItemBase::Modular(mod_base) => mod_base.generate_tags(self.components()),
1422 }
1423 }
1424
1425 pub fn is_modular(&self) -> bool {
1426 match &self.item_base {
1427 ItemBase::Simple(_) => false,
1428 ItemBase::Modular(_) => true,
1429 }
1430 }
1431
1432 pub fn item_hash(&self) -> u64 { self.hash }
1433
1434 pub fn persistence_item_id(&self) -> String {
1435 match &self.item_base {
1436 ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
1437 ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
1438 }
1439 }
1440
1441 pub fn durability_lost(&self) -> Option<u32> {
1442 self.durability_lost.map(|x| x.min(Self::MAX_DURABILITY))
1443 }
1444
1445 pub fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1446 let durability_lost = self.durability_lost.unwrap_or(0);
1447 debug_assert!(durability_lost <= Self::MAX_DURABILITY);
1448 const DURABILITY_THRESHOLD: u32 = 9;
1450 const MIN_FRAC: f32 = 0.25;
1451 let mult = (1.0
1452 - durability_lost.saturating_sub(DURABILITY_THRESHOLD) as f32
1453 / (Self::MAX_DURABILITY - DURABILITY_THRESHOLD) as f32)
1454 * (1.0 - MIN_FRAC)
1455 + MIN_FRAC;
1456 DurabilityMultiplier(mult)
1457 }
1458
1459 pub fn has_durability(&self) -> bool {
1460 self.kind().has_durability() && self.quality() != Quality::Debug
1461 }
1462
1463 pub fn increment_damage(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1464 if let Some(durability_lost) = &mut self.durability_lost {
1465 if *durability_lost < Self::MAX_DURABILITY {
1466 *durability_lost += 1;
1467 }
1468 }
1469 self.update_item_state(ability_map, msm);
1472 }
1473
1474 pub fn persistence_durability(&self) -> Option<NonZeroU32> {
1475 self.durability_lost.and_then(NonZeroU32::new)
1476 }
1477
1478 pub fn persistence_set_durability(&mut self, value: Option<NonZeroU32>) {
1479 if !self.has_durability() {
1482 self.durability_lost = None;
1483 } else {
1484 self.durability_lost = Some(value.map_or(0, NonZeroU32::get));
1487 }
1488 }
1489
1490 pub fn reset_durability(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1491 self.durability_lost = self.has_durability().then_some(0);
1492 self.update_item_state(ability_map, msm);
1495 }
1496
1497 #[must_use = "Returned items will be lost if not used"]
1501 pub fn take_amount(
1502 &mut self,
1503 ability_map: &AbilityMap,
1504 msm: &MaterialStatManifest,
1505 returning_amount: u32,
1506 ) -> Option<Item> {
1507 if self.is_stackable() && self.amount() > 1 && returning_amount < self.amount() {
1508 let mut return_item = self.duplicate(ability_map, msm);
1509 self.decrease_amount(returning_amount).ok()?;
1510 return_item.set_amount(returning_amount).expect(
1511 "return_item.amount() = returning_amount < self.amount() (since self.amount() ≥ \
1512 1) ≤ self.max_amount() = return_item.max_amount(), since return_item is a \
1513 duplicate of item",
1514 );
1515 Some(return_item)
1516 } else {
1517 None
1518 }
1519 }
1520
1521 #[must_use = "Returned items will be lost if not used"]
1525 pub fn take_half(
1526 &mut self,
1527 ability_map: &AbilityMap,
1528 msm: &MaterialStatManifest,
1529 ) -> Option<Item> {
1530 self.take_amount(ability_map, msm, self.amount() / 2)
1531 }
1532
1533 #[cfg(test)]
1534 pub fn create_test_item_from_kind(kind: ItemKind) -> Self {
1535 let ability_map = &AbilityMap::load().read();
1536 let msm = &MaterialStatManifest::load().read();
1537 Self::new_from_item_base(
1538 ItemBase::Simple(Arc::new(ItemDef::create_test_itemdef_from_kind(kind))),
1539 Vec::new(),
1540 ability_map,
1541 msm,
1542 )
1543 }
1544
1545 pub fn can_merge(&self, other: &Self) -> bool {
1550 if self.amount() > self.max_amount() || other.amount() > other.max_amount() {
1551 error!("An item amount is over max_amount!");
1552 return false;
1553 }
1554
1555 (self == other)
1556 && self.slots().iter().all(Option::is_none)
1557 && other.slots().iter().all(Option::is_none)
1558 && self.durability_lost() == other.durability_lost()
1559 }
1560
1561 pub fn try_merge(&mut self, mut other: Self) -> Result<Option<Self>, Self> {
1571 if self.can_merge(&other) {
1572 let max_amount = self.max_amount();
1573 debug_assert_eq!(
1574 max_amount,
1575 other.max_amount(),
1576 "Mergeable items must have the same max_amount()"
1577 );
1578
1579 let to_fill_self = max_amount
1582 .checked_sub(self.amount())
1583 .expect("can_merge should ensure that amount() <= max_amount()");
1584
1585 if let Some(remainder) = other.amount().checked_sub(to_fill_self).filter(|r| *r > 0) {
1586 self.set_amount(max_amount)
1587 .expect("max_amount() is always a valid amount.");
1588 other.set_amount(remainder).expect(
1589 "We know remainder is more than 0 and less than or equal to max_amount()",
1590 );
1591 Ok(Some(other))
1592 } else {
1593 self.increase_amount(other.amount())
1595 .expect("We know that we can at least add other.amount() to this item");
1596 drop(other);
1597 Ok(None)
1598 }
1599 } else {
1600 Err(other)
1601 }
1602 }
1603
1604 pub fn persistence_item_base(&self) -> &ItemBase { &self.item_base }
1607}
1608
1609impl FrontendItem {
1610 #[must_use]
1613 pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1614 FrontendItem(self.0.duplicate(ability_map, msm))
1615 }
1616
1617 pub fn set_amount(&mut self, amount: u32) -> Result<(), OperationFailure> {
1618 self.0.set_amount(amount)
1619 }
1620}
1621
1622impl PickupItem {
1623 pub fn new(item: Item, time: ProgramTime, should_merge: bool) -> Self {
1624 Self {
1625 items: vec![item],
1626 created_at: time,
1627 next_merge_check: time,
1628 should_merge,
1629 }
1630 }
1631
1632 pub fn item(&self) -> &Item {
1636 self.items
1637 .last()
1638 .expect("PickupItem without at least one item is an invariant")
1639 }
1640
1641 pub fn created(&self) -> ProgramTime { self.created_at }
1642
1643 pub fn next_merge_check(&self) -> ProgramTime { self.next_merge_check }
1644
1645 pub fn next_merge_check_mut(&mut self) -> &mut ProgramTime { &mut self.next_merge_check }
1646
1647 pub fn amount(&self) -> u32 {
1649 self.items
1650 .iter()
1651 .map(Item::amount)
1652 .fold(0, |total, amount| total.saturating_add(amount))
1653 }
1654
1655 pub fn remove_debug_items(&mut self) {
1658 for item in self.items.iter_mut() {
1659 item.slots_mut().iter_mut().for_each(|container_slot| {
1660 container_slot
1661 .take_if(|contained_item| matches!(contained_item.quality(), Quality::Debug));
1662 });
1663 }
1664 }
1665
1666 pub fn can_merge(&self, other: &PickupItem) -> bool {
1667 let self_item = self.item();
1668 let other_item = other.item();
1669
1670 self.should_merge && other.should_merge && self_item.can_merge(other_item)
1671 }
1672
1673 pub fn try_merge(&mut self, mut other: PickupItem) -> Result<(), PickupItem> {
1676 if self.can_merge(&other) {
1677 let mut self_last = self
1680 .items
1681 .pop()
1682 .expect("PickupItem without at least one item is an invariant");
1683 let other_last = other
1684 .items
1685 .pop()
1686 .expect("PickupItem without at least one item is an invariant");
1687
1688 let merged = self_last
1690 .try_merge(other_last)
1691 .expect("We know these items can be merged");
1692
1693 debug_assert!(
1694 other
1695 .items
1696 .iter()
1697 .chain(self.items.iter())
1698 .all(|item| item.amount() == item.max_amount()),
1699 "All items before the last in `PickupItem` should have a full amount"
1700 );
1701
1702 self.items.append(&mut other.items);
1705
1706 debug_assert!(
1707 merged.is_none() || self_last.amount() == self_last.max_amount(),
1708 "Merged can only be `Some` if the origin was set to `max_amount()`"
1709 );
1710
1711 self.items.push(self_last);
1713
1714 if let Some(remainder) = merged {
1717 self.items.push(remainder);
1718 }
1719
1720 Ok(())
1721 } else {
1722 Err(other)
1723 }
1724 }
1725
1726 pub fn pick_up(mut self) -> (Item, Option<Self>) {
1727 (
1728 self.items
1729 .pop()
1730 .expect("PickupItem without at least one item is an invariant"),
1731 (!self.items.is_empty()).then_some(self),
1732 )
1733 }
1734}
1735
1736pub fn flatten_counted_items<'a>(
1737 items: &'a [(u32, Item)],
1738 ability_map: &'a AbilityMap,
1739 msm: &'a MaterialStatManifest,
1740) -> impl Iterator<Item = Item> + 'a {
1741 items
1742 .iter()
1743 .flat_map(|(count, item)| item.stacked_duplicates(ability_map, msm, *count))
1744}
1745
1746pub trait ItemDesc {
1749 #[deprecated = "since item i18n"]
1750 fn description(&self) -> &str;
1751 #[deprecated = "since item i18n"]
1752 fn name(&self) -> Cow<str>;
1753 fn kind(&self) -> Cow<ItemKind>;
1754 fn amount(&self) -> NonZeroU32;
1755 fn quality(&self) -> Quality;
1756 fn num_slots(&self) -> u16;
1757 fn item_definition_id(&self) -> ItemDefinitionId<'_>;
1758 fn tags(&self) -> Vec<ItemTag>;
1759 fn is_modular(&self) -> bool;
1760 fn components(&self) -> &[Item];
1761 fn has_durability(&self) -> bool;
1762 fn durability_lost(&self) -> Option<u32>;
1763 fn stats_durability_multiplier(&self) -> DurabilityMultiplier;
1764
1765 fn tool_info(&self) -> Option<ToolKind> {
1766 if let ItemKind::Tool(tool) = &*self.kind() {
1767 Some(tool.kind)
1768 } else {
1769 None
1770 }
1771 }
1772
1773 fn i18n(&self, i18n: &ItemI18n) -> (Content, Content) {
1775 let item_key: ItemKey = self.into();
1776
1777 #[expect(deprecated)]
1778 i18n.item_text_opt(item_key).unwrap_or_else(|| {
1779 (
1780 Content::Plain(self.name().to_string()),
1781 Content::Plain(self.description().to_string()),
1782 )
1783 })
1784 }
1785}
1786
1787impl ItemDesc for Item {
1788 fn description(&self) -> &str {
1789 #[expect(deprecated)]
1790 self.description()
1791 }
1792
1793 fn name(&self) -> Cow<str> {
1794 #[expect(deprecated)]
1795 self.name()
1796 }
1797
1798 fn kind(&self) -> Cow<ItemKind> { self.kind() }
1799
1800 fn amount(&self) -> NonZeroU32 { self.amount }
1801
1802 fn quality(&self) -> Quality { self.quality() }
1803
1804 fn num_slots(&self) -> u16 { self.num_slots() }
1805
1806 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item_definition_id() }
1807
1808 fn tags(&self) -> Vec<ItemTag> { self.tags() }
1809
1810 fn is_modular(&self) -> bool { self.is_modular() }
1811
1812 fn components(&self) -> &[Item] { self.components() }
1813
1814 fn has_durability(&self) -> bool { self.has_durability() }
1815
1816 fn durability_lost(&self) -> Option<u32> { self.durability_lost() }
1817
1818 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1819 self.stats_durability_multiplier()
1820 }
1821}
1822
1823impl ItemDesc for FrontendItem {
1824 fn description(&self) -> &str {
1825 #[expect(deprecated)]
1826 self.0.description()
1827 }
1828
1829 fn name(&self) -> Cow<str> {
1830 #[expect(deprecated)]
1831 self.0.name()
1832 }
1833
1834 fn kind(&self) -> Cow<ItemKind> { self.0.kind() }
1835
1836 fn amount(&self) -> NonZeroU32 { self.0.amount }
1837
1838 fn quality(&self) -> Quality { self.0.quality() }
1839
1840 fn num_slots(&self) -> u16 { self.0.num_slots() }
1841
1842 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.0.item_definition_id() }
1843
1844 fn tags(&self) -> Vec<ItemTag> { self.0.tags() }
1845
1846 fn is_modular(&self) -> bool { self.0.is_modular() }
1847
1848 fn components(&self) -> &[Item] { self.0.components() }
1849
1850 fn has_durability(&self) -> bool { self.0.has_durability() }
1851
1852 fn durability_lost(&self) -> Option<u32> { self.0.durability_lost() }
1853
1854 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1855 self.0.stats_durability_multiplier()
1856 }
1857}
1858
1859impl ItemDesc for ItemDef {
1860 fn description(&self) -> &str {
1861 #[expect(deprecated)]
1862 &self.description
1863 }
1864
1865 fn name(&self) -> Cow<str> {
1866 #[expect(deprecated)]
1867 Cow::Borrowed(&self.name)
1868 }
1869
1870 fn kind(&self) -> Cow<ItemKind> { Cow::Borrowed(&self.kind) }
1871
1872 fn amount(&self) -> NonZeroU32 { NonZeroU32::new(1).unwrap() }
1873
1874 fn quality(&self) -> Quality { self.quality }
1875
1876 fn num_slots(&self) -> u16 { self.slots }
1877
1878 fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1879 ItemDefinitionId::Simple(Cow::Borrowed(&self.item_definition_id))
1880 }
1881
1882 fn tags(&self) -> Vec<ItemTag> { self.tags.to_vec() }
1883
1884 fn is_modular(&self) -> bool { false }
1885
1886 fn components(&self) -> &[Item] { &[] }
1887
1888 fn has_durability(&self) -> bool {
1889 self.kind().has_durability() && self.quality != Quality::Debug
1890 }
1891
1892 fn durability_lost(&self) -> Option<u32> { None }
1893
1894 fn stats_durability_multiplier(&self) -> DurabilityMultiplier { DurabilityMultiplier(1.0) }
1895}
1896
1897impl ItemDesc for PickupItem {
1898 fn description(&self) -> &str {
1899 #[expect(deprecated)]
1900 self.item().description()
1901 }
1902
1903 fn name(&self) -> Cow<str> {
1904 #[expect(deprecated)]
1905 self.item().name()
1906 }
1907
1908 fn kind(&self) -> Cow<ItemKind> { self.item().kind() }
1909
1910 fn amount(&self) -> NonZeroU32 {
1911 NonZeroU32::new(self.amount()).expect("Item having amount of 0 is invariant")
1912 }
1913
1914 fn quality(&self) -> Quality { self.item().quality() }
1915
1916 fn num_slots(&self) -> u16 { self.item().num_slots() }
1917
1918 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item().item_definition_id() }
1919
1920 fn tags(&self) -> Vec<ItemTag> { self.item().tags() }
1921
1922 fn is_modular(&self) -> bool { self.item().is_modular() }
1923
1924 fn components(&self) -> &[Item] { self.item().components() }
1925
1926 fn has_durability(&self) -> bool { self.item().has_durability() }
1927
1928 fn durability_lost(&self) -> Option<u32> { self.item().durability_lost() }
1929
1930 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1931 self.item().stats_durability_multiplier()
1932 }
1933}
1934
1935#[derive(Clone, Debug, Serialize, Deserialize)]
1936pub struct ItemDrops(pub Vec<(u32, Item)>);
1937
1938impl Component for ItemDrops {
1939 type Storage = DenseVecStorage<Self>;
1940}
1941
1942impl Component for PickupItem {
1943 type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1944}
1945
1946impl Component for ThrownItem {
1947 type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1948}
1949
1950#[derive(Copy, Clone, Debug)]
1951pub struct DurabilityMultiplier(pub f32);
1952
1953impl<T: ItemDesc + ?Sized> ItemDesc for &T {
1954 fn description(&self) -> &str {
1955 #[expect(deprecated)]
1956 (*self).description()
1957 }
1958
1959 fn name(&self) -> Cow<str> {
1960 #[expect(deprecated)]
1961 (*self).name()
1962 }
1963
1964 fn kind(&self) -> Cow<ItemKind> { (*self).kind() }
1965
1966 fn amount(&self) -> NonZeroU32 { (*self).amount() }
1967
1968 fn quality(&self) -> Quality { (*self).quality() }
1969
1970 fn num_slots(&self) -> u16 { (*self).num_slots() }
1971
1972 fn item_definition_id(&self) -> ItemDefinitionId<'_> { (*self).item_definition_id() }
1973
1974 fn tags(&self) -> Vec<ItemTag> { (*self).tags() }
1975
1976 fn is_modular(&self) -> bool { (*self).is_modular() }
1977
1978 fn components(&self) -> &[Item] { (*self).components() }
1979
1980 fn has_durability(&self) -> bool { (*self).has_durability() }
1981
1982 fn durability_lost(&self) -> Option<u32> { (*self).durability_lost() }
1983
1984 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1985 (*self).stats_durability_multiplier()
1986 }
1987}
1988
1989pub fn all_item_defs_expect() -> Vec<String> {
1993 try_all_item_defs().expect("Failed to access items directory")
1994}
1995
1996pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
1998 let defs = assets::load_rec_dir::<RawItemDef>("common.items")?;
1999 Ok(defs.read().ids().map(|id| id.to_string()).collect())
2000}
2001
2002pub fn all_items_expect() -> Vec<Item> {
2005 let defs = assets::load_rec_dir::<RawItemDef>("common.items")
2006 .expect("failed to load item asset directory");
2007
2008 let mut asset_items: Vec<Item> = defs
2010 .read()
2011 .ids()
2012 .map(|id| Item::new_from_asset_expect(id))
2013 .collect();
2014
2015 let mut material_parse_table = HashMap::new();
2016 for mat in Material::iter() {
2017 if let Some(id) = mat.asset_identifier() {
2018 material_parse_table.insert(id.to_owned(), mat);
2019 }
2020 }
2021
2022 let primary_comp_pool = modular::PRIMARY_COMPONENT_POOL.clone();
2023
2024 let mut primary_comps: Vec<Item> = primary_comp_pool
2026 .values()
2027 .flatten()
2028 .map(|(item, _hand_rules)| item.clone())
2029 .collect();
2030
2031 let mut modular_items: Vec<Item> = primary_comp_pool
2033 .keys()
2034 .flat_map(|(tool, mat_id)| {
2035 let mat = material_parse_table
2036 .get(mat_id)
2037 .expect("unexpected material ident");
2038
2039 modular::generate_weapons(*tool, *mat, None)
2041 .expect("failure during modular weapon generation")
2042 })
2043 .collect();
2044
2045 let mut all = Vec::new();
2054 all.append(&mut asset_items);
2055 all.append(&mut primary_comps);
2056 all.append(&mut modular_items);
2057
2058 all
2059}
2060
2061impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2062 fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
2063 use ItemDefinitionId as DefId;
2064 match self {
2065 Self::Simple(simple) => {
2066 matches!(other, DefId::Simple(other_simple) if simple == other_simple)
2067 },
2068 Self::Modular {
2069 pseudo_base,
2070 components,
2071 } => matches!(
2072 other,
2073 DefId::Modular { pseudo_base: other_base, components: other_comps }
2074 if pseudo_base == other_base && components == other_comps
2075 ),
2076 Self::Compound {
2077 simple_base,
2078 components,
2079 } => matches!(
2080 other,
2081 DefId::Compound { simple_base: other_base, components: other_comps }
2082 if simple_base == other_base && components == other_comps
2083 ),
2084 }
2085 }
2086}
2087
2088impl PartialEq<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2089 #[inline]
2090 fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self }
2091}
2092
2093impl Equivalent<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2094 fn equivalent(&self, key: &ItemDefinitionIdOwned) -> bool { self == key }
2095}
2096
2097impl From<&ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2098 fn from(value: &ItemDefinitionId<'_>) -> Self { value.to_owned() }
2099}
2100
2101#[cfg(test)]
2102mod tests {
2103 use super::*;
2104
2105 #[test]
2106 fn test_assets_items() {
2107 let ids = all_item_defs_expect();
2108 for item in ids.iter().map(|id| Item::new_from_asset_expect(id)) {
2109 drop(item)
2110 }
2111 }
2112
2113 #[test]
2114 fn test_item_i18n() { let _ = ItemI18n::new_expect(); }
2115
2116 #[test]
2117 fn test_all_items() { let _ = all_items_expect(); }
2119
2120 #[test]
2121 fn ensure_item_localization() {
2124 let manifest = ItemI18n::new_expect();
2125 let items = all_items_expect();
2126 let mut errs = vec![];
2127 for item in items {
2128 let item_key: ItemKey = (&item).into();
2129 if manifest.item_text_opt(item_key.clone()).is_none() {
2130 errs.push(item_key)
2131 }
2132 }
2133 if !errs.is_empty() {
2134 panic!("item i18n manifest misses translation-id for following items {errs:#?}")
2135 }
2136 }
2137}