pub mod armor;
pub mod item_key;
pub mod modular;
pub mod tool;
pub use modular::{MaterialStatManifest, ModularBase, ModularComponent};
pub use tool::{AbilityMap, AbilitySet, AbilitySpec, Hands, Tool, ToolKind};
use crate::{
assets::{self, AssetExt, BoxedError, Error},
comp::inventory::InvSlot,
effect::Effect,
recipe::RecipeInput,
resources::ProgramTime,
terrain::Block,
};
use common_i18n::Content;
use core::{
convert::TryFrom,
mem,
num::{NonZeroU32, NonZeroU64},
};
use crossbeam_utils::atomic::AtomicCell;
use hashbrown::{Equivalent, HashMap};
use item_key::ItemKey;
use serde::{de, Deserialize, Serialize, Serializer};
use specs::{Component, DenseVecStorage, DerefFlaggedStorage};
use std::{borrow::Cow, collections::hash_map::DefaultHasher, fmt, sync::Arc};
use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
use tracing::error;
use vek::Rgb;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Throwable {
Bomb,
SurpriseEgg,
TrainingDummy,
Firework(Reagent),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, strum::EnumString)]
pub enum Reagent {
Blue,
Green,
Purple,
Red,
White,
Yellow,
Phoenix,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Utility {
Coins,
Collar,
Key,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Lantern {
color: Rgb<u32>,
strength_thousandths: u32,
flicker_thousandths: u32,
}
impl Lantern {
pub fn strength(&self) -> f32 { self.strength_thousandths as f32 / 1000_f32 }
pub fn color(&self) -> Rgb<f32> { self.color.map(|c| c as f32 / 255.0) }
pub fn flicker(&self) -> f32 { self.flicker_thousandths as f32 / 1000_f32 }
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
pub enum Quality {
Low, Common, Moderate, High, Epic, Legendary, Artifact, Debug, }
impl Quality {
pub const MIN: Self = Self::Low;
}
pub trait TagExampleInfo {
fn name(&self) -> &str;
fn exemplar_identifier(&self) -> Option<&str>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr)]
pub enum MaterialKind {
Metal,
Gem,
Wood,
Stone,
Cloth,
Hide,
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
IntoStaticStr,
EnumString,
EnumIter,
)]
#[strum(serialize_all = "snake_case")]
pub enum Material {
Bronze,
Iron,
Steel,
Cobalt,
Bloodsteel,
Silver,
Gold,
Orichalcum,
Topaz,
Emerald,
Sapphire,
Amethyst,
Ruby,
Diamond,
Twig,
PlantFiber,
Wood,
Bamboo,
Hardwood,
Ironwood,
Frostwood,
Eldwood,
Rock,
Granite,
Bone,
Basalt,
Obsidian,
Velorite,
Linen,
RedLinen,
Wool,
Silk,
Lifecloth,
Moonweave,
Sunsilk,
Rawhide,
Leather,
RigidLeather,
Scale,
Carapace,
Plate,
Dragonscale,
}
impl Material {
pub fn material_kind(&self) -> MaterialKind {
match self {
Material::Bronze
| Material::Iron
| Material::Steel
| Material::Cobalt
| Material::Bloodsteel
| Material::Silver
| Material::Gold
| Material::Orichalcum => MaterialKind::Metal,
Material::Topaz
| Material::Emerald
| Material::Sapphire
| Material::Amethyst
| Material::Ruby
| Material::Diamond => MaterialKind::Gem,
Material::Wood
| Material::Twig
| Material::PlantFiber
| Material::Bamboo
| Material::Hardwood
| Material::Ironwood
| Material::Frostwood
| Material::Eldwood => MaterialKind::Wood,
Material::Rock
| Material::Granite
| Material::Bone
| Material::Basalt
| Material::Obsidian
| Material::Velorite => MaterialKind::Stone,
Material::Linen
| Material::RedLinen
| Material::Wool
| Material::Silk
| Material::Lifecloth
| Material::Moonweave
| Material::Sunsilk => MaterialKind::Cloth,
Material::Rawhide
| Material::Leather
| Material::RigidLeather
| Material::Scale
| Material::Carapace
| Material::Plate
| Material::Dragonscale => MaterialKind::Hide,
}
}
pub fn asset_identifier(&self) -> Option<&'static str> {
match self {
Material::Bronze => Some("common.items.mineral.ingot.bronze"),
Material::Iron => Some("common.items.mineral.ingot.iron"),
Material::Steel => Some("common.items.mineral.ingot.steel"),
Material::Cobalt => Some("common.items.mineral.ingot.cobalt"),
Material::Bloodsteel => Some("common.items.mineral.ingot.bloodsteel"),
Material::Silver => Some("common.items.mineral.ingot.silver"),
Material::Gold => Some("common.items.mineral.ingot.gold"),
Material::Orichalcum => Some("common.items.mineral.ingot.orichalcum"),
Material::Topaz => Some("common.items.mineral.gem.topaz"),
Material::Emerald => Some("common.items.mineral.gem.emerald"),
Material::Sapphire => Some("common.items.mineral.gem.sapphire"),
Material::Amethyst => Some("common.items.mineral.gem.amethyst"),
Material::Ruby => Some("common.items.mineral.gem.ruby"),
Material::Diamond => Some("common.items.mineral.gem.diamond"),
Material::Twig => Some("common.items.crafting_ing.twigs"),
Material::PlantFiber => Some("common.items.flowers.plant_fiber"),
Material::Wood => Some("common.items.log.wood"),
Material::Bamboo => Some("common.items.log.bamboo"),
Material::Hardwood => Some("common.items.log.hardwood"),
Material::Ironwood => Some("common.items.log.ironwood"),
Material::Frostwood => Some("common.items.log.frostwood"),
Material::Eldwood => Some("common.items.log.eldwood"),
Material::Rock
| Material::Granite
| Material::Bone
| Material::Basalt
| Material::Obsidian
| Material::Velorite => None,
Material::Linen => Some("common.items.crafting_ing.cloth.linen"),
Material::RedLinen => Some("common.items.crafting_ing.cloth.linen_red"),
Material::Wool => Some("common.items.crafting_ing.cloth.wool"),
Material::Silk => Some("common.items.crafting_ing.cloth.silk"),
Material::Lifecloth => Some("common.items.crafting_ing.cloth.lifecloth"),
Material::Moonweave => Some("common.items.crafting_ing.cloth.moonweave"),
Material::Sunsilk => Some("common.items.crafting_ing.cloth.sunsilk"),
Material::Rawhide => Some("common.items.crafting_ing.leather.simple_leather"),
Material::Leather => Some("common.items.crafting_ing.leather.thick_leather"),
Material::RigidLeather => Some("common.items.crafting_ing.leather.rigid_leather"),
Material::Scale => Some("common.items.crafting_ing.hide.scales"),
Material::Carapace => Some("common.items.crafting_ing.hide.carapace"),
Material::Plate => Some("common.items.crafting_ing.hide.plate"),
Material::Dragonscale => Some("common.items.crafting_ing.hide.dragon_scale"),
}
}
}
impl TagExampleInfo for Material {
fn name(&self) -> &str { self.into() }
fn exemplar_identifier(&self) -> Option<&str> { self.asset_identifier() }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ItemTag {
Material(Material),
MaterialKind(MaterialKind),
Cultist,
Gnarling,
Potion,
Charm,
Food,
BaseMaterial, CraftingTool, Utility,
Bag,
SalvageInto(Material, u32),
}
impl TagExampleInfo for ItemTag {
fn name(&self) -> &str {
match self {
ItemTag::Material(material) => material.name(),
ItemTag::MaterialKind(material_kind) => material_kind.into(),
ItemTag::Cultist => "cultist",
ItemTag::Gnarling => "gnarling",
ItemTag::Potion => "potion",
ItemTag::Charm => "charm",
ItemTag::Food => "food",
ItemTag::BaseMaterial => "basemat",
ItemTag::CraftingTool => "tool",
ItemTag::Utility => "utility",
ItemTag::Bag => "bag",
ItemTag::SalvageInto(_, _) => "salvage",
}
}
fn exemplar_identifier(&self) -> Option<&str> {
match self {
ItemTag::Material(material) => material.exemplar_identifier(),
ItemTag::Cultist => Some("common.items.tag_examples.cultist"),
ItemTag::Gnarling => Some("common.items.tag_examples.gnarling"),
ItemTag::MaterialKind(_)
| ItemTag::Potion
| ItemTag::Food
| ItemTag::Charm
| ItemTag::BaseMaterial
| ItemTag::CraftingTool
| ItemTag::Utility
| ItemTag::Bag
| ItemTag::SalvageInto(_, _) => None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Effects {
Any(Vec<Effect>),
All(Vec<Effect>),
One(Effect),
}
impl Effects {
pub fn effects(&self) -> &[Effect] {
match self {
Effects::Any(effects) => effects,
Effects::All(effects) => effects,
Effects::One(effect) => std::slice::from_ref(effect),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum ItemKind {
Tool(Tool),
ModularComponent(ModularComponent),
Lantern(Lantern),
Armor(armor::Armor),
Glider,
Consumable {
kind: ConsumableKind,
effects: Effects,
},
Throwable {
kind: Throwable,
},
Utility {
kind: Utility,
},
Ingredient {
#[deprecated = "part of non-localized name generation"]
descriptor: String,
},
TagExamples {
item_ids: Vec<String>,
},
RecipeGroup {
recipes: Vec<String>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ConsumableKind {
Drink,
Food,
ComplexFood,
Charm,
Recipe,
}
impl ItemKind {
pub fn is_equippable(&self) -> bool {
matches!(
self,
ItemKind::Tool(_) | ItemKind::Armor { .. } | ItemKind::Glider | ItemKind::Lantern(_)
)
}
pub fn get_itemkind_string(&self) -> String {
match self {
ItemKind::Tool(tool) => format!("Tool: {:?}", tool.kind),
ItemKind::ModularComponent(modular_component) => {
format!("ModularComponent: {:?}", modular_component.toolkind())
},
ItemKind::Lantern(lantern) => format!("Lantern: {:?}", lantern),
ItemKind::Armor(armor) => format!("Armor: {:?}", armor.stats),
ItemKind::Glider => "Glider:".to_string(),
ItemKind::Consumable { kind, .. } => {
format!("Consumable: {:?}", kind)
},
ItemKind::Throwable { kind } => format!("Throwable: {:?}", kind),
ItemKind::Utility { kind } => format!("Utility: {:?}", kind),
#[allow(deprecated)]
ItemKind::Ingredient { descriptor } => format!("Ingredient: {}", descriptor),
ItemKind::TagExamples { item_ids } => format!("TagExamples: {:?}", item_ids),
ItemKind::RecipeGroup { .. } => String::from("Recipes:"),
}
}
pub fn has_durability(&self) -> bool {
match self {
ItemKind::Tool(_) => true,
ItemKind::Armor(armor) => armor.kind.has_durability(),
ItemKind::ModularComponent(_)
| ItemKind::Lantern(_)
| ItemKind::Glider
| ItemKind::Consumable { .. }
| ItemKind::Throwable { .. }
| ItemKind::Utility { .. }
| ItemKind::Ingredient { .. }
| ItemKind::TagExamples { .. }
| ItemKind::RecipeGroup { .. } => false,
}
}
}
pub type ItemId = AtomicCell<Option<NonZeroU64>>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Item {
#[serde(skip)]
item_id: Arc<ItemId>,
item_base: ItemBase,
components: Vec<Item>,
amount: NonZeroU32,
slots: Vec<InvSlot>,
item_config: Option<Box<ItemConfig>>,
hash: u64,
durability_lost: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FrontendItem(Item);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PickupItem {
items: Vec<Item>,
created_at: ProgramTime,
next_merge_check: ProgramTime,
}
use std::hash::{Hash, Hasher};
impl Hash for Item {
fn hash<H: Hasher>(&self, state: &mut H) {
self.item_definition_id().hash(state);
self.components.iter().for_each(|comp| comp.hash(state));
}
}
type I18nId = String;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ItemI18n {
map: HashMap<ItemKey, I18nId>,
}
impl assets::Asset for ItemI18n {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
impl ItemI18n {
pub fn new_expect() -> Self {
ItemI18n::load_expect("common.item_i18n_manifest")
.read()
.clone()
}
fn item_text_opt(&self, mut item_key: ItemKey) -> Option<(Content, Content)> {
if let ItemKey::TagExamples(_, id) = item_key {
item_key = ItemKey::Simple(id.to_string());
}
let key = self.map.get(&item_key);
key.map(|key| {
(
Content::Key(key.to_owned()),
Content::Attr(key.to_owned(), "desc".to_owned()),
)
})
}
}
#[derive(Clone, Debug)]
pub enum ItemBase {
Simple(Arc<ItemDef>),
Modular(ModularBase),
}
impl Serialize for ItemBase {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.serialization_item_id())
}
}
impl<'de> Deserialize<'de> for ItemBase {
fn deserialize<D>(deserializer: D) -> Result<ItemBase, D::Error>
where
D: de::Deserializer<'de>,
{
struct ItemBaseStringVisitor;
impl<'de> de::Visitor<'de> for ItemBaseStringVisitor {
type Value = ItemBase;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("item def string")
}
fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
ItemBase::from_item_id_string(serialized_item_base)
.map_err(|err| E::custom(err.to_string()))
}
}
deserializer.deserialize_str(ItemBaseStringVisitor)
}
}
impl ItemBase {
fn num_slots(&self) -> u16 {
match self {
ItemBase::Simple(item_def) => item_def.num_slots(),
ItemBase::Modular(_) => 0,
}
}
fn serialization_item_id(&self) -> String {
match &self {
ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
}
}
fn from_item_id_string(item_id_string: &str) -> Result<Self, Error> {
if item_id_string.starts_with(crate::modular_item_id_prefix!()) {
Ok(ItemBase::Modular(ModularBase::load_from_pseudo_id(
item_id_string,
)))
} else {
Ok(ItemBase::Simple(Arc::<ItemDef>::load_cloned(
item_id_string,
)?))
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ItemDefinitionId<'a> {
Simple(Cow<'a, str>),
Modular {
pseudo_base: &'a str,
components: Vec<ItemDefinitionId<'a>>,
},
Compound {
simple_base: &'a str,
components: Vec<ItemDefinitionId<'a>>,
},
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum ItemDefinitionIdOwned {
Simple(String),
Modular {
pseudo_base: String,
components: Vec<ItemDefinitionIdOwned>,
},
Compound {
simple_base: String,
components: Vec<ItemDefinitionIdOwned>,
},
}
impl ItemDefinitionIdOwned {
pub fn as_ref(&self) -> ItemDefinitionId<'_> {
match *self {
Self::Simple(ref id) => ItemDefinitionId::Simple(Cow::Borrowed(id)),
Self::Modular {
ref pseudo_base,
ref components,
} => ItemDefinitionId::Modular {
pseudo_base,
components: components.iter().map(|comp| comp.as_ref()).collect(),
},
Self::Compound {
ref simple_base,
ref components,
} => ItemDefinitionId::Compound {
simple_base,
components: components.iter().map(|comp| comp.as_ref()).collect(),
},
}
}
}
impl<'a> ItemDefinitionId<'a> {
pub fn itemdef_id(&self) -> Option<&str> {
match self {
Self::Simple(id) => Some(id),
Self::Modular { .. } => None,
Self::Compound { simple_base, .. } => Some(simple_base),
}
}
pub fn to_owned(&self) -> ItemDefinitionIdOwned {
match self {
Self::Simple(id) => ItemDefinitionIdOwned::Simple(String::from(&**id)),
Self::Modular {
pseudo_base,
components,
} => ItemDefinitionIdOwned::Modular {
pseudo_base: String::from(*pseudo_base),
components: components.iter().map(|comp| comp.to_owned()).collect(),
},
Self::Compound {
simple_base,
components,
} => ItemDefinitionIdOwned::Compound {
simple_base: String::from(*simple_base),
components: components.iter().map(|comp| comp.to_owned()).collect(),
},
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ItemDef {
#[serde(default)]
item_definition_id: String,
#[deprecated = "since item i18n"]
name: String,
#[deprecated = "since item i18n"]
description: String,
pub kind: ItemKind,
pub quality: Quality,
pub tags: Vec<ItemTag>,
#[serde(default)]
pub slots: u16,
pub ability_spec: Option<AbilitySpec>,
}
impl PartialEq for ItemDef {
fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ItemConfig {
pub abilities: AbilitySet<tool::AbilityItem>,
}
#[derive(Debug)]
pub enum ItemConfigError {
BadItemKind,
}
impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
type Error = ItemConfigError;
fn try_from(
(item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
) -> Result<Self, Self::Error> {
match &*item.kind() {
ItemKind::Tool(tool) => {
let tool_default = |tool_kind| {
let key = &AbilitySpec::Tool(tool_kind);
ability_map.get_ability_set(key)
};
let abilities = if let Some(set_key) = item.ability_spec() {
if let Some(set) = ability_map.get_ability_set(&set_key) {
set.clone()
.modified_by_tool(tool, item.stats_durability_multiplier())
} else {
error!(
"Custom ability set: {:?} references non-existent set, falling back \
to default ability set.",
set_key
);
tool_default(tool.kind).cloned().unwrap_or_default()
}
} else if let Some(set) = tool_default(tool.kind) {
set.clone()
.modified_by_tool(tool, item.stats_durability_multiplier())
} else {
error!(
"No ability set defined for tool: {:?}, falling back to default ability \
set.",
tool.kind
);
Default::default()
};
Ok(ItemConfig { abilities })
},
ItemKind::Glider => item
.ability_spec()
.and_then(|set_key| ability_map.get_ability_set(&set_key))
.map(|abilities| ItemConfig {
abilities: abilities.clone(),
})
.ok_or(ItemConfigError::BadItemKind),
_ => Err(ItemConfigError::BadItemKind),
}
}
}
impl ItemDef {
pub fn is_stackable(&self) -> bool {
matches!(
self.kind,
ItemKind::Consumable { .. }
| ItemKind::Ingredient { .. }
| ItemKind::Throwable { .. }
| ItemKind::Utility { .. }
)
}
pub fn id(&self) -> &str { &self.item_definition_id }
#[cfg(test)]
pub fn new_test(
item_definition_id: String,
kind: ItemKind,
quality: Quality,
tags: Vec<ItemTag>,
slots: u16,
) -> Self {
#[allow(deprecated)]
Self {
item_definition_id,
name: "test item name".to_owned(),
description: "test item description".to_owned(),
kind,
quality,
tags,
slots,
ability_spec: None,
}
}
#[cfg(test)]
pub fn create_test_itemdef_from_kind(kind: ItemKind) -> Self {
#[allow(deprecated)]
Self {
item_definition_id: "test.item".to_string(),
name: "test item name".to_owned(),
description: "test item description".to_owned(),
kind,
quality: Quality::Common,
tags: vec![],
slots: 0,
ability_spec: None,
}
}
}
impl PartialEq for Item {
fn eq(&self, other: &Self) -> bool {
(match (&self.item_base, &other.item_base) {
(ItemBase::Simple(our_def), ItemBase::Simple(other_def)) => {
our_def.item_definition_id == other_def.item_definition_id
},
(ItemBase::Modular(our_base), ItemBase::Modular(other_base)) => our_base == other_base,
_ => false,
}) && self.components() == other.components()
}
}
impl assets::Compound for ItemDef {
fn load(cache: assets::AnyCache, specifier: &assets::SharedString) -> Result<Self, BoxedError> {
if specifier.starts_with("veloren.core.") {
return Err(format!(
"Attempted to load an asset from a specifier reserved for core veloren functions. \
Specifier: {}",
specifier
)
.into());
}
let RawItemDef {
legacy_name,
legacy_description,
kind,
quality,
tags,
slots,
ability_spec,
} = cache.load::<RawItemDef>(specifier)?.cloned();
let item_definition_id = specifier.replace('\\', ".");
#[allow(deprecated)]
Ok(ItemDef {
item_definition_id,
name: legacy_name,
description: legacy_description,
kind,
quality,
tags,
slots,
ability_spec,
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename = "ItemDef", deny_unknown_fields)]
struct RawItemDef {
legacy_name: String,
legacy_description: String,
kind: ItemKind,
quality: Quality,
tags: Vec<ItemTag>,
#[serde(default)]
slots: u16,
ability_spec: Option<AbilitySpec>,
}
impl assets::Asset for RawItemDef {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
#[derive(Debug)]
pub struct OperationFailure;
impl Item {
pub const MAX_DURABILITY: u32 = 12;
pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
pub fn new_from_item_base(
inner_item: ItemBase,
components: Vec<Item>,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Self {
let mut item = Item {
item_id: Arc::new(AtomicCell::new(None)),
amount: NonZeroU32::new(1).unwrap(),
components,
slots: vec![None; inner_item.num_slots() as usize],
item_base: inner_item,
item_config: None,
hash: 0,
durability_lost: None,
};
item.durability_lost = item.has_durability().then_some(0);
item.update_item_state(ability_map, msm);
item
}
pub fn new_from_item_definition_id(
item_definition_id: ItemDefinitionId<'_>,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<Self, Error> {
let (base, components) = match item_definition_id {
ItemDefinitionId::Simple(spec) => {
let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(&spec)?);
(base, Vec::new())
},
ItemDefinitionId::Modular {
pseudo_base,
components,
} => {
let base = ItemBase::Modular(ModularBase::load_from_pseudo_id(pseudo_base));
let components = components
.into_iter()
.map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
.collect::<Result<Vec<_>, _>>()?;
(base, components)
},
ItemDefinitionId::Compound {
simple_base,
components,
} => {
let base = ItemBase::Simple(Arc::<ItemDef>::load_cloned(simple_base)?);
let components = components
.into_iter()
.map(|id| Item::new_from_item_definition_id(id, ability_map, msm))
.collect::<Result<Vec<_>, _>>()?;
(base, components)
},
};
Ok(Item::new_from_item_base(base, components, ability_map, msm))
}
pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
Item::new_from_asset(asset_specifier).unwrap_or_else(|err| {
panic!(
"Expected asset to exist: {}, instead got error {:?}",
asset_specifier, err
);
})
}
pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
let specifier = asset_glob.strip_suffix(".*").unwrap_or(asset_glob);
let defs = assets::load_rec_dir::<RawItemDef>(specifier)?;
defs.read()
.ids()
.map(|id| Item::new_from_asset(id))
.collect()
}
pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
let inner_item = ItemBase::from_item_id_string(asset)?;
let msm = &MaterialStatManifest::load().read();
let ability_map = &AbilityMap::load().read();
Ok(Item::new_from_item_base(
inner_item,
Vec::new(),
ability_map,
msm,
))
}
#[must_use]
pub fn frontend_item(
&self,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> FrontendItem {
FrontendItem(self.duplicate(ability_map, msm))
}
#[must_use]
pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
let duplicated_components = self
.components
.iter()
.map(|comp| comp.duplicate(ability_map, msm))
.collect();
let mut new_item = Item::new_from_item_base(
match &self.item_base {
ItemBase::Simple(item_def) => ItemBase::Simple(Arc::clone(item_def)),
ItemBase::Modular(mod_base) => ItemBase::Modular(mod_base.clone()),
},
duplicated_components,
ability_map,
msm,
);
new_item.set_amount(self.amount()).expect(
"`new_item` has the same `item_def` and as an invariant, \
self.set_amount(self.amount()) should always succeed.",
);
new_item.slots_mut().iter_mut().zip(self.slots()).for_each(
|(new_item_slot, old_item_slot)| {
*new_item_slot = old_item_slot
.as_ref()
.map(|old_item| old_item.duplicate(ability_map, msm));
},
);
new_item
}
pub fn stacked_duplicates<'a>(
&'a self,
ability_map: &'a AbilityMap,
msm: &'a MaterialStatManifest,
count: u32,
) -> impl Iterator<Item = Self> + 'a {
let max_stack_count = count / self.max_amount();
let rest = count % self.max_amount();
(0..max_stack_count)
.map(|_| {
let mut item = self.duplicate(ability_map, msm);
item.set_amount(item.max_amount())
.expect("max_amount() is always a valid amount.");
item
})
.chain((rest > 0).then(move || {
let mut item = self.duplicate(ability_map, msm);
item.set_amount(rest)
.expect("anything less than max_amount() is always a valid amount.");
item
}))
}
#[doc(hidden)]
pub fn get_item_id_for_database(&self) -> Arc<ItemId> { Arc::clone(&self.item_id) }
fn reset_item_id(&mut self) {
if let Some(item_id) = Arc::get_mut(&mut self.item_id) {
*item_id = AtomicCell::new(None);
} else {
self.item_id = Arc::new(AtomicCell::new(None));
}
for component in self.components.iter_mut() {
component.reset_item_id();
}
}
pub fn put_in_world(&mut self) { self.reset_item_id() }
pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), OperationFailure> {
let amount = u32::from(self.amount);
self.amount = amount
.checked_add(increase_by)
.filter(|&amount| amount <= self.max_amount())
.and_then(NonZeroU32::new)
.ok_or(OperationFailure)?;
Ok(())
}
pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), OperationFailure> {
let amount = u32::from(self.amount);
self.amount = amount
.checked_sub(decrease_by)
.and_then(NonZeroU32::new)
.ok_or(OperationFailure)?;
Ok(())
}
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), OperationFailure> {
if give_amount <= self.max_amount() {
self.amount = NonZeroU32::new(give_amount).ok_or(OperationFailure)?;
Ok(())
} else {
Err(OperationFailure)
}
}
pub fn persistence_access_add_component(&mut self, component: Item) {
self.components.push(component);
}
pub fn persistence_access_mutable_component(&mut self, index: usize) -> Option<&mut Self> {
self.components.get_mut(index)
}
pub fn update_item_state(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
if let Ok(item_config) = ItemConfig::try_from((&*self, ability_map, msm)) {
self.item_config = Some(Box::new(item_config));
}
self.hash = {
let mut s = DefaultHasher::new();
self.hash(&mut s);
s.finish()
};
}
pub fn drain(&mut self) -> impl Iterator<Item = Item> + '_ {
self.slots.iter_mut().filter_map(mem::take)
}
pub fn item_definition_id(&self) -> ItemDefinitionId<'_> {
match &self.item_base {
ItemBase::Simple(item_def) => {
if self.components.is_empty() {
ItemDefinitionId::Simple(Cow::Borrowed(&item_def.item_definition_id))
} else {
ItemDefinitionId::Compound {
simple_base: &item_def.item_definition_id,
components: self
.components
.iter()
.map(|item| item.item_definition_id())
.collect(),
}
}
},
ItemBase::Modular(mod_base) => ItemDefinitionId::Modular {
pseudo_base: mod_base.pseudo_item_id(),
components: self
.components
.iter()
.map(|item| item.item_definition_id())
.collect(),
},
}
}
pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
if let ItemBase::Simple(self_def) = &self.item_base {
self_def.item_definition_id == item_def.item_definition_id
} else {
false
}
}
pub fn matches_recipe_input(&self, recipe_input: &RecipeInput, amount: u32) -> bool {
match recipe_input {
RecipeInput::Item(item_def) => self.is_same_item_def(item_def),
RecipeInput::Tag(tag) => self.tags().contains(tag),
RecipeInput::TagSameItem(tag) => {
self.tags().contains(tag) && u32::from(self.amount) >= amount
},
RecipeInput::ListSameItem(item_defs) => item_defs.iter().any(|item_def| {
self.is_same_item_def(item_def) && u32::from(self.amount) >= amount
}),
}
}
pub fn is_salvageable(&self) -> bool {
self.tags()
.iter()
.any(|tag| matches!(tag, ItemTag::SalvageInto(_, _)))
}
pub fn salvage_output(&self) -> impl Iterator<Item = (&str, u32)> {
self.tags().into_iter().filter_map(|tag| {
if let ItemTag::SalvageInto(material, quantity) = tag {
material
.asset_identifier()
.map(|material_id| (material_id, quantity))
} else {
None
}
})
}
#[deprecated = "since item i18n"]
pub fn name(&self) -> Cow<str> {
match &self.item_base {
ItemBase::Simple(item_def) => {
if self.components.is_empty() {
#[allow(deprecated)]
Cow::Borrowed(&item_def.name)
} else {
#[allow(deprecated)]
modular::modify_name(&item_def.name, self)
}
},
ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()),
}
}
#[deprecated = "since item i18n"]
pub fn description(&self) -> &str {
match &self.item_base {
#[allow(deprecated)]
ItemBase::Simple(item_def) => &item_def.description,
ItemBase::Modular(_) => "",
}
}
pub fn kind(&self) -> Cow<ItemKind> {
match &self.item_base {
ItemBase::Simple(item_def) => Cow::Borrowed(&item_def.kind),
ItemBase::Modular(mod_base) => {
let msm = MaterialStatManifest::load().read();
mod_base.kind(self.components(), &msm, self.stats_durability_multiplier())
},
}
}
pub fn amount(&self) -> u32 { u32::from(self.amount) }
pub fn is_stackable(&self) -> bool {
match &self.item_base {
ItemBase::Simple(item_def) => item_def.is_stackable(),
ItemBase::Modular(_) => false,
}
}
pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
pub fn max_amount(&self) -> u32 { if self.is_stackable() { u32::MAX } else { 1 } }
pub fn quality(&self) -> Quality {
match &self.item_base {
ItemBase::Simple(item_def) => item_def.quality.max(
self.components
.iter()
.fold(Quality::MIN, |a, b| a.max(b.quality())),
),
ItemBase::Modular(mod_base) => mod_base.compute_quality(self.components()),
}
}
pub fn components(&self) -> &[Item] { &self.components }
pub fn slots(&self) -> &[InvSlot] { &self.slots }
pub fn slots_mut(&mut self) -> &mut [InvSlot] { &mut self.slots }
pub fn item_config(&self) -> Option<&ItemConfig> { self.item_config.as_deref() }
pub fn free_slots(&self) -> usize { self.slots.iter().filter(|x| x.is_none()).count() }
pub fn populated_slots(&self) -> usize { self.slots().len().saturating_sub(self.free_slots()) }
pub fn slot(&self, slot: usize) -> Option<&InvSlot> { self.slots.get(slot) }
pub fn slot_mut(&mut self, slot: usize) -> Option<&mut InvSlot> { self.slots.get_mut(slot) }
pub fn try_reclaim_from_block(block: Block) -> Option<Vec<(u32, Self)>> {
block.get_sprite()?.collectible_id()??.to_items()
}
pub fn ability_spec(&self) -> Option<Cow<AbilitySpec>> {
match &self.item_base {
ItemBase::Simple(item_def) => {
item_def.ability_spec.as_ref().map(Cow::Borrowed).or({
if let ItemKind::Tool(tool) = &item_def.kind {
Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
} else {
None
}
})
},
ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
}
}
pub fn tags(&self) -> Vec<ItemTag> {
match &self.item_base {
ItemBase::Simple(item_def) => item_def.tags.to_vec(),
ItemBase::Modular(mod_base) => mod_base.generate_tags(self.components()),
}
}
pub fn is_modular(&self) -> bool {
match &self.item_base {
ItemBase::Simple(_) => false,
ItemBase::Modular(_) => true,
}
}
pub fn item_hash(&self) -> u64 { self.hash }
pub fn persistence_item_id(&self) -> String {
match &self.item_base {
ItemBase::Simple(item_def) => item_def.item_definition_id.clone(),
ItemBase::Modular(mod_base) => String::from(mod_base.pseudo_item_id()),
}
}
pub fn durability_lost(&self) -> Option<u32> {
self.durability_lost.map(|x| x.min(Self::MAX_DURABILITY))
}
pub fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
let durability_lost = self.durability_lost.unwrap_or(0);
debug_assert!(durability_lost <= Self::MAX_DURABILITY);
const DURABILITY_THRESHOLD: u32 = 9;
const MIN_FRAC: f32 = 0.25;
let mult = (1.0
- durability_lost.saturating_sub(DURABILITY_THRESHOLD) as f32
/ (Self::MAX_DURABILITY - DURABILITY_THRESHOLD) as f32)
* (1.0 - MIN_FRAC)
+ MIN_FRAC;
DurabilityMultiplier(mult)
}
pub fn has_durability(&self) -> bool {
self.kind().has_durability() && self.quality() != Quality::Debug
}
pub fn increment_damage(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
if let Some(durability_lost) = &mut self.durability_lost {
if *durability_lost < Self::MAX_DURABILITY {
*durability_lost += 1;
}
}
self.update_item_state(ability_map, msm);
}
pub fn persistence_durability(&self) -> Option<NonZeroU32> {
self.durability_lost.and_then(NonZeroU32::new)
}
pub fn persistence_set_durability(&mut self, value: Option<NonZeroU32>) {
if !self.has_durability() {
self.durability_lost = None;
} else {
self.durability_lost = Some(value.map_or(0, NonZeroU32::get));
}
}
pub fn reset_durability(&mut self, ability_map: &AbilityMap, msm: &MaterialStatManifest) {
self.durability_lost = self.has_durability().then_some(0);
self.update_item_state(ability_map, msm);
}
#[must_use = "Returned items will be lost if not used"]
pub fn take_half(
&mut self,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Option<Item> {
if self.is_stackable() && self.amount() > 1 {
let mut return_item = self.duplicate(ability_map, msm);
let returning_amount = self.amount() / 2;
self.decrease_amount(returning_amount).ok()?;
return_item.set_amount(returning_amount).expect(
"return_item.amount() = self.amount() / 2 < self.amount() (since self.amount() ≥ \
1) ≤ self.max_amount() = return_item.max_amount(), since return_item is a \
duplicate of item",
);
Some(return_item)
} else {
None
}
}
#[cfg(test)]
pub fn create_test_item_from_kind(kind: ItemKind) -> Self {
let ability_map = &AbilityMap::load().read();
let msm = &MaterialStatManifest::load().read();
Self::new_from_item_base(
ItemBase::Simple(Arc::new(ItemDef::create_test_itemdef_from_kind(kind))),
Vec::new(),
ability_map,
msm,
)
}
pub fn can_merge(&self, other: &Self) -> bool {
if self.amount() > self.max_amount() || other.amount() > other.max_amount() {
error!("An item amount is over max_amount!");
return false;
}
(self == other)
&& self.slots().iter().all(Option::is_none)
&& other.slots().iter().all(Option::is_none)
&& self.durability_lost() == other.durability_lost()
}
pub fn try_merge(&mut self, mut other: Self) -> Result<Option<Self>, Self> {
if self.can_merge(&other) {
let max_amount = self.max_amount();
debug_assert_eq!(
max_amount,
other.max_amount(),
"Mergeable items must have the same max_amount()"
);
let to_fill_self = max_amount
.checked_sub(self.amount())
.expect("can_merge should ensure that amount() <= max_amount()");
if let Some(remainder) = other.amount().checked_sub(to_fill_self).filter(|r| *r > 0) {
self.set_amount(max_amount)
.expect("max_amount() is always a valid amount.");
other.set_amount(remainder).expect(
"We know remainder is more than 0 and less than or equal to max_amount()",
);
Ok(Some(other))
} else {
self.increase_amount(other.amount())
.expect("We know that we can at least add other.amount() to this item");
drop(other);
Ok(None)
}
} else {
Err(other)
}
}
pub fn persistence_item_base(&self) -> &ItemBase { &self.item_base }
}
impl FrontendItem {
#[must_use]
pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
FrontendItem(self.0.duplicate(ability_map, msm))
}
pub fn set_amount(&mut self, amount: u32) -> Result<(), OperationFailure> {
self.0.set_amount(amount)
}
}
impl PickupItem {
pub fn new(item: Item, time: ProgramTime) -> Self {
Self {
items: vec![item],
created_at: time,
next_merge_check: time,
}
}
pub fn item(&self) -> &Item {
self.items
.last()
.expect("PickupItem without at least one item is an invariant")
}
pub fn created(&self) -> ProgramTime { self.created_at }
pub fn next_merge_check(&self) -> ProgramTime { self.next_merge_check }
pub fn next_merge_check_mut(&mut self) -> &mut ProgramTime { &mut self.next_merge_check }
pub fn amount(&self) -> u32 {
self.items
.iter()
.map(Item::amount)
.fold(0, |total, amount| total.saturating_add(amount))
}
pub fn remove_debug_items(&mut self) {
for item in self.items.iter_mut() {
item.slots_mut().iter_mut().for_each(|container_slot| {
container_slot
.take_if(|contained_item| matches!(contained_item.quality(), Quality::Debug));
});
}
}
pub fn can_merge(&self, other: &PickupItem) -> bool {
let self_item = self.item();
let other_item = other.item();
self_item.can_merge(other_item)
}
pub fn try_merge(&mut self, mut other: PickupItem) -> Result<(), PickupItem> {
if self.can_merge(&other) {
let mut self_last = self
.items
.pop()
.expect("PickupItem without at least one item is an invariant");
let other_last = other
.items
.pop()
.expect("PickupItem without at least one item is an invariant");
let merged = self_last
.try_merge(other_last)
.expect("We know these items can be merged");
debug_assert!(
other
.items
.iter()
.chain(self.items.iter())
.all(|item| item.amount() == item.max_amount()),
"All items before the last in `PickupItem` should have a full amount"
);
self.items.append(&mut other.items);
debug_assert!(
merged.is_none() || self_last.amount() == self_last.max_amount(),
"Merged can only be `Some` if the origin was set to `max_amount()`"
);
self.items.push(self_last);
if let Some(remainder) = merged {
self.items.push(remainder);
}
Ok(())
} else {
Err(other)
}
}
pub fn pick_up(mut self) -> (Item, Option<Self>) {
(
self.items
.pop()
.expect("PickupItem without at least one item is an invariant"),
(!self.items.is_empty()).then_some(self),
)
}
}
pub fn flatten_counted_items<'a>(
items: &'a [(u32, Item)],
ability_map: &'a AbilityMap,
msm: &'a MaterialStatManifest,
) -> impl Iterator<Item = Item> + 'a {
items
.iter()
.flat_map(|(count, item)| item.stacked_duplicates(ability_map, msm, *count))
}
pub trait ItemDesc {
#[deprecated = "since item i18n"]
fn description(&self) -> &str;
#[deprecated = "since item i18n"]
fn name(&self) -> Cow<str>;
fn kind(&self) -> Cow<ItemKind>;
fn amount(&self) -> NonZeroU32;
fn quality(&self) -> Quality;
fn num_slots(&self) -> u16;
fn item_definition_id(&self) -> ItemDefinitionId<'_>;
fn tags(&self) -> Vec<ItemTag>;
fn is_modular(&self) -> bool;
fn components(&self) -> &[Item];
fn has_durability(&self) -> bool;
fn durability_lost(&self) -> Option<u32>;
fn stats_durability_multiplier(&self) -> DurabilityMultiplier;
fn tool_info(&self) -> Option<ToolKind> {
if let ItemKind::Tool(tool) = &*self.kind() {
Some(tool.kind)
} else {
None
}
}
fn i18n(&self, i18n: &ItemI18n) -> (Content, Content) {
let item_key: ItemKey = self.into();
#[allow(deprecated)]
i18n.item_text_opt(item_key).unwrap_or_else(|| {
(
Content::Plain(self.name().to_string()),
Content::Plain(self.description().to_string()),
)
})
}
}
impl ItemDesc for Item {
fn description(&self) -> &str {
#[allow(deprecated)]
self.description()
}
fn name(&self) -> Cow<str> {
#[allow(deprecated)]
self.name()
}
fn kind(&self) -> Cow<ItemKind> { self.kind() }
fn amount(&self) -> NonZeroU32 { self.amount }
fn quality(&self) -> Quality { self.quality() }
fn num_slots(&self) -> u16 { self.num_slots() }
fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item_definition_id() }
fn tags(&self) -> Vec<ItemTag> { self.tags() }
fn is_modular(&self) -> bool { self.is_modular() }
fn components(&self) -> &[Item] { self.components() }
fn has_durability(&self) -> bool { self.has_durability() }
fn durability_lost(&self) -> Option<u32> { self.durability_lost() }
fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
self.stats_durability_multiplier()
}
}
impl ItemDesc for FrontendItem {
fn description(&self) -> &str {
#[allow(deprecated)]
self.0.description()
}
fn name(&self) -> Cow<str> {
#[allow(deprecated)]
self.0.name()
}
fn kind(&self) -> Cow<ItemKind> { self.0.kind() }
fn amount(&self) -> NonZeroU32 { self.0.amount }
fn quality(&self) -> Quality { self.0.quality() }
fn num_slots(&self) -> u16 { self.0.num_slots() }
fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.0.item_definition_id() }
fn tags(&self) -> Vec<ItemTag> { self.0.tags() }
fn is_modular(&self) -> bool { self.0.is_modular() }
fn components(&self) -> &[Item] { self.0.components() }
fn has_durability(&self) -> bool { self.0.has_durability() }
fn durability_lost(&self) -> Option<u32> { self.0.durability_lost() }
fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
self.0.stats_durability_multiplier()
}
}
impl ItemDesc for ItemDef {
fn description(&self) -> &str {
#[allow(deprecated)]
&self.description
}
fn name(&self) -> Cow<str> {
#[allow(deprecated)]
Cow::Borrowed(&self.name)
}
fn kind(&self) -> Cow<ItemKind> { Cow::Borrowed(&self.kind) }
fn amount(&self) -> NonZeroU32 { NonZeroU32::new(1).unwrap() }
fn quality(&self) -> Quality { self.quality }
fn num_slots(&self) -> u16 { self.slots }
fn item_definition_id(&self) -> ItemDefinitionId<'_> {
ItemDefinitionId::Simple(Cow::Borrowed(&self.item_definition_id))
}
fn tags(&self) -> Vec<ItemTag> { self.tags.to_vec() }
fn is_modular(&self) -> bool { false }
fn components(&self) -> &[Item] { &[] }
fn has_durability(&self) -> bool {
self.kind().has_durability() && self.quality != Quality::Debug
}
fn durability_lost(&self) -> Option<u32> { None }
fn stats_durability_multiplier(&self) -> DurabilityMultiplier { DurabilityMultiplier(1.0) }
}
impl ItemDesc for PickupItem {
fn description(&self) -> &str {
#[allow(deprecated)]
self.item().description()
}
fn name(&self) -> Cow<str> {
#[allow(deprecated)]
self.item().name()
}
fn kind(&self) -> Cow<ItemKind> { self.item().kind() }
fn amount(&self) -> NonZeroU32 {
NonZeroU32::new(self.amount()).expect("Item having amount of 0 is invariant")
}
fn quality(&self) -> Quality { self.item().quality() }
fn num_slots(&self) -> u16 { self.item().num_slots() }
fn item_definition_id(&self) -> ItemDefinitionId<'_> { self.item().item_definition_id() }
fn tags(&self) -> Vec<ItemTag> { self.item().tags() }
fn is_modular(&self) -> bool { self.item().is_modular() }
fn components(&self) -> &[Item] { self.item().components() }
fn has_durability(&self) -> bool { self.item().has_durability() }
fn durability_lost(&self) -> Option<u32> { self.item().durability_lost() }
fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
self.item().stats_durability_multiplier()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ItemDrops(pub Vec<(u32, Item)>);
impl Component for ItemDrops {
type Storage = DenseVecStorage<Self>;
}
impl Component for PickupItem {
type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
}
#[derive(Copy, Clone, Debug)]
pub struct DurabilityMultiplier(pub f32);
impl<'a, T: ItemDesc + ?Sized> ItemDesc for &'a T {
fn description(&self) -> &str {
#[allow(deprecated)]
(*self).description()
}
fn name(&self) -> Cow<str> {
#[allow(deprecated)]
(*self).name()
}
fn kind(&self) -> Cow<ItemKind> { (*self).kind() }
fn amount(&self) -> NonZeroU32 { (*self).amount() }
fn quality(&self) -> Quality { (*self).quality() }
fn num_slots(&self) -> u16 { (*self).num_slots() }
fn item_definition_id(&self) -> ItemDefinitionId<'_> { (*self).item_definition_id() }
fn tags(&self) -> Vec<ItemTag> { (*self).tags() }
fn is_modular(&self) -> bool { (*self).is_modular() }
fn components(&self) -> &[Item] { (*self).components() }
fn has_durability(&self) -> bool { (*self).has_durability() }
fn durability_lost(&self) -> Option<u32> { (*self).durability_lost() }
fn stats_durability_multiplier(&self) -> DurabilityMultiplier {
(*self).stats_durability_multiplier()
}
}
pub fn all_item_defs_expect() -> Vec<String> {
try_all_item_defs().expect("Failed to access items directory")
}
pub fn try_all_item_defs() -> Result<Vec<String>, Error> {
let defs = assets::load_rec_dir::<RawItemDef>("common.items")?;
Ok(defs.read().ids().map(|id| id.to_string()).collect())
}
pub fn all_items_expect() -> Vec<Item> {
let defs = assets::load_rec_dir::<RawItemDef>("common.items")
.expect("failed to load item asset directory");
let mut asset_items: Vec<Item> = defs
.read()
.ids()
.map(|id| Item::new_from_asset_expect(id))
.collect();
let mut material_parse_table = HashMap::new();
for mat in Material::iter() {
if let Some(id) = mat.asset_identifier() {
material_parse_table.insert(id.to_owned(), mat);
}
}
let primary_comp_pool = modular::PRIMARY_COMPONENT_POOL.clone();
let mut primary_comps: Vec<Item> = primary_comp_pool
.values()
.flatten()
.map(|(item, _hand_rules)| item.clone())
.collect();
let mut modular_items: Vec<Item> = primary_comp_pool
.keys()
.flat_map(|(tool, mat_id)| {
let mat = material_parse_table
.get(mat_id)
.expect("unexpected material ident");
modular::generate_weapons(*tool, *mat, None)
.expect("failure during modular weapon generation")
})
.collect();
let mut all = Vec::new();
all.append(&mut asset_items);
all.append(&mut primary_comps);
all.append(&mut modular_items);
all
}
impl PartialEq<ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
fn eq(&self, other: &ItemDefinitionId<'_>) -> bool {
use ItemDefinitionId as DefId;
match self {
Self::Simple(simple) => {
matches!(other, DefId::Simple(other_simple) if simple == other_simple)
},
Self::Modular {
pseudo_base,
components,
} => matches!(
other,
DefId::Modular { pseudo_base: other_base, components: other_comps }
if pseudo_base == other_base && components == other_comps
),
Self::Compound {
simple_base,
components,
} => matches!(
other,
DefId::Compound { simple_base: other_base, components: other_comps }
if simple_base == other_base && components == other_comps
),
}
}
}
impl PartialEq<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
#[inline]
fn eq(&self, other: &ItemDefinitionIdOwned) -> bool { other == self }
}
impl Equivalent<ItemDefinitionIdOwned> for ItemDefinitionId<'_> {
fn equivalent(&self, key: &ItemDefinitionIdOwned) -> bool { self == key }
}
impl From<&ItemDefinitionId<'_>> for ItemDefinitionIdOwned {
fn from(value: &ItemDefinitionId<'_>) -> Self { value.to_owned() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assets_items() {
let ids = all_item_defs_expect();
for item in ids.iter().map(|id| Item::new_from_asset_expect(id)) {
drop(item)
}
}
#[test]
fn test_item_i18n() { let _ = ItemI18n::new_expect(); }
#[test]
fn test_all_items() { let _ = all_items_expect(); }
#[test]
fn ensure_item_localization() {
let manifest = ItemI18n::new_expect();
let items = all_items_expect();
let mut errs = vec![];
for item in items {
let item_key: ItemKey = (&item).into();
if manifest.item_text_opt(item_key.clone()).is_none() {
errs.push(item_key)
}
}
if !errs.is_empty() {
panic!("item i18n manifest misses translation-id for following items {errs:#?}")
}
}
}