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 Quest,
383}
384
385#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
386pub enum ConsumableKind {
387 Drink,
388 Food,
389 ComplexFood,
390 Charm,
391 Recipe,
392}
393
394impl ItemKind {
395 pub fn is_equippable(&self) -> bool {
396 matches!(
397 self,
398 ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Glider | ItemKind::Lantern(_)
399 )
400 }
401
402 pub fn get_itemkind_string(&self) -> String {
405 match self {
406 ItemKind::Tool(tool) => format!("Tool: {:?}", tool.kind),
408 ItemKind::ModularComponent(modular_component) => {
409 format!("ModularComponent: {:?}", modular_component.toolkind())
410 },
411 ItemKind::Lantern(lantern) => format!("Lantern: {:?}", lantern),
412 ItemKind::Armor(armor) => format!("Armor: {:?}", armor.stats),
413 ItemKind::Glider => "Glider:".to_string(),
414 ItemKind::Consumable { kind, .. } => {
415 format!("Consumable: {:?}", kind)
416 },
417 ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
418 #[expect(deprecated)]
419 ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
420 ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids),
421 ItemKind::RecipeGroup { .. } => String::from("Recipes:"),
422 ItemKind::Quest => String::from("Quest:"),
423 }
424 }
425
426 pub fn has_durability(&self) -> bool {
427 match self {
428 ItemKind::Tool(Tool { kind, .. }) => !matches!(kind, ToolKind::Throwable),
429 ItemKind::Armor(armor) => armor.kind.has_durability(),
430 ItemKind::ModularComponent(_)
431 | ItemKind::Lantern(_)
432 | ItemKind::Quest
433 | ItemKind::Glider
434 | ItemKind::Consumable { .. }
435 | ItemKind::Utility { .. }
436 | ItemKind::Ingredient { .. }
437 | ItemKind::TagExamples { .. }
438 | ItemKind::RecipeGroup { .. } => false,
439 }
440 }
441}
442
443pub type ItemId = AtomicCell<Option<NonZeroU64>>;
444
445#[derive(Clone, Debug, Serialize, Deserialize)]
459pub struct Item {
460 #[serde(skip)]
467 item_id: Arc<ItemId>,
468 item_base: ItemBase,
472 components: Vec<Item>,
481 amount: NonZeroU32,
484 slots: Vec<InvSlot>,
486 item_config: Option<Box<ItemConfig>>,
487 hash: u64,
488 durability_lost: Option<u32>,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
497pub struct FrontendItem(Item);
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
512pub struct PickupItem {
513 items: Vec<Item>,
514 created_at: ProgramTime,
516 next_merge_check: ProgramTime,
518 pub should_merge: bool,
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct ThrownItem(pub Item);
528
529use std::hash::{Hash, Hasher};
530
531impl Hash for Item {
533 fn hash<H: Hasher>(&self, state: &mut H) {
534 self.item_definition_id().hash(state);
535 self.components.iter().for_each(|comp| comp.hash(state));
536 }
537}
538
539type I18nId = String;
542
543#[derive(Clone, Debug, Serialize, Deserialize)]
544pub struct ItemI18n {
560 map: HashMap<ItemKey, I18nId>,
562 fragments: HashMap<FragmentKey, I18nId>,
566}
567
568#[derive(Hash, Eq, PartialEq, Debug, Clone, Deserialize, Serialize)]
569pub enum FragmentKey {
570 Ingredient(String),
572 WeaponPrimaryComponent(String, Hands),
574}
575
576impl ItemI18n {
577 pub fn new_expect() -> Self {
578 Ron::load_expect("common.item_i18n_manifest")
579 .read()
580 .clone()
581 .into_inner()
582 }
583
584 fn item_text_opt(&self, item_key: &ItemKey) -> Option<(Content, Content)> {
588 let key = self.try_key(item_key);
589 key.map(|key| {
590 (
591 Content::Key(key.to_owned()),
592 Content::Attr(key.to_owned(), "desc".to_owned()),
593 )
594 })
595 }
596
597 fn try_fragment(&self, fragment_key: &FragmentKey) -> Option<Content> {
600 self.fragments
601 .get(fragment_key)
602 .map(|key| Content::Key(key.to_owned()))
603 }
604
605 fn try_key(&self, item_key: &ItemKey) -> Option<&I18nId> {
608 let key;
611 let item_key = if let ItemKey::TagExamples(_, id) = item_key {
612 key = ItemKey::Simple(id.to_string());
613 &key
614 } else {
615 item_key
616 };
617
618 self.map.get(item_key)
619 }
620
621 pub fn all_fragments(&self) -> impl Iterator<Item = (&FragmentKey, &I18nId)> {
623 self.fragments.iter()
624 }
625}
626
627#[derive(Clone, Debug)]
628pub enum ItemBase {
629 Simple(Arc<ItemDef>),
630 Modular(ModularBase),
631}
632
633impl Serialize for ItemBase {
634 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
638 where
639 S: Serializer,
640 {
641 serializer.serialize_str(&self.serialization_item_id())
642 }
643}
644
645impl<'de> Deserialize<'de> for ItemBase {
646 fn deserialize<D>(deserializer: D) -> Result<ItemBase, D::Error>
649 where
650 D: de::Deserializer<'de>,
651 {
652 struct ItemBaseStringVisitor;
653
654 impl de::Visitor<'_> for ItemBaseStringVisitor {
655 type Value = ItemBase;
656
657 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
658 formatter.write_str("item def string")
659 }
660
661 fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
662 where
663 E: de::Error,
664 {
665 ItemBase::from_item_id_string(serialized_item_base)
666 .map_err(|err| E::custom(err.to_string()))
667 }
668 }
669
670 deserializer.deserialize_str(ItemBaseStringVisitor)
671 }
672}
673
674impl ItemBase {
675 fn num_slots(&self) -> u16 {
676 match self {
677 ItemBase::Simple(item_def) => item_def.num_slots(),
678 ItemBase::Modular(_) => 0,
679 }
680 }
681
682 fn serialization_item_id(&self) -> String {
685 match &self {
686 ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
687 ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
688 }
689 }
690
691 fn from_item_id_string(item_id_string: &str) -> Result<Self, Error> {
692 if item_id_string.starts_with(crate::modular_item_id_prefix!()) {
693 Ok(ItemBase::Modular(ModularBase::load_from_pseudo_id(
694 item_id_string,
695 )))
696 } else {
697 Ok(ItemBase::Simple(Arc::<ItemDef>::load_cloned(
698 item_id_string,
699 )?))
700 }
701 }
702}
703
704#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
708pub enum ItemDefinitionId<'a> {
709 Simple(Cow<'a, str>),
710 Modular {
711 pseudo_base: &'a str,
712 components: Vec<ItemDefinitionId<'a>>,
713 },
714 Compound {
715 simple_base: &'a str,
716 components: Vec<ItemDefinitionId<'a>>,
717 },
718}
719
720#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
721pub enum ItemDefinitionIdOwned {
722 Simple(String),
723 Modular {
724 pseudo_base: String,
725 components: Vec<ItemDefinitionIdOwned>,
726 },
727 Compound {
728 simple_base: String,
729 components: Vec<ItemDefinitionIdOwned>,
730 },
731}
732
733impl ItemDefinitionIdOwned {
734 pub fn as_ref(&self) -> ItemDefinitionId<'_> {
735 match *self {
736 Self::Simple(ref id) => ItemDefinitionId::Simple(Cow::Borrowed(id)),
737 Self::Modular {
738 ref pseudo_base,
739 ref components,
740 } => ItemDefinitionId::Modular {
741 pseudo_base,
742 components: components.iter().map(|comp| comp.as_ref()).collect(),
743 },
744 Self::Compound {
745 ref simple_base,
746 ref components,
747 } => ItemDefinitionId::Compound {
748 simple_base,
749 components: components.iter().map(|comp| comp.as_ref()).collect(),
750 },
751 }
752 }
753}
754
755impl ItemDefinitionId<'_> {
756 pub fn itemdef_id(&self) -> Option<&str> {
757 match self {
758 Self::Simple(id) => Some(id),
759 Self::Modular { .. } => None,
760 Self::Compound { simple_base, .. } => Some(simple_base),
761 }
762 }
763
764 pub fn to_owned(&self) -> ItemDefinitionIdOwned {
765 match self {
766 Self::Simple(id) => ItemDefinitionIdOwned::Simple(String::from(&**id)),
767 Self::Modular {
768 pseudo_base,
769 components,
770 } => ItemDefinitionIdOwned::Modular {
771 pseudo_base: String::from(*pseudo_base),
772 components: components.iter().map(|comp| comp.to_owned()).collect(),
773 },
774 Self::Compound {
775 simple_base,
776 components,
777 } => ItemDefinitionIdOwned::Compound {
778 simple_base: String::from(*simple_base),
779 components: components.iter().map(|comp| comp.to_owned()).collect(),
780 },
781 }
782 }
783}
784
785#[derive(Debug, Serialize, Deserialize)]
786pub struct ItemDef {
787 #[serde(default)]
788 item_definition_id: String,
792 #[deprecated = "since item i18n"]
793 legacy_name: String,
794 pub kind: ItemKind,
795 pub quality: Quality,
796 pub tags: Vec<ItemTag>,
797 #[serde(default)]
798 pub slots: u16,
799 pub ability_spec: Option<AbilitySpec>,
802}
803
804impl PartialEq for ItemDef {
805 fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
806}
807
808#[derive(Clone, Debug, Serialize, Deserialize)]
810pub struct ItemConfig {
811 pub abilities: AbilitySet<tool::AbilityItem>,
812}
813
814#[derive(Debug)]
815pub enum ItemConfigError {
816 BadItemKind,
817}
818
819impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
820 type Error = ItemConfigError;
821
822 fn try_from(
823 (item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
825 ) -> Result<Self, Self::Error> {
826 match &*item.kind() {
827 ItemKind::Tool(tool) => {
828 let tool_default = |tool_kind| {
830 let key = &AbilitySpec::Tool(tool_kind);
831 ability_map.get_ability_set(key)
832 };
833 let abilities = if let Some(set_key) = item.ability_spec() {
834 if let Some(set) = ability_map.get_ability_set(&set_key) {
835 set.clone()
836 .modified_by_tool(tool, item.stats_durability_multiplier())
837 } else {
838 error!(
839 "Custom ability set: {:?} references non-existent set, falling back \
840 to default ability set.",
841 set_key
842 );
843 tool_default(tool.kind).cloned().unwrap_or_default()
844 }
845 } else if let Some(set) = tool_default(tool.kind) {
846 set.clone()
847 .modified_by_tool(tool, item.stats_durability_multiplier())
848 } else {
849 error!(
850 "No ability set defined for tool: {:?}, falling back to default ability \
851 set.",
852 tool.kind
853 );
854 Default::default()
855 };
856
857 Ok(ItemConfig { abilities })
858 },
859 ItemKind::Glider => item
860 .ability_spec()
861 .and_then(|set_key| ability_map.get_ability_set(&set_key))
862 .map(|abilities| ItemConfig {
863 abilities: abilities.clone(),
864 })
865 .ok_or(ItemConfigError::BadItemKind),
866 _ => Err(ItemConfigError::BadItemKind),
867 }
868 }
869}
870
871impl ItemDef {
872 pub fn is_stackable(&self) -> bool {
873 matches!(
874 self.kind,
875 ItemKind::Consumable { .. }
876 | ItemKind::Quest
877 | ItemKind::Ingredient { .. }
878 | ItemKind::Utility { .. }
879 | ItemKind::Tool(Tool {
880 kind: ToolKind::Throwable,
881 ..
882 })
883 )
884 }
885
886 pub fn max_amount(&self) -> u32 { if self.is_stackable() { u32::MAX } else { 1 } }
889
890 pub fn id(&self) -> &str { &self.item_definition_id }
892
893 #[cfg(test)]
894 pub fn new_test(
895 item_definition_id: String,
896 kind: ItemKind,
897 quality: Quality,
898 tags: Vec<ItemTag>,
899 slots: u16,
900 ) -> Self {
901 #[expect(deprecated)]
902 Self {
903 item_definition_id,
904 legacy_name: "test item name".to_owned(),
905 kind,
906 quality,
907 tags,
908 slots,
909 ability_spec: None,
910 }
911 }
912
913 #[cfg(test)]
914 pub fn create_test_itemdef_from_kind(kind: ItemKind) -> Self {
915 #[expect(deprecated)]
916 Self {
917 item_definition_id: "test.item".to_string(),
918 legacy_name: "test item name".to_owned(),
919 kind,
920 quality: Quality::Common,
921 tags: vec![],
922 slots: 0,
923 ability_spec: None,
924 }
925 }
926}
927
928impl PartialEq for Item {
935 fn eq(&self, other: &Self) -> bool {
936 (match (&self.item_base, &other.item_base) {
937 (ItemBase::Simple(our_def), ItemBase::Simple(other_def)) => {
938 our_def.item_definition_id == other_def.item_definition_id
939 },
940 (ItemBase::Modular(our_base), ItemBase::Modular(other_base)) => our_base == other_base,
941 _ => false,
942 }) && self.components() == other.components()
943 }
944}
945
946impl Asset for ItemDef {
947 fn load(cache: &AssetCache, specifier: &SharedString) -> Result<Self, BoxedError> {
948 if specifier.starts_with("veloren.core.") {
949 return Err(format!(
950 "Attempted to load an asset from a specifier reserved for core veloren functions. \
951 Specifier: {}",
952 specifier
953 )
954 .into());
955 }
956
957 let RawItemDef {
958 legacy_name,
959 legacy_description: _,
960 kind,
961 quality,
962 tags,
963 slots,
964 ability_spec,
965 } = cache.load::<Ron<_>>(specifier)?.cloned().into_inner();
966
967 let item_definition_id = specifier.replace('\\', ".");
972
973 Ok(ItemDef {
974 item_definition_id,
975 #[expect(deprecated)]
976 legacy_name,
977 kind,
978 quality,
979 tags,
980 slots,
981 ability_spec,
982 })
983 }
984}
985
986#[derive(Clone, Debug, Serialize, Deserialize)]
987#[serde(rename = "ItemDef", deny_unknown_fields)]
988struct RawItemDef {
989 legacy_name: String,
990 legacy_description: String,
991 kind: ItemKind,
992 quality: Quality,
993 tags: Vec<ItemTag>,
994 #[serde(default)]
995 slots: u16,
996 ability_spec: Option<AbilitySpec>,
997}
998
999#[derive(Debug)]
1000pub struct OperationFailure;
1001
1002impl Item {
1003 pub const MAX_DURABILITY: u32 = 12;
1004
1005 pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
1008
1009 pub fn new_from_item_base(
1010 inner_item: ItemBase,
1011 components: Vec<Item>,
1012 ability_map: &AbilityMap,
1013 msm: &MaterialStatManifest,
1014 ) -> Self {
1015 let mut item = Item {
1016 item_id: Arc::new(AtomicCell::new(None)),
1017 amount: NonZeroU32::new(1).unwrap(),
1018 components,
1019 slots: vec![None; inner_item.num_slots() as usize],
1020 item_base: inner_item,
1021 item_config: None,
1023 hash: 0,
1024 durability_lost: None,
1025 };
1026 item.durability_lost = item.has_durability().then_some(0);
1027 item.update_item_state(ability_map, msm);
1028 item
1029 }
1030
1031 pub fn new_from_item_definition_id(
1032 item_definition_id: ItemDefinitionId<'_>,
1033 ability_map: &AbilityMap,
1034 msm: &MaterialStatManifest,
1035 ) -> Result<Self, Error> {
1036 let (base, components) = match item_definition_id {
1037 ItemDefinitionId::Simple(spec) => {
1038 let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(&spec)?);
1039 (base, Vec::new())
1040 },
1041 ItemDefinitionId::Modular {
1042 pseudo_base,
1043 components,
1044 } => {
1045 let base = ItemBase::Modular(ModularBase::load_from_pseudo_id(pseudo_base));
1046 let components = components
1047 .into_iter()
1048 .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1049 .collect::<Result<Vec<_>, _>>()?;
1050 (base, components)
1051 },
1052 ItemDefinitionId::Compound {
1053 simple_base,
1054 components,
1055 } => {
1056 let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(simple_base)?);
1057 let components = components
1058 .into_iter()
1059 .map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
1060 .collect::<Result<Vec<_>, _>>()?;
1061 (base, components)
1062 },
1063 };
1064 Ok(Item::new_from_item_base(base, components, ability_map, msm))
1065 }
1066
1067 pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
1070 Item::new_from_asset(asset_specifier).unwrap_or_else(|err| {
1071 panic!(
1072 "Expected asset to exist: {}, instead got error {:?}",
1073 asset_specifier, err
1074 );
1075 })
1076 }
1077
1078 pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
1081 let specifier = asset_glob.strip_suffix(".*").unwrap_or(asset_glob);
1082 let defs = assets::load_rec_dir::<Ron<RawItemDef>>(specifier)?;
1083 defs.read()
1084 .ids()
1085 .map(|id| Item::new_from_asset(id))
1086 .collect()
1087 }
1088
1089 pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
1092 let inner_item = ItemBase::from_item_id_string(asset)?;
1093 let msm = &MaterialStatManifest::load().read();
1095 let ability_map = &AbilityMap::load().read();
1096 Ok(Item::new_from_item_base(
1097 inner_item,
1098 Vec::new(),
1099 ability_map,
1100 msm,
1101 ))
1102 }
1103
1104 #[must_use]
1106 pub fn frontend_item(
1107 &self,
1108 ability_map: &AbilityMap,
1109 msm: &MaterialStatManifest,
1110 ) -> FrontendItem {
1111 FrontendItem(self.duplicate(ability_map, msm))
1112 }
1113
1114 #[must_use]
1116 pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1117 let duplicated_components = self
1118 .components
1119 .iter()
1120 .map(|comp| comp.duplicate(ability_map, msm))
1121 .collect();
1122 let mut new_item = Item::new_from_item_base(
1123 match &self.item_base {
1124 ItemBase::Simple(item_def) => ItemBase::Simple(Arc::clone(item_def)),
1125 ItemBase::Modular(mod_base) => ItemBase::Modular(mod_base.clone()),
1126 },
1127 duplicated_components,
1128 ability_map,
1129 msm,
1130 );
1131 new_item.set_amount(self.amount()).expect(
1132 "`new_item` has the same `item_def` and as an invariant, \
1133 self.set_amount(self.amount()) should always succeed.",
1134 );
1135 new_item.slots_mut().iter_mut().zip(self.slots()).for_each(
1136 |(new_item_slot, old_item_slot)| {
1137 *new_item_slot = old_item_slot
1138 .as_ref()
1139 .map(|old_item| old_item.duplicate(ability_map, msm));
1140 },
1141 );
1142 new_item
1143 }
1144
1145 pub fn stacked_duplicates<'a>(
1146 &'a self,
1147 ability_map: &'a AbilityMap,
1148 msm: &'a MaterialStatManifest,
1149 count: u32,
1150 ) -> impl Iterator<Item = Self> + 'a {
1151 let max_stack_count = count / self.max_amount();
1152 let rest = count % self.max_amount();
1153
1154 (0..max_stack_count)
1155 .map(|_| {
1156 let mut item = self.duplicate(ability_map, msm);
1157
1158 item.set_amount(item.max_amount())
1159 .expect("max_amount() is always a valid amount.");
1160
1161 item
1162 })
1163 .chain((rest > 0).then(move || {
1164 let mut item = self.duplicate(ability_map, msm);
1165
1166 item.set_amount(rest)
1167 .expect("anything less than max_amount() is always a valid amount.");
1168
1169 item
1170 }))
1171 }
1172
1173 #[doc(hidden)]
1181 pub fn get_item_id_for_database(&self) -> Arc<ItemId> { Arc::clone(&self.item_id) }
1182
1183 fn reset_item_id(&mut self) {
1193 if let Some(item_id) = Arc::get_mut(&mut self.item_id) {
1194 *item_id = AtomicCell::new(None);
1195 } else {
1196 self.item_id = Arc::new(AtomicCell::new(None));
1197 }
1198 for component in self.components.iter_mut() {
1200 component.reset_item_id();
1201 }
1202 }
1203
1204 pub fn put_in_world(&mut self) { self.reset_item_id() }
1209
1210 pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), OperationFailure> {
1211 let amount = u32::from(self.amount);
1212 self.amount = amount
1213 .checked_add(increase_by)
1214 .filter(|&amount| amount <= self.max_amount())
1215 .and_then(NonZeroU32::new)
1216 .ok_or(OperationFailure)?;
1217 Ok(())
1218 }
1219
1220 pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), OperationFailure> {
1221 let amount = u32::from(self.amount);
1222 self.amount = amount
1223 .checked_sub(decrease_by)
1224 .and_then(NonZeroU32::new)
1225 .ok_or(OperationFailure)?;
1226 Ok(())
1227 }
1228
1229 pub fn set_amount(&mut self, give_amount: u32) -> Result<(), OperationFailure> {
1230 if give_amount <= self.max_amount() {
1231 self.amount = NonZeroU32::new(give_amount).ok_or(OperationFailure)?;
1232 Ok(())
1233 } else {
1234 Err(OperationFailure)
1235 }
1236 }
1237
1238 pub fn persistence_access_add_component(&mut self, component: Item) {
1239 self.components.push(component);
1240 }
1241
1242 pub fn persistence_access_mutable_component(&mut self, index: usize) -> Option<&mut Self> {
1243 self.components.get_mut(index)
1244 }
1245
1246 pub fn update_item_state(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1250 if let Ok(item_config) = ItemConfig::try_from((&*self, ability_map, msm)) {
1252 self.item_config = Some(Box::new(item_config));
1253 }
1254 self.hash = {
1256 let mut s = DefaultHasher::new();
1257 self.hash(&mut s);
1258 s.finish()
1259 };
1260 }
1261
1262 pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
1264 self.slots.iter_mut().filter_map(mem::take)
1265 }
1266
1267 pub fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1268 match &self.item_base {
1269 ItemBase::Simple(item_def) => {
1270 if self.components.is_empty() {
1271 ItemDefinitionId::Simple(Cow::Borrowed(&item_def.item_definition_id))
1272 } else {
1273 ItemDefinitionId::Compound {
1274 simple_base: &item_def.item_definition_id,
1275 components: self
1276 .components
1277 .iter()
1278 .map(|item| item.item_definition_id())
1279 .collect(),
1280 }
1281 }
1282 },
1283 ItemBase::Modular(mod_base) => ItemDefinitionId::Modular {
1284 pseudo_base: mod_base.pseudo_item_id(),
1285 components: self
1286 .components
1287 .iter()
1288 .map(|item| item.item_definition_id())
1289 .collect(),
1290 },
1291 }
1292 }
1293
1294 pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
1295 if let ItemBase::Simple(self_def) = &self.item_base {
1296 self_def.item_definition_id == item_def.item_definition_id
1297 } else {
1298 false
1299 }
1300 }
1301
1302 pub fn matches_recipe_input(&self, recipe_input: &RecipeInput, amount: u32) -> bool {
1303 match recipe_input {
1304 RecipeInput::Item(item_def) => self.is_same_item_def(item_def),
1305 RecipeInput::Tag(tag) => self.tags().contains(tag),
1306 RecipeInput::TagSameItem(tag) => {
1307 self.tags().contains(tag) && u32::from(self.amount) >= amount
1308 },
1309 RecipeInput::ListSameItem(item_defs) => item_defs.iter().any(|item_def| {
1310 self.is_same_item_def(item_def) && u32::from(self.amount) >= amount
1311 }),
1312 }
1313 }
1314
1315 pub fn is_salvageable(&self) -> bool {
1316 self.tags()
1317 .iter()
1318 .any(|tag| matches!(tag, ItemTag::SalvageInto(_, _)))
1319 }
1320
1321 pub fn salvage_output(&self) -> impl Iterator<Item = (&str, u32)> {
1322 self.tags().into_iter().filter_map(|tag| {
1323 if let ItemTag::SalvageInto(material, quantity) = tag {
1324 material
1325 .asset_identifier()
1326 .map(|material_id| (material_id, quantity))
1327 } else {
1328 None
1329 }
1330 })
1331 }
1332
1333 #[deprecated = "since item i18n"]
1334 pub fn legacy_name(&self) -> Cow<'_, str> {
1335 match &self.item_base {
1336 ItemBase::Simple(item_def) => {
1337 if self.components.is_empty() {
1338 #[expect(deprecated)]
1339 Cow::Borrowed(&item_def.legacy_name)
1340 } else {
1341 #[expect(deprecated)]
1342 modular::modify_name(&item_def.legacy_name, self)
1343 }
1344 },
1345 #[expect(deprecated, reason = "since item i18n")]
1346 ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()),
1347 }
1348 }
1349
1350 pub fn kind(&self) -> Cow<'_, ItemKind> {
1351 match &self.item_base {
1352 ItemBase::Simple(item_def) => Cow::Borrowed(&item_def.kind),
1353 ItemBase::Modular(mod_base) => {
1354 let msm = &MaterialStatManifest::load().read();
1356 mod_base.kind(self.components(), msm, self.stats_durability_multiplier())
1357 },
1358 }
1359 }
1360
1361 pub fn amount(&self) -> u32 { u32::from(self.amount) }
1362
1363 pub fn is_stackable(&self) -> bool {
1364 match &self.item_base {
1365 ItemBase::Simple(item_def) => item_def.is_stackable(),
1366 ItemBase::Modular(_) => false,
1368 }
1369 }
1370
1371 pub fn max_amount(&self) -> u32 {
1374 match &self.item_base {
1375 ItemBase::Simple(item_def) => item_def.max_amount(),
1376 ItemBase::Modular(_) => {
1377 debug_assert!(!self.is_stackable());
1378 1
1379 },
1380 }
1381 }
1382
1383 pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
1384
1385 pub fn quality(&self) -> Quality {
1386 match &self.item_base {
1387 ItemBase::Simple(item_def) => item_def.quality.max(
1388 self.components
1389 .iter()
1390 .fold(Quality::MIN, |a, b| a.max(b.quality())),
1391 ),
1392 ItemBase::Modular(mod_base) => mod_base.compute_quality(self.components()),
1393 }
1394 }
1395
1396 pub fn components(&self) -> &[Item] { &self.components }
1397
1398 pub fn slots(&self) -> &[InvSlot] { &self.slots }
1399
1400 pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
1401
1402 pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() }
1403
1404 pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }
1405
1406 pub fn populated_slots(&self) -> usize { self.slots().len().saturating_sub(self.free_slots()) }
1407
1408 pub fn slot(&self, slot: usize) -> Option<&InvSlot> { self.slots.get(slot) }
1409
1410 pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
1411
1412 pub fn try_reclaim_from_block(
1413 block: Block,
1414 sprite_cfg: Option<&SpriteCfg>,
1415 ) -> Option<Vec<(u32, Self)>> {
1416 if let Some(loot_spec) = sprite_cfg.and_then(|sprite_cfg| sprite_cfg.loot_table.as_ref()) {
1417 LootSpec::LootTable(loot_spec).to_items()
1418 } else {
1419 block.get_sprite()?.default_loot_spec()??.to_items()
1420 }
1421 }
1422
1423 pub fn ability_spec(&self) -> Option<Cow<'_, AbilitySpec>> {
1424 match &self.item_base {
1425 ItemBase::Simple(item_def) => {
1426 item_def.ability_spec.as_ref().map(Cow::Borrowed).or({
1427 if let ItemKind::Tool(tool) = &item_def.kind {
1430 Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
1431 } else {
1432 None
1433 }
1434 })
1435 },
1436 ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
1437 }
1438 }
1439
1440 pub fn tags(&self) -> Vec<ItemTag> {
1443 match &self.item_base {
1444 ItemBase::Simple(item_def) => item_def.tags.to_vec(),
1445 ItemBase::Modular(mod_base) => mod_base.generate_tags(self.components()),
1447 }
1448 }
1449
1450 pub fn is_modular(&self) -> bool {
1451 match &self.item_base {
1452 ItemBase::Simple(_) => false,
1453 ItemBase::Modular(_) => true,
1454 }
1455 }
1456
1457 pub fn item_hash(&self) -> u64 { self.hash }
1458
1459 pub fn persistence_item_id(&self) -> String {
1460 match &self.item_base {
1461 ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
1462 ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
1463 }
1464 }
1465
1466 pub fn durability_lost(&self) -> Option<u32> {
1467 self.durability_lost.map(|x| x.min(Self::MAX_DURABILITY))
1468 }
1469
1470 pub fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1471 let durability_lost = self.durability_lost.unwrap_or(0);
1472 debug_assert!(durability_lost <= Self::MAX_DURABILITY);
1473 const DURABILITY_THRESHOLD: u32 = 9;
1475 const MIN_FRAC: f32 = 0.25;
1476 let mult = (1.0
1477 - durability_lost.saturating_sub(DURABILITY_THRESHOLD) as f32
1478 / (Self::MAX_DURABILITY - DURABILITY_THRESHOLD) as f32)
1479 * (1.0 - MIN_FRAC)
1480 + MIN_FRAC;
1481 DurabilityMultiplier(mult)
1482 }
1483
1484 pub fn has_durability(&self) -> bool {
1485 self.kind().has_durability() && self.quality() != Quality::Debug
1486 }
1487
1488 pub fn increment_damage(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1489 if let Some(durability_lost) = &mut self.durability_lost
1490 && *durability_lost < Self::MAX_DURABILITY
1491 {
1492 *durability_lost += 1;
1493 }
1494 self.update_item_state(ability_map, msm);
1497 }
1498
1499 pub fn persistence_durability(&self) -> Option<NonZeroU32> {
1500 self.durability_lost.and_then(NonZeroU32::new)
1501 }
1502
1503 pub fn persistence_set_durability(&mut self, value: Option<NonZeroU32>) {
1504 if !self.has_durability() {
1507 self.durability_lost = None;
1508 } else {
1509 self.durability_lost = Some(value.map_or(0, NonZeroU32::get));
1512 }
1513 }
1514
1515 pub fn reset_durability(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
1516 self.durability_lost = self.has_durability().then_some(0);
1517 self.update_item_state(ability_map, msm);
1520 }
1521
1522 #[must_use = "Returned items will be lost if not used"]
1526 pub fn take_amount(
1527 &mut self,
1528 ability_map: &AbilityMap,
1529 msm: &MaterialStatManifest,
1530 returning_amount: u32,
1531 ) -> Option<Item> {
1532 if self.is_stackable() && self.amount() > 1 && returning_amount < self.amount() {
1533 let mut return_item = self.duplicate(ability_map, msm);
1534 self.decrease_amount(returning_amount).ok()?;
1535 return_item.set_amount(returning_amount).expect(
1536 "return_item.amount() = returning_amount < self.amount() (since self.amount() ≥ \
1537 1) ≤ self.max_amount() = return_item.max_amount(), since return_item is a \
1538 duplicate of item",
1539 );
1540 Some(return_item)
1541 } else {
1542 None
1543 }
1544 }
1545
1546 #[must_use = "Returned items will be lost if not used"]
1550 pub fn take_half(
1551 &mut self,
1552 ability_map: &AbilityMap,
1553 msm: &MaterialStatManifest,
1554 ) -> Option<Item> {
1555 self.take_amount(ability_map, msm, self.amount() / 2)
1556 }
1557
1558 #[cfg(test)]
1559 pub fn create_test_item_from_kind(kind: ItemKind) -> Self {
1560 let ability_map = &AbilityMap::load().read();
1561 let msm = &MaterialStatManifest::load().read();
1562 Self::new_from_item_base(
1563 ItemBase::Simple(Arc::new(ItemDef::create_test_itemdef_from_kind(kind))),
1564 Vec::new(),
1565 ability_map,
1566 msm,
1567 )
1568 }
1569
1570 pub fn can_merge(&self, other: &Self) -> bool {
1575 if self.amount() > self.max_amount() || other.amount() > other.max_amount() {
1576 error!("An item amount is over max_amount!");
1577 return false;
1578 }
1579
1580 (self == other)
1581 && self.slots().iter().all(Option::is_none)
1582 && other.slots().iter().all(Option::is_none)
1583 && self.durability_lost() == other.durability_lost()
1584 }
1585
1586 pub fn try_merge(&mut self, mut other: Self) -> Result<Option<Self>, Self> {
1596 if self.can_merge(&other) {
1597 let max_amount = self.max_amount();
1598 debug_assert_eq!(
1599 max_amount,
1600 other.max_amount(),
1601 "Mergeable items must have the same max_amount()"
1602 );
1603
1604 let to_fill_self = max_amount
1607 .checked_sub(self.amount())
1608 .expect("can_merge should ensure that amount() <= max_amount()");
1609
1610 if let Some(remainder) = other.amount().checked_sub(to_fill_self).filter(|r| *r > 0) {
1611 self.set_amount(max_amount)
1612 .expect("max_amount() is always a valid amount.");
1613 other.set_amount(remainder).expect(
1614 "We know remainder is more than 0 and less than or equal to max_amount()",
1615 );
1616 Ok(Some(other))
1617 } else {
1618 self.increase_amount(other.amount())
1620 .expect("We know that we can at least add other.amount() to this item");
1621 drop(other);
1622 Ok(None)
1623 }
1624 } else {
1625 Err(other)
1626 }
1627 }
1628
1629 pub fn persistence_item_base(&self) -> &ItemBase { &self.item_base }
1632}
1633
1634impl FrontendItem {
1635 #[must_use]
1638 pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
1639 FrontendItem(self.0.duplicate(ability_map, msm))
1640 }
1641
1642 pub fn set_amount(&mut self, amount: u32) -> Result<(), OperationFailure> {
1643 self.0.set_amount(amount)
1644 }
1645}
1646
1647impl PickupItem {
1648 pub fn new(item: Item, time: ProgramTime, should_merge: bool) -> Self {
1649 Self {
1650 items: vec![item],
1651 created_at: time,
1652 next_merge_check: time,
1653 should_merge,
1654 }
1655 }
1656
1657 pub fn item(&self) -> &Item {
1661 self.items
1662 .last()
1663 .expect("PickupItem without at least one item is an invariant")
1664 }
1665
1666 pub fn created(&self) -> ProgramTime { self.created_at }
1667
1668 pub fn next_merge_check(&self) -> ProgramTime { self.next_merge_check }
1669
1670 pub fn next_merge_check_mut(&mut self) -> &mut ProgramTime { &mut self.next_merge_check }
1671
1672 pub fn amount(&self) -> u32 {
1674 self.items
1675 .iter()
1676 .map(Item::amount)
1677 .fold(0, |total, amount| total.saturating_add(amount))
1678 }
1679
1680 pub fn remove_debug_items(&mut self) {
1683 for item in self.items.iter_mut() {
1684 item.slots_mut().iter_mut().for_each(|container_slot| {
1685 container_slot
1686 .take_if(|contained_item| matches!(contained_item.quality(), Quality::Debug));
1687 });
1688 }
1689 }
1690
1691 pub fn can_merge(&self, other: &PickupItem) -> bool {
1692 let self_item = self.item();
1693 let other_item = other.item();
1694
1695 self.should_merge && other.should_merge && self_item.can_merge(other_item)
1696 }
1697
1698 pub fn try_merge(&mut self, mut other: PickupItem) -> Result<(), PickupItem> {
1701 if self.can_merge(&other) {
1702 let mut self_last = self
1705 .items
1706 .pop()
1707 .expect("PickupItem without at least one item is an invariant");
1708 let other_last = other
1709 .items
1710 .pop()
1711 .expect("PickupItem without at least one item is an invariant");
1712
1713 let merged = self_last
1715 .try_merge(other_last)
1716 .expect("We know these items can be merged");
1717
1718 debug_assert!(
1719 other
1720 .items
1721 .iter()
1722 .chain(self.items.iter())
1723 .all(|item| item.amount() == item.max_amount()),
1724 "All items before the last in `PickupItem` should have a full amount"
1725 );
1726
1727 self.items.append(&mut other.items);
1730
1731 debug_assert!(
1732 merged.is_none() || self_last.amount() == self_last.max_amount(),
1733 "Merged can only be `Some` if the origin was set to `max_amount()`"
1734 );
1735
1736 self.items.push(self_last);
1738
1739 if let Some(remainder) = merged {
1742 self.items.push(remainder);
1743 }
1744
1745 Ok(())
1746 } else {
1747 Err(other)
1748 }
1749 }
1750
1751 pub fn pick_up(mut self) -> (Item, Option<Self>) {
1752 (
1753 self.items
1754 .pop()
1755 .expect("PickupItem without at least one item is an invariant"),
1756 (!self.items.is_empty()).then_some(self),
1757 )
1758 }
1759}
1760
1761pub fn flatten_counted_items<'a>(
1762 items: &'a [(u32, Item)],
1763 ability_map: &'a AbilityMap,
1764 msm: &'a MaterialStatManifest,
1765) -> impl Iterator<Item = Item> + 'a {
1766 items
1767 .iter()
1768 .flat_map(|(count, item)| item.stacked_duplicates(ability_map, msm, *count))
1769}
1770
1771pub trait ItemDesc {
1774 #[deprecated = "since item i18n"]
1775 fn legacy_name(&self) -> Cow<'_, str>;
1776 fn kind(&self) -> Cow<'_, ItemKind>;
1777 fn amount(&self) -> NonZeroU32;
1778 fn quality(&self) -> Quality;
1779 fn num_slots(&self) -> u16;
1780 fn item_definition_id(&self) -> ItemDefinitionId<'_>;
1781 fn tags(&self) -> Vec<ItemTag>;
1782 fn is_modular(&self) -> bool;
1783 fn components(&self) -> &[Item];
1784 fn has_durability(&self) -> bool;
1785 fn durability_lost(&self) -> Option<u32>;
1786 fn stats_durability_multiplier(&self) -> DurabilityMultiplier;
1787
1788 fn tool_info(&self) -> Option<ToolKind> {
1789 if let ItemKind::Tool(tool) = &*self.kind() {
1790 Some(tool.kind)
1791 } else {
1792 None
1793 }
1794 }
1795
1796 fn i18n(&self, i18n: &ItemI18n) -> (Content, Content) {
1798 let item_key: ItemKey = self.into();
1799
1800 let (name, description) = i18n.item_text_opt(&item_key).unwrap_or_else(|| {
1801 (
1802 #[expect(deprecated)]
1803 Content::Plain(self.legacy_name().to_string()),
1804 Content::Plain(String::new()),
1805 )
1806 });
1807
1808 let b = |x| Box::new(x);
1809 if let ItemKey::ModularWeapon((comp_id, ing_id, hands)) = item_key {
1810 let title_fallback = Content::localized("weapon-modular-fallback-template")
1812 .with_arg(
1813 "material-fragment",
1814 i18n.try_fragment(&FragmentKey::Ingredient(ing_id))
1815 .unwrap_or_else(|| Content::Key("Modular".to_owned())),
1818 )
1819 .with_arg(
1820 "weapon",
1821 i18n.try_fragment(&FragmentKey::WeaponPrimaryComponent(comp_id, hands))
1822 .unwrap_or_else(|| Content::Key("Weapon".to_owned())),
1825 );
1826
1827 (
1828 Content::WithFallback(b(name), b(title_fallback)),
1829 description,
1831 )
1832 } else if let ItemKey::ModularWeaponComponent((comp_id, ing_id)) = item_key {
1833 let title_fallback = Content::localized("weapon-modular-comp-fallback-template")
1835 .with_arg(
1836 "material-fragment",
1837 i18n.try_fragment(&FragmentKey::Ingredient(ing_id))
1838 .unwrap_or_else(|| Content::Key("Modular".to_owned())),
1841 )
1842 .with_arg(
1843 "component",
1844 i18n.try_key(&ItemKey::Simple(comp_id))
1845 .map(|k| Content::Key(k.to_owned()))
1846 .unwrap_or_else(|| Content::Key("Component".to_owned())),
1849 );
1850
1851 (
1852 Content::WithFallback(b(name), b(title_fallback)),
1853 description,
1855 )
1856 } else {
1857 (name, description)
1858 }
1859 }
1860}
1861
1862impl ItemDesc for Item {
1863 fn legacy_name(&self) -> Cow<'_, str> {
1864 #[expect(deprecated)]
1865 self.legacy_name()
1866 }
1867
1868 fn kind(&self) -> Cow<'_, ItemKind> { self.kind() }
1869
1870 fn amount(&self) -> NonZeroU32 { self.amount }
1871
1872 fn quality(&self) -> Quality { self.quality() }
1873
1874 fn num_slots(&self) -> u16 { self.num_slots() }
1875
1876 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item_definition_id() }
1877
1878 fn tags(&self) -> Vec<ItemTag> { self.tags() }
1879
1880 fn is_modular(&self) -> bool { self.is_modular() }
1881
1882 fn components(&self) -> &[Item] { self.components() }
1883
1884 fn has_durability(&self) -> bool { self.has_durability() }
1885
1886 fn durability_lost(&self) -> Option<u32> { self.durability_lost() }
1887
1888 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1889 self.stats_durability_multiplier()
1890 }
1891}
1892
1893impl ItemDesc for FrontendItem {
1894 fn legacy_name(&self) -> Cow<'_, str> {
1895 #[expect(deprecated)]
1896 self.0.legacy_name()
1897 }
1898
1899 fn kind(&self) -> Cow<'_, ItemKind> { self.0.kind() }
1900
1901 fn amount(&self) -> NonZeroU32 { self.0.amount }
1902
1903 fn quality(&self) -> Quality { self.0.quality() }
1904
1905 fn num_slots(&self) -> u16 { self.0.num_slots() }
1906
1907 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.0.item_definition_id() }
1908
1909 fn tags(&self) -> Vec<ItemTag> { self.0.tags() }
1910
1911 fn is_modular(&self) -> bool { self.0.is_modular() }
1912
1913 fn components(&self) -> &[Item] { self.0.components() }
1914
1915 fn has_durability(&self) -> bool { self.0.has_durability() }
1916
1917 fn durability_lost(&self) -> Option<u32> { self.0.durability_lost() }
1918
1919 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1920 self.0.stats_durability_multiplier()
1921 }
1922}
1923
1924impl ItemDesc for ItemDef {
1925 fn legacy_name(&self) -> Cow<'_, str> {
1926 #[expect(deprecated)]
1927 Cow::Borrowed(&self.legacy_name)
1928 }
1929
1930 fn kind(&self) -> Cow<'_, ItemKind> { Cow::Borrowed(&self.kind) }
1931
1932 fn amount(&self) -> NonZeroU32 { NonZeroU32::new(1).unwrap() }
1933
1934 fn quality(&self) -> Quality { self.quality }
1935
1936 fn num_slots(&self) -> u16 { self.slots }
1937
1938 fn item_definition_id(&self) -> ItemDefinitionId<'_> {
1939 ItemDefinitionId::Simple(Cow::Borrowed(&self.item_definition_id))
1940 }
1941
1942 fn tags(&self) -> Vec<ItemTag> { self.tags.to_vec() }
1943
1944 fn is_modular(&self) -> bool { false }
1945
1946 fn components(&self) -> &[Item] { &[] }
1947
1948 fn has_durability(&self) -> bool {
1949 self.kind().has_durability() && self.quality != Quality::Debug
1950 }
1951
1952 fn durability_lost(&self) -> Option<u32> { None }
1953
1954 fn stats_durability_multiplier(&self) -> DurabilityMultiplier { DurabilityMultiplier(1.0) }
1955}
1956
1957impl ItemDesc for PickupItem {
1958 fn legacy_name(&self) -> Cow<'_, str> {
1959 #[expect(deprecated)]
1960 self.item().legacy_name()
1961 }
1962
1963 fn kind(&self) -> Cow<'_, ItemKind> { self.item().kind() }
1964
1965 fn amount(&self) -> NonZeroU32 {
1966 NonZeroU32::new(self.amount()).expect("Item having amount of 0 is invariant")
1967 }
1968
1969 fn quality(&self) -> Quality { self.item().quality() }
1970
1971 fn num_slots(&self) -> u16 { self.item().num_slots() }
1972
1973 fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item().item_definition_id() }
1974
1975 fn tags(&self) -> Vec<ItemTag> { self.item().tags() }
1976
1977 fn is_modular(&self) -> bool { self.item().is_modular() }
1978
1979 fn components(&self) -> &[Item] { self.item().components() }
1980
1981 fn has_durability(&self) -> bool { self.item().has_durability() }
1982
1983 fn durability_lost(&self) -> Option<u32> { self.item().durability_lost() }
1984
1985 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
1986 self.item().stats_durability_multiplier()
1987 }
1988}
1989
1990#[derive(Clone, Debug, Serialize, Deserialize)]
1991pub struct ItemDrops(pub Vec<(u32, Item)>);
1992
1993impl Component for ItemDrops {
1994 type Storage = DenseVecStorage<Self>;
1995}
1996
1997impl Component for PickupItem {
1998 type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
1999}
2000
2001impl Component for ThrownItem {
2002 type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
2003}
2004
2005#[derive(Copy, Clone, Debug)]
2006pub struct DurabilityMultiplier(pub f32);
2007
2008impl<T: ItemDesc + ?Sized> ItemDesc for &T {
2009 fn legacy_name(&self) -> Cow<'_, str> {
2010 #[expect(deprecated)]
2011 (*self).legacy_name()
2012 }
2013
2014 fn kind(&self) -> Cow<'_, ItemKind> { (*self).kind() }
2015
2016 fn amount(&self) -> NonZeroU32 { (*self).amount() }
2017
2018 fn quality(&self) -> Quality { (*self).quality() }
2019
2020 fn num_slots(&self) -> u16 { (*self).num_slots() }
2021
2022 fn item_definition_id(&self) -> ItemDefinitionId<'_> { (*self).item_definition_id() }
2023
2024 fn tags(&self) -> Vec<ItemTag> { (*self).tags() }
2025
2026 fn is_modular(&self) -> bool { (*self).is_modular() }
2027
2028 fn components(&self) -> &[Item] { (*self).components() }
2029
2030 fn has_durability(&self) -> bool { (*self).has_durability() }
2031
2032 fn durability_lost(&self) -> Option<u32> { (*self).durability_lost() }
2033
2034 fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
2035 (*self).stats_durability_multiplier()
2036 }
2037}
2038
2039pub fn all_item_defs_expect() -> Vec<String> {
2043 try_all_item_defs().expect("Failed to access items directory")
2044}
2045
2046pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
2048 let defs = assets::load_rec_dir::<Ron<RawItemDef>>("common.items")?;
2049 Ok(defs.read().ids().map(|id| id.to_string()).collect())
2050}
2051
2052pub fn all_items_expect() -> Vec<Item> {
2055 let defs = assets::load_rec_dir::<Ron<RawItemDef>>("common.items")
2056 .expect("failed to load item asset directory");
2057
2058 let mut asset_items: Vec<Item> = defs
2060 .read()
2061 .ids()
2062 .map(|id| Item::new_from_asset_expect(id))
2063 .collect();
2064
2065 let mut material_parse_table = HashMap::new();
2066 for mat in Material::iter() {
2067 if let Some(id) = mat.asset_identifier() {
2068 material_parse_table.insert(id.to_owned(), mat);
2069 }
2070 }
2071
2072 let primary_comp_pool = modular::PRIMARY_COMPONENT_POOL.clone();
2073
2074 let mut primary_comps: Vec<Item> = primary_comp_pool
2076 .values()
2077 .flatten()
2078 .map(|(item, _hand_rules)| item.clone())
2079 .collect();
2080
2081 let mut modular_items: Vec<Item> = primary_comp_pool
2083 .keys()
2084 .flat_map(|(tool, mat_id)| {
2085 let mat = material_parse_table
2086 .get(mat_id)
2087 .expect("unexpected material ident");
2088
2089 modular::generate_weapons(*tool, *mat, None)
2091 .expect("failure during modular weapon generation")
2092 })
2093 .collect();
2094
2095 let mut all = Vec::new();
2104 all.append(&mut asset_items);
2105 all.append(&mut primary_comps);
2106 all.append(&mut modular_items);
2107
2108 all
2109}
2110
2111impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2112 fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
2113 use ItemDefinitionId as DefId;
2114 match self {
2115 Self::Simple(simple) => {
2116 matches!(other, DefId::Simple(other_simple) if simple == other_simple)
2117 },
2118 Self::Modular {
2119 pseudo_base,
2120 components,
2121 } => matches!(
2122 other,
2123 DefId::Modular { pseudo_base: other_base, components: other_comps }
2124 if pseudo_base == other_base && components == other_comps
2125 ),
2126 Self::Compound {
2127 simple_base,
2128 components,
2129 } => matches!(
2130 other,
2131 DefId::Compound { simple_base: other_base, components: other_comps }
2132 if simple_base == other_base && components == other_comps
2133 ),
2134 }
2135 }
2136}
2137
2138impl PartialEq<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2139 #[inline]
2140 fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self }
2141}
2142
2143impl Equivalent<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
2144 fn equivalent(&self, key: &ItemDefinitionIdOwned) -> bool { self == key }
2145}
2146
2147impl From<&ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
2148 fn from(value: &ItemDefinitionId<'_>) -> Self { value.to_owned() }
2149}
2150
2151#[cfg(test)]
2152mod tests {
2153 use super::*;
2154 use hashbrown::HashSet;
2155
2156 #[test]
2157 fn test_assets_items() {
2158 let ids = all_item_defs_expect();
2159 for item in ids.iter().map(|id| Item::new_from_asset_expect(id)) {
2160 if let ItemKind::Consumable {
2161 container: Some(container),
2162 ..
2163 } = item.kind().as_ref()
2164 {
2165 Item::new_from_item_definition_id(
2166 container.as_ref(),
2167 &AbilityMap::load().read(),
2168 &MaterialStatManifest::load().read(),
2169 )
2170 .unwrap();
2171 }
2172 drop(item)
2173 }
2174 }
2175
2176 #[test]
2177 fn test_item_i18n() { let _ = ItemI18n::new_expect(); }
2178
2179 #[test]
2180 fn test_all_items() { let _ = all_items_expect(); }
2182
2183 #[test]
2184 fn ensure_item_localization() {
2187 let manifest = ItemI18n::new_expect();
2188 let items = all_items_expect();
2189 let mut errs = vec![];
2190 for item in items {
2191 let item_key: ItemKey = (&item).into();
2192 if manifest.item_text_opt(&item_key.clone()).is_none() {
2193 errs.push(item_key)
2194 }
2195 }
2196 if !errs.is_empty() {
2197 panic!("item i18n manifest misses translation-id for following items {errs:#?}")
2198 }
2199 }
2200
2201 #[test]
2202 fn ensure_modular_fragments() {
2205 let manifest = ItemI18n::new_expect();
2206 let items = all_items_expect();
2207 let mut errs = HashSet::new();
2208
2209 for item in items {
2210 let item_key: ItemKey = (&item).into();
2211 if let ItemKey::ModularWeapon((comp_id, ing_id, hands)) = item_key {
2212 if manifest
2213 .try_fragment(&FragmentKey::Ingredient(ing_id.clone()))
2214 .is_none()
2215 {
2216 errs.insert(FragmentKey::Ingredient(ing_id));
2217 }
2218 if manifest
2219 .try_fragment(&FragmentKey::WeaponPrimaryComponent(comp_id.clone(), hands))
2220 .is_none()
2221 {
2222 errs.insert(FragmentKey::WeaponPrimaryComponent(comp_id, hands));
2223 }
2224 }
2225 }
2226 if !errs.is_empty() {
2227 panic!("item i18n manifest missing fragment-id for following items {errs:#?}")
2228 }
2229 }
2230}