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)]
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 {

View File

@ -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<u32>, Vec<(&'a Item, u32)>> {
) -> Result<Vec<u32>, 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;

View File

@ -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<ItemDef>, u32),
pub inputs: Vec<(Arc<ItemDef>, 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<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,
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<Item = (&Item, u32)> {
self.inputs.iter().map(|(item, amount)| (item, *amount))
pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&Arc<ItemDef>, u32)> {
self.inputs
.iter()
.map(|(item_def, amount)| (item_def, *amount))
}
}
@ -75,11 +81,11 @@ impl Asset for RecipeBook {
.map::<Result<(String, Recipe), assets::Error>, _>(
|(name, ((output, amount), inputs))| {
Ok((name, Recipe {
output: (Item::new_from_asset(&output)?, amount),
output: (ItemDef::load(&output)?, amount),
inputs: inputs
.into_iter()
.map::<Result<(Item, u32), assets::Error>, _>(
|(name, amount)| Ok((Item::new_from_asset(&name)?, amount)),
.map::<Result<(Arc<ItemDef>, u32), assets::Error>, _>(
|(name, amount)| Ok((ItemDef::load(&name)?, amount)),
)
.collect::<Result<_, _>>()?,
}))

View File

@ -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 {

View File

@ -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()),

View File

@ -32,7 +32,7 @@ impl SlotKey<Inventory, ItemImgs> for InventorySlot {
type ImageKey = ItemKey;
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> {
@ -69,7 +69,7 @@ impl SlotKey<Loadout, ItemImgs> 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<u32> { None }
@ -100,7 +100,7 @@ impl<'a> SlotKey<HotbarSource<'a>, 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

View File

@ -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<str> = 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