diff --git a/client/src/lib.rs b/client/src/lib.rs index 489cf9ff0b..fb00511d0b 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1084,10 +1084,12 @@ impl Client { /// Crafts modular weapon from components in the provided slots. /// `sprite_pos` should be the location of the necessary crafting station in /// range of the player. + /// Returns whether or not the networking event was sent (which is based on + /// whether the player has two modular components in the provided slots) pub fn craft_modular_weapon( &mut self, - slot_a: InvSlotId, - slot_b: InvSlotId, + primary_component: InvSlotId, + secondary_component: InvSlotId, sprite_pos: Option>, ) -> bool { let inventories = self.inventories(); @@ -1112,17 +1114,10 @@ impl Client { _ => None, }; - // Gets slot order of damage and held components if two provided slots contains - // both a primary and secondary component - let slot_order = match (mod_kind(slot_a), mod_kind(slot_b)) { - (Some(ModKind::Primary), Some(ModKind::Secondary)) => Some((slot_a, slot_b)), - (Some(ModKind::Secondary), Some(ModKind::Primary)) => Some((slot_b, slot_a)), - _ => None, - }; - - drop(inventories); - - if let Some((primary_component, secondary_component)) = slot_order { + if let (Some(ModKind::Primary), Some(ModKind::Secondary)) = + (mod_kind(primary_component), mod_kind(secondary_component)) + { + drop(inventories); self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( InventoryEvent::CraftRecipe { craft_event: CraftEvent::ModularWeapon { diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 6c4a2768a4..4726980e78 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -967,12 +967,20 @@ mod tests { #[test] fn test_load_kits() { let kits = KitManifest::load_expect(KIT_MANIFEST_PATH).read(); + let mut rng = rand::thread_rng(); for kit in kits.0.values() { for (item_id, _) in kit.iter() { match item_id { - KitSpec::Item(item_id) => std::mem::drop(Item::new_from_asset_expect(item_id)), + KitSpec::Item(item_id) => { + Item::new_from_asset_expect(item_id); + }, KitSpec::ModularWeapon { tool, material } => { - std::mem::drop(comp::item::modular::random_weapon(*tool, *material, None)) + comp::item::modular::random_weapon(*tool, *material, None, &mut rng) + .unwrap_or_else(|_| { + panic!( + "Failed to synthesize a modular {tool:?} made of {material:?}." + ) + }); }, } } diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index f5f3b96964..78dddb9568 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -90,7 +90,7 @@ pub trait TagExampleInfo { fn name(&self) -> &str; /// What item to show in the crafting hud if the player has nothing with the /// tag - fn exemplar_identifier(&self) -> &str; + fn exemplar_identifier(&self) -> Option<&str>; } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr)] @@ -214,7 +214,7 @@ impl Material { impl TagExampleInfo for Material { fn name(&self) -> &str { self.into() } - fn exemplar_identifier(&self) -> &str { self.asset_identifier().unwrap_or("") } + fn exemplar_identifier(&self) -> Option<&str> { self.asset_identifier() } } #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] @@ -250,18 +250,18 @@ impl TagExampleInfo for ItemTag { } // TODO: Autogenerate these? - fn exemplar_identifier(&self) -> &str { + fn exemplar_identifier(&self) -> Option<&str> { match self { - ItemTag::Material(_) => "common.items.tag_examples.placeholder", - ItemTag::MaterialKind(_) => "common.items.tag_examples.placeholder", - ItemTag::Cultist => "common.items.tag_examples.cultist", - ItemTag::Potion => "common.items.tag_examples.placeholder", - ItemTag::Food => "common.items.tag_examples.placeholder", - ItemTag::BaseMaterial => "common.items.tag_examples.placeholder", - ItemTag::CraftingTool => "common.items.tag_examples.placeholder", - ItemTag::Utility => "common.items.tag_examples.placeholder", - ItemTag::Bag => "common.items.tag_examples.placeholder", - ItemTag::SalvageInto(_) => "common.items.tag_examples.placeholder", + ItemTag::Material(material) => material.exemplar_identifier(), + ItemTag::MaterialKind(_) => None, + ItemTag::Cultist => Some("common.items.tag_examples.cultist"), + ItemTag::Potion => None, + ItemTag::Food => None, + ItemTag::BaseMaterial => None, + ItemTag::CraftingTool => None, + ItemTag::Utility => None, + ItemTag::Bag => None, + ItemTag::SalvageInto(_) => None, } } } @@ -286,6 +286,7 @@ pub enum ItemKind { }, Ingredient { kind: String, + /// Used to generate names for modular items composed of this ingredient descriptor: String, }, TagExamples { @@ -339,10 +340,6 @@ pub struct Item { /// item_def is hidden because changing the item definition for an item /// could change invariants like whether it was stackable (invalidating /// the amount). - #[serde( - serialize_with = "serialize_item_base", - deserialize_with = "deserialize_item_base" - )] item_base: ItemBase, /// components is hidden to maintain the following invariants: /// - It should only contain modular components (and enhancements, once they @@ -373,51 +370,6 @@ impl Hash for Item { } } -// Custom serialization for ItemDef, we only want to send the item_definition_id -// over the network, the client will use deserialize_item_def to fetch the -// ItemDef from assets. -fn serialize_item_base(field: &ItemBase, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_str(match field { - ItemBase::Raw(item_def) => &item_def.item_definition_id, - ItemBase::Modular(mod_base) => mod_base.pseudo_item_id(), - }) -} - -// Custom de-serialization for ItemBase to retrieve the ItemBase from assets -// using its asset specifier (item_definition_id) -fn deserialize_item_base<'de, D>(deserializer: D) -> Result -where - D: de::Deserializer<'de>, -{ - struct ItemBaseStringVisitor; - - impl<'de> de::Visitor<'de> for ItemBaseStringVisitor { - type Value = ItemBase; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("item def string") - } - - fn visit_str(self, serialized_item_base: &str) -> Result - where - E: de::Error, - { - Ok( - if serialized_item_base.starts_with("veloren.core.pseudo_items.modular.") { - ItemBase::Modular(ModularBase::load_from_pseudo_id(serialized_item_base)) - } else { - ItemBase::Raw(Arc::::load_expect_cloned(serialized_item_base)) - }, - ) - } - } - - deserializer.deserialize_str(ItemBaseStringVisitor) -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub enum ItemName { Direct(String), @@ -425,12 +377,61 @@ pub enum ItemName { Component(String), } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug)] pub enum ItemBase { Raw(Arc), Modular(modular::ModularBase), } +impl Serialize for ItemBase { + // Custom serialization for ItemDef, we only want to send the item_definition_id + // over the network, the client will use deserialize_item_def to fetch the + // ItemDef from assets. + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(match self { + ItemBase::Raw(item_def) => &item_def.item_definition_id, + ItemBase::Modular(mod_base) => mod_base.pseudo_item_id(), + }) + } +} + +impl<'de> Deserialize<'de> for ItemBase { + // Custom de-serialization for ItemBase to retrieve the ItemBase from assets + // using its asset specifier (item_definition_id) + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct ItemBaseStringVisitor; + + impl<'de> de::Visitor<'de> for ItemBaseStringVisitor { + type Value = ItemBase; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("item def string") + } + + fn visit_str(self, serialized_item_base: &str) -> Result + where + E: de::Error, + { + Ok( + if serialized_item_base.starts_with(crate::modular_item_id_prefix!()) { + ItemBase::Modular(ModularBase::load_from_pseudo_id(serialized_item_base)) + } else { + ItemBase::Raw(Arc::::load_expect_cloned(serialized_item_base)) + }, + ) + } + } + + deserializer.deserialize_str(ItemBaseStringVisitor) + } +} + impl ItemBase { fn num_slots(&self) -> u16 { match self { @@ -672,7 +673,7 @@ impl Item { components, slots: vec![None; inner_item.num_slots() as usize], item_base: inner_item, - // Updated immediately below + // These fields are updated immediately below item_config: None, hash: 0, }; @@ -963,19 +964,27 @@ impl Item { pub fn ability_spec(&self) -> Option> { match &self.item_base { - ItemBase::Raw(item_def) => item_def.ability_spec.as_ref().map(Cow::Borrowed).or({ - // If no custom ability set is specified, fall back to abilityset of tool kind. - if let ItemKind::Tool(tool) = &item_def.kind { - Some(Cow::Owned(AbilitySpec::Tool(tool.kind))) - } else { - None - } - }), + ItemBase::Raw(item_def) => { + item_def + .ability_spec + .as_ref() + .map(Cow::Borrowed) + .or_else(|| { + // If no custom ability set is specified, fall back to abilityset of tool + // kind. + if let ItemKind::Tool(tool) = &item_def.kind { + Some(Cow::Owned(AbilitySpec::Tool(tool.kind))) + } else { + None + } + }) + }, ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()), } } - // TODO: Maybe try to make slice again instead of vec? + // TODO: Maybe try to make slice again instead of vec? Could also try to make an + // iterator? pub fn tags(&self) -> Vec { match &self.item_base { ItemBase::Raw(item_def) => item_def.tags.to_vec(), diff --git a/common/src/comp/inventory/item/modular.rs b/common/src/comp/inventory/item/modular.rs index 58192a8e39..fad781f831 100644 --- a/common/src/comp/inventory/item/modular.rs +++ b/common/src/comp/inventory/item/modular.rs @@ -6,10 +6,20 @@ use crate::{assets::AssetExt, recipe}; use common_base::dev_panic; use hashbrown::HashMap; use lazy_static::lazy_static; -use rand::{prelude::SliceRandom, thread_rng}; +use rand::{prelude::SliceRandom, Rng}; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, sync::Arc}; +// Macro instead of constant to work with concat! macro. +// DO NOT CHANGE. THIS PREFIX AFFECTS PERSISTENCE AND IF CHANGED A MIGRATION +// MUST BE PERFORMED. +#[macro_export] +macro_rules! modular_item_id_prefix { + () => { + "veloren.core.pseudo_items.modular." + }; +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub enum ModularBase { Tool, @@ -20,7 +30,7 @@ impl ModularBase { // FUNCTION BELOW. pub fn pseudo_item_id(&self) -> &str { match self { - ModularBase::Tool => "veloren.core.pseudo_items.modular.tool", + ModularBase::Tool => concat!(modular_item_id_prefix!(), "tool"), } } @@ -28,7 +38,7 @@ impl ModularBase { // FUNCTION ABOVE. pub fn load_from_pseudo_id(id: &str) -> Self { match id { - "veloren.core.pseudo_items.modular.tool" => ModularBase::Tool, + concat!(modular_item_id_prefix!(), "tool") => ModularBase::Tool, _ => panic!("Attempted to load a non existent pseudo item: {}", id), } } @@ -36,6 +46,8 @@ impl ModularBase { fn resolve_hands(components: &[Item]) -> Hands { // Checks if weapon has components that restrict hands to two. Restrictions to // one hand or no restrictions default to one-handed weapon. + // Note: Hand restriction here will never conflict on components + // TODO: Maybe look into adding an assert at some point? let hand_restriction = components.iter().find_map(|comp| match &*comp.kind() { ItemKind::ModularComponent(mc) => match mc { ModularComponent::ToolPrimaryComponent { @@ -52,7 +64,7 @@ impl ModularBase { } pub fn kind(&self, components: &[Item], msm: &MaterialStatManifest) -> Cow { - pub fn resolve_stats(components: &[Item], msm: &MaterialStatManifest) -> tool::Stats { + fn resolve_stats(components: &[Item], msm: &MaterialStatManifest) -> tool::Stats { components .iter() .filter_map(|comp| { @@ -230,7 +242,9 @@ impl ModularComponent { .reduce(|(stats_a, count_a), (stats_b, count_b)| { (stats_a + stats_b, count_a + count_b) }) - .map_or_else(tool::Stats::one, |(stats_sum, count)| stats_sum / count); + .map_or_else(tool::Stats::one, |(stats_sum, count)| { + stats_sum / (count as f32) + }); Some(*stats * average_material_mult) }, @@ -258,7 +272,6 @@ const SUPPORTED_TOOLKINDS: [ToolKind; 6] = [ type PrimaryComponentPool = HashMap<(ToolKind, String), Vec<(Item, Option)>>; type SecondaryComponentPool = HashMap, Option)>>; -// TODO: Fix this. It broke when changes were made to component recipes lazy_static! { static ref PRIMARY_COMPONENT_POOL: PrimaryComponentPool = { let mut component_pool = HashMap::new(); @@ -323,6 +336,7 @@ pub fn random_weapon( tool: ToolKind, material: Material, hand_restriction: Option, + mut rng: &mut impl Rng, ) -> Result { let result = (|| { if let Some(material_id) = material.asset_identifier() { @@ -330,8 +344,6 @@ pub fn random_weapon( let ability_map = &AbilityMap::load().read(); let msm = &MaterialStatManifest::load().read(); - let mut rng = thread_rng(); - let primary_components = PRIMARY_COMPONENT_POOL .get(&(tool, material_id.to_owned())) .into_iter() @@ -384,10 +396,10 @@ pub fn random_weapon( Err(ModularWeaponCreationError::MaterialNotFound) } })(); - if result.is_err() { + if let Err(err) = &result { let error_str = format!( "Failed to synthesize a modular {tool:?} made of {material:?} that had a hand \ - restriction of {hand_restriction:?}." + restriction of {hand_restriction:?}. Error: {err:?}" ); dev_panic!(error_str) } diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 21c8462d85..35fa4df480 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -193,11 +193,10 @@ impl Mul for Stats { impl MulAssign for Stats { fn mul_assign(&mut self, other: Stats) { *self = *self * other; } } -impl Div for Stats { +impl Div for Stats { type Output = Self; - fn div(self, scalar: usize) -> Self { - let scalar = scalar as f32; + fn div(self, scalar: f32) -> Self { Self { equip_time_secs: self.equip_time_secs / scalar, power: self.power / scalar, @@ -211,7 +210,7 @@ impl Div for Stats { } } impl DivAssign for Stats { - fn div_assign(&mut self, scalar: usize) { *self = *self / scalar; } + fn div_assign(&mut self, scalar: usize) { *self = *self / (scalar as f32); } } #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/common/src/comp/inventory/loadout.rs b/common/src/comp/inventory/loadout.rs index 458383bd2b..c0ea6258d2 100644 --- a/common/src/comp/inventory/loadout.rs +++ b/common/src/comp/inventory/loadout.rs @@ -403,6 +403,8 @@ impl Loadout { } } + /// Update internal computed state of all top level items in this loadout. + /// Used only when loading in persistence code. pub fn persistence_update_all_item_states( &mut self, ability_map: &item::tool::AbilityMap, diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index a7fe277da1..8b261627d5 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -40,7 +40,8 @@ enum ItemSpec { Item(String), Choice(Vec<(Weight, Option)>), /// Modular weapon - /// Example: + /// Parameters in this variant are used to randomly create a modular weapon + /// that meets the provided parameters Example: /// ModularWeapon { /// tool: Sword, /// material: Iron, @@ -76,9 +77,7 @@ impl ItemSpec { tool, material, hands, - } => { - item::modular::random_weapon(*tool, *material, *hands) - }, + } => item::modular::random_weapon(*tool, *material, *hands), } } @@ -98,6 +97,13 @@ impl ItemSpec { } Ok(()) }, + ItemSpec::ModularWeapon { + tool, + material, + hands, + } => { + item::modular::random_weapon(*tool, *material, *hands) + }, } } } @@ -136,11 +142,6 @@ impl Hands { pair_spec.try_to_pair(rng) }, - ItemSpec::ModularWeapon { - tool, - material, - hands, - } => item::modular::random_weapon(*tool, *material, *hands).ok(), } } diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index f22303468c..616f25e5c9 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -784,13 +784,15 @@ impl Inventory { pub fn swap_equipped_weapons(&mut self) { self.loadout.swap_equipped_weapons() } + /// Update internal computed state of all top level items in this loadout. + /// Used only when loading in persistence code. pub fn persistence_update_all_item_states( &mut self, ability_map: &item::tool::AbilityMap, msm: &item::tool::MaterialStatManifest, ) { - self.slots_mut().for_each(|mut slot| { - if let Some(item) = &mut slot { + self.slots_mut().for_each(|slot| { + if let Some(item) = slot { item.update_item_state(ability_map, msm); } }); diff --git a/common/src/comp/inventory/trade_pricing.rs b/common/src/comp/inventory/trade_pricing.rs index f584b93d0a..70e3d966d5 100644 --- a/common/src/comp/inventory/trade_pricing.rs +++ b/common/src/comp/inventory/trade_pricing.rs @@ -812,8 +812,8 @@ mod tests { assert!((lootsum2 - 1.0).abs() < 1e-4); // highly nested - let loot3 = expand_loot_table("common.loot_tables.creature.biped_large.wendigo"); - let lootsum3 = loot3.iter().fold(0.0, |s, i| s + i.0); + // let loot3 = expand_loot_table("common.loot_tables.creature.biped_large.wendigo"); + // let lootsum3 = loot3.iter().fold(0.0, |s, i| s + i.0); // TODO: Re-enable this. See note at top of test (though this specific // table can also be fixed by properly integrating modular weapons into // probability files) diff --git a/common/src/lottery.rs b/common/src/lottery.rs index 6ff83bf99b..1aae7e39bc 100644 --- a/common/src/lottery.rs +++ b/common/src/lottery.rs @@ -96,6 +96,7 @@ pub enum LootSpec> { impl> LootSpec { pub fn to_item(&self) -> Option { + let mut rng = thread_rng(); match self { Self::Item(item) => Item::new_from_asset(item.as_ref()).map_or_else( |e| { @@ -130,7 +131,7 @@ impl> LootSpec { tool, material, hands, - } => item::modular::random_weapon(*tool, *material, *hands).map_or_else( + } => item::modular::random_weapon(*tool, *material, *hands, &mut rng).map_or_else( |e| { warn!( ?e, @@ -138,7 +139,7 @@ impl> LootSpec { Hands: {:?}", tool, material, - hands + hands, ); None }, @@ -159,6 +160,7 @@ pub mod tests { #[cfg(test)] pub fn validate_loot_spec(item: &LootSpec) { + let mut rng = thread_rng(); match item { LootSpec::Item(item) => { Item::new_from_asset_expect(item); @@ -183,12 +185,20 @@ pub mod tests { validate_table_contents(loot_table); }, LootSpec::Nothing => {}, - // TODO: Figure out later LootSpec::ModularWeapon { tool, material, hands, - } => std::mem::drop(item::modular::random_weapon(*tool, *material, *hands)), + } => { + item::modular::random_weapon(*tool, *material, *hands, &mut rng).unwrap_or_else( + |_| { + panic!( + "Failed to synthesize a modular {tool:?} made of {material:?} that \ + had a hand restriction of {hands:?}." + ) + }, + ); + }, } } diff --git a/common/src/recipe.rs b/common/src/recipe.rs index b5dc70e964..be8b6eaad3 100644 --- a/common/src/recipe.rs +++ b/common/src/recipe.rs @@ -156,7 +156,11 @@ impl Recipe { .map(|(item_def, amount, is_mod_comp)| (item_def, *amount, *is_mod_comp)) } - /// Determines if the inventory contains the ingredients for a given recipe + /// 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( &self, inv: &Inventory, @@ -170,14 +174,15 @@ impl Recipe { } /// Determine whether the inventory contains the ingredients for a recipe. -/// If it does, return a vec of inventory slots that contain the +/// 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. +// Note: Doc comment duplicated on two public functions that call this function #[allow(clippy::type_complexity)] -fn inventory_contains_ingredients<'a, 'b, I: Iterator>( +fn inventory_contains_ingredients<'a, I: Iterator>( ingredients: I, - inv: &'b Inventory, + inv: &Inventory, ) -> Result, Vec<(&'a RecipeInput, u32)>> { // Hashmap tracking the quantity that needs to be removed from each slot (so // that it doesn't think a slot can provide more items than it contains) @@ -278,7 +283,7 @@ pub fn modular_weapon( }) } - // Checks if both components are comptabile, and if so returns the toolkind to + // Checks if both components are compatible, and if so returns the toolkind to // make weapon of let compatiblity = if let (Some(primary_component), Some(secondary_component)) = ( unwrap_modular(inv, primary_component), @@ -387,7 +392,7 @@ impl assets::Asset for RawRecipeBook { } #[derive(Deserialize, Clone)] -pub struct ItemList(pub Vec); +pub struct ItemList(Vec); impl assets::Asset for ItemList { type Loader = assets::RonLoader; @@ -484,12 +489,12 @@ impl assets::Asset for RawComponentRecipeBook { #[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)] pub struct ComponentKey { // Can't use ItemDef here because hash needed, item definition id used instead - // TODO: Figure out how to get back to ItemDef maybe? - // Keeping under ComponentRecipe may be sufficient? // TODO: Make more general for other things that have component inputs that should be tracked // after item creation pub toolkind: ToolKind, + /// Refers to the item definition id of the material pub material: String, + /// Refers to the item definition id of the material pub modifier: Option, } @@ -503,7 +508,7 @@ pub struct ComponentRecipe { } impl ComponentRecipe { - /// Craft an itme that has components, returning a list of missing items on + /// Craft an item that has components, returning a list of missing items on /// failure pub fn craft_component( &self, @@ -621,7 +626,10 @@ impl ComponentRecipe { } #[allow(clippy::type_complexity)] - /// Determines if the inventory contains the ignredients for a given recipe + /// Determine whether the inventory contains the additional ingredients for + /// a component recipe. If it does, return a vec of inventory slots that + /// contain the ingredients needed, whose positions correspond to particular + /// recipe are missing. pub fn inventory_contains_additional_ingredients<'a>( &self, inv: &'a Inventory, @@ -636,12 +644,6 @@ impl ComponentRecipe { pub fn item_output(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Item { match &self.output { - ComponentOutput::Item(item_def) => Item::new_from_item_base( - ItemBase::Raw(Arc::clone(item_def)), - Vec::new(), - ability_map, - msm, - ), ComponentOutput::ItemComponents { item: item_def, components, @@ -669,31 +671,38 @@ impl ComponentRecipe { pub fn inputs(&self) -> impl ExactSizeIterator { pub struct ComponentRecipeInputsIterator<'a> { - material: bool, - modifier: bool, - index: usize, - recipe: &'a ComponentRecipe, + // material: bool, + // modifier: bool, + // index: usize, + // recipe: &'a ComponentRecipe, + 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)> { - if !self.material { - self.material = true; - Some(&self.recipe.material) - } else if !self.modifier { - self.modifier = true; - if self.recipe.modifier.is_some() { - self.recipe.modifier.as_ref() - } else { - self.index += 1; - self.recipe.additional_inputs.get(self.index - 1) - } - } else { - self.index += 1; - self.recipe.additional_inputs.get(self.index - 1) - } + // if !self.material { + // self.material = true; + // Some(&self.recipe.material) + // } else if !self.modifier { + // self.modifier = true; + // if self.recipe.modifier.is_some() { + // self.recipe.modifier.as_ref() + // } else { + // self.index += 1; + // self.recipe.additional_inputs.get(self.index - 1) + // } + // } else { + // self.index += 1; + // self.recipe.additional_inputs.get(self.index - 1) + // } + self.material + .take() + .or_else(|| self.modifier.take()) + .or_else(|| self.additional_inputs.next()) } } @@ -703,17 +712,24 @@ impl ComponentRecipe { fn into_iter(self) -> Self::IntoIter { ComponentRecipeInputsIterator { - material: false, - modifier: false, - index: 0, - recipe: self, + // material: false, + // modifier: false, + // index: 0, + // recipe: self, + 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 { - 1 + self.recipe.modifier.is_some() as usize + self.recipe.additional_inputs.len() + // 1 + self.recipe.modifier.is_some() as usize + + // self.recipe.additional_inputs.len() + self.material.is_some() as usize + + self.modifier.is_some() as usize + + self.additional_inputs.len() } } @@ -732,7 +748,6 @@ struct RawComponentRecipe { #[derive(Clone, Debug, Serialize, Deserialize)] enum ComponentOutput { - Item(Arc), ItemComponents { item: Arc, components: Vec>, @@ -741,7 +756,6 @@ enum ComponentOutput { #[derive(Clone, Debug, Serialize, Deserialize)] enum RawComponentOutput { - Item(String), ItemComponents { item: String, components: Vec, @@ -778,9 +792,6 @@ impl assets::Compound for ComponentRecipeBook { output: &RawComponentOutput, ) -> Result { let def = match &output { - RawComponentOutput::Item(def) => { - ComponentOutput::Item(Arc::::load_cloned(def)?) - }, RawComponentOutput::ItemComponents { item: def, components: defs, diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index b778141cc3..8d7a5dcaff 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -400,6 +400,12 @@ impl SpriteKind { SpriteKind::ChestBuried => table("common.loot_tables.sprite.chest-buried"), SpriteKind::Mud => table("common.loot_tables.sprite.mud"), SpriteKind::Crate => table("common.loot_tables.sprite.crate"), + SpriteKind::Wood => item("common.items.log.wood"), + SpriteKind::Bamboo => item("common.items.log.bamboo"), + SpriteKind::Hardwood => item("common.items.log.hardwood"), + SpriteKind::Ironwood => item("common.items.log.ironwood"), + SpriteKind::Frostwood => item("common.items.log.frostwood"), + SpriteKind::Eldwood => item("common.items.log.eldwood"), _ => return None, }) } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 33a9590137..6be7521a46 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -53,7 +53,7 @@ use common_state::{BuildAreaError, BuildAreas}; use core::{cmp::Ordering, convert::TryFrom, time::Duration}; use hashbrown::{HashMap, HashSet}; use humantime::Duration as HumanDuration; -use rand::Rng; +use rand::{thread_rng, Rng}; use specs::{ saveload::MarkerAllocator, storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt, }; @@ -1941,11 +1941,12 @@ where return Err("Inventory doesn't have enough slots".to_owned()); } for (item_id, quantity) in kit { + let mut rng = thread_rng(); let mut item = match &item_id { KitSpec::Item(item_id) => comp::Item::new_from_asset(item_id) .map_err(|_| format!("Unknown item: {:#?}", item_id))?, KitSpec::ModularWeapon { tool, material } => { - comp::item::modular::random_weapon(*tool, *material, None) + comp::item::modular::random_weapon(*tool, *material, None, &mut rng) .map_err(|err| format!("{:#?}", err))? }, }; diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index ab86349c73..0fa8a96f72 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -264,18 +264,20 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv Slot::Inventory(slot) => { use item::ItemKind; - let is_equippable = inventory - .get(slot) - .map_or(false, |i| i.kind().is_equippable()); - if is_equippable { - if let Some(lantern_info) = - inventory.get(slot).and_then(|i| match &*i.kind() { + let (is_equippable, lantern_info) = + inventory.get(slot).map_or((false, None), |i| { + let kind = i.kind(); + let is_equippable = kind.is_equippable(); + let lantern_info = match &*kind { ItemKind::Lantern(lantern) => { Some((lantern.color(), lantern.strength())) }, _ => None, - }) - { + }; + (is_equippable, lantern_info) + }); + if is_equippable { + if let Some(lantern_info) = lantern_info { swap_lantern(&mut state.ecs().write_storage(), entity, lantern_info); } if let Some(pos) = state.ecs().read_storage::().get(entity) { @@ -595,37 +597,39 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv use comp::controller::CraftEvent; use recipe::ComponentKey; let recipe_book = default_recipe_book().read(); - let component_recipes = default_component_recipe_book().read(); let ability_map = &state.ecs().read_resource::(); let msm = state.ecs().read_resource::(); + let get_craft_sprite = |state, sprite_pos: Option>| { + sprite_pos + .filter(|pos| { + let entity_cylinder = get_cylinder(state, entity); + let in_range = within_pickup_range(entity_cylinder, || { + Some(find_dist::Cube { + min: pos.as_(), + side_length: 1.0, + }) + }); + if !in_range { + debug!( + ?entity_cylinder, + "Failed to craft recipe as not within range of required sprite, \ + sprite pos: {}", + pos + ); + } + in_range + }) + .and_then(|pos| state.terrain().get(pos).ok().copied()) + .and_then(|block| block.get_sprite()) + }; + let crafted_items = match craft_event { CraftEvent::Simple { recipe, slots } => recipe_book .get(&recipe) .filter(|r| { if let Some(needed_sprite) = r.craft_sprite { - let sprite = craft_sprite - .filter(|pos| { - let entity_cylinder = get_cylinder(state, entity); - if !within_pickup_range(entity_cylinder, || { - Some(find_dist::Cube { - min: pos.as_(), - side_length: 1.0, - }) - }) { - debug!( - ?entity_cylinder, - "Failed to craft recipe as not within range of \ - required sprite, sprite pos: {}", - pos - ); - false - } else { - true - } - }) - .and_then(|pos| state.terrain().get(pos).ok().copied()) - .and_then(|block| block.get_sprite()); + let sprite = get_craft_sprite(state, craft_sprite); Some(needed_sprite) == sprite } else { true @@ -641,28 +645,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .ok() }), CraftEvent::Salvage(slot) => { - let sprite = craft_sprite - .filter(|pos| { - let entity_cylinder = get_cylinder(state, entity); - if !within_pickup_range(entity_cylinder, || { - Some(find_dist::Cube { - min: pos.as_(), - side_length: 1.0, - }) - }) { - debug!( - ?entity_cylinder, - "Failed to craft recipe as not within range of required \ - sprite, sprite pos: {}", - pos - ); - false - } else { - true - } - }) - .and_then(|pos| state.terrain().get(pos).ok().copied()) - .and_then(|block| block.get_sprite()); + let sprite = get_craft_sprite(state, craft_sprite); if matches!(sprite, Some(SpriteKind::DismantlingBench)) { recipe::try_salvage(&mut inventory, slot, ability_map, &msm).ok() } else { @@ -673,28 +656,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv primary_component, secondary_component, } => { - let sprite = craft_sprite - .filter(|pos| { - let entity_cylinder = get_cylinder(state, entity); - if !within_pickup_range(entity_cylinder, || { - Some(find_dist::Cube { - min: pos.as_(), - side_length: 1.0, - }) - }) { - debug!( - ?entity_cylinder, - "Failed to craft recipe as not within range of required \ - sprite, sprite pos: {}", - pos - ); - false - } else { - true - } - }) - .and_then(|pos| state.terrain().get(pos).ok().copied()) - .and_then(|block| block.get_sprite()); + let sprite = get_craft_sprite(state, craft_sprite); if matches!(sprite, Some(SpriteKind::CraftingBench)) { recipe::modular_weapon( &mut inventory, @@ -715,6 +677,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv modifier, slots, } => { + let component_recipes = default_component_recipe_book().read(); let item_id = |slot| inventory.get(slot).map(|item| item.item_definition_id()); if let Some(material_item_id) = item_id(material) { component_recipes @@ -725,28 +688,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv }) .filter(|r| { if let Some(needed_sprite) = r.craft_sprite { - let sprite = craft_sprite - .filter(|pos| { - let entity_cylinder = get_cylinder(state, entity); - if !within_pickup_range(entity_cylinder, || { - Some(find_dist::Cube { - min: pos.as_(), - side_length: 1.0, - }) - }) { - debug!( - ?entity_cylinder, - "Failed to craft recipe as not within range \ - of required sprite, sprite pos: {}", - pos - ); - false - } else { - true - } - }) - .and_then(|pos| state.terrain().get(pos).ok().copied()) - .and_then(|block| block.get_sprite()); + let sprite = get_craft_sprite(state, craft_sprite); Some(needed_sprite) == sprite } else { true diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index 687aebec4c..bc9d0d61b7 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -268,7 +268,7 @@ fn get_mutable_item<'a, 'b, T>( inventory_items: &'a [Item], item_indices: &'a HashMap, inventory: &'b mut T, - get_mut_item: &'a dyn Fn(&'b mut T, &str) -> Option<&'b mut VelorenItem>, + get_mut_item: &'a impl Fn(&'b mut T, &str) -> Option<&'b mut VelorenItem>, ) -> Option<&'a mut VelorenItem> where 'b: 'a, diff --git a/voxygen/src/hud/crafting.rs b/voxygen/src/hud/crafting.rs index e50f6c3435..517ae910fe 100644 --- a/voxygen/src/hud/crafting.rs +++ b/voxygen/src/hud/crafting.rs @@ -498,30 +498,18 @@ impl<'a> Widget for Crafting<'a> { } }; - let weapon_recipe = Recipe { + let make_psuedo_recipe = |craft_sprite| Recipe { output: ( Arc::::load_expect_cloned("common.items.weapons.empty.empty"), 0, ), inputs: Vec::new(), - craft_sprite: Some(SpriteKind::CraftingBench), - }; - let metal_comp_recipe = Recipe { - output: ( - Arc::::load_expect_cloned("common.items.weapons.empty.empty"), - 0, - ), - inputs: Vec::new(), - craft_sprite: Some(SpriteKind::Anvil), - }; - let wood_comp_recipe = Recipe { - output: ( - Arc::::load_expect_cloned("common.items.weapons.empty.empty"), - 0, - ), - inputs: Vec::new(), - craft_sprite: Some(SpriteKind::CraftingBench), + craft_sprite: Some(craft_sprite), }; + + let weapon_recipe = make_psuedo_recipe(SpriteKind::CraftingBench); + let metal_comp_recipe = make_psuedo_recipe(SpriteKind::Anvil); + let wood_comp_recipe = make_psuedo_recipe(SpriteKind::CraftingBench); let modular_entries = { let mut modular_entries = BTreeMap::new(); modular_entries.insert( @@ -806,7 +794,6 @@ impl<'a> Widget for Crafting<'a> { }, None => None, } { - // TODO: Avoid reallocating string and appease borrow checker let recipe_name = String::from(recipe_name); let title = if let Some((_recipe, modular_name)) = modular_entries.get(&recipe_name) { *modular_name @@ -852,10 +839,8 @@ impl<'a> Widget for Crafting<'a> { _ => RecipeKind::Simple, }; - let mut modular_slot_check_hack = (None, None, false); - // Output slot, tags, and modular input slots - match recipe_kind { + let (modular_primary_slot, modular_secondary_slot, can_perform) = match recipe_kind { RecipeKind::ModularWeapon | RecipeKind::Component(_) => { let mut slot_maker = SlotMaker { empty_slot: self.imgs.inv_slot, @@ -894,8 +879,8 @@ impl<'a> Widget for Crafting<'a> { index: 0, invslot: self.show.crafting_fields.recipe_inputs.get(&0).copied(), requirement: match recipe_kind { - RecipeKind::ModularWeapon => |inv, slot| { - inv.and_then(|inv| inv.get(slot)).map_or(false, |item| { + RecipeKind::ModularWeapon => |item| { + item.map_or(false, |item| { matches!( &*item.kind(), ItemKind::ModularComponent( @@ -914,8 +899,8 @@ impl<'a> Widget for Crafting<'a> { // }, RecipeKind::Component( ToolKind::Sword | ToolKind::Axe | ToolKind::Hammer, - ) => |inv, slot| { - inv.and_then(|inv| inv.get(slot)).map_or(false, |item| { + ) => |item| { + item.map_or(false, |item| { matches!(&*item.kind(), ItemKind::Ingredient { .. }) && item .tags() @@ -928,8 +913,8 @@ impl<'a> Widget for Crafting<'a> { }, RecipeKind::Component( ToolKind::Bow | ToolKind::Staff | ToolKind::Sceptre, - ) => |inv, slot| { - inv.and_then(|inv| inv.get(slot)).map_or(false, |item| { + ) => |item| { + item.map_or(false, |item| { matches!(&*item.kind(), ItemKind::Ingredient { .. }) && item .tags() @@ -940,7 +925,7 @@ impl<'a> Widget for Crafting<'a> { .any(|tag| matches!(tag, ItemTag::Material(_))) }) }, - RecipeKind::Component(_) | RecipeKind::Simple => |_, _| false, + RecipeKind::Component(_) | RecipeKind::Simple => |_| unreachable!(), }, }; @@ -949,44 +934,28 @@ impl<'a> Widget for Crafting<'a> { .top_left_with_margins_on(state.ids.modular_art, 4.0, 4.0) .parent(state.ids.align_ing); - if let Some(slot) = primary_slot.invslot { - if let Some(item) = self.inventory.get(slot) { - primary_slot_widget - .with_item_tooltip( - self.item_tooltip_manager, - core::iter::once(item as &dyn ItemDesc), - &None, - &item_tooltip, - ) - .set(state.ids.modular_inputs[0], ui); - } else { - primary_slot_widget.set(state.ids.modular_inputs[0], ui); - } + if let Some(item) = primary_slot + .invslot + .and_then(|slot| self.inventory.get(slot)) + { + primary_slot_widget + .with_item_tooltip( + self.item_tooltip_manager, + core::iter::once(item as &dyn ItemDesc), + &None, + &item_tooltip, + ) + .set(state.ids.modular_inputs[0], ui); } else { primary_slot_widget.set(state.ids.modular_inputs[0], ui); } - // Not sure why it doesn't work, maybe revisit later? - // if let Some(item) = primary_slot.invslot.and_then(|slot| - // self.inventory.get(slot)) { primary_slot_widget - // .with_item_tooltip( - // self.item_tooltip_manager, - // core::iter::once(item as &dyn ItemDesc), - // &None, - // &item_tooltip, - // ) - // .set(state.ids.modular_inputs[0], ui); - // } else { - // primary_slot_widget - // .set(state.ids.modular_inputs[0], ui); - // } - let secondary_slot = CraftSlot { index: 1, invslot: self.show.crafting_fields.recipe_inputs.get(&1).copied(), requirement: match recipe_kind { - RecipeKind::ModularWeapon => |inv, slot| { - inv.and_then(|inv| inv.get(slot)).map_or(false, |item| { + RecipeKind::ModularWeapon => |item| { + item.map_or(false, |item| { matches!( &*item.kind(), ItemKind::ModularComponent( @@ -1003,13 +972,13 @@ impl<'a> Widget for Crafting<'a> { // _recipe)| key.toolkind == toolkind).any(|(key, _recipe)| key.modifier // == Some(item.item_definition_id())) // }) }, - RecipeKind::Component(_) => |inv, slot| { - inv.and_then(|inv| inv.get(slot)).map_or(false, |item| { + RecipeKind::Component(_) => |item| { + item.map_or(false, |item| { item.item_definition_id() .starts_with("common.items.crafting_ing.animal_misc") }) }, - RecipeKind::Simple => |_, _| false, + RecipeKind::Simple => |_| unreachable!(), }, }; @@ -1018,42 +987,27 @@ impl<'a> Widget for Crafting<'a> { .top_right_with_margins_on(state.ids.modular_art, 4.0, 4.0) .parent(state.ids.align_ing); - if let Some(slot) = secondary_slot.invslot { - if let Some(item) = self.inventory.get(slot) { - secondary_slot_widget - .with_item_tooltip( - self.item_tooltip_manager, - core::iter::once(item as &dyn ItemDesc), - &None, - &item_tooltip, - ) - .set(state.ids.modular_inputs[1], ui); - } else { - secondary_slot_widget.set(state.ids.modular_inputs[1], ui); - } + if let Some(item) = secondary_slot + .invslot + .and_then(|slot| self.inventory.get(slot)) + { + secondary_slot_widget + .with_item_tooltip( + self.item_tooltip_manager, + core::iter::once(item as &dyn ItemDesc), + &None, + &item_tooltip, + ) + .set(state.ids.modular_inputs[1], ui); } else { secondary_slot_widget.set(state.ids.modular_inputs[1], ui); } - // if let Some(item) = secondary_slot.invslot.and_then(|slot| - // self.inventory.get(slot)) { secondary_slot_widget - // .with_item_tooltip( - // self.item_tooltip_manager, - // core::iter::once(item as &dyn ItemDesc), - // &None, - // &item_tooltip, - // ) - // .set(state.ids.modular_inputs[1], ui); - // } else { - // secondary_slot_widget - // .set(state.ids.modular_inputs[1], ui); - // } - let prim_item_placed = primary_slot.invslot.is_some(); let sec_item_placed = secondary_slot.invslot.is_some(); let prim_icon = match recipe_kind { - RecipeKind::ModularWeapon => self.imgs.ing_ico_prim, + RecipeKind::ModularWeapon => self.imgs.icon_primary_comp, RecipeKind::Component(ToolKind::Sword) => self.imgs.icon_ingot, RecipeKind::Component(ToolKind::Axe) => self.imgs.icon_ingot, RecipeKind::Component(ToolKind::Hammer) => self.imgs.icon_ingot, @@ -1064,7 +1018,7 @@ impl<'a> Widget for Crafting<'a> { }; let sec_icon = match recipe_kind { - RecipeKind::ModularWeapon => self.imgs.ing_ico_sec, + RecipeKind::ModularWeapon => self.imgs.icon_secondary_comp, RecipeKind::Component(_) => self.imgs.icon_pelt, _ => self.imgs.not_found, }; @@ -1096,7 +1050,7 @@ impl<'a> Widget for Crafting<'a> { let ability_map = &AbilityMap::load().read(); let msm = &MaterialStatManifest::load().read(); - if let Some(output_item) = match recipe_kind { + let output_item = match recipe_kind { RecipeKind::ModularWeapon => { if let Some((primary_comp, toolkind)) = primary_slot .invslot @@ -1162,7 +1116,9 @@ impl<'a> Widget for Crafting<'a> { } }, RecipeKind::Simple => None, - } { + }; + + if let Some(output_item) = output_item { Button::image(animate_by_pulse( &self .item_imgs @@ -1184,8 +1140,12 @@ impl<'a> Widget for Crafting<'a> { &item_tooltip, ) .set(state.ids.output_img, ui); - modular_slot_check_hack = - (primary_slot.invslot, secondary_slot.invslot, true); + ( + primary_slot.invslot, + secondary_slot.invslot, + self.show.crafting_fields.craft_sprite.map(|(_, s)| s) + == recipe.craft_sprite, + ) } else { Text::new(self.localized_strings.get("hud.crafting.modular_desc")) .mid_top_with_margin_on(state.ids.modular_art, -18.0) @@ -1193,14 +1153,13 @@ impl<'a> Widget for Crafting<'a> { .font_size(self.fonts.cyri.scale(13)) .color(TEXT_COLOR) .set(state.ids.title_main, ui); - Image::new(self.imgs.wep_ico) + Image::new(self.imgs.icon_mod_weap) .middle_of(state.ids.output_img_frame) .color(Some(bg_col)) .w_h(70.0, 70.0) .graphics_for(state.ids.output_img) .set(state.ids.modular_wep_empty_bg, ui); - modular_slot_check_hack = - (primary_slot.invslot, secondary_slot.invslot, false); + (primary_slot.invslot, secondary_slot.invslot, false) } }, RecipeKind::Simple => { @@ -1291,29 +1250,19 @@ impl<'a> Widget for Crafting<'a> { .set(state.ids.tags_ing[i], ui); } } - }, - } - - let can_perform = match recipe_kind { - RecipeKind::ModularWeapon => { - modular_slot_check_hack.2 - && self.show.crafting_fields.craft_sprite.map(|(_, s)| s) - == recipe.craft_sprite - }, - RecipeKind::Component(_) => { - modular_slot_check_hack.2 - && self.show.crafting_fields.craft_sprite.map(|(_, s)| s) - == recipe.craft_sprite - }, - RecipeKind::Simple => { - self.client - .available_recipes() - .get(&recipe_name) - .map_or(false, |cs| { - cs.map_or(true, |cs| { - Some(cs) == self.show.crafting_fields.craft_sprite.map(|(_, s)| s) - }) - }) + ( + None, + None, + self.client + .available_recipes() + .get(&recipe_name) + .map_or(false, |cs| { + cs.map_or(true, |cs| { + Some(cs) + == self.show.crafting_fields.craft_sprite.map(|(_, s)| s) + }) + }), + ) }, }; @@ -1339,12 +1288,12 @@ impl<'a> Widget for Crafting<'a> { .mid_bottom_with_margin_on(state.ids.align_ing, -31.0) .parent(state.ids.window_frame) .set(state.ids.btn_craft, ui) - .was_clicked() + .was_clicked() && can_perform { match recipe_kind { RecipeKind::ModularWeapon => { - if let (Some(primary_slot), Some(secondary_slot), true) = - modular_slot_check_hack + if let (Some(primary_slot), Some(secondary_slot)) = + (modular_primary_slot, modular_secondary_slot) { events.push(Event::CraftModularWeapon { primary_slot, @@ -1353,11 +1302,11 @@ impl<'a> Widget for Crafting<'a> { } }, RecipeKind::Component(toolkind) => { - if let Some(primary_slot) = modular_slot_check_hack.0 { + if let Some(primary_slot) = modular_primary_slot { events.push(Event::CraftModularWeaponComponent { toolkind, material: primary_slot, - modifier: modular_slot_check_hack.1, + modifier: modular_secondary_slot, }); } }, @@ -1450,16 +1399,14 @@ impl<'a> Widget for Crafting<'a> { &mut iter_b }, RecipeKind::Component(toolkind) => { - if let Some(material) = modular_slot_check_hack - .0 + if let Some(material) = modular_primary_slot .and_then(|slot| self.inventory.get(slot)) .map(|item| String::from(item.item_definition_id())) { let component_key = ComponentKey { toolkind, material, - modifier: modular_slot_check_hack - .1 + modifier: modular_secondary_slot .and_then(|slot| self.inventory.get(slot)) .map(|item| String::from(item.item_definition_id())), }; @@ -1553,7 +1500,8 @@ impl<'a> Widget for Crafting<'a> { } }) }) - .unwrap_or_else(|| tag.exemplar_identifier()), + .or_else(|| tag.exemplar_identifier()) + .unwrap_or_else(|| "common.items.weapons.empty.empty"), ) }, RecipeInput::ListSameItem(item_defs) => Arc::::load_expect_cloned( @@ -1572,7 +1520,7 @@ impl<'a> Widget for Crafting<'a> { item_defs .first() .map(|i| i.item_definition_id()) - .unwrap_or("common.items.weapons.empty.empty") + .unwrap_or_else(|| "common.items.weapons.empty.empty") }), ), }; diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index af3c6cd6a6..380b704275 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -128,9 +128,9 @@ image_ids! { icon_bag: "voxygen.element.items.item_bag_leather_large", icon_processed_material: "voxygen.element.ui.crafting.icons.processed_material", crafting_modular_art: "voxygen.element.ui.crafting.modular_weps_art", - ing_ico_prim: "voxygen.element.ui.crafting.icons.blade", - ing_ico_sec: "voxygen.element.ui.crafting.icons.handle", - wep_ico: "voxygen.element.ui.bag.backgrounds.mainhand", + icon_primary_comp: "voxygen.element.ui.crafting.icons.blade", + icon_secondary_comp: "voxygen.element.ui.crafting.icons.handle", + icon_mod_weap: "voxygen.element.ui.bag.backgrounds.mainhand", // Group Window member_frame: "voxygen.element.ui.groups.group_member_frame", member_bg: "voxygen.element.ui.groups.group_member_bg", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index f707d95c85..19bedd3cfe 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -536,13 +536,13 @@ pub enum Event { CraftModularWeapon { primary_slot: InvSlotId, secondary_slot: InvSlotId, - craft_sprite: Option<(Vec3, SpriteKind)>, + craft_sprite: Option>, }, CraftModularWeaponComponent { toolkind: ToolKind, material: InvSlotId, modifier: Option, - craft_sprite: Option<(Vec3, SpriteKind)>, + craft_sprite: Option>, }, InviteMember(Uid), AcceptInvite, @@ -2963,7 +2963,11 @@ impl Hud { events.push(Event::CraftModularWeapon { primary_slot, secondary_slot, - craft_sprite: self.show.crafting_fields.craft_sprite, + craft_sprite: self + .show + .crafting_fields + .craft_sprite + .map(|(pos, _sprite)| pos), }); }, crafting::Event::CraftModularWeaponComponent { @@ -2975,7 +2979,11 @@ impl Hud { toolkind, material, modifier, - craft_sprite: self.show.crafting_fields.craft_sprite, + craft_sprite: self + .show + .crafting_fields + .craft_sprite + .map(|(pos, _sprite)| pos), }); }, crafting::Event::Close => { @@ -3467,7 +3475,11 @@ impl Hud { } } else if let (Inventory(i), Crafting(c)) = (a, b) { // Add item to crafting input - if (c.requirement)(inventories.get(client.entity()), i.slot) { + if (c.requirement)( + inventories + .get(client.entity()) + .and_then(|inv| inv.get(i.slot)), + ) { self.show .crafting_fields .recipe_inputs diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 7267ec87b3..3ad979359d 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -8,7 +8,7 @@ use crate::ui::slot::{self, SlotKey, SumSlot}; use common::comp::{ ability::{Ability, AbilityInput, AuxiliaryAbility}, slot::InvSlotId, - ActiveAbilities, Body, Energy, Inventory, ItemKey, SkillSet, + ActiveAbilities, Body, Energy, Inventory, Item, ItemKey, SkillSet, }; use conrod_core::{image, Color}; use specs::Entity as EcsEntity; @@ -239,7 +239,7 @@ impl<'a> SlotKey, img_ids::Imgs> for AbilitySlot { pub struct CraftSlot { pub index: u32, pub invslot: Option, - pub requirement: fn(Option<&Inventory>, InvSlotId) -> bool, + pub requirement: fn(Option<&Item>) -> bool, } impl PartialEq for CraftSlot { @@ -249,7 +249,13 @@ impl PartialEq for CraftSlot { } impl Debug for CraftSlot { - fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { todo!() } + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_struct("CraftSlot") + .field("index", &self.index) + .field("invslot", &self.invslot) + .field("requirement", &"fn ptr") + .finish() + } } impl SlotKey for CraftSlot { diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index 0aa7045edb..a9111b0298 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -70,8 +70,8 @@ pub fn price_desc( } } -pub fn kind_text<'a, I: ItemDesc + ?Sized>(item: &I, i18n: &'a Localization) -> Cow<'a, str> { - match &*item.kind() { +pub fn kind_text<'a>(kind: &ItemKind, i18n: &'a Localization) -> Cow<'a, str> { + match kind { ItemKind::Armor(armor) => Cow::Borrowed(armor_kind(armor, i18n)), ItemKind::Tool(tool) => Cow::Owned(format!( "{} ({})", diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 145b236a34..c90f695a15 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1429,7 +1429,7 @@ impl PlayState for SessionState { self.client.borrow_mut().craft_modular_weapon( primary_slot, secondary_slot, - craft_sprite.map(|(pos, _sprite)| pos), + craft_sprite, ); }, HudEvent::CraftModularWeaponComponent { @@ -1470,7 +1470,7 @@ impl PlayState for SessionState { material, modifier, additional_slots, - craft_sprite.map(|(pos, _sprite)| pos), + craft_sprite, ); } }, diff --git a/voxygen/src/ui/widgets/item_tooltip.rs b/voxygen/src/ui/widgets/item_tooltip.rs index ca32f77735..37dc917bea 100644 --- a/voxygen/src/ui/widgets/item_tooltip.rs +++ b/voxygen/src/ui/widgets/item_tooltip.rs @@ -465,11 +465,11 @@ impl<'a> Widget for ItemTooltip<'a> { let item_kind = &*item.kind(); - let equip_slot = inventory.equipped_items_replaceable_by(item_kind); + let equipped_item = inventory.equipped_items_replaceable_by(item_kind).next(); let (title, desc) = (item.name().to_string(), item.description().to_string()); - let item_kind = util::kind_text(item, i18n).to_string(); + let item_kind = util::kind_text(item_kind, i18n).to_string(); let material = item.tags().into_iter().find_map(|t| match t { ItemTag::MaterialKind(material) => Some(material), @@ -706,7 +706,7 @@ impl<'a> Widget for ItemTooltip<'a> { .down_from(state.ids.stats[5], V_PAD_STATS) .set(state.ids.stats[6], ui); - if let Some(equipped_item) = first_equipped { + if let Some(equipped_item) = equipped_item { if let ItemKind::Tool(equipped_tool) = &*equipped_item.kind() { let tool_stats = tool.stats; let equipped_tool_stats = equipped_tool.stats; @@ -1034,7 +1034,7 @@ impl<'a> Widget for ItemTooltip<'a> { }, } - if let Some(equipped_item) = first_equipped { + if let Some(equipped_item) = equipped_item { if let ItemKind::Armor(equipped_armor) = &*equipped_item.kind() { let diff = armor.stats - equipped_armor.stats; let protection_diff = util::option_comparison(