Files
shinespark/src/fighter/state/ground_attack.rs
T
EIREXE d521461625
CI / build-and-test (push) Successful in 1m28s
CI / production-build (push) Successful in 11m59s
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>
2026-09-25 10:45:27 +00:00

89 lines
2.6 KiB
Rust

use crate::fighter::{
attack::{AttackKind, update_active_hitbox_list},
state::{
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
InterruptContext, UpdateContext, wait::WaitState,
},
};
#[derive(Clone, Debug, Hash)]
pub struct GroundAttackState {
pub attack_kind: AttackKind,
}
impl FighterStateImpl for GroundAttackState {
const NAME: &'static str = "GroundAttack";
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
if state_context.is_current_animation_finished() {
Some(FighterState::Wait(WaitState))
} else {
None
}
}
fn update(&mut self, state_context: &mut UpdateContext) {
let animation_frame = state_context.get_current_animation_frame();
let script = state_context
.attack_scripts
.get(&state_context.attack_script_assets.scripts[&self.attack_kind]);
if let Some(script) = script {
update_active_hitbox_list(
script,
&mut state_context
.hitboxes
.0
.as_mut()
.expect("ground attack must have active hitboxes")
.active_hitboxes,
animation_frame,
);
}
}
fn on_enter(&mut self, state_context: &mut EnterContext) {
state_context.play_animation(self.attack_kind.get_animation(), false);
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack {
kind: self.attack_kind,
script,
active_hitboxes: Vec::new(),
solved_hitboxes: Vec::new(),
hit_fighters: Vec::new(),
stalled: false,
});
}
fn on_exit(&mut self, state_context: &mut ExitContext) {
state_context.hitboxes.0 = None;
}
fn check_collision_interrupt(
&mut self,
_state_context: &mut CollisionContext,
) -> Option<FighterState> {
None
}
fn is_visually_grounded<'a>(&'a self) -> bool {
true
}
}
pub fn check_interrupt(state_context: &InterruptContext) -> Option<FighterState> {
if state_context
.input
.has_command(crate::input::FighterCommands::Attack)
{
let attack_kind = state_context
.input
.get_attack_command_type()
.to_attack_kind_grounded();
Some(FighterState::GroundAttack(GroundAttackState {
attack_kind,
}))
} else {
None
}
}