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