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