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::(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::(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); + }, } update