From 46c2af8114e1aa9dc9e67d5003e7d10043c971f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Rom=C3=A1n=20N=C3=BA=C3=B1ez?= Date: Fri, 25 Sep 2026 19:59:53 +0200 Subject: [PATCH] Implement crouching --- src/fighter/animation.rs | 3 + src/fighter/state/crouch.rs | 126 ++++++++++++++++++++++++++++++++++++ src/fighter/state/ground.rs | 5 +- src/fighter/state/mod.rs | 2 + 4 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 src/fighter/state/crouch.rs diff --git a/src/fighter/animation.rs b/src/fighter/animation.rs index 567c223..0667ee7 100644 --- a/src/fighter/animation.rs +++ b/src/fighter/animation.rs @@ -84,6 +84,9 @@ pub enum AnimKind { AirDodge, Fall, Turn, + CrouchEnter, + CrouchIdle, + CrouchExit, FlinchGround, FlinchAir, diff --git a/src/fighter/state/crouch.rs b/src/fighter/state/crouch.rs new file mode 100644 index 0000000..0b92390 --- /dev/null +++ b/src/fighter/state/crouch.rs @@ -0,0 +1,126 @@ +use super::{ + CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext, ground, +}; +use crate::fighter::animation::AnimKind; +use crate::fighter::state::{FighterState, dash, ground_attack, turn, walk}; +use crate::fighter::state::ground::{grounded_movement_common_interrupts, grounded_movement_standstill_common_interrupts}; + +#[derive(Debug, Clone, Hash)] +pub enum CrouchSubState { + Entering, + Idle, + Exiting +} + +#[derive(Debug, Clone, Hash)] +pub struct CrouchState { + sub_state: CrouchSubState +} + +pub fn crouch_idle_interrupts( + state_context: &InterruptContext, +) -> Option { + if let Some(state) = ground_attack::check_interrupt(state_context) { + Some(state) + } else if let Some(state) = dash::check_input(state_context) { + Some(state) + } else if let Some(state) = turn::check_input(state_context) { + Some(state) + } else if let Some(state) = walk::check_input(state_context) { + Some(state) + } else { + None + } +} + +pub fn crouch_entering_interrupts( + state_context: &InterruptContext, +) -> Option { + if let Some(state) = ground_attack::check_interrupt(state_context) { + Some(state) + } else { + None + } +} + +impl FighterStateImpl for CrouchState { + const NAME: &'static str = "Crouch"; + fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option { + if let Some(state) = grounded_movement_common_interrupts(state_context) { + return Some(state); + } + + match self.sub_state { + CrouchSubState::Entering => { + // While entering the crouch animation we do not allow movement inputs, but we do allow attacks and jumps + if state_context.is_current_animation_finished() { + return Some(FighterState::Crouch( + CrouchState { + sub_state: CrouchSubState::Idle, + } + )); + } else { + return crouch_entering_interrupts(state_context); + } + }, + CrouchSubState::Idle => { + if state_context.input.get_last_frame().movement.y > -state_context.game_settings.input_common.run_stick_threshold { + return Some(FighterState::Crouch ( + CrouchState { + sub_state: CrouchSubState::Exiting, + } + )); + } else { + return crouch_idle_interrupts(state_context); + } + }, + CrouchSubState::Exiting => { + if state_context.is_current_animation_finished() { + return grounded_movement_standstill_common_interrupts(state_context); + } else { + return crouch_idle_interrupts(state_context); + } + }, + } + } + + fn on_enter(&mut self, state_context: &mut EnterContext) { + match self.sub_state { + CrouchSubState::Entering => { + state_context.play_animation(AnimKind::CrouchEnter, false); + }, + CrouchSubState::Idle => { + state_context.play_animation(AnimKind::CrouchIdle, true); + }, + CrouchSubState::Exiting => { + state_context.play_animation(AnimKind::CrouchExit, false); + }, + } + } + + fn update(&mut self, state_context: &mut UpdateContext) { + ground::update_friction_motion(state_context); + } + + fn check_collision_interrupt( + &mut self, + state_context: &mut CollisionContext, + ) -> Option { + ground::collision_interrupt(state_context, false) + } + + fn is_visually_grounded<'a>(&'a self) -> bool { + true + } +} + +pub fn check_input(state_context: &InterruptContext) -> Option { + let input_frame = state_context.input.get_last_frame(); + + if input_frame.movement.y <= -state_context.game_settings.input_common.run_stick_threshold { + return Some(FighterState::Crouch(CrouchState { + sub_state: CrouchSubState::Entering + })); + } + None +} diff --git a/src/fighter/state/ground.rs b/src/fighter/state/ground.rs index 7ae4f21..bf7ef58 100644 --- a/src/fighter/state/ground.rs +++ b/src/fighter/state/ground.rs @@ -3,8 +3,7 @@ //! bit-for-bit deterministic for rollback. use crate::fighter::state::{ - CollisionContext, FighterState, InterruptContext, air, dash, fall, ground_attack, turn, wait, - walk, + CollisionContext, FighterState, InterruptContext, air, crouch, dash, fall, ground_attack, turn, wait, walk, }; use crate::fighter::{ FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity, @@ -461,6 +460,8 @@ pub fn grounded_movement_standstill_common_interrupts( Some(state) } else if let Some(state) = turn::check_input(state_context) { Some(state) + } else if let Some(state) = crouch::check_input(state_context) { + Some(state) } else if let Some(state) = walk::check_input(state_context) { Some(state) } else if let Some(state) = wait::check_input(state_context) { diff --git a/src/fighter/state/mod.rs b/src/fighter/state/mod.rs index 8c743d7..469e14d 100644 --- a/src/fighter/state/mod.rs +++ b/src/fighter/state/mod.rs @@ -41,6 +41,7 @@ pub mod run; pub mod turn; pub mod wait; pub mod walk; +pub mod crouch; #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum SurfacePolicy { @@ -211,6 +212,7 @@ impl CollisionContext<'_> { fighter_states! { Wait => wait::WaitState, Walk => walk::WalkState, + Crouch => crouch::CrouchState, Dash => dash::DashState, Run => run::RunState, Fall => fall::FallState, -- 2.54.0