use crate::{
assets::{self, AssetExt, AssetHandle, CacheCombined, Concatenate},
comp::{
inventory::slot::{InvSlotId, Slot},
item::{
modular,
tool::{AbilityMap, ToolKind},
ItemBase, ItemDef, ItemDefinitionId, ItemDefinitionIdOwned, ItemKind, ItemTag,
MaterialStatManifest,
},
Inventory, Item,
},
terrain::SpriteKind,
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, sync::Arc};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum RecipeInput {
Item(Arc<ItemDef>),
Tag(ItemTag),
TagSameItem(ItemTag),
ListSameItem(Vec<Arc<ItemDef>>),
}
impl RecipeInput {
fn handle_requirement<'a, I: Iterator<Item = InvSlotId>>(
&'a self,
amount: u32,
slot_claims: &mut HashMap<InvSlotId, u32>,
unsatisfied_requirements: &mut Vec<(&'a RecipeInput, u32)>,
inv: &Inventory,
input_slots: I,
) {
let mut required = amount;
let contains_any = input_slots.into_iter().all(|slot| {
if let Some(item) = inv
.get(slot)
.filter(|item| item.matches_recipe_input(self, amount))
{
let claimed = slot_claims.entry(slot).or_insert(0);
let available = item.amount().saturating_sub(*claimed);
let provided = available.min(required);
required -= provided;
*claimed += provided;
true
} else {
false
}
});
if required > 0 || !contains_any {
unsatisfied_requirements.push((self, required));
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Recipe {
pub output: (Arc<ItemDef>, u32),
pub inputs: Vec<(RecipeInput, u32, bool)>,
pub craft_sprite: Option<SpriteKind>,
}
impl Recipe {
pub fn craft_simple(
&self,
inv: &mut Inventory,
slots: Vec<(u32, InvSlotId)>,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<Vec<Item>, Vec<(&RecipeInput, u32)>> {
let mut slot_claims = HashMap::new();
let mut unsatisfied_requirements = Vec::new();
let mut component_slots = Vec::new();
self.inputs
.iter()
.enumerate()
.for_each(|(i, (input, amount, mut is_component))| {
let mut required = *amount;
let mut contains_any = false;
let input_slots = slots
.iter()
.filter_map(|(j, slot)| if i as u32 == *j { Some(slot) } else { None });
for slot in input_slots {
if let Some(item) = inv
.get(*slot)
.filter(|item| item.matches_recipe_input(input, *amount))
{
let claimed = slot_claims.entry(*slot).or_insert(0);
let available = item.amount().saturating_sub(*claimed);
let provided = available.min(required);
required -= provided;
*claimed += provided;
if provided > 0 && is_component {
component_slots.push(*slot);
is_component = false;
}
contains_any = true;
}
}
if required > 0 || !contains_any {
unsatisfied_requirements.push((input, required));
}
});
if unsatisfied_requirements.is_empty() {
let mut components = Vec::new();
for slot in component_slots.iter() {
let component = inv
.take(*slot, ability_map, msm)
.expect("Expected item to exist in the inventory");
components.push(component);
let to_remove = slot_claims
.get_mut(slot)
.expect("If marked in component slots, should be in slot claims");
*to_remove -= 1;
}
for (slot, to_remove) in slot_claims.iter() {
for _ in 0..*to_remove {
let _ = inv
.take(*slot, ability_map, msm)
.expect("Expected item to exist in the inventory");
}
}
let (item_def, quantity) = &self.output;
let crafted_item = Item::new_from_item_base(
ItemBase::Simple(Arc::clone(item_def)),
components,
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 {
Err(unsatisfied_requirements)
}
}
pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&RecipeInput, u32, bool)> {
self.inputs
.iter()
.map(|(item_def, amount, is_mod_comp)| (item_def, *amount, *is_mod_comp))
}
pub fn inventory_contains_ingredients(
&self,
inv: &Inventory,
recipe_amount: u32,
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
inventory_contains_ingredients(
self.inputs()
.map(|(input, amount, _is_modular)| (input, amount)),
inv,
recipe_amount,
)
}
pub fn max_from_ingredients(&self, inv: &Inventory) -> u32 {
let mut max_recipes = None;
for (input, amount) in self
.inputs()
.map(|(input, amount, _is_modular)| (input, amount))
{
let needed = amount as f32;
let mut input_max = HashMap::<InvSlotId, u32>::new();
for (inv_slot_id, slot) in inv.slots_with_id() {
if let Some(item) = slot
.as_ref()
.filter(|item| item.matches_recipe_input(input, amount))
{
*input_max.entry(inv_slot_id).or_insert(0) += item.amount();
}
}
let max_item_proportion =
((input_max.values().sum::<u32>() as f32) / needed).floor() as u32;
max_recipes = Some(match max_recipes {
None => max_item_proportion,
Some(max_recipes) if (max_item_proportion < max_recipes) => max_item_proportion,
Some(n) => n,
});
}
max_recipes.unwrap_or(0)
}
}
#[allow(clippy::type_complexity)]
fn inventory_contains_ingredients<'a, I: Iterator<Item = (&'a RecipeInput, u32)>>(
ingredients: I,
inv: &Inventory,
recipe_amount: u32,
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&'a RecipeInput, u32)>> {
let mut slot_claims = HashMap::<InvSlotId, u32>::new();
let mut slots = Vec::<(u32, InvSlotId)>::new();
let mut missing = Vec::<(&RecipeInput, u32)>::new();
for (i, (input, amount)) in ingredients.enumerate() {
let mut needed = amount * recipe_amount;
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, amount))
{
let claim = slot_claims.entry(inv_slot_id).or_insert(0);
slots.push((i as u32, inv_slot_id));
let can_claim = (item.amount().saturating_sub(*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 {
NotSalvageable,
}
pub fn try_salvage(
inv: &mut Inventory,
slot: InvSlotId,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<Vec<Item>, SalvageError> {
if inv.get(slot).map_or(false, |item| item.is_salvageable()) {
let salvage_item = inv.get(slot).expect("Expected item to exist in inventory");
let salvage_output: Vec<_> = salvage_item
.salvage_output()
.flat_map(|(material, quantity)| {
std::iter::repeat(Item::new_from_asset_expect(material)).take(quantity as usize)
})
.collect();
if salvage_output.is_empty() {
Err(SalvageError::NotSalvageable)
} else {
let _ = inv
.take(slot, ability_map, msm)
.expect("Expected item to exist in inventory");
Ok(salvage_output)
}
} else {
Err(SalvageError::NotSalvageable)
}
}
pub enum ModularWeaponError {
InvalidSlot,
ComponentMismatch,
DifferentTools,
DifferentHands,
}
pub fn modular_weapon(
inv: &mut Inventory,
primary_component: InvSlotId,
secondary_component: InvSlotId,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<Item, ModularWeaponError> {
use modular::ModularComponent;
fn unwrap_modular(inv: &Inventory, slot: InvSlotId) -> Option<Cow<ModularComponent>> {
inv.get(slot).and_then(|item| match item.kind() {
Cow::Owned(ItemKind::ModularComponent(mod_comp)) => Some(Cow::Owned(mod_comp)),
Cow::Borrowed(ItemKind::ModularComponent(mod_comp)) => Some(Cow::Borrowed(mod_comp)),
_ => None,
})
}
let compatiblity = if let (Some(primary_component), Some(secondary_component)) = (
unwrap_modular(inv, primary_component),
unwrap_modular(inv, secondary_component),
) {
if let (
ModularComponent::ToolPrimaryComponent {
toolkind: tool_a,
hand_restriction: hands_a,
..
},
ModularComponent::ToolSecondaryComponent {
toolkind: tool_b,
hand_restriction: hands_b,
..
},
) = (&*primary_component, &*secondary_component)
{
if tool_a == tool_b {
let hands_check = hands_a.zip(*hands_b).map_or(true, |(a, b)| a == b);
if hands_check {
Ok(())
} else {
Err(ModularWeaponError::DifferentHands)
}
} else {
Err(ModularWeaponError::DifferentTools)
}
} else {
Err(ModularWeaponError::ComponentMismatch)
}
} else {
Err(ModularWeaponError::InvalidSlot)
};
match compatiblity {
Ok(()) => {
let primary_component = inv
.take(primary_component, ability_map, msm)
.expect("Expected component to exist");
let secondary_component = inv
.take(secondary_component, ability_map, msm)
.expect("Expected component to exist");
Ok(Item::new_from_item_base(
ItemBase::Modular(modular::ModularBase::Tool),
vec![primary_component, secondary_component],
ability_map,
msm,
))
},
Err(err) => Err(err),
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RecipeBookManifest {
recipes: HashMap<String, Recipe>,
}
impl RecipeBookManifest {
pub fn load() -> AssetHandle<Self> { Self::load_expect("common.recipe_book_manifest") }
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 keys(&self) -> impl ExactSizeIterator<Item = &String> { self.recipes.keys() }
pub fn get_available(&self, inv: &Inventory) -> Vec<(String, Recipe)> {
self.recipes
.iter()
.filter(|(_, recipe)| recipe.inventory_contains_ingredients(inv, 1).is_ok())
.map(|(name, recipe)| (name.clone(), recipe.clone()))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn complete_recipe_book_valid_key_check() {
let recipe_book = complete_recipe_book().read();
let is_invalid_key =
|input: &str| input.chars().any(|c| c.is_uppercase() || c.is_whitespace());
assert!(!recipe_book.iter().any(|(k, _)| is_invalid_key(k)));
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum RawRecipeInput {
Item(String),
Tag(ItemTag),
TagSameItem(ItemTag),
ListSameItem(String),
}
impl RawRecipeInput {
fn load_recipe_input(&self) -> Result<RecipeInput, assets::Error> {
let input = match self {
RawRecipeInput::Item(name) => RecipeInput::Item(Arc::<ItemDef>::load_cloned(name)?),
RawRecipeInput::Tag(tag) => RecipeInput::Tag(*tag),
RawRecipeInput::TagSameItem(tag) => RecipeInput::TagSameItem(*tag),
RawRecipeInput::ListSameItem(list) => {
let assets = &ItemList::load_expect(list).read().0;
let items = assets
.iter()
.map(|asset| Arc::<ItemDef>::load_expect_cloned(asset))
.collect();
RecipeInput::ListSameItem(items)
},
};
Ok(input)
}
}
#[derive(Clone, Deserialize)]
pub(crate) struct RawRecipe {
pub(crate) output: (String, u32),
pub(crate) inputs: Vec<(RawRecipeInput, u32, bool)>,
pub(crate) craft_sprite: Option<SpriteKind>,
}
#[derive(Clone, Deserialize)]
#[serde(transparent)]
pub(crate) struct RawRecipeBook(pub(crate) HashMap<String, RawRecipe>);
impl assets::Asset for RawRecipeBook {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
impl Concatenate for RawRecipeBook {
fn concatenate(self, b: Self) -> Self { RawRecipeBook(self.0.concatenate(b.0)) }
}
#[derive(Deserialize, Clone)]
struct ItemList(Vec<String>);
impl assets::Asset for ItemList {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
impl assets::Compound for RecipeBookManifest {
fn load(
cache: assets::AnyCache,
specifier: &assets::SharedString,
) -> Result<Self, assets::BoxedError> {
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))
}
fn load_recipe_input(
(input, amount, is_mod_comp): &(RawRecipeInput, u32, bool),
) -> Result<(RecipeInput, u32, bool), assets::Error> {
let def = input.load_recipe_input()?;
Ok((def, *amount, *is_mod_comp))
}
let raw = cache.load_and_combine::<RawRecipeBook>(specifier)?.cloned();
let recipes = raw
.0
.iter()
.map(
|(
name,
RawRecipe {
output,
inputs,
craft_sprite,
},
)| {
let inputs = inputs
.iter()
.map(load_recipe_input)
.collect::<Result<Vec<_>, _>>()?;
let output = load_item_def(output)?;
Ok((name.clone(), Recipe {
output,
inputs,
craft_sprite: *craft_sprite,
}))
},
)
.collect::<Result<_, assets::Error>>()?;
Ok(RecipeBookManifest { recipes })
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ComponentRecipeBook {
recipes: HashMap<ComponentKey, ComponentRecipe>,
}
#[derive(Clone, Debug)]
pub struct ReverseComponentRecipeBook {
recipes: HashMap<ItemDefinitionIdOwned, ComponentRecipe>,
}
impl ComponentRecipeBook {
pub fn get(&self, key: &ComponentKey) -> Option<&ComponentRecipe> { self.recipes.get(key) }
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&ComponentKey, &ComponentRecipe)> {
self.recipes.iter()
}
}
impl ReverseComponentRecipeBook {
pub fn get(&self, key: &ItemDefinitionIdOwned) -> Option<&ComponentRecipe> {
self.recipes.get(key)
}
}
#[derive(Clone, Deserialize)]
#[serde(transparent)]
struct RawComponentRecipeBook(Vec<RawComponentRecipe>);
impl assets::Asset for RawComponentRecipeBook {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
impl Concatenate for RawComponentRecipeBook {
fn concatenate(self, b: Self) -> Self { RawComponentRecipeBook(self.0.concatenate(b.0)) }
}
#[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
pub struct ComponentKey {
pub toolkind: ToolKind,
pub material: String,
pub modifier: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ComponentRecipe {
pub recipe_book_key: String,
output: ComponentOutput,
material: (RecipeInput, u32),
modifier: Option<(RecipeInput, u32)>,
additional_inputs: Vec<(RecipeInput, u32)>,
pub craft_sprite: Option<SpriteKind>,
}
impl ComponentRecipe {
pub fn craft_component(
&self,
inv: &mut Inventory,
material_slot: InvSlotId,
modifier_slot: Option<InvSlotId>,
slots: Vec<(u32, InvSlotId)>,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<Vec<Item>, Vec<(&RecipeInput, u32)>> {
let mut slot_claims = HashMap::new();
let mut unsatisfied_requirements = Vec::new();
self.material.0.handle_requirement(
self.material.1,
&mut slot_claims,
&mut unsatisfied_requirements,
inv,
core::iter::once(material_slot),
);
if let Some((modifier_input, modifier_amount)) = &self.modifier {
modifier_input.handle_requirement(
*modifier_amount,
&mut slot_claims,
&mut unsatisfied_requirements,
inv,
core::iter::once(modifier_slot.unwrap_or(InvSlotId::new(0, 0))),
);
}
self.additional_inputs
.iter()
.enumerate()
.for_each(|(i, (input, amount))| {
let input_slots = slots
.iter()
.filter_map(|(j, slot)| if i as u32 == *j { Some(slot) } else { None })
.copied();
input.handle_requirement(
*amount,
&mut slot_claims,
&mut unsatisfied_requirements,
inv,
input_slots,
);
});
if unsatisfied_requirements.is_empty() {
for (slot, to_remove) in slot_claims.iter() {
for _ in 0..*to_remove {
let _ = inv
.take(*slot, ability_map, msm)
.expect("Expected item to exist in the inventory");
}
}
let crafted_item = self.item_output(ability_map, msm);
Ok(vec![crafted_item])
} else {
Err(unsatisfied_requirements)
}
}
#[allow(clippy::type_complexity)]
pub fn inventory_contains_additional_ingredients(
&self,
inv: &Inventory,
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
inventory_contains_ingredients(
self.additional_inputs
.iter()
.map(|(input, amount)| (input, *amount)),
inv,
1,
)
}
pub fn itemdef_output(&self) -> ItemDefinitionIdOwned {
match &self.output {
ComponentOutput::ItemComponents {
item: item_def,
components,
} => {
let components = components
.iter()
.map(|item_def| ItemDefinitionIdOwned::Simple(item_def.id().to_owned()))
.collect::<Vec<_>>();
ItemDefinitionIdOwned::Compound {
simple_base: item_def.id().to_owned(),
components,
}
},
}
}
pub fn item_output(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Item {
match &self.output {
ComponentOutput::ItemComponents {
item: item_def,
components,
} => {
let components = components
.iter()
.map(|item_def| {
Item::new_from_item_base(
ItemBase::Simple(Arc::clone(item_def)),
Vec::new(),
ability_map,
msm,
)
})
.collect::<Vec<_>>();
Item::new_from_item_base(
ItemBase::Simple(Arc::clone(item_def)),
components,
ability_map,
msm,
)
},
}
}
pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&RecipeInput, u32)> {
self.into_iter().map(|(recipe, amount)| (recipe, *amount))
}
}
pub struct ComponentRecipeInputsIterator<'a> {
material: Option<&'a (RecipeInput, u32)>,
modifier: Option<&'a (RecipeInput, u32)>,
additional_inputs: std::slice::Iter<'a, (RecipeInput, u32)>,
}
impl<'a> Iterator for ComponentRecipeInputsIterator<'a> {
type Item = &'a (RecipeInput, u32);
fn next(&mut self) -> Option<&'a (RecipeInput, u32)> {
self.material
.take()
.or_else(|| self.modifier.take())
.or_else(|| self.additional_inputs.next())
}
}
impl<'a> IntoIterator for &'a ComponentRecipe {
type IntoIter = ComponentRecipeInputsIterator<'a>;
type Item = &'a (RecipeInput, u32);
fn into_iter(self) -> Self::IntoIter {
ComponentRecipeInputsIterator {
material: Some(&self.material),
modifier: self.modifier.as_ref(),
additional_inputs: self.additional_inputs.as_slice().iter(),
}
}
}
impl<'a> ExactSizeIterator for ComponentRecipeInputsIterator<'a> {
fn len(&self) -> usize {
self.material.is_some() as usize
+ self.modifier.is_some() as usize
+ self.additional_inputs.len()
}
}
#[derive(Clone, Deserialize)]
struct RawComponentRecipe {
recipe_book_key: String,
output: RawComponentOutput,
material: (String, u32),
modifier: Option<(String, u32)>,
additional_inputs: Vec<(RawRecipeInput, u32)>,
craft_sprite: Option<SpriteKind>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum ComponentOutput {
ItemComponents {
item: Arc<ItemDef>,
components: Vec<Arc<ItemDef>>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum RawComponentOutput {
ToolPrimaryComponent { toolkind: ToolKind, item: String },
}
impl assets::Compound for ComponentRecipeBook {
fn load(
cache: assets::AnyCache,
specifier: &assets::SharedString,
) -> Result<Self, assets::BoxedError> {
fn create_recipe_key(raw_recipe: &RawComponentRecipe) -> ComponentKey {
match &raw_recipe.output {
RawComponentOutput::ToolPrimaryComponent { toolkind, item: _ } => {
let material = String::from(&raw_recipe.material.0);
let modifier = raw_recipe
.modifier
.as_ref()
.map(|(modifier, _amount)| String::from(modifier));
ComponentKey {
toolkind: *toolkind,
material,
modifier,
}
},
}
}
fn load_recipe(raw_recipe: &RawComponentRecipe) -> Result<ComponentRecipe, assets::Error> {
let output = match &raw_recipe.output {
RawComponentOutput::ToolPrimaryComponent { toolkind: _, item } => {
let item = Arc::<ItemDef>::load_cloned(item)?;
let components = vec![Arc::<ItemDef>::load_cloned(&raw_recipe.material.0)?];
ComponentOutput::ItemComponents { item, components }
},
};
let material = (
RecipeInput::Item(Arc::<ItemDef>::load_cloned(&raw_recipe.material.0)?),
raw_recipe.material.1,
);
let modifier = if let Some((modifier, amount)) = &raw_recipe.modifier {
let modifier = Arc::<ItemDef>::load_cloned(modifier)?;
Some((RecipeInput::Item(modifier), *amount))
} else {
None
};
let additional_inputs = raw_recipe
.additional_inputs
.iter()
.map(|(input, amount)| input.load_recipe_input().map(|input| (input, *amount)))
.collect::<Result<Vec<_>, _>>()?;
let recipe_book_key = String::from(&raw_recipe.recipe_book_key);
Ok(ComponentRecipe {
recipe_book_key,
output,
material,
modifier,
additional_inputs,
craft_sprite: raw_recipe.craft_sprite,
})
}
let raw = cache
.load_and_combine::<RawComponentRecipeBook>(specifier)?
.cloned();
let recipes = raw
.0
.iter()
.map(|raw_recipe| {
load_recipe(raw_recipe).map(|recipe| (create_recipe_key(raw_recipe), recipe))
})
.collect::<Result<_, assets::Error>>()?;
Ok(ComponentRecipeBook { recipes })
}
}
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, Debug)]
enum RepairKey {
ItemDefId(String),
ModularWeapon { material: String },
}
impl RepairKey {
fn from_item(item: &Item) -> Option<Self> {
match item.item_definition_id() {
ItemDefinitionId::Simple(item_id) => Some(Self::ItemDefId(String::from(item_id))),
ItemDefinitionId::Compound { .. } => None,
ItemDefinitionId::Modular { pseudo_base, .. } => match pseudo_base {
"veloren.core.pseudo_items.modular.tool" => {
if let Some(ItemDefinitionId::Simple(material)) = item
.components()
.iter()
.find(|comp| {
matches!(
&*comp.kind(),
ItemKind::ModularComponent(
modular::ModularComponent::ToolPrimaryComponent { .. }
)
)
})
.and_then(|comp| {
comp.components()
.iter()
.next()
.map(|comp| comp.item_definition_id())
})
{
let material = String::from(material);
Some(Self::ModularWeapon { material })
} else {
None
}
},
_ => None,
},
}
}
}
#[derive(Serialize, Deserialize, Clone)]
struct RawRepairRecipe {
inputs: Vec<(RawRecipeInput, u32)>,
}
#[derive(Serialize, Deserialize, Clone)]
struct RawRepairRecipeBook {
recipes: HashMap<RepairKey, RawRepairRecipe>,
fallback: RawRepairRecipe,
}
impl assets::Asset for RawRepairRecipeBook {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct RepairRecipe {
inputs: Vec<(RecipeInput, u32)>,
}
impl RepairRecipe {
pub fn inventory_contains_ingredients(
&self,
item: &Item,
inv: &Inventory,
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&RecipeInput, u32)>> {
inventory_contains_ingredients(self.inputs(item), inv, 1)
}
pub fn inputs(&self, item: &Item) -> impl Iterator<Item = (&RecipeInput, u32)> {
let item_durability = item.durability_lost().unwrap_or(0);
self.inputs
.iter()
.filter_map(move |(input, original_amount)| {
let amount = (original_amount * item_durability) / Item::MAX_DURABILITY;
if *original_amount > 0 && amount == 0 {
None
} else {
Some((input, amount))
}
})
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct RepairRecipeBook {
recipes: HashMap<RepairKey, RepairRecipe>,
fallback: RepairRecipe,
}
impl RepairRecipeBook {
pub fn repair_recipe(&self, item: &Item) -> Option<&RepairRecipe> {
RepairKey::from_item(item)
.as_ref()
.and_then(|key| self.recipes.get(key))
.or_else(|| item.has_durability().then_some(&self.fallback))
}
pub fn repair_item(
&self,
inv: &mut Inventory,
item: Slot,
slots: Vec<(u32, InvSlotId)>,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<(), Vec<(&RecipeInput, u32)>> {
let mut slot_claims = HashMap::new();
let mut unsatisfied_requirements = Vec::new();
if let Some(item) = match item {
Slot::Equip(slot) => inv.equipped(slot),
Slot::Inventory(slot) => inv.get(slot),
Slot::Overflow(_) => None,
} {
if let Some(repair_recipe) = self.repair_recipe(item) {
repair_recipe
.inputs(item)
.enumerate()
.for_each(|(i, (input, amount))| {
let input_slots = slots
.iter()
.filter_map(|(j, slot)| if i as u32 == *j { Some(slot) } else { None })
.copied();
input.handle_requirement(
amount,
&mut slot_claims,
&mut unsatisfied_requirements,
inv,
input_slots,
);
})
}
}
if unsatisfied_requirements.is_empty() {
for (slot, to_remove) in slot_claims.iter() {
for _ in 0..*to_remove {
let _ = inv
.take(*slot, ability_map, msm)
.expect("Expected item to exist in the inventory");
}
}
inv.repair_item_at_slot(item, ability_map, msm);
Ok(())
} else {
Err(unsatisfied_requirements)
}
}
}
impl assets::Compound for RepairRecipeBook {
fn load(
cache: assets::AnyCache,
specifier: &assets::SharedString,
) -> Result<Self, assets::BoxedError> {
fn load_recipe_input(
(input, amount): &(RawRecipeInput, u32),
) -> Result<(RecipeInput, u32), assets::Error> {
let input = input.load_recipe_input()?;
Ok((input, *amount))
}
let raw = cache.load::<RawRepairRecipeBook>(specifier)?.cloned();
let recipes = raw
.recipes
.iter()
.map(|(key, RawRepairRecipe { inputs })| {
let inputs = inputs
.iter()
.map(load_recipe_input)
.collect::<Result<Vec<_>, _>>()?;
Ok((key.clone(), RepairRecipe { inputs }))
})
.collect::<Result<_, assets::Error>>()?;
let fallback = RepairRecipe {
inputs: raw
.fallback
.inputs
.iter()
.map(load_recipe_input)
.collect::<Result<Vec<_>, _>>()?,
};
Ok(RepairRecipeBook { recipes, fallback })
}
}
pub fn complete_recipe_book() -> AssetHandle<RecipeBookManifest> {
RecipeBookManifest::load_expect("common.recipe_book_manifest")
}
pub fn default_component_recipe_book() -> AssetHandle<ComponentRecipeBook> {
ComponentRecipeBook::load_expect("common.component_recipe_book")
}
pub fn default_repair_recipe_book() -> AssetHandle<RepairRecipeBook> {
RepairRecipeBook::load_expect("common.repair_recipe_book")
}
impl assets::Compound for ReverseComponentRecipeBook {
fn load(
cache: assets::AnyCache,
specifier: &assets::SharedString,
) -> Result<Self, assets::BoxedError> {
let forward = cache.load::<ComponentRecipeBook>(specifier)?.cloned();
let mut recipes = HashMap::new();
for (_, recipe) in forward.iter() {
recipes.insert(recipe.itemdef_output(), recipe.clone());
}
Ok(ReverseComponentRecipeBook { recipes })
}
}