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