diff --git a/common/src/combat.rs b/common/src/combat.rs index 5545668770..4e933ca4f0 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -761,46 +761,29 @@ impl Damage { inventory: Option<&Inventory>, stats: Option<&Stats>, ) -> f32 { - let inventory_dr = if let Some(inventory) = inventory { - let protection = inventory - .equipped_items() - .filter_map(|item| { - if let ItemKind::Armor(armor) = &item.kind() { - Some(armor.protection()) - } else { - None - } - }) - .map(|protection| match protection { - Some(Protection::Normal(protection)) => Some(protection), - Some(Protection::Invincible) => None, - None => Some(0.0), - }) - .sum::>(); + let protection = compute_protection(inventory); - let penetration = if let Some(damage) = damage { - if let DamageKind::Piercing = damage.kind { - (damage.value * PIERCING_PENETRATION_FRACTION) - .min(protection.unwrap_or(0.0)) - .max(0.0) - } else { - 0.0 - } + let penetration = if let Some(damage) = damage { + if let DamageKind::Piercing = damage.kind { + (damage.value * PIERCING_PENETRATION_FRACTION) + .min(protection.unwrap_or(0.0)) + .max(0.0) } else { 0.0 - }; - - let protection = protection.map(|p| p - penetration); - - const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0; - - match protection { - Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()), - None => 1.0, } } else { 0.0 }; + + let protection = protection.map(|p| p - penetration); + + const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0; + + let inventory_dr = match protection { + Some(dr) => dr / (FIFTY_PERCENT_DR_THRESHOLD + dr.abs()), + None => 1.0, + }; + let stats_dr = if let Some(stats) = stats { stats.damage_reduction } else { @@ -1172,3 +1155,25 @@ pub fn compute_stealth_coefficient(inventory: Option<&Inventory>) -> f32 { .fold(2.0, |a, b| a + b.max(0.0)) }) } + +/// Computes the total protection provided from armor. Is used to determine the +/// 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 { + inventory.map_or(Some(0.0), |inv| { + inv.equipped_items() + .filter_map(|item| { + if let ItemKind::Armor(armor) = &item.kind() { + armor.protection() + } else { + None + } + }) + .map(|protection| match protection { + Protection::Normal(protection) => Some(protection), + Protection::Invincible => None, + }) + .sum::>() + }) +} diff --git a/common/src/comp/poise.rs b/common/src/comp/poise.rs index 916844e44e..3cd52e052a 100644 --- a/common/src/comp/poise.rs +++ b/common/src/comp/poise.rs @@ -231,15 +231,14 @@ impl Poise { .equipped_items() .filter_map(|item| { if let ItemKind::Armor(armor) = &item.kind() { - Some(armor.poise_resilience()) + armor.poise_resilience() } else { None } }) .map(|protection| match protection { - Some(Protection::Normal(protection)) => Some(protection), - Some(Protection::Invincible) => None, - None => Some(0.0), + Protection::Normal(protection) => Some(protection), + Protection::Invincible => None, }) .sum::>(); match protection { diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index a5138fc8f4..85a42308ad 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -18,16 +18,23 @@ use i18n::Localization; use client::{self, Client}; use common::{ + combat, comp::{ self, ability::{Ability, ActiveAbilities, AuxiliaryAbility, MAX_ABILITIES}, - inventory::{item::tool::ToolKind, slot::EquipSlot}, + inventory::{ + item::{ + tool::{MaterialStatManifest, ToolKind}, + ItemKind, + }, + slot::EquipSlot, + }, skills::{ self, AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, RollSkill, SceptreSkill, Skill, StaffSkill, SwimSkill, SwordSkill, SKILL_MODIFIERS, }, skillset::{SkillGroupKind, SkillSet}, - Inventory, + Body, Energy, Health, Inventory, Poise, }, consts::{ENERGY_PER_LEVEL, HP_PER_LEVEL}, }; @@ -224,6 +231,11 @@ pub struct Diary<'a> { skill_set: &'a SkillSet, active_abilities: &'a ActiveAbilities, inventory: &'a Inventory, + health: &'a Health, + energy: &'a Energy, + poise: &'a Poise, + body: &'a Body, + msm: &'a MaterialStatManifest, imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a Fonts, @@ -264,6 +276,11 @@ impl<'a> Diary<'a> { skill_set: &'a SkillSet, active_abilities: &'a ActiveAbilities, inventory: &'a Inventory, + health: &'a Health, + energy: &'a Energy, + poise: &'a Poise, + body: &'a Body, + msm: &'a MaterialStatManifest, imgs: &'a Imgs, item_imgs: &'a ItemImgs, fonts: &'a Fonts, @@ -278,6 +295,11 @@ impl<'a> Diary<'a> { skill_set, active_abilities, inventory, + health, + energy, + poise, + body, + msm, imgs, item_imgs, fonts, @@ -308,21 +330,6 @@ const TREES: [&str; 8] = [ "Mining", ]; -const STATS: [&str; 12] = [ - "Hitpoints", - "Energy", - "Combat-Rating", - "Protection", - "Stun-Resistance", - "Crit-Power", - "Energy Reward", - "Stealth", - "Weapon Power", - "Weapon Speed", - "Weapon Poise", - "Weapon Crit-Chance", -]; - // Possible future sections: Bestiary ("Pokedex" of fought enemies), Weapon and // armour catalogue, Achievements... const SECTIONS: [&str; 3] = ["Abilities", "Skill-Trees", "Stats"]; @@ -1121,18 +1128,21 @@ impl<'a> Widget for Diary<'a> { events }, DiarySection::Stats => { - let hp = 100; - let energy = 50; - let cr = 1; - let prot = 1; - let stun_res = 1; - let critpwr = 1; - let energ_rew = 1; - let stealth = 1; - let wpn_pwr = 1; - let wpn_spd = 1; - let wpn_poi = 1; - let wpn_crit = 1; + const STATS: [&str; 13] = [ + "Hitpoints", + "Energy", + "Poise", + "Combat-Rating", + "Protection", + "Stun-Resistance", + "Crit-Power", + "Energy Reward", + "Stealth", + "Weapon Power", + "Weapon Speed", + "Weapon Poise", + "Weapon Crit-Chance", + ]; // Background Art Image::new(self.imgs.book_bg) @@ -1168,21 +1178,112 @@ impl<'a> Widget for Diary<'a> { }; txt.set(state.ids.stat_names[i], ui); + let main_weap_stats = self + .inventory + .equipped(EquipSlot::ActiveMainhand) + .and_then(|item| match &item.kind { + ItemKind::Tool(tool) => { + Some(tool.stats.resolve_stats(self.msm, item.components())) + }, + _ => None, + }); + + let off_weap_stats = self + .inventory + .equipped(EquipSlot::ActiveOffhand) + .and_then(|item| match &item.kind { + ItemKind::Tool(tool) => { + Some(tool.stats.resolve_stats(self.msm, item.components())) + }, + _ => None, + }); + // Stat values let value = match stat { - "Hitpoints" => format!("{}", hp), - "Energy" => format!("{}", energy), - "Combat-Rating" => format!("{}", cr), - "Protection" => format!("{}", prot), - "Stun-Resistance" => format!("{}", stun_res), - "Crit-Power" => format!("{}", critpwr), - "Energy Reward" => format!("{}", energ_rew), - "Stealth" => format!("{}", stealth), - "Weapon Power" => format!("{}", wpn_pwr), - "Weapon Speed" => format!("{}", wpn_spd), - "Weapon Poise" => format!("{}", wpn_poi), - "Weapon Crit-Chance" => format!("{}%", wpn_crit), - _ => "".to_string(), + "Hitpoints" => format!("{}", self.health.base_max() as u32), + "Energy" => format!("{}", self.energy.base_max() as u32), + "Poise" => format!("{}", self.poise.base_max() as u32), + "Combat-Rating" => { + let cr = combat::combat_rating( + self.inventory, + self.health, + self.energy, + self.poise, + self.skill_set, + *self.body, + self.msm, + ); + format!("{:.2}", cr) + }, + "Protection" => { + let protection = combat::compute_protection(Some(self.inventory)); + match protection { + Some(prot) => format!("{}", prot), + None => String::from("Invincible"), + } + }, + "Stun-Resistance" => { + let stun_res = Poise::compute_poise_damage_reduction(self.inventory); + format!("{}%", stun_res * 100.0) + }, + "Crit-Power" => { + let critpwr = combat::compute_crit_mult(Some(self.inventory)); + format!("x{}", critpwr) + }, + "Energy Reward" => { + let energy_rew = + combat::compute_energy_reward_mod(Some(self.inventory)); + format!("{:+.0}%", (energy_rew - 1.0) * 100.0) + }, + "Stealth" => { + let stealth = combat::compute_stealth_coefficient(Some(self.inventory)); + format!("{}", stealth) + }, + "Weapon Power" => match (main_weap_stats, off_weap_stats) { + (Some(m_stats), Some(o_stats)) => { + format!("{} {}", m_stats.power, o_stats.power) + }, + (Some(stats), None) | (None, Some(stats)) => format!("{}", stats.power), + _ => String::new(), + }, + "Weapon Speed" => { + let spd_fmt = |sp| (sp - 1.0) * 100.0; + match (main_weap_stats, off_weap_stats) { + (Some(m_stats), Some(o_stats)) => format!( + "{:+.0}% {:+.0}%", + spd_fmt(m_stats.speed), + spd_fmt(o_stats.speed) + ), + (Some(stats), None) | (None, Some(stats)) => { + format!("{:+.0}%", spd_fmt(stats.speed)) + }, + _ => String::new(), + } + }, + "Weapon Poise" => match (main_weap_stats, off_weap_stats) { + (Some(m_stats), Some(o_stats)) => { + format!("{} {}", m_stats.effect_power, o_stats.effect_power) + }, + (Some(stats), None) | (None, Some(stats)) => { + format!("{}", stats.effect_power) + }, + _ => String::new(), + }, + "Weapon Crit-Chance" => { + let crit_fmt = |cc| cc * 100.0; + match (main_weap_stats, off_weap_stats) { + (Some(m_stats), Some(o_stats)) => format!( + "{:.1}% {:.1}%", + crit_fmt(m_stats.crit_chance), + crit_fmt(o_stats.crit_chance) + ), + (Some(stats), None) | (None, Some(stats)) => { + format!("{:.1}%", crit_fmt(stats.crit_chance)) + }, + _ => String::new(), + } + }, + _ => String::new(), }; let mut number = Text::new(&value) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 93023f4853..e1c2944952 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -3097,10 +3097,22 @@ impl Hud { if self.show.diary { let entity = client.entity(); let skill_sets = ecs.read_storage::(); - if let (Some(skill_set), Some(active_abilities), Some(inventory)) = ( + if let ( + Some(skill_set), + Some(active_abilities), + Some(inventory), + Some(health), + Some(energy), + Some(body), + Some(poise), + ) = ( skill_sets.get(entity), active_abilities.get(entity), inventories.get(entity), + healths.get(entity), + energies.get(entity), + bodies.get(entity), + poises.get(entity), ) { for event in Diary::new( &self.show, @@ -3108,6 +3120,11 @@ impl Hud { skill_set, active_abilities, inventory, + health, + energy, + poise, + body, + &msm, &self.imgs, &self.item_imgs, &self.fonts,