From 38094b6ba29cc2f89d0b485bb442a001067cad6d Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 20 Feb 2022 23:39:28 -0500 Subject: [PATCH] Finisher melee --- .../common/abilities/ability_set_manifest.ron | 4 +- .../common/abilities/sword/testfinisher.ron | 27 +++ common/src/comp/ability.rs | 76 +++++++- common/src/comp/character_state.rs | 13 ++ common/src/states/finisher_melee.rs | 171 ++++++++++++++++++ common/src/states/mod.rs | 1 + common/systems/src/stats.rs | 71 ++++---- 7 files changed, 324 insertions(+), 39 deletions(-) create mode 100644 assets/common/abilities/sword/testfinisher.ron create mode 100644 common/src/states/finisher_melee.rs diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 7f7dad517b..73dad63d26 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -4,7 +4,9 @@ Tool(Sword): ( primary: "common.abilities.sword.balancedstance", secondary: "common.abilities.sword.lunge", - abilities: [], + abilities: [ + (None, "common.abilities.sword.testfinisher"), + ], ), Tool(Axe): ( primary: "common.abilities.axe.doublestrike", diff --git a/assets/common/abilities/sword/testfinisher.ron b/assets/common/abilities/sword/testfinisher.ron new file mode 100644 index 0000000000..0962d76ea6 --- /dev/null +++ b/assets/common/abilities/sword/testfinisher.ron @@ -0,0 +1,27 @@ +FinisherMelee( + energy_cost: 40, + buildup_duration: 0.2, + swing_duration: 0.05, + recover_duration: 0.2, + melee_constructor: ( + kind: Slash( + damage: 0, + poise: 0, + knockback: 0, + energy_regen: 0, + ), + range: 3.0, + angle: 10.0, + scaled: Some(Slash( + damage: 25, + poise: 0, + knockback: 0, + energy_regen: 0, + )), + ), + minimum_combo: 10, + scaling: Some(( + target: Attack, + kind: Linear, + )), +) \ No newline at end of file diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index c5e0a82930..715800fc3a 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -405,7 +405,8 @@ impl From<&CharacterState> for CharacterAbilityType { | CharacterState::SpriteInteract(_) | CharacterState::Skate(_) | CharacterState::Wallrun(_) - | CharacterState::ComboMelee2(_) => Self::Other, + | CharacterState::ComboMelee2(_) + | CharacterState::FinisherMelee(_) => Self::Other, } } } @@ -657,6 +658,19 @@ pub enum CharacterAbility { Music { play_duration: f32, ori_modifier: f32, + #[serde(default)] + meta: AbilityMeta, + }, + FinisherMelee { + energy_cost: f32, + buildup_duration: f32, + swing_duration: f32, + recover_duration: f32, + melee_constructor: MeleeConstructor, + minimum_combo: u32, + scaling: Option, + #[serde(default)] + meta: AbilityMeta, }, } @@ -729,6 +743,14 @@ impl CharacterAbility { | !*scales_with_combo) && update.energy.try_change_by(-*energy_cost).is_ok() }, + CharacterAbility::FinisherMelee { + energy_cost, + minimum_combo, + .. + } => { + data.combo.map_or(false, |c| c.counter() >= *minimum_combo) + && update.energy.try_change_by(-*energy_cost).is_ok() + }, CharacterAbility::ComboMelee { .. } | CharacterAbility::ComboMelee2 { .. } | CharacterAbility::Boost { .. } @@ -1144,9 +1166,26 @@ impl CharacterAbility { Music { ref mut play_duration, ori_modifier: _, + meta: _, } => { *play_duration /= stats.speed; }, + FinisherMelee { + ref mut energy_cost, + ref mut buildup_duration, + ref mut swing_duration, + ref mut recover_duration, + ref mut melee_constructor, + minimum_combo: _, + scaling: _, + meta: _, + } => { + *buildup_duration /= stats.speed; + *swing_duration /= stats.speed; + *recover_duration /= stats.speed; + *energy_cost /= stats.energy_efficiency; + *melee_constructor = melee_constructor.adjusted_by_stats(stats); + }, } self } @@ -1166,7 +1205,8 @@ impl CharacterAbility { | Shockwave { energy_cost, .. } | BasicAura { energy_cost, .. } | BasicBlock { energy_cost, .. } - | SelfBuff { energy_cost, .. } => *energy_cost, + | SelfBuff { energy_cost, .. } + | FinisherMelee { energy_cost, .. } => *energy_cost, BasicBeam { energy_drain, .. } => { if *energy_drain > f32::EPSILON { 1.0 @@ -1207,7 +1247,9 @@ impl CharacterAbility { | ComboMelee2 { meta, .. } | Blink { meta, .. } | BasicSummon { meta, .. } - | SpriteSummon { meta, .. } => *meta, + | SpriteSummon { meta, .. } + | FinisherMelee { meta, .. } + | Music { meta, .. } => *meta, } } @@ -1223,6 +1265,9 @@ impl CharacterAbility { BasicMelee { energy_cost, .. } => { *energy_cost *= ENERGY_REDUCTION; }, + FinisherMelee { energy_cost, .. } => { + *energy_cost *= ENERGY_REDUCTION; + }, _ => {}, } } @@ -2271,6 +2316,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { CharacterAbility::Music { play_duration, ori_modifier, + meta: _, } => CharacterState::Music(music::Data { static_data: music::StaticData { play_duration: Duration::from_secs_f32(*play_duration), @@ -2281,6 +2327,30 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { stage_section: StageSection::Action, exhausted: false, }), + CharacterAbility::FinisherMelee { + energy_cost: _, + buildup_duration, + swing_duration, + recover_duration, + melee_constructor, + minimum_combo, + scaling, + meta: _, + } => CharacterState::FinisherMelee(finisher_melee::Data { + static_data: finisher_melee::StaticData { + buildup_duration: Duration::from_secs_f32(*buildup_duration), + swing_duration: Duration::from_secs_f32(*swing_duration), + recover_duration: Duration::from_secs_f32(*recover_duration), + melee_constructor: *melee_constructor, + scaling: *scaling, + minimum_combo: *minimum_combo, + combo_on_use: data.combo.map_or(0, |c| c.counter()), + ability_info, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + exhausted: false, + }), } } } diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index b03f6be338..632201dccc 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -129,6 +129,8 @@ pub enum CharacterState { Skate(skate::Data), /// Play music instrument Music(music::Data), + /// Melee attack that scales off and consumes combo + FinisherMelee(finisher_melee::Data), } impl CharacterState { @@ -163,6 +165,7 @@ impl CharacterState { was_wielded: true, .. }) + | CharacterState::FinisherMelee(_) ) } @@ -202,6 +205,7 @@ impl CharacterState { | CharacterState::Blink(_) | CharacterState::BasicSummon(_) | CharacterState::SpriteSummon(_) + | CharacterState::FinisherMelee(_) ) } @@ -224,6 +228,7 @@ impl CharacterState { | CharacterState::UseItem(_) | CharacterState::Wielding(_) | CharacterState::Talk + | CharacterState::FinisherMelee(_) ) } @@ -354,6 +359,7 @@ impl CharacterState { CharacterState::SpriteInteract(data) => data.behavior(j, output_events), CharacterState::Skate(data) => data.behavior(j, output_events), CharacterState::Music(data) => data.behavior(j, output_events), + CharacterState::FinisherMelee(data) => data.behavior(j, output_events), } } @@ -403,6 +409,7 @@ impl CharacterState { CharacterState::SpriteInteract(data) => data.handle_event(j, output_events, action), CharacterState::Skate(data) => data.handle_event(j, output_events, action), CharacterState::Music(data) => data.handle_event(j, output_events, action), + CharacterState::FinisherMelee(data) => data.handle_event(j, output_events, action), } } @@ -449,6 +456,9 @@ impl CharacterState { CharacterState::SpriteSummon(data) => Some(data.static_data.ability_info), CharacterState::UseItem(_) => None, CharacterState::SpriteInteract(_) => None, + CharacterState::FinisherMelee(data) => Some(data.static_data.ability_info), + CharacterState::Music(data) => Some(data.static_data.ability_info), + CharacterState::Skate(_) => None, } } @@ -487,6 +497,9 @@ impl CharacterState { CharacterState::SpriteSummon(data) => Some(data.stage_section), CharacterState::UseItem(data) => Some(data.stage_section), CharacterState::SpriteInteract(data) => Some(data.stage_section), + CharacterState::FinisherMelee(data) => Some(data.stage_section), + CharacterState::Music(data) => Some(data.stage_section), + CharacterState::Skate(_) => None, } } } diff --git a/common/src/states/finisher_melee.rs b/common/src/states/finisher_melee.rs new file mode 100644 index 0000000000..5a4f33d607 --- /dev/null +++ b/common/src/states/finisher_melee.rs @@ -0,0 +1,171 @@ +use crate::{ + combat::{CombatBuff, CombatEffect}, + comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate}, + event::ServerEvent, + states::{ + behavior::{CharacterBehavior, JoinData}, + utils::*, + wielding, + }, +}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// How long until state should deal damage + pub buildup_duration: Duration, + /// How long the state is swinging for + pub swing_duration: Duration, + /// How long the state has until exiting + pub recover_duration: Duration, + /// Used to construct the Melee attack + pub melee_constructor: MeleeConstructor, + /// Used to determine if and how scaling of the melee attack should happen + pub scaling: Option, + /// Minimum amount of combo needed to activate ability + pub minimum_combo: u32, + /// Amount of combo when ability was activated + pub combo_on_use: u32, + /// What key is used to press ability + pub ability_info: AbilityInfo, +} + +#[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, + /// What section the character stage is in + pub stage_section: StageSection, + /// Whether the attack can deal more damage + pub exhausted: bool, +} + +impl CharacterBehavior for Data { + fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate { + let mut update = StateUpdate::from(data); + + handle_orientation(data, &mut update, 1.0, None); + handle_move(data, &mut update, 0.7); + handle_jump(data, output_events, &mut update, 1.0); + handle_interrupts(data, &mut update, None); + + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + if let CharacterState::FinisherMelee(c) = &mut update.character { + c.timer = tick_attack_or_default(data, self.timer, None); + } + } else { + // Transitions to swing section of stage + if let CharacterState::FinisherMelee(c) = &mut update.character { + c.timer = Duration::default(); + c.stage_section = StageSection::Action; + } + } + }, + StageSection::Action => { + if !self.exhausted { + if let CharacterState::FinisherMelee(c) = &mut update.character { + c.timer = Duration::default(); + c.exhausted = true; + } + + // Consume combo + output_events.emit_server(ServerEvent::ComboChange { + entity: data.entity, + change: -(self.static_data.combo_on_use as i32), + }); + + let mut melee_constructor = self.static_data.melee_constructor; + + if let Some(scaling) = self.static_data.scaling { + let scaled_by = match scaling.kind { + ScalingKind::Linear => { + self.static_data.combo_on_use as f32 + / self.static_data.minimum_combo as f32 + }, + }; + match scaling.target { + ScalingTarget::Attack => { + melee_constructor = melee_constructor.handle_scaling(scaled_by); + }, + ScalingTarget::Buff => { + if let Some(CombatEffect::Buff(CombatBuff { strength, .. })) = + &mut melee_constructor.damage_effect + { + *strength *= scaled_by; + } + }, + } + } + + let crit_data = get_crit_data(data, self.static_data.ability_info); + let buff_strength = get_buff_strength(data, self.static_data.ability_info); + + data.updater.insert( + data.entity, + melee_constructor.create_melee(crit_data, buff_strength), + ); + } else if self.timer < self.static_data.swing_duration { + // Swings + if let CharacterState::FinisherMelee(c) = &mut update.character { + c.timer = tick_attack_or_default(data, self.timer, None); + } + } else { + // Transitions to recover section of stage + if let CharacterState::FinisherMelee(c) = &mut update.character { + c.timer = Duration::default(); + c.stage_section = StageSection::Recover + } + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + // Recovery + if let CharacterState::FinisherMelee(c) = &mut update.character { + c.timer = tick_attack_or_default(data, self.timer, None); + } + } else { + // Done + update.character = + CharacterState::Wielding(wielding::Data { is_sneaking: false }); + // 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(wielding::Data { is_sneaking: false }); + // Make sure attack component is removed + data.updater.remove::(data.entity); + }, + } + + update + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ScalingTarget { + Attack, + Buff, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ScalingKind { + // Reaches a scaling of 1 when at minimum combo, and a scaling of 2 when at double minimum + // combo + Linear, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Scaling { + pub target: ScalingTarget, + pub kind: ScalingKind, +} diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index 254d007d04..7f4af9e7ee 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -15,6 +15,7 @@ pub mod combo_melee2; pub mod dance; pub mod dash_melee; pub mod equipping; +pub mod finisher_melee; pub mod glide; pub mod glide_wield; pub mod idle; diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index 2e47cd777c..c2aa98349d 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -145,16 +145,16 @@ impl<'a> System<'a> for Sys { { match character_state { // Accelerate recharging energy. - CharacterState::Idle { .. } - | CharacterState::Talk { .. } - | CharacterState::Sit { .. } - | CharacterState::Dance { .. } - | CharacterState::Glide { .. } - | CharacterState::Skate { .. } - | CharacterState::GlideWield { .. } - | CharacterState::Wielding { .. } - | CharacterState::Equipping { .. } - | CharacterState::Boost { .. } => { + CharacterState::Idle(_) + | CharacterState::Talk + | CharacterState::Sit + | CharacterState::Dance + | CharacterState::Glide(_) + | CharacterState::Skate(_) + | CharacterState::GlideWield(_) + | CharacterState::Wielding(_) + | CharacterState::Equipping(_) + | CharacterState::Boost(_) => { let res = { energy.current() < energy.maximum() }; if res { @@ -179,36 +179,37 @@ impl<'a> System<'a> for Sys { } }, // Ability use does not regen and sets the rate back to zero. - CharacterState::BasicMelee { .. } - | CharacterState::DashMelee { .. } - | CharacterState::LeapMelee { .. } - | CharacterState::SpinMelee { .. } - | CharacterState::ComboMelee { .. } - | CharacterState::ComboMelee2 { .. } - | CharacterState::BasicRanged { .. } - | CharacterState::Music { .. } - | CharacterState::ChargedMelee { .. } - | CharacterState::ChargedRanged { .. } - | CharacterState::RepeaterRanged { .. } - | CharacterState::Shockwave { .. } - | CharacterState::BasicBeam { .. } - | CharacterState::BasicAura { .. } - | CharacterState::Blink { .. } - | CharacterState::BasicSummon { .. } - | CharacterState::SelfBuff { .. } - | CharacterState::SpriteSummon { .. } => { + CharacterState::BasicMelee(_) + | CharacterState::DashMelee(_) + | CharacterState::LeapMelee(_) + | CharacterState::SpinMelee(_) + | CharacterState::ComboMelee(_) + | CharacterState::ComboMelee2(_) + | CharacterState::BasicRanged(_) + | CharacterState::Music(_) + | CharacterState::ChargedMelee(_) + | CharacterState::ChargedRanged(_) + | CharacterState::RepeaterRanged(_) + | CharacterState::Shockwave(_) + | CharacterState::BasicBeam(_) + | CharacterState::BasicAura(_) + | CharacterState::Blink(_) + | CharacterState::BasicSummon(_) + | CharacterState::SelfBuff(_) + | CharacterState::SpriteSummon(_) + | CharacterState::FinisherMelee(_) => { if energy.regen_rate != 0.0 { energy.regen_rate = 0.0 } }, // Abilities that temporarily stall energy gain, but preserve regen_rate. - CharacterState::Roll { .. } - | CharacterState::Wallrun { .. } - | CharacterState::Climb { .. } - | CharacterState::Stunned { .. } - | CharacterState::BasicBlock { .. } - | CharacterState::UseItem { .. } - | CharacterState::SpriteInteract { .. } => {}, + CharacterState::Roll(_) + | CharacterState::Wallrun(_) + | CharacterState::Climb(_) + | CharacterState::Stunned(_) + | CharacterState::BasicBlock(_) + | CharacterState::UseItem(_) + | CharacterState::SpriteInteract(_) => {}, } }