diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 84acb0980f..4f52efda50 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -1211,10 +1211,10 @@ impl CharacterAbility { *base_damage *= modifiers.base_damage.powi(level.into()); } if let Ok(Some(level)) = skillset.skill_level(Hammer(LKnockback)) { - *knockback *= modifiers.base_damage.powi(level.into()); + *knockback *= modifiers.knockback.powi(level.into()); } if let Ok(Some(level)) = skillset.skill_level(Hammer(LCost)) { - *energy_cost *= modifiers.base_damage.powi(level.into()); + *energy_cost *= modifiers.energy_cost.powi(level.into()); } if let Ok(Some(level)) = skillset.skill_level(Hammer(LDistance)) { let strength = modifiers.leap_strength; @@ -1275,7 +1275,7 @@ impl CharacterAbility { *charge_duration *= charge_time.powi(level.into()); } if let Ok(Some(level)) = skillset.skill_level(Bow(CMove)) { - *move_speed *= modifiers.charge_rate.powi(level.into()); + *move_speed *= modifiers.move_speed.powi(level.into()); } }, CharacterAbility::RepeaterRanged { @@ -1344,8 +1344,8 @@ impl CharacterAbility { let regen_level = skillset.skill_level_or(Staff(BRegen), 0); let range_level = skillset.skill_level_or(Staff(BRadius), 0); let power = modifiers.power.powi(damage_level.into()); - let regen = modifiers.power.powi(regen_level.into()); - let range = modifiers.power.powi(range_level.into()); + let regen = modifiers.regen.powi(regen_level.into()); + let range = modifiers.range.powi(range_level.into()); *projectile = projectile.modified_projectile(power, regen, range); }, CharacterAbility::BasicBeam { diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 33840b2a2c..ab747f78a1 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -16,7 +16,7 @@ pub mod theropod; use crate::{ assets::{self, Asset}, - consts::{HUMAN_DENSITY, WATER_DENSITY}, + consts::{HUMANOID_HP_PER_LEVEL, HUMAN_DENSITY, WATER_DENSITY}, make_case_elim, npc::NpcKind, }; @@ -563,7 +563,7 @@ impl Body { #[allow(unreachable_patterns)] pub fn base_health_increase(&self) -> u32 { match self { - Body::Humanoid(_) => 50, + Body::Humanoid(_) => HUMANOID_HP_PER_LEVEL, Body::QuadrupedSmall(quadruped_small) => match quadruped_small.species { quadruped_small::Species::Boar => 20, quadruped_small::Species::Batfox => 10, diff --git a/common/src/comp/energy.rs b/common/src/comp/energy.rs index db3ae03eb0..0451625a9d 100644 --- a/common/src/comp/energy.rs +++ b/common/src/comp/energy.rs @@ -1,9 +1,8 @@ -use crate::comp::Body; +use crate::{comp::Body, consts::ENERGY_PER_LEVEL}; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; use specs_idvs::IdvStorage; -pub const ENERGY_PER_LEVEL: u32 = 50; #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub struct Energy { current: u32, diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 062a80aa4e..62b9d48509 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -14,19 +14,25 @@ use std::{ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum ToolKind { + // weapons Sword, Axe, Hammer, Bow, - Dagger, Staff, Sceptre, + // future weapons + Dagger, Shield, Spear, - Natural, // Intended for invisible weapons (e.g. a creature using its claws or biting) + // tools Debug, Farming, Pick, + // npcs + /// Intended for invisible weapons (e.g. a creature using its claws or + /// biting) + Natural, /// This is an placeholder item, it is used by non-humanoid npcs to attack Empty, } diff --git a/common/src/comp/skills.rs b/common/src/comp/skills.rs index ec3994698f..b140eb0d69 100644 --- a/common/src/comp/skills.rs +++ b/common/src/comp/skills.rs @@ -1,10 +1,6 @@ use crate::{ assets::{self, Asset, AssetExt}, - comp::{ - self, - body::{humanoid, Body}, - item::tool::ToolKind, - }, + comp::item::tool::ToolKind, }; use hashbrown::{HashMap, HashSet}; use lazy_static::lazy_static; @@ -442,8 +438,8 @@ impl SceptreTreeModifiers { pub struct MiningTreeModifiers { pub speed: f32, - pub gem_gain: f64, - pub ore_gain: f64, + pub gem_gain: f32, + pub ore_gain: f32, } impl MiningTreeModifiers { @@ -493,58 +489,6 @@ impl GeneralTreeModifiers { } } } - -/// Enum which returned as result from `boost` function -/// `Number` can represent values from -inf to +inf, -/// but it should generaly be in range -50..50 -/// -/// Number(-25) says that some value -/// will be reduced by 25% (for example energy consumption) -/// -/// Number(15) says that some value -/// will be increased by 15% (for example damage) -// TODO: move it to voxygen diary code -// (and inline directly to formating skill descriptions) -#[derive(Debug, Clone, Copy)] -pub enum BoostValue { - Number(i16), - NonDescriptive, -} - -impl From for BoostValue { - fn from(number: i16) -> Self { BoostValue::Number(number) } -} - -/// Returns value which corresponds to the boost given by this skill -pub trait Boost { - fn boost(self) -> BoostValue; -} - -impl Boost for Skill { - fn boost(self) -> BoostValue { - match self { - // General tree boosts - Skill::General(s) => s.boost(), - // Weapon tree boosts - Skill::Sword(s) => s.boost(), - Skill::Axe(s) => s.boost(), - Skill::Hammer(s) => s.boost(), - Skill::Bow(s) => s.boost(), - Skill::Staff(s) => s.boost(), - Skill::Sceptre(s) => s.boost(), - - // Movement tree boosts - Skill::Roll(s) => s.boost(), - Skill::Climb(s) => s.boost(), - Skill::Swim(s) => s.boost(), - // Non-combat tree boosts - Skill::Pick(s) => s.boost(), - // Unlock Group has more complex semantic - Skill::UnlockGroup(_) => BoostValue::NonDescriptive, - } - } -} - pub enum SkillError { MissingSkill, } @@ -573,32 +517,6 @@ pub enum SwordSkill { SSpins, } -impl Boost for SwordSkill { - fn boost(self) -> BoostValue { - match self { - // Dash - Self::DDamage => 20.into(), - Self::DCost => (-25_i16).into(), - Self::DDrain => (-25_i16).into(), - Self::DScaling => 20.into(), - Self::DSpeed => 15.into(), - // Spin - Self::SDamage => 40.into(), - Self::SSpeed => (-20_i16).into(), - Self::SCost => (-25_i16).into(), - // Non-descriptive values - Self::InterruptingAttacks - | Self::TsCombo - | Self::TsDamage - | Self::TsRegen - | Self::TsSpeed - | Self::DInfinite - | Self::UnlockSpin - | Self::SSpins => BoostValue::NonDescriptive, - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum AxeSkill { // Double strike upgrades @@ -620,30 +538,6 @@ pub enum AxeSkill { LDistance, } -impl Boost for AxeSkill { - fn boost(self) -> BoostValue { - match self { - // Spin upgrades - Self::SDamage => 30.into(), - Self::SSpeed => (-20_i16).into(), - Self::SCost => (-25_i16).into(), - // Leap upgrades - Self::LDamage => 35.into(), - Self::LKnockback => 40.into(), - Self::LCost => (-25_i16).into(), - Self::LDistance => 20.into(), - // Non-descriptive boosts - Self::UnlockLeap - | Self::DsCombo - | Self::DsDamage - | Self::DsSpeed - | Self::DsRegen - | Self::SInfinite - | Self::SHelicopter => BoostValue::NonDescriptive, - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum HammerSkill { // Single strike upgrades @@ -665,30 +559,6 @@ pub enum HammerSkill { LRange, } -impl Boost for HammerSkill { - fn boost(self) -> BoostValue { - match self { - // Single strike upgrades - Self::SsKnockback => 50.into(), - // Charged melee upgrades - Self::CDamage => 25.into(), - Self::CKnockback => 50.into(), - Self::CDrain => (-25_i16).into(), - Self::CSpeed => 25.into(), - // Leap upgrades - Self::LDamage => 40.into(), - Self::LKnockback => 50.into(), - Self::LCost => (-25_i16).into(), - Self::LDistance => 25.into(), - Self::LRange => 1.into(), - // Non-descriptive values - Self::UnlockLeap | Self::SsDamage | Self::SsSpeed | Self::SsRegen => { - BoostValue::NonDescriptive - }, - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum BowSkill { // Passives @@ -711,32 +581,6 @@ pub enum BowSkill { SSpread, } -impl Boost for BowSkill { - fn boost(self) -> BoostValue { - match self { - // Passive - Self::ProjSpeed => 20.into(), - // Charged upgrades - Self::CDamage => 20.into(), - Self::CRegen => 20.into(), - Self::CKnockback => 20.into(), - Self::CSpeed => 10.into(), - Self::CMove => 10.into(), - // Repeater upgrades - Self::RDamage => 20.into(), - Self::RCost => (-20_i16).into(), - Self::RSpeed => 20.into(), - // Shotgun upgrades - Self::SDamage => 20.into(), - Self::SCost => (-20_i16).into(), - Self::SArrows => 1.into(), - Self::SSpread => (-20_i16).into(), - // Non-descriptive values - Self::UnlockShotgun => BoostValue::NonDescriptive, - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum StaffSkill { // Basic ranged upgrades @@ -756,29 +600,6 @@ pub enum StaffSkill { SCost, } -impl Boost for StaffSkill { - fn boost(self) -> BoostValue { - match self { - // Fireball upgrades - Self::BDamage => 20.into(), - Self::BRegen => 20.into(), - Self::BRadius => 15.into(), - // Flamethrower upgrades - Self::FDamage => 30.into(), - Self::FRange => 25.into(), - Self::FDrain => (-20_i16).into(), - Self::FVelocity => 25.into(), - // Shockwave upgrades - Self::SDamage => 30.into(), - Self::SKnockback => 30.into(), - Self::SRange => 20.into(), - Self::SCost => (-20_i16).into(), - // Non-descriptive values - Self::UnlockShockwave => BoostValue::NonDescriptive, - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum SceptreSkill { // Lifesteal beam upgrades @@ -786,7 +607,7 @@ pub enum SceptreSkill { LRange, LLifesteal, LRegen, - // Healing beam upgrades + // Healing aura upgrades HHeal, HRange, HDuration, @@ -799,53 +620,12 @@ pub enum SceptreSkill { ACost, } -impl Boost for SceptreSkill { - fn boost(self) -> BoostValue { - match self { - // Lifesteal beam upgrades - Self::LDamage => 20.into(), - Self::LRange => 20.into(), - Self::LRegen => 20.into(), - Self::LLifesteal => 15.into(), - // Healing beam upgrades - Self::HHeal => 20.into(), - Self::HRange => 20.into(), - Self::HDuration => 20.into(), - Self::HCost => (-20_i16).into(), - // Warding aura upgrades - Self::AStrength => 15.into(), - Self::ADuration => 20.into(), - Self::ARange => 25.into(), - Self::ACost => (-15_i16).into(), - Self::UnlockAura => BoostValue::NonDescriptive, - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum GeneralSkill { HealthIncrease, EnergyIncrease, } -impl Boost for GeneralSkill { - fn boost(self) -> BoostValue { - // NOTE: These should be used only for UI. - // Source of truth are corresponding systems - match self { - Self::HealthIncrease => { - let health_increase = - (Body::Humanoid(humanoid::Body::random()).base_health_increase() / 10) as i16; - health_increase.into() - }, - Self::EnergyIncrease => { - let energy_increase = (comp::energy::ENERGY_PER_LEVEL / 10) as i16; - energy_increase.into() - }, - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum RollSkill { Cost, @@ -853,44 +633,17 @@ pub enum RollSkill { Duration, } -impl Boost for RollSkill { - fn boost(self) -> BoostValue { - match self { - Self::Cost => (-10_i16).into(), - Self::Strength => 10.into(), - Self::Duration => 10.into(), - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum ClimbSkill { Cost, Speed, } -impl Boost for ClimbSkill { - fn boost(self) -> BoostValue { - match self { - Self::Cost => (-20_i16).into(), - Self::Speed => 20.into(), - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum SwimSkill { Speed, } -impl Boost for SwimSkill { - fn boost(self) -> BoostValue { - match self { - Self::Speed => 25.into(), - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum MiningSkill { Speed, @@ -898,16 +651,6 @@ pub enum MiningSkill { GemGain, } -impl Boost for MiningSkill { - fn boost(self) -> BoostValue { - match self { - Self::Speed => 10.into(), - Self::OreGain => 5.into(), - Self::GemGain => 5.into(), - } - } -} - #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum SkillGroupKind { General, diff --git a/common/src/consts.rs b/common/src/consts.rs index 4cbaa9598e..1e1757e15a 100644 --- a/common/src/consts.rs +++ b/common/src/consts.rs @@ -26,3 +26,7 @@ pub const MIN_RECOMMENDED_RAYON_THREADS: usize = 2; pub const MIN_RECOMMENDED_TOKIO_THREADS: usize = 2; pub const SOUND_TRAVEL_DIST_PER_VOLUME: f32 = 3.0; + +// Stat increase per level (multiplied by 10 compared to what you'll see in UI) +pub const ENERGY_PER_LEVEL: u32 = 50; +pub const HUMANOID_HP_PER_LEVEL: u32 = 50; diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index cf748dd14b..5c41ad8b8a 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -354,14 +354,14 @@ pub fn handle_mine_block( let mut rng = rand::thread_rng(); let need_double_ore = |rng: &mut rand::rngs::ThreadRng| { - let chance_mod = SKILL_MODIFIERS.mining_tree.ore_gain; + let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.ore_gain); let skill_level = skillset.skill_level_or(Skill::Pick(MiningSkill::OreGain), 0); rng.gen_bool(chance_mod * f64::from(skill_level)) }; let need_double_gem = |rng: &mut rand::rngs::ThreadRng| { - let chance_mod = SKILL_MODIFIERS.mining_tree.gem_gain; + let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.gem_gain); let skill_level = skillset.skill_level_or(Skill::Pick(MiningSkill::GemGain), 0); diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index 5ac4023446..c3eaf722e0 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -4,7 +4,10 @@ use super::{ Position, PositionSpecifier, Show, CRITICAL_HP_COLOR, HP_COLOR, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; -use crate::ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}; +use crate::{ + hud, + ui::{fonts::Fonts, ImageFrame, Tooltip, TooltipManager, Tooltipable}, +}; use conrod_core::{ color, image, widget::{self, Button, Image, Rectangle, State, Text}, @@ -13,11 +16,19 @@ use conrod_core::{ use i18n::Localization; use client::{self, Client}; -use common::comp::{ - item::tool::ToolKind, - skills::{self, Boost, BoostValue, Skill}, - SkillSet, +use common::{ + comp::{ + item::tool::ToolKind, + skills::{ + self, AxeSkill, BowSkill, ClimbSkill, GeneralSkill, HammerSkill, MiningSkill, + RollSkill, SceptreSkill, Skill, SkillGroupKind, StaffSkill, SwimSkill, SwordSkill, + SKILL_MODIFIERS, + }, + SkillSet, + }, + consts::{ENERGY_PER_LEVEL, HUMANOID_HP_PER_LEVEL}, }; +use std::borrow::Cow; const ART_SIZE: [f64; 2] = [320.0, 320.0]; @@ -576,42 +587,6 @@ fn skill_tree_from_str(string: &str) -> Option { } } -/// Formats skill description, e.g. -/// -/// "Increase max health by {boost}{SP}" -/// -/// turns into -/// -/// """ -/// Increase max health by 5 -/// -/// Requires 4 SP -/// """ -fn format_skill_description<'a>( - raw_description: &'a str, - skill: Skill, - skill_set: &'a skills::SkillSet, - localized_strings: &'a Localization, -) -> String { - let boost = skill.boost(); - let with_values = match boost { - BoostValue::Number(x) => { - let value = x.abs(); - raw_description.replace("{boost}", &value.to_string()) - }, - BoostValue::NonDescriptive => raw_description.to_owned(), - }; - match skill_set.skill_level(skill) { - Ok(level) if level == skill.max_level() => with_values.replace("{SP}", ""), - _ => with_values.replace( - "{SP}", - &localized_strings - .get("hud.skill.req_sp") - .replace("{number}", &format!("{}", skill_set.skill_cost(skill))), - ), - } -} - impl<'a> Diary<'a> { fn handle_general_skills_window( &mut self, @@ -643,13 +618,7 @@ impl<'a> Diary<'a> { skills_bot_l, skills_bot_r, ); - use skills::{ - ClimbSkill, - GeneralSkill::*, - RollSkill::{self, *}, - SkillGroupKind::*, - SwimSkill, - }; + use skills::{GeneralSkill::*, RollSkill::*, SkillGroupKind::*}; use ToolKind::*; // General Combat Image::new(animate_by_pulse( @@ -2391,15 +2360,14 @@ impl<'a> Diary<'a> { diary_tooltip: &Tooltip, ) { let label = if self.skill_set.prerequisites_met(skill) { - format!( - "{}/{}", - self.skill_set - .skill_level(skill) - .map_or(0, |l| l.unwrap_or(1)), - skill.max_level().unwrap_or(1) - ) + let current = self + .skill_set + .skill_level(skill) + .map_or(0, |l| l.unwrap_or(1)); + let max = skill.max_level().unwrap_or(1); + format!("{}/{}", current, max) } else { - "".to_string() + "".to_owned() }; let label_color = if self.skill_set.is_at_max_level(skill) { @@ -2416,16 +2384,13 @@ impl<'a> Diary<'a> { Color::Rgba(0.41, 0.41, 0.41, 0.7) }; - let skill_title_key = &format!("hud.skill.{}_title", skill_key); - let skill_title = self.localized_strings.get(skill_title_key); + let (skill_title, skill_description) = self.skill_info(skill); - let skill_description_key = &format!("hud.skill.{}", skill_key); - let skill_description = &format_skill_description( - self.localized_strings.get(skill_description_key), - skill, - self.skill_set, - self.localized_strings, - ); + // Borrowcheck forced me to do this. + // I need to borrow self.tooltip_manager mutably later, while + // keeping references to self.localized_strings otherwise. + let skill_title: String = skill_title.to_owned(); + let skill_description: String = skill_description.into_owned(); let button = Button::image(skill_image) .w_h(74.0, 74.0) @@ -2439,8 +2404,8 @@ impl<'a> Diary<'a> { .image_color(image_color) .with_tooltip( self.tooltip_manager, - skill_title, - skill_description, + &skill_title, + &skill_description, diary_tooltip, TEXT_COLOR, ) @@ -2450,4 +2415,759 @@ impl<'a> Diary<'a> { events.push(Event::UnlockSkill(skill)); }; } + + fn skill_info(&self, skill: Skill) -> (&str, Cow) { + let (title, description) = skill_strings(skill, self.localized_strings); + let description = if description.contains("{SP}") { + Cow::Owned(self.splice_skill_reqs(skill, &description)) + } else { + description + }; + + (title, description) + } + + fn splice_skill_reqs(&self, skill: Skill, desc: &str) -> String { + let current_level = self.skill_set.skill_level(skill); + if matches!(current_level, Ok(level) if level == skill.max_level()) { + desc.replace("{SP}", "") + } else { + let req_sp_text = self.localized_strings.get("hud.skill.req_sp"); + let skill_cost_text = self.skill_set.skill_cost(skill).to_string(); + desc.replace("{SP}", &req_sp_text.replace("{number}", &skill_cost_text)) + } + } +} + +/// Returns skill info as a tuple of title and description. +/// +/// Title is ready to use, description may include `"{SP}"` placeholder you +/// will want to handle yourself. +pub fn skill_strings(skill: Skill, i18n: &Localization) -> (&str, Cow) { + match skill { + // general tree + Skill::General(s) => general_skill_strings(s, i18n), + Skill::UnlockGroup(s) => unlock_skill_strings(s, i18n), + // weapon trees + Skill::Sword(s) => sword_skill_strings(s, i18n), + Skill::Axe(s) => axe_skill_strings(s, i18n), + Skill::Hammer(s) => hammer_skill_strings(s, i18n), + Skill::Bow(s) => bow_skill_strings(s, i18n), + Skill::Staff(s) => staff_skill_strings(s, i18n), + Skill::Sceptre(s) => sceptre_skill_strings(s, i18n), + // movement trees + Skill::Roll(s) => roll_skill_strings(s, i18n), + Skill::Climb(s) => climb_skill_strings(s, i18n), + Skill::Swim(s) => swim_skill_strings(s, i18n), + // mining + Skill::Pick(s) => mining_skill_strings(s, i18n), + } +} + +fn general_skill_strings(skill: GeneralSkill, i18n: &Localization) -> (&str, Cow) { + match skill { + GeneralSkill::HealthIncrease => splice_constant( + i18n, + "hud.skill.inc_health_title", + "hud.skill.inc_health", + HUMANOID_HP_PER_LEVEL / 10, + ), + GeneralSkill::EnergyIncrease => splice_constant( + i18n, + "hud.skill.inc_energy_title", + "hud.skill.inc_energy", + ENERGY_PER_LEVEL / 10, + ), + } +} + +fn unlock_skill_strings(group: SkillGroupKind, i18n: &Localization) -> (&str, Cow) { + match group { + SkillGroupKind::Weapon(ToolKind::Sword) => { + localize(i18n, "hud.skill.unlck_sword_title", "hud.skill.unlck_sword") + }, + SkillGroupKind::Weapon(ToolKind::Axe) => { + localize(i18n, "hud.skill.unlck_axe_title", "hud.skill.unlck_axe") + }, + SkillGroupKind::Weapon(ToolKind::Hammer) => localize( + i18n, + "hud.skill.unlck_hammer_title", + "hud.skill.unlck_hammer", + ), + SkillGroupKind::Weapon(ToolKind::Bow) => { + localize(i18n, "hud.skill.unlck_bow_title", "hud.skill.unlck_bow") + }, + SkillGroupKind::Weapon(ToolKind::Staff) => { + localize(i18n, "hud.skill.unlck_staff_title", "hud.skill.unlck_staff") + }, + SkillGroupKind::Weapon(ToolKind::Sceptre) => localize( + i18n, + "hud.skill.unlck_sceptre_title", + "hud.skill.unlck_sceptre", + ), + SkillGroupKind::General + | SkillGroupKind::Weapon( + ToolKind::Dagger + | ToolKind::Shield + | ToolKind::Spear + | ToolKind::Debug + | ToolKind::Farming + | ToolKind::Pick + | ToolKind::Natural + | ToolKind::Empty, + ) => { + tracing::warn!("Requesting title for unlocking unexpected skill group"); + ("", Cow::Owned(String::new())) + }, + } +} + +fn sword_skill_strings<'a>(skill: SwordSkill, i18n: &'a Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.sword_tree; + match skill { + // triple strike + SwordSkill::TsCombo => localize( + i18n, + "hud.skill.sw_trip_str_combo_title", + "hud.skill.sw_trip_str_combo", + ), + SwordSkill::TsDamage => localize( + i18n, + "hud.skill.sw_trip_str_dmg_title", + "hud.skill.sw_trip_str_dmg", + ), + SwordSkill::TsSpeed => localize( + i18n, + "hud.skill.sw_trip_str_sp_title", + "hud.skill.sw_trip_str_sp", + ), + SwordSkill::TsRegen => localize( + i18n, + "hud.skill.sw_trip_str_reg_title", + "hud.skill.sw_trip_str_reg", + ), + // dash + SwordSkill::DDamage => splice_multiplier( + i18n, + "hud.skill.sw_dash_dmg_title", + "hud.skill.sw_dash_dmg", + modifiers.dash.base_damage, + ), + SwordSkill::DDrain => splice_multiplier( + i18n, + "hud.skill.sw_dash_drain_title", + "hud.skill.sw_dash_drain", + modifiers.dash.energy_drain, + ), + SwordSkill::DCost => splice_multiplier( + i18n, + "hud.skill.sw_dash_cost_title", + "hud.skill.sw_dash_cost", + modifiers.dash.energy_cost, + ), + SwordSkill::DSpeed => splice_multiplier( + i18n, + "hud.skill.sw_dash_speed_title", + "hud.skill.sw_dash_speed", + modifiers.dash.forward_speed, + ), + SwordSkill::DInfinite => localize( + i18n, + "hud.skill.sw_dash_charge_through_title", + "hud.skill.sw_dash_charge_through", + ), + SwordSkill::DScaling => splice_multiplier( + i18n, + "hud.skill.sw_dash_scale_title", + "hud.skill.sw_dash_scale", + modifiers.dash.scaled_damage, + ), + // spin + SwordSkill::UnlockSpin => localize(i18n, "hud.skill.sw_spin_title", "hud.skill.sw_spin"), + SwordSkill::SDamage => splice_multiplier( + i18n, + "hud.skill.sw_spin_dmg_title", + "hud.skill.sw_spin_dmg", + modifiers.spin.base_damage, + ), + SwordSkill::SSpeed => splice_multiplier( + i18n, + "hud.skill.sw_spin_spd_title", + "hud.skill.sw_spin_spd", + modifiers.spin.swing_duration, + ), + SwordSkill::SCost => splice_multiplier( + i18n, + "hud.skill.sw_spin_cost_title", + "hud.skill.sw_spin_cost", + modifiers.spin.energy_cost, + ), + SwordSkill::SSpins => splice_constant( + i18n, + "hud.skill.sw_spin_spins_title", + "hud.skill.sw_spin_spins", + modifiers.spin.num, + ), + // independent skills + SwordSkill::InterruptingAttacks => localize( + i18n, + "hud.skill.sw_interrupt_title", + "hud.skill.sw_interrupt", + ), + } +} + +fn axe_skill_strings(skill: AxeSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.axe_tree; + match skill { + // Double strike upgrades + AxeSkill::DsCombo => localize( + i18n, + "hud.skill.axe_double_strike_combo_title", + "hud.skill.axe_double_strike_combo", + ), + AxeSkill::DsDamage => localize( + i18n, + "hud.skill.axe_double_strike_damage_title", + "hud.skill.axe_double_strike_damage", + ), + AxeSkill::DsSpeed => localize( + i18n, + "hud.skill.axe_double_strike_speed_title", + "hud.skill.axe_double_strike_speed", + ), + AxeSkill::DsRegen => localize( + i18n, + "hud.skill.axe_double_strike_regen_title", + "hud.skill.axe_double_strike_regen", + ), + // Spin upgrades + AxeSkill::SInfinite => localize( + i18n, + "hud.skill.axe_infinite_axe_spin_title", + "hud.skill.axe_infinite_axe_spin", + ), + AxeSkill::SHelicopter => localize( + i18n, + "hud.skill.axe_spin_helicopter_title", + "hud.skill.axe_spin_helicopter", + ), + AxeSkill::SDamage => splice_multiplier( + i18n, + "hud.skill.axe_spin_damage_title", + "hud.skill.axe_spin_damage", + modifiers.spin.base_damage, + ), + AxeSkill::SSpeed => splice_multiplier( + i18n, + "hud.skill.axe_spin_speed_title", + "hud.skill.axe_spin_speed", + modifiers.spin.swing_duration, + ), + AxeSkill::SCost => splice_multiplier( + i18n, + "hud.skill.axe_spin_cost_title", + "hud.skill.axe_spin_cost", + modifiers.spin.energy_cost, + ), + // Leap upgrades + AxeSkill::UnlockLeap => localize( + i18n, + "hud.skill.axe_unlock_leap_title", + "hud.skill.axe_unlock_leap", + ), + AxeSkill::LDamage => splice_multiplier( + i18n, + "hud.skill.axe_leap_damage_title", + "hud.skill.axe_leap_damage", + modifiers.leap.base_damage, + ), + AxeSkill::LKnockback => splice_multiplier( + i18n, + "hud.skill.axe_leap_knockback_title", + "hud.skill.axe_leap_knockback", + modifiers.leap.knockback, + ), + AxeSkill::LCost => splice_multiplier( + i18n, + "hud.skill.axe_leap_cost_title", + "hud.skill.axe_leap_cost", + modifiers.leap.energy_cost, + ), + AxeSkill::LDistance => splice_multiplier( + i18n, + "hud.skill.axe_leap_distance_title", + "hud.skill.axe_leap_distance", + modifiers.leap.leap_strength, + ), + } +} + +fn hammer_skill_strings(skill: HammerSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.hammer_tree; + // Single strike upgrades + match skill { + HammerSkill::SsKnockback => splice_multiplier( + i18n, + "hud.skill.hmr_single_strike_knockback_title", + "hud.skill.hmr_single_strike_knockback", + modifiers.single_strike.knockback, + ), + HammerSkill::SsDamage => localize( + i18n, + "hud.skill.hmr_single_strike_damage_title", + "hud.skill.hmr_single_strike_damage", + ), + HammerSkill::SsSpeed => localize( + i18n, + "hud.skill.hmr_single_strike_speed_title", + "hud.skill.hmr_single_strike_speed", + ), + HammerSkill::SsRegen => localize( + i18n, + "hud.skill.hmr_single_strike_regen_title", + "hud.skill.hmr_single_strike_regen", + ), + // Charged melee upgrades + HammerSkill::CDamage => splice_multiplier( + i18n, + "hud.skill.hmr_charged_melee_damage_title", + "hud.skill.hmr_charged_melee_damage", + modifiers.charged.scaled_damage, + ), + HammerSkill::CKnockback => splice_multiplier( + i18n, + "hud.skill.hmr_charged_melee_knockback_title", + "hud.skill.hmr_charged_melee_knockback", + modifiers.charged.scaled_knockback, + ), + HammerSkill::CDrain => splice_multiplier( + i18n, + "hud.skill.hmr_charged_melee_nrg_drain_title", + "hud.skill.hmr_charged_melee_nrg_drain", + modifiers.charged.energy_drain, + ), + HammerSkill::CSpeed => splice_multiplier( + i18n, + "hud.skill.hmr_charged_rate_title", + "hud.skill.hmr_charged_rate", + modifiers.charged.charge_rate, + ), + // Leap upgrades + HammerSkill::UnlockLeap => localize( + i18n, + "hud.skill.hmr_unlock_leap_title", + "hud.skill.hmr_unlock_leap", + ), + HammerSkill::LDamage => splice_multiplier( + i18n, + "hud.skill.hmr_leap_damage_title", + "hud.skill.hmr_leap_damage", + modifiers.leap.base_damage, + ), + HammerSkill::LCost => splice_multiplier( + i18n, + "hud.skill.hmr_leap_cost_title", + "hud.skill.hmr_leap_cost", + modifiers.leap.energy_cost, + ), + HammerSkill::LDistance => splice_multiplier( + i18n, + "hud.skill.hmr_leap_distance_title", + "hud.skill.hmr_leap_distance", + modifiers.leap.leap_strength, + ), + HammerSkill::LKnockback => splice_multiplier( + i18n, + "hud.skill.hmr_leap_knockback_title", + "hud.skill.hmr_leap_knockback", + modifiers.leap.knockback, + ), + HammerSkill::LRange => splice_multiplier( + i18n, + "hud.skill.hmr_leap_radius_title", + "hud.skill.hmr_leap_radius", + modifiers.leap.range, + ), + } +} + +fn bow_skill_strings(skill: BowSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.bow_tree; + match skill { + // Passives + BowSkill::ProjSpeed => splice_multiplier( + i18n, + "hud.skill.bow_projectile_speed_title", + "hud.skill.bow_projectile_speed", + modifiers.universal.projectile_speed, + ), + // Charged upgrades + BowSkill::CDamage => splice_multiplier( + i18n, + "hud.skill.bow_charged_damage_title", + "hud.skill.bow_charged_damage", + modifiers.charged.damage_scaling, + ), + BowSkill::CRegen => splice_multiplier( + i18n, + "hud.skill.bow_charged_energy_regen_title", + "hud.skill.bow_charged_energy_regen", + modifiers.charged.regen_scaling, + ), + BowSkill::CKnockback => splice_multiplier( + i18n, + "hud.skill.bow_charged_knockback_title", + "hud.skill.bow_charged_knockback", + modifiers.charged.knockback_scaling, + ), + BowSkill::CSpeed => splice_multiplier( + i18n, + "hud.skill.bow_charged_speed_title", + "hud.skill.bow_charged_speed", + modifiers.charged.charge_rate, + ), + BowSkill::CMove => splice_multiplier( + i18n, + "hud.skill.bow_charged_move_title", + "hud.skill.bow_charged_move", + modifiers.charged.move_speed, + ), + // Repeater upgrades + BowSkill::RDamage => splice_multiplier( + i18n, + "hud.skill.bow_repeater_damage_title", + "hud.skill.bow_repeater_damage", + modifiers.repeater.power, + ), + BowSkill::RCost => splice_multiplier( + i18n, + "hud.skill.bow_repeater_cost_title", + "hud.skill.bow_repeater_cost", + modifiers.repeater.energy_cost, + ), + BowSkill::RSpeed => splice_multiplier( + i18n, + "hud.skill.bow_repeater_speed_title", + "hud.skill.bow_repeater_speed", + modifiers.repeater.max_speed, + ), + // Shotgun upgrades + BowSkill::UnlockShotgun => localize( + i18n, + "hud.skill.bow_shotgun_unlock_title", + "hud.skill.bow_shotgun_unlock", + ), + BowSkill::SDamage => splice_multiplier( + i18n, + "hud.skill.bow_shotgun_damage_title", + "hud.skill.bow_shotgun_damage", + modifiers.shotgun.power, + ), + BowSkill::SCost => splice_multiplier( + i18n, + "hud.skill.bow_shotgun_cost_title", + "hud.skill.bow_shotgun_cost", + modifiers.shotgun.energy_cost, + ), + BowSkill::SArrows => splice_constant( + i18n, + "hud.skill.bow_shotgun_arrow_count_title", + "hud.skill.bow_shotgun_arrow_count", + modifiers.shotgun.num_projectiles, + ), + BowSkill::SSpread => splice_multiplier( + i18n, + "hud.skill.bow_shotgun_spread_title", + "hud.skill.bow_shotgun_spread", + modifiers.shotgun.spread, + ), + } +} + +fn staff_skill_strings(skill: StaffSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.staff_tree; + match skill { + // Basic ranged upgrades + StaffSkill::BDamage => splice_multiplier( + i18n, + "hud.skill.st_damage_title", + "hud.skill.st_damage", + modifiers.fireball.power, + ), + StaffSkill::BRegen => splice_multiplier( + i18n, + "hud.skill.st_energy_regen_title", + "hud.skill.st_energy_regen", + modifiers.fireball.regen, + ), + StaffSkill::BRadius => splice_multiplier( + i18n, + "hud.skill.st_explosion_radius_title", + "hud.skill.st_explosion_radius", + modifiers.fireball.range, + ), + // Flamethrower upgrades + StaffSkill::FDamage => splice_multiplier( + i18n, + "hud.skill.st_flamethrower_damage_title", + "hud.skill.st_flamethrower_damage", + modifiers.flamethrower.damage, + ), + StaffSkill::FRange => splice_multiplier( + i18n, + "hud.skill.st_flamethrower_range_title", + "hud.skill.st_flamethrower_range", + modifiers.flamethrower.range, + ), + StaffSkill::FDrain => splice_multiplier( + i18n, + "hud.skill.st_energy_drain_title", + "hud.skill.st_energy_drain", + modifiers.flamethrower.energy_drain, + ), + StaffSkill::FVelocity => splice_multiplier( + i18n, + "hud.skill.st_flame_velocity_title", + "hud.skill.st_flame_velocity", + modifiers.flamethrower.velocity, + ), + // Shockwave upgrades + StaffSkill::UnlockShockwave => localize( + i18n, + "hud.skill.st_shockwave_unlock_title", + "hud.skill.st_shockwave_unlock", + ), + StaffSkill::SDamage => splice_multiplier( + i18n, + "hud.skill.st_shockwave_damage_title", + "hud.skill.st_shockwave_damage", + modifiers.shockwave.damage, + ), + StaffSkill::SKnockback => splice_multiplier( + i18n, + "hud.skill.st_shockwave_knockback_title", + "hud.skill.st_shockwave_knockback", + modifiers.shockwave.knockback, + ), + StaffSkill::SRange => splice_multiplier( + i18n, + "hud.skill.st_shockwave_range_title", + "hud.skill.st_shockwave_range", + modifiers.shockwave.duration, + ), + StaffSkill::SCost => splice_multiplier( + i18n, + "hud.skill.st_shockwave_cost_title", + "hud.skill.st_shockwave_cost", + modifiers.shockwave.energy_cost, + ), + } +} + +fn sceptre_skill_strings(skill: SceptreSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.sceptre_tree; + match skill { + // Lifesteal beam upgrades + SceptreSkill::LDamage => splice_multiplier( + i18n, + "hud.skill.sc_lifesteal_damage_title", + "hud.skill.sc_lifesteal_damage", + modifiers.beam.damage, + ), + SceptreSkill::LRange => splice_multiplier( + i18n, + "hud.skill.sc_lifesteal_range_title", + "hud.skill.sc_lifesteal_range", + modifiers.beam.range, + ), + SceptreSkill::LLifesteal => splice_multiplier( + i18n, + "hud.skill.sc_lifesteal_lifesteal_title", + "hud.skill.sc_lifesteal_lifesteal", + modifiers.beam.lifesteal, + ), + SceptreSkill::LRegen => splice_multiplier( + i18n, + "hud.skill.sc_lifesteal_regen_title", + "hud.skill.sc_lifesteal_regen", + modifiers.beam.energy_regen, + ), + // Healing aura upgrades + SceptreSkill::HHeal => splice_multiplier( + i18n, + "hud.skill.sc_heal_heal_title", + "hud.skill.sc_heal_heal", + modifiers.healing_aura.strength, + ), + SceptreSkill::HRange => splice_multiplier( + i18n, + "hud.skill.sc_heal_range_title", + "hud.skill.sc_heal_range", + modifiers.healing_aura.range, + ), + SceptreSkill::HDuration => splice_multiplier( + i18n, + "hud.skill.sc_heal_duration_title", + "hud.skill.sc_heal_duration", + modifiers.healing_aura.duration, + ), + SceptreSkill::HCost => splice_multiplier( + i18n, + "hud.skill.sc_heal_cost_title", + "hud.skill.sc_heal_cost", + modifiers.healing_aura.energy_cost, + ), + // Warding aura upgrades + SceptreSkill::UnlockAura => localize( + i18n, + "hud.skill.sc_wardaura_unlock_title", + "hud.skill.sc_wardaura_unlock", + ), + SceptreSkill::AStrength => splice_multiplier( + i18n, + "hud.skill.sc_wardaura_strength_title", + "hud.skill.sc_wardaura_strength", + modifiers.warding_aura.strength, + ), + SceptreSkill::ADuration => splice_multiplier( + i18n, + "hud.skill.sc_wardaura_duration_title", + "hud.skill.sc_wardaura_duration", + modifiers.warding_aura.duration, + ), + SceptreSkill::ARange => splice_multiplier( + i18n, + "hud.skill.sc_wardaura_range_title", + "hud.skill.sc_wardaura_range", + modifiers.warding_aura.range, + ), + SceptreSkill::ACost => splice_multiplier( + i18n, + "hud.skill.sc_wardaura_cost_title", + "hud.skill.sc_wardaura_cost", + modifiers.warding_aura.energy_cost, + ), + } +} + +fn roll_skill_strings(skill: RollSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.general_tree.roll; + match skill { + RollSkill::Cost => splice_multiplier( + i18n, + "hud.skill.roll_energy_title", + "hud.skill.roll_energy", + modifiers.energy_cost, + ), + RollSkill::Strength => splice_multiplier( + i18n, + "hud.skill.roll_speed_title", + "hud.skill.roll_speed", + modifiers.strength, + ), + RollSkill::Duration => splice_multiplier( + i18n, + "hud.skill.roll_dur_title", + "hud.skill.roll_dur", + modifiers.duration, + ), + } +} + +fn climb_skill_strings(skill: ClimbSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.general_tree.climb; + match skill { + ClimbSkill::Cost => splice_multiplier( + i18n, + "hud.skill.climbing_cost_title", + "hud.skill.climbing_cost", + modifiers.energy_cost, + ), + ClimbSkill::Speed => splice_multiplier( + i18n, + "hud.skill.climbing_speed_title", + "hud.skill.climbing_speed", + modifiers.speed, + ), + } +} + +fn swim_skill_strings(skill: SwimSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.general_tree.swim; + match skill { + SwimSkill::Speed => splice_multiplier( + i18n, + "hud.skill.swim_speed_title", + "hud.skill.swim_speed", + modifiers.speed, + ), + } +} + +fn mining_skill_strings(skill: MiningSkill, i18n: &Localization) -> (&str, Cow) { + let modifiers = SKILL_MODIFIERS.mining_tree; + match skill { + MiningSkill::Speed => splice_multiplier( + i18n, + "hud.skill.pick_strike_speed_title", + "hud.skill.pick_strike_speed", + modifiers.speed, + ), + MiningSkill::OreGain => splice_multiplier( + i18n, + "hud.skill.pick_strike_oregain_title", + "hud.skill.pick_strike_oregain", + modifiers.ore_gain, + ), + MiningSkill::GemGain => splice_multiplier( + i18n, + "hud.skill.pick_strike_gemgain_title", + "hud.skill.pick_strike_gemgain", + modifiers.gem_gain, + ), + } +} + +/// Helper function which takes title i18n key and description i18n key +/// and returns localized title and localized description replacing "{boost}" +/// placeholder with passed constant. +// TODO: do something better when we get to multimodifier skills +fn splice_constant<'loc>( + i18n: &'loc Localization, + title: &'loc str, + desc: &str, + constant: u32, +) -> (&'loc str, Cow<'loc, str>) { + let title = i18n.get(title); + let desc = i18n.get(desc); + let desc = desc.replace("{boost}", &constant.to_string()); + + (title, Cow::Owned(desc)) +} + +/// Helper function which takes title i18n key and description i18n key +/// and returns localized title and localized description replacing "{boost}" +/// placeholder with absolute value of percentage effect of multiplier. +// TODO: do something better when we get to multimodifier skills +fn splice_multiplier<'loc>( + i18n: &'loc Localization, + title: &'loc str, + desc: &str, + multipler: f32, +) -> (&'loc str, Cow<'loc, str>) { + let percentage = hud::multiplier_to_percentage(multipler).unsigned_abs(); + splice_constant(i18n, title, desc, percentage) +} + +// Small helper function to get localized skill text. +fn localize<'loc>( + i18n: &'loc Localization, + title: &'loc str, + desc: &'loc str, +) -> (&'loc str, Cow<'loc, str>) { + let title = i18n.get(title); + let desc = i18n.get(desc); + + (title, Cow::Borrowed(desc)) } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index aef808929a..4cede45fde 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -4132,3 +4132,14 @@ pub fn angle_of_attack_text( "Angle of Attack: Not moving".to_owned() } } + +/// Converts multiplier to percentage. +/// +/// # Examples +/// ``` +/// use veloren_voxygen::hud::multiplier_to_percentage; +/// +/// assert_eq!(multiplier_to_percentage(1.05), 5); +/// assert_eq!(multiplier_to_percentage(0.85), -15); +/// ``` +pub fn multiplier_to_percentage(value: f32) -> i32 { (value * 100.0 - 100.0).round() as i32 }