Simple recipes now take a vec of slots to look in for the ingredients.

This commit is contained in:
Sam 2021-10-06 18:18:12 -04:00
parent 7a17863988
commit 794b072d3e
6 changed files with 124 additions and 83 deletions

View File

@ -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<InvSlotId>,
craft_sprite: Option<(Vec3<i32>, 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),
},
)));

View File

@ -92,7 +92,10 @@ impl From<InventoryEvent> for InventoryManip {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum CraftEvent {
Simple(String),
Simple {
recipe: String,
slots: Vec<InvSlotId>,
},
Salvage(InvSlotId),
}

View File

@ -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<HashMap<InvSlotId, u32>, Vec<(&'a RecipeInput, u32)>> {
let mut slot_claims = HashMap::<InvSlotId, u32>::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.

View File

@ -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<InvSlotId>,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<(Item, u32), Vec<(&RecipeInput, u32)>> {
// Get ingredient cells from inventory,
let mut components = Vec::new();
) -> Result<Vec<Item>, 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<Item = (&RecipeInput, u32)> {
@ -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<InvSlotId>, Vec<(&RecipeInput, u32)>> {
let mut slot_claims = HashMap::<InvSlotId, u32>::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::<InvSlotId>::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()
}

View File

@ -572,7 +572,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
let msm = state.ecs().read_resource::<MaterialStatManifest>();
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::<AbilityMap>(),
&state.ecs().read_resource::<item::MaterialStatManifest>(),
)
.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()

View File

@ -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);