use crate::{ fighter::{ animation::AnimKind, attack::FighterHitReaction, state::{ CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air, fall::FallState, ground::{GroundedMotionResult, GroundedStateCommon, collide_with_stage_grounded}, land::{LandingState, LandingType}, wait::WaitState, }, }, math::int::FGi32, }; #[derive(Clone, Debug, Hash)] pub enum HitstopStateKind { Grounded(GroundedStateCommon), Airborne, } #[derive(Clone, Debug, Hash)] pub struct HitstunState { pub hit_reaction: FighterHitReaction, pub hitstop_kind: HitstopStateKind, } impl FighterStateImpl for HitstunState { const NAME: &'static str = "Hitstun"; fn name(&self) -> String { let hitstop_kind = match self.hitstop_kind { HitstopStateKind::Grounded(_) => "Grounded", HitstopStateKind::Airborne => "Airborne", }; let hit_reaction_type = match &self.hit_reaction.kind { crate::fighter::attack::HitReactionKind::Flinch => "Flinch", crate::fighter::attack::HitReactionKind::Tumble => "Tumble", }; format!("Hitstun ({hitstop_kind}, {hit_reaction_type})") } fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option { if state_context.get_current_animation_frame() > self.hit_reaction.hitstun_frames { match &self.hitstop_kind { HitstopStateKind::Grounded(grounded_state_common) => { Some(FighterState::Wait(WaitState { grounded_common: grounded_state_common.clone(), })) } HitstopStateKind::Airborne => Some(FighterState::Fall(FallState)), } } else { None } } fn update(&mut self, ctx: &mut UpdateContext) { air::integrate_gravity(ctx.velocity, ctx.fighter_attributes); let speed = ctx.knockback_velocity.length(); let next_speed = speed .saturating_sub(ctx.game_settings.fighter_common.knockback_decay_per_frame) .max(FGi32::ZERO); ctx.knockback_velocity.0 = ctx.knockback_velocity.normalize_or_zero() * next_speed; ctx.translation.0 += ctx.velocity.0 + ctx.knockback_velocity.0; } fn on_enter(&mut self, state_context: &mut EnterContext) { let animation = match self.hitstop_kind { HitstopStateKind::Grounded(_) => AnimKind::FlinchGround, HitstopStateKind::Airborne => AnimKind::FlinchAir, }; state_context.play_animation(animation, false); state_context.knockback_velocity.0 = self.hit_reaction.launch_velocity; } fn on_exit(&mut self, _state_context: &mut ExitContext) {} fn check_collision_interrupt( &mut self, state_context: &mut CollisionContext, ) -> Option { match &mut self.hitstop_kind { HitstopStateKind::Grounded(grounded_state_common) => { if let GroundedMotionResult::InAir = collide_with_stage_grounded(state_context, grounded_state_common, true) { Some(FighterState::Fall(FallState {})) } else { None } } HitstopStateKind::Airborne => { if let Some(res) = state_context.air_collide_with_stage() { // Adjust translation state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point(); let grounded_common = GroundedStateCommon { current_line_id: res.line_id, }; Some(FighterState::Land(LandingState::create( grounded_common, LandingType::Normal, ))) } else { None } } } } }