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"), )