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" dbg-voxygen = "run --bin veloren-voxygen --profile debuginfo"
# misc # misc
swarm = "run --bin swarm --features client/bin_bot,client/tick_network --" 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,125 +1,317 @@
// Keep in mind that material stats are multiplied by the form stats, not added (e.g. equip_time_secs is most sensitive to this) // Keep in mind that material stats are multiplied by the form stats, not added (e.g. equip_time_secs is most sensitive to this)
({ (
// Metals tool_stats: {
"common.items.mineral.ingot.bronze": ( // Metals
equip_time_secs: 1.0, "common.items.mineral.ingot.bronze": (
power: 0.75, equip_time_secs: 1.0,
effect_power: 1.0, power: 0.75,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.mineral.ingot.iron": ( ),
equip_time_secs: 1.0, "common.items.mineral.ingot.iron": (
power: 1.0, equip_time_secs: 1.0,
effect_power: 1.0, power: 1.0,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.mineral.ingot.steel": ( ),
equip_time_secs: 1.0, "common.items.mineral.ingot.steel": (
power: 1.25, equip_time_secs: 1.0,
effect_power: 1.0, power: 1.25,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.mineral.ingot.cobalt": ( ),
equip_time_secs: 1.0, "common.items.mineral.ingot.cobalt": (
power: 1.5, equip_time_secs: 1.0,
effect_power: 1.0, power: 1.5,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.mineral.ingot.bloodsteel": ( ),
equip_time_secs: 1.0, "common.items.mineral.ingot.bloodsteel": (
power: 1.75, equip_time_secs: 1.0,
effect_power: 1.0, power: 1.75,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.mineral.ingot.orichalcum": ( ),
equip_time_secs: 1.0, "common.items.mineral.ingot.orichalcum": (
power: 2.0, equip_time_secs: 1.0,
effect_power: 1.0, power: 2.0,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
// Woods ),
"common.items.log.wood": ( // Woods
equip_time_secs: 1.0, "common.items.log.wood": (
power: 0.75, equip_time_secs: 1.0,
effect_power: 1.0, power: 0.75,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.log.bamboo": ( ),
equip_time_secs: 1.0, "common.items.log.bamboo": (
power: 1.0, equip_time_secs: 1.0,
effect_power: 1.0, power: 1.0,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.log.hardwood": ( ),
equip_time_secs: 1.0, "common.items.log.hardwood": (
power: 1.25, equip_time_secs: 1.0,
effect_power: 1.0, power: 1.25,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.log.ironwood": ( ),
equip_time_secs: 1.0, "common.items.log.ironwood": (
power: 1.5, equip_time_secs: 1.0,
effect_power: 1.0, power: 1.5,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.log.frostwood": ( ),
equip_time_secs: 1.0, "common.items.log.frostwood": (
power: 1.75, equip_time_secs: 1.0,
effect_power: 1.0, power: 1.75,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, energy_efficiency: 1.0,
), buff_strength: 1.0,
"common.items.log.eldwood": ( ),
equip_time_secs: 1.0, "common.items.log.eldwood": (
power: 2.0, equip_time_secs: 1.0,
effect_power: 1.0, power: 2.0,
speed: 1.0, effect_power: 1.0,
crit_chance: 1.0, speed: 1.0,
range: 1.0, crit_chance: 1.0,
energy_efficiency: 1.0, range: 1.0,
buff_strength: 1.0, 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::{ item::{
armor::{ArmorKind, Protection}, armor::{ArmorKind, Protection},
tool::{Hands, Tool, ToolKind}, tool::{Hands, Tool, ToolKind},
Item, Item, MaterialStatManifest,
}, },
}, },
generation::{EntityConfig, EntityInfo}, generation::{EntityConfig, EntityInfo},
@ -56,20 +56,22 @@ fn armor_stats() -> Result<(), Box<dyn Error>> {
continue; 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::Invincible) => "Invincible".to_string(),
Some(Protection::Normal(value)) => value.to_string(), Some(Protection::Normal(value)) => value.to_string(),
None => "0.0".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::Invincible) => "Invincible".to_string(),
Some(Protection::Normal(value)) => value.to_string(), Some(Protection::Normal(value)) => value.to_string(),
None => "0.0".to_string(), None => "0.0".to_string(),
}; };
let max_energy = armor.energy_max().unwrap_or(0.0).to_string(); let max_energy = armor.stats(msm).energy_max.unwrap_or(0.0).to_string();
let energy_reward = armor.energy_reward().unwrap_or(0.0).to_string(); let energy_reward = armor.stats(msm).energy_reward.unwrap_or(0.0).to_string();
let crit_power = armor.crit_power().unwrap_or(0.0).to_string(); let crit_power = armor.stats(msm).crit_power.unwrap_or(0.0).to_string();
let stealth = armor.stealth().unwrap_or(0.0).to_string(); let stealth = armor.stats(msm).stealth.unwrap_or(0.0).to_string();
wtr.write_record(&[ wtr.write_record(&[
item.item_definition_id() item.item_definition_id()

View File

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

View File

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

View File

@ -1,11 +1,15 @@
use crate::{ use crate::{
comp::item::Rgb, comp::item::{MaterialStatManifest, Rgb},
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
}; };
use serde::{Deserialize, Serialize}; 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 { pub enum ArmorKind {
Shoulder, Shoulder,
Chest, 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 { pub struct Stats {
/// Protection is non-linearly transformed (following summation) to a damage /// Protection is non-linearly transformed (following summation) to a damage
/// reduction using (prot / (60 + prot)) /// reduction using (prot / (60 + prot))
protection: Option<Protection>, pub protection: Option<Protection>,
/// Poise protection is non-linearly transformed (following summation) to a /// Poise protection is non-linearly transformed (following summation) to a
/// poise damage reduction using (prot / (60 + prot)) /// 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 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 recovery is summed, and then added to 1.0. When attacks reward
/// energy, it is then multiplied by this value before the energy is /// energy, it is then multiplied by this value before the energy is
/// rewarded. /// rewarded.
energy_reward: Option<f32>, pub energy_reward: Option<f32>,
/// Crit power is summed, and then added to the default crit multiplier of /// 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. /// 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 /// Stealth is summed along with the base stealth bonus (2.0), and then
/// the agent's perception distance is divided by this value /// the agent's perception distance is divided by this value
stealth: Option<f32>, pub stealth: Option<f32>,
/// Ground contact type, mostly for shoes /// Ground contact type, mostly for shoes
#[serde(default)] #[serde(default)]
ground_contact: Friction, pub ground_contact: Friction,
} }
impl Stats { impl Stats {
// DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING fn none() -> Self {
// Added for csv import of stats Stats {
pub fn new( protection: None,
protection: Option<Protection>, poise_resilience: None,
poise_resilience: Option<Protection>, energy_max: None,
energy_max: Option<f32>, energy_reward: None,
energy_reward: Option<f32>, crit_power: None,
crit_power: Option<f32>, stealth: None,
stealth: Option<f32>,
) -> Self {
Self {
protection,
poise_resilience,
energy_max,
energy_reward,
crit_power,
stealth,
ground_contact: Friction::Normal, 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 } fn mul(self, val: f32) -> Self::Output {
Stats {
pub fn energy_reward(&self) -> Option<f32> { self.energy_reward } protection: self.protection.map(|a| a * val),
poise_resilience: self.poise_resilience.map(|a| a * val),
pub fn crit_power(&self) -> Option<f32> { self.crit_power } energy_max: self.energy_max.map(|a| a * val),
energy_reward: self.energy_reward.map(|a| a * val),
pub fn stealth(&self) -> Option<f32> { self.stealth } crit_power: self.crit_power.map(|a| a * val),
stealth: self.stealth.map(|a| a * val),
pub fn ground_contact(&self) -> Friction { self.ground_contact } // There is nothing to multiply, it is just an enum
ground_contact: self.ground_contact,
}
}
} }
impl Sub<Stats> for Stats { 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 { impl PartialOrd for Protection {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (*self, *other) { match (*self, *other) {
@ -191,25 +206,39 @@ impl PartialOrd for Protection {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Armor { pub struct Armor {
pub kind: ArmorKind, pub kind: ArmorKind,
pub stats: Stats, stats: StatsSource,
} }
impl Armor { 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 } set_stats * multiplier
},
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 }
#[cfg(test)] #[cfg(test)]
pub fn test_armor( pub fn test_armor(
@ -219,7 +248,7 @@ impl Armor {
) -> Armor { ) -> Armor {
Armor { Armor {
kind, kind,
stats: Stats { stats: StatsSource::Direct(Stats {
protection: Some(protection), protection: Some(protection),
poise_resilience: Some(poise_resilience), poise_resilience: Some(poise_resilience),
energy_max: None, energy_max: None,
@ -227,7 +256,7 @@ impl Armor {
crit_power: None, crit_power: None,
stealth: None, stealth: None,
ground_contact: Friction::Normal, ground_contact: Friction::Normal,
}, }),
} }
} }
} }

View File

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

View File

@ -1,8 +1,12 @@
use super::{ use super::{
tool::{self, AbilityMap, AbilitySpec, Hands, MaterialStatManifest}, armor,
tool::{self, AbilityMap, AbilitySpec, Hands},
Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, Material, Quality, ToolKind, 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 common_base::dev_panic;
use hashbrown::HashMap; use hashbrown::HashMap;
use lazy_static::lazy_static; 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)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ModularBase { pub enum ModularBase {
Tool, Tool,
@ -241,7 +267,7 @@ impl ModularComponent {
.filter_map(|comp| { .filter_map(|comp| {
comp.item_definition_id() comp.item_definition_id()
.itemdef_id() .itemdef_id()
.and_then(|id| msm.0.get(id)) .and_then(|id| msm.tool_stats.get(id))
.copied() .copied()
.zip(Some(1)) .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); } 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)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Tool { pub struct Tool {
pub kind: ToolKind, pub kind: ToolKind,

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ use crate::{
combat::{DamageContributor, DamageSource}, combat::{DamageContributor, DamageSource},
comp::{ comp::{
self, self,
inventory::item::{armor::Protection, ItemKind}, inventory::item::{armor::Protection, ItemKind, MaterialStatManifest},
CharacterState, Inventory, CharacterState, Inventory,
}, },
resources::Time, resources::Time,
@ -226,12 +226,15 @@ impl Poise {
} }
/// Returns the total poise damage reduction provided by all equipped items /// 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 let protection = inventory
.equipped_items() .equipped_items()
.filter_map(|item| { .filter_map(|item| {
if let ItemKind::Armor(armor) = &*item.kind() { if let ItemKind::Armor(armor) = &*item.kind() {
armor.poise_resilience() armor.stats(msm).poise_resilience
} else { } else {
None None
} }
@ -249,9 +252,13 @@ impl Poise {
/// Modifies a poise change when optionally given an inventory to aid in /// Modifies a poise change when optionally given an inventory to aid in
/// calculation of poise damage reduction /// 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| { 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| { footwear = data.inventory.and_then(|inv| {
inv.equipped(EquipSlot::Armor(ArmorSlot::Feet)) inv.equipped(EquipSlot::Armor(ArmorSlot::Feet))
.map(|armor| match armor.kind().as_ref() { .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, _ => 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); .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) (crit_chance, crit_mult)
} }

View File

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

View File

@ -2,6 +2,7 @@ use common::{
combat, combat,
comp::{ comp::{
self, self,
item::MaterialStatManifest,
skills::{GeneralSkill, Skill}, skills::{GeneralSkill, Skill},
Body, CharacterState, Combo, Energy, Health, Inventory, Poise, PoiseChange, Pos, SkillSet, Body, CharacterState, Combo, Energy, Health, Inventory, Poise, PoiseChange, Pos, SkillSet,
Stats, StatsModifier, Stats, StatsModifier,
@ -11,7 +12,8 @@ use common::{
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use specs::{ 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; use vek::Vec3;
@ -28,6 +30,7 @@ pub struct ReadData<'a> {
bodies: ReadStorage<'a, Body>, bodies: ReadStorage<'a, Body>,
char_states: ReadStorage<'a, CharacterState>, char_states: ReadStorage<'a, CharacterState>,
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
msm: ReadExpect<'a, MaterialStatManifest>,
} }
/// This system kills players, levels them up, and regenerates energy. /// 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 // Calculates energy scaling from stats and inventory
let energy_mods = StatsModifier { let energy_mods = StatsModifier {
add_mod: stat.max_energy_modifiers.add_mod 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, 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 inventories = ecs.read_storage::<Inventory>();
let stats = ecs.read_storage::<Stats>(); 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 // Handle health change
if let Some(mut health) = ecs.write_storage::<comp::Health>().get_mut(entity) { 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), Some(damage),
inventories.get(entity), inventories.get(entity),
stats.get(entity), stats.get(entity),
&msm,
); );
let change = let change =
damage.calculate_health_change(damage_reduction, None, false, 0.0, 1.0, *time); 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 // Handle poise change
if let Some(mut poise) = ecs.write_storage::<comp::Poise>().get_mut(entity) { 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_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 { let poise_change = comp::PoiseChange {
amount: poise_change, amount: poise_change,
impulse: Vec3::unit_z(), impulse: Vec3::unit_z(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ use common::{
item::{ item::{
armor::{Armor, ArmorKind, Protection}, armor::{Armor, ArmorKind, Protection},
tool::{Hands, Tool, ToolKind}, tool::{Hands, Tool, ToolKind},
ItemDesc, ItemKind, MaterialKind, ItemDesc, ItemKind, MaterialKind, MaterialStatManifest,
}, },
BuffKind, 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() { let mut count = match &*item.kind() {
ItemKind::Armor(armor) => { ItemKind::Armor(armor) => {
if matches!(armor.kind, ArmorKind::Bag) { if matches!(armor.kind, ArmorKind::Bag) {
0 0
} else { } else {
armor.stats.energy_reward().is_some() as usize armor.stats(msm).energy_reward.is_some() as usize
+ armor.stats.energy_max().is_some() as usize + armor.stats(msm).energy_max.is_some() as usize
+ armor.stats.stealth().is_some() as usize + armor.stats(msm).stealth.is_some() as usize
+ armor.stats.crit_power().is_some() as usize + armor.stats(msm).crit_power.is_some() as usize
+ armor.stats.poise_resilience().is_some() as usize + armor.stats(msm).poise_resilience.is_some() as usize
} }
}, },
ItemKind::Tool(_) => 7, ItemKind::Tool(_) => 7,

View File

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