Add waveland snapping (#95)
Reviewed-on: #95 Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #95.
This commit is contained in:
@@ -129,6 +129,7 @@ pub fn collision_interrupt(
|
|||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if ctx.velocity.y <= FGi32::ZERO {
|
if ctx.velocity.y <= FGi32::ZERO {
|
||||||
if let Some(ledge_result) = ctx.air_collide_with_ledges() {
|
if let Some(ledge_result) = ctx.air_collide_with_ledges() {
|
||||||
|
ctx.apply_desired_ecb();
|
||||||
return Some(FighterState::LedgeGrab(LedgeGrabState::new(ledge_result.ledge)));
|
return Some(FighterState::LedgeGrab(LedgeGrabState::new(ledge_result.ledge)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
FighterInvulnerabilityState,
|
FighterInvulnerabilityState, animation::AnimKind, state::{
|
||||||
animation::AnimKind,
|
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air::{self, AirborneStateCommon}, fall::FallState, land::{LandingState, LandingType},
|
||||||
state::{
|
|
||||||
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext,
|
|
||||||
UpdateContext,
|
|
||||||
air::{self, AirborneStateCommon},
|
|
||||||
fall::FallState,
|
|
||||||
land::LandingType,
|
|
||||||
},
|
},
|
||||||
},
|
}, input, math::vec::FGVec2, stage::line::{StageLineID, StagePolyLineSegmentType},
|
||||||
input,
|
|
||||||
math::vec::FGVec2,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
@@ -52,6 +44,27 @@ impl FighterStateImpl for AirDodgeState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut CollisionContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
|
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.translation.0;
|
||||||
|
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)
|
air::collision_interrupt(state_context, LandingType::Special, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,12 +43,13 @@ impl FighterStateImpl for AngelState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state_context: &mut super::CollisionContext,
|
state_context: &mut super::CollisionContext,
|
||||||
) -> Option<super::FighterState> {
|
) -> Option<super::FighterState> {
|
||||||
|
state_context.apply_desired_ecb();
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn surface_policy(&self) -> super::SurfacePolicy {
|
fn surface_policy(&self) -> super::SurfacePolicy {
|
||||||
super::SurfacePolicy::Airborne(crate::fighter::surface::EcbLockPolicy::Clear)
|
super::SurfacePolicy::Airborne(crate::fighter::surface::EcbLockPolicy::Clear)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,7 +211,6 @@ pub fn collide_with_stage_grounded(
|
|||||||
crate::fighter::surface::EcbLockPolicy::StartOnDetach,
|
crate::fighter::surface::EcbLockPolicy::StartOnDetach,
|
||||||
state_context.leave_ground_ecb_lock_duration,
|
state_context.leave_ground_ecb_lock_duration,
|
||||||
);
|
);
|
||||||
state_context.ecb_advanced = true;
|
|
||||||
return GroundedMotionResult::InAir;
|
return GroundedMotionResult::InAir;
|
||||||
};
|
};
|
||||||
match resolve_grounded_position(
|
match resolve_grounded_position(
|
||||||
@@ -238,7 +237,6 @@ pub fn collide_with_stage_grounded(
|
|||||||
crate::fighter::surface::EcbLockPolicy::StartOnDetach,
|
crate::fighter::surface::EcbLockPolicy::StartOnDetach,
|
||||||
state_context.leave_ground_ecb_lock_duration,
|
state_context.leave_ground_ecb_lock_duration,
|
||||||
);
|
);
|
||||||
state_context.ecb_advanced = true;
|
|
||||||
return GroundedMotionResult::InAir;
|
return GroundedMotionResult::InAir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,7 +245,6 @@ pub fn collide_with_stage_grounded(
|
|||||||
*state_context.ecb = current_ecb;
|
*state_context.ecb = current_ecb;
|
||||||
state_context.translation.0 = current_position;
|
state_context.translation.0 = current_position;
|
||||||
state_context.surface.land(line_id, state_context.ledge_info);
|
state_context.surface.land(line_id, state_context.ledge_info);
|
||||||
state_context.ecb_advanced = true;
|
|
||||||
GroundedMotionResult::InGround
|
GroundedMotionResult::InGround
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +416,7 @@ mod tests {
|
|||||||
origin: FGVec2::ZERO,
|
origin: FGVec2::ZERO,
|
||||||
end: FGVec2::ZERO,
|
end: FGVec2::ZERO,
|
||||||
},
|
},
|
||||||
ecb_advanced: false,
|
waveland_snap_distance: FGi32::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
@@ -484,7 +481,7 @@ mod tests {
|
|||||||
origin: FGVec2::ZERO,
|
origin: FGVec2::ZERO,
|
||||||
end: FGVec2::ZERO,
|
end: FGVec2::ZERO,
|
||||||
},
|
},
|
||||||
ecb_advanced: false,
|
waveland_snap_distance: FGi32::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|||||||
@@ -144,8 +144,9 @@ impl FighterStateImpl for LedgeActionState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state_context: &mut CollisionContext,
|
state_context: &mut CollisionContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
|
state_context.apply_desired_ecb();
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,8 +102,9 @@ impl FighterStateImpl for LedgeWaitState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state_context: &mut CollisionContext,
|
state_context: &mut CollisionContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
|
state_context.apply_desired_ecb();
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-25
@@ -120,8 +120,9 @@ pub trait FighterStateImpl: Sized {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state_context: &mut CollisionContext,
|
state_context: &mut CollisionContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
|
state_context.apply_desired_ecb();
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +261,7 @@ pub struct CollisionContext<'a> {
|
|||||||
pub ledge_info: &'a mut FighterLedgeInformation,
|
pub ledge_info: &'a mut FighterLedgeInformation,
|
||||||
pub fighter_ledge_collision_box: FGRect,
|
pub fighter_ledge_collision_box: FGRect,
|
||||||
pub downward_stick_threshold: FGi32,
|
pub downward_stick_threshold: FGi32,
|
||||||
pub(crate) ecb_advanced: bool,
|
pub waveland_snap_distance: FGi32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CollisionContext<'_> {
|
impl CollisionContext<'_> {
|
||||||
@@ -268,7 +269,6 @@ impl CollisionContext<'_> {
|
|||||||
/// substep resolver can later replace this with fractional steps.
|
/// substep resolver can later replace this with fractional steps.
|
||||||
pub fn apply_desired_ecb(&mut self) {
|
pub fn apply_desired_ecb(&mut self) {
|
||||||
ecb::interpolate_ecb(self.ecb, *self.desired_ecb, FGi32::ONE);
|
ecb::interpolate_ecb(self.ecb, *self.desired_ecb, FGi32::ONE);
|
||||||
self.ecb_advanced = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn air_collide_with_stage(
|
pub fn air_collide_with_stage(
|
||||||
@@ -278,7 +278,7 @@ impl CollisionContext<'_> {
|
|||||||
let ignored_polys = self.surface.ignored_polygon_ids();
|
let ignored_polys = self.surface.ignored_polygon_ids();
|
||||||
let ignore_soft_platforms = can_ignore_soft_platforms
|
let ignore_soft_platforms = can_ignore_soft_platforms
|
||||||
&& self.input.get_last_frame().movement.y <= -self.downward_stick_threshold;
|
&& self.input.get_last_frame().movement.y <= -self.downward_stick_threshold;
|
||||||
let result = crate::fighter::collision::air_collide_with_stage_substepped(
|
crate::fighter::collision::air_collide_with_stage_substepped(
|
||||||
self.prev_translation,
|
self.prev_translation,
|
||||||
self.translation,
|
self.translation,
|
||||||
self.velocity,
|
self.velocity,
|
||||||
@@ -287,15 +287,7 @@ impl CollisionContext<'_> {
|
|||||||
self.stage_collision,
|
self.stage_collision,
|
||||||
&ignored_polys,
|
&ignored_polys,
|
||||||
ignore_soft_platforms,
|
ignore_soft_platforms,
|
||||||
);
|
)
|
||||||
self.ecb_advanced = true;
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_ecb_frame(&mut self) {
|
|
||||||
if !self.ecb_advanced {
|
|
||||||
self.apply_desired_ecb();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pub fn air_collide_with_ledges(
|
pub fn air_collide_with_ledges(
|
||||||
&self,
|
&self,
|
||||||
@@ -502,7 +494,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
.leave_ground_ecb_frame_lock_duration,
|
.leave_ground_ecb_frame_lock_duration,
|
||||||
downward_stick_threshold: game_settings.input_common.run_stick_threshold,
|
downward_stick_threshold: game_settings.input_common.run_stick_threshold,
|
||||||
fighter_ledge_collision_box,
|
fighter_ledge_collision_box,
|
||||||
ecb_advanced: false,
|
waveland_snap_distance: game_settings.fighter_common.waveland_snap_distance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -797,13 +789,9 @@ pub fn state_collision_interrupt_system(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_state = {
|
let new_state = state.check_collision_interrupt(
|
||||||
let mut collision_context =
|
&mut frame.collision_context(&stage_collision, manifest, &game_settings),
|
||||||
frame.collision_context(&stage_collision, manifest, &game_settings);
|
);
|
||||||
let new_state = state.check_collision_interrupt(&mut collision_context);
|
|
||||||
collision_context.finish_ecb_frame();
|
|
||||||
new_state
|
|
||||||
};
|
|
||||||
if let Some(new_state) = new_state {
|
if let Some(new_state) = new_state {
|
||||||
transition_state(
|
transition_state(
|
||||||
&mut state,
|
&mut state,
|
||||||
@@ -872,7 +860,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||||
let mut presentation_snap = FighterPresentationSnap::default();
|
let mut presentation_snap = FighterPresentationSnap::default();
|
||||||
let mut context = CollisionContext {
|
let context = CollisionContext {
|
||||||
translation: &mut translation,
|
translation: &mut translation,
|
||||||
prev_translation: &previous_translation,
|
prev_translation: &previous_translation,
|
||||||
input: &input,
|
input: &input,
|
||||||
@@ -890,7 +878,7 @@ mod tests {
|
|||||||
end: FGVec2::lit("1", "-1"),
|
end: FGVec2::lit("1", "-1"),
|
||||||
},
|
},
|
||||||
downward_stick_threshold: FGi32::ONE,
|
downward_stick_threshold: FGi32::ONE,
|
||||||
ecb_advanced: false,
|
waveland_snap_distance: FGi32::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(context.air_collide_with_ledges().is_none());
|
assert!(context.air_collide_with_ledges().is_none());
|
||||||
@@ -899,7 +887,5 @@ mod tests {
|
|||||||
context.air_collide_with_ledges().map(|result| result.ledge),
|
context.air_collide_with_ledges().map(|result| result.ledge),
|
||||||
Some(ledge_id)
|
Some(ledge_id)
|
||||||
);
|
);
|
||||||
context.finish_ecb_frame();
|
|
||||||
assert_eq!(context.ecb.top, FGVec2::lit("0", "2"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ pub struct FighterSettingsCommon {
|
|||||||
pub sakurai_angle_high_knockback_angle: FGi32,
|
pub sakurai_angle_high_knockback_angle: FGi32,
|
||||||
/// past this threshold, the knockback is considered high, so it will use [sakurai_angle_high_knockback_angle]
|
/// past this threshold, the knockback is considered high, so it will use [sakurai_angle_high_knockback_angle]
|
||||||
pub sakurai_angle_knockback_threshold: FGi32,
|
pub sakurai_angle_knockback_threshold: FGi32,
|
||||||
|
|
||||||
|
pub waveland_snap_distance: FGi32,
|
||||||
}
|
}
|
||||||
#[derive(Reflect, Serialize, Deserialize, Clone)]
|
#[derive(Reflect, Serialize, Deserialize, Clone)]
|
||||||
#[reflect(opaque)]
|
#[reflect(opaque)]
|
||||||
|
|||||||
@@ -717,7 +717,6 @@ pub fn postprocess_input(
|
|||||||
FighterAttackCommandType::Neutral
|
FighterAttackCommandType::Neutral
|
||||||
};
|
};
|
||||||
|
|
||||||
dbg!(input.movement_flick.y, attack_type);
|
|
||||||
input.set_lifetime(FighterCommands::Attack, tap);
|
input.set_lifetime(FighterCommands::Attack, tap);
|
||||||
input.set_attack_command_type(attack_type);
|
input.set_attack_command_type(attack_type);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-5
@@ -1,15 +1,15 @@
|
|||||||
use bevy::{color::palettes::css::RED, prelude::*};
|
use bevy::{color::palettes::css::RED, prelude::*};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
match_loading::MatchConfig, math::{int::FGi32, vec3::FGVec3}, stage::{
|
game_settings::GameSettings, match_loading::MatchConfig, math::{int::FGi32, vec3::FGVec3}, stage::{
|
||||||
line::{StageCollision, StageLedgeSide},
|
line::{StageCollision, StageLedgeSide, StagePolyLineSegmentType}, manifest::{StageManifest, StageManifestRegistry},
|
||||||
manifest::{StageManifest, StageManifestRegistry},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>) {
|
pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>, game_settings: Res<GameSettings>) {
|
||||||
for poly in stage_collision.stage_polys.iter() {
|
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 from = segment.segment.point1().to_vec2();
|
||||||
let to = segment.segment.point2().to_vec2();
|
let to = segment.segment.point2().to_vec2();
|
||||||
let from_3d = Vec3::new(from.x, from.y, 0.0);
|
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<StageCollision>
|
|||||||
gizmos.line(from_3d, end_3d, RED);
|
gizmos.line(from_3d, end_3d, RED);
|
||||||
let midpoint = (from_3d + end_3d) * 0.5;
|
let midpoint = (from_3d + end_3d) * 0.5;
|
||||||
gizmos.arrow(midpoint, midpoint + normal_3d, RED);
|
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::<f32>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user