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.
This commit is contained in:
Sam 2021-01-16 12:01:57 -05:00
parent 0b156542e4
commit eaa41c7dea
31 changed files with 718 additions and 612 deletions

2
Cargo.lock generated
View File

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

View File

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

Binary file not shown.

BIN
assets/voxygen/element/icons/combat_rating_shadow.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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"]}

View File

@ -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<ToolKind>, Option<ToolKind>) {
(
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()
}

View File

@ -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<skills::Skill, skills::Level>,
skillset: &skills::SkillSet,
tool: Option<ToolKind>,
) -> 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::<Vec<combo_melee::Stage<u64>>>();
}
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;
}

View File

@ -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 {

View File

@ -18,7 +18,7 @@ impl Asset for SkillTreeMap {
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SkillLevelMap(HashMap<Skill, Level>);
pub struct SkillLevelMap(HashMap<Skill, Option<u16>>);
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<Skill, HashMap<Skill, Level>>);
pub struct SkillPrerequisitesMap(HashMap<Skill, HashMap<Skill, Option<u16>>>);
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<Skill, Level> = {
pub static ref SKILL_MAX_LEVEL: HashMap<Skill, Option<u16>> = {
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<Skill, HashMap<Skill, Level>> = {
pub static ref SKILL_PREREQUISITES: HashMap<Skill, HashMap<Skill, Option<u16>>> = {
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<SkillGroup>,
pub skills: HashMap<Skill, Level>,
pub skills: HashMap<Skill, Option<u16>>,
pub modify_health: bool,
pub modify_energy: bool,
}
pub type Level = Option<u16>;
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<Option<u16>, 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<u16> {
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<Skill, Level> {
pub fn prerequisite_skills(self, level: Option<u16>) -> HashMap<Skill, Option<u16>> {
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>) -> 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<u16> { SKILL_MAX_LEVEL.get(&self).copied().flatten() }
pub fn max_level(self) -> Option<u16> { 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<SkillGroupType> {
SKILL_GROUP_DEFS.iter().find_map(|(key, val)| {
if val.contains(&self) {
Some(*key)
} else {
None
}
})
pub fn skill_group_type(self) -> Option<SkillGroupType> {
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));

View File

@ -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<f32>,
},
}

View File

@ -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.",

View File

@ -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))
{

View File

@ -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::<f32>() > 0.8
{
inputs.ability3.set_state(true);

View File

@ -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::<SkillGroupType>::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::<HashSet<_>>();
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();

View File

@ -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"}

View File

@ -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::<Pos>();
let alignments = state.ecs().read_storage::<Alignment>();
let uids = state.ecs().read_storage::<Uid>();
let mut outcomes = state.ecs().write_resource::<Vec<Outcome>>();
let inventories = state.ecs().read_storage::<comp::Inventory>();
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::<Vec<_>>();
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::<comp::Inventory>().get(e) {
combat::get_weapons(inventory)
} else {
(None, None)
};
if let Some(mut stats) = stats.get_mut(e) {
let mut xp_pools = HashSet::<SkillGroupType>::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::<Vec<Outcome>>()
.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::<comp::Inventory>().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::<SkillGroupType>::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::<Vec<Outcome>>()
.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<Outcome>,
) {
let (main_tool_kind, second_tool_kind) = combat::get_weapons(inventory);
let mut xp_pools = HashSet::<SkillGroupType>::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,
});
}

View File

@ -379,7 +379,7 @@ fn convert_skill_groups_from_database(skill_groups: &[SkillGroup]) -> Vec<skills
new_skill_groups
}
fn convert_skills_from_database(skills: &[Skill]) -> HashMap<skills::Skill, skills::Level> {
fn convert_skills_from_database(skills: &[Skill]) -> HashMap<skills::Skill, Option<u16>> {
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<skills::SkillGroup>,
) -> Vec<SkillGroup> {
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::Skill, skills::Level>,
skills: HashMap<skills::Skill, Option<u16>>,
) -> Vec<Skill> {
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()
}

View File

@ -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));

View File

@ -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;

View File

@ -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);

View File

@ -358,7 +358,7 @@ impl SfxMgr {
audio.play_sfx(file_ref, *pos, None);
}
},
_ => {},
Outcome::ExpChange { .. } => {},
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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))),
),
}
}

View File

@ -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::<common::comp::Stats>();
let healths = client_state.ecs().read_storage::<common::comp::Health>();
let energy = client_state.ecs().read_storage::<common::comp::Energy>();
let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
let inventory = client_state.ecs().read_storage::<common::comp::Inventory>();
let uid_allocator = client_state.ecs().read_resource::<UidAllocator>();
// 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)

View File

@ -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,

View File

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

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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<u8> = 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"),
)