diff --git a/common/src/combat.rs b/common/src/combat.rs new file mode 100644 index 0000000000..bebda0aa11 --- /dev/null +++ b/common/src/combat.rs @@ -0,0 +1,170 @@ +use crate::{ + comp::{HealthChange, HealthSource, Loadout}, + sync::Uid, + util::Dir, +}; +use serde::{Deserialize, Serialize}; +use vek::*; + +pub const BLOCK_EFFICIENCY: f32 = 0.9; + +/// Each section of this struct determines what damage is applied to a +/// particular target, using some identifier +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Damages { + /// Targets enemies, and all other creatures not in your group + pub enemy: Option, + /// Targets people in the same group as you, and any pets you have + pub group: Option, +} + +impl Damages { + pub fn new(enemy: Option, group: Option) -> Self { Damages { enemy, group } } + + pub fn get_damage(self, same_group: bool) -> Option { + if same_group { self.group } else { self.enemy } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Damage { + Melee(f32), + Healing(f32), + Projectile(f32), + Explosion(f32), + Falling(f32), + Shockwave(f32), + Energy(f32), +} + +impl Damage { + pub fn modify_damage( + self, + block: bool, + loadout: Option<&Loadout>, + uid: Option, + ) -> HealthChange { + match self { + Damage::Melee(damage) => { + let mut damage = damage; + // Critical hit + let mut critdamage = 0.0; + if rand::random() { + critdamage = damage * 0.3; + } + // Block + if block { + damage *= 1.0 - BLOCK_EFFICIENCY + } + // Armor + let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); + damage *= 1.0 - damage_reduction; + + // Critical damage applies after armor for melee + if (damage_reduction - 1.0).abs() > f32::EPSILON { + damage += critdamage; + } + + HealthChange { + amount: -damage as i32, + cause: HealthSource::Attack { by: uid.unwrap() }, + } + }, + Damage::Projectile(damage) => { + let mut damage = damage; + // Critical hit + if rand::random() { + damage *= 1.2; + } + // Block + if block { + damage *= 1.0 - BLOCK_EFFICIENCY + } + // Armor + let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); + damage *= 1.0 - damage_reduction; + + HealthChange { + amount: -damage as i32, + cause: HealthSource::Projectile { owner: uid }, + } + }, + Damage::Explosion(damage) => { + let mut damage = damage; + // Block + if block { + damage *= 1.0 - BLOCK_EFFICIENCY + } + // Armor + let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); + damage *= 1.0 - damage_reduction; + + HealthChange { + amount: -damage as i32, + cause: HealthSource::Explosion { owner: uid }, + } + }, + Damage::Shockwave(damage) => { + let mut damage = damage; + // Armor + let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); + damage *= 1.0 - damage_reduction; + + HealthChange { + amount: -damage as i32, + cause: HealthSource::Attack { by: uid.unwrap() }, + } + }, + Damage::Energy(damage) => { + let mut damage = damage; + // Armor + let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); + damage *= 1.0 - damage_reduction; + + HealthChange { + amount: -damage as i32, + cause: HealthSource::Energy { owner: uid }, + } + }, + Damage::Healing(heal) => HealthChange { + amount: heal as i32, + cause: HealthSource::Healing { by: uid }, + }, + Damage::Falling(damage) => { + let mut damage = damage; + // Armor + let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); + if (damage_reduction - 1.0).abs() < f32::EPSILON { + damage = 0.0; + } + HealthChange { + amount: -damage as i32, + cause: HealthSource::World, + } + }, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum Knockback { + Away(f32), + Towards(f32), + Up(f32), + TowardsUp(f32), +} + +impl Knockback { + pub fn calculate_impulse(self, dir: Dir) -> Vec3 { + match self { + Knockback::Away(strength) => strength * *Dir::slerp(dir, Dir::new(Vec3::unit_z()), 0.5), + Knockback::Towards(strength) => { + strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.5) + }, + Knockback::Up(strength) => strength * Vec3::unit_z(), + Knockback::TowardsUp(strength) => { + strength * *Dir::slerp(-dir, Dir::new(Vec3::unit_z()), 0.85) + }, + } + } +} diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 04cf6e708f..ab0f92cd5d 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -8,6 +8,7 @@ use crate::{ *, }, sys::character_behavior::JoinData, + Knockback, }; use arraygen::Arraygen; use serde::{Deserialize, Serialize}; @@ -59,16 +60,16 @@ pub enum CharacterAbility { BasicMelee { energy_cost: u32, buildup_duration: Duration, + swing_duration: Duration, recover_duration: Duration, - base_healthchange: i32, + base_damage: u32, knockback: f32, range: f32, max_angle: f32, }, BasicRanged { energy_cost: u32, - holdable: bool, - prepare_duration: Duration, + buildup_duration: Duration, recover_duration: Duration, projectile: Projectile, projectile_body: Body, @@ -91,7 +92,7 @@ pub enum CharacterAbility { reps_remaining: u32, }, Boost { - duration: Duration, + movement_duration: Duration, only_up: bool, }, DashMelee { @@ -169,7 +170,7 @@ pub enum CharacterAbility { max_damage: u32, initial_knockback: f32, max_knockback: f32, - prepare_duration: Duration, + buildup_duration: Duration, charge_duration: Duration, recover_duration: Duration, projectile_body: Body, @@ -184,7 +185,7 @@ pub enum CharacterAbility { swing_duration: Duration, recover_duration: Duration, damage: u32, - knockback: f32, + knockback: Knockback, shockwave_angle: f32, shockwave_vertical_angle: f32, shockwave_speed: f32, @@ -234,10 +235,13 @@ impl CharacterAbility { .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok(), - CharacterAbility::LeapMelee { energy_cost, .. } => update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok(), + CharacterAbility::LeapMelee { energy_cost, .. } => { + update.vel.0.z >= 0.0 + && update + .energy + .try_change_by(-(*energy_cost as i32), EnergySource::Ability) + .is_ok() + }, CharacterAbility::SpinMelee { energy_cost, .. } => update .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) @@ -250,10 +254,15 @@ impl CharacterAbility { .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) .is_ok(), - CharacterAbility::RepeaterRanged { energy_cost, .. } => update - .energy - .try_change_by(-(*energy_cost as i32), EnergySource::Ability) - .is_ok(), + CharacterAbility::RepeaterRanged { + energy_cost, leap, .. + } => { + (leap.is_none() || update.vel.0.z >= 0.0) + && update + .energy + .try_change_by(-(*energy_cost as i32), EnergySource::Ability) + .is_ok() + }, CharacterAbility::Shockwave { energy_cost, .. } => update .energy .try_change_by(-(*energy_cost as i32), EnergySource::Ability) @@ -356,24 +365,29 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { match ability { CharacterAbility::BasicMelee { buildup_duration, + swing_duration, recover_duration, - base_healthchange, + base_damage, knockback, range, max_angle, energy_cost: _, } => CharacterState::BasicMelee(basic_melee::Data { + static_data: basic_melee::StaticData { + buildup_duration: *buildup_duration, + swing_duration: *swing_duration, + recover_duration: *recover_duration, + base_damage: *base_damage, + knockback: *knockback, + range: *range, + max_angle: *max_angle, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, exhausted: false, - buildup_duration: *buildup_duration, - recover_duration: *recover_duration, - base_healthchange: *base_healthchange, - knockback: *knockback, - range: *range, - max_angle: *max_angle, }), CharacterAbility::BasicRanged { - holdable, - prepare_duration, + buildup_duration, recover_duration, projectile, projectile_body, @@ -382,21 +396,29 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { projectile_speed, energy_cost: _, } => CharacterState::BasicRanged(basic_ranged::Data { + static_data: basic_ranged::StaticData { + buildup_duration: *buildup_duration, + recover_duration: *recover_duration, + projectile: projectile.clone(), + projectile_body: *projectile_body, + projectile_light: *projectile_light, + projectile_gravity: *projectile_gravity, + projectile_speed: *projectile_speed, + ability_key: key, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, exhausted: false, - prepare_timer: Duration::default(), - holdable: *holdable, - prepare_duration: *prepare_duration, - recover_duration: *recover_duration, - projectile: projectile.clone(), - projectile_body: *projectile_body, - projectile_light: *projectile_light, - projectile_gravity: *projectile_gravity, - projectile_speed: *projectile_speed, - ability_key: key, }), - CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data { - duration: *duration, - only_up: *only_up, + CharacterAbility::Boost { + movement_duration, + only_up, + } => CharacterState::Boost(boost::Data { + static_data: boost::StaticData { + movement_duration: *movement_duration, + only_up: *only_up, + }, + timer: Duration::default(), }), CharacterAbility::DashMelee { energy_cost: _, @@ -431,14 +453,21 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { recover_duration: *recover_duration, is_interruptible: *is_interruptible, }, - end_charge: false, + auto_charge: false, timer: Duration::default(), + refresh_timer: Duration::default(), stage_section: StageSection::Buildup, exhausted: false, }), CharacterAbility::BasicBlock => CharacterState::BasicBlock, CharacterAbility::Roll => CharacterState::Roll(roll::Data { - remaining_duration: Duration::from_millis(500), + static_data: roll::StaticData { + buildup_duration: Duration::from_millis(100), + movement_duration: Duration::from_millis(300), + recover_duration: Duration::from_millis(100), + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, was_wielded: false, // false by default. utils might set it to true }), CharacterAbility::ComboMelee { @@ -566,7 +595,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { max_damage, initial_knockback, max_knockback, - prepare_duration, + buildup_duration, charge_duration, recover_duration, projectile_body, @@ -575,21 +604,24 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { initial_projectile_speed, max_projectile_speed, } => CharacterState::ChargedRanged(charged_ranged::Data { + static_data: charged_ranged::StaticData { + buildup_duration: *buildup_duration, + charge_duration: *charge_duration, + recover_duration: *recover_duration, + energy_drain: *energy_drain, + initial_damage: *initial_damage, + max_damage: *max_damage, + initial_knockback: *initial_knockback, + max_knockback: *max_knockback, + projectile_body: *projectile_body, + projectile_light: *projectile_light, + projectile_gravity: *projectile_gravity, + initial_projectile_speed: *initial_projectile_speed, + max_projectile_speed: *max_projectile_speed, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, exhausted: false, - energy_drain: *energy_drain, - initial_damage: *initial_damage, - max_damage: *max_damage, - initial_knockback: *initial_knockback, - max_knockback: *max_knockback, - prepare_duration: *prepare_duration, - charge_duration: *charge_duration, - charge_timer: Duration::default(), - recover_duration: *recover_duration, - projectile_body: *projectile_body, - projectile_light: *projectile_light, - projectile_gravity: *projectile_gravity, - initial_projectile_speed: *initial_projectile_speed, - max_projectile_speed: *max_projectile_speed, }), CharacterAbility::RepeaterRanged { energy_cost: _, diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 1a0d311ea4..2c15b7fede 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -1,4 +1,4 @@ -use crate::sync::Uid; +use crate::{sync::Uid, Damages}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -8,8 +8,7 @@ use std::time::Duration; pub struct Properties { pub angle: f32, pub speed: f32, - pub damage: u32, - pub heal: u32, + pub damages: Damages, pub lifesteal_eff: f32, pub energy_regen: u32, pub energy_cost: u32, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 53ecc899c3..f439fbcfdf 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -3,6 +3,7 @@ use crate::{ event::{LocalEvent, ServerEvent}, states::*, sys::character_behavior::JoinData, + Damages, Knockback, }; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage, VecStorage}; @@ -152,13 +153,12 @@ impl Component for CharacterState { #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Attacking { - pub base_damage: u32, - pub base_heal: u32, + pub damages: Damages, pub range: f32, pub max_angle: f32, pub applied: bool, pub hit_count: u32, - pub knockback: f32, + pub knockback: Knockback, } impl Component for Attacking { diff --git a/common/src/comp/damage.rs b/common/src/comp/damage.rs deleted file mode 100644 index 6fae9d3e71..0000000000 --- a/common/src/comp/damage.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::comp::Loadout; -use serde::{Deserialize, Serialize}; - -pub const BLOCK_EFFICIENCY: f32 = 0.9; - -pub struct Damage { - pub healthchange: f32, - pub source: DamageSource, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum DamageSource { - Melee, - Healing, - Projectile, - Explosion, - Falling, - Shockwave, - Energy, -} - -impl Damage { - pub fn modify_damage(&mut self, block: bool, loadout: &Loadout) { - match self.source { - DamageSource::Melee => { - // Critical hit - let mut critdamage = 0.0; - if rand::random() { - critdamage = self.healthchange * 0.3; - } - // Block - if block { - self.healthchange *= 1.0 - BLOCK_EFFICIENCY - } - // Armor - let damage_reduction = loadout.get_damage_reduction(); - self.healthchange *= 1.0 - damage_reduction; - - // Critical damage applies after armor for melee - if (damage_reduction - 1.0).abs() > f32::EPSILON { - self.healthchange += critdamage; - } - }, - DamageSource::Projectile => { - // Critical hit - if rand::random() { - self.healthchange *= 1.2; - } - // Block - if block { - self.healthchange *= 1.0 - BLOCK_EFFICIENCY - } - // Armor - let damage_reduction = loadout.get_damage_reduction(); - self.healthchange *= 1.0 - damage_reduction; - }, - DamageSource::Explosion => { - // Block - if block { - self.healthchange *= 1.0 - BLOCK_EFFICIENCY - } - // Armor - let damage_reduction = loadout.get_damage_reduction(); - self.healthchange *= 1.0 - damage_reduction; - }, - DamageSource::Shockwave => { - // Armor - let damage_reduction = loadout.get_damage_reduction(); - self.healthchange *= 1.0 - damage_reduction; - }, - DamageSource::Energy => { - // Armor - let damage_reduction = loadout.get_damage_reduction(); - self.healthchange *= 1.0 - damage_reduction; - }, - _ => {}, - } - } -} diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 9e847bce6d..3b4ab95573 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -4,7 +4,7 @@ use crate::{ comp::{body::object, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile}, states::combo_melee, - Explosion, + Damage, Damages, Explosion, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -174,15 +174,15 @@ impl Tool { DashMelee { energy_cost: 200, base_damage: (120.0 * self.base_power()) as u32, - max_damage: (260.0 * self.base_power()) as u32, - base_knockback: 10.0, - max_knockback: 20.0, + max_damage: (240.0 * self.base_power()) as u32, + base_knockback: 8.0, + max_knockback: 15.0, range: 5.0, angle: 45.0, energy_drain: 500, forward_speed: 4.0, buildup_duration: Duration::from_millis(250), - charge_duration: Duration::from_millis(400), + charge_duration: Duration::from_millis(600), swing_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(500), infinite_charge: true, @@ -206,9 +206,10 @@ impl Tool { Axe(_) => vec![ BasicMelee { energy_cost: 0, - buildup_duration: Duration::from_millis(700), + buildup_duration: Duration::from_millis(600), + swing_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(300), - base_healthchange: (-120.0 * self.base_power()) as i32, + base_damage: (120.0 * self.base_power()) as u32, knockback: 0.0, range: 3.5, max_angle: 20.0, @@ -244,9 +245,10 @@ impl Tool { Hammer(_) => vec![ BasicMelee { energy_cost: 0, - buildup_duration: Duration::from_millis(700), + buildup_duration: Duration::from_millis(600), + swing_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(300), - base_healthchange: (-120.0 * self.base_power()) as i32, + base_damage: (120.0 * self.base_power()) as u32, knockback: 0.0, range: 3.5, max_angle: 20.0, @@ -280,9 +282,10 @@ impl Tool { ], Farming(_) => vec![BasicMelee { energy_cost: 1, - buildup_duration: Duration::from_millis(700), + buildup_duration: Duration::from_millis(600), + swing_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(150), - base_healthchange: (-50.0 * self.base_power()) as i32, + base_damage: (50.0 * self.base_power()) as u32, knockback: 0.0, range: 3.5, max_angle: 20.0, @@ -290,14 +293,16 @@ impl Tool { Bow(_) => vec![ BasicRanged { energy_cost: 0, - holdable: true, - prepare_duration: Duration::from_millis(100), + buildup_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(400), projectile: Projectile { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ - projectile::Effect::Damage((-40.0 * self.base_power()) as i32), - projectile::Effect::Knockback(10.0), + projectile::Effect::Damages(Damages::new( + Some(Damage::Projectile(40.0 * self.base_power())), + None, + )), + projectile::Effect::Knockback(Knockback::Away(10.0)), projectile::Effect::RewardEnergy(50), projectile::Effect::Vanish, ], @@ -317,7 +322,7 @@ impl Tool { max_damage: (200.0 * self.base_power()) as u32, initial_knockback: 10.0, max_knockback: 20.0, - prepare_duration: Duration::from_millis(100), + buildup_duration: Duration::from_millis(100), charge_duration: Duration::from_millis(1500), recover_duration: Duration::from_millis(500), projectile_body: Body::Object(object::Body::MultiArrow), @@ -332,13 +337,15 @@ impl Tool { buildup_duration: Duration::from_millis(200), shoot_duration: Duration::from_millis(200), recover_duration: Duration::from_millis(800), - leap: Some(10.0), + leap: Some(5.0), projectile: Projectile { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ - projectile::Effect::Damage((-40.0 * self.base_power()) as i32), - projectile::Effect::Knockback(10.0), - projectile::Effect::RewardEnergy(50), + projectile::Effect::Damages(Damages::new( + Some(Damage::Projectile(40.0 * self.base_power())), + None, + )), + projectile::Effect::Knockback(Knockback::Away(10.0)), projectile::Effect::Vanish, ], time_left: Duration::from_secs(15), @@ -355,8 +362,9 @@ impl Tool { Dagger(_) => vec![BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(100), - recover_duration: Duration::from_millis(400), - base_healthchange: (-50.0 * self.base_power()) as i32, + swing_duration: Duration::from_millis(100), + recover_duration: Duration::from_millis(300), + base_damage: (50.0 * self.base_power()) as u32, knockback: 0.0, range: 3.5, max_angle: 20.0, @@ -378,8 +386,7 @@ impl Tool { }, BasicRanged { energy_cost: 800, - holdable: true, - prepare_duration: Duration::from_millis(800), + buildup_duration: Duration::from_millis(800), recover_duration: Duration::from_millis(50), projectile: Projectile { hit_solid: vec![ @@ -422,8 +429,7 @@ impl Tool { Staff(_) => vec![ BasicRanged { energy_cost: 0, - holdable: false, - prepare_duration: Duration::from_millis(500), + buildup_duration: Duration::from_millis(500), recover_duration: Duration::from_millis(350), projectile: Projectile { hit_solid: vec![ @@ -482,7 +488,7 @@ impl Tool { swing_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(300), damage: (200.0 * self.base_power()) as u32, - knockback: 25.0, + knockback: Knockback::Away(25.0), shockwave_angle: 360.0, shockwave_vertical_angle: 90.0, shockwave_speed: 20.0, @@ -495,8 +501,9 @@ impl Tool { BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(100), - recover_duration: Duration::from_millis(400), - base_healthchange: (-40.0 * self.base_power()) as i32, + swing_duration: Duration::from_millis(100), + recover_duration: Duration::from_millis(300), + base_damage: (40.0 * self.base_power()) as u32, knockback: 0.0, range: 3.0, max_angle: 120.0, @@ -508,10 +515,11 @@ impl Tool { vec![ BasicMelee { energy_cost: 0, - buildup_duration: Duration::from_millis(500), + buildup_duration: Duration::from_millis(400), + swing_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(250), knockback: 25.0, - base_healthchange: -200, + base_damage: 200, range: 5.0, max_angle: 120.0, }, @@ -521,7 +529,7 @@ impl Tool { swing_duration: Duration::from_millis(200), recover_duration: Duration::from_millis(800), damage: 500, - knockback: -40.0, + knockback: Knockback::TowardsUp(40.0), shockwave_angle: 90.0, shockwave_vertical_angle: 15.0, shockwave_speed: 20.0, @@ -533,10 +541,11 @@ impl Tool { } else if kind == "BeastClaws" { vec![BasicMelee { energy_cost: 0, - buildup_duration: Duration::from_millis(500), + buildup_duration: Duration::from_millis(250), + swing_duration: Duration::from_millis(250), recover_duration: Duration::from_millis(250), knockback: 25.0, - base_healthchange: -200, + base_damage: 200, range: 5.0, max_angle: 120.0, }] @@ -544,58 +553,50 @@ impl Tool { vec![BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(100), - recover_duration: Duration::from_millis(300), - base_healthchange: -10, + swing_duration: Duration::from_millis(100), + recover_duration: Duration::from_millis(200), + base_damage: 10, knockback: 0.0, range: 1.0, max_angle: 30.0, }] } }, - Debug(kind) => { - if kind == "Boost" { - vec![ - CharacterAbility::Boost { - duration: Duration::from_millis(50), - only_up: false, - }, - CharacterAbility::Boost { - duration: Duration::from_millis(50), - only_up: true, - }, - BasicRanged { - energy_cost: 0, - holdable: false, - prepare_duration: Duration::from_millis(0), - recover_duration: Duration::from_millis(10), - projectile: Projectile { - hit_solid: vec![projectile::Effect::Stick], - hit_entity: vec![ - projectile::Effect::Stick, - projectile::Effect::Possess, - ], - time_left: Duration::from_secs(10), - owner: None, - ignore_group: false, - }, - projectile_body: Body::Object(object::Body::ArrowSnake), - projectile_light: Some(LightEmitter { - col: (0.0, 1.0, 0.33).into(), - ..Default::default() - }), - projectile_gravity: None, - projectile_speed: 100.0, - }, - ] - } else { - vec![] - } - }, + Debug(_) => vec![ + CharacterAbility::Boost { + movement_duration: Duration::from_millis(50), + only_up: false, + }, + CharacterAbility::Boost { + movement_duration: Duration::from_millis(50), + only_up: true, + }, + BasicRanged { + energy_cost: 0, + buildup_duration: Duration::from_millis(0), + recover_duration: Duration::from_millis(10), + projectile: Projectile { + hit_solid: vec![projectile::Effect::Stick], + hit_entity: vec![projectile::Effect::Stick, projectile::Effect::Possess], + time_left: Duration::from_secs(10), + owner: None, + ignore_group: false, + }, + projectile_body: Body::Object(object::Body::ArrowSnake), + projectile_light: Some(LightEmitter { + col: (0.0, 1.0, 0.33).into(), + ..Default::default() + }), + projectile_gravity: None, + projectile_speed: 100.0, + }, + ], Empty => vec![BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(0), - recover_duration: Duration::from_millis(1000), - base_healthchange: -20, + swing_duration: Duration::from_millis(100), + recover_duration: Duration::from_millis(900), + base_damage: 20, knockback: 0.0, range: 3.5, max_angle: 15.0, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 329546a54e..3aa9556d99 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -7,7 +7,6 @@ pub mod buff; mod character_state; pub mod chat; mod controller; -mod damage; mod energy; pub mod group; mod inputs; @@ -44,7 +43,6 @@ pub use controller::{ Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input, InventoryManip, MountState, Mounting, }; -pub use damage::{Damage, DamageSource}; pub use energy::{Energy, EnergySource}; pub use group::Group; pub use inputs::CanBuild; diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index 6241af9c01..3b5888a456 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -1,4 +1,4 @@ -use crate::{sync::Uid, Explosion}; +use crate::{sync::Uid, Damages, Explosion, Knockback}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -6,8 +6,8 @@ use std::time::Duration; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Effect { - Damage(i32), - Knockback(f32), + Damages(Damages), + Knockback(Knockback), RewardEnergy(u32), Explode(Explosion), Vanish, diff --git a/common/src/comp/shockwave.rs b/common/src/comp/shockwave.rs index 78e38e44bf..2d5de87726 100644 --- a/common/src/comp/shockwave.rs +++ b/common/src/comp/shockwave.rs @@ -1,4 +1,4 @@ -use crate::sync::Uid; +use crate::{sync::Uid, Damages, Knockback}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -9,8 +9,8 @@ pub struct Properties { pub angle: f32, pub vertical_angle: f32, pub speed: f32, - pub damage: u32, - pub knockback: f32, + pub damages: Damages, + pub knockback: Knockback, pub requires_ground: bool, pub duration: Duration, pub owner: Option, diff --git a/common/src/lib.rs b/common/src/lib.rs index 4ea0d8e30c..01b986408a 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -22,6 +22,7 @@ pub mod astar; pub mod character; pub mod clock; pub mod cmd; +pub mod combat; pub mod comp; pub mod effect; pub mod event; @@ -51,5 +52,6 @@ pub mod util; pub mod vol; pub mod volumes; +pub use combat::{Damage, Damages, Knockback}; pub use explosion::Explosion; pub use loadout_builder::LoadoutBuilder; diff --git a/common/src/loadout_builder.rs b/common/src/loadout_builder.rs index 3fcb1c2fbb..5511a7826d 100644 --- a/common/src/loadout_builder.rs +++ b/common/src/loadout_builder.rs @@ -163,8 +163,9 @@ impl LoadoutBuilder { ability1: Some(CharacterAbility::BasicMelee { energy_cost: 0, buildup_duration: Duration::from_millis(0), - recover_duration: Duration::from_millis(400), - base_healthchange: -40, + swing_duration: Duration::from_millis(100), + recover_duration: Duration::from_millis(300), + base_damage: 40, knockback: 0.0, range: 3.5, max_angle: 15.0, @@ -343,9 +344,10 @@ impl LoadoutBuilder { item: Item::new_from_asset_expect("common.items.weapons.empty.empty"), ability1: Some(CharacterAbility::BasicMelee { energy_cost: 10, - buildup_duration: Duration::from_millis(600), + buildup_duration: Duration::from_millis(500), + swing_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(100), - base_healthchange: -(body.base_dmg() as i32), + base_damage: body.base_dmg(), knockback: 0.0, range: body.base_range(), max_angle: 20.0, diff --git a/common/src/metrics.rs b/common/src/metrics.rs index 672f13d7c5..e2ec3ec27e 100644 --- a/common/src/metrics.rs +++ b/common/src/metrics.rs @@ -9,5 +9,5 @@ pub struct SysMetrics { pub stats_ns: AtomicI64, pub phys_ns: AtomicI64, pub projectile_ns: AtomicI64, - pub combat_ns: AtomicI64, + pub melee_ns: AtomicI64, } diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index e215fae41f..4a387ba7da 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -4,6 +4,7 @@ use crate::{ states::utils::*, sync::Uid, sys::character_behavior::{CharacterBehavior, JoinData}, + Damage, Damages, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -73,14 +74,12 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.buildup_duration { // Build up update.character = CharacterState::BasicBeam(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, particle_ori: Some(*data.inputs.look_dir), - offset: self.offset, + ..*self }); } else { // Creates beam @@ -96,11 +95,11 @@ impl CharacterBehavior for Data { }; // Build up update.character = CharacterState::BasicBeam(Data { - static_data: self.static_data, timer: Duration::default(), stage_section: StageSection::Cast, particle_ori: Some(*data.inputs.look_dir), offset: eye_height * 0.55, + ..*self }); } }, @@ -108,10 +107,12 @@ impl CharacterBehavior for Data { if ability_key_is_pressed(data, self.static_data.ability_key) && (self.static_data.energy_drain == 0 || update.energy.current() > 0) { - let damage = - (self.static_data.base_dps as f32 / self.static_data.tick_rate) as u32; - let heal = - (self.static_data.base_hps as f32 / self.static_data.tick_rate) as u32; + let damage = Damage::Energy( + self.static_data.base_dps as f32 / self.static_data.tick_rate, + ); + let heal = Damage::Healing( + self.static_data.base_hps as f32 / self.static_data.tick_rate, + ); let energy_regen = (self.static_data.energy_regen as f32 / self.static_data.tick_rate) as u32; let energy_cost = @@ -121,8 +122,7 @@ impl CharacterBehavior for Data { let properties = beam::Properties { angle: self.static_data.max_angle.to_radians(), speed, - damage, - heal, + damages: Damages::new(Some(damage), Some(heal)), lifesteal_eff: self.static_data.lifesteal_eff, energy_regen, energy_cost, @@ -137,14 +137,12 @@ impl CharacterBehavior for Data { ori: Ori(data.inputs.look_dir), }); update.character = CharacterState::BasicBeam(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, particle_ori: Some(*data.inputs.look_dir), - offset: self.offset, + ..*self }); // Consumes energy if there's enough left and ability key is held down @@ -154,25 +152,22 @@ impl CharacterBehavior for Data { ); } else { update.character = CharacterState::BasicBeam(Data { - static_data: self.static_data, timer: Duration::default(), stage_section: StageSection::Recover, particle_ori: Some(*data.inputs.look_dir), - offset: self.offset, + ..*self }); } }, StageSection::Recover => { if self.timer < self.static_data.recover_duration { update.character = CharacterState::BasicBeam(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, particle_ori: Some(*data.inputs.look_dir), - offset: self.offset, + ..*self }); } else { // Done diff --git a/common/src/states/basic_melee.rs b/common/src/states/basic_melee.rs index 0d98e0aeac..228a10a204 100644 --- a/common/src/states/basic_melee.rs +++ b/common/src/states/basic_melee.rs @@ -2,24 +2,39 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, + Damage, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; +/// Separated out to condense update portions of character state #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Data { +pub struct StaticData { /// How long until state should deal damage pub buildup_duration: Duration, + /// How long the state is swinging for + pub swing_duration: Duration, /// How long the state has until exiting pub recover_duration: Duration, - /// Base damage (negative) or healing (positive) - pub base_healthchange: i32, + /// Base damage + pub base_damage: u32, /// Knockback pub knockback: f32, /// Max range pub range: f32, /// Max angle (45.0 will give you a 90.0 angle window) pub max_angle: f32, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, + /// What section the character stage is in + pub stage_section: StageSection, /// Whether the attack can deal more damage pub exhausted: bool, } @@ -31,65 +46,87 @@ impl CharacterBehavior for Data { handle_move(data, &mut update, 0.7); handle_jump(data, &mut update); - if self.buildup_duration != Duration::default() { - // Build up - update.character = CharacterState::BasicMelee(Data { - buildup_duration: self - .buildup_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - recover_duration: self.recover_duration, - base_healthchange: self.base_healthchange, - knockback: self.knockback, - range: self.range, - max_angle: self.max_angle, - exhausted: false, - }); - } else if !self.exhausted { - let (damage, heal) = if self.base_healthchange > 0 { - (0, self.base_healthchange as u32) - } else { - ((-self.base_healthchange) as u32, 0) - }; - // Hit attempt - data.updater.insert(data.entity, Attacking { - base_damage: damage, - base_heal: heal, - range: self.range, - max_angle: self.max_angle.to_radians(), - applied: false, - hit_count: 0, - knockback: self.knockback, - }); + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::BasicMelee(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Transitions to swing section of stage + update.character = CharacterState::BasicMelee(Data { + timer: Duration::default(), + stage_section: StageSection::Swing, + ..*self + }); + } + }, + StageSection::Swing => { + if !self.exhausted { + update.character = CharacterState::BasicMelee(Data { + timer: Duration::default(), + exhausted: true, + ..*self + }); - update.character = CharacterState::BasicMelee(Data { - buildup_duration: self.buildup_duration, - recover_duration: self.recover_duration, - base_healthchange: self.base_healthchange, - knockback: self.knockback, - range: self.range, - max_angle: self.max_angle, - exhausted: true, - }); - } else if self.recover_duration != Duration::default() { - // Recovery - update.character = CharacterState::BasicMelee(Data { - buildup_duration: self.buildup_duration, - recover_duration: self - .recover_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - base_healthchange: self.base_healthchange, - knockback: self.knockback, - range: self.range, - max_angle: self.max_angle, - exhausted: true, - }); - } else { - // Done - update.character = CharacterState::Wielding; - // Make sure attack component is removed - data.updater.remove::(data.entity); + // Hit attempt + data.updater.insert(data.entity, Attacking { + damages: Damages::new( + Some(Damage::Melee(self.static_data.base_damage as f32)), + None, + ), + range: self.static_data.range, + max_angle: 180_f32.to_radians(), + applied: false, + hit_count: 0, + knockback: Knockback::Away(self.static_data.knockback), + }); + } else if self.timer < self.static_data.swing_duration { + // Swings + update.character = CharacterState::BasicMelee(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Transitions to recover section of stage + update.character = CharacterState::BasicMelee(Data { + timer: Duration::default(), + stage_section: StageSection::Recover, + ..*self + }); + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + // Recovery + update.character = CharacterState::BasicMelee(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Done + update.character = CharacterState::Wielding; + // Make sure attack component is removed + data.updater.remove::(data.entity); + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + update.character = CharacterState::Wielding; + // Make sure attack component is removed + data.updater.remove::(data.entity); + }, } // Grant energy on successful hit diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index cbfc6afc7a..19fff5e2b5 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -7,27 +7,36 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::time::Duration; +/// Separated out to condense update portions of character state #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Data { - /// Can you hold the ability beyond the prepare duration - pub holdable: bool, - /// How long we have to prepare the weapon - pub prepare_duration: Duration, - /// How long we prepared the weapon already - pub prepare_timer: Duration, +pub struct StaticData { + /// How much buildup is required before the attack + pub buildup_duration: Duration, /// How long the state has until exiting pub recover_duration: Duration, + /// Projectile variables pub projectile: Projectile, pub projectile_body: Body, pub projectile_light: Option, pub projectile_gravity: Option, pub projectile_speed: f32, - /// Whether the attack fired already - pub exhausted: bool, /// What key is used to press ability pub ability_key: AbilityKey, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, + /// What section the character stage is in + pub stage_section: StageSection, + /// Whether the attack fired already + pub exhausted: bool, +} + impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); @@ -35,77 +44,67 @@ impl CharacterBehavior for Data { handle_move(data, &mut update, 0.3); handle_jump(data, &mut update); - if !self.exhausted - && if self.holdable { - ability_key_is_pressed(data, self.ability_key) - || self.prepare_timer < self.prepare_duration - } else { - self.prepare_timer < self.prepare_duration - } - { - // Prepare (draw the bow) - update.character = CharacterState::BasicRanged(Data { - prepare_timer: self.prepare_timer + Duration::from_secs_f32(data.dt.0), - holdable: self.holdable, - prepare_duration: self.prepare_duration, - recover_duration: self.recover_duration, - projectile: self.projectile.clone(), - projectile_body: self.projectile_body, - projectile_light: self.projectile_light, - projectile_gravity: self.projectile_gravity, - projectile_speed: self.projectile_speed, - exhausted: false, - ability_key: self.ability_key, - }); - } else if !self.exhausted { - // Fire - let mut projectile = self.projectile.clone(); - projectile.owner = Some(*data.uid); - update.server_events.push_front(ServerEvent::Shoot { - entity: data.entity, - dir: data.inputs.look_dir, - body: self.projectile_body, - projectile, - light: self.projectile_light, - gravity: self.projectile_gravity, - speed: self.projectile_speed, - }); + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::BasicRanged(Data { + static_data: self.static_data.clone(), + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Transitions to recover section of stage + update.character = CharacterState::BasicRanged(Data { + static_data: self.static_data.clone(), + timer: Duration::default(), + stage_section: StageSection::Recover, + ..*self + }); + } + }, + StageSection::Recover => { + if !self.exhausted { + // Fire + let mut projectile = self.static_data.projectile.clone(); + projectile.owner = Some(*data.uid); + update.server_events.push_front(ServerEvent::Shoot { + entity: data.entity, + dir: data.inputs.look_dir, + body: self.static_data.projectile_body, + projectile, + light: self.static_data.projectile_light, + gravity: self.static_data.projectile_gravity, + speed: self.static_data.projectile_speed, + }); - update.character = CharacterState::BasicRanged(Data { - prepare_timer: self.prepare_timer, - holdable: self.holdable, - prepare_duration: self.prepare_duration, - recover_duration: self.recover_duration, - projectile: self.projectile.clone(), - projectile_body: self.projectile_body, - projectile_light: self.projectile_light, - projectile_gravity: self.projectile_gravity, - projectile_speed: self.projectile_speed, - exhausted: true, - ability_key: self.ability_key, - }); - } else if self.recover_duration != Duration::default() { - // Recovery - update.character = CharacterState::BasicRanged(Data { - prepare_timer: self.prepare_timer, - holdable: self.holdable, - prepare_duration: self.prepare_duration, - recover_duration: self - .recover_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - projectile: self.projectile.clone(), - projectile_body: self.projectile_body, - projectile_light: self.projectile_light, - projectile_gravity: self.projectile_gravity, - projectile_speed: self.projectile_speed, - exhausted: true, - ability_key: self.ability_key, - }); - return update; - } else { - // Done - update.character = CharacterState::Wielding; + update.character = CharacterState::BasicRanged(Data { + static_data: self.static_data.clone(), + exhausted: true, + ..*self + }); + } else if self.timer < self.static_data.recover_duration { + // Recovers + update.character = CharacterState::BasicRanged(Data { + static_data: self.static_data.clone(), + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Done + update.character = CharacterState::Wielding; + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + update.character = CharacterState::Wielding; + }, } update diff --git a/common/src/states/boost.rs b/common/src/states/boost.rs index 52f1994152..4b0110c605 100644 --- a/common/src/states/boost.rs +++ b/common/src/states/boost.rs @@ -1,16 +1,25 @@ use crate::{ - comp::{Attacking, CharacterState, EnergySource, StateUpdate}, + comp::{CharacterState, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, }; use serde::{Deserialize, Serialize}; use std::time::Duration; +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + pub movement_duration: Duration, + pub only_up: bool, +} + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data { - /// How long the state has until exiting - pub duration: Duration, - pub only_up: bool, + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, } impl CharacterBehavior for Data { @@ -19,34 +28,25 @@ impl CharacterBehavior for Data { handle_move(data, &mut update, 1.0); - // Still going - if self.duration != Duration::default() { - if self.only_up { + if self.timer < self.static_data.movement_duration { + // Movement + if self.static_data.only_up { update.vel.0.z += 500.0 * data.dt.0; } else { update.vel.0 += *data.inputs.look_dir * 500.0 * data.dt.0; } update.character = CharacterState::Boost(Data { - duration: self - .duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - only_up: self.only_up, + ..*self }); - } - // Done - else { + } else { + // Done update.character = CharacterState::Wielding; } - // Grant energy on successful hit - if let Some(attack) = data.attacking { - if attack.applied && attack.hit_count > 0 { - data.updater.remove::(data.entity); - update.energy.change_by(100, EnergySource::HitEnemy); - } - } - update } } diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index e266fe99bf..e0255e89a4 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -2,6 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::{StageSection, *}, sys::character_behavior::*, + Damage, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -67,14 +68,12 @@ impl CharacterBehavior for Data { // Charge the attack update.character = CharacterState::ChargedMelee(Data { - static_data: self.static_data, - stage_section: self.stage_section, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - exhausted: self.exhausted, charge_amount: charge, + ..*self }); // Consumes energy if there's enough left and RMB is held down @@ -87,14 +86,11 @@ impl CharacterBehavior for Data { { // Maintains charge update.character = CharacterState::ChargedMelee(Data { - static_data: self.static_data, - stage_section: self.stage_section, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - exhausted: self.exhausted, - charge_amount: self.charge_amount, + ..*self }); // Consumes energy if there's enough left and RMB is held down @@ -105,65 +101,55 @@ impl CharacterBehavior for Data { } else { // Transitions to swing update.character = CharacterState::ChargedMelee(Data { - static_data: self.static_data, stage_section: StageSection::Swing, timer: Duration::default(), - exhausted: self.exhausted, - charge_amount: self.charge_amount, + ..*self }); } }, StageSection::Swing => { if !self.exhausted { - let damage = self.static_data.initial_damage - + ((self.static_data.max_damage - self.static_data.initial_damage) as f32 - * self.charge_amount) as u32; + let damage = self.static_data.initial_damage as f32 + + (self.static_data.max_damage - self.static_data.initial_damage) as f32 + * self.charge_amount; let knockback = self.static_data.initial_knockback + (self.static_data.max_knockback - self.static_data.initial_knockback) * self.charge_amount; // Hit attempt data.updater.insert(data.entity, Attacking { - base_damage: damage as u32, - base_heal: 0, + damages: Damages::new(Some(Damage::Melee(damage)), None), range: self.static_data.range, max_angle: self.static_data.max_angle.to_radians(), applied: false, hit_count: 0, - knockback, + knockback: Knockback::Away(knockback), }); // Starts swinging update.character = CharacterState::ChargedMelee(Data { - static_data: self.static_data, - stage_section: self.stage_section, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), exhausted: true, - charge_amount: self.charge_amount, + ..*self }); } else if self.timer < self.static_data.swing_duration { // Swings update.character = CharacterState::ChargedMelee(Data { - static_data: self.static_data, - stage_section: self.stage_section, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - exhausted: self.exhausted, - charge_amount: self.charge_amount, + ..*self }); } else { // Transitions to recover update.character = CharacterState::ChargedMelee(Data { - static_data: self.static_data, stage_section: StageSection::Recover, timer: Duration::default(), - exhausted: self.exhausted, - charge_amount: self.charge_amount, + ..*self }); } }, @@ -171,14 +157,11 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.recover_duration { // Recovers update.character = CharacterState::ChargedMelee(Data { - static_data: self.static_data, - stage_section: self.stage_section, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - exhausted: self.exhausted, - charge_amount: self.charge_amount, + ..*self }); } else { // Done diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index 1386b8b4d5..036f9ac5bc 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -6,14 +6,20 @@ use crate::{ event::ServerEvent, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, + Damage, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Data { - /// Whether the attack fired already - pub exhausted: bool, +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// How long the weapon needs to be prepared for + pub buildup_duration: Duration, + /// How long it takes to charge the weapon to max damage and knockback + pub charge_duration: Duration, + /// How long the state has until exiting + pub recover_duration: Duration, /// How much energy is drained per second when charging pub energy_drain: u32, /// How much damage is dealt with no charge @@ -24,14 +30,6 @@ pub struct Data { pub initial_knockback: f32, /// How much knockback there is at max charge pub max_knockback: f32, - /// How long the weapon needs to be prepared for - pub prepare_duration: Duration, - /// How long it takes to charge the weapon to max damage and knockback - pub charge_duration: Duration, - /// How long the state has been charging - pub charge_timer: Duration, - /// How long the state has until exiting - pub recover_duration: Duration, /// Projectile information pub projectile_body: Body, pub projectile_light: Option, @@ -40,6 +38,19 @@ pub struct Data { pub max_projectile_speed: f32, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, + /// What section the character stage is in + pub stage_section: StageSection, + /// Whether the attack fired already + pub exhausted: bool, +} + impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); @@ -47,160 +58,127 @@ impl CharacterBehavior for Data { handle_move(data, &mut update, 0.3); handle_jump(data, &mut update); - if self.prepare_duration != Duration::default() { - // Prepare (draw the bow) - update.character = CharacterState::ChargedRanged(Data { - exhausted: self.exhausted, - energy_drain: self.energy_drain, - initial_damage: self.initial_damage, - max_damage: self.max_damage, - initial_knockback: self.initial_knockback, - max_knockback: self.max_knockback, - prepare_duration: self - .prepare_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - charge_duration: self.charge_duration, - charge_timer: self.charge_timer, - recover_duration: self.recover_duration, - projectile_body: self.projectile_body, - projectile_light: self.projectile_light, - projectile_gravity: self.projectile_gravity, - initial_projectile_speed: self.initial_projectile_speed, - max_projectile_speed: self.max_projectile_speed, - }); - } else if data.inputs.secondary.is_pressed() - && self.charge_timer < self.charge_duration - && update.energy.current() > 0 - { - // Charge the bow - update.character = CharacterState::ChargedRanged(Data { - exhausted: self.exhausted, - energy_drain: self.energy_drain, - initial_damage: self.initial_damage, - max_damage: self.max_damage, - initial_knockback: self.initial_knockback, - max_knockback: self.max_knockback, - prepare_duration: self.prepare_duration, - charge_timer: self - .charge_timer - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - charge_duration: self.charge_duration, - recover_duration: self.recover_duration, - projectile_body: self.projectile_body, - projectile_light: self.projectile_light, - projectile_gravity: self.projectile_gravity, - initial_projectile_speed: self.initial_projectile_speed, - max_projectile_speed: self.max_projectile_speed, - }); + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::ChargedRanged(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Transitions to swing section of stage + update.character = CharacterState::ChargedRanged(Data { + timer: Duration::default(), + stage_section: StageSection::Charge, + ..*self + }); + } + }, + StageSection::Charge => { + if !data.inputs.secondary.is_pressed() && !self.exhausted { + let charge_frac = (self.timer.as_secs_f32() + / self.static_data.charge_duration.as_secs_f32()) + .min(1.0); + let damage = self.static_data.initial_damage as f32 + + (charge_frac + * (self.static_data.max_damage - self.static_data.initial_damage) + as f32); + let knockback = self.static_data.initial_knockback as f32 + + (charge_frac + * (self.static_data.max_knockback - self.static_data.initial_knockback) + as f32); + // Fire + let mut projectile = Projectile { + hit_solid: vec![projectile::Effect::Stick], + hit_entity: vec![ + projectile::Effect::Damages(Damages::new( + Some(Damage::Projectile(damage)), + None, + )), + projectile::Effect::Knockback(Knockback::Away(knockback)), + projectile::Effect::Vanish, + ], + time_left: Duration::from_secs(15), + owner: None, + ignore_group: true, + }; + projectile.owner = Some(*data.uid); + update.server_events.push_front(ServerEvent::Shoot { + entity: data.entity, + dir: data.inputs.look_dir, + body: self.static_data.projectile_body, + projectile, + light: self.static_data.projectile_light, + gravity: self.static_data.projectile_gravity, + speed: self.static_data.initial_projectile_speed + + charge_frac + * (self.static_data.max_projectile_speed + - self.static_data.initial_projectile_speed), + }); - // Consumes energy if there's enough left and RMB is held down - update.energy.change_by( - -(self.energy_drain as f32 * data.dt.0) as i32, - EnergySource::Ability, - ); - } else if data.inputs.secondary.is_pressed() { - // Charge the bow - update.character = CharacterState::ChargedRanged(Data { - exhausted: self.exhausted, - energy_drain: self.energy_drain, - initial_damage: self.initial_damage, - max_damage: self.max_damage, - initial_knockback: self.initial_knockback, - max_knockback: self.max_knockback, - prepare_duration: self.prepare_duration, - charge_timer: self.charge_timer, - charge_duration: self.charge_duration, - recover_duration: self.recover_duration, - projectile_body: self.projectile_body, - projectile_light: self.projectile_light, - projectile_gravity: self.projectile_gravity, - initial_projectile_speed: self.initial_projectile_speed, - max_projectile_speed: self.max_projectile_speed, - }); + update.character = CharacterState::ChargedRanged(Data { + timer: Duration::default(), + stage_section: StageSection::Recover, + exhausted: true, + ..*self + }); + } else if self.timer < self.static_data.charge_duration + && data.inputs.secondary.is_pressed() + { + // Charges + update.character = CharacterState::ChargedRanged(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); - // Consumes energy if there's enough left and RMB is held down - update.energy.change_by( - -(self.energy_drain as f32 * data.dt.0 / 5.0) as i32, - EnergySource::Ability, - ); - } else if !self.exhausted { - let charge_amount = - (self.charge_timer.as_secs_f32() / self.charge_duration.as_secs_f32()).min(1.0); - // Fire - let mut projectile = Projectile { - hit_solid: vec![projectile::Effect::Stick], - hit_entity: vec![ - projectile::Effect::Damage( - -(self.initial_damage as i32 - + (charge_amount * (self.max_damage - self.initial_damage) as f32) - as i32), - ), - projectile::Effect::Knockback( - self.initial_knockback - + charge_amount * (self.max_knockback - self.initial_knockback), - ), - projectile::Effect::Vanish, - ], - time_left: Duration::from_secs(15), - owner: None, - ignore_group: true, - }; - projectile.owner = Some(*data.uid); - update.server_events.push_front(ServerEvent::Shoot { - entity: data.entity, - dir: data.inputs.look_dir, - body: self.projectile_body, - projectile, - light: self.projectile_light, - gravity: self.projectile_gravity, - speed: self.initial_projectile_speed - + charge_amount * (self.max_projectile_speed - self.initial_projectile_speed), - }); + // Consumes energy if there's enough left and RMB is held down + update.energy.change_by( + -(self.static_data.energy_drain as f32 * data.dt.0) as i32, + EnergySource::Ability, + ); + } else if data.inputs.secondary.is_pressed() { + // Holds charge + update.character = CharacterState::ChargedRanged(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); - update.character = CharacterState::ChargedRanged(Data { - exhausted: true, - energy_drain: self.energy_drain, - initial_damage: self.initial_damage, - max_damage: self.max_damage, - initial_knockback: self.initial_knockback, - max_knockback: self.max_knockback, - prepare_duration: self.prepare_duration, - charge_timer: self.charge_timer, - charge_duration: self.charge_duration, - recover_duration: self.recover_duration, - projectile_body: self.projectile_body, - projectile_light: self.projectile_light, - projectile_gravity: self.projectile_gravity, - initial_projectile_speed: self.initial_projectile_speed, - max_projectile_speed: self.max_projectile_speed, - }); - } else if self.recover_duration != Duration::default() { - // Recovery - update.character = CharacterState::ChargedRanged(Data { - exhausted: self.exhausted, - energy_drain: self.energy_drain, - initial_damage: self.initial_damage, - max_damage: self.max_damage, - initial_knockback: self.initial_knockback, - max_knockback: self.max_knockback, - prepare_duration: self.prepare_duration, - charge_timer: self.charge_timer, - charge_duration: self.charge_duration, - recover_duration: self - .recover_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - projectile_body: self.projectile_body, - projectile_light: self.projectile_light, - projectile_gravity: self.projectile_gravity, - initial_projectile_speed: self.initial_projectile_speed, - max_projectile_speed: self.max_projectile_speed, - }); - } else { - // Done - update.character = CharacterState::Wielding; + // Consumes energy if there's enough left and RMB is held down + update.energy.change_by( + -(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32, + EnergySource::Ability, + ); + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + // Recovers + update.character = CharacterState::ChargedRanged(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Done + update.character = CharacterState::Wielding; + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + update.character = CharacterState::Wielding; + }, } update diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index fd59e00b3f..285804e278 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -2,6 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, + Damage, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -99,8 +100,6 @@ impl CharacterBehavior for Data { // Build up update.character = CharacterState::ComboMelee(Data { static_data: self.static_data.clone(), - stage: self.stage, - combo: self.combo, timer: self .timer .checked_add(Duration::from_secs_f32( @@ -110,51 +109,50 @@ impl CharacterBehavior for Data { * data.dt.0, )) .unwrap_or_default(), - stage_section: self.stage_section, - next_stage: self.next_stage, + ..*self }); } else { // Transitions to swing section of stage update.character = CharacterState::ComboMelee(Data { static_data: self.static_data.clone(), - stage: self.stage, - combo: self.combo, timer: Duration::default(), stage_section: StageSection::Swing, - next_stage: self.next_stage, + ..*self }); // Hit attempt + let damage = self.static_data.stage_data[stage_index].max_damage.min( + self.static_data.stage_data[stage_index].base_damage + + self.combo / self.static_data.num_stages + * self.static_data.stage_data[stage_index].damage_increase, + ); data.updater.insert(data.entity, Attacking { - base_damage: self.static_data.stage_data[stage_index].max_damage.min( - self.static_data.stage_data[stage_index].base_damage - + self.combo / self.static_data.num_stages - * self.static_data.stage_data[stage_index].damage_increase, - ), - base_heal: 0, + damages: Damages::new(Some(Damage::Melee(damage as f32)), None), range: self.static_data.stage_data[stage_index].range, max_angle: self.static_data.stage_data[stage_index].angle.to_radians(), applied: false, hit_count: 0, - knockback: self.static_data.stage_data[stage_index].knockback, + knockback: Knockback::Away( + self.static_data.stage_data[stage_index].knockback, + ), }); } }, StageSection::Swing => { if self.timer < self.static_data.stage_data[stage_index].base_swing_duration { // Forward movement - forward_move( + handle_forced_movement( data, &mut update, + ForcedMovement::Forward { + strength: self.static_data.stage_data[stage_index].forward_movement, + }, 0.3, - self.static_data.stage_data[stage_index].forward_movement, ); // Swings update.character = CharacterState::ComboMelee(Data { static_data: self.static_data.clone(), - stage: self.stage, - combo: self.combo, timer: self .timer .checked_add(Duration::from_secs_f32( @@ -164,18 +162,15 @@ impl CharacterBehavior for Data { * data.dt.0, )) .unwrap_or_default(), - stage_section: self.stage_section, - next_stage: self.next_stage, + ..*self }); } else { // Transitions to recover section of stage update.character = CharacterState::ComboMelee(Data { static_data: self.static_data.clone(), - stage: self.stage, - combo: self.combo, timer: Duration::default(), stage_section: StageSection::Recover, - next_stage: self.next_stage, + ..*self }); } }, @@ -186,8 +181,6 @@ impl CharacterBehavior for Data { // Checks if state will transition to next stage after recover update.character = CharacterState::ComboMelee(Data { static_data: self.static_data.clone(), - stage: self.stage, - combo: self.combo, timer: self .timer .checked_add(Duration::from_secs_f32( @@ -200,14 +193,12 @@ impl CharacterBehavior for Data { * data.dt.0, )) .unwrap_or_default(), - stage_section: self.stage_section, next_stage: true, + ..*self }); } else { update.character = CharacterState::ComboMelee(Data { static_data: self.static_data.clone(), - stage: self.stage, - combo: self.combo, timer: self .timer .checked_add(Duration::from_secs_f32( @@ -220,8 +211,7 @@ impl CharacterBehavior for Data { * data.dt.0, )) .unwrap_or_default(), - stage_section: self.stage_section, - next_stage: self.next_stage, + ..*self }); } } else if self.next_stage { @@ -229,10 +219,10 @@ impl CharacterBehavior for Data { update.character = CharacterState::ComboMelee(Data { static_data: self.static_data.clone(), stage: (self.stage % self.static_data.num_stages) + 1, - combo: self.combo, timer: Duration::default(), stage_section: StageSection::Buildup, next_stage: false, + ..*self }); } else { // Done diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 2b09a10203..8a99b80bd9 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -2,6 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, + Damage, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -46,9 +47,11 @@ pub struct Data { /// character state pub static_data: StaticData, /// Whether the charge should end - pub end_charge: bool, + pub auto_charge: bool, /// Timer for each stage pub timer: Duration, + /// Timer used to limit how often another attack will be applied + pub refresh_timer: Duration, /// What section the character stage is in pub stage_section: StageSection, /// Whether the state should attempt attacking again @@ -78,117 +81,93 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.buildup_duration { // Build up update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, - exhausted: self.exhausted, + ..*self }); } else { // Transitions to charge section of stage update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, + auto_charge: !data.inputs.secondary.is_pressed(), timer: Duration::default(), stage_section: StageSection::Charge, - exhausted: self.exhausted, + ..*self }); } }, StageSection::Charge => { - if (self.timer < self.static_data.charge_duration - || (self.static_data.infinite_charge && data.inputs.secondary.is_pressed())) + if (self.static_data.infinite_charge + || self.timer < self.static_data.charge_duration) + && (data.inputs.secondary.is_pressed() + || (self.auto_charge && self.timer < self.static_data.charge_duration)) && update.energy.current() > 0 - && !self.end_charge { // Forward movement - forward_move(data, &mut update, 0.1, self.static_data.forward_speed); - - // Hit attempt (also checks if player is moving) - if !self.exhausted && update.vel.0.distance_squared(Vec3::zero()) > 1.0 { - let charge_frac = (self.timer.as_secs_f32() - / self.static_data.charge_duration.as_secs_f32()) - .min(1.0); - let damage = (self.static_data.max_damage as f32 - - self.static_data.base_damage as f32) - * charge_frac - + self.static_data.base_damage as f32; - let knockback = (self.static_data.max_knockback - - self.static_data.base_knockback) - * charge_frac - + self.static_data.base_knockback; - data.updater.insert(data.entity, Attacking { - base_damage: damage as u32, - base_heal: 0, - range: self.static_data.range, - max_angle: self.static_data.angle.to_radians(), - applied: false, - hit_count: 0, - knockback, - }); - } + handle_forced_movement( + data, + &mut update, + ForcedMovement::Forward { + strength: self.static_data.forward_speed, + }, + 0.1, + ); // This logic basically just decides if a charge should end, and prevents the // character state spamming attacks while checking if it has hit something if !self.exhausted { + // Hit attempt (also checks if player is moving) + if update.vel.0.distance_squared(Vec3::zero()) > 1.0 { + let charge_frac = (self.timer.as_secs_f32() + / self.static_data.charge_duration.as_secs_f32()) + .min(1.0); + let damage = (self.static_data.max_damage as f32 + - self.static_data.base_damage as f32) + * charge_frac + + self.static_data.base_damage as f32; + let knockback = (self.static_data.max_knockback + - self.static_data.base_knockback) + * charge_frac + + self.static_data.base_knockback; + data.updater.insert(data.entity, Attacking { + damages: Damages::new(Some(Damage::Melee(damage)), None), + range: self.static_data.range, + max_angle: self.static_data.angle.to_radians(), + applied: false, + hit_count: 0, + knockback: Knockback::Away(knockback), + }); + } update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: StageSection::Charge, exhausted: true, + ..*self + }) + } else if self.refresh_timer < Duration::from_millis(50) { + update.character = CharacterState::DashMelee(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + refresh_timer: self + .refresh_timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self }) - } else if let Some(attack) = data.attacking { - if attack.applied && attack.hit_count > 0 { - update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: !self.static_data.infinite_charge, - timer: self - .timer - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - stage_section: StageSection::Charge, - exhausted: false, - }) - } else if attack.applied { - update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, - timer: self - .timer - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - stage_section: StageSection::Charge, - exhausted: false, - }) - } else { - update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: !self.static_data.infinite_charge, - timer: self - .timer - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - stage_section: StageSection::Charge, - exhausted: self.exhausted, - }) - } } else { update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: !self.static_data.infinite_charge, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: StageSection::Charge, - exhausted: self.exhausted, + refresh_timer: Duration::default(), + exhausted: false, + ..*self }) } @@ -200,11 +179,9 @@ impl CharacterBehavior for Data { } else { // Transitions to swing section of stage update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, timer: Duration::default(), stage_section: StageSection::Swing, - exhausted: self.exhausted, + ..*self }); } }, @@ -212,23 +189,18 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.swing_duration { // Swings update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, - exhausted: self.exhausted, + ..*self }); } else { // Transitions to recover section of stage update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, timer: Duration::default(), stage_section: StageSection::Recover, - exhausted: self.exhausted, + ..*self }); } }, @@ -236,14 +208,11 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.recover_duration { // Recover update.character = CharacterState::DashMelee(Data { - static_data: self.static_data, - end_charge: self.end_charge, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, - exhausted: self.exhausted, + ..*self }); } else { // Done diff --git a/common/src/states/equipping.rs b/common/src/states/equipping.rs index ebc91062ff..fac06e5351 100644 --- a/common/src/states/equipping.rs +++ b/common/src/states/equipping.rs @@ -6,10 +6,20 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::time::Duration; -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// Time required to draw weapon + pub buildup_duration: Duration, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data { - /// Time left before next state - pub time_left: Duration, + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, } impl CharacterBehavior for Data { @@ -19,18 +29,18 @@ impl CharacterBehavior for Data { handle_move(&data, &mut update, 1.0); handle_jump(&data, &mut update); - if self.time_left == Duration::default() { - // Wield delay has expired - update.character = CharacterState::Wielding; - } else { - // Wield delay hasn't expired yet - // Update wield delay + if self.timer < self.static_data.buildup_duration { + // Draw weapon update.character = CharacterState::Equipping(Data { - time_left: self - .time_left - .checked_sub(Duration::from_secs_f32(data.dt.0)) + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), + ..*self }); + } else { + // Done + update.character = CharacterState::Wielding; } update diff --git a/common/src/states/leap_melee.rs b/common/src/states/leap_melee.rs index 4f76f22daa..0739761281 100644 --- a/common/src/states/leap_melee.rs +++ b/common/src/states/leap_melee.rs @@ -2,10 +2,10 @@ use crate::{ comp::{Attacking, CharacterState, StateUpdate}, states::utils::{StageSection, *}, sys::character_behavior::{CharacterBehavior, JoinData}, + Damage, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; -use vek::Vec3; /// Separated out to condense update portions of character state #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -75,28 +75,20 @@ impl CharacterBehavior for Data { }, StageSection::Movement => { if self.timer < self.static_data.movement_duration { - // Apply jumping force while in Movement portion of state - update.vel.0 = Vec3::new( - data.inputs.look_dir.x, - data.inputs.look_dir.y, - self.static_data.vertical_leap_strength, - ) * 2.0 - // Multiply decreasing amount linearly over time of - // movement duration - * (1.0 - - self.timer.as_secs_f32() - / self.static_data.movement_duration.as_secs_f32()) - // Apply inputted movement directions at 0.25 strength - + (update.vel.0 * Vec3::new(2.0, 2.0, 0.0) - + 0.25 * data.inputs.move_dir.try_normalized().unwrap_or_default()) - .try_normalized() - .unwrap_or_default() - // Multiply by forward leap strength - * self.static_data.forward_leap_strength - // Control forward movement based on look direction. - // This allows players to stop moving forward when they - // look downward at target - * (1.0 - data.inputs.look_dir.z.abs()); + // Apply jumping force + let progress = 1.0 + - self.timer.as_secs_f32() + / self.static_data.movement_duration.as_secs_f32(); + handle_forced_movement( + data, + &mut update, + ForcedMovement::Leap { + vertical: self.static_data.vertical_leap_strength, + forward: self.static_data.forward_leap_strength, + progress, + }, + 0.15, + ); // Increment duration // If we were to set a timeout for state, this would be @@ -141,13 +133,15 @@ impl CharacterBehavior for Data { if !self.exhausted { // Hit attempt, when animation plays data.updater.insert(data.entity, Attacking { - base_damage: self.static_data.base_damage, - base_heal: 0, + damages: Damages::new( + Some(Damage::Melee(self.static_data.base_damage as f32)), + None, + ), range: self.static_data.range, max_angle: self.static_data.max_angle.to_radians(), applied: false, hit_count: 0, - knockback: self.static_data.knockback, + knockback: Knockback::Away(self.static_data.knockback), }); update.character = CharacterState::LeapMelee(Data { diff --git a/common/src/states/repeater_ranged.rs b/common/src/states/repeater_ranged.rs index c2c4e5d81b..b6ee044680 100644 --- a/common/src/states/repeater_ranged.rs +++ b/common/src/states/repeater_ranged.rs @@ -54,13 +54,18 @@ impl CharacterBehavior for Data { StageSection::Movement => { // Jumping if let Some(leap_strength) = self.static_data.leap { - update.vel.0 = Vec3::new( - data.vel.0.x, - data.vel.0.y, - leap_strength - * (1.0 - - self.timer.as_secs_f32() - / self.static_data.movement_duration.as_secs_f32()), + let progress = 1.0 + - self.timer.as_secs_f32() + / self.static_data.movement_duration.as_secs_f32(); + handle_forced_movement( + data, + &mut update, + ForcedMovement::Leap { + vertical: leap_strength, + forward: 10.0, + progress, + }, + 1.0, ); } if self.timer < self.static_data.movement_duration { @@ -71,8 +76,7 @@ impl CharacterBehavior for Data { .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, - reps_remaining: self.reps_remaining, + ..*self }); } else { // Transition to buildup @@ -80,14 +84,19 @@ impl CharacterBehavior for Data { static_data: self.static_data.clone(), timer: Duration::default(), stage_section: StageSection::Buildup, - reps_remaining: self.reps_remaining, + ..*self }); } }, StageSection::Buildup => { // Aim gliding if self.static_data.leap.is_some() { - update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0); + handle_forced_movement( + data, + &mut update, + ForcedMovement::Hover { move_input: 0.1 }, + 1.0, + ); } if self.timer < self.static_data.buildup_duration { // Buildup to attack @@ -97,8 +106,7 @@ impl CharacterBehavior for Data { .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, - reps_remaining: self.reps_remaining, + ..*self }); } else { // Transition to shoot @@ -106,7 +114,7 @@ impl CharacterBehavior for Data { static_data: self.static_data.clone(), timer: Duration::default(), stage_section: StageSection::Shoot, - reps_remaining: self.reps_remaining, + ..*self }); } }, @@ -152,8 +160,8 @@ impl CharacterBehavior for Data { .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, reps_remaining: self.reps_remaining - 1, + ..*self }); } else if self.timer < self.static_data.shoot_duration { // Finish shooting @@ -163,8 +171,7 @@ impl CharacterBehavior for Data { .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, - reps_remaining: self.reps_remaining, + ..*self }); } else { // Transition to recover @@ -172,7 +179,7 @@ impl CharacterBehavior for Data { static_data: self.static_data.clone(), timer: Duration::default(), stage_section: StageSection::Recover, - reps_remaining: self.reps_remaining, + ..*self }); } }, @@ -188,8 +195,7 @@ impl CharacterBehavior for Data { .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, - reps_remaining: self.reps_remaining, + ..*self }); } else { // Done diff --git a/common/src/states/roll.rs b/common/src/states/roll.rs index 3dc7887e7f..cb18a441f5 100644 --- a/common/src/states/roll.rs +++ b/common/src/states/roll.rs @@ -1,5 +1,6 @@ use crate::{ comp::{CharacterState, StateUpdate}, + states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, util::Dir, }; @@ -8,10 +9,27 @@ use std::time::Duration; use vek::Vec3; const ROLL_SPEED: f32 = 25.0; -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] + +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// How long until state should roll + pub buildup_duration: Duration, + /// How long state is rolling for + pub movement_duration: Duration, + /// How long it takes to recover from roll + pub recover_duration: Duration, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Data { - /// How long the state has until exiting - pub remaining_duration: Duration, + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, + /// What section the character stage is in + pub stage_section: StageSection, /// Had weapon pub was_wielded: bool, } @@ -31,23 +49,72 @@ impl CharacterBehavior for Data { // Smooth orientation update.ori.0 = Dir::slerp_to_vec3(update.ori.0, update.vel.0.xy().into(), 9.0 * data.dt.0); - if self.remaining_duration == Duration::default() { - // Roll duration has expired - update.vel.0 *= 0.3; - if self.was_wielded { - update.character = CharacterState::Wielding; - } else { - update.character = CharacterState::Idle; - } - } else { - // Otherwise, tick down remaining_duration - update.character = CharacterState::Roll(Data { - remaining_duration: self - .remaining_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - was_wielded: self.was_wielded, - }); + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + update.character = CharacterState::Roll(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Transitions to movement section of stage + update.character = CharacterState::Roll(Data { + timer: Duration::default(), + stage_section: StageSection::Movement, + ..*self + }); + } + }, + StageSection::Movement => { + if self.timer < self.static_data.movement_duration { + // Movement + update.character = CharacterState::Roll(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Transitions to recover section of stage + update.character = CharacterState::Roll(Data { + timer: Duration::default(), + stage_section: StageSection::Recover, + ..*self + }); + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + // Build up + update.character = CharacterState::Roll(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + ..*self + }); + } else { + // Done + if self.was_wielded { + update.character = CharacterState::Wielding; + } else { + update.character = CharacterState::Idle; + } + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + if self.was_wielded { + update.character = CharacterState::Wielding; + } else { + update.character = CharacterState::Idle; + } + }, } update diff --git a/common/src/states/shockwave.rs b/common/src/states/shockwave.rs index fc2f72e10c..de0cbf3ab5 100644 --- a/common/src/states/shockwave.rs +++ b/common/src/states/shockwave.rs @@ -3,6 +3,7 @@ use crate::{ event::ServerEvent, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, + Damage, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -19,7 +20,7 @@ pub struct StaticData { /// Base damage pub damage: u32, /// Knockback - pub knockback: f32, + pub knockback: Knockback, /// Angle of the shockwave pub shockwave_angle: f32, /// Vertical angle of the shockwave @@ -56,12 +57,11 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.buildup_duration { // Build up update.character = CharacterState::Shockwave(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, + ..*self }); } else { // Attack @@ -70,7 +70,10 @@ impl CharacterBehavior for Data { vertical_angle: self.static_data.shockwave_vertical_angle, speed: self.static_data.shockwave_speed, duration: self.static_data.shockwave_duration, - damage: self.static_data.damage, + damages: Damages::new( + Some(Damage::Shockwave(self.static_data.damage as f32)), + None, + ), knockback: self.static_data.knockback, requires_ground: self.static_data.requires_ground, owner: Some(*data.uid), @@ -83,9 +86,9 @@ impl CharacterBehavior for Data { // Transitions to swing update.character = CharacterState::Shockwave(Data { - static_data: self.static_data, timer: Duration::default(), stage_section: StageSection::Swing, + ..*self }); } }, @@ -93,19 +96,18 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.swing_duration { // Swings update.character = CharacterState::Shockwave(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, + ..*self }); } else { // Transitions to recover update.character = CharacterState::Shockwave(Data { - static_data: self.static_data, timer: Duration::default(), stage_section: StageSection::Recover, + ..*self }); } }, @@ -113,12 +115,11 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.swing_duration { // Recovers update.character = CharacterState::Shockwave(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - stage_section: self.stage_section, + ..*self }); } else { // Done diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index 8c97b99c0a..14c4bec054 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -1,7 +1,11 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::*, - sys::character_behavior::{CharacterBehavior, JoinData}, + sys::{ + character_behavior::{CharacterBehavior, JoinData}, + phys::GRAVITY, + }, + Damage, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -56,7 +60,8 @@ impl CharacterBehavior for Data { let mut update = StateUpdate::from(data); if self.static_data.is_helicopter { - update.vel.0 = Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * 5.0; + update.vel.0 = Vec3::new(0.0, 0.0, update.vel.0.z + GRAVITY * data.dt.0) + + data.inputs.move_dir * 5.0; } // Allows for other states to interrupt this state @@ -75,61 +80,60 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.buildup_duration { // Build up update.character = CharacterState::SpinMelee(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - spins_remaining: self.spins_remaining, - stage_section: self.stage_section, - exhausted: self.exhausted, + ..*self }); } else { // Transitions to swing section of stage update.character = CharacterState::SpinMelee(Data { - static_data: self.static_data, timer: Duration::default(), - spins_remaining: self.spins_remaining, stage_section: StageSection::Swing, - exhausted: self.exhausted, + ..*self }); } }, StageSection::Swing => { if !self.exhausted { update.character = CharacterState::SpinMelee(Data { - static_data: self.static_data, timer: Duration::default(), - spins_remaining: self.spins_remaining, - stage_section: self.stage_section, exhausted: true, + ..*self }); // Hit attempt data.updater.insert(data.entity, Attacking { - base_damage: self.static_data.base_damage, - base_heal: 0, + damages: Damages::new( + Some(Damage::Melee(self.static_data.base_damage as f32)), + None, + ), range: self.static_data.range, max_angle: 180_f32.to_radians(), applied: false, hit_count: 0, - knockback: self.static_data.knockback, + knockback: Knockback::Away(self.static_data.knockback), }); } else if self.timer < self.static_data.swing_duration { if !self.static_data.is_helicopter { - forward_move(data, &mut update, 0.1, self.static_data.forward_speed); + handle_forced_movement( + data, + &mut update, + ForcedMovement::Forward { + strength: self.static_data.forward_speed, + }, + 0.1, + ); handle_orientation(data, &mut update, 1.0); } // Swings update.character = CharacterState::SpinMelee(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - spins_remaining: self.spins_remaining, - stage_section: self.stage_section, - exhausted: self.exhausted, + ..*self }); } else if update.energy.current() >= self.static_data.energy_cost && (self.spins_remaining != 0 @@ -141,11 +145,10 @@ impl CharacterBehavior for Data { self.spins_remaining - 1 }; update.character = CharacterState::SpinMelee(Data { - static_data: self.static_data, timer: Duration::default(), spins_remaining: new_spins_remaining, - stage_section: self.stage_section, exhausted: false, + ..*self }); // Consumes energy if there's enough left and RMB is held down update.energy.change_by( @@ -155,11 +158,9 @@ impl CharacterBehavior for Data { } else { // Transitions to recover section of stage update.character = CharacterState::SpinMelee(Data { - static_data: self.static_data, timer: Duration::default(), - spins_remaining: self.spins_remaining, stage_section: StageSection::Recover, - exhausted: self.exhausted, + ..*self }); } }, @@ -167,14 +168,11 @@ impl CharacterBehavior for Data { if self.timer < self.static_data.recover_duration { // Recover update.character = CharacterState::SpinMelee(Data { - static_data: self.static_data, timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - spins_remaining: self.spins_remaining, - stage_section: self.stage_section, - exhausted: self.exhausted, + ..*self }); } else { // Done diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index c0b71b7b5c..b2dd5ecb9e 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -9,6 +9,7 @@ use crate::{ util::Dir, }; use serde::{Deserialize, Serialize}; +use std::time::Duration; use vek::*; pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0; @@ -90,18 +91,54 @@ fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { handle_orientation(data, update, data.body.base_ori_rate()); } -/// Similar to basic_move function, but with forced forward movement -pub fn forward_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, forward: f32) { - let accel = if data.physics.on_ground { - data.body.base_accel() - } else { - BASE_HUMANOID_AIR_ACCEL - }; - - update.vel.0 += Vec2::broadcast(data.dt.0) - * accel - * (data.inputs.move_dir * efficiency + (*update.ori.0).xy() * forward); +/// Handles forced movement +pub fn handle_forced_movement( + data: &JoinData, + update: &mut StateUpdate, + movement: ForcedMovement, + efficiency: f32, +) { + match movement { + ForcedMovement::Forward { strength } => { + let accel = if data.physics.on_ground { + data.body.base_accel() + } else { + BASE_HUMANOID_AIR_ACCEL + }; + update.vel.0 += Vec2::broadcast(data.dt.0) + * accel + * (data.inputs.move_dir * efficiency + (*update.ori.0).xy() * strength); + }, + ForcedMovement::Leap { + vertical, + forward, + progress, + } => { + // Apply jumping force + update.vel.0 = Vec3::new( + data.inputs.look_dir.x, + data.inputs.look_dir.y, + vertical, + ) + // Multiply decreasing amount linearly over time (with average of 1) + * 2.0 * progress + // Apply inputted movement directions with some efficiency + + (data.inputs.move_dir.try_normalized().unwrap_or_default() + update.vel.0.xy()) + .try_normalized() + .unwrap_or_default() + // Multiply by forward leap strength + * forward + // Control forward movement based on look direction. + // This allows players to stop moving forward when they + // look downward at target + * (1.0 - data.inputs.look_dir.z.abs()); + }, + ForcedMovement::Hover { move_input } => { + update.vel.0 = Vec3::new(data.vel.0.x, data.vel.0.y, 0.0) + + move_input * data.inputs.move_dir.try_normalized().unwrap_or_default(); + }, + } handle_orientation(data, update, data.body.base_ori_rate() * efficiency); } @@ -161,7 +198,10 @@ pub fn handle_wield(data: &JoinData, update: &mut StateUpdate) { pub fn attempt_wield(data: &JoinData, update: &mut StateUpdate) { if let Some(ItemKind::Tool(tool)) = data.loadout.active_item.as_ref().map(|i| i.item.kind()) { update.character = CharacterState::Equipping(equipping::Data { - time_left: tool.equip_time(), + static_data: equipping::StaticData { + buildup_duration: tool.equip_time(), + }, + timer: Duration::default(), }); } else { update.character = CharacterState::Idle; @@ -385,3 +425,18 @@ pub enum AbilityKey { Skill1, Dodge, } + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum ForcedMovement { + Forward { + strength: f32, + }, + Leap { + vertical: f32, + forward: f32, + progress: f32, + }, + Hover { + move_input: f32, + }, +} diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index d39a8e299f..ea6f5dd3b2 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -1,11 +1,12 @@ use crate::{ comp::{ - group, Beam, BeamSegment, Body, CharacterState, Damage, DamageSource, Energy, EnergySource, - HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, Stats, + group, Beam, BeamSegment, Body, CharacterState, Energy, EnergySource, HealthChange, + HealthSource, Last, Loadout, Ori, Pos, Scale, Stats, }, event::{EventBus, ServerEvent}, state::{DeltaTime, Time}, sync::{Uid, UidAllocator}, + Damage, }; use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; @@ -166,54 +167,29 @@ impl<'a> System<'a> for Sys { if Some(*uid_b) == beam_segment.owner { continue; } - // Don't heal if outside group - // Don't damage in the same group - let is_damage = !same_group && (beam_segment.damage > 0); - let is_heal = same_group && (beam_segment.heal > 0); - if !is_heal && !is_damage { + + let damage = if let Some(damage) = beam_segment.damages.get_damage(same_group) { + damage + } else { continue; - } - - // Weapon gives base damage - let source = if is_heal { - DamageSource::Healing - } else { - DamageSource::Energy - }; - let healthchange = if is_heal { - beam_segment.heal as f32 - } else { - -(beam_segment.damage as f32) - }; - - let mut damage = Damage { - healthchange, - source, }; let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) // TODO: investigate whether this calculation is proper for beams && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; - if let Some(loadout) = loadouts.get(b) { - damage.modify_damage(block, loadout); - } + let change = damage.modify_damage(block, loadouts.get(b), beam_segment.owner); - if is_damage { + if matches!(damage, Damage::Healing(_)) { server_emitter.emit(ServerEvent::Damage { uid: *uid_b, - change: HealthChange { - amount: damage.healthchange as i32, - cause: HealthSource::Energy { - owner: beam_segment.owner, - }, - }, + change, }); if beam_segment.lifesteal_eff > 0.0 { server_emitter.emit(ServerEvent::Damage { uid: beam_segment.owner.unwrap_or(*uid), change: HealthChange { - amount: (-damage.healthchange * beam_segment.lifesteal_eff) + amount: (-change.amount as f32 * beam_segment.lifesteal_eff) as i32, cause: HealthSource::Healing { by: beam_segment.owner, @@ -227,26 +203,18 @@ impl<'a> System<'a> for Sys { EnergySource::HitEnemy, ); } - } - if is_heal { - if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) { - if energy_mut - .try_change_by( - -(beam_segment.energy_cost as i32), // Stamina use - EnergySource::Ability, - ) - .is_ok() - { - server_emitter.emit(ServerEvent::Damage { - uid: *uid_b, - change: HealthChange { - amount: damage.healthchange as i32, - cause: HealthSource::Healing { - by: beam_segment.owner, - }, - }, - }); - } + } else if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) { + if energy_mut + .try_change_by( + -(beam_segment.energy_cost as i32), // Stamina use + EnergySource::Ability, + ) + .is_ok() + { + server_emitter.emit(ServerEvent::Damage { + uid: *uid_b, + change, + }); } } // Adds entities that were hit to the hit_entities list on the beam, sees if it diff --git a/common/src/sys/combat.rs b/common/src/sys/melee.rs similarity index 70% rename from common/src/sys/combat.rs rename to common/src/sys/melee.rs index cfa469a910..b974ca6bd4 100644 --- a/common/src/sys/combat.rs +++ b/common/src/sys/melee.rs @@ -1,8 +1,5 @@ use crate::{ - comp::{ - buff, group, Attacking, Body, CharacterState, Damage, DamageSource, HealthChange, - HealthSource, Loadout, Ori, Pos, Scale, Stats, - }, + comp::{buff, group, Attacking, Body, CharacterState, Loadout, Ori, Pos, Scale, Stats}, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, span, @@ -14,7 +11,6 @@ use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage} use std::time::Duration; use vek::*; -pub const BLOCK_EFFICIENCY: f32 = 0.9; pub const BLOCK_ANGLE: f32 = 180.0; /// This system is responsible for handling accepted inputs like moving or @@ -59,7 +55,7 @@ impl<'a> System<'a> for Sys { ): Self::SystemData, ) { let start_time = std::time::Instant::now(); - span!(_guard, "run", "combat::Sys::run"); + span!(_guard, "run", "melee::Sys::run"); let mut server_emitter = server_bus.emitter(); let mut _local_emitter = local_bus.emitter(); // Attacks @@ -113,56 +109,34 @@ impl<'a> System<'a> for Sys { .get(entity) .map(|group_a| Some(group_a) == groups.get(b)) .unwrap_or(false); - // Don't heal if outside group - // Don't damage in the same group - let is_damage = !same_group && (attack.base_damage > 0); - let is_heal = same_group && (attack.base_heal > 0); - if !is_heal && !is_damage { - continue; - } - // Weapon gives base damage - let (source, healthchange) = if is_heal { - (DamageSource::Healing, attack.base_heal as f32) + let damage = if let Some(damage) = attack.damages.get_damage(same_group) { + damage } else { - (DamageSource::Melee, -(attack.base_damage as f32)) - }; - let mut damage = Damage { - healthchange, - source, + continue; }; let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; - if let Some(loadout) = loadouts.get(b) { - damage.modify_damage(block, loadout); - } + let change = damage.modify_damage(block, loadouts.get(b), Some(*uid)); - if damage.healthchange != 0.0 { - let cause = if is_heal { - HealthSource::Healing { by: Some(*uid) } - } else { - HealthSource::Attack { by: *uid } - }; + if change.amount != 0 { server_emitter.emit(ServerEvent::Damage { uid: *uid_b, - change: HealthChange { - amount: damage.healthchange as i32, - cause, - }, + change, }); // Apply bleeding buff on melee hits with 10% chance // TODO: Don't have buff uniformly applied on all melee attacks - if thread_rng().gen::() < 0.1 { + if change.amount < 0 && thread_rng().gen::() < 0.1 { use buff::*; server_emitter.emit(ServerEvent::Buff { entity: b, buff_change: BuffChange::Add(Buff::new( BuffKind::Bleeding, BuffData { - strength: attack.base_damage as f32 / 10.0, + strength: -change.amount as f32 / 10.0, duration: Some(Duration::from_secs(10)), }, vec![BuffCategory::Physical], @@ -172,18 +146,18 @@ impl<'a> System<'a> for Sys { } attack.hit_count += 1; } - if attack.knockback != 0.0 && damage.healthchange != 0.0 { + + if change.amount != 0 { let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); - server_emitter.emit(ServerEvent::Knockback { - entity: b, - impulse: attack.knockback - * *Dir::slerp(kb_dir, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5), - }); + let impulse = attack.knockback.calculate_impulse(kb_dir); + if !impulse.is_approx_zero() { + server_emitter.emit(ServerEvent::Knockback { entity: b, impulse }); + } } } } } - sys_metrics.combat_ns.store( + sys_metrics.melee_ns.store( start_time.elapsed().as_nanos() as i64, std::sync::atomic::Ordering::Relaxed, ); diff --git a/common/src/sys/mod.rs b/common/src/sys/mod.rs index 98c70fd145..50f222744d 100644 --- a/common/src/sys/mod.rs +++ b/common/src/sys/mod.rs @@ -2,8 +2,8 @@ pub mod agent; mod beam; mod buff; pub mod character_behavior; -pub mod combat; pub mod controller; +pub mod melee; mod mount; pub mod phys; mod projectile; @@ -15,7 +15,7 @@ use specs::DispatcherBuilder; // System names pub const CHARACTER_BEHAVIOR_SYS: &str = "character_behavior_sys"; -pub const COMBAT_SYS: &str = "combat_sys"; +pub const MELEE_SYS: &str = "melee_sys"; pub const AGENT_SYS: &str = "agent_sys"; pub const BEAM_SYS: &str = "beam_sys"; pub const CONTROLLER_SYS: &str = "controller_sys"; @@ -39,5 +39,5 @@ pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) { dispatch_builder.add(projectile::Sys, PROJECTILE_SYS, &[PHYS_SYS]); dispatch_builder.add(shockwave::Sys, SHOCKWAVE_SYS, &[PHYS_SYS]); dispatch_builder.add(beam::Sys, BEAM_SYS, &[PHYS_SYS]); - dispatch_builder.add(combat::Sys, COMBAT_SYS, &[PROJECTILE_SYS]); + dispatch_builder.add(melee::Sys, MELEE_SYS, &[PROJECTILE_SYS]); } diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 5936575c33..cfae192ec9 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -1,20 +1,18 @@ use crate::{ comp::{ - projectile, Damage, DamageSource, Energy, EnergySource, Group, HealthChange, HealthSource, - Loadout, Ori, PhysicsState, Pos, Projectile, Vel, + projectile, Energy, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState, Pos, + Projectile, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, span, state::DeltaTime, sync::UidAllocator, - util::Dir, }; use specs::{ saveload::MarkerAllocator, Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage, }; use std::time::Duration; -use vek::*; /// This system is responsible for handling projectile effect triggers pub struct Sys; @@ -73,20 +71,21 @@ impl<'a> System<'a> for Sys { { // Hit entity for other in physics.touch_entities.iter().copied() { + let same_group = projectile + .owner + // Note: somewhat inefficient since we do the lookup for every touching + // entity, but if we pull this out of the loop we would want to do it only + // if there is at least one touching entity + .and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into())) + .and_then(|e| groups.get(e)) + .map_or(false, |owner_group| + Some(owner_group) == uid_allocator + .retrieve_entity_internal(other.into()) + .and_then(|e| groups.get(e)) + ); if projectile.ignore_group // Skip if in the same group - && projectile - .owner - // Note: somewhat inefficient since we do the lookup for every touching - // entity, but if we pull this out of the loop we would want to do it only - // if there is at least one touching entity - .and_then(|uid| uid_allocator.retrieve_entity_internal(uid.into())) - .and_then(|e| groups.get(e)) - .map_or(false, |owner_group| - Some(owner_group) == uid_allocator - .retrieve_entity_internal(other.into()) - .and_then(|e| groups.get(e)) - ) + && same_group { continue; } @@ -97,51 +96,34 @@ impl<'a> System<'a> for Sys { for effect in projectile.hit_entity.drain(..) { match effect { - projectile::Effect::Damage(healthchange) => { - let owner_uid = projectile.owner.unwrap(); - let mut damage = Damage { - healthchange: healthchange as f32, - source: DamageSource::Projectile, - }; - - let other_entity = uid_allocator.retrieve_entity_internal(other.into()); - if let Some(loadout) = other_entity.and_then(|e| loadouts.get(e)) { - damage.modify_damage(false, loadout); + projectile::Effect::Damages(damages) => { + if Some(other) == projectile.owner { + continue; } + let damage = if let Some(damage) = damages.get_damage(same_group) { + damage + } else { + continue; + }; + let other_entity_loadout = uid_allocator + .retrieve_entity_internal(other.into()) + .and_then(|e| loadouts.get(e)); + let change = + damage.modify_damage(false, other_entity_loadout, projectile.owner); - if other != owner_uid { - if damage.healthchange < 0.0 { - server_emitter.emit(ServerEvent::Damage { - uid: other, - change: HealthChange { - amount: damage.healthchange as i32, - cause: HealthSource::Projectile { - owner: Some(owner_uid), - }, - }, - }); - } else if damage.healthchange > 0.0 { - server_emitter.emit(ServerEvent::Damage { - uid: other, - change: HealthChange { - amount: damage.healthchange as i32, - cause: HealthSource::Healing { - by: Some(owner_uid), - }, - }, - }); - } + if change.amount != 0 { + server_emitter.emit(ServerEvent::Damage { uid: other, change }); } }, projectile::Effect::Knockback(knockback) => { if let Some(entity) = uid_allocator.retrieve_entity_internal(other.into()) { - local_emitter.emit(LocalEvent::ApplyImpulse { - entity, - impulse: knockback - * *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5), - }); + let impulse = knockback.calculate_impulse(ori.0); + if !impulse.is_approx_zero() { + local_emitter + .emit(LocalEvent::ApplyImpulse { entity, impulse }); + } } }, projectile::Effect::RewardEnergy(energy) => { diff --git a/common/src/sys/shockwave.rs b/common/src/sys/shockwave.rs index 1dbf0530cb..0da87146bf 100644 --- a/common/src/sys/shockwave.rs +++ b/common/src/sys/shockwave.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - group, Body, CharacterState, Damage, DamageSource, HealthChange, HealthSource, Last, - Loadout, Ori, PhysicsState, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats, + group, Body, CharacterState, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale, + Shockwave, ShockwaveHitEntities, Stats, }, event::{EventBus, LocalEvent, ServerEvent}, state::{DeltaTime, Time}, @@ -189,51 +189,32 @@ impl<'a> System<'a> for Sys { }) } && (pos_b_ground - pos.0).angle_between(pos_b.0 - pos.0) < max_angle - && (!shockwave.requires_ground || physics_state_b.on_ground) - && !same_group; + && (!shockwave.requires_ground || physics_state_b.on_ground); if hit { - let mut damage = Damage { - healthchange: -(shockwave.damage as f32), - source: DamageSource::Shockwave, + let damage = if let Some(damage) = shockwave.damages.get_damage(same_group) { + damage + } else { + continue; }; let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; - if let Some(loadout) = loadouts.get(b) { - damage.modify_damage(block, loadout); - } + let owner_uid = shockwave.owner.unwrap_or(*uid); + let change = damage.modify_damage(block, loadouts.get(b), Some(owner_uid)); - if damage.healthchange != 0.0 { - let cause = if damage.healthchange < 0.0 { - HealthSource::Attack { - by: shockwave.owner.unwrap_or(*uid), - } - } else { - HealthSource::Healing { - by: Some(shockwave.owner.unwrap_or(*uid)), - } - }; + if change.amount != 0 { server_emitter.emit(ServerEvent::Damage { uid: *uid_b, - change: HealthChange { - amount: damage.healthchange as i32, - cause, - }, + change, }); shockwave_hit_list.hit_entities.push(*uid_b); - } - if shockwave.knockback != 0.0 && damage.healthchange != 0.0 { let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); - let impulse = if shockwave.knockback < 0.0 { - shockwave.knockback - * *Dir::slerp(kb_dir, Dir::new(Vec3::new(0.0, 0.0, -1.0)), 0.85) - } else { - shockwave.knockback - * *Dir::slerp(kb_dir, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5) - }; - server_emitter.emit(ServerEvent::Knockback { entity: b, impulse }); + let impulse = shockwave.knockback.calculate_impulse(kb_dir); + if !impulse.is_approx_zero() { + server_emitter.emit(ServerEvent::Knockback { entity: b, impulse }); + } } } } diff --git a/common/src/util/dir.rs b/common/src/util/dir.rs index af9a29ced0..30cb4cfe07 100644 --- a/common/src/util/dir.rs +++ b/common/src/util/dir.rs @@ -98,6 +98,13 @@ impl std::ops::Deref for Dir { impl From> for Dir { fn from(dir: Vec3) -> Self { Dir::new(dir) } } + +impl std::ops::Neg for Dir { + type Output = Dir; + + fn neg(self) -> Dir { Dir::new(-self.0) } +} + /// Begone ye NaN's /// Slerp two `Vec3`s skipping the slerp if their directions are very close /// This avoids a case where `vek`s slerp produces NaN's diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index e32e7318b7..cb175c4c20 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -8,18 +8,17 @@ use common::{ comp::{ self, buff, chat::{KillSource, KillType}, - object, Alignment, Body, Damage, DamageSource, Group, HealthChange, HealthSource, Item, - Player, Pos, Stats, + object, Alignment, Body, Group, HealthChange, HealthSource, Item, Player, Pos, Stats, }, lottery::Lottery, msg::{PlayerListUpdate, ServerGeneral}, outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, - sys::combat::BLOCK_ANGLE, + sys::melee::BLOCK_ANGLE, terrain::{Block, TerrainGrid}, vol::ReadVol, - Explosion, + Damage, Explosion, }; use comp::item::Reagent; use rand::prelude::*; @@ -456,17 +455,10 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) if vel.z <= -30.0 { if let Some(stats) = state.ecs().write_storage::().get_mut(entity) { let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0; - let mut damage = Damage { - healthchange: -falldmg, - source: DamageSource::Falling, - }; - if let Some(loadout) = state.ecs().read_storage::().get(entity) { - damage.modify_damage(false, loadout); - } - stats.health.change_by(comp::HealthChange { - amount: damage.healthchange as i32, - cause: comp::HealthSource::World, - }); + let damage = Damage::Falling(falldmg); + let loadouts = state.ecs().read_storage::(); + let change = damage.modify_damage(false, loadouts.get(entity), None); + stats.health.change_by(change); } } } @@ -576,43 +568,26 @@ pub fn handle_explosion( continue; } - // Weapon gives base damage - let source = if is_heal { - DamageSource::Healing - } else { - DamageSource::Explosion - }; let strength = 1.0 - distance_squared / explosion.radius.powi(2); - let healthchange = if is_heal { - explosion.min_heal as f32 - + (explosion.max_heal - explosion.min_heal) as f32 * strength + let damage = if is_heal { + Damage::Healing( + explosion.min_heal as f32 + + (explosion.max_heal - explosion.min_heal) as f32 * strength, + ) } else { - -(explosion.min_damage as f32 - + (explosion.max_damage - explosion.min_damage) as f32 * strength) - }; - - let mut damage = Damage { - healthchange, - source, + Damage::Explosion( + explosion.min_damage as f32 + + (explosion.max_damage - explosion.min_damage) as f32 * strength, + ) }; let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) && ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; - if let Some(loadout) = loadout_b { - damage.modify_damage(block, loadout); - } + let change = damage.modify_damage(block, loadout_b, owner); - if damage.healthchange != 0.0 { - let cause = if is_heal { - HealthSource::Healing { by: owner } - } else { - HealthSource::Explosion { owner } - }; - stats_b.health.change_by(HealthChange { - amount: damage.healthchange as i32, - cause, - }); + if change.amount != 0 { + stats_b.health.change_by(change); if let Some(owner) = owner_entity { if let Some(energy) = ecs.write_storage::().get_mut(owner) { energy diff --git a/server/src/lib.rs b/server/src/lib.rs index d2a6111f05..e3acfa2f02 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -717,7 +717,7 @@ impl Server { let stats_ns = res.stats_ns.load(Ordering::Relaxed); let phys_ns = res.phys_ns.load(Ordering::Relaxed); let projectile_ns = res.projectile_ns.load(Ordering::Relaxed); - let combat_ns = res.combat_ns.load(Ordering::Relaxed); + let melee_ns = res.melee_ns.load(Ordering::Relaxed); c.with_label_values(&[common::sys::AGENT_SYS]) .inc_by(agent_ns); @@ -733,8 +733,8 @@ impl Server { .inc_by(phys_ns); c.with_label_values(&[common::sys::PROJECTILE_SYS]) .inc_by(projectile_ns); - c.with_label_values(&[common::sys::COMBAT_SYS]) - .inc_by(combat_ns); + c.with_label_values(&[common::sys::MELEE_SYS]) + .inc_by(melee_ns); const NANOSEC_PER_SEC: f64 = Duration::from_secs(1).as_nanos() as f64; let h = &self.state_tick_metrics.state_tick_time_hist; @@ -752,8 +752,8 @@ impl Server { .observe(phys_ns as f64 / NANOSEC_PER_SEC); h.with_label_values(&[common::sys::PROJECTILE_SYS]) .observe(projectile_ns as f64 / NANOSEC_PER_SEC); - h.with_label_values(&[common::sys::COMBAT_SYS]) - .observe(combat_ns as f64 / NANOSEC_PER_SEC); + h.with_label_values(&[common::sys::MELEE_SYS]) + .observe(melee_ns as f64 / NANOSEC_PER_SEC); } // Report other info diff --git a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs index ad03ea0ea5..448c976f6b 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs @@ -23,7 +23,10 @@ fn maps_wield_while_equipping() { let result = CombatEventMapper::map_event( &CharacterState::Equipping(states::equipping::Data { - time_left: Duration::from_millis(10), + static_data: states::equipping::StaticData { + buildup_duration: Duration::from_millis(10), + }, + timer: Duration::default(), }), &PreviousEntityState { event: SfxEvent::Idle, @@ -77,12 +80,17 @@ fn maps_basic_melee() { let result = CombatEventMapper::map_event( &CharacterState::BasicMelee(states::basic_melee::Data { - buildup_duration: Duration::default(), - recover_duration: Duration::default(), - knockback: 0.0, - base_healthchange: 10, - range: 1.0, - max_angle: 1.0, + static_data: states::basic_melee::StaticData { + buildup_duration: Duration::default(), + swing_duration: Duration::default(), + recover_duration: Duration::default(), + base_damage: 10, + knockback: 0.0, + range: 1.0, + max_angle: 1.0, + }, + timer: Duration::default(), + stage_section: states::utils::StageSection::Buildup, exhausted: false, }), &PreviousEntityState { diff --git a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs index 40ef0d5edb..1ac43e80a3 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/tests.rs @@ -153,7 +153,13 @@ fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() { fn maps_roll() { let result = MovementEventMapper::map_movement_event( &CharacterState::Roll(states::roll::Data { - remaining_duration: Duration::from_millis(300), + static_data: states::roll::StaticData { + buildup_duration: Duration::default(), + movement_duration: Duration::default(), + recover_duration: Duration::default(), + }, + timer: Duration::default(), + stage_section: states::utils::StageSection::Buildup, was_wielded: true, }), &PhysicsState { diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs index e17245c2a8..7be45e7de9 100644 --- a/voxygen/src/hud/hotbar.rs +++ b/voxygen/src/hud/hotbar.rs @@ -79,7 +79,7 @@ impl State { if let ItemKind::Tool(kind) = kind { match &kind.kind { ToolKind::Staff(_) => true, - ToolKind::Debug(kind) => kind == "Boost", + ToolKind::Debug(_) => true, ToolKind::Sword(_) => true, ToolKind::Hammer(_) => true, ToolKind::Axe(_) => true, diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 172671622e..ad8045517d 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -524,6 +524,14 @@ impl<'a> Widget for Skillbar<'a> { .map(|i| i.item.kind()) .and_then(|kind| match kind { ItemKind::Tool(Tool { kind, .. }) => match kind { + ToolKind::Hammer(_) => Some(( + "Smash of Doom", + "\nAn AOE attack with knockback. \nLeaps to position of \ + cursor.", + )), + ToolKind::Axe(_) => { + Some(("Spin Leap", "\nA slashing running spin leap.")) + }, ToolKind::Staff(_) => Some(( "Firebomb", "\nWhirls a big fireball into the air. \nExplodes the ground \ @@ -533,14 +541,14 @@ impl<'a> Widget for Skillbar<'a> { "Whirlwind", "\nMove forward while spinning with \n your sword.", )), - ToolKind::Debug(kind) => match kind.as_ref() { - "Boost" => Some(( - "Possessing Arrow", - "\nShoots a poisonous arrow.\nLets you control your \ - target.", - )), - _ => None, - }, + ToolKind::Bow(_) => Some(( + "Burst", + "\nLaunches a burst of arrows at the top \nof a running leap.", + )), + ToolKind::Debug(_) => Some(( + "Possessing Arrow", + "\nShoots a poisonous arrow.\nLets you control your target.", + )), _ => None, }, _ => None, @@ -621,10 +629,7 @@ impl<'a> Widget for Skillbar<'a> { ToolKind::Bow(_) => self.imgs.bow_m1, ToolKind::Sceptre(_) => self.imgs.heal_0, ToolKind::Staff(_) => self.imgs.fireball, - ToolKind::Debug(kind) => match kind.as_ref() { - "Boost" => self.imgs.flyingrod_m1, - _ => self.imgs.nothing, - }, + ToolKind::Debug(_) => self.imgs.flyingrod_m1, _ => self.imgs.nothing, }, _ => self.imgs.nothing, @@ -671,10 +676,7 @@ impl<'a> Widget for Skillbar<'a> { Some(ToolKind::Bow(_)) => self.imgs.bow_m2, Some(ToolKind::Sceptre(_)) => self.imgs.heal_bomb, Some(ToolKind::Staff(_)) => self.imgs.flamethrower, - Some(ToolKind::Debug(kind)) => match kind.as_ref() { - "Boost" => self.imgs.flyingrod_m2, - _ => self.imgs.nothing, - }, + Some(ToolKind::Debug(_)) => self.imgs.flyingrod_m2, _ => self.imgs.nothing, }) .w_h(36.0, 36.0) diff --git a/voxygen/src/hud/slots.rs b/voxygen/src/hud/slots.rs index ab31c28cda..56bd3dda56 100644 --- a/voxygen/src/hud/slots.rs +++ b/voxygen/src/hud/slots.rs @@ -116,10 +116,7 @@ impl<'a> SlotKey, HotbarImageSource<'a>> for HotbarSlot { ToolKind::Hammer(_) => Some(HotbarImage::HammerLeap), ToolKind::Axe(_) => Some(HotbarImage::AxeLeapSlash), ToolKind::Bow(_) => Some(HotbarImage::BowJumpBurst), - ToolKind::Debug(kind) => match kind.as_ref() { - "Boost" => Some(HotbarImage::SnakeArrow), - _ => None, - }, + ToolKind::Debug(_) => Some(HotbarImage::SnakeArrow), ToolKind::Sword(_) => Some(HotbarImage::SwordWhirlwind), _ => None, }, diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index e9002961a5..e757a727f6 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -594,8 +594,8 @@ impl ParticleMgr { )); } } else { - for d in 0..10 * distance as i32 { - let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 10.0; + for d in 0..3 * distance as i32 { + let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 3.0; let position = pos.0 + distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);