Merge branch 'juliancoffee/skill_boost_rework' into 'master'

ECS & Diary info synchronization

See merge request veloren/veloren!2771
This commit is contained in:
Marcel 2021-09-02 16:04:23 +00:00
commit 970d57f905
13 changed files with 3400 additions and 2658 deletions

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ pub mod theropod;
use crate::{ use crate::{
assets::{self, Asset}, assets::{self, Asset},
consts::{HUMAN_DENSITY, WATER_DENSITY}, consts::{HUMANOID_HP_PER_LEVEL, HUMAN_DENSITY, WATER_DENSITY},
make_case_elim, make_case_elim,
npc::NpcKind, npc::NpcKind,
}; };
@ -563,7 +563,7 @@ impl Body {
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
pub fn base_health_increase(&self) -> u32 { pub fn base_health_increase(&self) -> u32 {
match self { match self {
Body::Humanoid(_) => 50, Body::Humanoid(_) => HUMANOID_HP_PER_LEVEL,
Body::QuadrupedSmall(quadruped_small) => match quadruped_small.species { Body::QuadrupedSmall(quadruped_small) => match quadruped_small.species {
quadruped_small::Species::Boar => 20, quadruped_small::Species::Boar => 20,
quadruped_small::Species::Batfox => 10, quadruped_small::Species::Batfox => 10,

View File

@ -1,9 +1,8 @@
use crate::comp::Body; use crate::{comp::Body, consts::ENERGY_PER_LEVEL};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage}; use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
pub const ENERGY_PER_LEVEL: u32 = 50;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Energy { pub struct Energy {
current: u32, current: u32,

View File

@ -14,19 +14,25 @@ use std::{
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ToolKind { pub enum ToolKind {
// weapons
Sword, Sword,
Axe, Axe,
Hammer, Hammer,
Bow, Bow,
Dagger,
Staff, Staff,
Sceptre, Sceptre,
// future weapons
Dagger,
Shield, Shield,
Spear, Spear,
Natural, // Intended for invisible weapons (e.g. a creature using its claws or biting) // tools
Debug, Debug,
Farming, Farming,
Pick, Pick,
// npcs
/// Intended for invisible weapons (e.g. a creature using its claws or
/// biting)
Natural,
/// This is an placeholder item, it is used by non-humanoid npcs to attack /// This is an placeholder item, it is used by non-humanoid npcs to attack
Empty, Empty,
} }

View File

@ -367,6 +367,7 @@ impl ProjectileConstructor {
} }
} }
// TODO: split this to three methods per stat
pub fn modified_projectile(mut self, power: f32, regen: f32, range: f32) -> Self { pub fn modified_projectile(mut self, power: f32, regen: f32, range: f32) -> Self {
use ProjectileConstructor::*; use ProjectileConstructor::*;
match self { match self {

View File

@ -1,10 +1,6 @@
use crate::{ use crate::{
assets::{self, Asset, AssetExt}, assets::{self, Asset, AssetExt},
comp::{ comp::item::tool::ToolKind,
self,
body::{humanoid, Body},
item::tool::ToolKind,
},
}; };
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -47,9 +43,10 @@ impl Asset for SkillPrerequisitesMap {
} }
lazy_static! { lazy_static! {
// Determines the skills that comprise each skill group - this data is used to determine // Determines the skills that comprise each skill group.
// which of a player's skill groups a particular skill should be added to when a skill unlock //
// is requested. // This data is used to determine which of a player's skill groups a
// particular skill should be added to when a skill unlock is requested.
pub static ref SKILL_GROUP_DEFS: HashMap<SkillGroupKind, SkillGroupDef> = { pub static ref SKILL_GROUP_DEFS: HashMap<SkillGroupKind, SkillGroupDef> = {
let map = SkillTreeMap::load_expect_cloned( let map = SkillTreeMap::load_expect_cloned(
"common.skill_trees.skills_skill-groups_manifest", "common.skill_trees.skills_skill-groups_manifest",
@ -98,6 +95,8 @@ lazy_static! {
/// kind of active ability, or a passive effect etc. Obviously because this is /// kind of active ability, or a passive effect etc. Obviously because this is
/// an enum it doesn't describe what the skill actually -does-, this will be /// an enum it doesn't describe what the skill actually -does-, this will be
/// handled by dedicated ECS systems. /// handled by dedicated ECS systems.
// NOTE: if skill does use some constant, add it to corresponding
// SkillTree Modifiers below.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum Skill { pub enum Skill {
General(GeneralSkill), General(GeneralSkill),
@ -114,127 +113,382 @@ pub enum Skill {
Pick(MiningSkill), Pick(MiningSkill),
} }
/// Enum which returned as result from `boost` function /// Tree of modifiers that represent how stats are
/// `Number` can represent values from -inf to +inf, /// changed per each skill level.
/// but it should generaly be in range -50..50
/// ///
/// Number(-25) says that some value /// It's used as bridge between ECS systems
/// will be reduced by 25% (for example energy consumption) /// and voxygen Diary for skill descriptions and helps to sync them.
/// ///
/// Number(15) says that some value /// NOTE: Just adding constant does nothing, you need to use it in both
/// will be increased by 15% (for example damage) /// ECS systems and Diary.
#[derive(Debug, Clone, Copy)] // TODO: make it lazy_static and move to .ron?
pub enum BoostValue { pub const SKILL_MODIFIERS: SkillTreeModifiers = SkillTreeModifiers::get();
Number(i16),
NonDescriptive, pub struct SkillTreeModifiers {
pub sword_tree: SwordTreeModifiers,
pub axe_tree: AxeTreeModifiers,
pub hammer_tree: HammerTreeModifiers,
pub bow_tree: BowTreeModifiers,
pub staff_tree: StaffTreeModifiers,
pub sceptre_tree: SceptreTreeModifiers,
pub mining_tree: MiningTreeModifiers,
pub general_tree: GeneralTreeModifiers,
} }
impl BoostValue { impl SkillTreeModifiers {
pub fn as_mult_maybe(self) -> Option<f32> { const fn get() -> Self {
match self { Self {
Self::Number(x) => Some(1.0 + x as f32 * 0.01), sword_tree: SwordTreeModifiers::get(),
Self::NonDescriptive => None, axe_tree: AxeTreeModifiers::get(),
} hammer_tree: HammerTreeModifiers::get(),
} bow_tree: BowTreeModifiers::get(),
staff_tree: StaffTreeModifiers::get(),
pub fn as_i16_maybe(self) -> Option<i16> { sceptre_tree: SceptreTreeModifiers::get(),
match self { mining_tree: MiningTreeModifiers::get(),
Self::Number(x) => Some(x), general_tree: GeneralTreeModifiers::get(),
Self::NonDescriptive => None,
} }
} }
} }
impl From<i16> for BoostValue { pub struct SwordTreeModifiers {
fn from(number: i16) -> Self { BoostValue::Number(number) } pub dash: SwordDashModifiers,
pub spin: SwordSpinModifiers,
} }
pub fn adjust_with_level(skillset: &SkillSet, skill: Skill, effect: impl FnOnce(f32, u16)) { pub struct SwordDashModifiers {
// NOTE: We are unwrapping before checking skill level, pub energy_cost: f32,
// because if it falls we want know it even if we don't have this level pub energy_drain: f32,
let multiplier = match skill.boost().as_mult_maybe() { pub base_damage: f32,
Some(m) => m, pub scaled_damage: f32,
None => return invalid_skill_boost(skill), pub forward_speed: f32,
};
if let Ok(Some(level)) = skillset.skill_level(skill) {
effect(multiplier, level);
}
} }
pub fn adjust_counter_with_level(skillset: &SkillSet, skill: Skill, effect: impl FnOnce(i16, i16)) { pub struct SwordSpinModifiers {
// NOTE: We are unwrapping before checking skill level, pub base_damage: f32,
// because if it falls we want know it even if we don't have this level pub swing_duration: f32,
let counter = match skill.boost().as_i16_maybe() { pub energy_cost: f32,
Some(c) => c, pub num: u32,
None => return invalid_skill_boost(skill),
};
if let Ok(Some(level)) = skillset.skill_level(skill) {
effect(counter, level as i16);
}
} }
pub fn set_if_has(skillset: &SkillSet, skill: Skill, effect: impl FnOnce(f32)) { impl SwordTreeModifiers {
// NOTE: We are unwrapping before checking skill level, const fn get() -> Self {
// because if it falls we want know it even if we don't have this level Self {
let multiplier = match skill.boost().as_mult_maybe() { dash: SwordDashModifiers {
Some(c) => c, energy_cost: 0.75,
None => return invalid_skill_boost(skill), energy_drain: 0.75,
}; base_damage: 1.2,
if skillset.has_skill(skill) { scaled_damage: 1.2,
effect(multiplier); forward_speed: 1.15,
} },
} spin: SwordSpinModifiers {
base_damage: 1.4,
#[track_caller] swing_duration: 0.8,
pub fn invalid_skill_boost(skill: Skill) { energy_cost: 0.75,
let err_msg = format!( num: 1,
r#" },
{:?} produced unexpected BoostValue: {:?}
Clearly that shouldn't happen and tests should catch this.
If they didn't, probably because we've added new skills/weapons.
In this case, please find `test_adjusting_skills`,
fix tests and fix corresponding `impl Boost` for this skill.
"#,
skill,
skill.boost()
);
common_base::dev_panic!(err_msg);
}
/// Returns value which corresponds to the boost given by this skill
pub trait Boost {
fn boost(self) -> BoostValue;
}
impl Boost for Skill {
fn boost(self) -> BoostValue {
match self {
// General tree boosts
Skill::General(s) => s.boost(),
// Weapon tree boosts
Skill::Sword(s) => s.boost(),
Skill::Axe(s) => s.boost(),
Skill::Hammer(s) => s.boost(),
Skill::Bow(s) => s.boost(),
Skill::Staff(s) => s.boost(),
Skill::Sceptre(s) => s.boost(),
// Movement tree boosts
Skill::Roll(s) => s.boost(),
Skill::Climb(s) => s.boost(),
Skill::Swim(s) => s.boost(),
// Non-combat tree boosts
Skill::Pick(s) => s.boost(),
// Unlock Group has more complex semantic
Skill::UnlockGroup(_) => BoostValue::NonDescriptive,
} }
} }
} }
pub struct AxeTreeModifiers {
pub spin: AxeSpinModifiers,
pub leap: AxeLeapModifiers,
}
pub struct AxeSpinModifiers {
pub base_damage: f32,
pub swing_duration: f32,
pub energy_cost: f32,
}
pub struct AxeLeapModifiers {
pub base_damage: f32,
pub knockback: f32,
pub energy_cost: f32,
// TODO: split to forward and vertical?
pub leap_strength: f32,
}
impl AxeTreeModifiers {
const fn get() -> Self {
Self {
spin: AxeSpinModifiers {
base_damage: 1.3,
swing_duration: 0.8,
energy_cost: 0.75,
},
leap: AxeLeapModifiers {
base_damage: 1.35,
knockback: 1.4,
energy_cost: 0.75,
leap_strength: 1.2,
},
}
}
}
pub struct HammerTreeModifiers {
pub single_strike: HammerStrikeModifiers,
pub charged: HammerChargedModifers,
pub leap: HammerLeapModifiers,
}
pub struct HammerStrikeModifiers {
pub knockback: f32,
}
pub struct HammerChargedModifers {
pub scaled_damage: f32,
pub scaled_knockback: f32,
pub energy_drain: f32,
pub charge_rate: f32,
}
pub struct HammerLeapModifiers {
pub base_damage: f32,
pub knockback: f32,
pub energy_cost: f32,
pub leap_strength: f32,
pub range: f32,
}
impl HammerTreeModifiers {
const fn get() -> Self {
Self {
single_strike: HammerStrikeModifiers { knockback: 1.5 },
charged: HammerChargedModifers {
scaled_damage: 1.25,
scaled_knockback: 1.5,
energy_drain: 0.75,
charge_rate: 1.25,
},
leap: HammerLeapModifiers {
base_damage: 1.4,
knockback: 1.5,
energy_cost: 0.75,
leap_strength: 1.25,
range: 1.0,
},
}
}
}
pub struct BowTreeModifiers {
pub universal: BowUniversalModifiers,
pub charged: BowChargedModifiers,
pub repeater: BowRepeaterModifiers,
pub shotgun: BowShotgunModifiers,
}
pub struct BowUniversalModifiers {
// TODO: split per abilities?
pub projectile_speed: f32,
}
pub struct BowChargedModifiers {
pub damage_scaling: f32,
pub regen_scaling: f32,
pub knockback_scaling: f32,
pub charge_rate: f32,
pub move_speed: f32,
}
pub struct BowRepeaterModifiers {
pub power: f32,
pub energy_cost: f32,
pub max_speed: f32,
}
pub struct BowShotgunModifiers {
pub power: f32,
pub energy_cost: f32,
pub num_projectiles: u32,
pub spread: f32,
}
impl BowTreeModifiers {
const fn get() -> Self {
Self {
universal: BowUniversalModifiers {
projectile_speed: 1.2,
},
charged: BowChargedModifiers {
damage_scaling: 1.2,
regen_scaling: 1.2,
knockback_scaling: 1.2,
charge_rate: 1.1,
move_speed: 1.1,
},
repeater: BowRepeaterModifiers {
power: 1.2,
energy_cost: 0.8,
max_speed: 1.2,
},
shotgun: BowShotgunModifiers {
power: 1.2,
energy_cost: 1.2,
num_projectiles: 1,
spread: 0.8,
},
}
}
}
pub struct StaffTreeModifiers {
pub fireball: StaffFireballModifiers,
pub flamethrower: StaffFlamethrowerModifiers,
pub shockwave: StaffShockwaveModifiers,
}
pub struct StaffFireballModifiers {
pub power: f32,
pub regen: f32,
pub range: f32,
}
pub struct StaffFlamethrowerModifiers {
pub damage: f32,
pub range: f32,
pub energy_drain: f32,
pub velocity: f32,
}
pub struct StaffShockwaveModifiers {
pub damage: f32,
pub knockback: f32,
pub duration: f32,
pub energy_cost: f32,
}
impl StaffTreeModifiers {
const fn get() -> Self {
Self {
fireball: StaffFireballModifiers {
power: 1.2,
regen: 1.2,
range: 1.15,
},
flamethrower: StaffFlamethrowerModifiers {
damage: 1.3,
range: 1.25,
energy_drain: 0.8,
velocity: 1.25,
},
shockwave: StaffShockwaveModifiers {
damage: 1.3,
knockback: 1.3,
duration: 1.2,
energy_cost: 0.8,
},
}
}
}
pub struct SceptreTreeModifiers {
pub beam: SceptreBeamModifiers,
pub healing_aura: SceptreHealingAuraModifiers,
pub warding_aura: SceptreWardingAuraModifiers,
}
pub struct SceptreBeamModifiers {
pub damage: f32,
pub range: f32,
pub energy_regen: f32,
pub lifesteal: f32,
}
pub struct SceptreHealingAuraModifiers {
pub strength: f32,
pub duration: f32,
pub range: f32,
pub energy_cost: f32,
}
pub struct SceptreWardingAuraModifiers {
pub strength: f32,
pub duration: f32,
pub range: f32,
pub energy_cost: f32,
}
impl SceptreTreeModifiers {
const fn get() -> Self {
Self {
beam: SceptreBeamModifiers {
damage: 1.2,
range: 1.2,
energy_regen: 1.2,
lifesteal: 1.15,
},
healing_aura: SceptreHealingAuraModifiers {
strength: 1.15,
duration: 1.2,
range: 1.25,
energy_cost: 0.85,
},
warding_aura: SceptreWardingAuraModifiers {
strength: 1.15,
duration: 1.2,
range: 1.25,
energy_cost: 0.85,
},
}
}
}
pub struct MiningTreeModifiers {
pub speed: f32,
pub gem_gain: f32,
pub ore_gain: f32,
}
impl MiningTreeModifiers {
const fn get() -> Self {
Self {
speed: 1.1,
gem_gain: 0.05,
ore_gain: 0.05,
}
}
}
pub struct GeneralTreeModifiers {
pub roll: RollTreeModifiers,
pub swim: SwimTreeModifiers,
pub climb: ClimbTreeModifiers,
}
pub struct RollTreeModifiers {
pub energy_cost: f32,
pub strength: f32,
pub duration: f32,
}
pub struct SwimTreeModifiers {
pub speed: f32,
}
pub struct ClimbTreeModifiers {
pub energy_cost: f32,
pub speed: f32,
}
impl GeneralTreeModifiers {
const fn get() -> Self {
Self {
roll: RollTreeModifiers {
energy_cost: 0.9,
strength: 1.1,
duration: 1.1,
},
swim: SwimTreeModifiers { speed: 1.25 },
climb: ClimbTreeModifiers {
energy_cost: 0.8,
speed: 1.2,
},
}
}
}
pub enum SkillError { pub enum SkillError {
MissingSkill, MissingSkill,
} }
@ -263,32 +517,6 @@ pub enum SwordSkill {
SSpins, SSpins,
} }
impl Boost for SwordSkill {
fn boost(self) -> BoostValue {
match self {
// Dash
Self::DDamage => 20.into(),
Self::DCost => (-25_i16).into(),
Self::DDrain => (-25_i16).into(),
Self::DScaling => 20.into(),
Self::DSpeed => 15.into(),
// Spin
Self::SDamage => 40.into(),
Self::SSpeed => (-20_i16).into(),
Self::SCost => (-25_i16).into(),
// Non-descriptive values
Self::InterruptingAttacks
| Self::TsCombo
| Self::TsDamage
| Self::TsRegen
| Self::TsSpeed
| Self::DInfinite
| Self::UnlockSpin
| Self::SSpins => BoostValue::NonDescriptive,
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum AxeSkill { pub enum AxeSkill {
// Double strike upgrades // Double strike upgrades
@ -310,30 +538,6 @@ pub enum AxeSkill {
LDistance, LDistance,
} }
impl Boost for AxeSkill {
fn boost(self) -> BoostValue {
match self {
// Spin upgrades
Self::SDamage => 30.into(),
Self::SSpeed => (-20_i16).into(),
Self::SCost => (-25_i16).into(),
// Leap upgrades
Self::LDamage => 35.into(),
Self::LKnockback => 40.into(),
Self::LCost => (-25_i16).into(),
Self::LDistance => 20.into(),
// Non-descriptive boosts
Self::UnlockLeap
| Self::DsCombo
| Self::DsDamage
| Self::DsSpeed
| Self::DsRegen
| Self::SInfinite
| Self::SHelicopter => BoostValue::NonDescriptive,
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum HammerSkill { pub enum HammerSkill {
// Single strike upgrades // Single strike upgrades
@ -355,30 +559,6 @@ pub enum HammerSkill {
LRange, LRange,
} }
impl Boost for HammerSkill {
fn boost(self) -> BoostValue {
match self {
// Single strike upgrades
Self::SsKnockback => 50.into(),
// Charged melee upgrades
Self::CDamage => 25.into(),
Self::CKnockback => 50.into(),
Self::CDrain => (-25_i16).into(),
Self::CSpeed => 25.into(),
// Leap upgrades
Self::LDamage => 40.into(),
Self::LKnockback => 50.into(),
Self::LCost => (-25_i16).into(),
Self::LDistance => 25.into(),
Self::LRange => 1.into(),
// Non-descriptive values
Self::UnlockLeap | Self::SsDamage | Self::SsSpeed | Self::SsRegen => {
BoostValue::NonDescriptive
},
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum BowSkill { pub enum BowSkill {
// Passives // Passives
@ -401,32 +581,6 @@ pub enum BowSkill {
SSpread, SSpread,
} }
impl Boost for BowSkill {
fn boost(self) -> BoostValue {
match self {
// Passive
Self::ProjSpeed => 20.into(),
// Charged upgrades
Self::CDamage => 20.into(),
Self::CRegen => 20.into(),
Self::CKnockback => 20.into(),
Self::CSpeed => 10.into(),
Self::CMove => 10.into(),
// Repeater upgrades
Self::RDamage => 20.into(),
Self::RCost => (-20_i16).into(),
Self::RSpeed => 20.into(),
// Shotgun upgrades
Self::SDamage => 20.into(),
Self::SCost => (-20_i16).into(),
Self::SArrows => 1.into(),
Self::SSpread => (-20_i16).into(),
// Non-descriptive values
Self::UnlockShotgun => BoostValue::NonDescriptive,
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum StaffSkill { pub enum StaffSkill {
// Basic ranged upgrades // Basic ranged upgrades
@ -446,29 +600,6 @@ pub enum StaffSkill {
SCost, SCost,
} }
impl Boost for StaffSkill {
fn boost(self) -> BoostValue {
match self {
// Fireball upgrades
Self::BDamage => 20.into(),
Self::BRegen => 20.into(),
Self::BRadius => 15.into(),
// Flamethrower upgrades
Self::FDamage => 30.into(),
Self::FRange => 25.into(),
Self::FDrain => (-20_i16).into(),
Self::FVelocity => 25.into(),
// Shockwave upgrades
Self::SDamage => 30.into(),
Self::SKnockback => 30.into(),
Self::SRange => 20.into(),
Self::SCost => (-20_i16).into(),
// Non-descriptive values
Self::UnlockShockwave => BoostValue::NonDescriptive,
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum SceptreSkill { pub enum SceptreSkill {
// Lifesteal beam upgrades // Lifesteal beam upgrades
@ -476,7 +607,7 @@ pub enum SceptreSkill {
LRange, LRange,
LLifesteal, LLifesteal,
LRegen, LRegen,
// Healing beam upgrades // Healing aura upgrades
HHeal, HHeal,
HRange, HRange,
HDuration, HDuration,
@ -489,53 +620,12 @@ pub enum SceptreSkill {
ACost, ACost,
} }
impl Boost for SceptreSkill {
fn boost(self) -> BoostValue {
match self {
// Lifesteal beam upgrades
Self::LDamage => 20.into(),
Self::LRange => 20.into(),
Self::LRegen => 20.into(),
Self::LLifesteal => 15.into(),
// Healing beam upgrades
Self::HHeal => 20.into(),
Self::HRange => 20.into(),
Self::HDuration => 20.into(),
Self::HCost => (-20_i16).into(),
// Warding aura upgrades
Self::AStrength => 15.into(),
Self::ADuration => 20.into(),
Self::ARange => 25.into(),
Self::ACost => (-15_i16).into(),
Self::UnlockAura => BoostValue::NonDescriptive,
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum GeneralSkill { pub enum GeneralSkill {
HealthIncrease, HealthIncrease,
EnergyIncrease, EnergyIncrease,
} }
impl Boost for GeneralSkill {
fn boost(self) -> BoostValue {
// NOTE: These should be used only for UI.
// Source of truth are corresponding systems
match self {
Self::HealthIncrease => {
let health_increase =
(Body::Humanoid(humanoid::Body::random()).base_health_increase() / 10) as i16;
health_increase.into()
},
Self::EnergyIncrease => {
let energy_increase = (comp::energy::ENERGY_PER_LEVEL / 10) as i16;
energy_increase.into()
},
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum RollSkill { pub enum RollSkill {
Cost, Cost,
@ -543,44 +633,17 @@ pub enum RollSkill {
Duration, Duration,
} }
impl Boost for RollSkill {
fn boost(self) -> BoostValue {
match self {
Self::Cost => (-10_i16).into(),
Self::Strength => 10.into(),
Self::Duration => 10.into(),
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum ClimbSkill { pub enum ClimbSkill {
Cost, Cost,
Speed, Speed,
} }
impl Boost for ClimbSkill {
fn boost(self) -> BoostValue {
match self {
Self::Cost => (-20_i16).into(),
Self::Speed => 20.into(),
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum SwimSkill { pub enum SwimSkill {
Speed, Speed,
} }
impl Boost for SwimSkill {
fn boost(self) -> BoostValue {
match self {
Self::Speed => 25.into(),
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum MiningSkill { pub enum MiningSkill {
Speed, Speed,
@ -588,16 +651,6 @@ pub enum MiningSkill {
GemGain, GemGain,
} }
impl Boost for MiningSkill {
fn boost(self) -> BoostValue {
match self {
Self::Speed => 10.into(),
Self::OreGain => 5.into(),
Self::GemGain => 5.into(),
}
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum SkillGroupKind { pub enum SkillGroupKind {
General, General,
@ -956,6 +1009,15 @@ impl SkillSet {
} }
} }
/// Returns the level of the skill or passed value as default
pub fn skill_level_or(&self, skill: Skill, default: u16) -> u16 {
if let Ok(Some(level)) = self.skill_level(skill) {
level
} else {
default
}
}
/// Checks the next level of a skill /// Checks the next level of a skill
fn next_skill_level(&self, skill: Skill) -> Option<u16> { fn next_skill_level(&self, skill: Skill) -> Option<u16> {
if let Ok(level) = self.skill_level(skill) { if let Ok(level) = self.skill_level(skill) {

View File

@ -26,3 +26,7 @@ pub const MIN_RECOMMENDED_RAYON_THREADS: usize = 2;
pub const MIN_RECOMMENDED_TOKIO_THREADS: usize = 2; pub const MIN_RECOMMENDED_TOKIO_THREADS: usize = 2;
pub const SOUND_TRAVEL_DIST_PER_VOLUME: f32 = 3.0; pub const SOUND_TRAVEL_DIST_PER_VOLUME: f32 = 3.0;
// Stat increase per level (multiplied by 10 compared to what you'll see in UI)
pub const ENERGY_PER_LEVEL: u32 = 50;
pub const HUMANOID_HP_PER_LEVEL: u32 = 50;

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
comp::{ comp::{
skills::{ClimbSkill::*, Skill}, skills::{ClimbSkill::*, Skill, SKILL_MODIFIERS},
CharacterState, Climb, EnergySource, InputKind, Ori, StateUpdate, CharacterState, Climb, EnergySource, InputKind, Ori, StateUpdate,
}, },
consts::GRAVITY, consts::GRAVITY,
@ -30,12 +30,13 @@ pub struct Data {
impl Data { impl Data {
pub fn create_adjusted_by_skills(join_data: &JoinData) -> Self { pub fn create_adjusted_by_skills(join_data: &JoinData) -> Self {
let modifiers = SKILL_MODIFIERS.general_tree.climb;
let mut data = Data::default(); let mut data = Data::default();
if let Ok(Some(level)) = join_data.skill_set.skill_level(Skill::Climb(Cost)) { if let Ok(Some(level)) = join_data.skill_set.skill_level(Skill::Climb(Cost)) {
data.static_data.energy_cost *= 0.8_f32.powi(level.into()); data.static_data.energy_cost *= modifiers.energy_cost.powi(level.into());
} }
if let Ok(Some(level)) = join_data.skill_set.skill_level(Skill::Climb(Speed)) { if let Ok(Some(level)) = join_data.skill_set.skill_level(Skill::Climb(Speed)) {
data.static_data.movement_speed *= 1.2_f32.powi(level.into()); data.static_data.movement_speed *= modifiers.speed.powi(level.into());
} }
data data
} }

View File

@ -98,6 +98,7 @@ impl Stage<f32> {
} }
} }
// TODO: name it as using knockback
pub fn modify_strike(mut self, knockback_mult: f32) -> Self { pub fn modify_strike(mut self, knockback_mult: f32) -> Self {
self.knockback *= knockback_mult; self.knockback *= knockback_mult;
self self

View File

@ -6,7 +6,7 @@ use crate::{
inventory::slot::{EquipSlot, Slot}, inventory::slot::{EquipSlot, Slot},
item::{Hands, ItemKind, Tool, ToolKind}, item::{Hands, ItemKind, Tool, ToolKind},
quadruped_low, quadruped_medium, quadruped_small, quadruped_low, quadruped_medium, quadruped_small,
skills::{Skill, SwimSkill}, skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind, theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
InventoryAction, StateUpdate, InventoryAction, StateUpdate,
}, },
@ -391,7 +391,8 @@ fn swim_move(
let mut water_accel = force / data.mass.0; let mut water_accel = force / data.mass.0;
if let Ok(Some(level)) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) { if let Ok(Some(level)) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) {
water_accel *= 1.25_f32.powi(level.into()); let modifiers = SKILL_MODIFIERS.general_tree.swim;
water_accel *= modifiers.speed.powi(level.into());
} }
let dir = if data.body.can_strafe() { let dir = if data.body.can_strafe() {

View File

@ -349,29 +349,32 @@ pub fn handle_mine_block(
xp_pools: HashSet::from_iter(vec![SkillGroupKind::Weapon(tool)]), xp_pools: HashSet::from_iter(vec![SkillGroupKind::Weapon(tool)]),
}); });
} }
use common::comp::skills::{MiningSkill, Skill}; use common::comp::skills::{MiningSkill, Skill, SKILL_MODIFIERS};
use rand::Rng; use rand::Rng;
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
if item.item_definition_id().contains("mineral.ore.")
&& rng.gen_bool( let need_double_ore = |rng: &mut rand::rngs::ThreadRng| {
0.05 * skillset let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.ore_gain);
.skill_level(Skill::Pick(MiningSkill::OreGain)) let skill_level =
.ok() skillset.skill_level_or(Skill::Pick(MiningSkill::OreGain), 0);
.flatten()
.unwrap_or(0) as f64, rng.gen_bool(chance_mod * f64::from(skill_level))
) };
{ let need_double_gem = |rng: &mut rand::rngs::ThreadRng| {
let _ = item.increase_amount(1); let chance_mod = f64::from(SKILL_MODIFIERS.mining_tree.gem_gain);
} let skill_level =
if item.item_definition_id().contains("mineral.gem.") skillset.skill_level_or(Skill::Pick(MiningSkill::GemGain), 0);
&& rng.gen_bool(
0.05 * skillset rng.gen_bool(chance_mod * f64::from(skill_level))
.skill_level(Skill::Pick(MiningSkill::GemGain)) };
.ok()
.flatten() let double_gain = (item.item_definition_id().contains("mineral.ore.")
.unwrap_or(0) as f64, && need_double_ore(&mut rng))
) || (item.item_definition_id().contains("mineral.gem.")
{ && need_double_gem(&mut rng));
if double_gain {
// Ignore non-stackable errors
let _ = item.increase_amount(1); let _ = item.increase_amount(1);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -325,10 +325,19 @@ widget_ids! {
// TODO: extend as you need it // TODO: extend as you need it
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum PositionSpecifier { pub enum PositionSpecifier {
MidBottomWithMarginOn(widget::Id, f64), // Place the widget near other widget with the given margins
TopLeftWithMarginsOn(widget::Id, f64, f64),
TopRightWithMarginsOn(widget::Id, f64, f64), TopRightWithMarginsOn(widget::Id, f64, f64),
BottomRightWithMarginsOn(widget::Id, f64, f64), MidBottomWithMarginOn(widget::Id, f64),
BottomLeftWithMarginsOn(widget::Id, f64, f64), BottomLeftWithMarginsOn(widget::Id, f64, f64),
BottomRightWithMarginsOn(widget::Id, f64, f64),
// Place the widget near other widget with given margin
MidTopWithMarginOn(widget::Id, f64),
// Place the widget near other widget at given distance
MiddleOf(widget::Id),
UpFrom(widget::Id, f64),
DownFrom(widget::Id, f64),
LeftFrom(widget::Id, f64),
RightFrom(widget::Id, f64), RightFrom(widget::Id, f64),
} }
@ -385,18 +394,31 @@ pub trait Position {
impl<W: Positionable> Position for W { impl<W: Positionable> Position for W {
fn position(self, request: PositionSpecifier) -> Self { fn position(self, request: PositionSpecifier) -> Self {
match request { match request {
PositionSpecifier::MidBottomWithMarginOn(other, margin) => { // Place the widget near other widget with the given margins
self.mid_bottom_with_margin_on(other, margin) PositionSpecifier::TopLeftWithMarginsOn(other, top, right) => {
self.top_left_with_margins_on(other, top, right)
}, },
PositionSpecifier::TopRightWithMarginsOn(other, top, right) => { PositionSpecifier::TopRightWithMarginsOn(other, top, right) => {
self.top_right_with_margins_on(other, top, right) self.top_right_with_margins_on(other, top, right)
}, },
PositionSpecifier::MidBottomWithMarginOn(other, margin) => {
self.mid_bottom_with_margin_on(other, margin)
},
PositionSpecifier::BottomRightWithMarginsOn(other, bottom, right) => { PositionSpecifier::BottomRightWithMarginsOn(other, bottom, right) => {
self.bottom_right_with_margins_on(other, bottom, right) self.bottom_right_with_margins_on(other, bottom, right)
}, },
PositionSpecifier::BottomLeftWithMarginsOn(other, bottom, left) => { PositionSpecifier::BottomLeftWithMarginsOn(other, bottom, left) => {
self.bottom_left_with_margins_on(other, bottom, left) self.bottom_left_with_margins_on(other, bottom, left)
}, },
// Place the widget near other widget with given margin
PositionSpecifier::MidTopWithMarginOn(other, margin) => {
self.mid_top_with_margin_on(other, margin)
},
// Place the widget near other widget at given distance
PositionSpecifier::MiddleOf(other) => self.middle_of(other),
PositionSpecifier::UpFrom(other, offset) => self.up_from(other, offset),
PositionSpecifier::DownFrom(other, offset) => self.down_from(other, offset),
PositionSpecifier::LeftFrom(other, offset) => self.left_from(other, offset),
PositionSpecifier::RightFrom(other, offset) => self.right_from(other, offset), PositionSpecifier::RightFrom(other, offset) => self.right_from(other, offset),
} }
} }
@ -4116,3 +4138,17 @@ pub fn angle_of_attack_text(
"Angle of Attack: Not moving".to_owned() "Angle of Attack: Not moving".to_owned()
} }
} }
/// Converts multiplier to percentage.
/// NOTE: floats are not the most precise type.
///
/// # Examples
/// ```
/// use veloren_voxygen::hud::multiplier_to_percentage;
///
/// let positive = multiplier_to_percentage(1.05);
/// assert!((positive - 5.0).abs() < 0.0001);
/// let negative = multiplier_to_percentage(0.85);
/// assert!((negative - (-15.0)).abs() < 0.0001);
/// ```
pub fn multiplier_to_percentage(value: f32) -> f32 { value * 100.0 - 100.0 }