Implement hitstop shake (#42)
CI / build-and-test (push) Successful in 1m22s

Reviewed-on: #42
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #42.
This commit is contained in:
2026-09-18 11:56:53 +00:00
committed by EIREXE
parent b9f1a52bbc
commit 2b5af49e15
19 changed files with 201 additions and 57 deletions
+6
View File
@@ -49,6 +49,12 @@ impl FighterStateImpl for AirDodgeState {
state_context.velocity.0 =
self.direction * state_context.fighter_attributes.air_dodge_velocity;
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
false
}
}
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
+6
View File
@@ -108,6 +108,12 @@ impl FighterStateImpl for DashState {
) -> Option<FighterState> {
ground::collision_interrupt(state_context, &mut self.ground_common, true)
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
true
}
}
fn check_smash_input_with_dir(state_context: &InterruptContext) -> Option<FighterFacingDirection> {
+6
View File
@@ -28,6 +28,12 @@ impl FighterStateImpl for FallState {
crate::fighter::state::land::LandingType::Normal,
)
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
false
}
}
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
+6
View File
@@ -67,6 +67,12 @@ impl FighterStateImpl for GroundAttackState {
) -> Option<FighterState> {
None
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
true
}
}
pub fn check_interrupt(
+9
View File
@@ -95,4 +95,13 @@ impl FighterStateImpl for HitstunState {
}
}
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
match self.hitstop_kind {
HitstopStateKind::Grounded(_) => true,
HitstopStateKind::Airborne => false,
}
}
}
+12
View File
@@ -96,6 +96,12 @@ impl FighterStateImpl for JumpSquatState {
state_context.play_animation(AnimKind::JumpSquat, false);
state_context.input.clear_command(FighterCommands::Jump);
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
true
}
}
impl FighterStateImpl for JumpState {
@@ -174,6 +180,12 @@ impl FighterStateImpl for JumpState {
crate::fighter::state::land::LandingType::Normal,
)
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
false
}
}
pub fn check_input(
+6
View File
@@ -69,4 +69,10 @@ impl FighterStateImpl for LandingState {
}
};
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
true
}
}
+21 -2
View File
@@ -16,8 +16,7 @@ use crate::fighter::manifest::FighterManifest;
use crate::fighter::state::hitstun::HitstunState;
use crate::fighter::visual::FighterAnimations;
use crate::fighter::{
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
FighterPreviousTranslation, FighterTranslation, FighterVelocity,
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop, FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
};
use crate::game_settings::GameSettings;
use crate::input::FighterInput;
@@ -66,6 +65,10 @@ macro_rules! fighter_states {
pub fn check_collision_interrupt(&mut self, ctx: &mut CollisionContext) -> Option<FighterState> {
match self { $(Self::$variant(s) => s.check_collision_interrupt(ctx)),* }
}
pub fn is_visually_grounded(&self) -> bool {
match self { $(Self::$variant(s) => s.is_visually_grounded()),* }
}
}
$(impl From<$ty> for FighterState {
@@ -91,6 +94,10 @@ pub trait FighterStateImpl: Sized {
) -> Option<FighterState> {
None
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool;
}
pub struct AnimationStatus {
@@ -320,6 +327,7 @@ pub struct StatefulFighterQuery {
pub attack_script_assets: &'static FighterAttackScriptAssets,
pub pending_hit_reaction: &'static mut FighterPendingHitReaction,
pub hitstop: &'static mut FighterHitstop,
pub hitstop_shake: &'static mut FighterHitstopShake,
}
pub fn state_update_system(
@@ -384,6 +392,7 @@ pub fn state_interrupt_system(
attack_script_assets,
mut pending_hit_reaction,
mut hitstop,
mut hitstop_shake,
} = fighter;
let manifest = fighter_manifests
.get(&fighter.manifest)
@@ -391,6 +400,16 @@ pub fn state_interrupt_system(
let new_state = if let Some(reaction) = pending_hit_reaction.take() {
hitstop.remaining_frames = *reaction.hitstop_frames;
let shake_frames = reaction.hitstop_frames.calculate_shake_frames(&game_settings);
hitstop_shake.role = FighterHitstopShakeRole::Receiver;
hitstop_shake.total_frames = shake_frames;
hitstop_shake.remaining_frames = shake_frames;
hitstop_shake.kind = if state.is_visually_grounded() {
FighterHitstopShakeKind::Horizontal
} else {
FighterHitstopShakeKind::Vertical
};
let mut hitstop_kind = match &*state {
FighterState::Wait(value) => {
hitstun::HitstopStateKind::Grounded(value.grounded_common.clone())
+6
View File
@@ -64,6 +64,12 @@ impl FighterStateImpl for RunState {
) -> Option<FighterState> {
ground::collision_interrupt(state_context, &mut self.grounded_common, true)
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
true
}
}
pub fn check_input(
+6
View File
@@ -56,6 +56,12 @@ impl FighterStateImpl for TurnState {
) -> Option<FighterState> {
ground::collision_interrupt(state_context, &mut self.grounded_common, false)
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
true
}
}
pub fn check_input(
+6
View File
@@ -46,6 +46,12 @@ impl FighterStateImpl for WaitState {
) -> Option<FighterState> {
ground::collision_interrupt(state_context, &mut self.grounded_common, false)
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
true
}
}
pub fn check_input(
+6
View File
@@ -56,6 +56,12 @@ impl FighterStateImpl for WalkState {
) -> Option<FighterState> {
ground::collision_interrupt(state_context, &mut self.grounded_common, false)
}
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
true
}
}
pub fn check_input(