From ce7581037ca5d78eb4f147eab2eb2bd2b5aa1db5 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 18 Apr 2021 18:09:57 -0400 Subject: [PATCH] Overhauled dash melee. --- CHANGELOG.md | 1 + assets/common/abilities/axesimple/dash.ron | 2 +- assets/common/abilities/spear/dash.ron | 2 +- assets/common/abilities/sword/dash.ron | 10 +- assets/common/abilities/swordsimple/dash.ron | 4 +- .../abilities/unique/quadlowbreathe/dash.ron | 2 +- .../abilities/unique/quadlowquick/dash.ron | 4 +- .../abilities/unique/quadmedcharge/dash.ron | 2 +- .../abilities/unique/quadmedquick/dash.ron | 4 +- .../abilities/unique/theropodbasic/dash.ron | 2 +- assets/voxygen/i18n/en/skills.ron | 4 +- common/src/comp/ability.rs | 12 +- common/src/comp/skills.rs | 2 +- common/src/states/dash_melee.rs | 260 ++++++++++++------ voxygen/src/hud/diary.rs | 10 +- 15 files changed, 202 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2add111def..7dcf72121d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - There is now a brief period after a character leaves the world where they cannot rejoin until their data is saved - Certain uses of client-authoritative physics now subject the player to server-authoritative physics. - Dodge roll iframes and staff explosion are now unlocked by default, with points refunded for existing characters. +- Dash melee now stops after hitting something. Infinite dash also now replaced with dash through. ### Removed diff --git a/assets/common/abilities/axesimple/dash.ron b/assets/common/abilities/axesimple/dash.ron index 425bb21a9e..388bad9bf0 100644 --- a/assets/common/abilities/axesimple/dash.ron +++ b/assets/common/abilities/axesimple/dash.ron @@ -14,6 +14,6 @@ DashMelee( charge_duration: 3.0, swing_duration: 0.35, recover_duration: 1.2, - infinite_charge: true, + charge_through: true, is_interruptible: true, ) diff --git a/assets/common/abilities/spear/dash.ron b/assets/common/abilities/spear/dash.ron index a93ddd39a0..d21c8c776c 100644 --- a/assets/common/abilities/spear/dash.ron +++ b/assets/common/abilities/spear/dash.ron @@ -14,6 +14,6 @@ DashMelee( charge_duration: 1.0, swing_duration: 0.1, recover_duration: 0.5, - infinite_charge: true, + charge_through: true, is_interruptible: true, ) diff --git a/assets/common/abilities/sword/dash.ron b/assets/common/abilities/sword/dash.ron index f66177b7c4..5900974c2b 100644 --- a/assets/common/abilities/sword/dash.ron +++ b/assets/common/abilities/sword/dash.ron @@ -6,14 +6,14 @@ DashMelee( scaled_poise_damage: 0, base_knockback: 8.0, scaled_knockback: 7.0, - range: 5.0, - angle: 45.0, - energy_drain: 600, + range: 4.0, + angle: 60.0, + energy_drain: 300, forward_speed: 3.0, buildup_duration: 0.25, - charge_duration: 0.6, + charge_duration: 1.2, swing_duration: 0.1, recover_duration: 0.5, - infinite_charge: true, + charge_through: true, is_interruptible: true, ) diff --git a/assets/common/abilities/swordsimple/dash.ron b/assets/common/abilities/swordsimple/dash.ron index 1a97e27d8c..d9ddf8bb0a 100644 --- a/assets/common/abilities/swordsimple/dash.ron +++ b/assets/common/abilities/swordsimple/dash.ron @@ -11,9 +11,9 @@ DashMelee( energy_drain: 0, forward_speed: 4.0, buildup_duration: 0.25, - charge_duration: 0.6, + charge_duration: 1.2, swing_duration: 0.1, recover_duration: 0.5, - infinite_charge: true, + charge_through: true, is_interruptible: true, ) diff --git a/assets/common/abilities/unique/quadlowbreathe/dash.ron b/assets/common/abilities/unique/quadlowbreathe/dash.ron index fbd7cf66b0..b441542ed7 100644 --- a/assets/common/abilities/unique/quadlowbreathe/dash.ron +++ b/assets/common/abilities/unique/quadlowbreathe/dash.ron @@ -14,6 +14,6 @@ DashMelee( charge_duration: 1.0, swing_duration: 0.1, recover_duration: 0.8, - infinite_charge: true, + charge_through: true, is_interruptible: false, ) diff --git a/assets/common/abilities/unique/quadlowquick/dash.ron b/assets/common/abilities/unique/quadlowquick/dash.ron index e27e8afae3..de5ff5824d 100644 --- a/assets/common/abilities/unique/quadlowquick/dash.ron +++ b/assets/common/abilities/unique/quadlowquick/dash.ron @@ -11,9 +11,9 @@ DashMelee( energy_drain: 0, forward_speed: 2.0, buildup_duration: 0.5, - charge_duration: 0.4, + charge_duration: 0.8, swing_duration: 0.1, recover_duration: 0.5, - infinite_charge: true, + charge_through: true, is_interruptible: false, ) diff --git a/assets/common/abilities/unique/quadmedcharge/dash.ron b/assets/common/abilities/unique/quadmedcharge/dash.ron index 92a2629bef..5248bf9350 100644 --- a/assets/common/abilities/unique/quadmedcharge/dash.ron +++ b/assets/common/abilities/unique/quadmedcharge/dash.ron @@ -14,6 +14,6 @@ DashMelee( charge_duration: 1.2, swing_duration: 0.1, recover_duration: 1.1, - infinite_charge: true, + charge_through: true, is_interruptible: false, ) diff --git a/assets/common/abilities/unique/quadmedquick/dash.ron b/assets/common/abilities/unique/quadmedquick/dash.ron index 0a50952bc9..32417b9055 100644 --- a/assets/common/abilities/unique/quadmedquick/dash.ron +++ b/assets/common/abilities/unique/quadmedquick/dash.ron @@ -11,9 +11,9 @@ DashMelee( energy_drain: 0, forward_speed: 2.5, buildup_duration: 1.2, - charge_duration: 0.5, + charge_duration: 1.0, swing_duration: 0.1, recover_duration: 0.5, - infinite_charge: true, + charge_through: true, is_interruptible: false, ) diff --git a/assets/common/abilities/unique/theropodbasic/dash.ron b/assets/common/abilities/unique/theropodbasic/dash.ron index 110a20edc5..1bfb83ce33 100644 --- a/assets/common/abilities/unique/theropodbasic/dash.ron +++ b/assets/common/abilities/unique/theropodbasic/dash.ron @@ -14,6 +14,6 @@ DashMelee( charge_duration: 1.2, swing_duration: 0.1, recover_duration: 1.1, - infinite_charge: true, + charge_through: true, is_interruptible: false, ) \ No newline at end of file diff --git a/assets/voxygen/i18n/en/skills.ron b/assets/voxygen/i18n/en/skills.ron index 2ba7fe85f8..6c747fafb5 100644 --- a/assets/voxygen/i18n/en/skills.ron +++ b/assets/voxygen/i18n/en/skills.ron @@ -189,8 +189,8 @@ "hud.skill.sw_dash_cost": "Decreases the initial cost of the dash by 25%{SP}", "hud.skill.sw_dash_speed_title": "Dash Speed", "hud.skill.sw_dash_speed": "Increases how fast you go while dashing by 30%{SP}", - "hud.skill.sw_dash_inf_title": "Dash Infinite", - "hud.skill.sw_dash_inf": "Allows you to dash for as long as you have energy{SP}", + "hud.skill.sw_dash_charge_through_title": "Charge Through", + "hud.skill.sw_dash_charge_through": "Allows you to charge through the first enemies you hit{SP}", "hud.skill.sw_dash_scale_title": "Dash Scaling Damage", "hud.skill.sw_dash_scale": "Increases the damage scaling from the dash by 20%{SP}", "hud.skill.sw_spin_title": "Spin Unlock", diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 9db0bb1366..42273034bc 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -113,7 +113,7 @@ pub enum CharacterAbility { charge_duration: f32, swing_duration: f32, recover_duration: f32, - infinite_charge: bool, + charge_through: bool, is_interruptible: bool, }, BasicBlock, @@ -650,7 +650,7 @@ impl CharacterAbility { ref mut base_damage, ref mut scaled_damage, ref mut forward_speed, - ref mut infinite_charge, + ref mut charge_through, .. } => { *is_interruptible = skillset.has_skill(Sword(InterruptingAttacks)); @@ -669,7 +669,7 @@ impl CharacterAbility { if skillset.has_skill(Sword(DSpeed)) { *forward_speed *= 1.15; } - *infinite_charge = skillset.has_skill(Sword(DInfinite)); + *charge_through = skillset.has_skill(Sword(DInfinite)); }, SpinMelee { ref mut is_interruptible, @@ -1212,7 +1212,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { charge_duration, swing_duration, recover_duration, - infinite_charge, + charge_through, is_interruptible, } => CharacterState::DashMelee(dash_melee::Data { static_data: dash_melee::StaticData { @@ -1226,7 +1226,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { angle: *angle, energy_drain: *energy_drain, forward_speed: *forward_speed, - infinite_charge: *infinite_charge, + charge_through: *charge_through, buildup_duration: Duration::from_secs_f32(*buildup_duration), charge_duration: Duration::from_secs_f32(*charge_duration), swing_duration: Duration::from_secs_f32(*swing_duration), @@ -1236,7 +1236,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { }, auto_charge: false, timer: Duration::default(), - refresh_distance: 0.0, + charge_end_timer: Duration::from_secs_f32(*charge_duration), stage_section: StageSection::Buildup, exhausted: false, }), diff --git a/common/src/comp/skills.rs b/common/src/comp/skills.rs index 6fccc207c0..bc16c18e54 100644 --- a/common/src/comp/skills.rs +++ b/common/src/comp/skills.rs @@ -128,7 +128,7 @@ pub enum SwordSkill { DDamage, DScaling, DSpeed, - DInfinite, + DInfinite, // Represents charge through, not migrated because laziness // Spin upgrades UnlockSpin, SDamage, diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 4fa754889e..f7f0a58e39 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -9,7 +9,6 @@ use crate::{ }; 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)] @@ -34,8 +33,8 @@ pub struct StaticData { pub energy_drain: f32, /// How quickly dasher moves forward pub forward_speed: f32, - /// Whether state keeps charging after reaching max charge duration - pub infinite_charge: bool, + /// Whether the state can charge through enemies and do a second hit + pub charge_through: bool, /// How long until state should deal damage pub buildup_duration: Duration, /// How long the state charges for until it reaches max damage @@ -60,12 +59,12 @@ pub struct Data { pub auto_charge: bool, /// Timer for each stage pub timer: Duration, - /// Distance used to limit how often another attack will be applied - pub refresh_distance: f32, /// What section the character stage is in pub stage_section: StageSection, /// Whether the state should attempt attacking again pub exhausted: bool, + /// Time that charge should end (used for charge through) + pub charge_end_timer: Duration, } impl CharacterBehavior for Data { @@ -97,8 +96,7 @@ impl CharacterBehavior for Data { } }, StageSection::Charge => { - if (self.static_data.infinite_charge - || self.timer < self.static_data.charge_duration) + if self.timer < self.charge_end_timer && (input_is_pressed(data, self.static_data.ability_info.input) || (self.auto_charge && self.timer < self.static_data.charge_duration)) && update.energy.current() > 0 @@ -120,62 +118,60 @@ impl CharacterBehavior for Data { // 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 poise = AttackEffect::new( - Some(GroupTarget::OutOfGroup), - CombatEffect::Poise( - self.static_data.base_poise_damage as f32 - + charge_frac * self.static_data.scaled_poise_damage as f32, - ), - ) - .with_requirement(CombatRequirement::AnyDamage); - let knockback = AttackEffect::new( - Some(GroupTarget::OutOfGroup), - CombatEffect::Knockback(Knockback { - strength: self.static_data.base_knockback - + charge_frac * self.static_data.scaled_knockback, - direction: KnockbackDir::Away, - }), - ) - .with_requirement(CombatRequirement::AnyDamage); - let buff = CombatEffect::Buff(CombatBuff::default_physical()); - let damage = AttackDamage::new( - Damage { - source: DamageSource::Melee, - value: self.static_data.base_damage as f32 - + charge_frac * self.static_data.scaled_damage as f32, - }, - Some(GroupTarget::OutOfGroup), - ) - .with_effect(buff); - let (crit_chance, crit_mult) = - get_crit_data(data, self.static_data.ability_info); - let attack = Attack::default() - .with_damage(damage) - .with_crit(crit_chance, crit_mult) - .with_effect(poise) - .with_effect(knockback) - .with_combo_increment(); + // Hit attempt + let poise = AttackEffect::new( + Some(GroupTarget::OutOfGroup), + CombatEffect::Poise( + self.static_data.base_poise_damage as f32 + + charge_frac * self.static_data.scaled_poise_damage as f32, + ), + ) + .with_requirement(CombatRequirement::AnyDamage); + let knockback = AttackEffect::new( + Some(GroupTarget::OutOfGroup), + CombatEffect::Knockback(Knockback { + strength: self.static_data.base_knockback + + charge_frac * self.static_data.scaled_knockback, + direction: KnockbackDir::Away, + }), + ) + .with_requirement(CombatRequirement::AnyDamage); + let buff = CombatEffect::Buff(CombatBuff::default_physical()); + let damage = AttackDamage::new( + Damage { + source: DamageSource::Melee, + value: self.static_data.base_damage as f32 + + charge_frac * self.static_data.scaled_damage as f32, + }, + Some(GroupTarget::OutOfGroup), + ) + .with_effect(buff); + let (crit_chance, crit_mult) = + get_crit_data(data, self.static_data.ability_info); + let attack = Attack::default() + .with_damage(damage) + .with_crit(crit_chance, crit_mult) + .with_effect(poise) + .with_effect(knockback) + .with_combo_increment(); - data.updater.insert(data.entity, Melee { - attack, - range: self.static_data.range, - max_angle: self.static_data.angle.to_radians(), - applied: false, - hit_count: 0, - break_block: data - .inputs - .select_pos - .map(|p| { - ( - p.map(|e| e.floor() as i32), - self.static_data.ability_info.tool, - ) - }) - .filter(|(_, tool)| tool == &Some(ToolKind::Pick)), - }); - } + data.updater.insert(data.entity, Melee { + attack, + range: self.static_data.range, + max_angle: self.static_data.angle.to_radians(), + applied: false, + hit_count: 0, + break_block: data + .inputs + .select_pos + .map(|p| { + ( + p.map(|e| e.floor() as i32), + self.static_data.ability_info.tool, + ) + }) + .filter(|(_, tool)| tool == &Some(ToolKind::Pick)), + }); update.character = CharacterState::DashMelee(Data { timer: self .timer @@ -185,56 +181,68 @@ impl CharacterBehavior for Data { ..*self }) } else if let Some(melee) = data.melee_attack { - // Creates timer ahead of time so xmac can look at line count - let timer = self - .timer - .checked_add(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(); - // If melee attack has not applied yet, tick both duration and dsitance if !melee.applied { + // If melee attack has not applied, just tick duration update.character = CharacterState::DashMelee(Data { - timer, - refresh_distance: self.refresh_distance - + data.dt.0 * data.vel.0.magnitude(), + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), ..*self - }) - // If melee attack has applied, but hit nothing, remove - // exhausted so it can attack again + }); } else if melee.hit_count == 0 { + // If melee attack has applied, but not hit anything, remove exhausted + // so it can attack again update.character = CharacterState::DashMelee(Data { - timer, - refresh_distance: 0.0, + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), exhausted: false, ..*self - }) - // Else, melee attack applied and hit something, enter - // cooldown - } else if self.refresh_distance < self.static_data.range { + }); + } else if self.static_data.charge_through { + // If can charge through, set charge_end_timer to stop after a little + // more time + let charge_end_timer = + if self.charge_end_timer != self.static_data.charge_duration { + self.charge_end_timer + } else { + self.timer + .checked_add(Duration::from_secs_f32( + 0.2 * self.static_data.range + / self.static_data.forward_speed, + )) + .unwrap_or(self.static_data.charge_duration) + .min(self.static_data.charge_duration) + }; update.character = CharacterState::DashMelee(Data { - timer, - refresh_distance: self.refresh_distance - + data.dt.0 * data.vel.0.magnitude(), + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + charge_end_timer, ..*self - }) - // Else cooldown has finished, remove exhausted + }); } else { + // Stop charging now and go to swing stage section update.character = CharacterState::DashMelee(Data { - timer, - refresh_distance: 0.0, + timer: Duration::default(), + stage_section: StageSection::Swing, exhausted: false, ..*self - }) + }); } } else { + // If melee attack has not applied, just tick duration update.character = CharacterState::DashMelee(Data { timer: self .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - refresh_distance: 0.0, exhausted: false, ..*self - }) + }); } // Consumes energy if there's enough left and charge has not stopped @@ -247,12 +255,82 @@ impl CharacterBehavior for Data { update.character = CharacterState::DashMelee(Data { timer: Duration::default(), stage_section: StageSection::Swing, + exhausted: false, ..*self }); } }, StageSection::Swing => { - if self.timer < self.static_data.swing_duration { + if self.static_data.charge_through && !self.exhausted { + // If can charge through and not exhausted, do one more melee attack + + // Assumes charge got to charge_end_timer for damage calculations + let charge_frac = (self.charge_end_timer.as_secs_f32() + / self.static_data.charge_duration.as_secs_f32()) + .min(1.0); + + let poise = AttackEffect::new( + Some(GroupTarget::OutOfGroup), + CombatEffect::Poise( + self.static_data.base_poise_damage as f32 + + charge_frac * self.static_data.scaled_poise_damage as f32, + ), + ) + .with_requirement(CombatRequirement::AnyDamage); + let knockback = AttackEffect::new( + Some(GroupTarget::OutOfGroup), + CombatEffect::Knockback(Knockback { + strength: self.static_data.base_knockback + + charge_frac * self.static_data.scaled_knockback, + direction: KnockbackDir::Away, + }), + ) + .with_requirement(CombatRequirement::AnyDamage); + let buff = CombatEffect::Buff(CombatBuff::default_physical()); + let damage = AttackDamage::new( + Damage { + source: DamageSource::Melee, + value: self.static_data.base_damage as f32 + + charge_frac * self.static_data.scaled_damage as f32, + }, + Some(GroupTarget::OutOfGroup), + ) + .with_effect(buff); + let (crit_chance, crit_mult) = + get_crit_data(data, self.static_data.ability_info); + let attack = Attack::default() + .with_damage(damage) + .with_crit(crit_chance, crit_mult) + .with_effect(poise) + .with_effect(knockback) + .with_combo_increment(); + + data.updater.insert(data.entity, Melee { + attack, + range: self.static_data.range, + max_angle: self.static_data.angle.to_radians(), + applied: false, + hit_count: 0, + break_block: data + .inputs + .select_pos + .map(|p| { + ( + p.map(|e| e.floor() as i32), + self.static_data.ability_info.tool, + ) + }) + .filter(|(_, tool)| tool == &Some(ToolKind::Pick)), + }); + update.character = CharacterState::DashMelee(Data { + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) + .unwrap_or_default(), + exhausted: true, + ..*self + }) + } else if self.timer < self.static_data.swing_duration { // Swings update.character = CharacterState::DashMelee(Data { timer: self diff --git a/voxygen/src/hud/diary.rs b/voxygen/src/hud/diary.rs index c58409ac2c..8a2561d339 100644 --- a/voxygen/src/hud/diary.rs +++ b/voxygen/src/hud/diary.rs @@ -1387,7 +1387,7 @@ impl<'a> Widget for Diary<'a> { }; let skill = Skill::Sword(DInfinite); if create_skill_button( - self.imgs.physical_infinite_skill, + self.imgs.physical_distance_skill, state.skills_top_r[5], &self.skill_set, skill, @@ -1396,9 +1396,13 @@ impl<'a> Widget for Diary<'a> { ) .with_tooltip( self.tooltip_manager, - &self.localized_strings.get("hud.skill.sw_dash_inf_title"), + &self + .localized_strings + .get("hud.skill.sw_dash_charge_through_title"), &add_sp_cost_tooltip( - &self.localized_strings.get("hud.skill.sw_dash_inf"), + &self + .localized_strings + .get("hud.skill.sw_dash_charge_through"), skill, &self.skill_set, &self.localized_strings,