From 5be69cab52d520ce43d7e6ec25dd356a3a6db558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Rom=C3=A1n=20N=C3=BA=C3=B1ez?= Date: Tue, 13 Oct 2026 22:49:56 +0200 Subject: [PATCH] Add waveland snapping --- src/fighter/state/air_dodge.rs | 35 +++++++++++++++++++++++----------- src/fighter/state/mod.rs | 3 +++ src/game_settings.rs | 2 ++ src/input/buffer.rs | 1 - src/stage/debug.rs | 20 ++++++++++++++----- 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/fighter/state/air_dodge.rs b/src/fighter/state/air_dodge.rs index b993201..e03608c 100644 --- a/src/fighter/state/air_dodge.rs +++ b/src/fighter/state/air_dodge.rs @@ -1,17 +1,9 @@ use crate::{ fighter::{ - FighterInvulnerabilityState, - animation::AnimKind, - state::{ - CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, - UpdateContext, - air::{self, AirborneStateCommon}, - fall::FallState, - land::LandingType, + FighterInvulnerabilityState, animation::AnimKind, state::{ + CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air::{self, AirborneStateCommon}, fall::FallState, land::{LandingState, LandingType}, }, - }, - input, - math::vec::FGVec2, + }, input, math::vec::FGVec2, stage::line::{StageLineID, StagePolyLineSegmentType}, }; #[derive(Debug, Clone, Hash)] @@ -52,6 +44,27 @@ impl FighterStateImpl for AirDodgeState { &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) } diff --git a/src/fighter/state/mod.rs b/src/fighter/state/mod.rs index a465453..f6f1d7e 100644 --- a/src/fighter/state/mod.rs +++ b/src/fighter/state/mod.rs @@ -260,6 +260,7 @@ pub struct CollisionContext<'a> { pub ledge_info: &'a mut FighterLedgeInformation, pub fighter_ledge_collision_box: FGRect, pub downward_stick_threshold: FGi32, + pub waveland_snap_distance: FGi32, pub(crate) ecb_advanced: bool, } @@ -503,6 +504,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> { downward_stick_threshold: game_settings.input_common.run_stick_threshold, fighter_ledge_collision_box, ecb_advanced: false, + waveland_snap_distance: game_settings.fighter_common.waveland_snap_distance, } } } @@ -891,6 +893,7 @@ mod tests { }, downward_stick_threshold: FGi32::ONE, ecb_advanced: false, + waveland_snap_distance: FGi32::ZERO, }; assert!(context.air_collide_with_ledges().is_none()); diff --git a/src/game_settings.rs b/src/game_settings.rs index bcf4169..09c922d 100644 --- a/src/game_settings.rs +++ b/src/game_settings.rs @@ -66,6 +66,8 @@ pub struct FighterSettingsCommon { pub sakurai_angle_high_knockback_angle: FGi32, /// past this threshold, the knockback is considered high, so it will use [sakurai_angle_high_knockback_angle] pub sakurai_angle_knockback_threshold: FGi32, + + pub waveland_snap_distance: FGi32, } #[derive(Reflect, Serialize, Deserialize, Clone)] #[reflect(opaque)] diff --git a/src/input/buffer.rs b/src/input/buffer.rs index 8b6e183..e8fcb8b 100644 --- a/src/input/buffer.rs +++ b/src/input/buffer.rs @@ -717,7 +717,6 @@ pub fn postprocess_input( FighterAttackCommandType::Neutral }; - dbg!(input.movement_flick.y, attack_type); input.set_lifetime(FighterCommands::Attack, tap); input.set_attack_command_type(attack_type); } diff --git a/src/stage/debug.rs b/src/stage/debug.rs index 4a3bb12..d0b3234 100644 --- a/src/stage/debug.rs +++ b/src/stage/debug.rs @@ -1,15 +1,15 @@ use bevy::{color::palettes::css::RED, prelude::*}; use crate::{ - match_loading::MatchConfig, math::{int::FGi32, vec3::FGVec3}, stage::{ - line::{StageCollision, StageLedgeSide}, - manifest::{StageManifest, StageManifestRegistry}, + game_settings::GameSettings, match_loading::MatchConfig, math::{int::FGi32, vec3::FGVec3}, stage::{ + line::{StageCollision, StageLedgeSide, StagePolyLineSegmentType}, manifest::{StageManifest, StageManifestRegistry}, }, }; -pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res) { +pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res, game_settings: Res) { for poly in stage_collision.stage_polys.iter() { - for segment in &poly.segments { + for (segment, next_segment) in poly.segments.iter().zip(poly.segments.iter().cycle().skip(1)) { + let wavedash_normal = ((next_segment.normal.to_vec2() + segment.normal.to_vec2()) * 0.5).normalize(); let from = segment.segment.point1().to_vec2(); let to = segment.segment.point2().to_vec2(); let from_3d = Vec3::new(from.x, from.y, 0.0); @@ -18,6 +18,16 @@ pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res gizmos.line(from_3d, end_3d, RED); let midpoint = (from_3d + end_3d) * 0.5; gizmos.arrow(midpoint, midpoint + normal_3d, RED); + if matches!(segment.segment_type, StagePolyLineSegmentType::Floor) { + let radius = game_settings.fighter_common.waveland_snap_distance.to_num::(); + let a = segment.segment.point1().to_vec2(); + let a = Vec3::new(a.x, a.y, 0.0); + let b = segment.segment.point2().to_vec2(); + let b = Vec3::new(b.x, b.y, 0.0); + let offset = Vec3::new(wavedash_normal.x, wavedash_normal.y, 0.0) * radius; + gizmos.line(a + offset, b + offset, bevy::color::palettes::css::HOT_PINK); + gizmos.line(a - offset, b - offset, bevy::color::palettes::css::HOT_PINK); + } } }