diff --git a/common/src/comp/inventory/item/item_key.rs b/common/src/comp/inventory/item/item_key.rs index 76af713d44..0d196fdb06 100644 --- a/common/src/comp/inventory/item/item_key.rs +++ b/common/src/comp/inventory/item/item_key.rs @@ -50,6 +50,7 @@ impl From<&T> for ItemKey { } }, ModularComponent::ToolSecondaryComponent { .. } => { + // TODO: Maybe use a different ItemKey? ItemKey::Tool(item_definition_id.to_owned()) }, } diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index f71db73221..099a3ffe74 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -219,7 +219,6 @@ impl TagExampleInfo for Material { #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum ItemTag { - Leather, Material(Material), MaterialKind(MaterialKind), Cultist, @@ -237,7 +236,6 @@ impl TagExampleInfo for ItemTag { match self { ItemTag::Material(material) => material.name(), ItemTag::MaterialKind(material_kind) => material_kind.into(), - ItemTag::Leather => "leather", ItemTag::Cultist => "cultist", ItemTag::Potion => "potion", ItemTag::Food => "food", @@ -254,7 +252,6 @@ impl TagExampleInfo for ItemTag { match self { ItemTag::Material(_) => "common.items.tag_examples.placeholder", ItemTag::MaterialKind(_) => "common.items.tag_examples.placeholder", - ItemTag::Leather => "common.items.tag_examples.leather", ItemTag::Cultist => "common.items.tag_examples.cultist", ItemTag::Potion => "common.items.tag_examples.placeholder", ItemTag::Food => "common.items.tag_examples.placeholder", @@ -715,8 +712,8 @@ impl Item { ItemBase::Raw(Arc::::load_cloned(asset)?) }; // TODO: Get msm and ability_map less hackily - let msm = MaterialStatManifest::default(); - let ability_map = AbilityMap::default(); + let msm = MaterialStatManifest::load().read(); + let ability_map = AbilityMap::load().read(); Ok(Item::new_from_item_base( inner_item, Vec::new(), @@ -862,15 +859,15 @@ impl Item { } } - pub fn matches_recipe_input(&self, recipe_input: &RecipeInput) -> bool { + pub fn matches_recipe_input(&self, recipe_input: &RecipeInput, amount: u32) -> bool { match recipe_input { RecipeInput::Item(item_def) => self.is_same_item_def(item_def), RecipeInput::Tag(tag) => self.tags().contains(tag), - RecipeInput::TagSameItem(tag, amount) => { - self.tags().contains(tag) && u32::from(self.amount) >= *amount + RecipeInput::TagSameItem(tag) => { + self.tags().contains(tag) && u32::from(self.amount) >= amount }, - RecipeInput::ListSameItem(item_defs, amount) => item_defs.iter().any(|item_def| { - self.is_same_item_def(item_def) && u32::from(self.amount) >= *amount + RecipeInput::ListSameItem(item_defs) => item_defs.iter().any(|item_def| { + self.is_same_item_def(item_def) && u32::from(self.amount) >= amount }), } } @@ -893,7 +890,13 @@ impl Item { pub fn name(&self) -> Cow { match &self.item_base { - ItemBase::Raw(item_def) => Cow::Borrowed(&item_def.name), + ItemBase::Raw(item_def) => { + if self.components.is_empty() { + Cow::Borrowed(&item_def.name) + } else { + modular::modify_name(&item_def.name, self) + } + }, ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()), } } @@ -911,7 +914,7 @@ impl Item { ItemBase::Raw(item_def) => Cow::Borrowed(&item_def.kind), ItemBase::Modular(mod_base) => { // TODO: Try to move further upward - let msm = MaterialStatManifest::default(); + let msm = MaterialStatManifest::load().read(); mod_base.kind(self.components(), &msm) }, diff --git a/common/src/comp/inventory/item/modular.rs b/common/src/comp/inventory/item/modular.rs index 9c5b7036b4..283eb367e2 100644 --- a/common/src/comp/inventory/item/modular.rs +++ b/common/src/comp/inventory/item/modular.rs @@ -1,5 +1,5 @@ use super::{ - tool::{self, AbilitySpec, Hands, MaterialStatManifest}, + tool::{self, AbilityMap, AbilitySpec, Hands, MaterialStatManifest}, Item, ItemBase, ItemDef, ItemDesc, ItemKind, Material, Quality, ToolKind, }; use crate::{assets::AssetExt, recipe}; @@ -287,8 +287,8 @@ pub fn random_weapon( ) -> Result { if let Some(material_id) = material.asset_identifier() { // Loads default ability map and material stat manifest for later use - let ability_map = Default::default(); - let msm = Default::default(); + let ability_map = AbilityMap::load().read(); + let msm = MaterialStatManifest::load().read(); let mut rng = thread_rng(); @@ -351,6 +351,25 @@ pub fn random_weapon( } } +pub fn modify_name<'a>(item_name: &'a str, item: &'a Item) -> Cow<'a, str> { + if let ItemKind::ModularComponent(_) = &*item.kind() { + if let Some(material_name) = item + .components() + .iter() + .find_map(|comp| match &*comp.kind() { + ItemKind::Ingredient { descriptor, .. } => Some(descriptor.to_owned()), + _ => None, + }) + { + Cow::Owned(format!("{} {}", material_name, item_name)) + } else { + Cow::Borrowed(item_name) + } + } else { + Cow::Borrowed(item_name) + } +} + /// This is used as a key to uniquely identify the modular weapon in asset /// manifests in voxygen (Main component, material, hands) pub type ModularWeaponKey = (String, String, Hands); diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index a09d1ad660..912cc089bb 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -2,7 +2,7 @@ // version in voxygen\src\meta.rs in order to reset save files to being empty use crate::{ - assets::{self, Asset, AssetExt}, + assets::{self, Asset, AssetExt, AssetHandle}, comp::{skills::Skill, CharacterAbility}, }; use hashbrown::HashMap; @@ -217,6 +217,10 @@ impl DivAssign for Stats { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MaterialStatManifest(pub HashMap); +impl MaterialStatManifest { + pub fn load() -> AssetHandle { Self::load_expect("common.material_stats_manifest") } +} + // This could be a Compound that also loads the keys, but the RecipeBook // Compound impl already does that, so checking for existence here is redundant. impl Asset for MaterialStatManifest { @@ -225,13 +229,6 @@ impl Asset for MaterialStatManifest { const EXTENSION: &'static str = "ron"; } -impl Default for MaterialStatManifest { - fn default() -> MaterialStatManifest { - // TODO: Don't do this, loading a default should have no ability to panic - MaterialStatManifest::load_expect_cloned("common.material_stats_manifest") - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Tool { pub kind: ToolKind, @@ -348,16 +345,9 @@ pub struct AbilityItem { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AbilityMap(HashMap>); -impl Default for AbilityMap { - fn default() -> Self { - // TODO: Revert to old default - if let Ok(map) = Self::load_cloned("common.abilities.ability_set_manifest") { - map - } else { - let mut map = HashMap::new(); - map.insert(AbilitySpec::Tool(ToolKind::Empty), AbilitySet::default()); - AbilityMap(map) - } +impl AbilityMap { + pub fn load() -> AssetHandle { + Self::load_expect("common.abilities.ability_set_manifest") } } diff --git a/common/src/recipe.rs b/common/src/recipe.rs index 8ba5d80011..edef6e4daa 100644 --- a/common/src/recipe.rs +++ b/common/src/recipe.rs @@ -22,13 +22,19 @@ pub enum RecipeInput { /// Similar to RecipeInput::Tag(_), but all items must be the same. /// Specifically this means that a mix of different items with the tag /// cannot be used. - TagSameItem(ItemTag, u32), + /// TODO: Currently requires that all items must be in the same slot. + /// Eventually should be reworked so that items can be spread over multiple + /// slots. + TagSameItem(ItemTag), /// List is similar to tag, but has items defined in centralized file /// Similar to RecipeInput::TagSameItem(_), all items must be the same, they /// cannot be a mix of different items defined in the list. // Intent of using List over Tag is to make it harder for tag to be innocuously added to an // item breaking a recipe - ListSameItem(Vec>, u32), + /// TODO: Currently requires that all items must be in the same slot. + /// Eventually should be reworked so that items can be spread over multiple + /// slots. + ListSameItem(Vec>), } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -62,7 +68,8 @@ impl Recipe { self.inputs .iter() .enumerate() - .for_each(|(i, (input, mut required, mut is_component))| { + .for_each(|(i, (input, amount, mut is_component))| { + let mut required = *amount; // Check used for recipes that have an input that is not consumed, e.g. // craftsman hammer let mut contains_any = false; @@ -75,7 +82,7 @@ impl Recipe { // Checks that the item in the slot can be used for the input if let Some(item) = inv .get(*slot) - .filter(|item| item.matches_recipe_input(input)) + .filter(|item| item.matches_recipe_input(input, *amount)) { // Gets the number of items claimed from the slot, or sets to 0 if slot has // not been claimed by another input yet @@ -165,14 +172,15 @@ impl Recipe { // The inputs to a recipe that have missing items, and the amount missing let mut missing = Vec::<(&RecipeInput, u32)>::new(); - for (i, (input, mut needed, _)) in self.inputs().enumerate() { + for (i, (input, amount, _)) in self.inputs().enumerate() { + let mut needed = amount; let mut contains_any = false; // Checks through every slot, filtering to only those that contain items that // can satisfy the input for (inv_slot_id, slot) in inv.slots_with_id() { if let Some(item) = slot .as_ref() - .filter(|item| item.matches_recipe_input(&*input)) + .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)); @@ -390,14 +398,14 @@ impl assets::Compound for RecipeBook { let def = match &input { RawRecipeInput::Item(name) => RecipeInput::Item(Arc::::load_cloned(name)?), RawRecipeInput::Tag(tag) => RecipeInput::Tag(*tag), - RawRecipeInput::TagSameItem(tag) => RecipeInput::TagSameItem(*tag, *amount), + RawRecipeInput::TagSameItem(tag) => RecipeInput::TagSameItem(*tag), RawRecipeInput::ListSameItem(list) => { let assets = &ItemList::load_expect(list).read().0; let items = assets .iter() .map(|asset| Arc::::load_expect_cloned(asset)) .collect(); - RecipeInput::ListSameItem(items, *amount) + RecipeInput::ListSameItem(items) }, }; Ok((def, *amount, *is_mod_comp)) diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs index 82b9fdbc42..b82c849978 100644 --- a/common/systems/src/character_behavior.rs +++ b/common/systems/src/character_behavior.rs @@ -43,7 +43,7 @@ pub struct ReadData<'a> { stats: ReadStorage<'a, Stats>, skill_sets: ReadStorage<'a, SkillSet>, active_abilities: ReadStorage<'a, ActiveAbilities>, - msm: Read<'a, MaterialStatManifest>, + msm: ReadExpect<'a, MaterialStatManifest>, combos: ReadStorage<'a, Combo>, alignments: ReadStorage<'a, comp::Alignment>, terrain: ReadExpect<'a, TerrainGrid>, diff --git a/server/src/lib.rs b/server/src/lib.rs index aa702a788c..f7dd109548 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -73,7 +73,7 @@ use common::{ calendar::Calendar, character::CharacterId, cmd::ChatCommand, - comp::{self, item::MaterialStatManifest}, + comp, event::{EventBus, ServerEvent}, recipe::default_recipe_book, resources::{BattleMode, Time, TimeOfDay}, @@ -310,7 +310,7 @@ impl Server { ); state.ecs_mut().insert(ability_map); - let msm = comp::inventory::item::MaterialStatManifest::default(); + let msm = comp::inventory::item::MaterialStatManifest::load().cloned(); state.ecs_mut().insert(msm); state.ecs_mut().insert(CharacterLoader::new( @@ -1087,7 +1087,11 @@ impl Server { client_timeout: self.settings().client_timeout, world_map: self.map.clone(), recipe_book: default_recipe_book().cloned(), - material_stats: MaterialStatManifest::default(), + material_stats: (&*self + .state + .ecs() + .read_resource::()) + .clone(), ability_map: (&*self .state .ecs() diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index 14b6824a44..10bb54c36d 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -36,8 +36,9 @@ pub struct ItemModelPair { // shouldn't matter unless someone's hot-reloading material stats on the live // server lazy_static! { - pub static ref MATERIAL_STATS_MANIFEST: MaterialStatManifest = MaterialStatManifest::default(); - pub static ref ABILITY_MAP: AbilityMap = AbilityMap::default(); + pub static ref MATERIAL_STATS_MANIFEST: MaterialStatManifest = + MaterialStatManifest::load().cloned(); + pub static ref ABILITY_MAP: AbilityMap = AbilityMap::load().cloned(); } /// Returns a vector that contains all item rows to upsert; parent is diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs index 5ff0682e54..bd0cc90d57 100644 --- a/voxygen/src/hud/crafting.rs +++ b/voxygen/src/hud/crafting.rs @@ -198,12 +198,10 @@ impl CraftingTab { }, CraftingTab::Glider => matches!(&*item.kind(), ItemKind::Glider(_)), CraftingTab::Potion => item.tags().contains(&ItemTag::Potion), - CraftingTab::ProcessedMaterial => item.tags().iter().any(|tag| { - matches!( - tag, - &ItemTag::MaterialKind(_) | &ItemTag::Leather | &ItemTag::BaseMaterial - ) - }), + CraftingTab::ProcessedMaterial => item + .tags() + .iter() + .any(|tag| matches!(tag, &ItemTag::MaterialKind(_) | &ItemTag::BaseMaterial)), CraftingTab::Bag => item.tags().contains(&ItemTag::Bag), CraftingTab::Tool => item.tags().contains(&ItemTag::CraftingTool), CraftingTab::Utility => item.tags().contains(&ItemTag::Utility), @@ -469,10 +467,10 @@ impl<'a> Widget for Crafting<'a> { let input_name = match input { RecipeInput::Item(def) => def.name(), RecipeInput::Tag(tag) => Cow::Borrowed(tag.name()), - RecipeInput::TagSameItem(tag, _) => Cow::Borrowed(tag.name()), + RecipeInput::TagSameItem(tag) => Cow::Borrowed(tag.name()), // Works, but probably will have some...interesting false positives // Code reviewers probably required to do magic to make not hacky - RecipeInput::ListSameItem(defs, _) => { + RecipeInput::ListSameItem(defs) => { Cow::Owned(defs.iter().map(|def| def.name()).collect()) }, } @@ -914,13 +912,13 @@ impl<'a> Widget for Crafting<'a> { for (i, (recipe_input, amount, _)) in recipe.inputs.iter().enumerate() { let item_def = match recipe_input { RecipeInput::Item(item_def) => Arc::clone(item_def), - RecipeInput::Tag(tag) | RecipeInput::TagSameItem(tag, _) => { + RecipeInput::Tag(tag) | RecipeInput::TagSameItem(tag) => { Arc::::load_expect_cloned( self.inventory .slots() .find_map(|slot| { slot.as_ref().and_then(|item| { - if item.matches_recipe_input(recipe_input) { + if item.matches_recipe_input(recipe_input, *amount) { Some(item.item_definition_id()) } else { None @@ -930,12 +928,12 @@ impl<'a> Widget for Crafting<'a> { .unwrap_or_else(|| tag.exemplar_identifier()), ) }, - RecipeInput::ListSameItem(item_defs, _) => Arc::::load_expect_cloned( + RecipeInput::ListSameItem(item_defs) => Arc::::load_expect_cloned( self.inventory .slots() .find_map(|slot| { slot.as_ref().and_then(|item| { - if item.matches_recipe_input(recipe_input) { + if item.matches_recipe_input(recipe_input, *amount) { Some(item.item_definition_id()) } else { None @@ -1047,10 +1045,10 @@ impl<'a> Widget for Crafting<'a> { // Ingredients let name = match recipe_input { RecipeInput::Item(_) => item_def.name().to_string(), - RecipeInput::Tag(tag) | RecipeInput::TagSameItem(tag, _) => { + RecipeInput::Tag(tag) | RecipeInput::TagSameItem(tag) => { format!("Any {} item", tag.name()) }, - RecipeInput::ListSameItem(item_defs, _) => { + RecipeInput::ListSameItem(item_defs) => { format!( "Any of {}", item_defs.iter().map(|def| def.name()).collect::()