From eaa41c7dea4a89355482ab04e1cb6f5093089e00 Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 16 Jan 2021 12:01:57 -0500 Subject: [PATCH] Skills are now factored into combat rating. New formula for exp scaling per SP. Adjust lvl up msg fade Add female humanoid names WIP Changed text formatting when skill is max level. Added message to show you have 0 skill points available. Addressed a lot of comments. various changes to UI - fix skillbar offset - remove CR indicators for group members - add CR indicators to group member frames - use unified CR indicator icon Exp reward tweaks. Fixed flamethrower range skill description. --- Cargo.lock | 2 - assets/common/npc_names.ron | 93 +++++++ assets/voxygen/element/frames/enemybar_1.png | 4 +- .../element/icons/combat_rating_shadow.png | 3 + common/Cargo.toml | 1 - common/src/combat.rs | 71 +++-- common/src/comp/ability.rs | 257 +++++++++--------- common/src/comp/body.rs | 2 +- common/src/comp/skills.rs | 214 ++++++++------- common/src/outcome.rs | 1 + common/src/skillset_builder.rs | 6 +- common/src/states/utils.rs | 38 +-- common/sys/src/agent.rs | 12 +- common/sys/src/stats.rs | 39 ++- server/Cargo.toml | 1 - server/src/events/entity_manipulation.rs | 146 ++++------ .../src/persistence/character/conversions.rs | 14 +- server/src/state_ext.rs | 21 +- server/src/sys/terrain.rs | 2 - voxygen/anim/src/dyn_lib.rs | 3 +- voxygen/src/audio/sfx/mod.rs | 2 +- voxygen/src/hud/bag.rs | 26 +- voxygen/src/hud/buttons.rs | 15 +- voxygen/src/hud/diary.rs | 127 +++++---- voxygen/src/hud/group.rs | 62 ++++- voxygen/src/hud/hotbar.rs | 15 +- voxygen/src/hud/img_ids.rs | 5 +- voxygen/src/hud/mod.rs | 37 ++- voxygen/src/hud/overhead.rs | 85 +++--- voxygen/src/hud/skillbar.rs | 19 +- voxygen/src/menu/char_selection/ui/mod.rs | 7 +- 31 files changed, 718 insertions(+), 612 deletions(-) create mode 100644 assets/voxygen/element/icons/combat_rating_shadow.png diff --git a/Cargo.lock b/Cargo.lock index a41e27ab60..bdbf7973ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5995,7 +5995,6 @@ dependencies = [ "hashbrown 0.9.1", "image", "indexmap", - "inline_tweak", "lazy_static", "num-derive", "num-traits 0.2.14", @@ -6100,7 +6099,6 @@ dependencies = [ "futures-timer 3.0.2", "futures-util", "hashbrown 0.9.1", - "inline_tweak", "itertools", "lazy_static", "libsqlite3-sys", diff --git a/assets/common/npc_names.ron b/assets/common/npc_names.ron index 9e06cb5bad..f0754fe52b 100644 --- a/assets/common/npc_names.ron +++ b/assets/common/npc_names.ron @@ -106,6 +106,99 @@ "Zenner" ] ), + /*keyword: "humanoid_f", + names: [ + "Acele", + "Autumn", + "Acholate", + "Ada", + "Adorra", + "Ahanna", + "Brana", + "Bathelie", + "Calene", + "Calina", + "Celestine", + "Caela", + "Cassia", + "Celoa", + "Dalavesta", + "Dylena", + "Desini", + "Diva", + "Ebatryne", + "Efari", + "Enona", + "Enaldie", + "Ember", + "Esdel", + "Eune", + "Fayne", + "Frida", + "Ferra", + "Flora", + "Fintis", + "Gatlen", + "Gatline", + "Gronalyn", + "Helenia", + "Halete", + "Hyza", + "Helena", + "Halin", + "Hera", + "Hilda", + "Hydra", + "Ismeria", + "Iris", + "Joss", + "Kadra", + "Kagra", + "Kyra", + "Konta", + "Krinn", + "Lydia", + "Laelia", + "Leda", + "Leta", + "Lisbeth", + "Lyra", + "Luna", + "Medora", + "Mazarine", + "Merlyn", + "Marina", + "Nephele", + "Odessa", + "Orla", + "Perl", + "Rhodeia", + "Rosella", + "Raven", + "Rachel", + "Ryven", + "Solenne", + "Seren", + "Summer", + "Solstice", + "Stella", + "Sarah", + "Syrin", + "Tessa", + "Thea", + "Tez", + "Vivien", + "Varda", + "Veridia", + "Victoria", + "Vale", + "Vega", + "Yorja", + "Xaviera", + "Zorina", + "Zephyra" + ] + ),*/ species: ( danari: ( keyword: "danari", diff --git a/assets/voxygen/element/frames/enemybar_1.png b/assets/voxygen/element/frames/enemybar_1.png index 37573662b8..c9565c704c 100644 --- a/assets/voxygen/element/frames/enemybar_1.png +++ b/assets/voxygen/element/frames/enemybar_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4cfb11db560dbda70ccf97b99905f01c21839f0ef2000a9d5427c7cc7d741f2 -size 257 +oid sha256:058e1d1f7932c626695671daa158bdf4694ceac17e83709224ece9e62343bb9f +size 1457 diff --git a/assets/voxygen/element/icons/combat_rating_shadow.png b/assets/voxygen/element/icons/combat_rating_shadow.png new file mode 100644 index 0000000000..e069f3f3f6 --- /dev/null +++ b/assets/voxygen/element/icons/combat_rating_shadow.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be4dd798012d207818bd9604817d64227581955c90d2c84de7429e23f5eb1388 +size 2050 diff --git a/common/Cargo.toml b/common/Cargo.toml index fefde8a3f1..b4a79f060f 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -28,7 +28,6 @@ spin_sleep = "1.0" tracing = { version = "0.1", default-features = false } vek = { version = "0.12.0", features = ["serde"] } uuid = { version = "0.8.1", default-features = false, features = ["serde", "v4"] } -inline_tweak = "1.0.2" # Assets assets_manager = {version = "0.4.2", features = ["bincode", "ron", "json", "hot-reloading"]} diff --git a/common/src/combat.rs b/common/src/combat.rs index fa31aabea7..470a7231fb 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -1,15 +1,19 @@ use crate::{ comp::{ inventory::{ - item::{armor::Protection, tool::ToolKind, ItemKind}, + item::{ + armor::Protection, + tool::{Tool, ToolKind}, + ItemKind, + }, slot::EquipSlot, }, - Body, BuffKind, Health, HealthChange, HealthSource, Inventory, + skills::{SkillGroupType, SkillSet}, + BuffKind, Health, HealthChange, HealthSource, Inventory, Stats, }, uid::Uid, util::Dir, }; -use inline_tweak::*; use serde::{Deserialize, Serialize}; use vek::*; @@ -210,52 +214,47 @@ impl Knockback { } } +fn equipped_tool(inv: &Inventory, slot: EquipSlot) -> Option<&Tool> { + inv.equipped(slot).and_then(|i| { + if let ItemKind::Tool(tool) = &i.kind() { + Some(tool) + } else { + None + } + }) +} + pub fn get_weapons(inv: &Inventory) -> (Option, Option) { ( - inv.equipped(EquipSlot::Mainhand).and_then(|i| { - if let ItemKind::Tool(tool) = &i.kind() { - Some(tool.kind) - } else { - None - } - }), - inv.equipped(EquipSlot::Offhand).and_then(|i| { - if let ItemKind::Tool(tool) = &i.kind() { - Some(tool.kind) - } else { - None - } - }), + equipped_tool(inv, EquipSlot::Mainhand).map(|tool| tool.kind), + equipped_tool(inv, EquipSlot::Offhand).map(|tool| tool.kind), ) } -fn max_equipped_weapon_damage(inv: &Inventory) -> f32 { - let active_damage = inv.equipped(EquipSlot::Mainhand).map_or(0.0, |i| { - if let ItemKind::Tool(tool) = &i.kind() { - tool.base_power() * tool.base_speed() - } else { - 0.0 - } +fn max_equipped_weapon_damage(inv: &Inventory, skillset: &SkillSet) -> f32 { + let active_damage = equipped_tool(inv, EquipSlot::Mainhand).map_or(0.0, |tool| { + tool.base_power() + * tool.base_speed() + * (1.0 + 0.05 * skillset.earned_sp(SkillGroupType::Weapon(tool.kind)) as f32) }); - let second_damage = inv.equipped(EquipSlot::Offhand).map_or(0.0, |i| { - if let ItemKind::Tool(tool) = &i.kind() { - tool.base_power() * tool.base_speed() - } else { - 0.0 - } + let second_damage = equipped_tool(inv, EquipSlot::Offhand).map_or(0.0, |tool| { + tool.base_power() + * tool.base_speed() + * (1.0 + 0.05 * skillset.earned_sp(SkillGroupType::Weapon(tool.kind)) as f32) }); active_damage.max(second_damage) } -pub fn combat_rating(inventory: &Inventory, health: &Health, body: &Body) -> f32 { - let defensive_weighting = tweak!(1.0); - let offensive_weighting = tweak!(1.0); +pub fn combat_rating(inventory: &Inventory, health: &Health, stats: &Stats) -> f32 { + let defensive_weighting = 1.0; + let offensive_weighting = 1.0; let defensive_rating = health.maximum() as f32 / (1.0 - Damage::compute_damage_reduction(inventory)).max(0.00001) / 100.0; - let offensive_rating = max_equipped_weapon_damage(inventory).max(0.1); + let offensive_rating = max_equipped_weapon_damage(inventory, &stats.skill_set).max(0.1) + + 0.05 * stats.skill_set.earned_sp(SkillGroupType::General) as f32; let combined_rating = (offensive_rating * offensive_weighting + defensive_rating * defensive_weighting) - / (2.0 * offensive_weighting.max(defensive_weighting)); - combined_rating * body.combat_multiplier() + / (offensive_weighting + defensive_weighting); + combined_rating * stats.body_type.combat_multiplier() } diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 5f5daacb72..c78f5d4cfc 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -11,7 +11,6 @@ use crate::{ }, Knockback, }; -use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use std::time::Duration; use vek::Vec3; @@ -500,7 +499,7 @@ impl CharacterAbility { pub fn adjusted_by_skills( mut self, - skills: &HashMap, + skillset: &skills::SkillSet, tool: Option, ) -> Self { use skills::Skill::{self, *}; @@ -518,14 +517,12 @@ impl CharacterAbility { ref mut scales_from_combo, .. } => { - *is_interruptible = skills.contains_key(&Sword(InterruptingAttacks)); - let speed_segments = - Sword(TsSpeed).get_max_level().map_or(1, |l| l + 1) as f32; - let speed_level = if skills.contains_key(&Sword(TsCombo)) { - skills - .get(&Sword(TsSpeed)) - .copied() - .flatten() + *is_interruptible = skillset.has_skill(Sword(InterruptingAttacks)); + let speed_segments = Sword(TsSpeed).max_level().map_or(1, |l| l + 1) as f32; + let speed_level = if skillset.has_skill(Sword(TsCombo)) { + skillset + .skill_level(Sword(TsSpeed)) + .unwrap_or(None) .map_or(1, |l| l + 1) as f32 } else { 0.0 @@ -535,20 +532,18 @@ impl CharacterAbility { *max_speed_increase *= speed_level / speed_segments; } let energy_level = - if let Some(level) = skills.get(&Sword(TsRegen)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sword(TsRegen)) { level } else { 0 }; *max_energy_gain = (*max_energy_gain as f32 * ((energy_level + 1) * stage_data.len() as u16 - 1) as f32 - / ((Sword(TsRegen).get_max_level().unwrap() + 1) - * stage_data.len() as u16 + / ((Sword(TsRegen).max_level().unwrap() + 1) * stage_data.len() as u16 - 1) as f32) as u32; - *scales_from_combo = skills - .get(&Sword(TsDamage)) - .copied() - .flatten() + *scales_from_combo = skillset + .skill_level(Sword(TsDamage)) + .unwrap_or(None) .unwrap_or(0) .into(); }, @@ -562,27 +557,27 @@ impl CharacterAbility { ref mut infinite_charge, .. } => { - *is_interruptible = skills.contains_key(&Sword(InterruptingAttacks)); - if let Some(level) = skills.get(&Sword(DCost)).copied().flatten() { + *is_interruptible = skillset.has_skill(Sword(InterruptingAttacks)); + if let Ok(Some(level)) = skillset.skill_level(Sword(DCost)) { *energy_cost = (*energy_cost as f32 * 0.75_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Sword(DDrain)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sword(DDrain)) { *energy_drain = (*energy_drain as f32 * 0.75_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Sword(DDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sword(DDamage)) { *base_damage = (*base_damage as f32 * 1.2_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Sword(DScaling)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sword(DScaling)) { *scaled_damage = (*scaled_damage as f32 * 1.2_f32.powi(level.into())) as u32; } - if skills.contains_key(&Sword(DSpeed)) { + if skillset.has_skill(Sword(DSpeed)) { *forward_speed *= 1.3; } - *infinite_charge = skills.contains_key(&Sword(DInfinite)); + *infinite_charge = skillset.has_skill(Sword(DInfinite)); }, SpinMelee { ref mut is_interruptible, @@ -592,21 +587,24 @@ impl CharacterAbility { ref mut num_spins, .. } => { - *is_interruptible = skills.contains_key(&Sword(InterruptingAttacks)); - if let Some(level) = skills.get(&Sword(SDamage)).copied().flatten() { + *is_interruptible = skillset.has_skill(Sword(InterruptingAttacks)); + if let Ok(Some(level)) = skillset.skill_level(Sword(SDamage)) { *base_damage = (*base_damage as f32 * 1.4_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Sword(SSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sword(SSpeed)) { *swing_duration = (*swing_duration as f32 * 0.8_f32.powi(level.into())) as u64; } - if let Some(level) = skills.get(&Sword(SCost)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sword(SCost)) { *energy_cost = (*energy_cost as f32 * 0.75_f32.powi(level.into())) as u32; } - *num_spins = - skills.get(&Sword(SSpins)).copied().flatten().unwrap_or(0) as u32 + 1; + *num_spins = skillset + .skill_level(Sword(SSpins)) + .unwrap_or(None) + .unwrap_or(0) as u32 + + 1; }, _ => {}, } @@ -622,31 +620,31 @@ impl CharacterAbility { ref mut scales_from_combo, .. } => { - if !skills.contains_key(&Axe(DsCombo)) { + if !skillset.has_skill(Axe(DsCombo)) { stage_data.pop(); } - let speed_segments = Axe(DsSpeed).get_max_level().unwrap_or(1) as f32; - let speed_level = - skills.get(&Axe(DsSpeed)).copied().flatten().unwrap_or(0) as f32; + let speed_segments = Axe(DsSpeed).max_level().unwrap_or(1) as f32; + let speed_level = skillset + .skill_level(Axe(DsSpeed)) + .unwrap_or(None) + .unwrap_or(0) as f32; { *speed_increase *= speed_level / speed_segments; *max_speed_increase *= speed_level / speed_segments; } let energy_level = - if let Some(level) = skills.get(&Axe(DsRegen)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Axe(DsRegen)) { level } else { 0 }; *max_energy_gain = (*max_energy_gain as f32 * ((energy_level + 1) * stage_data.len() as u16 - 1) as f32 - / ((Axe(DsRegen).get_max_level().unwrap() + 1) - * stage_data.len() as u16 + / ((Axe(DsRegen).max_level().unwrap() + 1) * stage_data.len() as u16 - 1) as f32) as u32; - *scales_from_combo = skills - .get(&Axe(DsDamage)) - .copied() - .flatten() + *scales_from_combo = skillset + .skill_level(Axe(DsDamage)) + .unwrap_or(None) .unwrap_or(0) .into(); }, @@ -658,17 +656,17 @@ impl CharacterAbility { ref mut is_helicopter, .. } => { - *is_infinite = skills.contains_key(&Axe(SInfinite)); - *is_helicopter = skills.contains_key(&Axe(SHelicopter)); - if let Some(level) = skills.get(&Axe(SDamage)).copied().flatten() { + *is_infinite = skillset.has_skill(Axe(SInfinite)); + *is_helicopter = skillset.has_skill(Axe(SHelicopter)); + if let Ok(Some(level)) = skillset.skill_level(Axe(SDamage)) { *base_damage = (*base_damage as f32 * 1.3_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Axe(SSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Axe(SSpeed)) { *swing_duration = (*swing_duration as f32 * 0.8_f32.powi(level.into())) as u64; } - if let Some(level) = skills.get(&Axe(SCost)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Axe(SCost)) { *energy_cost = (*energy_cost as f32 * 0.75_f32.powi(level.into())) as u32; } @@ -681,18 +679,18 @@ impl CharacterAbility { ref mut vertical_leap_strength, .. } => { - if let Some(level) = skills.get(&Axe(LDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Axe(LDamage)) { *base_damage = (*base_damage as f32 * 1.35_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Axe(LKnockback)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Axe(LKnockback)) { *knockback *= 1.4_f32.powi(level.into()); } - if let Some(level) = skills.get(&Axe(LCost)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Axe(LCost)) { *energy_cost = (*energy_cost as f32 * 0.75_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Axe(LDistance)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Axe(LDistance)) { *forward_leap_strength *= 1.2_f32.powi(level.into()); *vertical_leap_strength *= 1.2_f32.powi(level.into()); } @@ -711,34 +709,34 @@ impl CharacterAbility { ref mut scales_from_combo, .. } => { - if let Some(level) = skills.get(&Hammer(SsKnockback)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(SsKnockback)) { *stage_data = (*stage_data) .iter() .map(|s| s.modify_strike(1.5_f32.powi(level.into()))) .collect::>>(); } - let speed_segments = Hammer(SsSpeed).get_max_level().unwrap_or(1) as f32; - let speed_level = - skills.get(&Hammer(SsSpeed)).copied().flatten().unwrap_or(0) as f32; + let speed_segments = Hammer(SsSpeed).max_level().unwrap_or(1) as f32; + let speed_level = skillset + .skill_level(Hammer(SsSpeed)) + .unwrap_or(None) + .unwrap_or(0) as f32; { *speed_increase *= speed_level / speed_segments; *max_speed_increase *= speed_level / speed_segments; } let energy_level = - if let Some(level) = skills.get(&Hammer(SsRegen)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(SsRegen)) { level } else { 0 }; *max_energy_gain = (*max_energy_gain as f32 * ((energy_level + 1) * stage_data.len() as u16) as f32 - / ((Hammer(SsRegen).get_max_level().unwrap() + 1) - * stage_data.len() as u16) as f32) - as u32; - *scales_from_combo = skills - .get(&Hammer(SsDamage)) - .copied() - .flatten() + / ((Hammer(SsRegen).max_level().unwrap() + 1) * stage_data.len() as u16) + as f32) as u32; + *scales_from_combo = skillset + .skill_level(Hammer(SsDamage)) + .unwrap_or(None) .unwrap_or(0) .into(); }, @@ -749,18 +747,18 @@ impl CharacterAbility { ref mut speed, .. } => { - if let Some(level) = skills.get(&Hammer(CDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(CDamage)) { *scaled_damage = (*scaled_damage as f32 * 1.25_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Hammer(CKnockback)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(CKnockback)) { *scaled_knockback *= 1.5_f32.powi(level.into()); } - if let Some(level) = skills.get(&Hammer(CDrain)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(CDrain)) { *energy_drain = (*energy_drain as f32 * 0.75_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Hammer(CSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(CSpeed)) { *speed *= 1.25_f32.powi(level.into()); } }, @@ -773,22 +771,22 @@ impl CharacterAbility { ref mut range, .. } => { - if let Some(level) = skills.get(&Hammer(LDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(LDamage)) { *base_damage = (*base_damage as f32 * 1.4_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Hammer(LKnockback)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(LKnockback)) { *knockback *= 1.5_f32.powi(level.into()); } - if let Some(level) = skills.get(&Hammer(LCost)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(LCost)) { *energy_cost = (*energy_cost as f32 * 0.75_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Hammer(LDistance)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(LDistance)) { *forward_leap_strength *= 1.25_f32.powi(level.into()); *vertical_leap_strength *= 1.25_f32.powi(level.into()); } - if let Some(level) = skills.get(&Hammer(LRange)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Hammer(LRange)) { *range += 1.0 * level as f32; } }, @@ -803,12 +801,17 @@ impl CharacterAbility { ref mut projectile_speed, .. } => { - if let Some(level) = skills.get(&Bow(ProjSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(ProjSpeed)) { *projectile_speed *= 1.5_f32.powi(level.into()); } - let damage_level = - skills.get(&Bow(BDamage)).copied().flatten().unwrap_or(0); - let regen_level = skills.get(&Bow(BRegen)).copied().flatten().unwrap_or(0); + let damage_level = skillset + .skill_level(Bow(BDamage)) + .unwrap_or(None) + .unwrap_or(0); + let regen_level = skillset + .skill_level(Bow(BRegen)) + .unwrap_or(None) + .unwrap_or(0); let power = 1.3_f32.powi(damage_level.into()); let regen = 1.5_f32.powi(regen_level.into()); *projectile = projectile.modified_projectile(power, regen, 1_f32, 1_f32); @@ -823,27 +826,27 @@ impl CharacterAbility { ref mut move_speed, .. } => { - if let Some(level) = skills.get(&Bow(ProjSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(ProjSpeed)) { *initial_projectile_speed *= 1.5_f32.powi(level.into()); } - if let Some(level) = skills.get(&Bow(CDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(CDamage)) { *scaled_damage = (*scaled_damage as f32 * 1.25_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Bow(CKnockback)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(CKnockback)) { *scaled_knockback *= 1.5_f32.powi(level.into()); } - if let Some(level) = skills.get(&Bow(CProjSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(CProjSpeed)) { *scaled_projectile_speed *= 1.2_f32.powi(level.into()); } - if let Some(level) = skills.get(&Bow(CDrain)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(CDrain)) { *energy_drain = (*energy_drain as f32 * 0.75_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Bow(CSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(CSpeed)) { *speed *= 1.25_f32.powi(level.into()); } - if let Some(level) = skills.get(&Bow(CMove)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(CMove)) { *move_speed *= 1.25_f32.powi(level.into()); } }, @@ -855,21 +858,21 @@ impl CharacterAbility { ref mut projectile_speed, .. } => { - if let Some(level) = skills.get(&Bow(ProjSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(ProjSpeed)) { *projectile_speed *= 1.5_f32.powi(level.into()); } - if let Some(level) = skills.get(&Bow(RDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(RDamage)) { let power = 1.3_f32.powi(level.into()); *projectile = projectile.modified_projectile(power, 1_f32, 1_f32, 1_f32); } - if !skills.contains_key(&Bow(RGlide)) { + if !skillset.has_skill(Bow(RGlide)) { *buildup_duration = 1; } - if let Some(level) = skills.get(&Bow(RArrows)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(RArrows)) { *reps_remaining += level as u32; } - if let Some(level) = skills.get(&Bow(RCost)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Bow(RCost)) { *energy_cost = (*energy_cost as f32 * 0.75_f32.powi(level.into())) as u32; } @@ -883,15 +886,21 @@ impl CharacterAbility { BasicRanged { ref mut projectile, .. } => { - if !skills.contains_key(&Staff(BExplosion)) { + if !skillset.has_skill(Staff(BExplosion)) { *projectile = projectile.fireball_to_firebolt(); } - let damage_level = - skills.get(&Staff(BDamage)).copied().flatten().unwrap_or(0); - let regen_level = - skills.get(&Staff(BRegen)).copied().flatten().unwrap_or(0); - let range_level = - skills.get(&Staff(BRadius)).copied().flatten().unwrap_or(0); + let damage_level = skillset + .skill_level(Staff(BDamage)) + .unwrap_or(None) + .unwrap_or(0); + let regen_level = skillset + .skill_level(Staff(BRegen)) + .unwrap_or(None) + .unwrap_or(0); + let range_level = skillset + .skill_level(Staff(BRadius)) + .unwrap_or(None) + .unwrap_or(0); let power = 1.2_f32.powi(damage_level.into()); let regen = 1.2_f32.powi(regen_level.into()); let range = 1.1_f32.powi(range_level.into()); @@ -904,20 +913,20 @@ impl CharacterAbility { ref mut beam_duration, .. } => { - if let Some(level) = skills.get(&Staff(FDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Staff(FDamage)) { *base_dps = (*base_dps as f32 * 1.3_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Staff(FRange)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Staff(FRange)) { let range_mod = 1.25_f32.powi(level.into()); *range *= range_mod; // Duration modified to keep velocity constant *beam_duration = (*beam_duration as f32 * range_mod) as u64; } - if let Some(level) = skills.get(&Staff(FDrain)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Staff(FDrain)) { *energy_drain = (*energy_drain as f32 * 0.8_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Staff(FVelocity)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Staff(FVelocity)) { let velocity_increase = 1.25_f32.powi(level.into()); let duration_mod = 1.0 / (1.0 + velocity_increase); *beam_duration = (*beam_duration as f32 * duration_mod) as u64; @@ -930,17 +939,17 @@ impl CharacterAbility { ref mut energy_cost, .. } => { - if let Some(level) = skills.get(&Staff(SDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Staff(SDamage)) { *damage = (*damage as f32 * 1.3_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Staff(SKnockback)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Staff(SKnockback)) { *knockback = knockback.modify_strength(1.3_f32.powi(level.into())); } - if let Some(level) = skills.get(&Staff(SRange)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Staff(SRange)) { *shockwave_duration = (*shockwave_duration as f32 * 1.2_f32.powi(level.into())) as u64; } - if let Some(level) = skills.get(&Staff(SCost)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Staff(SCost)) { *energy_cost = (*energy_cost as f32 * 0.8_f32.powi(level.into())) as u32; } @@ -961,26 +970,26 @@ impl CharacterAbility { ref mut beam_duration, .. } => { - if let Some(level) = skills.get(&Sceptre(BHeal)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(BHeal)) { *base_hps = (*base_hps as f32 * 1.2_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Sceptre(BDamage)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(BDamage)) { *base_dps = (*base_dps as f32 * 1.3_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Sceptre(BRange)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(BRange)) { let range_mod = 1.25_f32.powi(level.into()); *range *= range_mod; // Duration modified to keep velocity constant *beam_duration = (*beam_duration as f32 * range_mod) as u64; } - if let Some(level) = skills.get(&Sceptre(BLifesteal)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(BLifesteal)) { *lifesteal_eff *= 1.5_f32.powi(level.into()); } - if let Some(level) = skills.get(&Sceptre(BRegen)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(BRegen)) { *energy_regen = (*energy_regen as f32 * 1.1_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Sceptre(BCost)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(BCost)) { *energy_cost = (*energy_cost as f32 * 0.9_f32.powi(level.into())) as u32; } @@ -992,28 +1001,28 @@ impl CharacterAbility { .. } => { { - let heal_level = - skills.get(&Sceptre(PHeal)).copied().flatten().unwrap_or(0); - let damage_level = skills - .get(&Sceptre(PDamage)) - .copied() - .flatten() + let heal_level = skillset + .skill_level(Sceptre(PHeal)) + .unwrap_or(None) .unwrap_or(0); - let range_level = skills - .get(&Sceptre(PRadius)) - .copied() - .flatten() + let damage_level = skillset + .skill_level(Sceptre(PDamage)) + .unwrap_or(None) + .unwrap_or(0); + let range_level = skillset + .skill_level(Sceptre(PRadius)) + .unwrap_or(None) .unwrap_or(0); let heal = 1.2_f32.powi(heal_level.into()); let power = 1.2_f32.powi(damage_level.into()); let range = 1.4_f32.powi(range_level.into()); *projectile = projectile.modified_projectile(power, 1_f32, range, heal); } - if let Some(level) = skills.get(&Sceptre(PCost)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(PCost)) { *energy_cost = (*energy_cost as f32 * 0.8_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Sceptre(PProjSpeed)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Sceptre(PProjSpeed)) { *projectile_speed *= 1.25_f32.powi(level.into()); } }, @@ -1030,14 +1039,14 @@ impl CharacterAbility { .. } = self { - *immune_melee = skills.contains_key(&Skill::Roll(ImmuneMelee)); - if let Some(level) = skills.get(&Skill::Roll(Cost)).copied().flatten() { + *immune_melee = skillset.has_skill(Skill::Roll(ImmuneMelee)); + if let Ok(Some(level)) = skillset.skill_level(Skill::Roll(Cost)) { *energy_cost = (*energy_cost as f32 * 0.8_f32.powi(level.into())) as u32; } - if let Some(level) = skills.get(&Skill::Roll(Strength)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Skill::Roll(Strength)) { *roll_strength *= 1.2_f32.powi(level.into()); } - if let Some(level) = skills.get(&Skill::Roll(Duration)).copied().flatten() { + if let Ok(Some(level)) = skillset.skill_level(Skill::Roll(Duration)) { *movement_duration = (*movement_duration as f32 * 1.2_f32.powi(level.into())) as u64; } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index adc2bdcf4d..b757a36007 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -428,7 +428,7 @@ impl Body { /// Returns a multiplier representing increased difficulty not accounted for /// due to AI or not using an actual weapon // TODO: Match on species - pub fn combat_multiplier(&self) -> f32 { 1.0 } + pub fn combat_multiplier(&self) -> f32 { if let Body::Object(_) = self { 0.0 } else { 1.0 } } #[allow(unreachable_patterns)] pub fn base_exp(&self) -> u32 { diff --git a/common/src/comp/skills.rs b/common/src/comp/skills.rs index e3729d96f8..0ad4d74100 100644 --- a/common/src/comp/skills.rs +++ b/common/src/comp/skills.rs @@ -18,7 +18,7 @@ impl Asset for SkillTreeMap { } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SkillLevelMap(HashMap); +pub struct SkillLevelMap(HashMap>); impl Asset for SkillLevelMap { type Loader = assets::RonLoader; @@ -27,7 +27,7 @@ impl Asset for SkillLevelMap { } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SkillPrerequisitesMap(HashMap>); +pub struct SkillPrerequisitesMap(HashMap>>); impl Asset for SkillPrerequisitesMap { type Loader = assets::RonLoader; @@ -45,13 +45,13 @@ lazy_static! { ).0 }; // Loads the maximum level that a skill can obtain - pub static ref SKILL_MAX_LEVEL: HashMap = { + pub static ref SKILL_MAX_LEVEL: HashMap> = { SkillLevelMap::load_expect_cloned( "common.skill_trees.skill_max_levels", ).0 }; // Loads the prerequisite skills for a particular skill - pub static ref SKILL_PREREQUISITES: HashMap> = { + pub static ref SKILL_PREREQUISITES: HashMap>> = { SkillPrerequisitesMap::load_expect_cloned( "common.skill_trees.skill_prerequisites", ).0 @@ -75,6 +75,10 @@ pub enum Skill { Roll(RollSkill), } +pub enum SkillError { + MissingSkill, +} + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum SwordSkill { // Sword passives @@ -225,33 +229,38 @@ impl SkillGroupType { #[allow(clippy::many_single_char_names)] pub fn skill_point_cost(self, level: u16) -> u16 { let exp_increment = 10.0; - let starting_exp = 150.0; + let starting_exp = 100.0; let exp_ceiling = 1000.0; let scaling_factor = 0.1; - let a = exp_increment; - let b = (exp_ceiling - starting_exp) / ((1.0 + std::f32::consts::PI / 2.0) * exp_increment); - let c = scaling_factor; - let d = (-1.0_f32).tan(); - let e = starting_exp / exp_increment + b; - (a * (b * (c * level as f32 + d).atan() + e).floor()) as u16 + (exp_increment + * (exp_ceiling + / exp_increment + / (1.0 + + std::f32::consts::E.powf(-scaling_factor * level as f32) + * (exp_ceiling / starting_exp - 1.0))) + .floor()) as u16 } /// Gets the total amount of skill points that can be spent in a particular /// skill group - pub fn get_max_skill_points(self) -> u16 { - let mut cost = 0; + pub fn max_skill_points(self) -> u16 { if let Some(skill_list) = SKILL_GROUP_DEFS.get(&self) { - for skill in skill_list { - if let Some(max_level) = skill.get_max_level() { - for level in 1..=max_level { - cost += skill.skill_cost(Some(level)); + skill_list + .iter() + .map(|skill| { + if let Some(max_level) = skill.max_level() { + (1..=max_level) + .into_iter() + .map(|level| skill.skill_cost(Some(level))) + .sum() + } else { + skill.skill_cost(None) } - } else { - cost += skill.skill_cost(None); - } - } + }) + .sum() + } else { + 0 } - cost } } @@ -283,13 +292,11 @@ impl SkillGroup { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct SkillSet { pub skill_groups: Vec, - pub skills: HashMap, + pub skills: HashMap>, pub modify_health: bool, pub modify_energy: bool, } -pub type Level = Option; - impl Default for SkillSet { /// Instantiate a new skill set with the default skill groups with no /// unlocked skills in them - used when adding a skill set to a new @@ -344,14 +351,10 @@ impl SkillSet { /// assert_eq!(skillset.skills.len(), 1); /// ``` pub fn unlock_skill(&mut self, skill: Skill) { - if let Some(skill_group_type) = skill.get_skill_group_type() { - let next_level = if self.skills.contains_key(&skill) { - self.skills.get(&skill).copied().flatten().map(|l| l + 1) - } else { - skill.get_max_level().map(|_| 1) - }; + if let Some(skill_group_type) = skill.skill_group_type() { + let next_level = self.next_skill_level(skill); let prerequisites_met = self.prerequisites_met(skill); - if !matches!(self.skills.get(&skill), Some(level) if *level == skill.get_max_level()) { + if !matches!(self.skills.get(&skill), Some(level) if *level == skill.max_level()) { if let Some(mut skill_group) = self .skill_groups .iter_mut() @@ -406,20 +409,21 @@ impl SkillSet { /// assert_eq!(skillset.skills.len(), 0); /// ``` pub fn refund_skill(&mut self, skill: Skill) { - if self.skills.contains_key(&skill) { - if let Some(skill_group_type) = skill.get_skill_group_type() { + if self.has_skill(skill) { + if let Some(skill_group_type) = skill.skill_group_type() { if let Some(mut skill_group) = self .skill_groups .iter_mut() .find(|x| x.skill_group_type == skill_group_type) { - // We know key is already contained, so unwrap is safe - let level = *(self.skills.get(&skill).unwrap()); - skill_group.available_sp += skill.skill_cost(level); - if level.map_or(false, |l| l > 1) { - self.skills.insert(skill, level.map(|l| l - 1)); - } else { - self.skills.remove(&skill); + let level = self.skills.get(&skill).copied(); + if let Some(level) = level { + skill_group.available_sp += skill.skill_cost(level); + if level.map_or(false, |l| l > 1) { + self.skills.insert(skill, level.map(|l| l - 1)); + } else { + self.skills.remove(&skill); + } } } else { warn!("Tried to refund skill for a skill group that player does not have"); @@ -468,9 +472,16 @@ impl SkillSet { /// Adds a skill point while subtracting the necessary amount of experience pub fn earn_skill_point(&mut self, skill_group_type: SkillGroupType) { - let sp_cost = self.get_skill_point_cost(skill_group_type) as i32; - self.change_experience(skill_group_type, -sp_cost); - self.add_skill_points(skill_group_type, 1); + let sp_cost = self.skill_point_cost(skill_group_type); + if let Some(mut skill_group) = self + .skill_groups + .iter_mut() + .find(|x| x.skill_group_type == skill_group_type) + { + skill_group.exp = skill_group.exp.saturating_sub(sp_cost); + skill_group.available_sp = skill_group.available_sp.saturating_add(1); + skill_group.earned_sp = skill_group.earned_sp.saturating_add(1); + } } /// Checks if the skill set of an entity contains a particular skill group @@ -498,62 +509,53 @@ impl SkillSet { /// Checks that the skill set contains all prerequisite skills for a /// particular skill pub fn prerequisites_met(&self, skill: Skill) -> bool { - let next_level = if self.skills.contains_key(&skill) { - self.skills.get(&skill).copied().flatten().map(|l| l + 1) - } else { - skill.get_max_level().map(|_| 1) - }; + let next_level = self.next_skill_level(skill); skill.prerequisite_skills(next_level).iter().all(|(s, l)| { - self.skills.contains_key(s) && self.skills.get(s).map_or(false, |l_b| l_b >= l) + self.skill_level(*s).map_or(false, |l_b| l_b >= *l) + /* self.has_skill(*s) && self.skills.get(s).map_or(false, |l_b| + * l_b >= l) */ }) } /// Gets the available points for a particular skill group - pub fn get_available_sp(&self, skill_group: SkillGroupType) -> u16 { - let mut skill_groups = self - .skill_groups + pub fn available_sp(&self, skill_group: SkillGroupType) -> u16 { + self.skill_groups .iter() - .filter(|s_g| s_g.skill_group_type == skill_group); - skill_groups.next().map_or(0, |s_g| s_g.available_sp) + .find(|s_g| s_g.skill_group_type == skill_group) + .map_or(0, |s_g| s_g.available_sp) } /// Gets the total earned points for a particular skill group - pub fn get_earned_sp(&self, skill_group: SkillGroupType) -> u16 { - let mut skill_groups = self - .skill_groups + pub fn earned_sp(&self, skill_group: SkillGroupType) -> u16 { + self.skill_groups .iter() - .filter(|s_g| s_g.skill_group_type == skill_group); - skill_groups.next().map_or(0, |s_g| s_g.earned_sp) + .find(|s_g| s_g.skill_group_type == skill_group) + .map_or(0, |s_g| s_g.earned_sp) } /// Gets the available experience for a particular skill group - pub fn get_experience(&self, skill_group: SkillGroupType) -> u16 { - let mut skill_groups = self - .skill_groups + pub fn experience(&self, skill_group: SkillGroupType) -> u16 { + self.skill_groups .iter() - .filter(|s_g| s_g.skill_group_type == skill_group); - skill_groups.next().map_or(0, |s_g| s_g.exp) + .find(|s_g| s_g.skill_group_type == skill_group) + .map_or(0, |s_g| s_g.exp) } /// Gets skill point cost to purchase skill of next level - pub fn skill_point_cost(&self, skill: Skill) -> u16 { - let next_level = if self.skills.contains_key(&skill) { - self.skills.get(&skill).copied().flatten().map(|l| l + 1) - } else { - skill.get_max_level().map(|_| 1) - }; + pub fn skill_cost(&self, skill: Skill) -> u16 { + let next_level = self.next_skill_level(skill); skill.skill_cost(next_level) } /// Checks if player has sufficient skill points to purchase a skill pub fn sufficient_skill_points(&self, skill: Skill) -> bool { - if let Some(skill_group_type) = skill.get_skill_group_type() { + if let Some(skill_group_type) = skill.skill_group_type() { if let Some(skill_group) = self .skill_groups .iter() .find(|x| x.skill_group_type == skill_group_type) { - let needed_sp = self.skill_point_cost(skill); + let needed_sp = self.skill_cost(skill); skill_group.available_sp >= needed_sp } else { false @@ -567,12 +569,12 @@ impl SkillSet { pub fn has_available_sp(&self) -> bool { self.skill_groups.iter().any(|sg| { sg.available_sp > 0 - && (sg.earned_sp - sg.available_sp) < sg.skill_group_type.get_max_skill_points() + && (sg.earned_sp - sg.available_sp) < sg.skill_group_type.max_skill_points() }) } /// Checks how much experience is needed for the next skill point in a tree - pub fn get_skill_point_cost(&self, skill_group: SkillGroupType) -> u16 { + pub fn skill_point_cost(&self, skill_group: SkillGroupType) -> u16 { if let Some(level) = self .skill_groups .iter() @@ -584,12 +586,42 @@ impl SkillSet { skill_group.skill_point_cost(0) } } + + /// Checks if the skill is at max level in a skill set + pub fn is_at_max_level(&self, skill: Skill) -> bool { + if let Ok(level) = self.skill_level(skill) { + level == skill.max_level() + } else { + false + } + } + + /// Checks if skill set contains a skill + pub fn has_skill(&self, skill: Skill) -> bool { self.skills.contains_key(&skill) } + + /// Returns the level of the skill + pub fn skill_level(&self, skill: Skill) -> Result, SkillError> { + if let Some(level) = self.skills.get(&skill).copied() { + Ok(level) + } else { + Err(SkillError::MissingSkill) + } + } + + /// Checks the next level of a skill + fn next_skill_level(&self, skill: Skill) -> Option { + if let Ok(level) = self.skill_level(skill) { + level.map(|l| l + 1) + } else { + skill.max_level().map(|_| 1) + } + } } impl Skill { /// Returns a vec of prerequisite skills (it should only be necessary to /// note direct prerequisites) - pub fn prerequisite_skills(self, level: Level) -> HashMap { + pub fn prerequisite_skills(self, level: Option) -> HashMap> { let mut prerequisites = HashMap::new(); if let Some(level) = level { if level > 1 { @@ -604,25 +636,21 @@ impl Skill { } /// Returns the cost in skill points of unlocking a particular skill - pub fn skill_cost(self, level: Level) -> u16 { + pub fn skill_cost(self, level: Option) -> u16 { // TODO: Better balance the costs later level.unwrap_or(1) } /// Returns the maximum level a skill can reach, returns None if the skill /// doesn't level - pub fn get_max_level(self) -> Option { SKILL_MAX_LEVEL.get(&self).copied().flatten() } + pub fn max_level(self) -> Option { SKILL_MAX_LEVEL.get(&self).copied().flatten() } /// Returns the skill group type for a skill from the static skill group /// definitions. - pub fn get_skill_group_type(self) -> Option { - SKILL_GROUP_DEFS.iter().find_map(|(key, val)| { - if val.contains(&self) { - Some(*key) - } else { - None - } - }) + pub fn skill_group_type(self) -> Option { + SKILL_GROUP_DEFS + .iter() + .find_map(|(key, val)| val.contains(&self).then_some(*key)) } } @@ -639,12 +667,7 @@ mod tests { assert_eq!(skillset.skill_groups[1].available_sp, 0); assert_eq!(skillset.skills.len(), 1); - assert_eq!( - skillset - .skills - .contains_key(&Skill::Axe(AxeSkill::UnlockLeap)), - true - ); + assert_eq!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap)), true); skillset.refund_skill(Skill::Axe(AxeSkill::UnlockLeap)); @@ -679,12 +702,7 @@ mod tests { assert_eq!(skillset.skill_groups[1].available_sp, 0); assert_eq!(skillset.skills.len(), 1); - assert_eq!( - skillset - .skills - .contains_key(&Skill::Axe(AxeSkill::UnlockLeap)), - true - ); + assert_eq!(skillset.has_skill(Skill::Axe(AxeSkill::UnlockLeap)), true); // Try unlocking a skill without enough skill points skillset.unlock_skill(Skill::Axe(AxeSkill::DsCombo)); diff --git a/common/src/outcome.rs b/common/src/outcome.rs index a21ea918c6..653501ce93 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -34,6 +34,7 @@ pub enum Outcome { uid: Uid, skill_tree: comp::skills::SkillGroupType, total_points: u16, + // TODO: Access ECS to get position from Uid to conserve bandwidth pos: Vec3, }, } diff --git a/common/src/skillset_builder.rs b/common/src/skillset_builder.rs index 456eb87f50..75aec005ab 100644 --- a/common/src/skillset_builder.rs +++ b/common/src/skillset_builder.rs @@ -595,11 +595,11 @@ impl SkillSetBuilder { } pub fn with_skill(mut self, skill: Skill) -> Self { - if let Some(skill_group) = skill.get_skill_group_type() { + if let Some(skill_group) = skill.skill_group_type() { self.0 - .add_skill_points(skill_group, self.0.skill_point_cost(skill)); + .add_skill_points(skill_group, self.0.skill_cost(skill)); self.0.unlock_skill(skill); - if !self.0.skills.contains_key(&skill) { + if !self.0.has_skill(skill) { warn!( "Failed to add skill: {:?}. Verify that it has the appropriate skill group \ available and meets all prerequisite skills.", diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 43c57c151c..cc3b18b314 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -394,8 +394,7 @@ pub fn handle_ability1_input(data: &JoinData, update: &mut StateUpdate) { ItemKind::Tool(tool) => Some(tool.kind), _ => None, }; - a.clone() - .adjusted_by_skills(&data.stats.skill_set.skills, tool) + a.clone().adjusted_by_skills(&data.stats.skill_set, tool) }) }) .filter(|ability| ability.requirements_paid(data, update)) @@ -439,8 +438,7 @@ pub fn handle_ability2_input(data: &JoinData, update: &mut StateUpdate) { ItemKind::Tool(tool) => Some(tool.kind), _ => None, }; - a.clone() - .adjusted_by_skills(&data.stats.skill_set.skills, tool) + a.clone().adjusted_by_skills(&data.stats.skill_set, tool) }) }) .filter(|ability| ability.requirements_paid(data, update)) @@ -458,8 +456,7 @@ pub fn handle_ability2_input(data: &JoinData, update: &mut StateUpdate) { ItemKind::Tool(tool) => Some(tool.kind), _ => None, }; - a.clone() - .adjusted_by_skills(&data.stats.skill_set.skills, tool) + a.clone().adjusted_by_skills(&data.stats.skill_set, tool) }) }) .filter(|ability| ability.requirements_paid(data, update)) @@ -486,12 +483,12 @@ pub fn handle_ability3_input(data: &JoinData, update: &mut StateUpdate) { .ability3 .as_ref() .and_then(|s| match tool { + // TODO: Make this so abilities aren't hardcoded to ability3 Some(ToolKind::Sword) if !&data .stats .skill_set - .skills - .contains_key(&Skill::Sword(SwordSkill::UnlockSpin)) => + .has_skill(Skill::Sword(SwordSkill::UnlockSpin)) => { None }, @@ -499,8 +496,7 @@ pub fn handle_ability3_input(data: &JoinData, update: &mut StateUpdate) { if !&data .stats .skill_set - .skills - .contains_key(&Skill::Axe(AxeSkill::UnlockLeap)) => + .has_skill(Skill::Axe(AxeSkill::UnlockLeap)) => { None }, @@ -508,8 +504,7 @@ pub fn handle_ability3_input(data: &JoinData, update: &mut StateUpdate) { if !&data .stats .skill_set - .skills - .contains_key(&Skill::Hammer(HammerSkill::UnlockLeap)) => + .has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) => { None }, @@ -517,8 +512,7 @@ pub fn handle_ability3_input(data: &JoinData, update: &mut StateUpdate) { if !&data .stats .skill_set - .skills - .contains_key(&Skill::Bow(BowSkill::UnlockRepeater)) => + .has_skill(Skill::Bow(BowSkill::UnlockRepeater)) => { None }, @@ -526,17 +520,13 @@ pub fn handle_ability3_input(data: &JoinData, update: &mut StateUpdate) { if !&data .stats .skill_set - .skills - .contains_key(&Skill::Staff(StaffSkill::UnlockShockwave)) => + .has_skill(Skill::Staff(StaffSkill::UnlockShockwave)) => { None }, _ => Some(s), }) - .map(|a| { - a.clone() - .adjusted_by_skills(&data.stats.skill_set.skills, tool) - }) + .map(|a| a.clone().adjusted_by_skills(&data.stats.skill_set, tool)) }) .filter(|ability| ability.requirements_paid(data, update)) { @@ -553,10 +543,10 @@ pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) { .inventory .equipped(EquipSlot::Mainhand) .and_then(|i| { - i.item_config_expect().dodge_ability.as_ref().map(|a| { - a.clone() - .adjusted_by_skills(&data.stats.skill_set.skills, None) - }) + i.item_config_expect() + .dodge_ability + .as_ref() + .map(|a| a.clone().adjusted_by_skills(&data.stats.skill_set, None)) }) .filter(|ability| ability.requirements_paid(data, update)) { diff --git a/common/sys/src/agent.rs b/common/sys/src/agent.rs index 4eaf46eafd..e1aa73791c 100644 --- a/common/sys/src/agent.rs +++ b/common/sys/src/agent.rs @@ -583,7 +583,7 @@ impl<'a> System<'a> for Sys { } else if *powerup > 4.0 && energy.current() > 10 { inputs.secondary.set_state(true); *powerup += dt.0; - } else if stats.skill_set.skills.contains_key(&Skill::Axe(AxeSkill::UnlockLeap)) && energy.current() > 800 && thread_rng().gen_bool(0.5) { + } else if stats.skill_set.has_skill(Skill::Axe(AxeSkill::UnlockLeap)) && energy.current() > 800 && thread_rng().gen_bool(0.5) { inputs.ability3.set_state(true); *powerup += dt.0; } else { @@ -632,7 +632,7 @@ impl<'a> System<'a> for Sys { } else if *powerup > 2.0 { inputs.secondary.set_state(true); *powerup += dt.0; - } else if stats.skill_set.skills.contains_key(&Skill::Hammer(HammerSkill::UnlockLeap)) && energy.current() > 700 + } else if stats.skill_set.has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) && energy.current() > 700 && thread_rng().gen_bool(0.9) { inputs.ability3.set_state(true); *powerup += dt.0; @@ -662,7 +662,7 @@ impl<'a> System<'a> for Sys { .try_normalized() .unwrap_or(Vec2::zero()) * speed; - if stats.skill_set.skills.contains_key(&Skill::Hammer(HammerSkill::UnlockLeap)) && *powerup > 5.0 { + if stats.skill_set.has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) && *powerup > 5.0 { inputs.ability3.set_state(true); *powerup = 0.0; } else { @@ -690,7 +690,7 @@ impl<'a> System<'a> for Sys { Tactic::Sword => { if dist_sqrd < (MIN_ATTACK_DIST * scale).powi(2) { inputs.move_dir = Vec2::zero(); - if stats.skill_set.skills.contains_key(&Skill::Sword(SwordSkill::UnlockSpin)) && *powerup < 2.0 && energy.current() > 600 { + if stats.skill_set.has_skill(Skill::Sword(SwordSkill::UnlockSpin)) && *powerup < 2.0 && energy.current() > 600 { inputs.ability3.set_state(true); *powerup += dt.0; } else if *powerup > 2.0 { @@ -782,7 +782,7 @@ impl<'a> System<'a> for Sys { { inputs.secondary.set_state(true); *powerup += dt.0; - } else if stats.skill_set.skills.contains_key(&Skill::Bow(BowSkill::UnlockRepeater)) && energy.current() > 400 + } else if stats.skill_set.has_skill(Skill::Bow(BowSkill::UnlockRepeater)) && energy.current() > 400 && thread_rng().gen_bool(0.8) { inputs.secondary.set_state(false); @@ -835,7 +835,7 @@ impl<'a> System<'a> for Sys { } else { *powerup = 0.0; } - if stats.skill_set.skills.contains_key(&Skill::Staff(StaffSkill::UnlockShockwave)) && energy.current() > 800 + if stats.skill_set.has_skill(Skill::Staff(StaffSkill::UnlockShockwave)) && energy.current() > 800 && thread_rng().gen::() > 0.8 { inputs.ability3.set_state(true); diff --git a/common/sys/src/stats.rs b/common/sys/src/stats.rs index caabaced2a..8a698ca5e1 100644 --- a/common/sys/src/stats.rs +++ b/common/sys/src/stats.rs @@ -1,6 +1,6 @@ use common::{ comp::{ - skills::{GeneralSkill, Skill, SkillGroupType}, + skills::{GeneralSkill, Skill}, CharacterState, Energy, EnergyChange, EnergySource, Health, Pos, Stats, }, event::{EventBus, ServerEvent}, @@ -85,28 +85,25 @@ impl<'a> System<'a> for Sys { health.is_dead = true; } - let mut skills_to_level = HashSet::::new(); let stat = stats.get_unchecked(); - { - for skill_group in stat.skill_set.skill_groups.iter() { - if skill_group.exp - >= stat - .skill_set - .get_skill_point_cost(skill_group.skill_group_type) - { - skills_to_level.insert(skill_group.skill_group_type); - } - } - } + let skills_to_level = stat + .skill_set + .skill_groups + .iter() + .filter_map(|s_g| { + (s_g.exp >= stat.skill_set.skill_point_cost(s_g.skill_group_type)) + .then(|| s_g.skill_group_type) + }) + .collect::>(); if !skills_to_level.is_empty() { let mut stat = stats.get_mut_unchecked(); - for skill_group in skills_to_level.drain() { + for skill_group in skills_to_level { stat.skill_set.earn_skill_point(skill_group); outcomes.push(Outcome::SkillPointGain { uid: *uid, skill_tree: skill_group, - total_points: stat.skill_set.get_earned_sp(skill_group), + total_points: stat.skill_set.earned_sp(skill_group), pos: pos.0, }); } @@ -126,10 +123,8 @@ impl<'a> System<'a> for Sys { let mut health = health.get_mut_unchecked(); let health_level = stat .skill_set - .skills - .get(&Skill::General(GeneralSkill::HealthIncrease)) - .copied() - .flatten() + .skill_level(Skill::General(GeneralSkill::HealthIncrease)) + .unwrap_or(None) .unwrap_or(0); health.update_max_hp(Some(stat.body_type), health_level); let mut stat = stats.get_mut_unchecked(); @@ -140,10 +135,8 @@ impl<'a> System<'a> for Sys { let mut energy = energy.get_mut_unchecked(); let energy_level = stat .skill_set - .skills - .get(&Skill::General(GeneralSkill::EnergyIncrease)) - .copied() - .flatten() + .skill_level(Skill::General(GeneralSkill::EnergyIncrease)) + .unwrap_or(None) .unwrap_or(0); energy.update_max_energy(Some(stat.body_type), energy_level); let mut stat = stats.get_mut_unchecked(); diff --git a/server/Cargo.toml b/server/Cargo.toml index 99d41b2921..31af7389b8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -49,7 +49,6 @@ diesel_migrations = "1.4.0" dotenv = "0.15.0" slab = "0.4" const-tweaker = {version = "0.3.1", optional = true} -inline_tweak = "1.0.2" # Plugins plugin-api = { package = "veloren-plugin-api", path = "../plugin/api"} diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 969405ba6f..4791124e6f 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -29,7 +29,6 @@ use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; use common_sys::state::BlockChange; use comp::item::Reagent; use hashbrown::HashSet; -use inline_tweak::*; use rand::prelude::*; use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt}; use tracing::error; @@ -195,25 +194,24 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc // Maximum distance for other group members to receive exp const MAX_EXP_DIST: f32 = 150.0; - // Attacker gets same as exp of everyone else - const ATTACKER_EXP_WEIGHT: f32 = 1.0; // TODO: Scale xp from skillset rather than health, when NPCs have their own // skillsets /*let mut exp_reward = entity_stats.body_type.base_exp() as f32 * (entity_health.maximum() as f32 / entity_stats.body_type.base_health() as * f32); */ let mut exp_reward = - combat::combat_rating(entity_inventory, entity_health, &entity_stats.body_type) - * tweak!(2.5); + combat::combat_rating(entity_inventory, entity_health, entity_stats) * 2.5; // Distribute EXP to group let positions = state.ecs().read_storage::(); let alignments = state.ecs().read_storage::(); let uids = state.ecs().read_storage::(); + let mut outcomes = state.ecs().write_resource::>(); + let inventories = state.ecs().read_storage::(); if let (Some(attacker_group), Some(pos)) = (attacker_group, positions.get(entity)) { // TODO: rework if change to groups makes it easier to iterate entities in a // group - let mut num_not_pets_in_range = 0; + let mut non_pet_group_members_in_range = 1; let members_in_range = ( &state.ecs().entities(), &groups, @@ -230,100 +228,36 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc }) .map(|(entity, _, _, alignment, uid)| { if !matches!(alignment, Some(Alignment::Owned(owner)) if owner != uid) { - num_not_pets_in_range += 1; + non_pet_group_members_in_range += 1; } (entity, uid) }) .collect::>(); - let exp = exp_reward / (num_not_pets_in_range as f32 + ATTACKER_EXP_WEIGHT); - exp_reward = exp * ATTACKER_EXP_WEIGHT; + // Devides exp reward by square root of number of people in group + exp_reward /= (non_pet_group_members_in_range as f32).sqrt(); members_in_range.into_iter().for_each(|(e, uid)| { - let (main_tool_kind, second_tool_kind) = - if let Some(inventory) = state.ecs().read_storage::().get(e) { - combat::get_weapons(inventory) - } else { - (None, None) - }; - if let Some(mut stats) = stats.get_mut(e) { - let mut xp_pools = HashSet::::new(); - xp_pools.insert(SkillGroupType::General); - if let Some(w) = main_tool_kind { - if stats - .skill_set - .contains_skill_group(SkillGroupType::Weapon(w)) - { - xp_pools.insert(SkillGroupType::Weapon(w)); - } - } - if let Some(w) = second_tool_kind { - if stats - .skill_set - .contains_skill_group(SkillGroupType::Weapon(w)) - { - xp_pools.insert(SkillGroupType::Weapon(w)); - } - } - let num_pools = xp_pools.len() as f32; - for pool in xp_pools.drain() { - stats - .skill_set - .change_experience(pool, (exp / num_pools).ceil() as i32); - } - state - .ecs() - .write_resource::>() - .push(Outcome::ExpChange { - uid: *uid, - exp: exp as i32, - }); + if let (Some(inventory), Some(mut stats)) = (inventories.get(e), stats.get_mut(e)) { + handle_exp_gain(exp_reward, inventory, &mut stats, uid, &mut outcomes); } }); } - let (main_tool_kind, second_tool_kind) = - if let Some(inventory) = state.ecs().read_storage::().get(attacker) { - combat::get_weapons(inventory) - } else { - (None, None) - }; - if let (Some(mut attacker_stats), Some(attacker_uid)) = - (stats.get_mut(attacker), uids.get(attacker)) - { + if let (Some(mut attacker_stats), Some(attacker_uid), Some(attacker_inventory)) = ( + stats.get_mut(attacker), + uids.get(attacker), + inventories.get(attacker), + ) { // TODO: Discuss whether we should give EXP by Player // Killing or not. // attacker_stats.exp.change_by(exp_reward.ceil() as i64); - let mut xp_pools = HashSet::::new(); - xp_pools.insert(SkillGroupType::General); - if let Some(w) = main_tool_kind { - if attacker_stats - .skill_set - .contains_skill_group(SkillGroupType::Weapon(w)) - { - xp_pools.insert(SkillGroupType::Weapon(w)); - } - } - if let Some(w) = second_tool_kind { - if attacker_stats - .skill_set - .contains_skill_group(SkillGroupType::Weapon(w)) - { - xp_pools.insert(SkillGroupType::Weapon(w)); - } - } - let num_pools = xp_pools.len() as f32; - for pool in xp_pools.drain() { - attacker_stats - .skill_set - .change_experience(pool, (exp_reward / num_pools).ceil() as i32); - } - state - .ecs() - .write_resource::>() - .push(Outcome::ExpChange { - uid: *attacker_uid, - exp: exp_reward as i32, - }); + handle_exp_gain( + exp_reward, + attacker_inventory, + &mut attacker_stats, + attacker_uid, + &mut outcomes, + ); } })(); @@ -853,3 +787,41 @@ pub fn handle_energy_change(server: &Server, entity: EcsEntity, change: EnergyCh energy.change_by(change); } } + +fn handle_exp_gain( + exp_reward: f32, + inventory: &Inventory, + stats: &mut Stats, + uid: &Uid, + outcomes: &mut Vec, +) { + let (main_tool_kind, second_tool_kind) = combat::get_weapons(inventory); + let mut xp_pools = HashSet::::new(); + xp_pools.insert(SkillGroupType::General); + if let Some(w) = main_tool_kind { + if stats + .skill_set + .contains_skill_group(SkillGroupType::Weapon(w)) + { + xp_pools.insert(SkillGroupType::Weapon(w)); + } + } + if let Some(w) = second_tool_kind { + if stats + .skill_set + .contains_skill_group(SkillGroupType::Weapon(w)) + { + xp_pools.insert(SkillGroupType::Weapon(w)); + } + } + let num_pools = xp_pools.len() as f32; + for pool in xp_pools { + stats + .skill_set + .change_experience(pool, (exp_reward / num_pools).ceil() as i32); + } + outcomes.push(Outcome::ExpChange { + uid: *uid, + exp: exp_reward as i32, + }); +} diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index e25fd16a74..b4f2e38ad1 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -379,7 +379,7 @@ fn convert_skill_groups_from_database(skill_groups: &[SkillGroup]) -> Vec HashMap { +fn convert_skills_from_database(skills: &[Skill]) -> HashMap> { let mut new_skills = HashMap::new(); for skill in skills.iter() { let new_skill = json_models::db_string_to_skill(&skill.skill_type); @@ -392,7 +392,7 @@ pub fn convert_skill_groups_to_database( entity_id: CharacterId, skill_groups: Vec, ) -> Vec { - let db_skill_groups: Vec<_> = skill_groups + skill_groups .into_iter() .map(|sg| SkillGroup { entity_id, @@ -401,21 +401,19 @@ pub fn convert_skill_groups_to_database( available_sp: sg.available_sp as i32, earned_sp: sg.earned_sp as i32, }) - .collect(); - db_skill_groups + .collect() } pub fn convert_skills_to_database( entity_id: CharacterId, - skills: HashMap, + skills: HashMap>, ) -> Vec { - let db_skills: Vec<_> = skills + skills .iter() .map(|(s, l)| Skill { entity_id, skill_type: json_models::skill_to_db_string(*s), level: l.map(|l| l as i32), }) - .collect(); - db_skills + .collect() } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 5f624eb527..6031bb07d6 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -139,10 +139,17 @@ impl StateExt for State { }) .with(comp::Controller::default()) .with(body) + .with(comp::Energy::new( + body, + stats + .skill_set + .skill_level(Skill::General(GeneralSkill::EnergyIncrease)) + .unwrap_or(None) + .unwrap_or(0), + )) .with(stats) .with(health) .with(comp::Alignment::Npc) - .with(comp::Energy::new(body, 0)) .with(comp::Gravity(1.0)) .with(comp::CharacterState::default()) .with(inventory) @@ -274,17 +281,13 @@ impl StateExt for State { let (health_level, energy_level) = ( stats .skill_set - .skills - .get(&Skill::General(GeneralSkill::HealthIncrease)) - .copied() - .flatten() + .skill_level(Skill::General(GeneralSkill::HealthIncrease)) + .unwrap_or(None) .unwrap_or(0), stats .skill_set - .skills - .get(&Skill::General(GeneralSkill::EnergyIncrease)) - .copied() - .flatten() + .skill_level(Skill::General(GeneralSkill::EnergyIncrease)) + .unwrap_or(None) .unwrap_or(0), ); self.write_component(entity, comp::Health::new(stats.body_type, health_level)); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index f69534eee0..1f33c7f64a 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -125,8 +125,6 @@ impl<'a> System<'a> for Sys { let alignment = entity.alignment; let main_tool = entity.main_tool; let mut stats = comp::Stats::new(name, body); - // let damage = stats.level.level() as i32; TODO: Make NPC base damage - // non-linearly depend on their level let mut scale = entity.scale; diff --git a/voxygen/anim/src/dyn_lib.rs b/voxygen/anim/src/dyn_lib.rs index 7f817f625f..798df8bfbc 100644 --- a/voxygen/anim/src/dyn_lib.rs +++ b/voxygen/anim/src/dyn_lib.rs @@ -9,6 +9,7 @@ use std::{ }; use find_folder::Search; +use hashbrown::HashSet; use std::{env, path::PathBuf}; use tracing::{debug, error, info}; @@ -147,7 +148,7 @@ pub fn init() { // "Debounces" events since I can't find the option to do this in the latest // `notify` thread::spawn(move || { - let mut modified_paths = std::collections::HashSet::new(); + let mut modified_paths = HashSet::new(); while let Ok(path) = reload_recv.recv() { modified_paths.insert(path); diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 38280e3d30..d6e5ceb130 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -358,7 +358,7 @@ impl SfxMgr { audio.play_sfx(file_ref, *pos, None); } }, - _ => {}, + Outcome::ExpChange { .. } => {}, } } diff --git a/voxygen/src/hud/bag.rs b/voxygen/src/hud/bag.rs index 6ad564e6b0..81d1f3da21 100644 --- a/voxygen/src/hud/bag.rs +++ b/voxygen/src/hud/bag.rs @@ -28,7 +28,6 @@ use conrod_core::{ }; use crate::hud::slots::SlotKind; -use inline_tweak::*; use vek::Vec2; widget_ids! { @@ -430,16 +429,15 @@ impl<'a> Widget for Bag<'a> { .resize(STATS.len(), &mut ui.widget_id_generator()) }); // Thresholds (lower) - let common = tweak!(4.3); - let moderate = tweak!(6.0); - let high = tweak!(8.0); - let epic = tweak!(10.0); - let legendary = tweak!(79.0); - let artifact = tweak!(122.0); - let debug = tweak!(200.0); + let common = 4.3; + let moderate = 6.0; + let high = 8.0; + let epic = 10.0; + let legendary = 79.0; + let artifact = 122.0; + let debug = 200.0; // Stats - let combat_rating = - combat_rating(inventory, self.health, &self.stats.body_type).min(999.9); + let combat_rating = combat_rating(inventory, self.health, self.stats).min(999.9); let indicator_col = match combat_rating { x if (0.0..common).contains(&x) => QUALITY_LOW, x if (common..moderate).contains(&x) => QUALITY_COMMON, @@ -474,9 +472,9 @@ impl<'a> Widget for Bag<'a> { let combat_rating_txt = format!("{}", (combat_rating * 10.0) as usize); let btn = if i.0 == 0 { - btn.top_left_with_margins_on(state.ids.bg_frame, tweak!(55.0), tweak!(10.0)) + btn.top_left_with_margins_on(state.ids.bg_frame, 55.0, 10.0) } else { - btn.down_from(state.ids.stat_icons[i.0 - 1], tweak!(7.0)) + btn.down_from(state.ids.stat_icons[i.0 - 1], 7.0) }; // TODO: Translation let tooltip_head = match i.1 { @@ -507,9 +505,9 @@ impl<'a> Widget for Bag<'a> { "Protection" => &protection_txt, _ => "", }) - .right_from(state.ids.stat_icons[i.0], tweak!(10.0)) + .right_from(state.ids.stat_icons[i.0], 10.0) .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(tweak!(14))) + .font_size(self.fonts.cyri.scale(14)) .color(TEXT_COLOR) .graphics_for(state.ids.stat_icons[i.0]) .set(state.ids.stat_txts[i.0], ui); diff --git a/voxygen/src/hud/buttons.rs b/voxygen/src/hud/buttons.rs index 2fa94cfd9f..efe821615a 100644 --- a/voxygen/src/hud/buttons.rs +++ b/voxygen/src/hud/buttons.rs @@ -14,7 +14,6 @@ use conrod_core::{ widget::{self, Button, Image, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; -use inline_tweak::*; widget_ids! { struct Ids { bag, @@ -128,8 +127,7 @@ impl<'a> Widget for Buttons<'a> { None => return None, }; let localized_strings = self.localized_strings; - let arrow_ani = - (self.pulse * tweak!(4.0)/* speed factor */).cos() * tweak!(0.5) + tweak!(0.8); //Animation timer + let arrow_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer let button_tooltip = Tooltip::new({ // Edge images [t, b, r, l] @@ -382,24 +380,21 @@ impl<'a> Widget for Buttons<'a> { Image::new(self.imgs.sp_indicator_arrow) .w_h(20.0, 11.0) .graphics_for(state.ids.spellbook_button) - .mid_top_with_margin_on( - state.ids.spellbook_button, - tweak!(-12.0) + arrow_ani as f64, - ) + .mid_top_with_margin_on(state.ids.spellbook_button, -12.0 + arrow_ani as f64) .color(Some(QUALITY_LEGENDARY)) .set(state.ids.sp_arrow, ui); Text::new(&localized_strings.get("hud.sp_arrow_txt")) - .mid_top_with_margin_on(state.ids.sp_arrow, tweak!(-18.0)) + .mid_top_with_margin_on(state.ids.sp_arrow, -18.0) .graphics_for(state.ids.spellbook_button) .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(tweak!(14))) + .font_size(self.fonts.cyri.scale(14)) .color(BLACK) .set(state.ids.sp_arrow_txt_bg, ui); Text::new(&localized_strings.get("hud.sp_arrow_txt")) .graphics_for(state.ids.spellbook_button) .bottom_right_with_margins_on(state.ids.sp_arrow_txt_bg, 1.0, 1.0) .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(tweak!(14))) + .font_size(self.fonts.cyri.scale(14)) .color(QUALITY_LEGENDARY) .set(state.ids.sp_arrow_txt, ui); } diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index 10de56df51..8b2f436264 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -20,7 +20,6 @@ use common::comp::{ skills::{self, Skill}, Stats, }; -use inline_tweak::*; widget_ids! { pub struct Ids { @@ -351,24 +350,23 @@ impl<'a> Widget for Diary<'a> { }); let img = if i.0 == 0 { - img.top_left_with_margins_on(state.content_align, tweak!(10.0), tweak!(5.0)) + img.top_left_with_margins_on(state.content_align, 10.0, 5.0) } else { - img.down_from(state.weapon_btns[i.0 - 1], tweak!(5.0)) + img.down_from(state.weapon_btns[i.0 - 1], 5.0) }; let tooltip_txt = if !locked { "" } else { &self.localized_strings.get("hud.skill.not_unlocked") }; - img.w_h(tweak!(50.0), tweak!(50.0)) - .set(state.weapon_imgs[i.0], ui); + img.w_h(50.0, 50.0).set(state.weapon_imgs[i.0], ui); // Lock Image if locked { Image::new(self.imgs.lock) .w_h(50.0, 50.0) .middle_of(state.weapon_imgs[i.0]) .graphics_for(state.weapon_imgs[i.0]) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, tweak!(0.8)))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.8))) .set(state.lock_imgs[i.0], ui); } // Weapon icons @@ -376,12 +374,12 @@ impl<'a> Widget for Diary<'a> { .map(|st| { ( st, - self.stats.skill_set.get_available_sp(st), - self.stats.skill_set.get_earned_sp(st), + self.stats.skill_set.available_sp(st), + self.stats.skill_set.earned_sp(st), ) }) .map_or(false, |(st, a_pts, e_pts)| { - a_pts > 0 && (e_pts - a_pts) < st.get_max_skill_points() + a_pts > 0 && (e_pts - a_pts) < st.max_skill_points() }); if Button::image( if skill_tree_from_str(i.1).map_or(false, |st| st == *sel_tab || available_pts) { @@ -390,7 +388,7 @@ impl<'a> Widget for Diary<'a> { self.imgs.wpn_icon_border }, ) - .w_h(tweak!(50.0), tweak!(50.0)) + .w_h(50.0, 50.0) .hover_image(match skill_tree_from_str(i.1).map(|st| st == *sel_tab) { Some(true) => self.imgs.wpn_icon_border_pressed, Some(false) => self.imgs.wpn_icon_border_mo, @@ -423,17 +421,17 @@ impl<'a> Widget for Diary<'a> { } } // Exp Bars and Rank Display - let current_exp = self.stats.skill_set.get_experience(*sel_tab) as f64; - let max_exp = self.stats.skill_set.get_skill_point_cost(*sel_tab) as f64; + let current_exp = self.stats.skill_set.experience(*sel_tab) as f64; + let max_exp = self.stats.skill_set.skill_point_cost(*sel_tab) as f64; let exp_percentage = current_exp / max_exp; - let rank = self.stats.skill_set.get_earned_sp(*sel_tab); + let rank = self.stats.skill_set.earned_sp(*sel_tab); let rank_txt = format!("{}", rank); let exp_txt = format!("{}/{}", current_exp, max_exp); - let available_pts = self.stats.skill_set.get_available_sp(*sel_tab); + let available_pts = self.stats.skill_set.available_sp(*sel_tab); let available_pts_txt = format!("{}", available_pts); Image::new(self.imgs.diary_exp_bg) .w_h(480.0, 76.0) - .mid_bottom_with_margin_on(state.content_align, tweak!(10.0)) + .mid_bottom_with_margin_on(state.content_align, 10.0) .set(state.exp_bar_bg, ui); Rectangle::fill_with([400.0, 40.0], color::TRANSPARENT) .top_left_with_margins_on(state.exp_bar_bg, 32.0, 40.0) @@ -455,32 +453,34 @@ impl<'a> Widget for Diary<'a> { .map_or(false, |m| m.is_over()); if self.hovering_exp_bar { Text::new(&exp_txt) - .mid_top_with_margin_on(state.exp_bar_frame, tweak!(47.0)) + .mid_top_with_margin_on(state.exp_bar_frame, 47.0) .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(tweak!(14))) + .font_size(self.fonts.cyri.scale(14)) .color(TEXT_COLOR) .graphics_for(state.exp_bar_frame) .set(state.exp_bar_txt, ui); } Text::new(&rank_txt) - .mid_top_with_margin_on(state.exp_bar_frame, tweak!(5.0)) + .mid_top_with_margin_on(state.exp_bar_frame, 5.0) .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(tweak!(28))) + .font_size(self.fonts.cyri.scale(28)) .color(TEXT_COLOR) .set(state.exp_bar_rank, ui); - if available_pts > 0 { - Text::new( - &self - .localized_strings - .get("hud.skill.sp_available") - .replace("{number}", &available_pts_txt), - ) - .mid_top_with_margin_on(state.content_align, tweak!(700.0)) - .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(tweak!(28))) - .color(Color::Rgba(0.92, 0.76, 0.0, frame_ani)) - .set(state.available_pts_txt, ui); - } + Text::new( + &self + .localized_strings + .get("hud.skill.sp_available") + .replace("{number}", &available_pts_txt), + ) + .mid_top_with_margin_on(state.content_align, 700.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(28)) + .color(if available_pts > 0 { + Color::Rgba(0.92, 0.76, 0.0, frame_ani) + } else { + TEXT_COLOR + }) + .set(state.available_pts_txt, ui); let tree_title = match sel_tab { SelectedSkillTree::General => "General Combat", SelectedSkillTree::Weapon(ToolKind::Sword) => "Sword", @@ -492,15 +492,15 @@ impl<'a> Widget for Diary<'a> { _ => "Unknown", }; Text::new(&tree_title) - .mid_top_with_margin_on(state.content_align, tweak!(2.0)) + .mid_top_with_margin_on(state.content_align, 2.0) .font_id(self.fonts.cyri.conrod_id) - .font_size(self.fonts.cyri.scale(tweak!(34))) + .font_size(self.fonts.cyri.scale(34)) .color(TEXT_COLOR) .set(state.tree_title_txt, ui); // Skill Trees // Alignment Placing - let x = tweak!(200.0); - let y = tweak!(100.0); + let x = 200.0; + let y = 100.0; // Alignment rectangles for skills Rectangle::fill_with([124.0 * 2.0, 124.0 * 2.0], color::TRANSPARENT) .top_left_with_margins_on(state.content_align, y, x) @@ -649,7 +649,7 @@ impl<'a> Widget for Diary<'a> { } // Skill-Icons and Functionality // Art dimensions - let art_size = [tweak!(320.0), tweak!(320.0)]; + let art_size = [320.0, 320.0]; match sel_tab { SelectedSkillTree::General => { use skills::{GeneralSkill::*, RollSkill::*, SkillGroupType::*}; @@ -661,7 +661,7 @@ impl<'a> Widget for Diary<'a> { ) .wh(art_size) .middle_of(state.content_align) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, tweak!(1.0)))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) .set(state.general_combat_render_0, ui); Image::new( self.item_imgs @@ -669,7 +669,7 @@ impl<'a> Widget for Diary<'a> { ) .wh(art_size) .middle_of(state.general_combat_render_0) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, tweak!(1.0)))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) .set(state.general_combat_render_1, ui); // Top Left skills // 5 1 6 @@ -999,7 +999,7 @@ impl<'a> Widget for Diary<'a> { ) .wh(art_size) .middle_of(state.content_align) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, tweak!(1.0)))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) .set(state.sword_render, ui); // Top Left skills // 5 1 6 @@ -1442,7 +1442,7 @@ impl<'a> Widget for Diary<'a> { ) .wh(art_size) .middle_of(state.content_align) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, tweak!(1.0)))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) .set(state.axe_render, ui); // Top Left skills // 5 1 6 @@ -1856,7 +1856,7 @@ impl<'a> Widget for Diary<'a> { ) .wh(art_size) .middle_of(state.content_align) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, tweak!(1.0)))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) .set(state.hammer_render, ui); // Top Left skills // 5 1 6 @@ -2692,7 +2692,7 @@ impl<'a> Widget for Diary<'a> { ) .wh(art_size) .middle_of(state.content_align) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, tweak!(1.0)))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) .set(state.staff_render, ui); // Top Left skills // 5 1 6 @@ -2878,9 +2878,11 @@ impl<'a> Widget for Diary<'a> { self.tooltip_manager, &self .localized_strings - .get("hud.skill.st_flame_velocity_title"), + .get("hud.skill.st_flamethrower_range_title"), &add_sp_cost_tooltip( - &self.localized_strings.get("hud.skill.st_flame_velocity"), + &self + .localized_strings + .get("hud.skill.st_flamethrower_range"), skill, &self.stats.skill_set, &self.localized_strings, @@ -3074,7 +3076,7 @@ impl<'a> Widget for Diary<'a> { ) .wh(art_size) .middle_of(state.content_align) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, tweak!(1.0)))) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) .set(state.sceptre_render, ui); // Top Left skills // 5 1 6 @@ -3392,22 +3394,24 @@ fn create_skill_button<'a>( label: &'a str, ) -> Button<'a, button::Image> { Button::image(image) - .w_h(tweak!(74.0), tweak!(74.0)) + .w_h(74.0, 74.0) .middle_of(state) .label(label) - .label_y(conrod_core::position::Relative::Scalar(tweak!(-28.0))) - .label_x(conrod_core::position::Relative::Scalar(tweak!(32.0))) - .label_color(if skill_set.sufficient_skill_points(skill) { + .label_y(conrod_core::position::Relative::Scalar(-28.0)) + .label_x(conrod_core::position::Relative::Scalar(32.0)) + .label_color(if skill_set.is_at_max_level(skill) { + TEXT_COLOR + } else if skill_set.sufficient_skill_points(skill) { HP_COLOR } else { CRITICAL_HP_COLOR }) - .label_font_size(fonts.cyri.scale(tweak!(16))) + .label_font_size(fonts.cyri.scale(16)) .label_font_id(fonts.cyri.conrod_id) .image_color(if skill_set.prerequisites_met(skill) { TEXT_COLOR } else { - Color::Rgba(0.41, 0.41, 0.41, tweak!(0.7)) + Color::Rgba(0.41, 0.41, 0.41, 0.7) }) } @@ -3415,12 +3419,8 @@ fn get_skill_label(skill: Skill, skill_set: &skills::SkillSet) -> String { if skill_set.prerequisites_met(skill) { format!( "{}/{}", - skill_set - .skills - .get(&skill) - .copied() - .map_or(0, |l| l.unwrap_or(1)), - skill.get_max_level().unwrap_or(1) + skill_set.skill_level(skill).map_or(0, |l| l.unwrap_or(1)), + skill.max_level().unwrap_or(1) ) } else { "".to_string() @@ -3446,14 +3446,13 @@ fn add_sp_cost_tooltip<'a>( skill_set: &'a skills::SkillSet, localized_strings: &'a Localization, ) -> String { - match skill_set.skills.get(&skill).copied() { - Some(level) if level == skill.get_max_level() => tooltip.replace("{}", ""), + match skill_set.skill_level(skill) { + Ok(level) if level == skill.max_level() => tooltip.replace("{}", ""), _ => tooltip.replace( "{SP}", - &localized_strings.get("hud.skill.req_sp").replace( - "{number}", - &format!("{}", skill_set.skill_point_cost(skill)), - ), + &localized_strings + .get("hud.skill.req_sp") + .replace("{number}", &format!("{}", skill_set.skill_cost(skill))), ), } } diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 0c58e1f693..ab5e18823a 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -1,7 +1,9 @@ use super::{ img_ids::{Imgs, ImgsRot}, Show, BLACK, BUFF_COLOR, DEBUFF_COLOR, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR, - LOW_HP_COLOR, STAMINA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN, + LOW_HP_COLOR, QUALITY_ARTIFACT, QUALITY_COMMON, QUALITY_DEBUG, QUALITY_EPIC, QUALITY_HIGH, + QUALITY_LEGENDARY, QUALITY_LOW, QUALITY_MODERATE, STAMINA_COLOR, TEXT_COLOR, TEXT_COLOR_GREY, + UI_HIGHLIGHT_0, UI_MAIN, XP_COLOR, }; use crate::{ @@ -14,6 +16,7 @@ use crate::{ }; use client::{self, Client}; use common::{ + combat, comp::{group::Role, BuffKind, Stats}, uid::{Uid, UidAllocator}, }; @@ -26,6 +29,8 @@ use conrod_core::{ }; use specs::{saveload::MarkerAllocator, WorldExt}; +use inline_tweak::*; + widget_ids! { pub struct Ids { group_button, @@ -54,6 +59,7 @@ widget_ids! { buff_timers[], dead_txt[], health_txt[], + combat_rating_indicators[], timeout_bg, timeout, } @@ -323,12 +329,19 @@ impl<'a> Widget for Group<'a> { .resize(group_size, &mut ui.widget_id_generator()) }) }; - + if state.ids.combat_rating_indicators.len() < group_size { + state.update(|s| { + s.ids + .combat_rating_indicators + .resize(group_size, &mut ui.widget_id_generator()) + }) + }; let client_state = self.client.state(); let stats = client_state.ecs().read_storage::(); let healths = client_state.ecs().read_storage::(); let energy = client_state.ecs().read_storage::(); let buffs = client_state.ecs().read_storage::(); + let inventory = client_state.ecs().read_storage::(); let uid_allocator = client_state.ecs().read_resource::(); // Keep track of the total number of widget ids we are using for buffs @@ -340,10 +353,12 @@ impl<'a> Widget for Group<'a> { let health = entity.and_then(|entity| healths.get(entity)); let energy = entity.and_then(|entity| energy.get(entity)); let buffs = entity.and_then(|entity| buffs.get(entity)); - + let inventory = entity.and_then(|entity| inventory.get(entity)); let is_leader = uid == leader; if let Some(stats) = stats { + let combat_rating = + combat::combat_rating(inventory.unwrap(), &health.unwrap(), stats); // We can unwrap here because we check for stats first let char_name = stats.name.to_string(); if let Some(health) = health { let health_perc = health.current() as f64 / health.maximum() as f64; @@ -421,14 +436,43 @@ impl<'a> Widget for Group<'a> { .middle_of(state.ids.member_panels_bg[i]) .color(Some(UI_HIGHLIGHT_0)) .set(state.ids.member_panels_frame[i], ui); + // Thresholds (lower) + let common = 4.3; + let moderate = 6.0; + let high = 8.0; + let epic = 10.0; + let legendary = 79.0; + let artifact = 122.0; + let debug = 200.0; + + let indicator_col = match combat_rating { + x if (0.0..common).contains(&x) => QUALITY_LOW, + x if (common..moderate).contains(&x) => QUALITY_COMMON, + x if (moderate..high).contains(&x) => QUALITY_MODERATE, + x if (high..epic).contains(&x) => QUALITY_HIGH, + x if (epic..legendary).contains(&x) => QUALITY_EPIC, + x if (legendary..artifact).contains(&x) => QUALITY_LEGENDARY, + x if (artifact..debug).contains(&x) => QUALITY_ARTIFACT, + x if x >= debug => QUALITY_DEBUG, + _ => XP_COLOR, + }; + Image::new(self.imgs.combat_rating_ico_shadow) + .w_h(tweak!(18.0), tweak!(18.0)) + .top_left_with_margins_on( + state.ids.member_panels_frame[i], + tweak!(-20.0), + tweak!(2.0), + ) + .color(Some(indicator_col)) + .set(state.ids.combat_rating_indicators[i], ui); // Panel Text Text::new(&char_name) - .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0) - .font_size(20) - .font_id(self.fonts.cyri.conrod_id) - .color(BLACK) - .w(300.0) // limit name length display - .set(state.ids.member_panels_txt_bg[i], ui); + .top_left_with_margins_on(state.ids.member_panels_frame[i], tweak!(-22.0), tweak!(22.0)) + .font_size(20) + .font_id(self.fonts.cyri.conrod_id) + .color(BLACK) + .w(300.0) // limit name length display + .set(state.ids.member_panels_txt_bg[i], ui); Text::new(&char_name) .bottom_left_with_margins_on(state.ids.member_panels_txt_bg[i], 2.0, 2.0) .font_size(20) diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs index 72edd68bea..a334cb9c2e 100644 --- a/voxygen/src/hud/hotbar.rs +++ b/voxygen/src/hud/hotbar.rs @@ -89,24 +89,19 @@ impl State { match tool.kind { ToolKind::Sword => stat .skill_set - .skills - .contains_key(&Skill::Sword(skills::SwordSkill::UnlockSpin)), + .has_skill(Skill::Sword(skills::SwordSkill::UnlockSpin)), ToolKind::Axe => stat .skill_set - .skills - .contains_key(&Skill::Axe(skills::AxeSkill::UnlockLeap)), + .has_skill(Skill::Axe(skills::AxeSkill::UnlockLeap)), ToolKind::Hammer => stat .skill_set - .skills - .contains_key(&Skill::Hammer(skills::HammerSkill::UnlockLeap)), + .has_skill(Skill::Hammer(skills::HammerSkill::UnlockLeap)), ToolKind::Bow => stat .skill_set - .skills - .contains_key(&Skill::Bow(skills::BowSkill::UnlockRepeater)), + .has_skill(Skill::Bow(skills::BowSkill::UnlockRepeater)), ToolKind::Staff => stat .skill_set - .skills - .contains_key(&Skill::Staff(skills::StaffSkill::UnlockShockwave)), + .has_skill(Skill::Staff(skills::StaffSkill::UnlockShockwave)), ToolKind::Debug | ToolKind::Unique(UniqueKind::QuadMedQuick) | ToolKind::Unique(UniqueKind::QuadLowBreathe) => true, diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 4f5e06cab9..2b74de5496 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -211,7 +211,6 @@ image_ids! { // Other Icons/Art skull: "voxygen.element.icons.skull", skull_2: "voxygen.element.icons.skull_2", - indicator_bubble: "voxygen.element.icons.indicator_bubble", fireplace: "voxygen.element.misc_bg.fireplace", // Crosshair @@ -338,6 +337,7 @@ image_ids! { health_ico: "voxygen.element.icons.health", protection_ico: "voxygen.element.icons.protection", combat_rating_ico: "voxygen.element.icons.combat_rating", + combat_rating_ico_shadow: "voxygen.element.icons.combat_rating_shadow", not_found: "voxygen.element.not_found", @@ -366,8 +366,11 @@ image_ids! { // Enemy Healthbar enemy_health: "voxygen.element.frames.enemybar", enemy_health_bg: "voxygen.element.frames.enemybar_bg", + health_bar_group: "voxygen.element.frames.enemybar_1", + health_bar_group_bg: "voxygen.element.frames.enemybar_bg_1", // Enemy Bar Content: enemy_bar: "voxygen.element.skillbar.enemy_bar_content", + // Bag bag: "voxygen.element.buttons.bag.closed", bag_hover: "voxygen.element.buttons.bag.closed_hover", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 12567b7bdb..c174bf8070 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -79,7 +79,6 @@ use conrod_core::{ widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; use hashbrown::HashMap; -use inline_tweak::*; use rand::Rng; use specs::{Join, WorldExt}; use std::{ @@ -1142,14 +1141,16 @@ impl Hud { .iter_mut() .find(|d| d.owner == *uid) { - let fade = if display.timer < 1.0 { - display.timer as f32 + let fade = if display.timer < 3.0 { + display.timer as f32 * 0.33 + } else if display.timer < 2.0 { + display.timer as f32 * 0.33 * 0.1 } else { 1.0 }; // Background image - let offset = if display.timer < tweak!(2.0) { - 300.0 - (display.timer as f64 - tweak!(2.0)) * tweak!(-300.0) + let offset = if display.timer < 2.0 { + 300.0 - (display.timer as f64 - 2.0) * -300.0 } else { 300.0 }; @@ -1162,20 +1163,20 @@ impl Hud { // Rank Number let rank = display.total_points; Text::new(&format!("{}", rank)) - .font_size(tweak!(20)) + .font_size(20) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade)) - .mid_top_with_margin_on(self.ids.player_rank_up, tweak!(8.0)) + .mid_top_with_margin_on(self.ids.player_rank_up, 8.0) .set(self.ids.player_rank_up_txt_number, ui_widgets); // Static "New Rank!" text Text::new(&i18n.get("hud.rank_up")) - .font_size(tweak!(40)) + .font_size(40) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, fade)) - .mid_bottom_with_margin_on(self.ids.player_rank_up, tweak!(20.0)) + .mid_bottom_with_margin_on(self.ids.player_rank_up, 20.0) .set(self.ids.player_rank_up_txt_0_bg, ui_widgets); Text::new(&i18n.get("hud.rank_up")) - .font_size(tweak!(40)) + .font_size(40) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade)) .bottom_left_with_margins_on(self.ids.player_rank_up_txt_0_bg, 2.0, 2.0) @@ -1192,13 +1193,13 @@ impl Hud { _ => "Unknown", }; Text::new(skill) - .font_size(tweak!(20)) + .font_size(20) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(0.0, 0.0, 0.0, fade)) - .mid_top_with_margin_on(self.ids.player_rank_up, tweak!(45.0)) + .mid_top_with_margin_on(self.ids.player_rank_up, 45.0) .set(self.ids.player_rank_up_txt_1_bg, ui_widgets); Text::new(skill) - .font_size(tweak!(20)) + .font_size(20) .font_id(self.fonts.cyri.conrod_id) .color(Color::Rgba(1.0, 1.0, 1.0, fade)) .bottom_left_with_margins_on(self.ids.player_rank_up_txt_1_bg, 2.0, 2.0) @@ -1215,8 +1216,8 @@ impl Hud { Weapon(ToolKind::Staff) => self.imgs.staff, _ => self.imgs.swords_crossed, }) - .w_h(tweak!(20.0), tweak!(20.0)) - .left_from(self.ids.player_rank_up_txt_1_bg, tweak!(5.0)) + .w_h(20.0, 20.0) + .left_from(self.ids.player_rank_up_txt_1_bg, 5.0) .color(Some(Color::Rgba(1.0, 1.0, 1.0, fade))) .set(self.ids.player_rank_up_icon, ui_widgets); @@ -1335,11 +1336,7 @@ impl Hud { health, buffs, energy, - combat_rating: combat::combat_rating( - inventory, - health, - &stats.body_type, - ), + combat_rating: combat::combat_rating(inventory, health, stats), }); let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) { speech_bubbles.get(uid) diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index bfd9886872..567203965d 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -18,9 +18,7 @@ use conrod_core::{ widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; const MAX_BUBBLE_WIDTH: f64 = 250.0; - use inline_tweak::*; - widget_ids! { struct Ids { // Speech bubble @@ -313,7 +311,7 @@ impl<'a> Widget for Overhead<'a> { let crit_hp_color: Color = Color::Rgba(0.93, 0.59, 0.03, hp_ani); // Background - Image::new(self.imgs.enemy_health_bg) + Image::new(if self.in_group {self.imgs.health_bar_group_bg} else {self.imgs.enemy_health_bg}) .w_h(84.0 * BARSIZE, 10.0 * BARSIZE) .x_y(0.0, MANA_BAR_Y + 6.5) //-25.5) .color(Some(Color::Rgba(0.1, 0.1, 0.1, 0.8))) @@ -321,12 +319,21 @@ impl<'a> Widget for Overhead<'a> { .set(state.ids.health_bar_bg, ui); // % HP Filling + let size_factor = (hp_percentage / 100.0) * BARSIZE; + let w = if self.in_group { + tweak!(82.0) * size_factor + } else { + 73.0 * size_factor + }; + let h = 6.0 * BARSIZE; + let x = if self.in_group { + (tweak!(0.0) + (hp_percentage / 100.0 * tweak!(41.0) - tweak!(41.0))) * BARSIZE + } else { + (4.5 + (hp_percentage / 100.0 * 36.45 - 36.45)) * BARSIZE + }; Image::new(self.imgs.enemy_bar) - .w_h(73.0 * (hp_percentage / 100.0) * BARSIZE, 6.0 * BARSIZE) - .x_y( - (4.5 + (hp_percentage / 100.0 * 36.45 - 36.45)) * BARSIZE, - MANA_BAR_Y + 7.5, - ) + .w_h(w, h) + .x_y(x, MANA_BAR_Y + tweak!(8.0)) .color(if self.in_group { // Different HP bar colors only for group members Some(match hp_percentage { @@ -354,37 +361,41 @@ impl<'a> Widget for Overhead<'a> { // % Mana Filling if let Some(energy) = energy { let energy_factor = energy.current() as f64 / energy.maximum() as f64; - - Rectangle::fill_with( - [72.0 * energy_factor * BARSIZE, MANA_BAR_HEIGHT], - STAMINA_COLOR, - ) - .x_y( - ((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE, - MANA_BAR_Y, //-32.0, - ) - .parent(id) - .set(state.ids.mana_bar, ui); + let size_factor = energy_factor * BARSIZE; + let w = if self.in_group { + tweak!(80.0) * size_factor + } else { + 72.0 * size_factor + }; + let x = if self.in_group { + ((tweak!(0.0) + (energy_factor * tweak!(40.0))) - tweak!(40.0)) * BARSIZE + } else { + ((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE + }; + Rectangle::fill_with([w, MANA_BAR_HEIGHT], STAMINA_COLOR) + .x_y( + x, MANA_BAR_Y, //-32.0, + ) + .parent(id) + .set(state.ids.mana_bar, ui); } // Foreground - Image::new(self.imgs.enemy_health) + Image::new(if self.in_group {self.imgs.health_bar_group} else {self.imgs.enemy_health}) .w_h(84.0 * BARSIZE, 10.0 * BARSIZE) .x_y(0.0, MANA_BAR_Y + 6.5) //-25.5) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99))) .parent(id) .set(state.ids.health_bar_fg, ui); - // TODO: Add strength comparison here, this is just an example - // Thresholds (lower) - let common = tweak!(4.3); - let moderate = tweak!(6.0); - let high = tweak!(8.0); - let epic = tweak!(10.0); - let legendary = tweak!(79.0); - let artifact = tweak!(122.0); - let debug = tweak!(200.0); + let common = 4.3; + let moderate = 6.0; + let high = 8.0; + let epic = 10.0; + let legendary = 79.0; + let artifact = 122.0; + let debug = 200.0; let indicator_col = match combat_rating { x if (0.0..common).contains(&x) => QUALITY_LOW, @@ -411,12 +422,16 @@ impl<'a> Widget for Overhead<'a> { .parent(id) .set(state.ids.level_skull, ui); } else { - Image::new(self.imgs.indicator_bubble) - .w_h(5.0 * BARSIZE, 5.0 * BARSIZE) - .x_y(tweak!(-37.0) * BARSIZE, MANA_BAR_Y + tweak!(7.5)) - .color(Some(indicator_col)) - .parent(id) - .set(state.ids.level, ui); + Image::new(if self.in_group { + self.imgs.nothing + } else { + self.imgs.combat_rating_ico + }) + .w_h(tweak!(7.0) * BARSIZE, tweak!(7.0) * BARSIZE) + .x_y(tweak!(-37.0) * BARSIZE, MANA_BAR_Y + tweak!(6.0)) + .color(Some(indicator_col)) + .parent(id) + .set(state.ids.level, ui); } } } diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index fb43e0e6ec..20f7ec834d 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -28,7 +28,6 @@ use conrod_core::{ widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; -use inline_tweak::*; use vek::*; widget_ids! { @@ -222,7 +221,7 @@ impl<'a> Widget for Skillbar<'a> { let localized_strings = self.localized_strings; - let slot_offset = tweak!(3.0); + let slot_offset = 3.0; // Death message if self.health.is_dead { @@ -277,11 +276,7 @@ impl<'a> Widget for Skillbar<'a> { let show_stamina = self.energy.current() != self.energy.maximum(); if show_health && !self.health.is_dead { - let offset = if show_stamina { - tweak!(1.0) - } else { - tweak!(1.0) - }; + let offset = 1.0; Image::new(self.imgs.health_bg) .w_h(484.0, 24.0) .mid_top_with_margin_on(state.ids.frame, -offset) @@ -306,11 +301,7 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.frame_health, ui); } if show_stamina && !self.health.is_dead { - let offset = if show_health { - tweak!(34.0) - } else { - tweak!(1.0) - }; + let offset = if show_health { 34.0 } else { 1.0 }; Image::new(self.imgs.stamina_bg) .w_h(323.0, 16.0) .mid_top_with_margin_on(state.ids.frame, -offset) @@ -533,7 +524,7 @@ impl<'a> Widget for Skillbar<'a> { let slot = slot_maker .fabricate(hotbar::Slot::Four, [40.0; 2]) .filled_slot(self.imgs.skillbar_slot) - .right_from(state.ids.slot3, 0.0); + .right_from(state.ids.slot3, slot_offset); if let Some((title, desc)) = tooltip_text(hotbar::Slot::Four) { slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) .set(state.ids.slot4, ui); @@ -544,7 +535,7 @@ impl<'a> Widget for Skillbar<'a> { let slot = slot_maker .fabricate(hotbar::Slot::Five, [40.0; 2]) .filled_slot(self.imgs.skillbar_slot) - .right_from(state.ids.slot4, 0.0); + .right_from(state.ids.slot4, slot_offset); if let Some((title, desc)) = tooltip_text(hotbar::Slot::Five) { slot.with_tooltip(self.tooltip_manager, title, desc, &item_tooltip, TEXT_COLOR) .set(state.ids.slot5, ui); diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index 6871785bde..ae83da0f75 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -37,8 +37,6 @@ use iced::{ }; use vek::Rgba; -use inline_tweak::*; - pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0); pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2); pub const TOOLTIP_BACK_COLOR: Rgba = Rgba::new(20, 18, 10, 255); @@ -446,11 +444,8 @@ impl Controls { select_button, Column::with_children(vec![ Text::new(&character.character.alias) - .size(fonts.cyri.scale(tweak!(26))) + .size(fonts.cyri.scale(26)) .into(), - // TODO: only construct string once when characters - // are - // loaded Text::new( i18n.get("char_selection.uncanny_valley"), )