diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index a3e912af18..fd92e7840a 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -119,13 +119,17 @@ pub struct Item { #[derive(Debug, Serialize, Deserialize)] pub struct ItemDef { - #[serde(skip)] + #[serde(default)] item_definition_id: String, pub name: String, pub description: String, pub kind: ItemKind, } +impl PartialEq for ItemDef { + fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id } +} + impl ItemDef { pub fn is_stackable(&self) -> bool { matches!(self.kind, ItemKind::Consumable { .. } @@ -264,6 +268,10 @@ impl Item { pub fn item_definition_id(&self) -> &str { &self.item_def.item_definition_id } + pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool { + self.item_def.item_definition_id == item_def.item_definition_id + } + pub fn is_stackable(&self) -> bool { self.item_def.is_stackable() } pub fn name(&self) -> &str { &self.item_def.name } @@ -314,24 +322,30 @@ impl Item { _ => return None, })) } +} - /// Determines whether two items are superficially equivalent to one another - /// (i.e: one may be substituted for the other in crafting recipes or - /// item possession checks). - pub fn superficially_eq(&self, other: &Self) -> bool { - match (&self.kind(), &other.kind()) { - (ItemKind::Tool(a), ItemKind::Tool(b)) => a.superficially_eq(b), - // TODO: Differentiate between lantern colors? - (ItemKind::Lantern(_), ItemKind::Lantern(_)) => true, - (ItemKind::Glider(_), ItemKind::Glider(_)) => true, - (ItemKind::Armor(a), ItemKind::Armor(b)) => a.superficially_eq(b), - (ItemKind::Consumable { kind: a, .. }, ItemKind::Consumable { kind: b, .. }) => a == b, - (ItemKind::Throwable { kind: a, .. }, ItemKind::Throwable { kind: b, .. }) => a == b, - (ItemKind::Utility { kind: a, .. }, ItemKind::Utility { kind: b, .. }) => a == b, - (ItemKind::Ingredient { kind: a, .. }, ItemKind::Ingredient { kind: b, .. }) => a == b, - _ => false, - } - } +/// Provides common methods providing details about an item definition +/// for either an `Item` containing the definition, or the actual `ItemDef` +pub trait ItemDesc { + fn description(&self) -> &str; + fn name(&self) -> &str; + fn kind(&self) -> &ItemKind; +} + +impl ItemDesc for Item { + fn description(&self) -> &str { &self.item_def.description } + + fn name(&self) -> &str { &self.item_def.name } + + fn kind(&self) -> &ItemKind { &self.item_def.kind } +} + +impl ItemDesc for ItemDef { + fn description(&self) -> &str { &self.description } + + fn name(&self) -> &str { &self.name } + + fn kind(&self) -> &ItemKind { &self.kind } } impl Component for Item { diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 25a26a4aca..d57f268d4a 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -1,7 +1,7 @@ pub mod item; pub mod slot; -use crate::recipe::Recipe; +use crate::{comp::inventory::item::ItemDef, recipe::Recipe}; use core::ops::Not; use item::Item; use serde::{Deserialize, Serialize}; @@ -211,11 +211,11 @@ impl Inventory { } /// Determine how many of a particular item there is in the inventory. - pub fn item_count(&self, item: &Item) -> u64 { + pub fn item_count(&self, item_def: &ItemDef) -> u64 { self.slots() .iter() .flatten() - .filter(|it| it.superficially_eq(item)) + .filter(|it| it.is_same_item_def(item_def)) .map(|it| u64::from(it.amount())) .sum() } @@ -228,15 +228,15 @@ impl Inventory { pub fn contains_ingredients<'a>( &self, recipe: &'a Recipe, - ) -> Result, Vec<(&'a Item, u32)>> { + ) -> Result, Vec<(&'a ItemDef, u32)>> { let mut slot_claims = vec![0; self.slots.len()]; - let mut missing = Vec::new(); + let mut missing = Vec::<(&ItemDef, u32)>::new(); for (input, mut needed) in recipe.inputs() { let mut contains_any = false; for (i, slot) in self.slots().iter().enumerate() { - if let Some(item) = slot.as_ref().filter(|item| item.superficially_eq(input)) { + if let Some(item) = slot.as_ref().filter(|item| item.is_same_item_def(&*input)) { let can_claim = (item.amount() - slot_claims[i]).min(needed); slot_claims[i] += can_claim; needed -= can_claim; diff --git a/common/src/recipe.rs b/common/src/recipe.rs index 76663022ca..e6aa0936e0 100644 --- a/common/src/recipe.rs +++ b/common/src/recipe.rs @@ -1,6 +1,6 @@ use crate::{ assets::{self, Asset}, - comp::{Inventory, Item}, + comp::{item::ItemDef, Inventory, Item}, }; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; @@ -8,14 +8,17 @@ use std::{fs::File, io::BufReader, sync::Arc}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Recipe { - pub output: (Item, u32), - pub inputs: Vec<(Item, u32)>, + pub output: (Arc, u32), + pub inputs: Vec<(Arc, u32)>, } #[allow(clippy::type_complexity)] impl Recipe { /// Perform a recipe, returning a list of missing items on failure - pub fn perform(&self, inv: &mut Inventory) -> Result, Vec<(&Item, u32)>> { + pub fn perform( + &self, + inv: &mut Inventory, + ) -> Result, Vec<(&ItemDef, u32)>> { // Get ingredient cells from inventory, inv.contains_ingredients(self)? .into_iter() @@ -27,7 +30,8 @@ impl Recipe { }); for i in 0..self.output.1 { - if let Some(item) = inv.push(self.output.0.duplicate()) { + let crafted_item = Item::new(Arc::clone(&self.output.0)); + if let Some(item) = inv.push(crafted_item) { return Ok(Some((item, self.output.1 - i))); } } @@ -35,8 +39,10 @@ impl Recipe { Ok(None) } - pub fn inputs(&self) -> impl ExactSizeIterator { - self.inputs.iter().map(|(item, amount)| (item, *amount)) + pub fn inputs(&self) -> impl ExactSizeIterator, u32)> { + self.inputs + .iter() + .map(|(item_def, amount)| (item_def, *amount)) } } @@ -75,11 +81,11 @@ impl Asset for RecipeBook { .map::, _>( |(name, ((output, amount), inputs))| { Ok((name, Recipe { - output: (Item::new_from_asset(&output)?, amount), + output: (ItemDef::load(&output)?, amount), inputs: inputs .into_iter() - .map::, _>( - |(name, amount)| Ok((Item::new_from_asset(&name)?, amount)), + .map::, u32), assets::Error>, _>( + |(name, amount)| Ok((ItemDef::load(&name)?, amount)), ) .collect::>()?, })) diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs index 6535bc6deb..23077964d6 100644 --- a/voxygen/src/hud/crafting.rs +++ b/voxygen/src/hud/crafting.rs @@ -8,7 +8,7 @@ use crate::{ ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, }; use client::{self, Client}; -use common::comp::Inventory; +use common::comp::{item::ItemDesc, Inventory}; use conrod_core::{ color, widget::{self, Button, Image, Rectangle, Scrollbar, Text}, @@ -247,10 +247,10 @@ impl<'a> Widget for Crafting<'a> { { let output_text = format!("x{}", &recipe.output.1.to_string()); // Output Image - let (title, desc) = super::util::item_text(&recipe.output.0); + let (title, desc) = super::util::item_text(&*recipe.output.0); Button::image( self.item_imgs - .img_id_or_not_found_img((&recipe.output.0).into()), + .img_id_or_not_found_img((&*recipe.output.0.kind()).into()), ) .w_h(55.0, 55.0) .middle_of(state.ids.output_img_frame) @@ -362,9 +362,9 @@ impl<'a> Widget for Crafting<'a> { }); }; // Widget generation for every ingredient - for (i, (item, amount)) in recipe.inputs.iter().enumerate() { + for (i, (item_def, amount)) in recipe.inputs.iter().enumerate() { // Grey color for images and text if their amount is too low to craft the item - let item_count_in_inventory = self.inventory.item_count(item); + let item_count_in_inventory = self.inventory.item_count(item_def); let col = if item_count_in_inventory >= u64::from(*amount) { TEXT_COLOR } else { @@ -393,8 +393,8 @@ impl<'a> Widget for Crafting<'a> { }; frame.set(state.ids.ingredient_frame[i], ui); //Item Image - let (title, desc) = super::util::item_text(&item); - Button::image(self.item_imgs.img_id_or_not_found_img(item.into())) + let (title, desc) = super::util::item_text(&**item_def); + Button::image(self.item_imgs.img_id_or_not_found_img((&*item_def.kind()).into())) .w_h(22.0, 22.0) .middle_of(state.ids.ingredient_frame[i]) //.image_color(col) @@ -414,7 +414,7 @@ impl<'a> Widget for Crafting<'a> { .font_size(self.fonts.cyri.scale(14)) .color(col) .set(state.ids.req_text[i], ui); - Text::new(&item.name()) + Text::new(&item_def.name()) .right_from(state.ids.ingredient_frame[i], 10.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) @@ -425,7 +425,7 @@ impl<'a> Widget for Crafting<'a> { let input = format!( "{}x {} ({})", amount, - &item.name(), + &item_def.name(), if item_count_in_inventory > 99 { over9k } else { diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index 1d15a24e5d..12de1f50cf 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -4,7 +4,7 @@ use common::{ comp::item::{ armor::{Armor, ArmorKind}, tool::{Tool, ToolKind}, - Glider, Item, ItemKind, Lantern, Throwable, Utility, + Glider, ItemKind, Lantern, Throwable, Utility, }, figure::Segment, }; @@ -29,9 +29,10 @@ pub enum ItemKey { Ingredient(String), Empty, } -impl From<&Item> for ItemKey { - fn from(item: &Item) -> Self { - match &item.kind() { + +impl From<&ItemKind> for ItemKey { + fn from(item_kind: &ItemKind) -> Self { + match item_kind { ItemKind::Tool(Tool { kind, .. }) => ItemKey::Tool(kind.clone()), ItemKind::Lantern(Lantern { kind, .. }) => ItemKey::Lantern(kind.clone()), ItemKind::Glider(Glider { kind, .. }) => ItemKey::Glider(kind.clone()), diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 33b6c6efc4..f365cf8087 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -32,7 +32,7 @@ impl SlotKey for InventorySlot { type ImageKey = ItemKey; fn image_key(&self, source: &Inventory) -> Option<(Self::ImageKey, Option)> { - source.get(self.0).map(|i| (i.into(), None)) + source.get(self.0).map(|i| (i.kind().into(), None)) } fn amount(&self, source: &Inventory) -> Option { @@ -69,7 +69,7 @@ impl SlotKey for EquipSlot { EquipSlot::Glider => source.glider.as_ref(), }; - item.map(|i| (i.into(), None)) + item.map(|i| (i.kind().into(), None)) } fn amount(&self, _: &Loadout) -> Option { None } @@ -100,7 +100,7 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { hotbar.get(*self).and_then(|contents| match contents { hotbar::SlotContents::Inventory(idx) => inventory .get(idx) - .map(|item| HotbarImage::Item(item.into())) + .map(|item| HotbarImage::Item(item.kind().into())) .map(|i| (i, None)), hotbar::SlotContents::Ability3 => loadout .active_item diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index 8b23ca3d6c..612d42a888 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -1,12 +1,12 @@ use common::comp::item::{ armor::{Armor, ArmorKind, Protection}, tool::{Tool, ToolKind}, - Item, ItemKind, + ItemDesc, ItemKind, }; use std::borrow::Cow; pub fn loadout_slot_text<'a>( - item: Option<&'a Item>, + item: Option<&'a impl ItemDesc>, mut empty: impl FnMut() -> (&'a str, &'a str), ) -> (&'a str, Cow<'a, str>) { item.map_or_else( @@ -18,7 +18,7 @@ pub fn loadout_slot_text<'a>( ) } -pub fn item_text<'a>(item: &'a Item) -> (&'_ str, Cow<'a, str>) { +pub fn item_text<'a>(item: &'a impl ItemDesc) -> (&'_ str, Cow<'a, str>) { let desc: Cow = match item.kind() { ItemKind::Armor(armor) => Cow::Owned(armor_desc(&armor, item.description())), ItemKind::Tool(tool) => Cow::Owned(tool_desc(&tool, item.description())), @@ -34,6 +34,7 @@ pub fn item_text<'a>(item: &'a Item) -> (&'_ str, Cow<'a, str>) { (item.name(), desc) } + // Armor Description fn armor_desc(armor: &Armor, desc: &str) -> String { // TODO: localization