Implement land from below, fix attacks not lifting fighters off the ground #96
+216
-12
@@ -48,6 +48,14 @@ pub struct AirCollisionWithStageResult {
|
|||||||
pub hit_normal: FGVec2,
|
pub hit_normal: FGVec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct AirCollisionOptions {
|
||||||
|
/// Allow downward input to disable collision with pass-through platforms.
|
||||||
|
pub can_pass_through_soft_platforms: bool,
|
||||||
|
/// Treat an upward crossing of a pass-through platform by the ECB bottom as a landing.
|
||||||
|
pub land_on_soft_platforms_from_below: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn air_collide_with_stage(
|
fn air_collide_with_stage(
|
||||||
prev_translation: &FighterPreviousTranslation,
|
prev_translation: &FighterPreviousTranslation,
|
||||||
@@ -56,7 +64,7 @@ fn air_collide_with_stage(
|
|||||||
ecb: &FighterECB,
|
ecb: &FighterECB,
|
||||||
stage_collision: &StageCollision,
|
stage_collision: &StageCollision,
|
||||||
ignored_polys: &[usize],
|
ignored_polys: &[usize],
|
||||||
can_ignore_soft_platforms: bool,
|
options: AirCollisionOptions,
|
||||||
) -> Option<AirCollisionWithStageResult> {
|
) -> Option<AirCollisionWithStageResult> {
|
||||||
let mut translation = *translation;
|
let mut translation = *translation;
|
||||||
let mut velocity = FighterVelocity::default();
|
let mut velocity = FighterVelocity::default();
|
||||||
@@ -73,7 +81,7 @@ fn air_collide_with_stage(
|
|||||||
&FighterDesiredECB::from(*ecb),
|
&FighterDesiredECB::from(*ecb),
|
||||||
stage_collision,
|
stage_collision,
|
||||||
ignored_polys,
|
ignored_polys,
|
||||||
can_ignore_soft_platforms,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +109,7 @@ pub fn air_collide_with_stage_substepped(
|
|||||||
desired: &FighterDesiredECB,
|
desired: &FighterDesiredECB,
|
||||||
stage_collision: &StageCollision,
|
stage_collision: &StageCollision,
|
||||||
ignored_polys: &[usize],
|
ignored_polys: &[usize],
|
||||||
can_ignore_soft_platforms: bool,
|
options: AirCollisionOptions,
|
||||||
) -> Option<AirCollisionWithStageResult> {
|
) -> Option<AirCollisionWithStageResult> {
|
||||||
let requested_translation = translation.0;
|
let requested_translation = translation.0;
|
||||||
let movement = requested_translation - prev_translation.0;
|
let movement = requested_translation - prev_translation.0;
|
||||||
@@ -151,7 +159,7 @@ pub fn air_collide_with_stage_substepped(
|
|||||||
&lower_edges,
|
&lower_edges,
|
||||||
StagePolyLineSegmentType::Floor,
|
StagePolyLineSegmentType::Floor,
|
||||||
ignored_polys,
|
ignored_polys,
|
||||||
can_ignore_soft_platforms,
|
options.can_pass_through_soft_platforms,
|
||||||
) {
|
) {
|
||||||
*ecb = current_ecb;
|
*ecb = current_ecb;
|
||||||
translation.0 = current_position + result.correction;
|
translation.0 = current_position + result.correction;
|
||||||
@@ -193,6 +201,26 @@ pub fn air_collide_with_stage_substepped(
|
|||||||
velocity.0 -= velocity.0.project_onto_normalized(contact.hit_normal);
|
velocity.0 -= velocity.0.project_onto_normalized(contact.hit_normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.land_on_soft_platforms_from_below
|
||||||
|
&& let Some(result) = sweep_bottom_point_through_platforms(
|
||||||
|
stage_collision,
|
||||||
|
previous_position + previous_ecb.bottom,
|
||||||
|
current_position + current_ecb.bottom,
|
||||||
|
ignored_polys,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
*ecb = current_ecb;
|
||||||
|
translation.0 = current_position + result.correction;
|
||||||
|
velocity.0 -= velocity.0.project_onto_normalized(result.hit_normal);
|
||||||
|
let bottom_contact = translation.0 + current_ecb.bottom;
|
||||||
|
return Some(AirCollisionWithStageResult {
|
||||||
|
line_id: result.line_id,
|
||||||
|
hit_position: bottom_contact,
|
||||||
|
resolved_translation: translation.0,
|
||||||
|
hit_normal: result.hit_normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let side_edges = [
|
let side_edges = [
|
||||||
(
|
(
|
||||||
previous_ecb.bottom,
|
previous_ecb.bottom,
|
||||||
@@ -245,6 +273,59 @@ struct PointSweepResult {
|
|||||||
correction: FGVec2,
|
correction: FGVec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sweep_bottom_point_through_platforms(
|
||||||
|
stage_collision: &StageCollision,
|
||||||
|
previous_bottom: FGVec2,
|
||||||
|
current_bottom: FGVec2,
|
||||||
|
ignored_polys: &[usize],
|
||||||
|
) -> Option<PointSweepResult> {
|
||||||
|
let bottom_sweep = FGSegment2d::new(previous_bottom, current_bottom);
|
||||||
|
|
||||||
|
stage_collision
|
||||||
|
.stage_polys
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(poly_idx, poly)| {
|
||||||
|
!ignored_polys.contains(poly_idx) && matches!(poly.poly_type, StagePolyType::Platform)
|
||||||
|
})
|
||||||
|
.flat_map(|(poly_idx, poly)| {
|
||||||
|
poly.segments
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, segment)| {
|
||||||
|
matches!(segment.segment_type, StagePolyLineSegmentType::Floor)
|
||||||
|
})
|
||||||
|
.filter_map(move |(segment_idx, segment)| {
|
||||||
|
let previous_distance = segment
|
||||||
|
.normal
|
||||||
|
.dot(previous_bottom - segment.segment.point1());
|
||||||
|
let current_distance = segment
|
||||||
|
.normal
|
||||||
|
.dot(current_bottom - segment.segment.point1());
|
||||||
|
if previous_distance >= crate::math::int::FGWide::ZERO
|
||||||
|
|| current_distance < crate::math::int::FGWide::ZERO
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hit_position = segment.segment.segment_intersection(&bottom_sweep)?;
|
||||||
|
Some((
|
||||||
|
PointSweepResult {
|
||||||
|
line_id: StageLineID {
|
||||||
|
polygon: poly_idx,
|
||||||
|
segment: segment_idx,
|
||||||
|
},
|
||||||
|
hit_normal: segment.normal,
|
||||||
|
correction: hit_position - current_bottom,
|
||||||
|
},
|
||||||
|
hit_position.distance_squared(previous_bottom),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.min_by_key(|(_, distance)| *distance)
|
||||||
|
.map(|(result, _)| result)
|
||||||
|
}
|
||||||
|
|
||||||
fn sweep_edges(
|
fn sweep_edges(
|
||||||
stage_collision: &StageCollision,
|
stage_collision: &StageCollision,
|
||||||
previous_position: FGVec2,
|
previous_position: FGVec2,
|
||||||
@@ -442,7 +523,7 @@ mod tests {
|
|||||||
&desired,
|
&desired,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[],
|
&[],
|
||||||
false,
|
Default::default(),
|
||||||
)
|
)
|
||||||
.expect("the floor should be hit");
|
.expect("the floor should be hit");
|
||||||
|
|
||||||
@@ -482,7 +563,7 @@ mod tests {
|
|||||||
&desired,
|
&desired,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[],
|
&[],
|
||||||
false,
|
Default::default(),
|
||||||
)
|
)
|
||||||
.is_none());
|
.is_none());
|
||||||
assert_eq!(translation.0, FGVec2::lit("3", "0"));
|
assert_eq!(translation.0, FGVec2::lit("3", "0"));
|
||||||
@@ -518,7 +599,7 @@ mod tests {
|
|||||||
&desired,
|
&desired,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[],
|
&[],
|
||||||
false,
|
Default::default(),
|
||||||
)
|
)
|
||||||
.is_none());
|
.is_none());
|
||||||
assert_eq!(translation.0.x, FGi32::lit("3"));
|
assert_eq!(translation.0.x, FGi32::lit("3"));
|
||||||
@@ -554,7 +635,7 @@ mod tests {
|
|||||||
&desired,
|
&desired,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[],
|
&[],
|
||||||
false,
|
Default::default(),
|
||||||
)
|
)
|
||||||
.is_none());
|
.is_none());
|
||||||
assert_eq!(translation.0, FGVec2::lit("0", "1"));
|
assert_eq!(translation.0, FGVec2::lit("0", "1"));
|
||||||
@@ -589,7 +670,7 @@ mod tests {
|
|||||||
&ecb,
|
&ecb,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[1],
|
&[1],
|
||||||
false,
|
Default::default(),
|
||||||
)
|
)
|
||||||
.expect("the non-ignored platform should still be collidable");
|
.expect("the non-ignored platform should still be collidable");
|
||||||
|
|
||||||
@@ -626,7 +707,10 @@ mod tests {
|
|||||||
&ecb,
|
&ecb,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[],
|
&[],
|
||||||
true,
|
AirCollisionOptions {
|
||||||
|
can_pass_through_soft_platforms: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
@@ -639,7 +723,7 @@ mod tests {
|
|||||||
&ecb,
|
&ecb,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[],
|
&[],
|
||||||
false,
|
Default::default(),
|
||||||
)
|
)
|
||||||
.expect("air dodges still collide with soft platforms")
|
.expect("air dodges still collide with soft platforms")
|
||||||
.hit_position,
|
.hit_position,
|
||||||
@@ -675,11 +759,131 @@ mod tests {
|
|||||||
&ecb,
|
&ecb,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[],
|
&[],
|
||||||
true,
|
AirCollisionOptions {
|
||||||
|
can_pass_through_soft_platforms: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.expect("solid floors remain collidable while passing down");
|
.expect("solid floors remain collidable while passing down");
|
||||||
|
|
||||||
assert_eq!(collision.line_id.polygon, 0);
|
assert_eq!(collision.line_id.polygon, 0);
|
||||||
assert_eq!(collision.hit_position, FGVec2::lit("0", "0"));
|
assert_eq!(collision.hit_position, FGVec2::lit("0", "0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn upward_bottom_crossing_can_land_on_soft_platform() {
|
||||||
|
let stage_collision = StageCollision {
|
||||||
|
stage_polys: vec![horizontal_platform("0")],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "-1"));
|
||||||
|
let mut translation = FighterTranslation(FGVec2::lit("0", "1"));
|
||||||
|
let mut velocity = FighterVelocity(FGVec2::lit("0", "2"));
|
||||||
|
let mut ecb = FighterECB::default();
|
||||||
|
let desired = FighterDesiredECB::from(ecb);
|
||||||
|
|
||||||
|
let collision = air_collide_with_stage_substepped(
|
||||||
|
&previous_translation,
|
||||||
|
&mut translation,
|
||||||
|
&mut velocity,
|
||||||
|
&mut ecb,
|
||||||
|
&desired,
|
||||||
|
&stage_collision,
|
||||||
|
&[],
|
||||||
|
AirCollisionOptions {
|
||||||
|
land_on_soft_platforms_from_below: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("the upward bottom-point crossing should land on the platform");
|
||||||
|
|
||||||
|
assert_eq!(collision.hit_position, FGVec2::ZERO);
|
||||||
|
assert_eq!(collision.line_id, StageLineID { polygon: 0, segment: 0 });
|
||||||
|
assert_eq!(translation.0, FGVec2::ZERO);
|
||||||
|
assert_eq!(velocity.y, FGi32::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn upward_platform_landing_requires_crossing_and_opt_in() {
|
||||||
|
let stage_collision = StageCollision {
|
||||||
|
stage_polys: vec![horizontal_platform("0")],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let previous_ecb = FighterPreviousECB::default();
|
||||||
|
let ecb = FighterECB::default();
|
||||||
|
let options = AirCollisionOptions {
|
||||||
|
land_on_soft_platforms_from_below: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
air_collide_with_stage(
|
||||||
|
&FighterPreviousTranslation(FGVec2::lit("0", "-2")),
|
||||||
|
&FighterTranslation(FGVec2::lit("0", "-1")),
|
||||||
|
&previous_ecb,
|
||||||
|
&ecb,
|
||||||
|
&stage_collision,
|
||||||
|
&[],
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
air_collide_with_stage(
|
||||||
|
&FighterPreviousTranslation(FGVec2::lit("0", "-1")),
|
||||||
|
&FighterTranslation(FGVec2::lit("0", "1")),
|
||||||
|
&previous_ecb,
|
||||||
|
&ecb,
|
||||||
|
&stage_collision,
|
||||||
|
&[],
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn upward_crossing_does_not_land_on_solid_or_ignored_platforms() {
|
||||||
|
let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "-1"));
|
||||||
|
let translation = FighterTranslation(FGVec2::lit("0", "1"));
|
||||||
|
let previous_ecb = FighterPreviousECB::default();
|
||||||
|
let ecb = FighterECB::default();
|
||||||
|
let options = AirCollisionOptions {
|
||||||
|
land_on_soft_platforms_from_below: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let solid_stage = StageCollision {
|
||||||
|
stage_polys: vec![solid_floor()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let platform_stage = StageCollision {
|
||||||
|
stage_polys: vec![horizontal_platform("0")],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
air_collide_with_stage(
|
||||||
|
&previous_translation,
|
||||||
|
&translation,
|
||||||
|
&previous_ecb,
|
||||||
|
&ecb,
|
||||||
|
&solid_stage,
|
||||||
|
&[],
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
air_collide_with_stage(
|
||||||
|
&previous_translation,
|
||||||
|
&translation,
|
||||||
|
&previous_ecb,
|
||||||
|
&ecb,
|
||||||
|
&platform_stage,
|
||||||
|
&[0],
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -245,7 +245,6 @@ impl Plugin for FighterPlugin {
|
|||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
(
|
(
|
||||||
state::state_update_system,
|
state::state_update_system,
|
||||||
decay_hitstop,
|
|
||||||
decay_invulnerability,
|
decay_invulnerability,
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
@@ -265,6 +264,8 @@ impl Plugin for FighterPlugin {
|
|||||||
ecb::decrement_ecb_lockout,
|
ecb::decrement_ecb_lockout,
|
||||||
ecb::update_ecb,
|
ecb::update_ecb,
|
||||||
state::state_collision_interrupt_system,
|
state::state_collision_interrupt_system,
|
||||||
|
// Every gameplay phase must observe the same hitstop value.
|
||||||
|
decay_hitstop,
|
||||||
decay_ledge_grab_lockout,
|
decay_ledge_grab_lockout,
|
||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ pub fn air_interrupts_common(
|
|||||||
pub fn collision_interrupt(
|
pub fn collision_interrupt(
|
||||||
ctx: &mut CollisionContext,
|
ctx: &mut CollisionContext,
|
||||||
landing_type: LandingType,
|
landing_type: LandingType,
|
||||||
can_ignore_soft_platforms: bool,
|
options: crate::fighter::collision::AirCollisionOptions,
|
||||||
) -> 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() {
|
||||||
@@ -134,7 +134,7 @@ pub fn collision_interrupt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = ctx.air_collide_with_stage(can_ignore_soft_platforms)?;
|
let res = ctx.air_collide_with_stage(options)?;
|
||||||
ctx.translation.0 = res.resolved_translation;
|
ctx.translation.0 = res.resolved_translation;
|
||||||
let bottom_offset = ctx.ecb.rebase_onto_bottom();
|
let bottom_offset = ctx.ecb.rebase_onto_bottom();
|
||||||
ctx.translation.0 += bottom_offset;
|
ctx.translation.0 += bottom_offset;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ impl FighterStateImpl for AirAttackState {
|
|||||||
let coll_interrupt = air::collision_interrupt(
|
let coll_interrupt = air::collision_interrupt(
|
||||||
state_context,
|
state_context,
|
||||||
crate::fighter::state::land::LandingType::Normal,
|
crate::fighter::state::land::LandingType::Normal,
|
||||||
false,
|
Default::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
coll_interrupt.map(|state| {
|
coll_interrupt.map(|state| {
|
||||||
|
|||||||
@@ -65,7 +65,14 @@ impl FighterStateImpl for AirDodgeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
air::collision_interrupt(state_context, LandingType::Special, false)
|
air::collision_interrupt(
|
||||||
|
state_context,
|
||||||
|
LandingType::Special,
|
||||||
|
crate::fighter::collision::AirCollisionOptions {
|
||||||
|
land_on_soft_platforms_from_below: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ impl FighterStateImpl for FallState {
|
|||||||
air::collision_interrupt(
|
air::collision_interrupt(
|
||||||
state_context,
|
state_context,
|
||||||
crate::fighter::state::land::LandingType::Normal,
|
crate::fighter::state::land::LandingType::Normal,
|
||||||
true,
|
crate::fighter::collision::AirCollisionOptions {
|
||||||
|
can_pass_through_soft_platforms: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
animation::AnimKind,
|
FighterVelocity, animation::AnimKind, attack::{self, FighterHitReaction}, state::{
|
||||||
attack::{self, FighterHitReaction},
|
|
||||||
state::{
|
|
||||||
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
|
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
|
||||||
InterruptContext, UpdateContext,
|
InterruptContext, UpdateContext,
|
||||||
air::{self, AirborneStateCommon},
|
air::{self, AirborneStateCommon},
|
||||||
@@ -40,6 +38,30 @@ impl HitstunState {
|
|||||||
impl FighterStateImpl for HitstunState {
|
impl FighterStateImpl for HitstunState {
|
||||||
const NAME: &'static str = "Hitstun";
|
const NAME: &'static str = "Hitstun";
|
||||||
|
|
||||||
|
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
||||||
|
if state_context.get_current_animation_frame() > self.hit_reaction.hitstun_frames {
|
||||||
|
match &self.hitstop_kind {
|
||||||
|
HitstopStateKind::Grounded => Some(FighterState::Wait(WaitState)),
|
||||||
|
HitstopStateKind::Airborne => Some(FighterState::Fall(FallState::new(
|
||||||
|
AirborneStateCommon::default(),
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.velocity.0 = FGVec2::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_exit(&mut self, _state_context: &mut ExitContext) {}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
let hitstop_kind = match self.hitstop_kind {
|
let hitstop_kind = match self.hitstop_kind {
|
||||||
HitstopStateKind::Grounded => "Grounded",
|
HitstopStateKind::Grounded => "Grounded",
|
||||||
@@ -54,19 +76,6 @@ impl FighterStateImpl for HitstunState {
|
|||||||
format!("Hitstun ({hitstop_kind}, {hit_reaction_type})")
|
format!("Hitstun ({hitstop_kind}, {hit_reaction_type})")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
|
||||||
if state_context.get_current_animation_frame() > self.hit_reaction.hitstun_frames {
|
|
||||||
match &self.hitstop_kind {
|
|
||||||
HitstopStateKind::Grounded => Some(FighterState::Wait(WaitState)),
|
|
||||||
HitstopStateKind::Airborne => Some(FighterState::Fall(FallState::new(
|
|
||||||
AirborneStateCommon::default(),
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &mut UpdateContext) {
|
fn update(&mut self, ctx: &mut UpdateContext) {
|
||||||
if !self.knockback_applied {
|
if !self.knockback_applied {
|
||||||
let launch_direction = attack::directional_influence::apply_directional_influence(
|
let launch_direction = attack::directional_influence::apply_directional_influence(
|
||||||
@@ -83,6 +92,8 @@ impl FighterStateImpl for HitstunState {
|
|||||||
HitstopStateKind::Grounded => {}
|
HitstopStateKind::Grounded => {}
|
||||||
HitstopStateKind::Airborne => {
|
HitstopStateKind::Airborne => {
|
||||||
air::integrate_gravity(ctx.velocity, ctx.fighter_attributes, false);
|
air::integrate_gravity(ctx.velocity, ctx.fighter_attributes, false);
|
||||||
|
let vel_with_kb = FighterVelocity(ctx.velocity.0 + ctx.knockback_velocity.0);
|
||||||
|
air::apply_air_motion(&vel_with_kb, ctx.translation, ctx.prev_translation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,21 +103,8 @@ impl FighterStateImpl for HitstunState {
|
|||||||
.max(FGi32::ZERO);
|
.max(FGi32::ZERO);
|
||||||
|
|
||||||
ctx.knockback_velocity.0 = ctx.knockback_velocity.normalize_or_zero() * next_speed;
|
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.velocity.0 = FGVec2::ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_exit(&mut self, _state_context: &mut ExitContext) {}
|
|
||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut CollisionContext,
|
||||||
@@ -114,7 +112,7 @@ impl FighterStateImpl for HitstunState {
|
|||||||
match &mut self.hitstop_kind {
|
match &mut self.hitstop_kind {
|
||||||
HitstopStateKind::Grounded => collision_interrupt(state_context, true),
|
HitstopStateKind::Grounded => collision_interrupt(state_context, true),
|
||||||
HitstopStateKind::Airborne => {
|
HitstopStateKind::Airborne => {
|
||||||
air::collision_interrupt(state_context, LandingType::Normal, false)
|
air::collision_interrupt(state_context, LandingType::Normal, Default::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ impl FighterStateImpl for JumpSquatState {
|
|||||||
|
|
||||||
fn on_exit(&mut self, state_context: &mut crate::fighter::state::ExitContext) {
|
fn on_exit(&mut self, state_context: &mut crate::fighter::state::ExitContext) {
|
||||||
state_context.input.disable_hold_all();
|
state_context.input.disable_hold_all();
|
||||||
|
// We clear tap jump here, it may have weird interactions with air dodging up
|
||||||
|
state_context.input.clear_command(FighterCommands::TapJump);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_visually_grounded<'a>(&'a self) -> bool {
|
fn is_visually_grounded<'a>(&'a self) -> bool {
|
||||||
@@ -174,7 +176,10 @@ impl FighterStateImpl for JumpState {
|
|||||||
air::collision_interrupt(
|
air::collision_interrupt(
|
||||||
state_context,
|
state_context,
|
||||||
crate::fighter::state::land::LandingType::Normal,
|
crate::fighter::state::land::LandingType::Normal,
|
||||||
true,
|
crate::fighter::collision::AirCollisionOptions {
|
||||||
|
can_pass_through_soft_platforms: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,10 +273,10 @@ impl CollisionContext<'_> {
|
|||||||
|
|
||||||
pub fn air_collide_with_stage(
|
pub fn air_collide_with_stage(
|
||||||
&mut self,
|
&mut self,
|
||||||
can_ignore_soft_platforms: bool,
|
mut options: crate::fighter::collision::AirCollisionOptions,
|
||||||
) -> Option<crate::fighter::collision::AirCollisionWithStageResult> {
|
) -> Option<crate::fighter::collision::AirCollisionWithStageResult> {
|
||||||
let ignored_polys = self.surface.ignored_polygon_ids();
|
let ignored_polys = self.surface.ignored_polygon_ids();
|
||||||
let ignore_soft_platforms = can_ignore_soft_platforms
|
options.can_pass_through_soft_platforms = options.can_pass_through_soft_platforms
|
||||||
&& self.input.get_last_frame().movement.y <= -self.downward_stick_threshold;
|
&& self.input.get_last_frame().movement.y <= -self.downward_stick_threshold;
|
||||||
crate::fighter::collision::air_collide_with_stage_substepped(
|
crate::fighter::collision::air_collide_with_stage_substepped(
|
||||||
self.prev_translation,
|
self.prev_translation,
|
||||||
@@ -286,7 +286,7 @@ impl CollisionContext<'_> {
|
|||||||
self.desired_ecb,
|
self.desired_ecb,
|
||||||
self.stage_collision,
|
self.stage_collision,
|
||||||
&ignored_polys,
|
&ignored_polys,
|
||||||
ignore_soft_platforms,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pub fn air_collide_with_ledges(
|
pub fn air_collide_with_ledges(
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ impl FighterStateImpl for PlatformPassState {
|
|||||||
air::collision_interrupt(
|
air::collision_interrupt(
|
||||||
state_context,
|
state_context,
|
||||||
crate::fighter::state::land::LandingType::Normal,
|
crate::fighter::state::land::LandingType::Normal,
|
||||||
true,
|
crate::fighter::collision::AirCollisionOptions {
|
||||||
|
can_pass_through_soft_platforms: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user