From a8e834e754750d90942ac729588aa6dcd57a123d Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 16 Sep 2020 20:31:27 -0500 Subject: [PATCH] Initial implementation of spin attack for sword. --- common/src/comp/ability.rs | 47 +++-- common/src/comp/inventory/item/tool.rs | 26 ++- common/src/states/spin_melee.rs | 193 +++++++++++--------- voxygen/src/anim/src/character/spinmelee.rs | 132 +++++++------ voxygen/src/hud/hotbar.rs | 2 + voxygen/src/hud/skillbar.rs | 4 + voxygen/src/scene/figure/mod.rs | 27 ++- 7 files changed, 264 insertions(+), 167 deletions(-) diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 1d3bcd529a..11a47a796d 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -106,10 +106,17 @@ pub enum CharacterAbility { base_damage: u32, }, SpinMelee { - energy_cost: u32, buildup_duration: Duration, + swing_duration: Duration, recover_duration: Duration, base_damage: u32, + knockback: f32, + range: f32, + energy_cost: u32, + is_infinite: bool, + is_helicopter: bool, + forward_speed: f32, + num_spins: u32, }, ChargedRanged { energy_cost: u32, @@ -402,24 +409,34 @@ impl From<&CharacterAbility> for CharacterState { base_damage: *base_damage, }), CharacterAbility::SpinMelee { - energy_cost, buildup_duration, + swing_duration, recover_duration, base_damage, + knockback, + range, + energy_cost, + is_infinite, + is_helicopter, + forward_speed, + num_spins, } => CharacterState::SpinMelee(spin_melee::Data { - exhausted: false, - energy_cost: *energy_cost, - buildup_duration: *buildup_duration, - buildup_duration_default: *buildup_duration, - recover_duration: *recover_duration, - recover_duration_default: *recover_duration, - base_damage: *base_damage, - // This isn't needed for it's continuous implementation, but is left in should this - // skill be moved to the skillbar - hits_remaining: 1, - hits_remaining_default: 1, /* Should be the same value as hits_remaining, also - * this value can be removed if ability moved to - * skillbar */ + static_data: spin_melee::StaticData { + buildup_duration: *buildup_duration, + swing_duration: *swing_duration, + recover_duration: *recover_duration, + base_damage: *base_damage, + knockback: *knockback, + range: *range, + energy_cost: *energy_cost, + is_infinite: *is_infinite, + is_helicopter: *is_helicopter, + forward_speed: *forward_speed, + num_spins: *num_spins, + }, + timer: Duration::default(), + spins_remaining: *num_spins - 1, + stage_section: StageSection::Buildup, }), CharacterAbility::ChargedRanged { energy_cost: _, diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 0baab1e22c..9b481f6b30 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -183,6 +183,19 @@ impl Tool { infinite_charge: true, is_interruptible: true, }, + SpinMelee { + buildup_duration: Duration::from_millis(150), + swing_duration: Duration::from_millis(100), + recover_duration: Duration::from_millis(150), + base_damage: (100.0 * self.base_power()) as u32, + knockback: 0.0, + range: 3.5, + energy_cost: 200, + is_infinite: false, + is_helicopter: false, + forward_speed: 1.0, + num_spins: 3, + } ], Axe(_) => vec![ BasicMelee { @@ -195,10 +208,17 @@ impl Tool { max_angle: 20.0, }, SpinMelee { - energy_cost: 100, - buildup_duration: Duration::from_millis(125), - recover_duration: Duration::from_millis(125), + buildup_duration: Duration::from_millis(100), + swing_duration: Duration::from_millis(50), + recover_duration: Duration::from_millis(100), base_damage: (60.0 * self.base_power()) as u32, + knockback: 0.0, + range: 3.5, + energy_cost: 100, + is_infinite: true, + is_helicopter: true, + forward_speed: 0.0, + num_spins: 1, }, ], Hammer(_) => vec![ diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index 1bb1141fa2..a25d148c87 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -1,131 +1,144 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, + states::utils::{StageSection, *}, sys::character_behavior::*, }; use serde::{Deserialize, Serialize}; use std::time::Duration; use vek::Vec3; -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] -pub struct Data { +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { /// How long until the state attacks pub buildup_duration: Duration, - /// Allows for buildup_duration to be reset to default value - pub buildup_duration_default: Duration, + /// How long the state is in the swing duration + pub swing_duration: Duration, /// How long until state ends pub recover_duration: Duration, - /// Allows for recover_duration to be reset to default value - pub recover_duration_default: Duration, /// Base damage pub base_damage: u32, - /// Whether the attack can deal more damage - pub exhausted: bool, - /// How many hits it can do before ending - pub hits_remaining: u32, - /// Allows for hits_remaining to be reset to default value - pub hits_remaining_default: u32, + /// Knockback + pub knockback: f32, + /// Range + pub range: f32, /// Energy cost per attack pub energy_cost: u32, + /// Whether spin state is infinite + pub is_infinite: bool, + /// Used to maintain classic axe spin physics + pub is_helicopter: bool, + /// Used for forced forward movement + pub forward_speed: f32, + /// Number of spins + pub num_spins: u32, } -const MOVE_SPEED: f32 = 5.0; +#[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, + /// How many spins it can do before ending + pub spins_remaining: u32, + /// What section the character stage is in + pub stage_section: StageSection, +} impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); - if self.buildup_duration != Duration::default() { - // Allows for moving + if self.static_data.is_helicopter { update.vel.0 = - Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * MOVE_SPEED; + Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * 5.0; + } else { + handle_orientation(data, &mut update, 1.0); + forward_move(data, &mut update, 0.1, self.static_data.forward_speed); + } + if self.stage_section == StageSection::Buildup + && self.timer < self.static_data.buildup_duration + { + // Build up update.character = CharacterState::SpinMelee(Data { - buildup_duration: self - .buildup_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) + static_data: self.static_data, + timer: self + .timer + .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - buildup_duration_default: self.buildup_duration_default, - recover_duration: self.recover_duration, - recover_duration_default: self.recover_duration_default, - base_damage: self.base_damage, - exhausted: self.exhausted, - hits_remaining: self.hits_remaining, - hits_remaining_default: self.hits_remaining_default, - energy_cost: self.energy_cost, + spins_remaining: self.spins_remaining, + stage_section: self.stage_section, }); - } else if !self.exhausted { - //Hit attempt + } else if self.stage_section == StageSection::Buildup { + // 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, + }); + // Hit attempt data.updater.insert(data.entity, Attacking { - base_healthchange: -(self.base_damage as i32), - range: 3.5, - max_angle: 360_f32.to_radians(), + base_healthchange: -(self.static_data.base_damage as i32), + range: self.static_data.range, + max_angle: 180_f32.to_radians(), applied: false, hit_count: 0, - knockback: 0.0, + knockback: self.static_data.knockback, }); - - update.character = CharacterState::SpinMelee(Data { - buildup_duration: self.buildup_duration, - buildup_duration_default: self.buildup_duration_default, - recover_duration: self.recover_duration, - recover_duration_default: self.recover_duration_default, - base_damage: self.base_damage, - exhausted: true, - hits_remaining: self.hits_remaining - 1, - hits_remaining_default: self.hits_remaining_default, - energy_cost: self.energy_cost, - }); - } else if self.recover_duration != Duration::default() { - // Allows for moving - update.vel.0 = - Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * MOVE_SPEED; - - update.character = CharacterState::SpinMelee(Data { - buildup_duration: self.buildup_duration, - buildup_duration_default: self.buildup_duration_default, - recover_duration: self - .recover_duration - .checked_sub(Duration::from_secs_f32(data.dt.0)) - .unwrap_or_default(), - recover_duration_default: self.recover_duration_default, - base_damage: self.base_damage, - exhausted: self.exhausted, - hits_remaining: self.hits_remaining, - hits_remaining_default: self.hits_remaining_default, - energy_cost: self.energy_cost, - }); - } else if self.hits_remaining != 0 { - // Allows for one ability usage to have multiple hits - // This isn't needed for it's continuous implementation, but is left in should - // this skill be moved to the skillbar - update.character = CharacterState::SpinMelee(Data { - buildup_duration: self.buildup_duration_default, - buildup_duration_default: self.buildup_duration_default, - recover_duration: self.recover_duration_default, - recover_duration_default: self.recover_duration_default, - base_damage: self.base_damage, - exhausted: false, - hits_remaining: self.hits_remaining, - hits_remaining_default: self.hits_remaining_default, - energy_cost: self.energy_cost, - }); - } else if update.energy.current() >= self.energy_cost && data.inputs.secondary.is_pressed() + } else if self.stage_section == StageSection::Swing + && self.timer < self.static_data.swing_duration { + // Swings update.character = CharacterState::SpinMelee(Data { - buildup_duration: self.buildup_duration_default, - buildup_duration_default: self.buildup_duration_default, - recover_duration: self.recover_duration_default, - recover_duration_default: self.recover_duration_default, - base_damage: self.base_damage, - exhausted: false, - hits_remaining: self.hits_remaining_default, - hits_remaining_default: self.hits_remaining_default, - energy_cost: self.energy_cost, + 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, + }); + } else if self.stage_section == StageSection::Swing { + // 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, + }) + } else if self.stage_section == StageSection::Recover + && 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, + }) + } else if update.energy.current() >= self.static_data.energy_cost && (self.spins_remaining != 0 || (self.static_data.is_infinite && data.inputs.secondary.is_pressed())) { + let new_spins_remaining = if self.static_data.is_infinite { + self.spins_remaining + } else { + self.spins_remaining - 1 + }; + update.character = CharacterState::SpinMelee(Data { + static_data: self.static_data, + timer: Duration::default(), + spins_remaining: new_spins_remaining, + stage_section: StageSection::Buildup, }); // Consumes energy if there's enough left and RMB is held down update .energy - .change_by(-(self.energy_cost as i32), EnergySource::Ability); + .change_by(-(self.static_data.energy_cost as i32), EnergySource::Ability); } else { // Done update.character = CharacterState::Wielding; diff --git a/voxygen/src/anim/src/character/spinmelee.rs b/voxygen/src/anim/src/character/spinmelee.rs index b7c06d9fe4..1e350418c6 100644 --- a/voxygen/src/anim/src/character/spinmelee.rs +++ b/voxygen/src/anim/src/character/spinmelee.rs @@ -2,13 +2,22 @@ use super::{ super::{vek::*, Animation}, CharacterSkeleton, SkeletonAttr, }; -use common::comp::item::{Hands, ToolKind}; +use common::{ + comp::item::{Hands, ToolKind}, + states::utils::StageSection, +}; use std::f32::consts::PI; pub struct SpinMeleeAnimation; impl Animation for SpinMeleeAnimation { - type Dependency = (Option, Option, Vec3, f64); + type Dependency = ( + Option, + Option, + Vec3, + f64, + Option, + ); type Skeleton = CharacterSkeleton; #[cfg(feature = "use-dyn-lib")] @@ -18,7 +27,7 @@ impl Animation for SpinMeleeAnimation { #[allow(clippy::approx_constant)] // TODO: Pending review in #587 fn update_skeleton_inner( skeleton: &Self::Skeleton, - (active_tool_kind, second_tool_kind, velocity, _global_time): Self::Dependency, + (active_tool_kind, second_tool_kind, velocity, _global_time, stage_section): Self::Dependency, anim_time: f64, rate: &mut f32, skeleton_attr: &SkeletonAttr, @@ -49,61 +58,74 @@ impl Animation for SpinMeleeAnimation { let slowersmooth = (anim_time as f32 * lab as f32 * 4.0).sin(); let quick = (anim_time as f32 * lab as f32 * 8.0).sin(); - if let Some(ToolKind::Axe(_)) = active_tool_kind { - next.l_hand.position = Vec3::new(-0.5, 0.0, 4.0); - next.l_hand.orientation = Quaternion::rotation_x(PI / 2.0) - * Quaternion::rotation_z(0.0) - * Quaternion::rotation_y(PI); - next.l_hand.scale = Vec3::one() * 1.08; - next.r_hand.position = Vec3::new(0.5, 0.0, -2.5); - next.r_hand.orientation = Quaternion::rotation_x(PI / 2.0) - * Quaternion::rotation_z(0.0) - * Quaternion::rotation_y(0.0); - next.r_hand.scale = Vec3::one() * 1.06; - next.main.position = Vec3::new(-0.0, -2.0, -1.0); - next.main.orientation = Quaternion::rotation_x(0.0) - * Quaternion::rotation_y(0.0) - * Quaternion::rotation_z(0.0); + match active_tool_kind { + Some(ToolKind::Sword(_)) => { + if let Some(stage_section) = stage_section { + match stage_section { + StageSection::Buildup => {}, + StageSection::Swing => {}, + StageSection::Recover => {}, + _ => {}, + } + } + } + Some(ToolKind::Axe(_)) => { + next.l_hand.position = Vec3::new(-0.5, 0.0, 4.0); + next.l_hand.orientation = Quaternion::rotation_x(PI / 2.0) + * Quaternion::rotation_z(0.0) + * Quaternion::rotation_y(PI); + next.l_hand.scale = Vec3::one() * 1.08; + next.r_hand.position = Vec3::new(0.5, 0.0, -2.5); + next.r_hand.orientation = Quaternion::rotation_x(PI / 2.0) + * Quaternion::rotation_z(0.0) + * Quaternion::rotation_y(0.0); + next.r_hand.scale = Vec3::one() * 1.06; + next.main.position = Vec3::new(-0.0, -2.0, -1.0); + next.main.orientation = Quaternion::rotation_x(0.0) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(0.0); - next.control.position = Vec3::new(0.0, 16.0, 3.0); - next.control.orientation = Quaternion::rotation_x(-1.4) - * Quaternion::rotation_y(0.0) - * Quaternion::rotation_z(1.4); - next.control.scale = Vec3::one(); + next.control.position = Vec3::new(0.0, 16.0, 3.0); + next.control.orientation = Quaternion::rotation_x(-1.4) + * Quaternion::rotation_y(0.0) + * Quaternion::rotation_z(1.4); + next.control.scale = Vec3::one(); - next.head.position = Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1); - next.head.orientation = Quaternion::rotation_z(0.0) - * Quaternion::rotation_x(-0.15) - * Quaternion::rotation_y(0.08); - next.chest.position = Vec3::new( - 0.0, - skeleton_attr.chest.0 - 3.0, - skeleton_attr.chest.1 - 2.0, - ); - next.chest.orientation = Quaternion::rotation_z(0.0) - * Quaternion::rotation_x(-0.1) - * Quaternion::rotation_y(0.3); - next.chest.scale = Vec3::one(); + next.head.position = Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1); + next.head.orientation = Quaternion::rotation_z(0.0) + * Quaternion::rotation_x(-0.15) + * Quaternion::rotation_y(0.08); + next.chest.position = Vec3::new( + 0.0, + skeleton_attr.chest.0 - 3.0, + skeleton_attr.chest.1 - 2.0, + ); + next.chest.orientation = Quaternion::rotation_z(0.0) + * Quaternion::rotation_x(-0.1) + * Quaternion::rotation_y(0.3); + next.chest.scale = Vec3::one(); - next.belt.position = Vec3::new(0.0, 1.0, -1.0); - next.belt.orientation = Quaternion::rotation_z(0.0) - * Quaternion::rotation_x(0.4) - * Quaternion::rotation_y(0.0); - next.belt.scale = Vec3::one() * 0.98; - next.shorts.position = Vec3::new(0.0, 3.0, -2.5); - next.shorts.orientation = Quaternion::rotation_z(0.0) - * Quaternion::rotation_x(0.7) - * Quaternion::rotation_y(0.0); - next.shorts.scale = Vec3::one(); - next.torso.position = Vec3::new( - -xshift * (anim_time as f32).min(0.6), - -yshift * (anim_time as f32).min(0.6), - 0.0, - ) * skeleton_attr.scaler; - next.torso.orientation = Quaternion::rotation_z(spin * -16.0) - * Quaternion::rotation_x(0.0) - * Quaternion::rotation_y(0.0); - next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + next.belt.position = Vec3::new(0.0, 1.0, -1.0); + next.belt.orientation = Quaternion::rotation_z(0.0) + * Quaternion::rotation_x(0.4) + * Quaternion::rotation_y(0.0); + next.belt.scale = Vec3::one() * 0.98; + next.shorts.position = Vec3::new(0.0, 3.0, -2.5); + next.shorts.orientation = Quaternion::rotation_z(0.0) + * Quaternion::rotation_x(0.7) + * Quaternion::rotation_y(0.0); + next.shorts.scale = Vec3::one(); + next.torso.position = Vec3::new( + -xshift * (anim_time as f32).min(0.6), + -yshift * (anim_time as f32).min(0.6), + 0.0, + ) * skeleton_attr.scaler; + next.torso.orientation = Quaternion::rotation_z(spin * -16.0) + * Quaternion::rotation_x(0.0) + * Quaternion::rotation_y(0.0); + next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler; + }, + _ => {}, } if velocity.z.abs() > 0.1 { next.l_foot.position = diff --git a/voxygen/src/hud/hotbar.rs b/voxygen/src/hud/hotbar.rs index 7ca59b2945..37d9cd1b41 100644 --- a/voxygen/src/hud/hotbar.rs +++ b/voxygen/src/hud/hotbar.rs @@ -81,6 +81,8 @@ impl State { kind != "Sceptre" && kind != "SceptreVelorite" } else if let ToolKind::Debug(kind) = &kind.kind { kind == "Boost" + } else if let ToolKind::Sword(_) = &kind.kind { + true } else { false } diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index cba416f454..215a0fe37c 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -803,6 +803,10 @@ impl<'a> Widget for Skillbar<'a> { "\nWhirls a big fireball into the air. \nExplodes the ground \ and does\na big amount of damage", )), + ToolKind::Sword(_) => Some(( + "Whirlwind", + "\nMove forward while spinning with \n your sword." + )), ToolKind::Debug(kind) => match kind.as_ref() { "Boost" => Some(( "Possessing Arrow", diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index df5f49062f..45b217454d 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -25,7 +25,7 @@ use anim::{ }; use common::{ comp::{ - item::ItemKind, Body, CharacterState, Item, Last, LightAnimation, LightEmitter, Loadout, + item::{ItemKind, ToolKind}, Body, CharacterState, Item, Last, LightAnimation, LightEmitter, Loadout, Ori, PhysicsState, Pos, Scale, Stats, Vel, }, span, @@ -935,11 +935,30 @@ impl FigureMgr { skeleton_attr, ) }, - CharacterState::SpinMelee(_) => { + CharacterState::SpinMelee(s) => { + let stage_progress = match active_tool_kind { + Some(ToolKind::Sword(_)) => { + let stage_time = s.timer.as_secs_f64(); + match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f64() + }, + StageSection::Swing => { + stage_time / s.static_data.swing_duration.as_secs_f64() + }, + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f64() + }, + _ => 0.0, + } + }, + _ => state.state_time + }; + anim::character::SpinMeleeAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, vel.0, time), - state.state_time, + (active_tool_kind, second_tool_kind, vel.0, time, Some(s.stage_section),), + stage_progress, &mut state_animation_rate, skeleton_attr, )