use crate::{ assets::{self, AssetExt, AssetHandle}, comp::{ item::{modular, tool::AbilityMap, ItemDef, ItemTag, MaterialStatManifest}, Inventory, Item, }, terrain::SpriteKind, }; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::sync::Arc; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum RecipeInput { Item(Arc), Tag(ItemTag), } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Recipe { pub output: (Arc, u32), pub inputs: Vec<(RecipeInput, u32)>, pub craft_sprite: Option, } #[allow(clippy::type_complexity)] impl Recipe { /// Perform a recipe, returning a list of missing items on failure pub fn perform( &self, inv: &mut Inventory, ability_map: &AbilityMap, msm: &MaterialStatManifest, ) -> Result, Vec<(&RecipeInput, u32)>> { // Get ingredient cells from inventory, let mut components = 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); }) }); for i in 0..self.output.1 { let crafted_item = Item::new_from_item_def(Arc::clone(&self.output.0), &components, ability_map, msm); if let Err(item) = inv.push(crafted_item) { return Ok(Some((item, self.output.1 - i))); } } Ok(None) } pub fn inputs(&self) -> impl ExactSizeIterator { self.inputs .iter() .map(|(item_def, amount)| (item_def, *amount)) } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RecipeBook { recipes: HashMap, } impl RecipeBook { pub fn get(&self, recipe: &str) -> Option<&Recipe> { self.recipes.get(recipe) } pub fn iter(&self) -> impl ExactSizeIterator { self.recipes.iter() } pub fn get_available(&self, inv: &Inventory) -> Vec<(String, Recipe)> { self.recipes .iter() .filter(|(_, recipe)| inv.contains_ingredients(recipe).is_ok()) .map(|(name, recipe)| (name.clone(), recipe.clone())) .collect() } } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum RawRecipeInput { Item(String), Tag(ItemTag), } #[derive(Clone, Deserialize)] pub(crate) struct RawRecipe { pub(crate) output: (String, u32), pub(crate) inputs: Vec<(RawRecipeInput, u32)>, #[serde(default)] pub(crate) craft_sprite: Option, } #[derive(Clone, Deserialize)] #[serde(transparent)] pub(crate) struct RawRecipeBook(pub(crate) HashMap); impl assets::Asset for RawRecipeBook { type Loader = assets::RonLoader; const EXTENSION: &'static str = "ron"; } impl assets::Compound for RecipeBook { fn load( cache: &assets_manager::AssetCache, specifier: &str, ) -> Result { #[inline] fn load_item_def(spec: &(String, u32)) -> Result<(Arc, u32), assets::Error> { let def = Arc::::load_cloned(&spec.0)?; Ok((def, spec.1)) } #[inline] fn load_recipe_input( spec: &(RawRecipeInput, u32), ) -> Result<(RecipeInput, u32), assets::Error> { let def = match &spec.0 { RawRecipeInput::Item(name) => { RecipeInput::Item(Arc::::load_cloned(&name)?) }, RawRecipeInput::Tag(tag) => RecipeInput::Tag(*tag), }; Ok((def, spec.1)) } let mut raw = cache.load::(specifier)?.read().clone(); // Avoid showing purple-question-box recipes until the assets are added // (the `if false` is needed because commenting out the call will add a warning // that there are no other uses of append_modular_recipes) if false { modular::append_modular_recipes(&mut raw); } let recipes = raw .0 .iter() .map( |( name, RawRecipe { output, inputs, craft_sprite, }, )| { let inputs = inputs .iter() .map(load_recipe_input) .collect::, _>>()?; let output = load_item_def(output)?; Ok((name.clone(), Recipe { output, inputs, craft_sprite: *craft_sprite, })) }, ) .collect::>()?; Ok(RecipeBook { recipes }) } } pub fn default_recipe_book() -> AssetHandle { RecipeBook::load_expect("common.recipe_book") }