Refactored crafting to use ItemDef instead of Item

This commit is contained in:
Ben Wallis 2020-09-26 16:20:46 +01:00
parent 98c8240879
commit 11fc74642e
7 changed files with 75 additions and 53 deletions

View File

@ -119,13 +119,17 @@ pub struct Item {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ItemDef { pub struct ItemDef {
#[serde(skip)] #[serde(default)]
item_definition_id: String, item_definition_id: String,
pub name: String, pub name: String,
pub description: String, pub description: String,
pub kind: ItemKind, pub kind: ItemKind,
} }
impl PartialEq for ItemDef {
fn eq(&self, other: &Self) -> bool { self.item_definition_id == other.item_definition_id }
}
impl ItemDef { impl ItemDef {
pub fn is_stackable(&self) -> bool { pub fn is_stackable(&self) -> bool {
matches!(self.kind, ItemKind::Consumable { .. } 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 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 is_stackable(&self) -> bool { self.item_def.is_stackable() }
pub fn name(&self) -> &str { &self.item_def.name } pub fn name(&self) -> &str { &self.item_def.name }
@ -314,24 +322,30 @@ impl Item {
_ => return None, _ => return None,
})) }))
} }
}
/// Determines whether two items are superficially equivalent to one another /// Provides common methods providing details about an item definition
/// (i.e: one may be substituted for the other in crafting recipes or /// for either an `Item` containing the definition, or the actual `ItemDef`
/// item possession checks). pub trait ItemDesc {
pub fn superficially_eq(&self, other: &Self) -> bool { fn description(&self) -> &str;
match (&self.kind(), &other.kind()) { fn name(&self) -> &str;
(ItemKind::Tool(a), ItemKind::Tool(b)) => a.superficially_eq(b), fn kind(&self) -> &ItemKind;
// TODO: Differentiate between lantern colors? }
(ItemKind::Lantern(_), ItemKind::Lantern(_)) => true,
(ItemKind::Glider(_), ItemKind::Glider(_)) => true, impl ItemDesc for Item {
(ItemKind::Armor(a), ItemKind::Armor(b)) => a.superficially_eq(b), fn description(&self) -> &str { &self.item_def.description }
(ItemKind::Consumable { kind: a, .. }, ItemKind::Consumable { kind: b, .. }) => a == b,
(ItemKind::Throwable { kind: a, .. }, ItemKind::Throwable { kind: b, .. }) => a == b, fn name(&self) -> &str { &self.item_def.name }
(ItemKind::Utility { kind: a, .. }, ItemKind::Utility { kind: b, .. }) => a == b,
(ItemKind::Ingredient { kind: a, .. }, ItemKind::Ingredient { kind: b, .. }) => a == b, fn kind(&self) -> &ItemKind { &self.item_def.kind }
_ => false, }
}
} 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 { impl Component for Item {

View File

@ -1,7 +1,7 @@
pub mod item; pub mod item;
pub mod slot; pub mod slot;
use crate::recipe::Recipe; use crate::{comp::inventory::item::ItemDef, recipe::Recipe};
use core::ops::Not; use core::ops::Not;
use item::Item; use item::Item;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -211,11 +211,11 @@ impl Inventory {
} }
/// Determine how many of a particular item there is in the 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() self.slots()
.iter() .iter()
.flatten() .flatten()
.filter(|it| it.superficially_eq(item)) .filter(|it| it.is_same_item_def(item_def))
.map(|it| u64::from(it.amount())) .map(|it| u64::from(it.amount()))
.sum() .sum()
} }
@ -228,15 +228,15 @@ impl Inventory {
pub fn contains_ingredients<'a>( pub fn contains_ingredients<'a>(
&self, &self,
recipe: &'a Recipe, recipe: &'a Recipe,
) -> Result<Vec<u32>, Vec<(&'a Item, u32)>> { ) -> Result<Vec<u32>, Vec<(&'a ItemDef, u32)>> {
let mut slot_claims = vec![0; self.slots.len()]; 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() { for (input, mut needed) in recipe.inputs() {
let mut contains_any = false; let mut contains_any = false;
for (i, slot) in self.slots().iter().enumerate() { 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); let can_claim = (item.amount() - slot_claims[i]).min(needed);
slot_claims[i] += can_claim; slot_claims[i] += can_claim;
needed -= can_claim; needed -= can_claim;

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
assets::{self, Asset}, assets::{self, Asset},
comp::{Inventory, Item}, comp::{item::ItemDef, Inventory, Item},
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,14 +8,17 @@ use std::{fs::File, io::BufReader, sync::Arc};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Recipe { pub struct Recipe {
pub output: (Item, u32), pub output: (Arc<ItemDef>, u32),
pub inputs: Vec<(Item, u32)>, pub inputs: Vec<(Arc<ItemDef>, u32)>,
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
impl Recipe { impl Recipe {
/// Perform a recipe, returning a list of missing items on failure /// Perform a recipe, returning a list of missing items on failure
pub fn perform(&self, inv: &mut Inventory) -> Result<Option<(Item, u32)>, Vec<(&Item, u32)>> { pub fn perform(
&self,
inv: &mut Inventory,
) -> Result<Option<(Item, u32)>, Vec<(&ItemDef, u32)>> {
// Get ingredient cells from inventory, // Get ingredient cells from inventory,
inv.contains_ingredients(self)? inv.contains_ingredients(self)?
.into_iter() .into_iter()
@ -27,7 +30,8 @@ impl Recipe {
}); });
for i in 0..self.output.1 { 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))); return Ok(Some((item, self.output.1 - i)));
} }
} }
@ -35,8 +39,10 @@ impl Recipe {
Ok(None) Ok(None)
} }
pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&Item, u32)> { pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&Arc<ItemDef>, u32)> {
self.inputs.iter().map(|(item, amount)| (item, *amount)) self.inputs
.iter()
.map(|(item_def, amount)| (item_def, *amount))
} }
} }
@ -75,11 +81,11 @@ impl Asset for RecipeBook {
.map::<Result<(String, Recipe), assets::Error>, _>( .map::<Result<(String, Recipe), assets::Error>, _>(
|(name, ((output, amount), inputs))| { |(name, ((output, amount), inputs))| {
Ok((name, Recipe { Ok((name, Recipe {
output: (Item::new_from_asset(&output)?, amount), output: (ItemDef::load(&output)?, amount),
inputs: inputs inputs: inputs
.into_iter() .into_iter()
.map::<Result<(Item, u32), assets::Error>, _>( .map::<Result<(Arc<ItemDef>, u32), assets::Error>, _>(
|(name, amount)| Ok((Item::new_from_asset(&name)?, amount)), |(name, amount)| Ok((ItemDef::load(&name)?, amount)),
) )
.collect::<Result<_, _>>()?, .collect::<Result<_, _>>()?,
})) }))

View File

@ -8,7 +8,7 @@ use crate::{
ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, ui::{fonts::ConrodVoxygenFonts, ImageFrame, Tooltip, TooltipManager, Tooltipable},
}; };
use client::{self, Client}; use client::{self, Client};
use common::comp::Inventory; use common::comp::{item::ItemDesc, Inventory};
use conrod_core::{ use conrod_core::{
color, color,
widget::{self, Button, Image, Rectangle, Scrollbar, Text}, 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()); let output_text = format!("x{}", &recipe.output.1.to_string());
// Output Image // Output Image
let (title, desc) = super::util::item_text(&recipe.output.0); let (title, desc) = super::util::item_text(&*recipe.output.0);
Button::image( Button::image(
self.item_imgs 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) .w_h(55.0, 55.0)
.middle_of(state.ids.output_img_frame) .middle_of(state.ids.output_img_frame)
@ -362,9 +362,9 @@ impl<'a> Widget for Crafting<'a> {
}); });
}; };
// Widget generation for every ingredient // 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 // 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) { let col = if item_count_in_inventory >= u64::from(*amount) {
TEXT_COLOR TEXT_COLOR
} else { } else {
@ -393,8 +393,8 @@ impl<'a> Widget for Crafting<'a> {
}; };
frame.set(state.ids.ingredient_frame[i], ui); frame.set(state.ids.ingredient_frame[i], ui);
//Item Image //Item Image
let (title, desc) = super::util::item_text(&item); let (title, desc) = super::util::item_text(&**item_def);
Button::image(self.item_imgs.img_id_or_not_found_img(item.into())) Button::image(self.item_imgs.img_id_or_not_found_img((&*item_def.kind()).into()))
.w_h(22.0, 22.0) .w_h(22.0, 22.0)
.middle_of(state.ids.ingredient_frame[i]) .middle_of(state.ids.ingredient_frame[i])
//.image_color(col) //.image_color(col)
@ -414,7 +414,7 @@ impl<'a> Widget for Crafting<'a> {
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
.color(col) .color(col)
.set(state.ids.req_text[i], ui); .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) .right_from(state.ids.ingredient_frame[i], 10.0)
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))
@ -425,7 +425,7 @@ impl<'a> Widget for Crafting<'a> {
let input = format!( let input = format!(
"{}x {} ({})", "{}x {} ({})",
amount, amount,
&item.name(), &item_def.name(),
if item_count_in_inventory > 99 { if item_count_in_inventory > 99 {
over9k over9k
} else { } else {

View File

@ -4,7 +4,7 @@ use common::{
comp::item::{ comp::item::{
armor::{Armor, ArmorKind}, armor::{Armor, ArmorKind},
tool::{Tool, ToolKind}, tool::{Tool, ToolKind},
Glider, Item, ItemKind, Lantern, Throwable, Utility, Glider, ItemKind, Lantern, Throwable, Utility,
}, },
figure::Segment, figure::Segment,
}; };
@ -29,9 +29,10 @@ pub enum ItemKey {
Ingredient(String), Ingredient(String),
Empty, Empty,
} }
impl From<&Item> for ItemKey {
fn from(item: &Item) -> Self { impl From<&ItemKind> for ItemKey {
match &item.kind() { fn from(item_kind: &ItemKind) -> Self {
match item_kind {
ItemKind::Tool(Tool { kind, .. }) => ItemKey::Tool(kind.clone()), ItemKind::Tool(Tool { kind, .. }) => ItemKey::Tool(kind.clone()),
ItemKind::Lantern(Lantern { kind, .. }) => ItemKey::Lantern(kind.clone()), ItemKind::Lantern(Lantern { kind, .. }) => ItemKey::Lantern(kind.clone()),
ItemKind::Glider(Glider { kind, .. }) => ItemKey::Glider(kind.clone()), ItemKind::Glider(Glider { kind, .. }) => ItemKey::Glider(kind.clone()),

View File

@ -32,7 +32,7 @@ impl SlotKey<Inventory, ItemImgs> for InventorySlot {
type ImageKey = ItemKey; type ImageKey = ItemKey;
fn image_key(&self, source: &Inventory) -> Option<(Self::ImageKey, Option<Color>)> { fn image_key(&self, source: &Inventory) -> Option<(Self::ImageKey, Option<Color>)> {
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<u32> { fn amount(&self, source: &Inventory) -> Option<u32> {
@ -69,7 +69,7 @@ impl SlotKey<Loadout, ItemImgs> for EquipSlot {
EquipSlot::Glider => source.glider.as_ref(), EquipSlot::Glider => source.glider.as_ref(),
}; };
item.map(|i| (i.into(), None)) item.map(|i| (i.kind().into(), None))
} }
fn amount(&self, _: &Loadout) -> Option<u32> { None } fn amount(&self, _: &Loadout) -> Option<u32> { None }
@ -100,7 +100,7 @@ impl<'a> SlotKey<HotbarSource<'a>, HotbarImageSource<'a>> for HotbarSlot {
hotbar.get(*self).and_then(|contents| match contents { hotbar.get(*self).and_then(|contents| match contents {
hotbar::SlotContents::Inventory(idx) => inventory hotbar::SlotContents::Inventory(idx) => inventory
.get(idx) .get(idx)
.map(|item| HotbarImage::Item(item.into())) .map(|item| HotbarImage::Item(item.kind().into()))
.map(|i| (i, None)), .map(|i| (i, None)),
hotbar::SlotContents::Ability3 => loadout hotbar::SlotContents::Ability3 => loadout
.active_item .active_item

View File

@ -1,12 +1,12 @@
use common::comp::item::{ use common::comp::item::{
armor::{Armor, ArmorKind, Protection}, armor::{Armor, ArmorKind, Protection},
tool::{Tool, ToolKind}, tool::{Tool, ToolKind},
Item, ItemKind, ItemDesc, ItemKind,
}; };
use std::borrow::Cow; use std::borrow::Cow;
pub fn loadout_slot_text<'a>( pub fn loadout_slot_text<'a>(
item: Option<&'a Item>, item: Option<&'a impl ItemDesc>,
mut empty: impl FnMut() -> (&'a str, &'a str), mut empty: impl FnMut() -> (&'a str, &'a str),
) -> (&'a str, Cow<'a, str>) { ) -> (&'a str, Cow<'a, str>) {
item.map_or_else( 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<str> = match item.kind() { let desc: Cow<str> = match item.kind() {
ItemKind::Armor(armor) => Cow::Owned(armor_desc(&armor, item.description())), ItemKind::Armor(armor) => Cow::Owned(armor_desc(&armor, item.description())),
ItemKind::Tool(tool) => Cow::Owned(tool_desc(&tool, 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) (item.name(), desc)
} }
// Armor Description // Armor Description
fn armor_desc(armor: &Armor, desc: &str) -> String { fn armor_desc(armor: &Armor, desc: &str) -> String {
// TODO: localization // TODO: localization