From fe47a14ba5f57aab840d38245c6ee91b0001fea1 Mon Sep 17 00:00:00 2001
From: Samuel Keiffer <samuelkeiffer@gmail.com>
Date: Wed, 8 Jul 2020 19:58:41 +0000
Subject: [PATCH] Spin attack for axe

---
 CHANGELOG.md                                  |   1 +
 assets/voxygen/element/icons/2haxe_m1.png     |   4 +-
 .../voxygen/element/icons/skill_axespin.png   |   3 +
 assets/voxygen/voxel/weapon/axe/rusty_2h.vox  |   4 +-
 common/src/comp/ability.rs                    |  32 ++++
 common/src/comp/character_state.rs            |   8 +-
 common/src/comp/inventory/item/tool.rs        |  10 +-
 common/src/states/mod.rs                      |   1 +
 common/src/states/spin_melee.rs               | 146 +++++++++++++++
 common/src/sys/character_behavior.rs          |   2 +
 common/src/sys/stats.rs                       |   1 +
 voxygen/src/anim/src/character/mod.rs         |   5 +-
 voxygen/src/anim/src/character/spinmelee.rs   | 170 ++++++++++++++++++
 voxygen/src/anim/src/character/wield.rs       |  62 +++++--
 voxygen/src/hud/img_ids.rs                    |   1 +
 voxygen/src/hud/skillbar.rs                   |   2 +-
 voxygen/src/scene/figure/mod.rs               |   9 +
 17 files changed, 428 insertions(+), 33 deletions(-)
 create mode 100644 assets/voxygen/element/icons/skill_axespin.png
 create mode 100644 common/src/states/spin_melee.rs
 create mode 100644 voxygen/src/anim/src/character/spinmelee.rs

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90d4ddc625..c81a1137d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Better pathfinding
 - Bombs
 - Training dummy items
+- Added spin attack for axe
 
 ### Changed
 
diff --git a/assets/voxygen/element/icons/2haxe_m1.png b/assets/voxygen/element/icons/2haxe_m1.png
index 5b26cd998a..42871690f2 100644
--- a/assets/voxygen/element/icons/2haxe_m1.png
+++ b/assets/voxygen/element/icons/2haxe_m1.png
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:bddd94d15a337c957883c58d8c0cc437f59c1adab9cadd083d3bbaeedc4540be
-size 947
+oid sha256:9804ff754021eaec1c916001cb04dd690cd489b864b9e0b29e5f1a1606023545
+size 517
diff --git a/assets/voxygen/element/icons/skill_axespin.png b/assets/voxygen/element/icons/skill_axespin.png
new file mode 100644
index 0000000000..f6d46e2212
--- /dev/null
+++ b/assets/voxygen/element/icons/skill_axespin.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a86ceb4f26f3e2d1ce1e346c02ef424033e5e955152226998d55a812efa571d0
+size 479
diff --git a/assets/voxygen/voxel/weapon/axe/rusty_2h.vox b/assets/voxygen/voxel/weapon/axe/rusty_2h.vox
index 6c88590b64..de6bed8668 100644
--- a/assets/voxygen/voxel/weapon/axe/rusty_2h.vox
+++ b/assets/voxygen/voxel/weapon/axe/rusty_2h.vox
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:32f582124883285583904f940e6ee84c79972fc20202c4c12f57cc718758c9ff
-size 1752
+oid sha256:41c7d03d3f941210ad3f8e278477ddf154233cf00c03267220d0623028bf55e9
+size 2088
diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs
index cb5bba0419..b09b7bfaf8 100644
--- a/common/src/comp/ability.rs
+++ b/common/src/comp/ability.rs
@@ -20,6 +20,7 @@ pub enum CharacterAbilityType {
     BasicBlock,
     TripleStrike(Stage),
     LeapMelee,
+    SpinMelee,
 }
 
 impl From<&CharacterState> for CharacterAbilityType {
@@ -32,6 +33,7 @@ impl From<&CharacterState> for CharacterAbilityType {
             CharacterState::BasicBlock => Self::BasicBlock,
             CharacterState::LeapMelee(_) => Self::LeapMelee,
             CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage),
+            CharacterState::SpinMelee(_) => Self::SpinMelee,
             _ => Self::BasicMelee,
         }
     }
@@ -80,6 +82,12 @@ pub enum CharacterAbility {
         recover_duration: Duration,
         base_damage: u32,
     },
+    SpinMelee {
+        energy_cost: u32,
+        buildup_duration: Duration,
+        recover_duration: Duration,
+        base_damage: u32,
+    },
 }
 
 impl CharacterAbility {
@@ -117,6 +125,10 @@ impl CharacterAbility {
                 .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)
+                .is_ok(),
             _ => true,
         }
     }
@@ -239,6 +251,26 @@ impl From<&CharacterAbility> for CharacterState {
                 recover_duration: *recover_duration,
                 base_damage: *base_damage,
             }),
+            CharacterAbility::SpinMelee {
+                energy_cost,
+                buildup_duration,
+                recover_duration,
+                base_damage,
+            } => 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 */
+            }),
         }
     }
 }
diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs
index 3b6c5ba2e0..ee132711fd 100644
--- a/common/src/comp/character_state.rs
+++ b/common/src/comp/character_state.rs
@@ -64,6 +64,8 @@ pub enum CharacterState {
     TripleStrike(triple_strike::Data),
     /// A leap followed by a small aoe ground attack
     LeapMelee(leap_melee::Data),
+    /// Spin around, dealing damage to enemies surrounding you
+    SpinMelee(spin_melee::Data),
 }
 
 impl CharacterState {
@@ -75,7 +77,8 @@ impl CharacterState {
             | CharacterState::DashMelee(_)
             | CharacterState::TripleStrike(_)
             | CharacterState::BasicBlock
-            | CharacterState::LeapMelee(_) => true,
+            | CharacterState::LeapMelee(_)
+            | CharacterState::SpinMelee(_) => true,
             _ => false,
         }
     }
@@ -86,7 +89,8 @@ impl CharacterState {
             | CharacterState::BasicRanged(_)
             | CharacterState::DashMelee(_)
             | CharacterState::TripleStrike(_)
-            | CharacterState::LeapMelee(_) => true,
+            | CharacterState::LeapMelee(_)
+            | CharacterState::SpinMelee(_) => true,
             _ => false,
         }
     }
diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs
index 19b741eed8..abeb4a04c9 100644
--- a/common/src/comp/inventory/item/tool.rs
+++ b/common/src/comp/inventory/item/tool.rs
@@ -221,13 +221,11 @@ impl Tool {
                     base_damage: 7,
                     needs_timing: true,
                 },
-                BasicMelee {
+                SpinMelee {
                     energy_cost: 100,
-                    buildup_duration: Duration::from_millis(700),
-                    recover_duration: Duration::from_millis(100),
-                    base_healthchange: -12,
-                    range: 3.5,
-                    max_angle: 30.0,
+                    buildup_duration: Duration::from_millis(125),
+                    recover_duration: Duration::from_millis(125),
+                    base_damage: 5,
                 },
             ],
             Hammer(_) => vec![
diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs
index aaf7f0bdc1..35676a2d17 100644
--- a/common/src/states/mod.rs
+++ b/common/src/states/mod.rs
@@ -12,6 +12,7 @@ pub mod idle;
 pub mod leap_melee;
 pub mod roll;
 pub mod sit;
+pub mod spin_melee;
 pub mod triple_strike;
 pub mod utils;
 pub mod wielding;
diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs
new file mode 100644
index 0000000000..55d00c14db
--- /dev/null
+++ b/common/src/states/spin_melee.rs
@@ -0,0 +1,146 @@
+use crate::{
+    comp::{Attacking, CharacterState, EnergySource, StateUpdate},
+    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 {
+    /// 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 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,
+    /// Energy cost per attack
+    pub energy_cost: u32,
+}
+
+const MOVE_SPEED: f32 = 5.0;
+
+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
+            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
+                    .checked_sub(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,
+            });
+        } else if !self.exhausted {
+            //Hit attempt
+            data.updater.insert(data.entity, Attacking {
+                base_healthchange: -(self.base_damage as i32),
+                range: 3.5,
+                max_angle: 360_f32.to_radians(),
+                applied: false,
+                hit_count: 0,
+                knockback: 0.0,
+            });
+
+            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()
+        {
+            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,
+            });
+            // Consumes energy if there's enough left and RMB is held down
+            update
+                .energy
+                .change_by(-(self.energy_cost as i32), EnergySource::Ability);
+        } else {
+            // Done
+            update.character = CharacterState::Wielding;
+            // Make sure attack component is removed
+            data.updater.remove::<Attacking>(data.entity);
+        }
+
+        // Grant energy on successful hit
+        if let Some(attack) = data.attacking {
+            if attack.applied && attack.hit_count > 0 {
+                data.updater.remove::<Attacking>(data.entity);
+                update.energy.change_by(10, EnergySource::HitEnemy);
+            }
+        }
+
+        update
+    }
+}
diff --git a/common/src/sys/character_behavior.rs b/common/src/sys/character_behavior.rs
index 586dc239ba..496a3f2207 100644
--- a/common/src/sys/character_behavior.rs
+++ b/common/src/sys/character_behavior.rs
@@ -244,6 +244,7 @@ impl<'a> System<'a> for Sys {
                     CharacterState::Boost(data) => data.handle_event(&j, action),
                     CharacterState::DashMelee(data) => data.handle_event(&j, action),
                     CharacterState::LeapMelee(data) => data.handle_event(&j, action),
+                    CharacterState::SpinMelee(data) => data.handle_event(&j, action),
                 };
                 local_emitter.append(&mut state_update.local_events);
                 server_emitter.append(&mut state_update.server_events);
@@ -269,6 +270,7 @@ impl<'a> System<'a> for Sys {
                 CharacterState::Boost(data) => data.behavior(&j),
                 CharacterState::DashMelee(data) => data.behavior(&j),
                 CharacterState::LeapMelee(data) => data.behavior(&j),
+                CharacterState::SpinMelee(data) => data.behavior(&j),
             };
 
             local_emitter.append(&mut state_update.local_events);
diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs
index 2335c64fce..8f7d57b100 100644
--- a/common/src/sys/stats.rs
+++ b/common/src/sys/stats.rs
@@ -103,6 +103,7 @@ impl<'a> System<'a> for Sys {
                 CharacterState::BasicMelee { .. }
                 | CharacterState::DashMelee { .. }
                 | CharacterState::LeapMelee { .. }
+                | CharacterState::SpinMelee { .. }
                 | CharacterState::TripleStrike { .. }
                 | CharacterState::BasicRanged { .. } => {
                     if energy.get_unchecked().regen_rate != 0.0 {
diff --git a/voxygen/src/anim/src/character/mod.rs b/voxygen/src/anim/src/character/mod.rs
index 6416638929..f3440fa3ed 100644
--- a/voxygen/src/anim/src/character/mod.rs
+++ b/voxygen/src/anim/src/character/mod.rs
@@ -17,6 +17,7 @@ pub mod run;
 pub mod shoot;
 pub mod sit;
 pub mod spin;
+pub mod spinmelee;
 pub mod stand;
 pub mod swim;
 pub mod wield;
@@ -28,8 +29,8 @@ pub use self::{
     dance::DanceAnimation, dash::DashAnimation, equip::EquipAnimation,
     glidewield::GlideWieldAnimation, gliding::GlidingAnimation, idle::IdleAnimation,
     jump::JumpAnimation, leapmelee::LeapAnimation, roll::RollAnimation, run::RunAnimation,
-    shoot::ShootAnimation, sit::SitAnimation, spin::SpinAnimation, stand::StandAnimation,
-    swim::SwimAnimation, wield::WieldAnimation,
+    shoot::ShootAnimation, sit::SitAnimation, spin::SpinAnimation, spinmelee::SpinMeleeAnimation,
+    stand::StandAnimation, swim::SwimAnimation, wield::WieldAnimation,
 };
 
 use super::{Bone, FigureBoneData, Skeleton};
diff --git a/voxygen/src/anim/src/character/spinmelee.rs b/voxygen/src/anim/src/character/spinmelee.rs
new file mode 100644
index 0000000000..26b4f43269
--- /dev/null
+++ b/voxygen/src/anim/src/character/spinmelee.rs
@@ -0,0 +1,170 @@
+use super::{super::Animation, CharacterSkeleton, SkeletonAttr};
+use common::comp::item::{Hands, ToolKind};
+use std::f32::consts::PI;
+use vek::*;
+
+pub struct SpinMeleeAnimation;
+
+impl Animation for SpinMeleeAnimation {
+    type Dependency = (Option<ToolKind>, Option<ToolKind>, Vec3<f32>, f64);
+    type Skeleton = CharacterSkeleton;
+
+    #[cfg(feature = "use-dyn-lib")]
+    const UPDATE_FN: &'static [u8] = b"character_spinmelee\0";
+
+    #[cfg_attr(feature = "be-dyn-lib", export_name = "character_spinmelee")]
+    #[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,
+        anim_time: f64,
+        rate: &mut f32,
+        skeleton_attr: &SkeletonAttr,
+    ) -> Self::Skeleton {
+        *rate = 1.0;
+        let lab = 1.0;
+        let speed = Vec2::<f32>::from(velocity).magnitude();
+        let mut next = (*skeleton).clone();
+        //torso movement
+        let xshift = if velocity.z.abs() < 0.1 {
+            ((anim_time as f32 - 1.1) * lab as f32 * 3.0).sin()
+        } else {
+            0.0
+        };
+        let yshift = if velocity.z.abs() < 0.1 {
+            ((anim_time as f32 - 1.1) * lab as f32 * 3.0 + PI / 2.0).sin()
+        } else {
+            0.0
+        };
+
+        let spin = if anim_time < 1.1 && velocity.z.abs() < 0.1 {
+            0.5 * ((anim_time as f32).powf(2.0))
+        } else {
+            lab as f32 * anim_time as f32 * 0.9
+        };
+
+        //feet
+        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.offset = Vec3::new(-0.5, 0.0, 4.0);
+            next.l_hand.ori = 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.offset = Vec3::new(0.5, 0.0, -2.5);
+            next.r_hand.ori = 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.offset = Vec3::new(-0.0, -2.0, -1.0);
+            next.main.ori = Quaternion::rotation_x(0.0)
+                * Quaternion::rotation_y(0.0)
+                * Quaternion::rotation_z(0.0);
+
+            next.control.offset = Vec3::new(0.0, 16.0, 3.0);
+            next.control.ori = Quaternion::rotation_x(-1.4)
+                * Quaternion::rotation_y(0.0)
+                * Quaternion::rotation_z(1.4);
+            next.control.scale = Vec3::one();
+
+            next.head.offset = Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1);
+            next.head.ori = Quaternion::rotation_z(0.0)
+                * Quaternion::rotation_x(-0.15)
+                * Quaternion::rotation_y(0.08);
+            next.chest.offset = Vec3::new(
+                0.0,
+                skeleton_attr.chest.0 - 3.0,
+                skeleton_attr.chest.1 - 2.0,
+            );
+            next.chest.ori = Quaternion::rotation_z(0.0)
+                * Quaternion::rotation_x(-0.1)
+                * Quaternion::rotation_y(0.3);
+            next.chest.scale = Vec3::one();
+
+            next.belt.offset = Vec3::new(0.0, 1.0, -1.0);
+            next.belt.ori = Quaternion::rotation_z(0.0)
+                * Quaternion::rotation_x(0.4)
+                * Quaternion::rotation_y(0.0);
+            next.belt.scale = Vec3::one() * 0.98;
+            next.shorts.offset = Vec3::new(0.0, 3.0, -2.5);
+            next.shorts.ori = Quaternion::rotation_z(0.0)
+                * Quaternion::rotation_x(0.7)
+                * Quaternion::rotation_y(0.0);
+            next.shorts.scale = Vec3::one();
+            next.torso.offset = 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.ori = 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.offset = Vec3::new(-skeleton_attr.foot.0, 8.0, skeleton_attr.foot.2 + 2.0);
+            next.l_foot.ori = Quaternion::rotation_x(1.0) * Quaternion::rotation_z(0.0);
+            next.l_foot.scale = Vec3::one();
+
+            next.r_foot.offset = Vec3::new(skeleton_attr.foot.0, 8.0, skeleton_attr.foot.2 + 2.0);
+            next.r_foot.ori = Quaternion::rotation_x(1.0);
+            next.r_foot.scale = Vec3::one();
+        } else if speed < 0.5 {
+            next.l_foot.offset = Vec3::new(
+                -skeleton_attr.foot.0,
+                2.0 + quick * -6.0,
+                skeleton_attr.foot.2,
+            );
+            next.l_foot.ori =
+                Quaternion::rotation_x(0.5 + slowersmooth * 0.2) * Quaternion::rotation_z(0.0);
+            next.l_foot.scale = Vec3::one();
+
+            next.r_foot.offset = Vec3::new(skeleton_attr.foot.0, 4.0, skeleton_attr.foot.2);
+            next.r_foot.ori =
+                Quaternion::rotation_x(0.5 - slowersmooth * 0.2) * Quaternion::rotation_y(-0.4);
+            next.r_foot.scale = Vec3::one();
+        } else {
+            next.l_foot.offset = Vec3::new(
+                -skeleton_attr.foot.0,
+                2.0 + quick * -6.0,
+                skeleton_attr.foot.2,
+            );
+            next.l_foot.ori =
+                Quaternion::rotation_x(0.5 + slowersmooth * 0.2) * Quaternion::rotation_z(0.0);
+            next.l_foot.scale = Vec3::one();
+
+            next.r_foot.offset = Vec3::new(
+                skeleton_attr.foot.0,
+                2.0 + quick * 6.0,
+                skeleton_attr.foot.2,
+            );
+            next.r_foot.ori =
+                Quaternion::rotation_x(0.5 - slowersmooth * 0.2) * Quaternion::rotation_z(0.0);
+            next.r_foot.scale = Vec3::one();
+        };
+        next.lantern.offset = Vec3::new(
+            skeleton_attr.lantern.0,
+            skeleton_attr.lantern.1,
+            skeleton_attr.lantern.2,
+        );
+        next.lantern.ori = Quaternion::rotation_z(0.0)
+            * Quaternion::rotation_x(0.7)
+            * Quaternion::rotation_y(-0.8);
+        next.glider.offset = Vec3::new(0.0, 0.0, 10.0);
+        next.glider.scale = Vec3::one() * 0.0;
+        next.l_control.scale = Vec3::one();
+        next.r_control.scale = Vec3::one();
+
+        next.second.scale = match (
+            active_tool_kind.map(|tk| tk.into_hands()),
+            second_tool_kind.map(|tk| tk.into_hands()),
+        ) {
+            (Some(Hands::OneHand), Some(Hands::OneHand)) => Vec3::one(),
+            (_, _) => Vec3::zero(),
+        };
+
+        next
+    }
+}
diff --git a/voxygen/src/anim/src/character/wield.rs b/voxygen/src/anim/src/character/wield.rs
index eb11f819aa..299146b5f4 100644
--- a/voxygen/src/anim/src/character/wield.rs
+++ b/voxygen/src/anim/src/character/wield.rs
@@ -131,7 +131,7 @@ impl Animation for WieldAnimation {
                 let hand_scale = 1.12;
 
                 next.control.offset = Vec3::new(0.0, 0.0, 0.0);
-                // next.control.ori = Quaternion::rotation_x(u_slow * 0.15 + 1.0)
+                //next.control.ori = Quaternion::rotation_x(slow * 1.0);
                 //     * Quaternion::rotation_y(0.0)
                 //     * Quaternion::rotation_z(u_slowalt * 0.08);
                 // next.control.scale = Vec3::one();
@@ -172,26 +172,52 @@ impl Animation for WieldAnimation {
                 // next.r_control.scale = Vec3::one();
             },
             Some(ToolKind::Axe(_)) => {
-                next.l_hand.offset = Vec3::new(-4.0, 3.0, 6.0);
-                next.l_hand.ori = Quaternion::rotation_x(-0.3)
-                    * Quaternion::rotation_z(3.14 - 0.3)
-                    * Quaternion::rotation_y(-0.8);
+                if velocity < 0.5 {
+                    next.head.offset = Vec3::new(
+                        0.0,
+                        -3.5 + skeleton_attr.head.0,
+                        skeleton_attr.head.1 + u_slow * 0.1,
+                    );
+                    next.head.ori = Quaternion::rotation_z(head_look.x)
+                        * Quaternion::rotation_x(0.35 + head_look.y.abs());
+                    next.head.scale = Vec3::one() * skeleton_attr.head_scale;
+                    next.chest.ori = Quaternion::rotation_x(-0.35)
+                        * Quaternion::rotation_y(u_slowalt * 0.04)
+                        * Quaternion::rotation_z(0.15);
+                    next.belt.offset =
+                        Vec3::new(0.0, 1.0 + skeleton_attr.belt.0, skeleton_attr.belt.1);
+                    next.belt.ori = Quaternion::rotation_x(0.15)
+                        * Quaternion::rotation_y(u_slowalt * 0.03)
+                        * Quaternion::rotation_z(0.15);
+                    next.shorts.offset =
+                        Vec3::new(0.0, 1.0 + skeleton_attr.shorts.0, skeleton_attr.shorts.1);
+                    next.shorts.ori = Quaternion::rotation_x(0.15) * Quaternion::rotation_z(0.25);
+                    next.control.ori = Quaternion::rotation_x(1.8)
+                        * Quaternion::rotation_y(-0.5)
+                        * Quaternion::rotation_z(PI - 0.2);
+                    next.control.scale = Vec3::one();
+                } else {
+                    next.control.ori = Quaternion::rotation_x(2.1)
+                        * Quaternion::rotation_y(-0.4)
+                        * Quaternion::rotation_z(PI - 0.2);
+                    next.control.scale = Vec3::one();
+                }
+                next.l_hand.offset = Vec3::new(-0.5, 0.0, 4.0);
+                next.l_hand.ori = Quaternion::rotation_x(PI / 2.0)
+                    * Quaternion::rotation_z(0.0)
+                    * Quaternion::rotation_y(0.0);
                 next.l_hand.scale = Vec3::one() * 1.08;
-                next.r_hand.offset = Vec3::new(-2.5, 9.0, 4.0);
-                next.r_hand.ori = Quaternion::rotation_x(-0.3)
-                    * Quaternion::rotation_z(3.14 - 0.3)
-                    * Quaternion::rotation_y(-0.8);
+                next.r_hand.offset = Vec3::new(0.5, 0.0, -2.5);
+                next.r_hand.ori = 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.offset = Vec3::new(-6.0, 10.0, -1.0);
-                next.main.ori = Quaternion::rotation_x(1.27)
-                    * Quaternion::rotation_y(-0.3)
-                    * Quaternion::rotation_z(-0.8);
+                next.main.offset = Vec3::new(-0.0, -2.0, -1.0);
+                next.main.ori = Quaternion::rotation_x(0.0)
+                    * Quaternion::rotation_y(0.0)
+                    * Quaternion::rotation_z(0.0);
 
-                next.control.offset = Vec3::new(0.0, 0.0, 0.0);
-                next.control.ori = Quaternion::rotation_x(u_slowalt * 0.1 + 0.2)
-                    * Quaternion::rotation_y(-0.3)
-                    * Quaternion::rotation_z(u_slow * 0.1 + 0.0);
-                next.control.scale = Vec3::one();
+                next.control.offset = Vec3::new(-3.0, 11.0, 3.0);
             },
             Some(ToolKind::Hammer(_)) => {
                 next.l_hand.offset = Vec3::new(-12.0, 0.0, 0.0);
diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs
index 9357985bb7..50fff76512 100644
--- a/voxygen/src/hud/img_ids.rs
+++ b/voxygen/src/hud/img_ids.rs
@@ -117,6 +117,7 @@ image_ids! {
         flyingrod_m2: "voxygen.element.icons.debug_wand_m2",
         charge: "voxygen.element.icons.skill_charge_3",
         hammerleap: "voxygen.element.icons.skill_hammerleap",
+        axespin: "voxygen.element.icons.skill_axespin",
 
         // Skillbar
         level_up: "voxygen.element.misc_bg.level_up",
diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs
index b8d21f71d8..54a3a9e032 100644
--- a/voxygen/src/hud/skillbar.rs
+++ b/voxygen/src/hud/skillbar.rs
@@ -708,7 +708,7 @@ impl<'a> Widget for Skillbar<'a> {
             Some(ToolKind::Dagger(_)) => self.imgs.onehdagger_m2,
             Some(ToolKind::Shield(_)) => self.imgs.onehshield_m2,
             Some(ToolKind::Hammer(_)) => self.imgs.hammerleap,
-            Some(ToolKind::Axe(_)) => self.imgs.nothing,
+            Some(ToolKind::Axe(_)) => self.imgs.axespin,
             Some(ToolKind::Bow(_)) => self.imgs.bow_m2,
             Some(ToolKind::Staff(StaffKind::Sceptre)) => self.imgs.heal_0,
             Some(ToolKind::Staff(_)) => self.imgs.staff_m2,
diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs
index e1b182bdc6..32db9276ba 100644
--- a/voxygen/src/scene/figure/mod.rs
+++ b/voxygen/src/scene/figure/mod.rs
@@ -708,6 +708,15 @@ impl FigureMgr {
                                 skeleton_attr,
                             )
                         },
+                        CharacterState::SpinMelee(_) => {
+                            anim::character::SpinMeleeAnimation::update_skeleton(
+                                &target_base,
+                                (active_tool_kind, second_tool_kind, vel.0, time),
+                                state.state_time,
+                                &mut state_animation_rate,
+                                skeleton_attr,
+                            )
+                        },
                         CharacterState::TripleStrike(s) => match s.stage {
                             triple_strike::Stage::First => {
                                 anim::character::AlphaAnimation::update_skeleton(