Code changes and msm

This commit is contained in:
Sam 2022-05-28 19:41:31 -04:00
parent 0e39345243
commit 5e57eabd11
26 changed files with 660 additions and 356 deletions

View File

@ -21,3 +21,4 @@ tracy-voxygen = "run --bin veloren-voxygen --no-default-features --features trac
dbg-voxygen = "run --bin veloren-voxygen --profile debuginfo"
# misc
swarm = "run --bin swarm --features client/bin_bot,client/tick_network --"
ci-clippy = "clippy --all-targets --locked --features=bin_cmd_doc_gen,bin_compression,bin_csv,bin_graphviz,bin_bot,bin_asset_migrate,asset_tweak"

View File

@ -1,5 +1,6 @@
// Keep in mind that material stats are multiplied by the form stats, not added (e.g. equip_time_secs is most sensitive to this)
({
(
tool_stats: {
// Metals
"common.items.mineral.ingot.bronze": (
equip_time_secs: 1.0,
@ -122,4 +123,195 @@
energy_efficiency: 1.0,
buff_strength: 1.0,
),
})
},
armor_stats: {
// Metals
"common.items.mineral.ingot.bronze": (
protection: Some(Normal(40.0)),
poise_resilience: Some(Normal(10.0)),
),
"common.items.mineral.ingot.iron": (
protection: Some(Normal(80.0)),
poise_resilience: Some(Normal(20.0)),
),
"common.items.mineral.ingot.steel": (
protection: Some(Normal(120.0)),
poise_resilience: Some(Normal(30.0)),
),
"common.items.mineral.ingot.cobalt": (
protection: Some(Normal(160.0)),
poise_resilience: Some(Normal(40.0)),
),
"common.items.mineral.ingot.bloodsteel": (
protection: Some(Normal(200.0)),
poise_resilience: Some(Normal(50.0)),
),
"common.items.mineral.ingot.orichalcum": (
protection: Some(Normal(240.0)),
poise_resilience: Some(Normal(60.0)),
),
// Hides
"common.items.crafting_ing.leather.simple_leather": (
protection: Some(Normal(20.0)),
crit_power: Some(0.333),
stealth: Some(0.333),
),
"common.items.crafting_ing.leather.thick_leather": (
protection: Some(Normal(40.0)),
crit_power: Some(0.667),
stealth: Some(0.667),
),
"common.items.crafting_ing.hide.scales": (
protection: Some(Normal(60.0)),
crit_power: Some(1.0),
stealth: Some(1.0),
),
"common.items.crafting_ing.hide.carapace": (
protection: Some(Normal(80.0)),
crit_power: Some(1.33),
stealth: Some(1.33),
),
"common.items.crafting_ing.hide.plate": (
protection: Some(Normal(100.0)),
crit_power: Some(1.67),
stealth: Some(1.67),
),
"common.items.crafting_ing.hide.dragon_scale": (
protection: Some(Normal(120.0)),
crit_power: Some(2.0),
stealth: Some(2.0),
),
// Cloths
"common.items.crafting_ing.cloth.linen": (
protection: Some(Normal(15.0)),
energy_max: Some(16.7),
energy_reward: Some(0.167),
stealth: Some(0.167),
),
"common.items.crafting_ing.cloth.wool": (
protection: Some(Normal(30.0)),
energy_max: Some(33.3),
energy_reward: Some(0.333),
stealth: Some(0.333),
),
"common.items.crafting_ing.cloth.silk": (
protection: Some(Normal(45.0)),
energy_max: Some(50.0),
energy_reward: Some(0.5),
stealth: Some(0.5),
),
"common.items.crafting_ing.cloth.lifecloth": (
protection: Some(Normal(60.0)),
energy_max: Some(66.7),
energy_reward: Some(0.667),
stealth: Some(0.667),
),
"common.items.crafting_ing.cloth.moonweave": (
protection: Some(Normal(75.0)),
energy_max: Some(83.3),
energy_reward: Some(0.833),
stealth: Some(0.833),
),
"common.items.crafting_ing.cloth.sunsilk": (
protection: Some(Normal(90.0)),
energy_max: Some(100.0),
energy_reward: Some(1.0),
stealth: Some(1.0),
),
// Pseudo materials
"common.items.modular.pseudo_material.alchemist": (
protection: Some(Normal(160.0)),
poise_resilience: Some(Normal(20.0)),
energy_max: Some(45.0),
energy_reward: Some(0.5),
crit_power: Some(0.4),
),
"common.items.modular.pseudo_material.assassin": (
protection: Some(Normal(50.0)),
poise_resilience: Some(Normal(5.0)),
),
"common.items.modular.pseudo_material.blacksmith": (
protection: Some(Normal(160.0)),
poise_resilience: Some(Normal(20.0)),
energy_max: Some(45.0),
energy_reward: Some(0.5),
crit_power: Some(0.4),
),
"common.items.modular.pseudo_material.bonerattler": (
protection: Some(Normal(100.0)),
),
"common.items.modular.pseudo_material.chef": (
protection: Some(Normal(160.0)),
poise_resilience: Some(Normal(20.0)),
energy_max: Some(45.0),
energy_reward: Some(0.5),
crit_power: Some(0.4),
),
"common.items.modular.pseudo_material.cloth_blue": (
protection: Some(Normal(5.0)),
),
"common.items.modular.pseudo_material.cloth_green": (
protection: Some(Normal(5.0)),
),
"common.items.modular.pseudo_material.cloth_purple": (
protection: Some(Normal(5.0)),
),
"common.items.modular.pseudo_material.cultist": (
protection: Some(Normal(150.0)),
poise_resilience: Some(Normal(20.0)),
energy_max: Some(45.0),
energy_reward: Some(0.5),
crit_power: Some(0.4),
stealth: Some(0.4),
),
"common.items.modular.pseudo_material.ferocious": (
protection: Some(Normal(240.0)),
),
"common.items.modular.pseudo_material.leather_plate": (
protection: Some(Normal(160.0)),
poise_resilience: Some(Normal(40.0)),
),
"common.items.modular.pseudo_material.merchant": (
protection: Some(Normal(160.0)),
poise_resilience: Some(Normal(20.0)),
energy_max: Some(45.0),
energy_reward: Some(0.5),
crit_power: Some(0.4),
),
"common.items.modular.pseudo_material.pirate": (
protection: Some(Normal(160.0)),
poise_resilience: Some(Normal(20.0)),
energy_max: Some(45.0),
energy_reward: Some(0.5),
crit_power: Some(0.4),
),
"common.items.modular.pseudo_material.rugged": (
protection: Some(Normal(5.0)),
),
"common.items.modular.pseudo_material.savage": (
protection: Some(Normal(100.0)),
),
"common.items.modular.pseudo_material.tarasque": (
protection: Some(Normal(100.0)),
),
"common.items.modular.pseudo_material.twigs": (
protection: Some(Normal(60.0)),
),
"common.items.modular.pseudo_material.twigsflowers": (
protection: Some(Normal(60.0)),
),
"common.items.modular.pseudo_material.twigsleaves": (
protection: Some(Normal(60.0)),
),
"common.items.modular.pseudo_material.velorite_mage": (
protection: Some(Normal(115.0)),
),
"common.items.modular.pseudo_material.witch": (
protection: Some(Normal(160.0)),
poise_resilience: Some(Normal(20.0)),
energy_max: Some(45.0),
energy_reward: Some(0.5),
crit_power: Some(0.4),
),
},
)

View File

@ -14,7 +14,7 @@ use veloren_common::{
item::{
armor::{ArmorKind, Protection},
tool::{Hands, Tool, ToolKind},
Item,
Item, MaterialStatManifest,
},
},
generation::{EntityConfig, EntityInfo},
@ -56,20 +56,22 @@ fn armor_stats() -> Result<(), Box<dyn Error>> {
continue;
}
let protection = match armor.protection() {
let msm = &MaterialStatManifest::load().read();
let protection = match armor.stats(msm).protection {
Some(Protection::Invincible) => "Invincible".to_string(),
Some(Protection::Normal(value)) => value.to_string(),
None => "0.0".to_string(),
};
let poise_resilience = match armor.poise_resilience() {
let poise_resilience = match armor.stats(msm).poise_resilience {
Some(Protection::Invincible) => "Invincible".to_string(),
Some(Protection::Normal(value)) => value.to_string(),
None => "0.0".to_string(),
};
let max_energy = armor.energy_max().unwrap_or(0.0).to_string();
let energy_reward = armor.energy_reward().unwrap_or(0.0).to_string();
let crit_power = armor.crit_power().unwrap_or(0.0).to_string();
let stealth = armor.stealth().unwrap_or(0.0).to_string();
let max_energy = armor.stats(msm).energy_max.unwrap_or(0.0).to_string();
let energy_reward = armor.stats(msm).energy_reward.unwrap_or(0.0).to_string();
let crit_power = armor.stats(msm).crit_power.unwrap_or(0.0).to_string();
let stealth = armor.stats(msm).stealth.unwrap_or(0.0).to_string();
wtr.write_record(&[
item.item_definition_id()

View File

@ -12,7 +12,7 @@ use veloren_common::{
comp::{
self,
item::{
armor::{ArmorKind, Protection},
armor::{ArmorKind, Protection, StatsSource},
tool::{AbilitySpec, Hands, Stats, ToolKind},
ItemDefinitionId, ItemKind, ItemTag, Material, Quality,
},
@ -124,7 +124,7 @@ fn armor_stats() -> Result<(), Box<dyn Error>> {
None
};
let max_energy =
let energy_max =
if let Some(max_energy_raw) = record.get(headers["Max Energy"]) {
let value = max_energy_raw.parse().unwrap();
if value == 0.0 { None } else { Some(value) }
@ -174,15 +174,19 @@ fn armor_stats() -> Result<(), Box<dyn Error>> {
};
let kind = armor.kind;
let armor_stats = comp::item::armor::Stats::new(
let armor_stats = comp::item::armor::Stats {
protection,
poise_resilience,
max_energy,
energy_max,
energy_reward,
crit_power,
stealth,
ground_contact: Default::default(),
};
let armor = comp::item::armor::Armor::new(
kind,
StatsSource::Direct(armor_stats),
);
let armor = comp::item::armor::Armor::new(kind, armor_stats);
let quality = if let Some(quality_raw) = record.get(headers["Quality"])
{
match quality_raw {

View File

@ -133,11 +133,12 @@ impl Attack {
source: AttackSource,
dir: Dir,
damage: Damage,
msm: &MaterialStatManifest,
mut emit: impl FnMut(ServerEvent),
mut emit_outcome: impl FnMut(Outcome),
) -> f32 {
let damage_reduction =
Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats);
Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats, msm);
let block_reduction = match source {
AttackSource::Melee => {
if let (Some(CharacterState::BasicBlock(data)), Some(ori)) =
@ -186,6 +187,9 @@ impl Attack {
mut emit: impl FnMut(ServerEvent),
mut emit_outcome: impl FnMut(Outcome),
) -> bool {
// TODO: Maybe move this higher and pass it as argument into this function?
let msm = &MaterialStatManifest::load().read();
let AttackOptions {
target_dodging,
may_harm,
@ -220,6 +224,7 @@ impl Attack {
attack_source,
dir,
damage.damage,
msm,
&mut emit,
&mut emit_outcome,
);
@ -272,7 +277,7 @@ impl Attack {
let reduced_damage =
applied_damage * damage_reduction / (1.0 - damage_reduction);
let poise = reduced_damage * CRUSHING_POISE_FRACTION;
let change = -Poise::apply_poise_reduction(poise, target.inventory);
let change = -Poise::apply_poise_reduction(poise, target.inventory, msm);
let poise_change = PoiseChange {
amount: change,
impulse: *dir,
@ -307,7 +312,7 @@ impl Attack {
emit(ServerEvent::EnergyChange {
entity: attacker.entity,
change: *ec
* compute_energy_reward_mod(attacker.inventory)
* compute_energy_reward_mod(attacker.inventory, msm)
* strength_modifier,
});
}
@ -342,7 +347,7 @@ impl Attack {
}
},
CombatEffect::Poise(p) => {
let change = -Poise::apply_poise_reduction(*p, target.inventory)
let change = -Poise::apply_poise_reduction(*p, target.inventory, msm)
* strength_modifier;
if change.abs() > Poise::POISE_EPSILON {
let poise_change = PoiseChange {
@ -450,7 +455,7 @@ impl Attack {
emit(ServerEvent::EnergyChange {
entity: attacker.entity,
change: ec
* compute_energy_reward_mod(attacker.inventory)
* compute_energy_reward_mod(attacker.inventory, msm)
* strength_modifier,
});
}
@ -485,8 +490,8 @@ impl Attack {
}
},
CombatEffect::Poise(p) => {
let change =
-Poise::apply_poise_reduction(p, target.inventory) * strength_modifier;
let change = -Poise::apply_poise_reduction(p, target.inventory, msm)
* strength_modifier;
if change.abs() > Poise::POISE_EPSILON {
let poise_change = PoiseChange {
amount: change,
@ -755,8 +760,9 @@ impl Damage {
damage: Option<Self>,
inventory: Option<&Inventory>,
stats: Option<&Stats>,
msm: &MaterialStatManifest,
) -> f32 {
let protection = compute_protection(inventory);
let protection = compute_protection(inventory, msm);
let penetration = if let Some(damage) = damage {
if let DamageKind::Piercing = damage.kind {
@ -1058,20 +1064,20 @@ pub fn combat_rating(
// Normalized with a standard max health of 100
let health_rating = health.base_max()
/ 100.0
/ (1.0 - Damage::compute_damage_reduction(None, Some(inventory), None)).max(0.00001);
/ (1.0 - Damage::compute_damage_reduction(None, Some(inventory), None, msm)).max(0.00001);
// Normalized with a standard max energy of 100 and energy reward multiplier of
// x1
let energy_rating = (energy.base_max() + compute_max_energy_mod(Some(inventory))) / 100.0
* compute_energy_reward_mod(Some(inventory));
let energy_rating = (energy.base_max() + compute_max_energy_mod(Some(inventory), msm)) / 100.0
* compute_energy_reward_mod(Some(inventory), msm);
// Normalized with a standard max poise of 100
let poise_rating = poise.base_max() as f32
/ 100.0
/ (1.0 - Poise::compute_poise_damage_reduction(inventory)).max(0.00001);
/ (1.0 - Poise::compute_poise_damage_reduction(inventory, msm)).max(0.00001);
// Normalized with a standard crit multiplier of 1.2
let crit_rating = compute_crit_mult(Some(inventory)) / 1.2;
let crit_rating = compute_crit_mult(Some(inventory), msm) / 1.2;
// Assumes a standard person has earned 20 skill points in the general skill
// tree and 10 skill points for the weapon skill tree
@ -1100,14 +1106,14 @@ pub fn combat_rating(
}
#[cfg(not(target_arch = "wasm32"))]
pub fn compute_crit_mult(inventory: Option<&Inventory>) -> f32 {
pub fn compute_crit_mult(inventory: Option<&Inventory>, msm: &MaterialStatManifest) -> f32 {
// Starts with a value of 1.25 when summing the stats from each armor piece, and
// defaults to a value of 1.25 if no inventory is equipped
inventory.map_or(1.25, |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.crit_power()
armor.stats(msm).crit_power
} else {
None
}
@ -1118,14 +1124,14 @@ pub fn compute_crit_mult(inventory: Option<&Inventory>) -> f32 {
/// Computes the energy reward modifer from worn armor
#[cfg(not(target_arch = "wasm32"))]
pub fn compute_energy_reward_mod(inventory: Option<&Inventory>) -> f32 {
pub fn compute_energy_reward_mod(inventory: Option<&Inventory>, msm: &MaterialStatManifest) -> f32 {
// Starts with a value of 1.0 when summing the stats from each armor piece, and
// defaults to a value of 1.0 if no inventory is present
inventory.map_or(1.0, |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.energy_reward()
armor.stats(msm).energy_reward
} else {
None
}
@ -1137,13 +1143,13 @@ pub fn compute_energy_reward_mod(inventory: Option<&Inventory>) -> f32 {
/// Computes the additive modifier that should be applied to max energy from the
/// currently equipped items
#[cfg(not(target_arch = "wasm32"))]
pub fn compute_max_energy_mod(inventory: Option<&Inventory>) -> f32 {
pub fn compute_max_energy_mod(inventory: Option<&Inventory>, msm: &MaterialStatManifest) -> f32 {
// Defaults to a value of 0 if no inventory is present
inventory.map_or(0.0, |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.energy_max()
armor.stats(msm).energy_max
} else {
None
}
@ -1158,10 +1164,11 @@ pub fn compute_max_energy_mod(inventory: Option<&Inventory>) -> f32 {
pub fn perception_dist_multiplier_from_stealth(
inventory: Option<&Inventory>,
character_state: Option<&CharacterState>,
msm: &MaterialStatManifest,
) -> f32 {
const SNEAK_MULTIPLIER: f32 = 0.7;
let item_stealth_multiplier = stealth_multiplier_from_items(inventory);
let item_stealth_multiplier = stealth_multiplier_from_items(inventory, msm);
let is_sneaking = character_state.map_or(false, |state| state.is_stealthy());
let multiplier = item_stealth_multiplier * if is_sneaking { SNEAK_MULTIPLIER } else { 1.0 };
@ -1170,12 +1177,15 @@ pub fn perception_dist_multiplier_from_stealth(
}
#[cfg(not(target_arch = "wasm32"))]
pub fn stealth_multiplier_from_items(inventory: Option<&Inventory>) -> f32 {
pub fn stealth_multiplier_from_items(
inventory: Option<&Inventory>,
msm: &MaterialStatManifest,
) -> f32 {
let stealth_sum = inventory.map_or(0.0, |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.stealth()
armor.stats(msm).stealth
} else {
None
}
@ -1190,12 +1200,15 @@ pub fn stealth_multiplier_from_items(inventory: Option<&Inventory>) -> f32 {
/// damage reduction applied to damage received by an entity None indicates that
/// the armor equipped makes the entity invulnerable
#[cfg(not(target_arch = "wasm32"))]
pub fn compute_protection(inventory: Option<&Inventory>) -> Option<f32> {
pub fn compute_protection(
inventory: Option<&Inventory>,
msm: &MaterialStatManifest,
) -> Option<f32> {
inventory.map_or(Some(0.0), |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.protection()
armor.stats(msm).protection
} else {
None
}

View File

@ -1,11 +1,15 @@
use crate::{
comp::item::Rgb,
comp::item::{MaterialStatManifest, Rgb},
terrain::{Block, BlockKind},
};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, ops::Sub};
use std::{
cmp::Ordering,
ops::{Mul, Sub},
};
use strum::{EnumIter, IntoEnumIterator};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter)]
pub enum ArmorKind {
Shoulder,
Chest,
@ -69,66 +73,66 @@ impl Friction {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Default)]
pub struct Stats {
/// Protection is non-linearly transformed (following summation) to a damage
/// reduction using (prot / (60 + prot))
protection: Option<Protection>,
pub protection: Option<Protection>,
/// Poise protection is non-linearly transformed (following summation) to a
/// poise damage reduction using (prot / (60 + prot))
poise_resilience: Option<Protection>,
pub poise_resilience: Option<Protection>,
/// Energy max is summed, and then applied directly to the max energy stat
energy_max: Option<f32>,
pub energy_max: Option<f32>,
/// Energy recovery is summed, and then added to 1.0. When attacks reward
/// energy, it is then multiplied by this value before the energy is
/// rewarded.
energy_reward: Option<f32>,
pub energy_reward: Option<f32>,
/// Crit power is summed, and then added to the default crit multiplier of
/// 1.25. Damage is multiplied by this value when an attack crits.
crit_power: Option<f32>,
pub crit_power: Option<f32>,
/// Stealth is summed along with the base stealth bonus (2.0), and then
/// the agent's perception distance is divided by this value
stealth: Option<f32>,
pub stealth: Option<f32>,
/// Ground contact type, mostly for shoes
#[serde(default)]
ground_contact: Friction,
pub ground_contact: Friction,
}
impl Stats {
// DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
// Added for csv import of stats
pub fn new(
protection: Option<Protection>,
poise_resilience: Option<Protection>,
energy_max: Option<f32>,
energy_reward: Option<f32>,
crit_power: Option<f32>,
stealth: Option<f32>,
) -> Self {
Self {
protection,
poise_resilience,
energy_max,
energy_reward,
crit_power,
stealth,
fn none() -> Self {
Stats {
protection: None,
poise_resilience: None,
energy_max: None,
energy_reward: None,
crit_power: None,
stealth: None,
ground_contact: Friction::Normal,
}
}
}
pub fn protection(&self) -> Option<Protection> { self.protection }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum StatsSource {
Direct(Stats),
FromSet(String),
}
pub fn poise_resilience(&self) -> Option<Protection> { self.poise_resilience }
impl Mul<f32> for Stats {
type Output = Self;
pub fn energy_max(&self) -> Option<f32> { self.energy_max }
pub fn energy_reward(&self) -> Option<f32> { self.energy_reward }
pub fn crit_power(&self) -> Option<f32> { self.crit_power }
pub fn stealth(&self) -> Option<f32> { self.stealth }
pub fn ground_contact(&self) -> Friction { self.ground_contact }
fn mul(self, val: f32) -> Self::Output {
Stats {
protection: self.protection.map(|a| a * val),
poise_resilience: self.poise_resilience.map(|a| a * val),
energy_max: self.energy_max.map(|a| a * val),
energy_reward: self.energy_reward.map(|a| a * val),
crit_power: self.crit_power.map(|a| a * val),
stealth: self.stealth.map(|a| a * val),
// There is nothing to multiply, it is just an enum
ground_contact: self.ground_contact,
}
}
}
impl Sub<Stats> for Stats {
@ -177,6 +181,17 @@ impl Sub for Protection {
}
}
impl Mul<f32> for Protection {
type Output = Self;
fn mul(self, val: f32) -> Self::Output {
match self {
Protection::Invincible => Protection::Invincible,
Protection::Normal(a) => Protection::Normal(a * val),
}
}
}
impl PartialOrd for Protection {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (*self, *other) {
@ -191,25 +206,39 @@ impl PartialOrd for Protection {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Armor {
pub kind: ArmorKind,
pub stats: Stats,
stats: StatsSource,
}
impl Armor {
pub fn new(kind: ArmorKind, stats: Stats) -> Self { Self { kind, stats } }
pub fn new(kind: ArmorKind, stats: StatsSource) -> Self { Self { kind, stats } }
pub fn protection(&self) -> Option<Protection> { self.stats.protection }
pub fn stats(&self, msm: &MaterialStatManifest) -> Stats {
match &self.stats {
StatsSource::Direct(stats) => *stats,
StatsSource::FromSet(set) => {
let set_stats = msm.armor_stats(set).unwrap_or_else(Stats::none);
let armor_kind_weight = |kind| match kind {
ArmorKind::Shoulder => 2.0,
ArmorKind::Chest => 3.0,
ArmorKind::Belt => 0.5,
ArmorKind::Hand => 1.0,
ArmorKind::Pants => 2.0,
ArmorKind::Foot => 1.0,
ArmorKind::Back => 0.5,
ArmorKind::Ring => 0.0,
ArmorKind::Neck => 0.0,
ArmorKind::Head => 0.0,
ArmorKind::Tabard => 0.0,
ArmorKind::Bag => 0.0,
};
pub fn poise_resilience(&self) -> Option<Protection> { self.stats.poise_resilience }
let armor_weights_sum: f32 = ArmorKind::iter().map(armor_kind_weight).sum();
let multiplier = armor_kind_weight(self.kind) / armor_weights_sum;
pub fn energy_max(&self) -> Option<f32> { self.stats.energy_max }
pub fn energy_reward(&self) -> Option<f32> { self.stats.energy_reward }
pub fn crit_power(&self) -> Option<f32> { self.stats.crit_power }
pub fn stealth(&self) -> Option<f32> { self.stats.stealth }
pub fn ground_contact(&self) -> Friction { self.stats.ground_contact }
set_stats * multiplier
},
}
}
#[cfg(test)]
pub fn test_armor(
@ -219,7 +248,7 @@ impl Armor {
) -> Armor {
Armor {
kind,
stats: Stats {
stats: StatsSource::Direct(Stats {
protection: Some(protection),
poise_resilience: Some(poise_resilience),
energy_max: None,
@ -227,7 +256,7 @@ impl Armor {
crit_power: None,
stealth: None,
ground_contact: Friction::Normal,
},
}),
}
}
}

View File

@ -4,8 +4,8 @@ pub mod modular;
pub mod tool;
// Reexports
pub use modular::{ModularBase, ModularComponent};
pub use tool::{AbilitySet, AbilitySpec, Hands, MaterialStatManifest, Tool, ToolKind};
pub use modular::{MaterialStatManifest, ModularBase, ModularComponent};
pub use tool::{AbilitySet, AbilitySpec, Hands, Tool, ToolKind};
use crate::{
assets::{self, AssetExt, BoxedError, Error},

View File

@ -1,8 +1,12 @@
use super::{
tool::{self, AbilityMap, AbilitySpec, Hands, MaterialStatManifest},
armor,
tool::{self, AbilityMap, AbilitySpec, Hands},
Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, Material, Quality, ToolKind,
};
use crate::{assets::AssetExt, recipe};
use crate::{
assets::{self, Asset, AssetExt, AssetHandle},
recipe,
};
use common_base::dev_panic;
use hashbrown::HashMap;
use lazy_static::lazy_static;
@ -20,6 +24,28 @@ macro_rules! modular_item_id_prefix {
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MaterialStatManifest {
tool_stats: HashMap<String, tool::Stats>,
armor_stats: HashMap<String, armor::Stats>,
}
impl MaterialStatManifest {
pub fn load() -> AssetHandle<Self> { Self::load_expect("common.material_stats_manifest") }
pub fn armor_stats(&self, key: &str) -> Option<armor::Stats> {
self.armor_stats.get(key).copied()
}
}
// 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";
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ModularBase {
Tool,
@ -241,7 +267,7 @@ impl ModularComponent {
.filter_map(|comp| {
comp.item_definition_id()
.itemdef_id()
.and_then(|id| msm.0.get(id))
.and_then(|id| msm.tool_stats.get(id))
.copied()
.zip(Some(1))
})

View File

@ -225,21 +225,6 @@ impl DivAssign<usize> for Stats {
fn div_assign(&mut self, scalar: usize) { *self = *self / (scalar as f32); }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MaterialStatManifest(pub HashMap<String, Stats>);
impl MaterialStatManifest {
pub fn load() -> AssetHandle<Self> { Self::load_expect("common.material_stats_manifest") }
}
// This could be a Compound that also loads the keys, but the RecipeBook
// Compound impl already does that, so checking for existence here is redundant.
impl Asset for MaterialStatManifest {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Tool {
pub kind: ToolKind,

View File

@ -408,7 +408,7 @@ impl Loadout {
pub fn persistence_update_all_item_states(
&mut self,
ability_map: &item::tool::AbilityMap,
msm: &item::tool::MaterialStatManifest,
msm: &item::MaterialStatManifest,
) {
self.slots.iter_mut().for_each(|slot| {
if let Some(item) = &mut slot.slot {

View File

@ -808,7 +808,7 @@ impl Inventory {
pub fn persistence_update_all_item_states(
&mut self,
ability_map: &item::tool::AbilityMap,
msm: &item::tool::MaterialStatManifest,
msm: &item::MaterialStatManifest,
) {
self.slots_mut().for_each(|slot| {
if let Some(item) = slot {

View File

@ -686,14 +686,16 @@ impl TradePricing {
#[cfg(test)]
fn print_sorted(&self) {
use crate::comp::item::{armor, ItemKind};
use crate::comp::item::{armor, ItemKind, MaterialStatManifest};
println!("Item, ForSale, Amount, Good, Quality, Deal, Unit,");
fn more_information(i: &Item, p: f32) -> (String, &'static str) {
let msm = &MaterialStatManifest::load().read();
if let ItemKind::Armor(a) = &*i.kind() {
(
match a.protection() {
match a.stats(msm).protection {
Some(armor::Protection::Invincible) => "Invincible".into(),
Some(armor::Protection::Normal(x)) => format!("{:.4}", x * p),
None => "0.0".into(),

View File

@ -2,7 +2,7 @@ use crate::{
combat::{DamageContributor, DamageSource},
comp::{
self,
inventory::item::{armor::Protection, ItemKind},
inventory::item::{armor::Protection, ItemKind, MaterialStatManifest},
CharacterState, Inventory,
},
resources::Time,
@ -226,12 +226,15 @@ impl Poise {
}
/// Returns the total poise damage reduction provided by all equipped items
pub fn compute_poise_damage_reduction(inventory: &Inventory) -> f32 {
pub fn compute_poise_damage_reduction(
inventory: &Inventory,
msm: &MaterialStatManifest,
) -> f32 {
let protection = inventory
.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.poise_resilience()
armor.stats(msm).poise_resilience
} else {
None
}
@ -249,9 +252,13 @@ impl Poise {
/// Modifies a poise change when optionally given an inventory to aid in
/// calculation of poise damage reduction
pub fn apply_poise_reduction(value: f32, inventory: Option<&Inventory>) -> f32 {
pub fn apply_poise_reduction(
value: f32,
inventory: Option<&Inventory>,
msm: &MaterialStatManifest,
) -> f32 {
inventory.map_or(value, |inv| {
value * (1.0 - Poise::compute_poise_damage_reduction(inv))
value * (1.0 - Poise::compute_poise_damage_reduction(inv, msm))
})
}
}

View File

@ -312,7 +312,7 @@ pub fn handle_skating(data: &JoinData, update: &mut StateUpdate) {
footwear = data.inventory.and_then(|inv| {
inv.equipped(EquipSlot::Armor(ArmorSlot::Feet))
.map(|armor| match armor.kind().as_ref() {
ItemKind::Armor(a) => a.ground_contact(),
ItemKind::Armor(a) => a.stats(data.msm).ground_contact,
_ => crate::comp::inventory::item::armor::Friction::Normal,
})
});
@ -1084,7 +1084,7 @@ pub fn get_crit_data(data: &JoinData<'_>, ai: AbilityInfo) -> (f32, f32) {
})
.unwrap_or(DEFAULT_CRIT_CHANCE);
let crit_mult = combat::compute_crit_mult(data.inventory);
let crit_mult = combat::compute_crit_mult(data.inventory, data.msm);
(crit_chance, crit_mult)
}

View File

@ -7,6 +7,7 @@ use common::{
Buffs,
},
fluid_dynamics::{Fluid, LiquidKind},
item::MaterialStatManifest,
Energy, Group, Health, HealthChange, Inventory, LightEmitter, ModifierKind, PhysicsState,
Stats,
},
@ -21,8 +22,8 @@ use common_ecs::{Job, Origin, ParMode, Phase, System};
use hashbrown::HashMap;
use rayon::iter::ParallelIterator;
use specs::{
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadStorage,
SystemData, World, WriteStorage,
saveload::MarkerAllocator, shred::ResourceId, Entities, Join, ParJoin, Read, ReadExpect,
ReadStorage, SystemData, World, WriteStorage,
};
use std::time::Duration;
@ -38,6 +39,7 @@ pub struct ReadData<'a> {
groups: ReadStorage<'a, Group>,
uid_allocator: Read<'a, UidAllocator>,
time: Read<'a, Time>,
msm: ReadExpect<'a, MaterialStatManifest>,
}
#[derive(Default)]
@ -207,6 +209,7 @@ impl<'a> System<'a> for Sys {
None,
read_data.inventories.get(entity),
Some(&stat),
&read_data.msm,
);
if (damage_reduction - 1.0).abs() < f32::EPSILON {
for (id, buff) in buff_comp.buffs.iter() {

View File

@ -2,6 +2,7 @@ use common::{
combat,
comp::{
self,
item::MaterialStatManifest,
skills::{GeneralSkill, Skill},
Body, CharacterState, Combo, Energy, Health, Inventory, Poise, PoiseChange, Pos, SkillSet,
Stats, StatsModifier,
@ -11,7 +12,8 @@ use common::{
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{
shred::ResourceId, Entities, Join, Read, ReadStorage, SystemData, World, Write, WriteStorage,
shred::ResourceId, Entities, Join, Read, ReadExpect, ReadStorage, SystemData, World, Write,
WriteStorage,
};
use vek::Vec3;
@ -28,6 +30,7 @@ pub struct ReadData<'a> {
bodies: ReadStorage<'a, Body>,
char_states: ReadStorage<'a, CharacterState>,
inventories: ReadStorage<'a, Inventory>,
msm: ReadExpect<'a, MaterialStatManifest>,
}
/// This system kills players, levels them up, and regenerates energy.
@ -100,7 +103,7 @@ impl<'a> System<'a> for Sys {
// Calculates energy scaling from stats and inventory
let energy_mods = StatsModifier {
add_mod: stat.max_energy_modifiers.add_mod
+ combat::compute_max_energy_mod(inventory),
+ combat::compute_max_energy_mod(inventory, &read_data.msm),
mult_mod: stat.max_energy_modifiers.mult_mod,
};

View File

@ -558,7 +558,8 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
let inventories = ecs.read_storage::<Inventory>();
let stats = ecs.read_storage::<Stats>();
let time = server.state.ecs().read_resource::<Time>();
let time = ecs.read_resource::<Time>();
let msm = ecs.read_resource::<MaterialStatManifest>();
// Handle health change
if let Some(mut health) = ecs.write_storage::<comp::Health>().get_mut(entity) {
@ -571,6 +572,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
Some(damage),
inventories.get(entity),
stats.get(entity),
&msm,
);
let change =
damage.calculate_health_change(damage_reduction, None, false, 0.0, 1.0, *time);
@ -579,7 +581,8 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
// Handle poise change
if let Some(mut poise) = ecs.write_storage::<comp::Poise>().get_mut(entity) {
let poise_damage = -(mass.0 * vel.magnitude_squared() / 1500.0);
let poise_change = Poise::apply_poise_reduction(poise_damage, inventories.get(entity));
let poise_change =
Poise::apply_poise_reduction(poise_damage, inventories.get(entity), &msm);
let poise_change = comp::PoiseChange {
amount: poise_change,
impulse: Vec3::unit_z(),

View File

@ -1092,7 +1092,7 @@ impl Server {
material_stats: (&*self
.state
.ecs()
.read_resource::<comp::item::tool::MaterialStatManifest>())
.read_resource::<comp::item::MaterialStatManifest>())
.clone(),
ability_map: (&*self
.state

View File

@ -15,6 +15,7 @@ use common::{
combat::DamageContributor,
comp::{
self,
item::MaterialStatManifest,
skills::{GeneralSkill, Skill},
Group, Inventory, Item, Poise,
},
@ -127,6 +128,7 @@ pub trait StateExt {
impl StateExt for State {
fn apply_effect(&self, entity: EcsEntity, effects: Effect, source: Option<Uid>) {
let msm = self.ecs().read_resource::<MaterialStatManifest>();
match effects {
Effect::Health(change) => {
self.ecs()
@ -150,6 +152,7 @@ impl StateExt for State {
Some(damage),
inventories.get(entity),
stats.get(entity),
&msm,
),
damage_contributor,
false,
@ -164,7 +167,7 @@ impl StateExt for State {
},
Effect::Poise(poise) => {
let inventories = self.ecs().read_storage::<Inventory>();
let change = Poise::apply_poise_reduction(poise, inventories.get(entity));
let change = Poise::apply_poise_reduction(poise, inventories.get(entity), &msm);
// Check to make sure the entity is not already stunned
if let Some(character_state) = self
.ecs()

View File

@ -236,6 +236,7 @@ impl<'a> System<'a> for Sys {
char_state,
active_abilities,
cached_spatial_grid: &read_data.cached_spatial_grid,
msm: &read_data.msm,
};
///////////////////////////////////////////////////////////
// Behavior tree
@ -2502,7 +2503,7 @@ impl<'a> AgentData<'a> {
let other_inventory = read_data.inventories.get(other);
let other_char_state = read_data.char_states.get(other);
perception_dist_multiplier_from_stealth(other_inventory, other_char_state)
perception_dist_multiplier_from_stealth(other_inventory, other_char_state, self.msm)
};
let within_sight_dist = {

View File

@ -1,9 +1,9 @@
use crate::rtsim::Entity as RtSimData;
use common::{
comp::{
buff::Buffs, group, ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy,
Health, Inventory, LightEmitter, LootOwner, Ori, PhysicsState, Pos, Scale, SkillSet, Stats,
Vel,
buff::Buffs, group, item::MaterialStatManifest, ActiveAbilities, Alignment, Body,
CharacterState, Combo, Energy, Health, Inventory, LightEmitter, LootOwner, Ori,
PhysicsState, Pos, Scale, SkillSet, Stats, Vel,
},
link::Is,
mounting::Mount,
@ -43,6 +43,7 @@ pub struct AgentData<'a> {
pub char_state: &'a CharacterState,
pub active_abilities: &'a ActiveAbilities,
pub cached_spatial_grid: &'a common::CachedSpatialGrid,
pub msm: &'a MaterialStatManifest,
}
pub struct TargetData<'a> {
@ -154,6 +155,7 @@ pub struct ReadData<'a> {
pub combos: ReadStorage<'a, Combo>,
pub active_abilities: ReadStorage<'a, ActiveAbilities>,
pub loot_owners: ReadStorage<'a, LootOwner>,
pub msm: ReadExpect<'a, MaterialStatManifest>,
}
pub enum Path {

View File

@ -937,19 +937,28 @@ impl<'a> Widget for Bag<'a> {
let protection_txt = format!(
"{}%",
(100.0
* Damage::compute_damage_reduction(None, Some(inventory), Some(self.stats)))
as i32
* Damage::compute_damage_reduction(
None,
Some(inventory),
Some(self.stats),
self.msm
)) as i32
);
let health_txt = format!("{}", self.health.maximum().round() as usize);
let energy_txt = format!("{}", self.energy.maximum().round() as usize);
let combat_rating_txt = format!("{}", (combat_rating * 10.0) as usize);
let stun_res_txt = format!(
"{}",
(100.0 * Poise::compute_poise_damage_reduction(inventory)) as i32
(100.0 * Poise::compute_poise_damage_reduction(inventory, self.msm)) as i32
);
let stealth_txt = format!(
"{:.1}%",
((1.0 - perception_dist_multiplier_from_stealth(Some(inventory), None))
((1.0
- perception_dist_multiplier_from_stealth(
Some(inventory),
None,
self.msm
))
* 100.0)
);
let btn = if i.0 == 0 {

View File

@ -33,11 +33,7 @@ use common::{
self,
ability::{Ability, ActiveAbilities, AuxiliaryAbility, MAX_ABILITIES},
inventory::{
item::{
item_key::ItemKey,
tool::{MaterialStatManifest, ToolKind},
ItemKind,
},
item::{item_key::ItemKey, tool::ToolKind, ItemKind, MaterialStatManifest},
slot::EquipSlot,
},
skills::{
@ -1184,23 +1180,25 @@ impl<'a> Widget for Diary<'a> {
format!("{:.2}", cr * 10.0)
},
"Protection" => {
let protection = combat::compute_protection(Some(self.inventory));
let protection =
combat::compute_protection(Some(self.inventory), self.msm);
match protection {
Some(prot) => format!("{}", prot),
None => String::from("Invincible"),
}
},
"Stun-Resistance" => {
let stun_res = Poise::compute_poise_damage_reduction(self.inventory);
let stun_res =
Poise::compute_poise_damage_reduction(self.inventory, self.msm);
format!("{:.2}%", stun_res * 100.0)
},
"Crit-Power" => {
let critpwr = combat::compute_crit_mult(Some(self.inventory));
let critpwr = combat::compute_crit_mult(Some(self.inventory), self.msm);
format!("x{:.2}", critpwr)
},
"Energy Reward" => {
let energy_rew =
combat::compute_energy_reward_mod(Some(self.inventory));
combat::compute_energy_reward_mod(Some(self.inventory), self.msm);
format!("{:+.0}%", (energy_rew - 1.0) * 100.0)
},
"Stealth" => {
@ -1208,6 +1206,7 @@ impl<'a> Widget for Diary<'a> {
combat::perception_dist_multiplier_from_stealth(
Some(self.inventory),
None,
self.msm,
);
let txt =
format!("{:+.1}%", (1.0 - stealth_perception_multiplier) * 100.0);

View File

@ -5,7 +5,7 @@ use common::{
item::{
armor::{Armor, ArmorKind, Protection},
tool::{Hands, Tool, ToolKind},
ItemDesc, ItemKind, MaterialKind,
ItemDesc, ItemKind, MaterialKind, MaterialStatManifest,
},
BuffKind,
},
@ -109,17 +109,17 @@ pub fn material_kind_text<'a>(kind: &MaterialKind, i18n: &'a Localization) -> &'
}
}
pub fn stats_count(item: &dyn ItemDesc) -> usize {
pub fn stats_count(item: &dyn ItemDesc, msm: &MaterialStatManifest) -> usize {
let mut count = match &*item.kind() {
ItemKind::Armor(armor) => {
if matches!(armor.kind, ArmorKind::Bag) {
0
} else {
armor.stats.energy_reward().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.stealth().is_some() as usize
+ armor.stats.crit_power().is_some() as usize
+ armor.stats.poise_resilience().is_some() as usize
armor.stats(msm).energy_reward.is_some() as usize
+ armor.stats(msm).energy_max.is_some() as usize
+ armor.stats(msm).stealth.is_some() as usize
+ armor.stats(msm).crit_power.is_some() as usize
+ armor.stats(msm).poise_resilience.is_some() as usize
}
},
ItemKind::Tool(_) => 7,

View File

@ -18,8 +18,8 @@ use common::{
inventory::slot::{EquipSlot, Slot},
invite::InviteKind,
item::{
tool::{AbilityMap, MaterialStatManifest, ToolKind},
ItemDesc,
tool::{AbilityMap, ToolKind},
ItemDesc, MaterialStatManifest,
},
ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, Stats, UtteranceKind, Vel,
},

View File

@ -505,17 +505,19 @@ impl<'a> Widget for ItemTooltip<'a> {
_ => self.imgs.inv_slot_red,
};
let stats_count = util::stats_count(item, self.msm);
// Update widget array size
state.update(|s| {
s.ids
.stats
.resize(util::stats_count(item), &mut ui.widget_id_generator())
.resize(stats_count, &mut ui.widget_id_generator())
});
state.update(|s| {
s.ids
.diffs
.resize(util::stats_count(item), &mut ui.widget_id_generator())
.resize(stats_count, &mut ui.widget_id_generator())
});
// Background image frame
@ -820,12 +822,22 @@ impl<'a> Widget for ItemTooltip<'a> {
},
_ => {
// Armour
let protection = armor.protection().unwrap_or(Protection::Normal(0.0));
let poise_res = armor.poise_resilience().unwrap_or(Protection::Normal(0.0));
let energy_max = armor.energy_max().unwrap_or(0.0);
let energy_reward = armor.energy_reward().map(|x| x * 100.0).unwrap_or(0.0);
let crit_power = armor.crit_power().unwrap_or(0.0);
let stealth = armor.stealth().unwrap_or(0.0);
let protection = armor
.stats(self.msm)
.protection
.unwrap_or(Protection::Normal(0.0));
let poise_res = armor
.stats(self.msm)
.poise_resilience
.unwrap_or(Protection::Normal(0.0));
let energy_max = armor.stats(self.msm).energy_max.unwrap_or(0.0);
let energy_reward = armor
.stats(self.msm)
.energy_reward
.map(|x| x * 100.0)
.unwrap_or(0.0);
let crit_power = armor.stats(self.msm).crit_power.unwrap_or(0.0);
let stealth = armor.stats(self.msm).stealth.unwrap_or(0.0);
widget::Text::new(&util::protec2string(protection))
.graphics_for(id)
@ -847,7 +859,7 @@ impl<'a> Widget for ItemTooltip<'a> {
.set(state.ids.main_stat_text, ui);
// Poise res
if armor.stats.poise_resilience().is_some() {
if armor.stats(self.msm).poise_resilience.is_some() {
widget::Text::new(&format!(
"{} : {}",
i18n.get("common.stats.poise_res"),
@ -863,7 +875,7 @@ impl<'a> Widget for ItemTooltip<'a> {
}
// Max Energy
if armor.stats.energy_max().is_some() {
if armor.stats(self.msm).energy_max.is_some() {
widget::Text::new(&format!(
"{} : {:.1}",
i18n.get("common.stats.energy_max"),
@ -874,7 +886,7 @@ impl<'a> Widget for ItemTooltip<'a> {
.with_style(self.style.desc)
.color(text_color)
.and(|t| {
if armor.stats.poise_resilience().is_some() {
if armor.stats(self.msm).poise_resilience.is_some() {
t.down_from(state.ids.stats[0], V_PAD_STATS)
} else {
t.x_align_to(
@ -885,13 +897,14 @@ impl<'a> Widget for ItemTooltip<'a> {
}
})
.set(
state.ids.stats[armor.stats.poise_resilience().is_some() as usize],
state.ids.stats
[armor.stats(self.msm).poise_resilience.is_some() as usize],
ui,
);
}
// Energy Recovery
if armor.stats.energy_reward().is_some() {
if armor.stats(self.msm).energy_reward.is_some() {
widget::Text::new(&format!(
"{} : {:.1}%",
i18n.get("common.stats.energy_reward"),
@ -902,8 +915,8 @@ impl<'a> Widget for ItemTooltip<'a> {
.with_style(self.style.desc)
.color(text_color)
.and(|t| {
match armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
match armor.stats(self.msm).poise_resilience.is_some() as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
{
0 => t
.x_align_to(
@ -915,14 +928,15 @@ impl<'a> Widget for ItemTooltip<'a> {
}
})
.set(
state.ids.stats[armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize],
state.ids.stats[armor.stats(self.msm).poise_resilience.is_some()
as usize
+ armor.stats(self.msm).energy_max.is_some() as usize],
ui,
);
}
// Crit Power
if armor.stats.crit_power().is_some() {
if armor.stats(self.msm).crit_power.is_some() {
widget::Text::new(&format!(
"{} : {:.3}",
i18n.get("common.stats.crit_power"),
@ -933,9 +947,9 @@ impl<'a> Widget for ItemTooltip<'a> {
.with_style(self.style.desc)
.color(text_color)
.and(|t| {
match armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.energy_reward().is_some() as usize
match armor.stats(self.msm).poise_resilience.is_some() as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
+ armor.stats(self.msm).energy_reward.is_some() as usize
{
0 => t
.x_align_to(
@ -947,15 +961,16 @@ impl<'a> Widget for ItemTooltip<'a> {
}
})
.set(
state.ids.stats[armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.energy_reward().is_some() as usize],
state.ids.stats[armor.stats(self.msm).poise_resilience.is_some()
as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
+ armor.stats(self.msm).energy_reward.is_some() as usize],
ui,
);
}
// Stealth
if armor.stats.stealth().is_some() {
if armor.stats(self.msm).stealth.is_some() {
widget::Text::new(&format!(
"{} : {:.3}",
i18n.get("common.stats.stealth"),
@ -966,10 +981,10 @@ impl<'a> Widget for ItemTooltip<'a> {
.with_style(self.style.desc)
.color(text_color)
.and(|t| {
match armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.energy_reward().is_some() as usize
+ armor.stats.crit_power().is_some() as usize
match armor.stats(self.msm).poise_resilience.is_some() as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
+ armor.stats(self.msm).energy_reward.is_some() as usize
+ armor.stats(self.msm).crit_power.is_some() as usize
{
0 => t
.x_align_to(
@ -981,10 +996,11 @@ impl<'a> Widget for ItemTooltip<'a> {
}
})
.set(
state.ids.stats[armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.energy_reward().is_some() as usize
+ armor.stats.crit_power().is_some() as usize],
state.ids.stats[armor.stats(self.msm).poise_resilience.is_some()
as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
+ armor.stats(self.msm).energy_reward.is_some() as usize
+ armor.stats(self.msm).crit_power.is_some() as usize],
ui,
);
}
@ -1001,11 +1017,11 @@ impl<'a> Widget for ItemTooltip<'a> {
.with_style(self.style.desc)
.color(text_color)
.and(|t| {
match armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.energy_reward().is_some() as usize
+ armor.stats.crit_power().is_some() as usize
+ armor.stats.stealth().is_some() as usize
match armor.stats(self.msm).poise_resilience.is_some() as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
+ armor.stats(self.msm).energy_reward.is_some() as usize
+ armor.stats(self.msm).crit_power.is_some() as usize
+ armor.stats(self.msm).stealth.is_some() as usize
{
0 => t
.x_align_to(
@ -1017,11 +1033,12 @@ impl<'a> Widget for ItemTooltip<'a> {
}
})
.set(
state.ids.stats[armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.energy_reward().is_some() as usize
+ armor.stats.crit_power().is_some() as usize
+ armor.stats.stealth().is_some() as usize],
state.ids.stats[armor.stats(self.msm).poise_resilience.is_some()
as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
+ armor.stats(self.msm).energy_reward.is_some() as usize
+ armor.stats(self.msm).crit_power.is_some() as usize
+ armor.stats(self.msm).stealth.is_some() as usize],
ui,
);
}
@ -1030,31 +1047,33 @@ impl<'a> Widget for ItemTooltip<'a> {
if let Some(equipped_item) = equipped_item {
if let ItemKind::Armor(equipped_armor) = &*equipped_item.kind() {
let diff = armor.stats - equipped_armor.stats;
let diff = armor.stats(self.msm) - equipped_armor.stats(self.msm);
let protection_diff = util::option_comparison(
&armor.protection(),
&equipped_armor.protection(),
&armor.stats(self.msm).protection,
&equipped_armor.stats(self.msm).protection,
);
let poise_res_diff = util::option_comparison(
&armor.poise_resilience(),
&equipped_armor.poise_resilience(),
&armor.stats(self.msm).poise_resilience,
&equipped_armor.stats(self.msm).poise_resilience,
);
let energy_max_diff = util::option_comparison(
&armor.energy_max(),
&equipped_armor.energy_max(),
&armor.stats(self.msm).energy_max,
&equipped_armor.stats(self.msm).energy_max,
);
let energy_reward_diff = util::option_comparison(
&armor.energy_reward(),
&equipped_armor.energy_reward(),
&armor.stats(self.msm).energy_reward,
&equipped_armor.stats(self.msm).energy_reward,
);
let crit_power_diff = util::option_comparison(
&armor.crit_power(),
&equipped_armor.crit_power(),
&armor.stats(self.msm).crit_power,
&equipped_armor.stats(self.msm).crit_power,
);
let stealth_diff = util::option_comparison(
&armor.stats(self.msm).stealth,
&equipped_armor.stats(self.msm).stealth,
);
let stealth_diff =
util::option_comparison(&armor.stealth(), &equipped_armor.stealth());
if let Some(p_diff) = diff.protection() {
if let Some(p_diff) = diff.protection {
if p_diff != Protection::Normal(0.0) {
widget::Text::new(protection_diff.0)
.right_from(state.ids.main_stat_text, H_PAD)
@ -1077,7 +1096,7 @@ impl<'a> Widget for ItemTooltip<'a> {
.set(state.ids.diffs[id_index], ui)
};
if let Some(p_r_diff) = diff.poise_resilience() {
if let Some(p_r_diff) = diff.poise_resilience {
if p_r_diff != Protection::Normal(0.0) {
let text = format!(
"{} {}",
@ -1088,53 +1107,53 @@ impl<'a> Widget for ItemTooltip<'a> {
}
}
if let Some(e_m_diff) = diff.energy_max() {
if let Some(e_m_diff) = diff.energy_max {
if e_m_diff.abs() > Energy::ENERGY_EPSILON {
let text = format!("{} {:.1}", &energy_max_diff.0, e_m_diff);
diff_text(
text,
energy_max_diff.1,
armor.stats.poise_resilience().is_some() as usize,
armor.stats(self.msm).poise_resilience.is_some() as usize,
)
}
}
if let Some(e_r_diff) = diff.energy_reward() {
if let Some(e_r_diff) = diff.energy_reward {
if e_r_diff.abs() > Energy::ENERGY_EPSILON {
let text =
format!("{} {:.1}", &energy_reward_diff.0, e_r_diff * 100.0);
diff_text(
text,
energy_reward_diff.1,
armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize,
armor.stats(self.msm).poise_resilience.is_some() as usize
+ armor.stats(self.msm).energy_max.is_some() as usize,
)
}
}
if let Some(c_p_diff) = diff.crit_power() {
if let Some(c_p_diff) = diff.crit_power {
if c_p_diff != 0.0_f32 {
let text = format!("{} {:.3}", &crit_power_diff.0, c_p_diff);
diff_text(
text,
crit_power_diff.1,
armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.energy_reward().is_some() as usize,
armor.stats(self.msm).poise_resilience.is_some() as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
+ armor.stats(self.msm).energy_reward.is_some() as usize,
)
}
}
if let Some(s_diff) = diff.stealth() {
if let Some(s_diff) = diff.stealth {
if s_diff != 0.0_f32 {
let text = format!("{} {:.3}", &stealth_diff.0, s_diff);
diff_text(
text,
stealth_diff.1,
armor.stats.poise_resilience().is_some() as usize
+ armor.stats.energy_max().is_some() as usize
+ armor.stats.energy_reward().is_some() as usize
+ armor.stats.crit_power().is_some() as usize,
armor.stats(self.msm).poise_resilience.is_some() as usize
+ armor.stats(self.msm).energy_max.is_some() as usize
+ armor.stats(self.msm).energy_reward.is_some() as usize
+ armor.stats(self.msm).crit_power.is_some() as usize,
)
}
}
@ -1326,7 +1345,7 @@ impl<'a> Widget for ItemTooltip<'a> {
.with_style(self.style.desc)
.color(conrod_core::color::GREY)
.down_from(
if util::stats_count(item) > 0 {
if stats_count > 0 {
state.ids.stats[state.ids.stats.len() - 1]
} else {
state.ids.item_frame
@ -1352,7 +1371,7 @@ impl<'a> Widget for ItemTooltip<'a> {
.down_from(
if !desc.is_empty() {
state.ids.desc
} else if util::stats_count(item) > 0 {
} else if stats_count > 0 {
state.ids.stats[state.ids.stats.len() - 1]
} else {
state.ids.item_frame
@ -1412,13 +1431,14 @@ impl<'a> Widget for ItemTooltip<'a> {
let frame_h = ICON_SIZE[1] + V_PAD;
// Stats
let stat_h = if util::stats_count(self.item) > 0 {
let stats_count = util::stats_count(self.item, self.msm);
let stat_h = if stats_count > 0 {
widget::Text::new("placeholder")
.with_style(self.style.desc)
.get_h(ui)
.unwrap_or(0.0)
* util::stats_count(self.item) as f64
+ (util::stats_count(self.item) - 1) as f64 * V_PAD_STATS
* stats_count as f64
+ (stats_count - 1) as f64 * V_PAD_STATS
+ V_PAD
} else {
0.0