From 05091687df818cf6ba8cc9fc8d06443a7ffd9cb9 Mon Sep 17 00:00:00 2001
From: Sam <samuelkeiffer@gmail.com>
Date: Sun, 27 Sep 2020 20:58:49 -0500
Subject: [PATCH] Added keyframes to charged melee.

---
 common/src/comp/ability.rs             |  34 +--
 common/src/comp/inventory/item/tool.rs |   6 +-
 common/src/states/charged_melee.rs     | 291 ++++++++++++++-----------
 3 files changed, 180 insertions(+), 151 deletions(-)

diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs
index cae57558e5..4d44a8bb9b 100644
--- a/common/src/comp/ability.rs
+++ b/common/src/comp/ability.rs
@@ -152,11 +152,11 @@ pub enum CharacterAbility {
         max_damage: u32,
         initial_knockback: f32,
         max_knockback: f32,
-        prepare_duration: Duration,
-        charge_duration: Duration,
-        recover_duration: Duration,
         range: f32,
         max_angle: f32,
+        charge_duration: Duration,
+        swing_duration: Duration,
+        recover_duration: Duration,
     },
     ChargedRanged {
         energy_cost: u32,
@@ -526,24 +526,28 @@ impl From<&CharacterAbility> for CharacterState {
                 max_damage,
                 initial_knockback,
                 max_knockback,
-                prepare_duration,
                 charge_duration,
+                swing_duration,
                 recover_duration,
                 range,
                 max_angle,
             } => CharacterState::ChargedMelee(charged_melee::Data {
+                static_data: charged_melee::StaticData {
+                    energy_drain: *energy_drain,
+                    initial_damage: *initial_damage,
+                    max_damage: *max_damage,
+                    initial_knockback: *initial_knockback,
+                    max_knockback: *max_knockback,
+                    range: *range,
+                    max_angle: *max_angle,
+                    charge_duration: *charge_duration,
+                    swing_duration: *swing_duration,
+                    recover_duration: *recover_duration,
+                },
+                stage_section: StageSection::Buildup,
+                timer: Duration::default(),
                 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,
-                range: *range,
-                max_angle: *max_angle,
+                charge_amount: 0.0,
             }),
             CharacterAbility::ChargedRanged {
                 energy_cost: _,
diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs
index f47ea2ab7f..a7a29cba21 100644
--- a/common/src/comp/inventory/item/tool.rs
+++ b/common/src/comp/inventory/item/tool.rs
@@ -256,11 +256,11 @@ impl Tool {
                     max_damage: (170.0 * self.base_power()) as u32,
                     initial_knockback: 12.0,
                     max_knockback: 60.0,
-                    prepare_duration: Duration::from_millis(200),
-                    charge_duration: Duration::from_millis(1200),
-                    recover_duration: Duration::from_millis(500),
                     range: 3.5,
                     max_angle: 30.0,
+                    charge_duration: Duration::from_millis(1200),
+                    swing_duration: Duration::from_millis(100),
+                    recover_duration: Duration::from_millis(500),
                 },
                 LeapMelee {
                     energy_cost: 700,
diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs
index b7a9fce9c9..c022659254 100644
--- a/common/src/states/charged_melee.rs
+++ b/common/src/states/charged_melee.rs
@@ -1,15 +1,14 @@
 use crate::{
     comp::{Attacking, CharacterState, EnergySource, StateUpdate},
-    states::utils::*,
+    states::utils::{StageSection, *},
     sys::character_behavior::*,
 };
 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,
+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
+/// Separated out to condense update portions of character state
+pub struct StaticData {
     /// How much energy is drained per second when charging
     pub energy_drain: u32,
     /// How much damage is dealt with no charge
@@ -20,18 +19,31 @@ 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,
     /// Max range
     pub range: f32,
     /// Max angle (45.0 will give you a 90.0 angle window)
     pub max_angle: f32,
+    /// How long it takes to charge the weapon to max damage and knockback
+    pub charge_duration: Duration,
+    /// How long the weapon is swinging for
+    pub swing_duration: Duration,
+    /// How long the state has until exiting
+    pub recover_duration: Duration,
+}
+
+#[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,
+    /// Checks what section a stage is in
+    pub stage_section: StageSection,
+    /// Timer for each stage
+    pub timer: Duration,
+    /// Whether the attack fired already
+    pub exhausted: bool,
+    /// How much the attack charged by
+    pub charge_amount: f32,
 }
 
 impl CharacterBehavior for Data {
@@ -41,129 +53,142 @@ impl CharacterBehavior for Data {
         handle_move(data, &mut update, 0.3);
         handle_jump(data, &mut update);
 
-        if self.prepare_duration != Duration::default() {
-            // Prepare (draw back weapon)
-            update.character = CharacterState::ChargedMelee(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,
-                range: self.range,
-                max_angle: self.max_angle,
-            });
-        } else if data.inputs.secondary.is_pressed()
-            && self.charge_timer < self.charge_duration
-            && update.energy.current() > 0
-        {
-            // Charge the attack
-            update.character = CharacterState::ChargedMelee(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,
-                range: self.range,
-                max_angle: self.max_angle,
-            });
+        match self.stage_section {
+            StageSection::Charge => {
+                if data.inputs.secondary.is_pressed()
+                    && self.timer < self.static_data.charge_duration
+                    && update.energy.current() > 0
+                {
+                    let charge = (self.timer.as_secs_f32()
+                        / self.static_data.charge_duration.as_secs_f32())
+                    .min(1.0);
 
-            // 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 attack
-            update.character = CharacterState::ChargedMelee(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,
-                range: self.range,
-                max_angle: self.max_angle,
-            });
+                    // 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,
+                    });
 
-            // 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);
-            let damage = self.initial_damage as f32 + (charge_amount * (self.max_damage - self.initial_damage) as f32);
-            // Hit attempt
-            data.updater.insert(data.entity, Attacking {
-                base_damage: damage as u32,
-                base_heal: 0,
-                range: self.range,
-                max_angle: self.max_angle.to_radians(),
-                applied: false,
-                hit_count: 0,
-                knockback: self.initial_knockback
-                    + charge_amount * (self.max_knockback - self.initial_knockback),
-            });
+                    // 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() {
+                    // 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,
+                    });
 
-            update.character = CharacterState::ChargedMelee(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,
-                range: self.range,
-                max_angle: self.max_angle,
-            });
-        } else if self.recover_duration != Duration::default() {
-            // Recovery
-            update.character = CharacterState::ChargedMelee(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(),
-                range: self.range,
-                max_angle: self.max_angle,
-            });
-        } else {
-            // Done
-            update.character = CharacterState::Wielding;
-            // Make sure attack component is removed
-            data.updater.remove::<Attacking>(data.entity);
+                    // 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,
+                    );
+                } 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,
+                    });
+                }
+            },
+            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 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,
+                        range: self.static_data.range,
+                        max_angle: self.static_data.max_angle.to_radians(),
+                        applied: false,
+                        hit_count: 0,
+                        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,
+                    });
+                } 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,
+                    });
+                } 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,
+                    });
+                }
+            },
+            StageSection::Recover => {
+                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,
+                    });
+                } else {
+                    // Done
+                    update.character = CharacterState::Wielding;
+                    // Make sure attack component is removed
+                    data.updater.remove::<Attacking>(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::<Attacking>(data.entity);
+            },
         }
 
         update