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