veloren/common/src/recipe.rs

164 lines
4.9 KiB
Rust
Raw Normal View History

2020-07-14 20:11:39 +00:00
use crate::{
2020-12-12 22:14:24 +00:00
assets::{self, AssetExt, AssetHandle},
comp::{
item::{modular, ItemDef, ItemTag, MaterialStatManifest},
Inventory, Item,
},
2021-04-17 14:58:43 +00:00
terrain::SpriteKind,
2020-07-14 20:11:39 +00:00
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
2020-12-12 22:14:24 +00:00
use std::sync::Arc;
2020-07-14 20:11:39 +00:00
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum RecipeInput {
Item(Arc<ItemDef>),
Tag(ItemTag),
}
2020-07-14 20:11:39 +00:00
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Recipe {
pub output: (Arc<ItemDef>, u32),
pub inputs: Vec<(RecipeInput, u32)>,
2021-04-17 14:58:43 +00:00
pub craft_sprite: Option<SpriteKind>,
2020-07-14 20:11:39 +00:00
}
#[allow(clippy::type_complexity)]
impl Recipe {
/// Perform a recipe, returning a list of missing items on failure
pub fn perform(
&self,
inv: &mut Inventory,
msm: &MaterialStatManifest,
) -> Result<Option<(Item, u32)>, Vec<(&RecipeInput, u32)>> {
2020-07-14 20:11:39 +00:00
// Get ingredient cells from inventory,
let mut components = Vec::new();
2020-07-14 20:11:39 +00:00
inv.contains_ingredients(self)?
.into_iter()
.for_each(|(pos, n)| {
2020-07-14 20:11:39 +00:00
(0..n).for_each(|_| {
let component = inv
.take(pos, msm)
.expect("Expected item to exist in inventory");
components.push(component);
2020-07-14 20:11:39 +00:00
})
});
for i in 0..self.output.1 {
let crafted_item =
Item::new_from_item_def(Arc::clone(&self.output.0), &components, msm);
if let Err(item) = inv.push(crafted_item) {
2020-07-14 20:11:39 +00:00
return Ok(Some((item, self.output.1 - i)));
}
}
Ok(None)
}
pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&RecipeInput, u32)> {
self.inputs
.iter()
.map(|(item_def, amount)| (item_def, *amount))
2020-07-14 20:11:39 +00:00
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RecipeBook {
recipes: HashMap<String, Recipe>,
}
impl RecipeBook {
pub fn get(&self, recipe: &str) -> Option<&Recipe> { self.recipes.get(recipe) }
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&String, &Recipe)> { 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),
}
2021-04-17 14:58:43 +00:00
#[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<SpriteKind>,
}
#[derive(Clone, Deserialize)]
2020-12-12 22:14:24 +00:00
#[serde(transparent)]
2020-12-13 14:57:10 +00:00
#[allow(clippy::type_complexity)]
pub(crate) struct RawRecipeBook(
2021-04-17 14:58:43 +00:00
pub(crate) HashMap<String, RawRecipe>,
);
2020-12-12 22:14:24 +00:00
impl assets::Asset for RawRecipeBook {
type Loader = assets::RonLoader;
2020-12-13 01:09:57 +00:00
const EXTENSION: &'static str = "ron";
2020-12-12 22:14:24 +00:00
}
impl assets::Compound for RecipeBook {
2020-12-13 01:09:57 +00:00
fn load<S: assets_manager::source::Source>(
cache: &assets_manager::AssetCache<S>,
specifier: &str,
) -> Result<Self, assets_manager::Error> {
2020-12-12 22:14:24 +00:00
#[inline]
fn load_item_def(spec: &(String, u32)) -> Result<(Arc<ItemDef>, u32), assets::Error> {
let def = Arc::<ItemDef>::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::<ItemDef>::load_cloned(&name)?)
},
RawRecipeInput::Tag(tag) => RecipeInput::Tag(*tag),
};
Ok((def, spec.1))
}
2020-12-12 22:14:24 +00:00
let mut raw = cache.load::<RawRecipeBook>(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);
}
2020-12-12 22:14:24 +00:00
2020-12-13 01:09:57 +00:00
let recipes = raw
.0
.iter()
2021-04-17 14:58:43 +00:00
.map(|(name, RawRecipe { output, inputs, craft_sprite })| {
let inputs = inputs
.iter()
.map(load_recipe_input)
2021-04-17 14:58:43 +00:00
.collect::<Result<Vec<_>, _>>()?;
2020-12-12 22:14:24 +00:00
let output = load_item_def(output)?;
2021-04-17 14:58:43 +00:00
Ok((name.clone(), Recipe { output, inputs, craft_sprite: *craft_sprite }))
2020-07-14 20:11:39 +00:00
})
2020-12-12 22:14:24 +00:00
.collect::<Result<_, assets::Error>>()?;
Ok(RecipeBook { recipes })
2020-07-14 20:11:39 +00:00
}
}
2020-12-12 22:14:24 +00:00
pub fn default_recipe_book() -> AssetHandle<RecipeBook> {
RecipeBook::load_expect("common.recipe_book")
}