mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Put material stats in their own manifest, and multiply a form's stats by the weighted average of the material multipliers.
This commit is contained in:
parent
4e57678f34
commit
78014d7d3b
@ -5,5 +5,5 @@ ItemDef(
|
||||
kind: "BloodsteelIngot",
|
||||
),
|
||||
quality: Common,
|
||||
tags: [MetalIngot(1.75)],
|
||||
tags: [MetalIngot],
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ ItemDef(
|
||||
kind: "BronzeIngot",
|
||||
),
|
||||
quality: Common,
|
||||
tags: [MetalIngot(0.75)],
|
||||
tags: [MetalIngot],
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ ItemDef(
|
||||
kind: "CobaltIngot",
|
||||
),
|
||||
quality: Common,
|
||||
tags: [MetalIngot(1.5)],
|
||||
tags: [MetalIngot],
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ ItemDef(
|
||||
kind: "CopperIngot",
|
||||
),
|
||||
quality: Common,
|
||||
tags: [MetalIngot(0.4)],
|
||||
tags: [MetalIngot],
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ ItemDef(
|
||||
kind: "IronIngot",
|
||||
),
|
||||
quality: Common,
|
||||
tags: [MetalIngot(1.0)],
|
||||
tags: [MetalIngot],
|
||||
)
|
||||
|
@ -5,5 +5,5 @@ ItemDef(
|
||||
kind: "SteelIngot",
|
||||
),
|
||||
quality: Common,
|
||||
tags: [MetalIngot(1.25)],
|
||||
tags: [MetalIngot],
|
||||
)
|
||||
|
45
assets/common/material_stats_manifest.ron
Normal file
45
assets/common/material_stats_manifest.ron
Normal file
@ -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,
|
||||
),
|
||||
})
|
@ -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),
|
||||
]
|
||||
],
|
||||
),
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
@ -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<ToolKind>, Option<ToolKind>) {
|
||||
}
|
||||
|
||||
#[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)
|
||||
|
@ -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<CharacterAbility>,
|
||||
}
|
||||
|
||||
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<ItemDef>, input_components: &[Item]) -> Self {
|
||||
pub fn new_from_item_def(
|
||||
inner_item: Arc<ItemDef>,
|
||||
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::<ItemDef>::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<Self, Error> {
|
||||
let inner_item = Arc::<ItemDef>::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
|
||||
|
@ -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<Stats> for Stats {
|
||||
fn add_assign(&mut self, other: Stats) {
|
||||
self.equip_time_millis += other.equip_time_millis;
|
||||
@ -86,11 +88,43 @@ impl AddAssign<Stats> for Stats {
|
||||
self.speed += other.speed;
|
||||
}
|
||||
}
|
||||
impl MulAssign<f32> for Stats {
|
||||
fn mul_assign(&mut self, scalar: f32) {
|
||||
self.power *= scalar;
|
||||
self.poise_strength *= scalar;
|
||||
self.speed *= scalar;
|
||||
impl MulAssign<Stats> 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<usize> 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<String, Stats>);
|
||||
|
||||
// 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<f32> = None;
|
||||
let mut multipliers: Vec<Stats> = 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<CharacterAbility> {
|
||||
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<T> {
|
||||
}
|
||||
|
||||
impl AbilitySet<CharacterAbility> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -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<Item> {
|
||||
pub fn take(&mut self, inv_slot_id: InvSlotId, msm: &MaterialStatManifest) -> Option<Item> {
|
||||
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()?;
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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<Option<(Item, u32)>, 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)));
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
});
|
||||
|
@ -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),
|
||||
|
@ -196,6 +196,7 @@ impl State {
|
||||
ecs.insert(EventBus::<LocalEvent>::default());
|
||||
ecs.insert(game_mode);
|
||||
ecs.insert(Vec::<common::outcome::Outcome>::new());
|
||||
ecs.insert(comp::inventory::item::MaterialStatManifest::default());
|
||||
// TODO: only register on the server
|
||||
ecs.insert(EventBus::<ServerEvent>::default());
|
||||
ecs.insert(comp::group::GroupManager::default());
|
||||
|
@ -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::<MaterialStatManifest>();
|
||||
// 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(
|
||||
|
@ -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::<MaterialStatManifest>();
|
||||
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::<Pos>();
|
||||
|
@ -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::<item::MaterialStatManifest>(),
|
||||
) {
|
||||
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::<item::MaterialStatManifest>(),
|
||||
)
|
||||
.ok()
|
||||
});
|
||||
|
||||
// FIXME: We should really require the drop and write to be atomic!
|
||||
if craft_result.is_some() {
|
||||
|
@ -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::<MaterialStatManifest>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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<Inventory, Error> {
|
||||
// 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<Loadout, Error> {
|
||||
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 {
|
||||
|
@ -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<CharacterLoaderRequest>,
|
||||
}
|
||||
|
||||
// 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<Self> {
|
||||
let (update_tx, internal_rx) = crossbeam_channel::unbounded::<CharacterLoaderRequest>();
|
||||
@ -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!(
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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::<comp::Body>();
|
||||
let items = ecs.read_storage::<comp::Item>();
|
||||
let inventories = ecs.read_storage::<comp::Inventory>();
|
||||
let msm = ecs.read_resource::<MaterialStatManifest>();
|
||||
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::<comp::Stats>();
|
||||
let buffs = ecs.read_storage::<comp::Buffs>();
|
||||
let msm = ecs.read_resource::<MaterialStatManifest>();
|
||||
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)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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<HotbarSource<'a>, 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<HotbarSource<'a>, 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)
|
||||
{
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user