From 794b072d3e120a9624b4bde400b72fee225232c2 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 6 Oct 2021 18:18:12 -0400 Subject: [PATCH] Simple recipes now take a vec of slots to look in for the ingredients. --- client/src/lib.rs | 8 +- common/src/comp/controller.rs | 5 +- common/src/comp/inventory/mod.rs | 45 +--------- common/src/recipe.rs | 120 +++++++++++++++++++++------ server/src/events/inventory_manip.rs | 12 +-- voxygen/src/session/mod.rs | 17 +++- 6 files changed, 124 insertions(+), 83 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 0cf679117c..794ace1d55 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -988,7 +988,7 @@ impl Client { .zip(self.inventories().get(self.entity())) .map(|(recipe, inv)| { ( - inv.contains_ingredients(&*recipe).is_ok(), + recipe.inventory_contains_ingredients(inv).is_ok(), recipe.craft_sprite, ) }) @@ -998,6 +998,7 @@ impl Client { pub fn craft_recipe( &mut self, recipe: &str, + slots: Vec, craft_sprite: Option<(Vec3, SpriteKind)>, ) -> bool { let (can_craft, required_sprite) = self.can_craft_recipe(recipe); @@ -1005,7 +1006,10 @@ impl Client { if can_craft && has_sprite { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( InventoryEvent::CraftRecipe { - craft_event: CraftEvent::Simple(recipe.to_string()), + craft_event: CraftEvent::Simple { + recipe: recipe.to_string(), + slots, + }, craft_sprite: craft_sprite.map(|(pos, _)| pos), }, ))); diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index 61e2af20ab..a12fa2b404 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -92,7 +92,10 @@ impl From for InventoryManip { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum CraftEvent { - Simple(String), + Simple { + recipe: String, + slots: Vec, + }, Salvage(InvSlotId), } diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 76ea19e12b..a1347a4dd5 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -2,7 +2,7 @@ use core::ops::Not; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; use specs_idvs::IdvStorage; -use std::{collections::HashMap, convert::TryFrom, mem, ops::Range}; +use std::{convert::TryFrom, mem, ops::Range}; use tracing::{debug, trace, warn}; use vek::Vec3; @@ -16,7 +16,6 @@ use crate::{ slot::{InvSlotId, SlotId}, Item, }, - recipe::{Recipe, RecipeInput}, uid::Uid, LoadoutBuilder, }; @@ -460,48 +459,6 @@ impl Inventory { .sum() } - /// Determine whether the inventory contains the ingredients for a recipe. - /// If it does, return a vector of numbers, where is number corresponds - /// to an inventory slot, along with the number of items that need - /// removing from it. It items are missing, return the missing items, and - /// how many are missing. - pub fn contains_ingredients<'a>( - &self, - recipe: &'a Recipe, - ) -> Result, Vec<(&'a RecipeInput, u32)>> { - let mut slot_claims = HashMap::::new(); - let mut missing = Vec::<(&RecipeInput, u32)>::new(); - - for (input, mut needed) in recipe.inputs() { - let mut contains_any = false; - - for (inv_slot_id, slot) in self.slots_with_id() { - if let Some(item) = slot - .as_ref() - .filter(|item| item.matches_recipe_input(&*input)) - { - let claim = slot_claims.entry(inv_slot_id).or_insert(0); - // FIXME: Fishy, looks like it can underflow before min which can trigger an - // overflow check. - let can_claim = (item.amount() - *claim).min(needed); - *claim += can_claim; - needed -= can_claim; - contains_any = true; - } - } - - if needed > 0 || !contains_any { - missing.push((input, needed)); - } - } - - if missing.is_empty() { - Ok(slot_claims) - } else { - Err(missing) - } - } - /// Adds a new item to the first empty slot of the inventory. Returns the /// item again in an Err if no free slot was found, otherwise returns a /// reference to the item. diff --git a/common/src/recipe.rs b/common/src/recipe.rs index b0c5f84df3..2ceea34c11 100644 --- a/common/src/recipe.rs +++ b/common/src/recipe.rs @@ -27,38 +27,60 @@ pub struct Recipe { #[allow(clippy::type_complexity)] impl Recipe { /// Perform a recipe, returning a list of missing items on failure - pub fn perform( + pub fn craft_simple( &self, inv: &mut Inventory, + slots: Vec, ability_map: &AbilityMap, msm: &MaterialStatManifest, - ) -> Result<(Item, u32), Vec<(&RecipeInput, u32)>> { - // Get ingredient cells from inventory, - let mut components = Vec::new(); + ) -> Result, Vec<(&RecipeInput, u32)>> { + let mut recipe_inputs = Vec::new(); + let mut unsatisfied_requirements = Vec::new(); - inv.contains_ingredients(self)? - .into_iter() - .for_each(|(pos, n)| { - (0..n).for_each(|_| { - let component = inv - .take(pos, ability_map, msm) - .expect("Expected item to exist in inventory"); - components.push(component); - }) + self.inputs + .iter() + .enumerate() + .for_each(|(i, (input, amount))| { + let valid_input = if let Some(item) = slots.get(i).and_then(|slot| inv.get(*slot)) { + item.matches_recipe_input(input) + } else { + false + }; + + if let Some(slot) = slots.get(i) { + if !valid_input { + unsatisfied_requirements.push((input, *amount)); + } else { + for taken in 0..*amount { + if let Some(item) = inv.take(*slot, ability_map, msm) { + recipe_inputs.push(item); + } else { + unsatisfied_requirements.push((input, *amount - taken)); + break; + } + } + } + } else { + unsatisfied_requirements.push((input, *amount)); + } }); - let crafted_item = - Item::new_from_item_def(Arc::clone(&self.output.0), &components, ability_map, msm); - - Ok((crafted_item, self.output.1)) - - // for i in 0..self.output.1 { - // if let Err(item) = inv.push(crafted_item) { - // return Ok(Some((item, self.output.1 - i))); - // } - // } - - // Ok(None) + if unsatisfied_requirements.is_empty() { + let (item_def, quantity) = &self.output; + let crafted_item = + Item::new_from_item_def(Arc::clone(item_def), &[], ability_map, msm); + let mut crafted_items = Vec::with_capacity(*quantity as usize); + for _ in 0..*quantity { + crafted_items.push(crafted_item.duplicate(ability_map, msm)); + } + Ok(crafted_items) + } else { + for item in recipe_inputs { + inv.push(item) + .expect("Item was in inventory before craft attempt"); + } + Err(unsatisfied_requirements) + } } pub fn inputs(&self) -> impl ExactSizeIterator { @@ -66,6 +88,52 @@ impl Recipe { .iter() .map(|(item_def, amount)| (item_def, *amount)) } + + /// Determine whether the inventory contains the ingredients for a recipe. + /// If it does, return a vec of inventory slots that contain the + /// ingredients needed, whose positions correspond to particular recipe + /// inputs. If items are missing, return the missing items, and how many + /// are missing. + pub fn inventory_contains_ingredients<'a>( + &self, + inv: &'a Inventory, + ) -> Result, Vec<(&RecipeInput, u32)>> { + let mut slot_claims = HashMap::::new(); + // Important to be a vec and to remain separate from slot_claims as it must + // remain ordered, unlike the hashmap + let mut slots = Vec::::new(); + let mut missing = Vec::<(&RecipeInput, u32)>::new(); + + for (input, mut needed) in self.inputs() { + let mut contains_any = false; + + for (inv_slot_id, slot) in inv.slots_with_id() { + if let Some(item) = slot + .as_ref() + .filter(|item| item.matches_recipe_input(&*input)) + { + let claim = slot_claims.entry(inv_slot_id).or_insert(0); + slots.push(inv_slot_id); + // FIXME: Fishy, looks like it can underflow before min which can trigger an + // overflow check. + let can_claim = (item.amount() - *claim).min(needed); + *claim += can_claim; + needed -= can_claim; + contains_any = true; + } + } + + if needed > 0 || !contains_any { + missing.push((input, needed)); + } + } + + if missing.is_empty() { + Ok(slots) + } else { + Err(missing) + } + } } pub enum SalvageError { @@ -108,7 +176,7 @@ impl RecipeBook { pub fn get_available(&self, inv: &Inventory) -> Vec<(String, Recipe)> { self.recipes .iter() - .filter(|(_, recipe)| inv.contains_ingredients(recipe).is_ok()) + .filter(|(_, recipe)| recipe.inventory_contains_ingredients(inv).is_ok()) .map(|(name, recipe)| (name.clone(), recipe.clone())) .collect() } diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index b2843f6322..d3b8c4c627 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -572,7 +572,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv let msm = state.ecs().read_resource::(); let crafted_items = match craft_event { - CraftEvent::Simple(recipe) => recipe_book + CraftEvent::Simple { recipe, slots } => recipe_book .get(&recipe) .filter(|r| { if let Some(needed_sprite) = r.craft_sprite { @@ -604,19 +604,13 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv } }) .and_then(|r| { - r.perform( + r.craft_simple( &mut inventory, + slots, &state.ecs().read_resource::(), &state.ecs().read_resource::(), ) .ok() - }) - .map(|(crafted_item, amount)| { - let mut crafted_items = Vec::with_capacity(amount as usize); - for _ in 0..amount { - crafted_items.push(crafted_item.duplicate(ability_map, &msm)); - } - crafted_items }), CraftEvent::Salvage(slot) => { recipe::try_salvage(&mut inventory, slot, ability_map, &msm).ok() diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 4cdae8a24f..7d04cea671 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1395,7 +1395,22 @@ impl PlayState for SessionState { recipe, craft_sprite, } => { - self.client.borrow_mut().craft_recipe(&recipe, craft_sprite); + let slots = { + let client = self.client.borrow(); + if let Some(recipe) = client.recipe_book().get(&recipe) { + client + .inventories() + .get(client.entity()) + .and_then(|inv| recipe.inventory_contains_ingredients(inv).ok()) + } else { + None + } + }; + if let Some(slots) = slots { + self.client + .borrow_mut() + .craft_recipe(&recipe, slots, craft_sprite); + } }, HudEvent::SalvageItem(slot) => { self.client.borrow_mut().salvage_item(slot);