From 78014d7d3be8735e26c4d7126dc5a89ac6ce58dd Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Tue, 23 Feb 2021 15:29:27 -0500 Subject: [PATCH] Put material stats in their own manifest, and multiply a form's stats by the weighted average of the material multipliers. --- .../items/crafting_ing/bloodsteel_ingot.ron | 2 +- .../items/crafting_ing/bronze_ingot.ron | 2 +- .../items/crafting_ing/cobalt_ingot.ron | 2 +- .../items/crafting_ing/copper_ingot.ron | 2 +- .../common/items/crafting_ing/iron_ingot.ron | 2 +- .../common/items/crafting_ing/steel_ingot.ron | 2 +- assets/common/material_stats_manifest.ron | 45 +++++++ assets/common/recipe_book.ron | 6 +- client/src/lib.rs | 2 + common/net/src/msg/server.rs | 3 +- common/src/combat.rs | 22 ++-- common/src/comp/inventory/item/mod.rs | 59 ++++----- common/src/comp/inventory/item/tool.rs | 124 ++++++++++++------ common/src/comp/inventory/mod.rs | 6 +- common/src/comp/inventory/test_helpers.rs | 4 +- common/src/recipe.rs | 10 +- common/src/states/behavior.rs | 14 +- common/src/states/utils.rs | 2 +- common/sys/src/character_behavior.rs | 20 ++- common/sys/src/state.rs | 1 + server/src/cmd.rs | 4 +- server/src/events/entity_manipulation.rs | 12 +- server/src/events/inventory_manip.rs | 15 ++- server/src/events/trade.rs | 5 +- server/src/lib.rs | 3 +- server/src/persistence/character.rs | 13 +- .../src/persistence/character/conversions.rs | 9 +- server/src/persistence/character_loader.rs | 42 +++++- voxygen/src/hud/bag.rs | 10 +- voxygen/src/hud/group.rs | 10 +- voxygen/src/hud/mod.rs | 11 +- voxygen/src/hud/skillbar.rs | 7 +- voxygen/src/hud/slots.rs | 13 +- voxygen/src/hud/util.rs | 31 ++++- 34 files changed, 366 insertions(+), 149 deletions(-) create mode 100644 assets/common/material_stats_manifest.ron diff --git a/assets/common/items/crafting_ing/bloodsteel_ingot.ron b/assets/common/items/crafting_ing/bloodsteel_ingot.ron index 3b210deb0b..c1b9a1d785 100644 --- a/assets/common/items/crafting_ing/bloodsteel_ingot.ron +++ b/assets/common/items/crafting_ing/bloodsteel_ingot.ron @@ -5,5 +5,5 @@ ItemDef( kind: "BloodsteelIngot", ), quality: Common, - tags: [MetalIngot(1.75)], + tags: [MetalIngot], ) diff --git a/assets/common/items/crafting_ing/bronze_ingot.ron b/assets/common/items/crafting_ing/bronze_ingot.ron index e3bce07b04..b3296faf91 100644 --- a/assets/common/items/crafting_ing/bronze_ingot.ron +++ b/assets/common/items/crafting_ing/bronze_ingot.ron @@ -5,5 +5,5 @@ ItemDef( kind: "BronzeIngot", ), quality: Common, - tags: [MetalIngot(0.75)], + tags: [MetalIngot], ) diff --git a/assets/common/items/crafting_ing/cobalt_ingot.ron b/assets/common/items/crafting_ing/cobalt_ingot.ron index 13d11cfa82..1ed63ae88a 100644 --- a/assets/common/items/crafting_ing/cobalt_ingot.ron +++ b/assets/common/items/crafting_ing/cobalt_ingot.ron @@ -5,5 +5,5 @@ ItemDef( kind: "CobaltIngot", ), quality: Common, - tags: [MetalIngot(1.5)], + tags: [MetalIngot], ) diff --git a/assets/common/items/crafting_ing/copper_ingot.ron b/assets/common/items/crafting_ing/copper_ingot.ron index 52c825363b..4e76603982 100644 --- a/assets/common/items/crafting_ing/copper_ingot.ron +++ b/assets/common/items/crafting_ing/copper_ingot.ron @@ -5,5 +5,5 @@ ItemDef( kind: "CopperIngot", ), quality: Common, - tags: [MetalIngot(0.4)], + tags: [MetalIngot], ) diff --git a/assets/common/items/crafting_ing/iron_ingot.ron b/assets/common/items/crafting_ing/iron_ingot.ron index c4f4d517fa..b5e6b7c7b9 100644 --- a/assets/common/items/crafting_ing/iron_ingot.ron +++ b/assets/common/items/crafting_ing/iron_ingot.ron @@ -5,5 +5,5 @@ ItemDef( kind: "IronIngot", ), quality: Common, - tags: [MetalIngot(1.0)], + tags: [MetalIngot], ) diff --git a/assets/common/items/crafting_ing/steel_ingot.ron b/assets/common/items/crafting_ing/steel_ingot.ron index 5f607b3578..a2f3b327aa 100644 --- a/assets/common/items/crafting_ing/steel_ingot.ron +++ b/assets/common/items/crafting_ing/steel_ingot.ron @@ -5,5 +5,5 @@ ItemDef( kind: "SteelIngot", ), quality: Common, - tags: [MetalIngot(1.25)], + tags: [MetalIngot], ) diff --git a/assets/common/material_stats_manifest.ron b/assets/common/material_stats_manifest.ron new file mode 100644 index 0000000000..4ce0747fcb --- /dev/null +++ b/assets/common/material_stats_manifest.ron @@ -0,0 +1,45 @@ +// Keep in mind that material stats are multiplied by the form stats, not added (e.g. equip_time_millis is most sensitive to this) +({ + "common.items.crafting_ing.bloodsteel_ingot": ( + equip_time_millis: 1, + power: 1.75, + poise_strength: 1.75, + speed: 1.75, + ), + "common.items.crafting_ing.bronze_ingot": ( + equip_time_millis: 1, + power: 0.75, + poise_strength: 0.75, + speed: 0.75, + ), + "common.items.crafting_ing.cobalt_ingot": ( + equip_time_millis: 1, + power: 1.5, + poise_strength: 1.5, + speed: 1.5, + ), + "common.items.crafting_ing.copper_ingot": ( + equip_time_millis: 1, + power: 0.4, + poise_strength: 0.4, + speed: 0.4, + ), + "common.items.crafting_ing.iron_ingot": ( + equip_time_millis: 1, + power: 1.0, + poise_strength: 1.0, + speed: 1.0, + ), + "common.items.crafting_ing.steel_ingot": ( + equip_time_millis: 1, + power: 1.25, + poise_strength: 1.25, + speed: 1.25, + ), + "common.items.crafting_ing.tin_ingot": ( + equip_time_millis: 1, + power: 0.25, + poise_strength: 0.25, + speed: 0.25, + ), +}) diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron index a0a6989dc4..9eed04e03f 100644 --- a/assets/common/recipe_book.ron +++ b/assets/common/recipe_book.ron @@ -361,13 +361,13 @@ [ (Tag(ClothItem), 1), (Item("common.items.crafting_tools.sewing_set"), 0), - ] + ], ), "metal_blade": ( ("common.items.crafting_ing.modular.damage.sword.metal_blade", 1), [ - (Tag(MetalIngot(0.0)), 5), + (Tag(MetalIngot), 5), (Item("common.items.crafting_tools.craftsman_hammer"), 0), - ] + ], ), } diff --git a/client/src/lib.rs b/client/src/lib.rs index e2cf78b335..2b68817639 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -253,6 +253,7 @@ impl Client { client_timeout, world_map, recipe_book, + material_stats, ability_map, } => { // Initialize `State` @@ -264,6 +265,7 @@ impl Client { let entity = state.ecs_mut().apply_entity_package(entity_package); *state.ecs_mut().write_resource() = time_of_day; + *state.ecs_mut().write_resource() = material_stats; state.ecs_mut().insert(ability_map); let map_size_lg = common::terrain::MapSizeLg::new(world_map.dimensions_lg) diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index ec6caf53dd..2f55c0d21f 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -3,7 +3,7 @@ use crate::sync; use authc::AuthClientError; use common::{ character::{self, CharacterItem}, - comp::{self, invite::InviteKind}, + comp::{self, invite::InviteKind, item::MaterialStatManifest}, outcome::Outcome, recipe::RecipeBook, resources::TimeOfDay, @@ -56,6 +56,7 @@ pub enum ServerInit { client_timeout: Duration, world_map: crate::msg::world_msg::WorldMapMsg, recipe_book: RecipeBook, + material_stats: MaterialStatManifest, ability_map: comp::item::tool::AbilityMap, }, } diff --git a/common/src/combat.rs b/common/src/combat.rs index 882dbff97c..5580e398dc 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -7,7 +7,7 @@ use crate::{ item::{ armor::Protection, tool::{Tool, ToolKind}, - Item, ItemKind, + Item, ItemKind, MaterialStatManifest, }, slot::EquipSlot, }, @@ -654,30 +654,36 @@ pub fn get_weapons(inv: &Inventory) -> (Option, Option) { } #[cfg(not(target_arch = "wasm32"))] -fn offensive_rating(inv: &Inventory, skillset: &SkillSet) -> f32 { +fn offensive_rating(inv: &Inventory, skillset: &SkillSet, msm: &MaterialStatManifest) -> f32 { let active_damage = equipped_item_and_tool(inv, EquipSlot::Mainhand).map_or(0.0, |(item, tool)| { - tool.base_power(item.components()) - * tool.base_speed(item.components()) + tool.base_power(msm, item.components()) + * tool.base_speed(msm, item.components()) * (1.0 + 0.05 * skillset.earned_sp(SkillGroupKind::Weapon(tool.kind)) as f32) }); let second_damage = equipped_item_and_tool(inv, EquipSlot::Offhand).map_or(0.0, |(item, tool)| { - tool.base_power(item.components()) - * tool.base_speed(item.components()) + tool.base_power(msm, item.components()) + * tool.base_speed(msm, item.components()) * (1.0 + 0.05 * skillset.earned_sp(SkillGroupKind::Weapon(tool.kind)) as f32) }); active_damage.max(second_damage) } #[cfg(not(target_arch = "wasm32"))] -pub fn combat_rating(inventory: &Inventory, health: &Health, stats: &Stats, body: Body) -> f32 { +pub fn combat_rating( + inventory: &Inventory, + health: &Health, + stats: &Stats, + body: Body, + msm: &MaterialStatManifest, +) -> f32 { let defensive_weighting = 1.0; let offensive_weighting = 1.0; let defensive_rating = health.maximum() as f32 / (1.0 - Damage::compute_damage_reduction(inventory)).max(0.00001) / 100.0; - let offensive_rating = offensive_rating(inventory, &stats.skill_set).max(0.1) + let offensive_rating = offensive_rating(inventory, &stats.skill_set, msm).max(0.1) + 0.05 * stats.skill_set.earned_sp(SkillGroupKind::General) as f32; let combined_rating = (offensive_rating * offensive_weighting + defensive_rating * defensive_weighting) diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 2ae1a987d1..cba47ade2a 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -4,7 +4,7 @@ pub mod tool; // Reexports pub use modular::{ModularComponent, ModularComponentKind, ModularComponentTag}; -pub use tool::{AbilitySet, Hands, Tool, ToolKind, UniqueKind}; +pub use tool::{AbilitySet, Hands, MaterialStatManifest, Tool, ToolKind, UniqueKind}; use crate::{ assets::{self, AssetExt, Error}, @@ -87,25 +87,11 @@ pub trait TagExampleInfo { fn exemplar_identifier(&self) -> &'static str; } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum ItemTag { ClothItem, ModularComponent(ModularComponentTag), - MetalIngot(f32), -} - -// The PartialEq implementation for ItemTag is used for determining whether a -// RecipeInput matches -impl PartialEq for ItemTag { - fn eq(&self, other: &Self) -> bool { - use ItemTag::*; - match (self, other) { - (ClothItem, ClothItem) => true, - (ModularComponent(a), ModularComponent(b)) => a == b, - (MetalIngot(_), MetalIngot(_)) => true, - _ => false, - } - } + MetalIngot, } impl TagExampleInfo for ItemTag { @@ -113,7 +99,7 @@ impl TagExampleInfo for ItemTag { match self { ItemTag::ClothItem => "cloth item", ItemTag::ModularComponent(kind) => kind.name(), - ItemTag::MetalIngot(_) => "metal ingot", + ItemTag::MetalIngot => "metal ingot", } } @@ -121,7 +107,7 @@ impl TagExampleInfo for ItemTag { match self { ItemTag::ClothItem => "common.items.tag_examples.cloth_item", ItemTag::ModularComponent(tag) => tag.exemplar_identifier(), - ItemTag::MetalIngot(_) => "common.items.tag_examples.metal_ingot", + ItemTag::MetalIngot => "common.items.tag_examples.metal_ingot", } } } @@ -229,10 +215,12 @@ pub struct ItemConfig { pub dodge_ability: Option, } -impl From<(&ItemKind, &[Item], &AbilityMap)> for ItemConfig { - fn from((item_kind, components, map): (&ItemKind, &[Item], &AbilityMap)) -> Self { +impl From<(&ItemKind, &[Item], &AbilityMap, &MaterialStatManifest)> for ItemConfig { + fn from( + (item_kind, components, map, msm): (&ItemKind, &[Item], &AbilityMap, &MaterialStatManifest), + ) -> Self { if let ItemKind::Tool(tool) = item_kind { - let abilities = tool.get_abilities(components, map); + let abilities = tool.get_abilities(msm, components, map); return ItemConfig { abilities, @@ -389,12 +377,16 @@ impl Item { // loadout when no weapon is present pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") } - pub fn new_from_item_def(inner_item: Arc, input_components: &[Item]) -> Self { + pub fn new_from_item_def( + inner_item: Arc, + input_components: &[Item], + msm: &MaterialStatManifest, + ) -> Self { let mut components = Vec::new(); if inner_item.is_modular() { // recipe ensures that types match (i.e. no axe heads on a sword hilt, or double // sword blades) - components.extend(input_components.iter().map(|comp| comp.duplicate())); + components.extend(input_components.iter().map(|comp| comp.duplicate(msm))); } let mut item = Item { @@ -405,7 +397,7 @@ impl Item { item_def: inner_item, item_config: None, }; - item.update_item_config(); + item.update_item_config(msm); item } @@ -413,7 +405,8 @@ impl Item { /// Panics if the asset does not exist. pub fn new_from_asset_expect(asset_specifier: &str) -> Self { let inner_item = Arc::::load_expect_cloned(asset_specifier); - Item::new_from_item_def(inner_item, &[]) + let msm = MaterialStatManifest::default(); + Item::new_from_item_def(inner_item, &[], &msm) } /// Creates a Vec containing one of each item that matches the provided @@ -426,12 +419,13 @@ impl Item { /// it exists pub fn new_from_asset(asset: &str) -> Result { let inner_item = Arc::::load_cloned(asset)?; - Ok(Item::new_from_item_def(inner_item, &[])) + let msm = MaterialStatManifest::default(); + Ok(Item::new_from_item_def(inner_item, &[], &msm)) } /// Duplicates an item, creating an exact copy but with a new item ID - pub fn duplicate(&self) -> Self { - Item::new_from_item_def(Arc::clone(&self.item_def), &self.components) + pub fn duplicate(&self, msm: &MaterialStatManifest) -> Self { + Item::new_from_item_def(Arc::clone(&self.item_def), &self.components, msm) } /// FIXME: HACK: In order to set the entity ID asynchronously, we currently @@ -494,21 +488,22 @@ impl Item { } } - pub fn add_component(&mut self, component: Item) { + pub fn add_component(&mut self, component: Item, msm: &MaterialStatManifest) { // TODO: hook for typechecking (not needed atm if this is only used by DB // persistence, but will definitely be needed once enhancement slots are // added to prevent putting a sword into another sword) self.components.push(component); // adding a component changes the stats, so recalculate the ItemConfig - self.update_item_config(); + self.update_item_config(msm); } - fn update_item_config(&mut self) { + fn update_item_config(&mut self, msm: &MaterialStatManifest) { self.item_config = if let ItemKind::Tool(_) = self.kind() { Some(Box::new(ItemConfig::from(( self.kind(), self.components(), &self.item_def.ability_map, + msm, )))) } else { None diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 001ee5c6fc..80f928f28e 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -2,17 +2,13 @@ // version in voxygen\src\meta.rs in order to reset save files to being empty use crate::{ - assets::{self, Asset}, - comp::{ - item::{ItemDesc, ItemKind, ItemTag}, - skills::Skill, - CharacterAbility, Item, - }, + assets::{self, Asset, AssetExt}, + comp::{item::ItemKind, skills::Skill, CharacterAbility, Item}, }; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::{ - ops::{AddAssign, MulAssign}, + ops::{AddAssign, DivAssign, MulAssign}, time::Duration, }; use tracing::error; @@ -78,6 +74,12 @@ impl Stats { } } +impl Asset for Stats { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + impl AddAssign for Stats { fn add_assign(&mut self, other: Stats) { self.equip_time_millis += other.equip_time_millis; @@ -86,11 +88,43 @@ impl AddAssign for Stats { self.speed += other.speed; } } -impl MulAssign for Stats { - fn mul_assign(&mut self, scalar: f32) { - self.power *= scalar; - self.poise_strength *= scalar; - self.speed *= scalar; +impl MulAssign for Stats { + fn mul_assign(&mut self, other: Stats) { + // equip_time_millis doesn't quite work with mul since it's u32, so we can't + // scale delay down, only up, so it needs to be balanced carefully in + // multiplicative contexts + self.equip_time_millis *= other.equip_time_millis; + self.power *= other.power; + self.poise_strength *= other.poise_strength; + self.speed *= other.speed; + } +} +impl DivAssign for Stats { + fn div_assign(&mut self, scalar: usize) { + self.equip_time_millis /= scalar as u32; + // since averaging occurs when the stats are used multiplicatively, don't permit + // multiplying an equip_time_millis by 0, since that would be overpowered + self.equip_time_millis = self.equip_time_millis.max(1); + self.power /= scalar as f32; + self.poise_strength /= scalar as f32; + self.speed /= scalar as f32; + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MaterialStatManifest(HashMap); + +// 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 { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + +impl Default for MaterialStatManifest { + fn default() -> MaterialStatManifest { + MaterialStatManifest::load_expect_cloned("common.material_stats_manifest") } } @@ -101,38 +135,36 @@ pub enum StatKind { } impl StatKind { - pub fn resolve_stats(&self, components: &[Item]) -> Stats { + pub fn resolve_stats(&self, msm: &MaterialStatManifest, components: &[Item]) -> Stats { let mut stats = match self { StatKind::Direct(stats) => *stats, StatKind::Modular => Stats::zeroed(), }; - let mut best_multiplier: Option = None; + let mut multipliers: Vec = Vec::new(); for item in components.iter() { match item.kind() { ItemKind::ModularComponent(mc) => { - let inner_stats = StatKind::Direct(mc.stats).resolve_stats(item.components()); + let inner_stats = + StatKind::Direct(mc.stats).resolve_stats(msm, item.components()); stats += inner_stats; }, ItemKind::Ingredient { .. } => { - for tag in item.tags() { - // exhaustive match to ensure that new tags get stats counted - match tag { - ItemTag::ClothItem => {}, - ItemTag::ModularComponent(_) => {}, - ItemTag::MetalIngot(multiplier) => { - best_multiplier = - Some(best_multiplier.unwrap_or(*multiplier).max(*multiplier)); - }, - } + if let Some(mult_stats) = msm.0.get(item.item_definition_id()) { + multipliers.push(*mult_stats); } }, - // TODO: add stats from enhancement slots, unless those end up as tagged - // ingredients + // TODO: add stats from enhancement slots _ => (), } } - if let Some(multiplier) = best_multiplier { - stats *= multiplier; + // Take the average of the material multipliers, to allow alloyed blades + if !multipliers.is_empty() { + let mut average_mult = Stats::zeroed(); + for stat in multipliers.iter() { + average_mult += *stat; + } + average_mult /= multipliers.len(); + stats *= average_mult; } // if an item has 0.0 speed, that panics due to being infinite duration, so // enforce speed >= 0.1 @@ -141,9 +173,9 @@ impl StatKind { } } -impl From<(&[Item], &Tool)> for Stats { - fn from((components, tool): (&[Item], &Tool)) -> Self { - let raw_stats = tool.stats.resolve_stats(components); +impl From<(&MaterialStatManifest, &[Item], &Tool)> for Stats { + fn from((msm, components, tool): (&MaterialStatManifest, &[Item], &Tool)) -> Self { + let raw_stats = tool.stats.resolve_stats(msm, components); let (power, speed) = match tool.hands { Hands::One => (0.67, 1.33), // TODO: Restore this when one-handed weapons are made accessible @@ -204,29 +236,30 @@ impl Tool { } // Keep power between 0.5 and 2.00 - pub fn base_power(&self, components: &[Item]) -> f32 { - self.stats.resolve_stats(components).power + pub fn base_power(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 { + self.stats.resolve_stats(msm, components).power } - pub fn base_poise_strength(&self, components: &[Item]) -> f32 { - self.stats.resolve_stats(components).poise_strength + pub fn base_poise_strength(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 { + self.stats.resolve_stats(msm, components).poise_strength } - pub fn base_speed(&self, components: &[Item]) -> f32 { - self.stats.resolve_stats(components).speed + pub fn base_speed(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 { + self.stats.resolve_stats(msm, components).speed } - pub fn equip_time(&self, components: &[Item]) -> Duration { - Duration::from_millis(self.stats.resolve_stats(components).equip_time_millis as u64) + pub fn equip_time(&self, msm: &MaterialStatManifest, components: &[Item]) -> Duration { + Duration::from_millis(self.stats.resolve_stats(msm, components).equip_time_millis as u64) } pub fn get_abilities( &self, + msm: &MaterialStatManifest, components: &[Item], map: &AbilityMap, ) -> AbilitySet { if let Some(set) = map.0.get(&self.kind).cloned() { - set.modified_by_tool(&self, components) + set.modified_by_tool(&self, msm, components) } else { error!( "ToolKind: {:?} has no AbilitySet in the ability map falling back to default", @@ -245,8 +278,13 @@ pub struct AbilitySet { } impl AbilitySet { - pub fn modified_by_tool(self, tool: &Tool, components: &[Item]) -> Self { - let stats = Stats::from((components, tool)); + pub fn modified_by_tool( + self, + tool: &Tool, + msm: &MaterialStatManifest, + components: &[Item], + ) -> Self { + let stats = Stats::from((msm, components, tool)); self.map(|a| a.adjusted_by_stats(stats.power, stats.poise_strength, stats.speed)) } } diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs index 2a7cf3713f..df78f961a5 100644 --- a/common/src/comp/inventory/mod.rs +++ b/common/src/comp/inventory/mod.rs @@ -9,7 +9,7 @@ use tracing::{debug, trace, warn}; use crate::{ comp::{ inventory::{ - item::ItemDef, + item::{ItemDef, MaterialStatManifest}, loadout::Loadout, slot::{EquipSlot, Slot, SlotError}, }, @@ -257,9 +257,9 @@ impl Inventory { } /// Remove just one item from the slot - pub fn take(&mut self, inv_slot_id: InvSlotId) -> Option { + pub fn take(&mut self, inv_slot_id: InvSlotId, msm: &MaterialStatManifest) -> Option { if let Some(Some(item)) = self.slot_mut(inv_slot_id) { - let mut return_item = item.duplicate(); + let mut return_item = item.duplicate(msm); if item.is_stackable() && item.amount() > 1 { item.decrease_amount(1).ok()?; diff --git a/common/src/comp/inventory/test_helpers.rs b/common/src/comp/inventory/test_helpers.rs index a3f07cf78b..79533f75e1 100644 --- a/common/src/comp/inventory/test_helpers.rs +++ b/common/src/comp/inventory/test_helpers.rs @@ -3,7 +3,7 @@ use crate::comp::{ armor, armor::{ArmorKind, Protection}, tool::AbilityMap, - ItemDef, ItemKind, Quality, + ItemDef, ItemKind, MaterialStatManifest, Quality, }, Item, }; @@ -23,5 +23,5 @@ pub(super) fn get_test_bag(slots: u16) -> Item { AbilityMap::default(), ); - Item::new_from_item_def(Arc::new(item_def), &[]) + Item::new_from_item_def(Arc::new(item_def), &[], &MaterialStatManifest::default()) } diff --git a/common/src/recipe.rs b/common/src/recipe.rs index f11b464d79..477a7ff659 100644 --- a/common/src/recipe.rs +++ b/common/src/recipe.rs @@ -1,7 +1,7 @@ use crate::{ assets::{self, AssetExt, AssetHandle}, comp::{ - item::{modular, ItemDef, ItemTag}, + item::{modular, ItemDef, ItemTag, MaterialStatManifest}, Inventory, Item, }, }; @@ -27,6 +27,7 @@ impl Recipe { pub fn perform( &self, inv: &mut Inventory, + msm: &MaterialStatManifest, ) -> Result, Vec<(&RecipeInput, u32)>> { // Get ingredient cells from inventory, let mut components = Vec::new(); @@ -35,13 +36,16 @@ impl Recipe { .into_iter() .for_each(|(pos, n)| { (0..n).for_each(|_| { - let component = inv.take(pos).expect("Expected item to exist in inventory"); + let component = inv + .take(pos, msm) + .expect("Expected item to exist in inventory"); components.push(component); }) }); for i in 0..self.output.1 { - let crafted_item = Item::new_from_item_def(Arc::clone(&self.output.0), &components); + let crafted_item = + Item::new_from_item_def(Arc::clone(&self.output.0), &components, msm); if let Some(item) = inv.push(crafted_item) { return Ok(Some((item, self.output.1 - i))); } diff --git a/common/src/states/behavior.rs b/common/src/states/behavior.rs index b37ebddea0..73f1b054bc 100644 --- a/common/src/states/behavior.rs +++ b/common/src/states/behavior.rs @@ -1,7 +1,8 @@ use crate::{ comp::{ - Beam, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy, Health, - Inventory, LoadoutManip, Melee, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, + item::MaterialStatManifest, Beam, Body, CharacterState, ControlAction, Controller, + ControllerInputs, Energy, Health, Inventory, LoadoutManip, Melee, Ori, PhysicsState, Pos, + StateUpdate, Stats, Vel, }, resources::DeltaTime, uid::Uid, @@ -66,6 +67,7 @@ pub struct JoinData<'a> { pub melee_attack: Option<&'a Melee>, pub updater: &'a LazyUpdate, pub stats: &'a Stats, + pub msm: &'a MaterialStatManifest, } type RestrictedMut<'a, C> = PairedStorage< @@ -96,7 +98,12 @@ pub struct JoinStruct<'a> { } impl<'a> JoinData<'a> { - pub fn new(j: &'a JoinStruct<'a>, updater: &'a LazyUpdate, dt: &'a DeltaTime) -> Self { + pub fn new( + j: &'a JoinStruct<'a>, + updater: &'a LazyUpdate, + dt: &'a DeltaTime, + msm: &'a MaterialStatManifest, + ) -> Self { Self { entity: j.entity, uid: j.uid, @@ -115,6 +122,7 @@ impl<'a> JoinData<'a> { stats: j.stat, updater, dt, + msm, } } } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index a37682adba..334f8ae039 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -301,7 +301,7 @@ pub fn attempt_wield(data: &JoinData, update: &mut StateUpdate) { { update.character = CharacterState::Equipping(equipping::Data { static_data: equipping::StaticData { - buildup_duration: tool.equip_time(item.components()), + buildup_duration: tool.equip_time(data.msm, item.components()), }, timer: Duration::default(), }); diff --git a/common/sys/src/character_behavior.rs b/common/sys/src/character_behavior.rs index f35023857e..5e67aa40d5 100644 --- a/common/sys/src/character_behavior.rs +++ b/common/sys/src/character_behavior.rs @@ -5,7 +5,10 @@ use specs::{ use common::{ comp::{ - inventory::slot::{EquipSlot, Slot}, + inventory::{ + item::MaterialStatManifest, + slot::{EquipSlot, Slot}, + }, Beam, Body, CharacterState, Controller, Energy, Health, Inventory, Melee, Mounting, Ori, PhysicsState, Poise, PoiseState, Pos, StateUpdate, Stats, Vel, }, @@ -62,6 +65,7 @@ pub struct ReadData<'a> { uids: ReadStorage<'a, Uid>, mountings: ReadStorage<'a, Mounting>, stats: ReadStorage<'a, Stats>, + msm: Read<'a, MaterialStatManifest>, } /// ## Character Behavior System @@ -252,7 +256,12 @@ impl<'a> System<'a> for Sys { }; for action in actions { - let j = JoinData::new(&join_struct, &read_data.lazy_update, &read_data.dt); + let j = JoinData::new( + &join_struct, + &read_data.lazy_update, + &read_data.dt, + &read_data.msm, + ); let mut state_update = match j.character { CharacterState::Idle => states::idle::Data.handle_event(&j, action), CharacterState::Talk => states::talk::Data.handle_event(&j, action), @@ -295,7 +304,12 @@ impl<'a> System<'a> for Sys { incorporate_update(&mut join_struct, state_update); } - let j = JoinData::new(&join_struct, &read_data.lazy_update, &read_data.dt); + let j = JoinData::new( + &join_struct, + &read_data.lazy_update, + &read_data.dt, + &read_data.msm, + ); let mut state_update = match j.character { CharacterState::Idle => states::idle::Data.behavior(&j), diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs index ec3a339f20..80d00654e2 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -196,6 +196,7 @@ impl State { ecs.insert(EventBus::::default()); ecs.insert(game_mode); ecs.insert(Vec::::new()); + ecs.insert(comp::inventory::item::MaterialStatManifest::default()); // TODO: only register on the server ecs.insert(EventBus::::default()); ecs.insert(comp::group::GroupManager::default()); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 24c8b40ebb..06daa78b91 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -13,6 +13,7 @@ use common::{ self, aura::{Aura, AuraKind, AuraTarget}, buff::{BuffCategory, BuffData, BuffKind, BuffSource}, + inventory::item::MaterialStatManifest, invite::InviteKind, ChatType, Inventory, Item, LightEmitter, WaypointArea, }, @@ -205,6 +206,7 @@ fn handle_give_item( } }); } else { + let msm = server.state.ecs().read_resource::(); // This item can't stack. Give each item in a loop. server .state @@ -213,7 +215,7 @@ fn handle_give_item( .get_mut(target) .map(|mut inv| { for i in 0..give_amount { - if inv.push(item.duplicate()).is_some() { + if inv.push(item.duplicate(&msm)).is_some() { server.notify_client( client, ServerGeneral::server_msg( diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 1ceda84abe..900ec70fa8 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -13,6 +13,7 @@ use common::{ comp::{ self, aura, buff, chat::{KillSource, KillType}, + inventory::item::MaterialStatManifest, object, Alignment, Body, CharacterState, Energy, EnergyChange, Group, Health, HealthChange, HealthSource, Inventory, Item, Player, Poise, PoiseChange, PoiseSource, Pos, Stats, }, @@ -224,9 +225,14 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc const MAX_EXP_DIST: f32 = 150.0; // TODO: Scale xp from skillset rather than health, when NPCs have their own // skillsets - let mut exp_reward = - combat::combat_rating(entity_inventory, entity_health, entity_stats, *entity_body) - * 2.5; + let msm = state.ecs().read_resource::(); + let mut exp_reward = combat::combat_rating( + entity_inventory, + entity_health, + entity_stats, + *entity_body, + &msm, + ) * 2.5; // Distribute EXP to group let positions = state.ecs().read_storage::(); diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 42ac76d077..41485b560c 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -260,7 +260,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Slo })); } Some(comp::InventoryUpdateEvent::Used) - } else if let Some(item) = inventory.take(slot) { + } else if let Some(item) = inventory.take( + slot, + &state.ecs().read_resource::(), + ) { match item.kind() { ItemKind::Consumable { kind, effect, .. } => { maybe_effect = Some(effect.clone()); @@ -484,9 +487,13 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Slo .get_mut(entity) { let recipe_book = default_recipe_book().read(); - let craft_result = recipe_book - .get(&recipe) - .and_then(|r| r.perform(&mut inv).ok()); + let craft_result = recipe_book.get(&recipe).and_then(|r| { + r.perform( + &mut inv, + &state.ecs().read_resource::(), + ) + .ok() + }); // FIXME: We should really require the drop and write to be atomic! if craft_result.is_some() { diff --git a/server/src/events/trade.rs b/server/src/events/trade.rs index 5161c58959..f1ba619e6d 100644 --- a/server/src/events/trade.rs +++ b/server/src/events/trade.rs @@ -1,6 +1,6 @@ use crate::Server; use common::{ - comp::inventory::Inventory, + comp::inventory::{item::MaterialStatManifest, Inventory}, trade::{PendingTrade, TradeAction, TradeId, TradeResult, Trades}, }; use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; @@ -116,6 +116,7 @@ fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult { } } let mut items = [Vec::new(), Vec::new()]; + let msm = ecs.read_resource::(); for who in [0, 1].iter().cloned() { for (slot, quantity) in trade.offers[who].iter() { // Take the items one by one, to benefit from Inventory's stack handling @@ -123,7 +124,7 @@ fn commit_trade(ecs: &specs::World, trade: &PendingTrade) -> TradeResult { inventories .get_mut(entities[who]) .expect(invmsg) - .take(*slot) + .take(*slot, &msm) .map(|item| items[who].push(item)); } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 70a3bd2aec..1f91276b58 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -58,7 +58,7 @@ use common::{ assets::AssetExt, cmd::ChatCommand, comp, - comp::CharacterAbility, + comp::{item::MaterialStatManifest, CharacterAbility}, event::{EventBus, ServerEvent}, recipe::default_recipe_book, resources::TimeOfDay, @@ -954,6 +954,7 @@ impl Server { client_timeout: self.settings().client_timeout, world_map: self.map.clone(), recipe_book: default_recipe_book().cloned(), + material_stats: MaterialStatManifest::default(), ability_map: (&*self .state .ecs() diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index 24cc6c85de..55158ea656 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -9,7 +9,7 @@ extern crate diesel; use super::{error::Error, models::*, schema, VelorenTransaction}; use crate::{ comp, - comp::Inventory, + comp::{item::MaterialStatManifest, Inventory}, persistence::{ character::conversions::{ convert_body_from_database, convert_body_to_database_json, @@ -78,6 +78,7 @@ pub fn load_character_data( requesting_player_uuid: String, char_id: CharacterId, connection: VelorenTransaction, + msm: &MaterialStatManifest, ) -> CharacterDataResult { use schema::{body::dsl::*, character::dsl::*, skill_group::dsl::*}; @@ -127,6 +128,7 @@ pub fn load_character_data( &inventory_items, character_containers.loadout_container_id, &loadout_items, + msm, )?, char_waypoint, )) @@ -142,6 +144,7 @@ pub fn load_character_data( pub fn load_character_list( player_uuid_: &str, connection: VelorenTransaction, + msm: &MaterialStatManifest, ) -> CharacterListResult { use schema::{body::dsl::*, character::dsl::*}; @@ -170,7 +173,7 @@ pub fn load_character_list( let loadout_items = load_items_bfs(connection, loadout_container_id)?; let loadout = - convert_loadout_from_database_items(loadout_container_id, &loadout_items)?; + convert_loadout_from_database_items(loadout_container_id, &loadout_items, msm)?; Ok(CharacterItem { character: char, @@ -186,6 +189,7 @@ pub fn create_character( character_alias: &str, persisted_components: PersistedComponents, connection: VelorenTransaction, + msm: &MaterialStatManifest, ) -> CharacterCreationResult { use schema::item::dsl::*; @@ -317,7 +321,7 @@ pub fn create_character( ))); } - load_character_list(uuid, connection).map(|list| (character_id, list)) + load_character_list(uuid, connection, msm).map(|list| (character_id, list)) } /// Delete a character. Returns the updated character list. @@ -325,6 +329,7 @@ pub fn delete_character( requesting_player_uuid: &str, char_id: CharacterId, connection: VelorenTransaction, + msm: &MaterialStatManifest, ) -> CharacterListResult { use schema::{body::dsl::*, character::dsl::*, skill::dsl::*, skill_group::dsl::*}; @@ -402,7 +407,7 @@ pub fn delete_character( ))); } - load_character_list(requesting_player_uuid, connection) + load_character_list(requesting_player_uuid, connection, msm) } /// Before creating a character, we ensure that the limit on the number of diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index 2cf8fa8932..a747a5a6fd 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -11,6 +11,7 @@ use common::{ character::CharacterId, comp::{ inventory::{ + item::MaterialStatManifest, loadout::{Loadout, LoadoutError}, loadout_builder::LoadoutBuilder, slot::InvSlotId, @@ -225,6 +226,7 @@ pub fn convert_inventory_from_database_items( inventory_items: &[Item], loadout_container_id: i64, loadout_items: &[Item], + msm: &MaterialStatManifest, ) -> Result { // Loadout items must be loaded before inventory items since loadout items // provide inventory slots. Since items stored inside loadout items actually @@ -232,7 +234,7 @@ pub fn convert_inventory_from_database_items( // on populating the loadout items first, and then inserting the items into the // inventory at the correct position. // - let loadout = convert_loadout_from_database_items(loadout_container_id, loadout_items)?; + let loadout = convert_loadout_from_database_items(loadout_container_id, loadout_items, msm)?; let mut inventory = Inventory::new_with_loadout(loadout); let mut item_indices = HashMap::new(); @@ -294,7 +296,7 @@ pub fn convert_inventory_from_database_items( } } else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) { if let Some(Some(parent)) = inventory.slot_mut(slot(&inventory_items[j].position)?) { - parent.add_component(item); + parent.add_component(item, msm); } else { return Err(Error::ConversionError(format!( "Parent slot {} for component {} was empty even though it occurred earlier in \ @@ -316,6 +318,7 @@ pub fn convert_inventory_from_database_items( pub fn convert_loadout_from_database_items( loadout_container_id: i64, database_items: &[Item], + msm: &MaterialStatManifest, ) -> Result { let loadout_builder = LoadoutBuilder::new(); let mut loadout = loadout_builder.build(); @@ -348,7 +351,7 @@ pub fn convert_loadout_from_database_items( } else if let Some(&j) = item_indices.get(&db_item.parent_container_item_id) { loadout .update_item_at_slot_using_persistence_key(&database_items[j].position, |parent| { - parent.add_component(item); + parent.add_component(item, msm); }) .map_err(convert_error)?; } else { diff --git a/server/src/persistence/character_loader.rs b/server/src/persistence/character_loader.rs index 4d2aeff569..a43ceb8ed4 100644 --- a/server/src/persistence/character_loader.rs +++ b/server/src/persistence/character_loader.rs @@ -3,8 +3,12 @@ use crate::persistence::{ error::Error, establish_connection, PersistedComponents, }; -use common::character::{CharacterId, CharacterItem}; +use common::{ + character::{CharacterId, CharacterItem}, + comp::inventory::item::MaterialStatManifest, +}; use crossbeam_channel::{self, TryIter}; +use lazy_static::lazy_static; use std::path::Path; use tracing::error; @@ -66,6 +70,13 @@ pub struct CharacterLoader { update_tx: crossbeam_channel::Sender, } +// Decoupled from the ECS resource because the plumbing is getting complicated; +// 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(); +} + impl CharacterLoader { pub fn new(db_dir: &Path) -> diesel::QueryResult { let (update_tx, internal_rx) = crossbeam_channel::unbounded::(); @@ -93,6 +104,7 @@ impl CharacterLoader { &character_alias, persisted_components, txn, + &MATERIAL_STATS_MANIFEST, ) }, )), @@ -100,19 +112,37 @@ impl CharacterLoader { player_uuid, character_id, } => CharacterLoaderResponseKind::CharacterList(conn.transaction( - |txn| delete_character(&player_uuid, character_id, txn), + |txn| { + delete_character( + &player_uuid, + character_id, + txn, + &MATERIAL_STATS_MANIFEST, + ) + }, )), CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => { - CharacterLoaderResponseKind::CharacterList( - conn.transaction(|txn| load_character_list(&player_uuid, txn)), - ) + CharacterLoaderResponseKind::CharacterList(conn.transaction( + |txn| { + load_character_list( + &player_uuid, + txn, + &MATERIAL_STATS_MANIFEST, + ) + }, + )) }, CharacterLoaderRequestKind::LoadCharacterData { player_uuid, character_id, } => { let result = conn.transaction(|txn| { - load_character_data(player_uuid, character_id, txn) + load_character_data( + player_uuid, + character_id, + txn, + &MATERIAL_STATS_MANIFEST, + ) }); if result.is_err() { error!( diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 676f4d0ff7..7460a3bb74 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -18,7 +18,10 @@ use crate::{ use client::Client; use common::{ combat::{combat_rating, Damage}, - comp::{item::Quality, Body, Energy, Health, Stats}, + comp::{ + item::{MaterialStatManifest, Quality}, + Body, Energy, Health, Stats, + }, }; use conrod_core::{ color, @@ -101,6 +104,7 @@ pub struct Bag<'a> { energy: &'a Energy, show: &'a Show, body: &'a Body, + msm: &'a MaterialStatManifest, } impl<'a> Bag<'a> { @@ -120,6 +124,7 @@ impl<'a> Bag<'a> { energy: &'a Energy, show: &'a Show, body: &'a Body, + msm: &'a MaterialStatManifest, ) -> Self { Self { client, @@ -137,6 +142,7 @@ impl<'a> Bag<'a> { health, show, body, + msm, } } } @@ -433,7 +439,7 @@ impl<'a> Widget for Bag<'a> { }); // Stats let combat_rating = - combat_rating(inventory, self.health, self.stats, *self.body).min(999.9); + combat_rating(inventory, self.health, self.stats, *self.body, &self.msm).min(999.9); let indicator_col = cr_color(combat_rating); for i in STATS.iter().copied().enumerate() { let btn = Button::image(match i.1 { diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 8c51f9ecbf..d199b5c5b2 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -16,7 +16,9 @@ use crate::{ use client::{self, Client}; use common::{ combat, - comp::{group::Role, invite::InviteKind, BuffKind, Stats}, + comp::{ + group::Role, inventory::item::MaterialStatManifest, invite::InviteKind, BuffKind, Stats, + }, uid::{Uid, UidAllocator}, }; use common_net::sync::WorldSyncExt; @@ -80,6 +82,7 @@ pub struct Group<'a> { pulse: f32, global_state: &'a GlobalState, tooltip_manager: &'a mut TooltipManager, + msm: &'a MaterialStatManifest, #[conrod(common_builder)] common: widget::CommonBuilder, @@ -98,6 +101,7 @@ impl<'a> Group<'a> { pulse: f32, global_state: &'a GlobalState, tooltip_manager: &'a mut TooltipManager, + msm: &'a MaterialStatManifest, ) -> Self { Self { show, @@ -110,6 +114,7 @@ impl<'a> Group<'a> { pulse, global_state, tooltip_manager, + msm, common: widget::CommonBuilder::default(), } } @@ -358,7 +363,8 @@ impl<'a> Widget for Group<'a> { if let (Some(stats), Some(inventory), Some(health), Some(body)) = (stats, inventory, health, body) { - let combat_rating = combat::combat_rating(inventory, health, stats, *body); + let combat_rating = + combat::combat_rating(inventory, health, stats, *body, &self.msm); let char_name = stats.name.to_string(); let health_perc = health.current() as f64 / health.maximum() as f64; // change panel positions when debug info is shown diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 1261c20bf9..003248e1d3 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -66,7 +66,7 @@ use common::{ combat, comp::{ self, - item::{tool::ToolKind, ItemDesc, Quality}, + item::{tool::ToolKind, ItemDesc, MaterialStatManifest, Quality}, skills::{Skill, SkillGroupKind}, BuffKind, }, @@ -875,6 +875,7 @@ impl Hud { let bodies = ecs.read_storage::(); let items = ecs.read_storage::(); let inventories = ecs.read_storage::(); + let msm = ecs.read_resource::(); let entities = ecs.entities(); let me = client.entity(); @@ -1370,7 +1371,9 @@ impl Hud { health, buffs, energy, - combat_rating: combat::combat_rating(inventory, health, stats, *body), + combat_rating: combat::combat_rating( + inventory, health, stats, *body, &msm, + ), }); let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) { speech_bubbles.get(uid) @@ -1932,6 +1935,7 @@ impl Hud { let ecs = client.state().ecs(); let stats = ecs.read_storage::(); let buffs = ecs.read_storage::(); + let msm = ecs.read_resource::(); if let Some(player_stats) = stats.get(client.entity()) { match Buttons::new( client, @@ -1968,6 +1972,7 @@ impl Hud { self.pulse, &global_state, tooltip_manager, + &msm, ) .set(self.ids.group_window, ui_widgets) { @@ -2080,6 +2085,7 @@ impl Hud { &mut self.slot_manager, i18n, &ability_map, + &msm, ) .set(self.ids.skillbar, ui_widgets); } @@ -2106,6 +2112,7 @@ impl Hud { &energy, &self.show, &body, + &msm, ) .set(self.ids.bag, ui_widgets) { diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 3940b9012a..f11aea24f9 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -19,7 +19,7 @@ use common::comp::{ inventory::slot::EquipSlot, item::{ tool::{AbilityMap, Tool, ToolKind}, - Hands, Item, ItemKind, + Hands, Item, ItemKind, MaterialStatManifest, }, Energy, Health, Inventory, }; @@ -140,6 +140,7 @@ pub struct Skillbar<'a> { #[conrod(common_builder)] common: widget::CommonBuilder, ability_map: &'a AbilityMap, + msm: &'a MaterialStatManifest, } impl<'a> Skillbar<'a> { @@ -161,6 +162,7 @@ impl<'a> Skillbar<'a> { slot_manager: &'a mut slots::SlotManager, localized_strings: &'a Localization, ability_map: &'a AbilityMap, + msm: &'a MaterialStatManifest, ) -> Self { Self { global_state, @@ -180,6 +182,7 @@ impl<'a> Skillbar<'a> { slot_manager, localized_strings, ability_map, + msm, } } } @@ -623,7 +626,7 @@ impl<'a> Widget for Skillbar<'a> { .image_color(if let Some((item, tool)) = tool { if self.energy.current() >= tool - .get_abilities(item.components(), self.ability_map) + .get_abilities(&self.msm, item.components(), self.ability_map) .secondary .get_energy_cost() { diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index 95a4146c88..7dd43f47b6 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -2,6 +2,7 @@ use super::{ hotbar::{self, Slot as HotbarSlot}, img_ids, item_imgs::{ItemImgs, ItemKey}, + util::MATERIAL_STATS_MANIFEST, }; use crate::ui::slot::{self, SlotKey, SumSlot}; use common::comp::{ @@ -130,7 +131,11 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { ( i, if let Some(skill) = tool - .get_abilities(item.components(), ability_map) + .get_abilities( + &MATERIAL_STATS_MANIFEST, + item.components(), + ability_map, + ) .abilities .get(0) { @@ -173,7 +178,11 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { ( i, if let Some(skill) = tool - .get_abilities(item.components(), ability_map) + .get_abilities( + &MATERIAL_STATS_MANIFEST, + item.components(), + ability_map, + ) .abilities .get(skill_index) { diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index e6c55fd565..ad6c324874 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -1,10 +1,16 @@ use common::comp::item::{ armor::{Armor, ArmorKind, Protection}, tool::{Hands, StatKind, Tool, ToolKind}, - Item, ItemDesc, ItemKind, ModularComponent, + Item, ItemDesc, ItemKind, MaterialStatManifest, ModularComponent, }; +use lazy_static::lazy_static; use std::{borrow::Cow, fmt::Write}; +lazy_static! { + // TODO: even more plumbing + pub static ref MATERIAL_STATS_MANIFEST: MaterialStatManifest = MaterialStatManifest::default(); +} + pub fn loadout_slot_text<'a>( item: Option<&'a impl ItemDesc>, mut empty: impl FnMut() -> (&'a str, &'a str), @@ -23,10 +29,16 @@ pub fn item_text<'a>(item: &'a impl ItemDesc) -> (&'_ str, Cow<'a, str>) { ItemKind::Armor(armor) => { Cow::Owned(armor_desc(armor, item.description(), item.num_slots())) }, - ItemKind::Tool(tool) => Cow::Owned(tool_desc(&tool, item.components(), item.description())), + ItemKind::Tool(tool) => Cow::Owned(tool_desc( + &tool, + item.components(), + &MATERIAL_STATS_MANIFEST, + item.description(), + )), ItemKind::ModularComponent(mc) => Cow::Owned(modular_component_desc( mc, item.components(), + &MATERIAL_STATS_MANIFEST, item.description(), )), ItemKind::Glider(_glider) => Cow::Owned(glider_desc(item.description())), @@ -43,10 +55,15 @@ pub fn item_text<'a>(item: &'a impl ItemDesc) -> (&'_ str, Cow<'a, str>) { } // TODO: localization -fn modular_component_desc(mc: &ModularComponent, components: &[Item], description: &str) -> String { +fn modular_component_desc( + mc: &ModularComponent, + components: &[Item], + msm: &MaterialStatManifest, + description: &str, +) -> String { let mut result = format!( "Modular Component\n\n{:?}\n\n{}", - StatKind::Direct(mc.stats).resolve_stats(components), + StatKind::Direct(mc.stats).resolve_stats(msm, components), description ); if !components.is_empty() { @@ -119,7 +136,7 @@ fn armor_desc(armor: &Armor, desc: &str, slots: u16) -> String { description } -fn tool_desc(tool: &Tool, components: &[Item], desc: &str) -> String { +fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc: &str) -> String { let kind = match tool.kind { ToolKind::Sword => "Sword", ToolKind::Axe => "Axe", @@ -136,13 +153,13 @@ fn tool_desc(tool: &Tool, components: &[Item], desc: &str) -> String { }; // Get tool stats - let power = tool.base_power(components); + let power = tool.base_power(msm, components); //let poise_strength = tool.base_poise_strength(); let hands = match tool.hands { Hands::One => "One", Hands::Two => "Two", }; - let speed = tool.base_speed(components); + let speed = tool.base_speed(msm, components); let mut result = format!( "{}-Handed {}\n\nDPS: {:0.1}\n\nPower: {:0.1}\n\nSpeed: {:0.1}\n\n",