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