Add authoritative fighter surface state (#60)
## Summary - add a rollback-tracked `FighterSurfaceState` as the single source of truth for stage contact - move active stage line ownership out of individual grounded fighter states - update landing, airborne motion, ledge departure, hit reactions, and debug save states to maintain surface contact - add tests for contact lifecycle, connected-segment traversal, and ledge departure Reviewed-on: #60 Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #60.
This commit is contained in:
+34
-32
@@ -7,7 +7,8 @@ use bevy::prelude::*;
|
||||
|
||||
use crate::fighter::animation::{AnimKind, FighterAnimationFrame};
|
||||
use crate::fighter::attack::{
|
||||
FighterAttackScriptAssets, FighterKnockbackVelocity, FighterPendingHitReaction, FighterStaleMoveQueue, HitStop,
|
||||
FighterAttackScriptAssets, FighterKnockbackVelocity, FighterPendingHitReaction,
|
||||
FighterStaleMoveQueue, HitStop,
|
||||
};
|
||||
use crate::fighter::attributes::FighterAttributes;
|
||||
use crate::fighter::baked_animation::BakedFighterAnimations;
|
||||
@@ -16,7 +17,9 @@ use crate::fighter::manifest::FighterManifest;
|
||||
use crate::fighter::state::hitstun::HitstunState;
|
||||
use crate::fighter::visual::FighterAnimations;
|
||||
use crate::fighter::{
|
||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop, FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
||||
FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole,
|
||||
FighterPreviousTranslation, FighterSurfaceState, FighterTranslation, FighterVelocity,
|
||||
};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
@@ -95,9 +98,7 @@ pub trait FighterStateImpl: Sized {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_visually_grounded<'a>(
|
||||
&'a self,
|
||||
) -> bool;
|
||||
fn is_visually_grounded<'a>(&'a self) -> bool;
|
||||
}
|
||||
|
||||
pub struct AnimationStatus {
|
||||
@@ -151,6 +152,7 @@ pub struct UpdateContext<'a> {
|
||||
pub input: &'a mut FighterInput,
|
||||
pub hitboxes: &'a mut FighterHitboxes,
|
||||
pub velocity: &'a mut FighterVelocity,
|
||||
pub surface: &'a mut FighterSurfaceState,
|
||||
pub knockback_velocity: &'a mut FighterKnockbackVelocity,
|
||||
pub game_settings: &'a GameSettings,
|
||||
pub animation_frame: u32,
|
||||
@@ -170,6 +172,7 @@ pub struct CollisionContext<'a> {
|
||||
pub translation: &'a mut FighterTranslation,
|
||||
pub prev_translation: &'a FighterPreviousTranslation,
|
||||
pub velocity: &'a mut FighterVelocity,
|
||||
pub surface: &'a mut FighterSurfaceState,
|
||||
pub ecb: &'a FighterECB,
|
||||
pub prev_ecb: &'a FighterPreviousECB,
|
||||
pub stage_collision: &'a StageCollision,
|
||||
@@ -212,6 +215,7 @@ pub struct FighterFrameQuery {
|
||||
pub input: &'static mut FighterInput,
|
||||
pub hitboxes: &'static mut FighterHitboxes,
|
||||
pub velocity: &'static mut FighterVelocity,
|
||||
pub surface: &'static mut FighterSurfaceState,
|
||||
pub knockback_velocity: &'static mut FighterKnockbackVelocity,
|
||||
pub stale_move_queue: &'static mut FighterStaleMoveQueue,
|
||||
pub ecb: &'static FighterECB,
|
||||
@@ -292,13 +296,14 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||
input: &mut self.input,
|
||||
hitboxes: &mut self.hitboxes,
|
||||
velocity: &mut self.velocity,
|
||||
surface: &mut self.surface,
|
||||
knockback_velocity: &mut self.knockback_velocity,
|
||||
game_settings,
|
||||
animation_frame: self.animation_frame.frame,
|
||||
fighter_attributes: attributes,
|
||||
attack_scripts,
|
||||
attack_script_assets,
|
||||
facing_direction: &self.facing_direction
|
||||
facing_direction: &self.facing_direction,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +315,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||
translation: &mut self.translation,
|
||||
prev_translation: &self.prev_translation,
|
||||
velocity: &mut self.velocity,
|
||||
surface: &mut self.surface,
|
||||
ecb: &self.ecb,
|
||||
prev_ecb: &self.prev_ecb,
|
||||
stage_collision,
|
||||
@@ -400,7 +406,9 @@ 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);
|
||||
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;
|
||||
@@ -409,36 +417,26 @@ pub fn state_interrupt_system(
|
||||
} else {
|
||||
FighterHitstopShakeKind::Vertical
|
||||
};
|
||||
|
||||
let mut hitstop_kind = match &*state {
|
||||
FighterState::Wait(value) => {
|
||||
hitstun::HitstopStateKind::Grounded(value.grounded_common.clone())
|
||||
}
|
||||
FighterState::Walk(value) => {
|
||||
hitstun::HitstopStateKind::Grounded(value.grounded_common.clone())
|
||||
}
|
||||
FighterState::Dash(value) => {
|
||||
hitstun::HitstopStateKind::Grounded(value.ground_common.clone())
|
||||
}
|
||||
FighterState::Run(value) => {
|
||||
hitstun::HitstopStateKind::Grounded(value.grounded_common.clone())
|
||||
}
|
||||
FighterState::Turn(value) => {
|
||||
hitstun::HitstopStateKind::Grounded(value.grounded_common.clone())
|
||||
}
|
||||
FighterState::GroundAttack(value) => {
|
||||
hitstun::HitstopStateKind::Grounded(value.grounded_common.clone())
|
||||
}
|
||||
_ => hitstun::HitstopStateKind::Airborne,
|
||||
|
||||
let mut hitstop_kind = if frame.surface.is_grounded() {
|
||||
hitstun::HitstopStateKind::Grounded
|
||||
} else {
|
||||
hitstun::HitstopStateKind::Airborne
|
||||
};
|
||||
|
||||
if let hitstun::HitstopStateKind::Grounded(_) = hitstop_kind {
|
||||
if let hitstun::HitstopStateKind::Grounded = hitstop_kind {
|
||||
let straight_up_angle = FGi32::lit("90.0");
|
||||
if reaction.hitstop_frames >= HitStop::new(game_settings.fighter_common.tumble_bounce_hitstop_threshold) {
|
||||
if reaction.hitstop_frames
|
||||
>= HitStop::new(game_settings.fighter_common.tumble_bounce_hitstop_threshold)
|
||||
{
|
||||
hitstop_kind = hitstun::HitstopStateKind::Airborne;
|
||||
// TODO: Use floor normal here
|
||||
} else if reaction.launch_angle.abs_diff(straight_up_angle) < game_settings.fighter_common.attack_ground_to_airborne_transition_vertical_angle_threshold {
|
||||
hitstop_kind = hitstun::HitstopStateKind::Airborne;
|
||||
} else if reaction.launch_angle.abs_diff(straight_up_angle)
|
||||
< game_settings
|
||||
.fighter_common
|
||||
.attack_ground_to_airborne_transition_vertical_angle_threshold
|
||||
{
|
||||
hitstop_kind = hitstun::HitstopStateKind::Airborne;
|
||||
} else {
|
||||
// straight down is 270
|
||||
let straight_down_angle = FGi32::lit("270.0");
|
||||
@@ -450,6 +448,10 @@ pub fn state_interrupt_system(
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(hitstop_kind, hitstun::HitstopStateKind::Airborne) {
|
||||
frame.surface.leave_ground();
|
||||
}
|
||||
|
||||
info!("Entering hitstop kind: {:?}", hitstop_kind);
|
||||
Some(FighterState::Hitstun(HitstunState::new(
|
||||
reaction,
|
||||
|
||||
Reference in New Issue
Block a user