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