use crate::{ fighter::{ FighterInvulnerabilityState, animation::AnimKind, state::{ CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air::{self, AirborneStateCommon}, fall::FallState, land::{LandingState, LandingType}, }, }, input, math::vec::FGVec2, stage::line::{StageLineID, StagePolyLineSegmentType}, }; #[derive(Debug, Clone, Hash)] pub struct AirDodgeState { pub direction: FGVec2, } impl FighterStateImpl for AirDodgeState { const NAME: &'static str = "AirDodge"; fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option { if state_context.is_current_animation_finished() { Some(FighterState::Fall(FallState::new( AirborneStateCommon::default(), ))) } else { None } } fn update(&mut self, state_context: &mut UpdateContext) { let airdodge_intangible_end = state_context.fighter_attributes.air_dodge_intangible_start + state_context.fighter_attributes.air_dodge_intangible_duration; if state_context.animation_frame == state_context.fighter_attributes.air_dodge_intangible_start { *state_context.vulnerability = FighterInvulnerabilityState::Intangible { frames_remaining: Some(state_context.fighter_attributes.air_dodge_intangible_duration) }; } else if state_context.animation_frame >= airdodge_intangible_end { *state_context.vulnerability = FighterInvulnerabilityState::Vulnerable; } state_context.velocity.0 *= state_context.fighter_attributes.air_dodge_decay; air::apply_air_motion( state_context.velocity, state_context.translation, state_context.prev_translation, ); } fn check_collision_interrupt( &mut self, state_context: &mut CollisionContext, ) -> Option { let can_wavesnap = self.direction.y.is_zero() || self.direction.y.is_negative(); if can_wavesnap { for (poly_idx, poly) in state_context.stage_collision.stage_polys.iter().enumerate() { for (segment_idx, segment) in poly.segments.iter().enumerate() { if !matches!(segment.segment_type, StagePolyLineSegmentType::Floor) { continue; } let bottom_point = state_context.translation.0 + state_context.ecb.get_bottom_point(); let point_in_segment = segment.segment.closest_point(bottom_point); let dist_to_segment = point_in_segment.distance(bottom_point); if dist_to_segment <= state_context.waveland_snap_distance { state_context.ecb.bottom = point_in_segment; state_context.ecb.rebase_onto_bottom(); state_context.surface.land(StageLineID { polygon: poly_idx, segment: segment_idx }, state_context.ledge_info); return Some(FighterState::Land(LandingState::create(LandingType::Special))); } } } } air::collision_interrupt(state_context, LandingType::Special, false) } fn on_enter(&mut self, state_context: &mut EnterContext) { state_context.play_animation(AnimKind::AirDodge, false); state_context .input .clear_command(input::FighterCommands::Shield); state_context .input .clear_command(input::FighterCommands::GuardHold); state_context.velocity.0 = self.direction * state_context.fighter_attributes.air_dodge_velocity; } fn is_visually_grounded<'a>(&'a self) -> bool { false } fn surface_policy(&self) -> super::SurfacePolicy { super::SurfacePolicy::Airborne(crate::fighter::surface::EcbLockPolicy::StartOnDetach) } fn on_exit(&mut self, state_context: &mut super::ExitContext) { *state_context.vulnerability = FighterInvulnerabilityState::Vulnerable; } fn name(&self) -> String { Self::NAME.to_owned() } } pub fn check_input(state_context: &InterruptContext) -> Option { state_context .input .has_command(input::FighterCommands::Shield) .then_some(()) .or_else(|| { state_context .input .has_command(input::FighterCommands::GuardHold) .then_some(()) }) .map(|_| { FighterState::AirDodge(AirDodgeState { direction: state_context .input .get_last_frame() .movement .normalize_or_zero(), }) }) }