Add ledge getup, ledge intangibility limit & fix walk-off velocity
This commit is contained in:
@@ -89,6 +89,8 @@ pub enum AnimKind {
|
|||||||
CrouchExit,
|
CrouchExit,
|
||||||
LedgeWait,
|
LedgeWait,
|
||||||
LedgeJump,
|
LedgeJump,
|
||||||
|
LedgeGetUp,
|
||||||
|
LedgeGrab,
|
||||||
|
|
||||||
PlatformPass,
|
PlatformPass,
|
||||||
|
|
||||||
|
|||||||
+19
-18
@@ -87,46 +87,47 @@ pub struct FighterHitstop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Default, Copy, Clone, Hash, Debug, PartialEq, Eq)]
|
#[derive(Component, Default, Copy, Clone, Hash, Debug, PartialEq, Eq)]
|
||||||
pub struct FighterLedgeGrabLockout {
|
pub struct FighterLedgeInformation {
|
||||||
pub remaining_frames: u32,
|
pub regrab_lockout_remaining_frames: u32,
|
||||||
|
pub grab_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterLedgeGrabLockout {
|
impl FighterLedgeInformation {
|
||||||
pub fn start(&mut self, duration_frames: u32) {
|
pub fn start(&mut self, duration_frames: u32) {
|
||||||
self.remaining_frames = duration_frames;
|
self.regrab_lockout_remaining_frames = duration_frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_active(&self) -> bool {
|
pub fn is_regrab_lockout_active(&self) -> bool {
|
||||||
self.remaining_frames > 0
|
self.regrab_lockout_remaining_frames > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self) {
|
fn tick(&mut self) {
|
||||||
self.remaining_frames = self.remaining_frames.saturating_sub(1);
|
self.regrab_lockout_remaining_frames = self.regrab_lockout_remaining_frames.saturating_sub(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod ledge_grab_lockout_tests {
|
mod ledge_grab_lockout_tests {
|
||||||
use super::FighterLedgeGrabLockout;
|
use super::FighterLedgeInformation;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lockout_expires_after_the_requested_number_of_ticks() {
|
fn lockout_expires_after_the_requested_number_of_ticks() {
|
||||||
let mut lockout = FighterLedgeGrabLockout::default();
|
let mut lockout = FighterLedgeInformation::default();
|
||||||
lockout.start(2);
|
lockout.start(2);
|
||||||
|
|
||||||
assert!(lockout.is_active());
|
assert!(lockout.is_regrab_lockout_active());
|
||||||
lockout.tick();
|
lockout.tick();
|
||||||
assert!(lockout.is_active());
|
assert!(lockout.is_regrab_lockout_active());
|
||||||
lockout.tick();
|
lockout.tick();
|
||||||
assert!(!lockout.is_active());
|
assert!(!lockout.is_regrab_lockout_active());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero_duration_does_not_enable_lockout() {
|
fn zero_duration_does_not_enable_lockout() {
|
||||||
let mut lockout = FighterLedgeGrabLockout::default();
|
let mut lockout = FighterLedgeInformation::default();
|
||||||
lockout.start(0);
|
lockout.start(0);
|
||||||
|
|
||||||
assert!(!lockout.is_active());
|
assert!(!lockout.is_regrab_lockout_active());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +193,7 @@ pub struct FighterPendingRespawn(bool);
|
|||||||
FighterPendingHitReaction,
|
FighterPendingHitReaction,
|
||||||
FighterPendingRespawn,
|
FighterPendingRespawn,
|
||||||
FighterHitstop,
|
FighterHitstop,
|
||||||
FighterLedgeGrabLockout,
|
FighterLedgeInformation,
|
||||||
FighterKnockbackVelocity,
|
FighterKnockbackVelocity,
|
||||||
FighterTranslation,
|
FighterTranslation,
|
||||||
FighterInvulnerabilityState,
|
FighterInvulnerabilityState,
|
||||||
@@ -298,8 +299,8 @@ impl Plugin for FighterPlugin {
|
|||||||
.checksum_component_with_hash::<FighterPendingRespawn>()
|
.checksum_component_with_hash::<FighterPendingRespawn>()
|
||||||
.rollback_component_with_copy::<FighterHitstop>()
|
.rollback_component_with_copy::<FighterHitstop>()
|
||||||
.checksum_component_with_hash::<FighterHitstop>()
|
.checksum_component_with_hash::<FighterHitstop>()
|
||||||
.rollback_component_with_copy::<FighterLedgeGrabLockout>()
|
.rollback_component_with_copy::<FighterLedgeInformation>()
|
||||||
.checksum_component_with_hash::<FighterLedgeGrabLockout>()
|
.checksum_component_with_hash::<FighterLedgeInformation>()
|
||||||
.rollback_component_with_copy::<FighterInvulnerabilityState>()
|
.rollback_component_with_copy::<FighterInvulnerabilityState>()
|
||||||
.checksum_component_with_hash::<FighterInvulnerabilityState>()
|
.checksum_component_with_hash::<FighterInvulnerabilityState>()
|
||||||
.rollback_component_with_copy::<FighterKnockbackVelocity>()
|
.rollback_component_with_copy::<FighterKnockbackVelocity>()
|
||||||
@@ -430,7 +431,7 @@ pub fn decay_hitstop(query: Query<(&mut FighterHitstop, &mut FighterHitstopShake
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decay_ledge_grab_lockout(query: Query<&mut FighterLedgeGrabLockout>) {
|
pub fn decay_ledge_grab_lockout(query: Query<&mut FighterLedgeInformation>) {
|
||||||
for mut lockout in query {
|
for mut lockout in query {
|
||||||
lockout.tick();
|
lockout.tick();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::fighter::state::jump::{self};
|
use crate::fighter::state::jump::{self};
|
||||||
use crate::fighter::state::land::{LandingState, LandingType};
|
use crate::fighter::state::land::{LandingState, LandingType};
|
||||||
use crate::fighter::state::ledge_wait::LedgeWaitState;
|
use crate::fighter::state::ledge_grab::LedgeGrabState;
|
||||||
use crate::fighter::state::{
|
use crate::fighter::state::{
|
||||||
CollisionContext, FighterState, InterruptContext, air_attack, air_dodge,
|
CollisionContext, FighterState, InterruptContext, air_attack, air_dodge,
|
||||||
};
|
};
|
||||||
@@ -129,10 +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() {
|
||||||
return Some(FighterState::LedgeWait(LedgeWaitState {
|
return Some(FighterState::LedgeGrab(LedgeGrabState::new(ledge_result.ledge)));
|
||||||
airborne_state_common: AirborneStateCommon::default(),
|
|
||||||
ledge_id: ledge_result.ledge,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +141,7 @@ pub fn collision_interrupt(
|
|||||||
ctx.ecb.right -= diff;
|
ctx.ecb.right -= diff;
|
||||||
ctx.translation.0 = res.hit_position;
|
ctx.translation.0 = res.hit_position;
|
||||||
ctx.presentation_snap.0 = true;
|
ctx.presentation_snap.0 = true;
|
||||||
ctx.surface.land(res.line_id);
|
ctx.surface.land(res.line_id, ctx.ledge_info);
|
||||||
Some(FighterState::Land(LandingState::create(landing_type)))
|
Some(FighterState::Land(LandingState::create(landing_type)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ impl FighterStateImpl for FallState {
|
|||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
||||||
state_context.play_animation(AnimKind::Fall, true);
|
state_context.play_animation(AnimKind::Fall, true);
|
||||||
|
state_context.velocity.x = state_context.velocity.x.clamp(-state_context.fighter_attributes.air_drift_max, state_context.fighter_attributes.air_drift_max);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut UpdateContext) {
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ pub fn collide_with_stage_grounded(
|
|||||||
state_context.surface.land(StageLineID {
|
state_context.surface.land(StageLineID {
|
||||||
polygon: current_line_id.polygon,
|
polygon: current_line_id.polygon,
|
||||||
segment: segment_idx,
|
segment: segment_idx,
|
||||||
});
|
}, state_context.ledge_info);
|
||||||
GroundedMotionResult::InGround
|
GroundedMotionResult::InGround
|
||||||
}
|
}
|
||||||
GroundedPositionResult::InAir => {
|
GroundedPositionResult::InAir => {
|
||||||
@@ -246,7 +246,7 @@ pub fn collision_interrupt(ctx: &mut CollisionContext, can_walk_off: bool) -> Op
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterSurfaceState};
|
use crate::fighter::{FighterECB, FighterLedgeInformation, FighterPreviousTranslation, FighterSurfaceState};
|
||||||
use crate::math::rect::FGRect;
|
use crate::math::rect::FGRect;
|
||||||
use crate::stage::line::StagePolyType;
|
use crate::stage::line::StagePolyType;
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
@@ -371,6 +371,7 @@ use crate::stage::line::StagePolyType;
|
|||||||
let mut translation = FighterTranslation(FGVec2::lit("11", "0"));
|
let mut translation = FighterTranslation(FGVec2::lit("11", "0"));
|
||||||
let previous_translation = FighterPreviousTranslation(translation.0);
|
let previous_translation = FighterPreviousTranslation(translation.0);
|
||||||
let mut velocity = FighterVelocity::default();
|
let mut velocity = FighterVelocity::default();
|
||||||
|
let mut ledge_info = FighterLedgeInformation::default();
|
||||||
let mut ecb = FighterECB {
|
let mut ecb = FighterECB {
|
||||||
bottom: FGVec2::lit("0.0", "-0.5"),
|
bottom: FGVec2::lit("0.0", "-0.5"),
|
||||||
top: FGVec2::lit("0.0", "0.5"),
|
top: FGVec2::lit("0.0", "0.5"),
|
||||||
@@ -386,6 +387,7 @@ use crate::stage::line::StagePolyType;
|
|||||||
velocity: &mut velocity,
|
velocity: &mut velocity,
|
||||||
surface: &mut surface,
|
surface: &mut surface,
|
||||||
ecb: &mut ecb,
|
ecb: &mut ecb,
|
||||||
|
ledge_info: &mut ledge_info,
|
||||||
prev_ecb: &previous_ecb,
|
prev_ecb: &previous_ecb,
|
||||||
stage_collision: &stage_collision,
|
stage_collision: &stage_collision,
|
||||||
leave_ground_ecb_lock_duration: 10,
|
leave_ground_ecb_lock_duration: 10,
|
||||||
@@ -395,7 +397,6 @@ use crate::stage::line::StagePolyType;
|
|||||||
origin: FGVec2::ZERO,
|
origin: FGVec2::ZERO,
|
||||||
end: FGVec2::ZERO,
|
end: FGVec2::ZERO,
|
||||||
},
|
},
|
||||||
ledge_grab_lockout_active: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
@@ -440,6 +441,7 @@ use crate::stage::line::StagePolyType;
|
|||||||
};
|
};
|
||||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
||||||
|
let mut ledge_info = FighterLedgeInformation::default();
|
||||||
let mut context = CollisionContext {
|
let mut context = CollisionContext {
|
||||||
translation: &mut translation,
|
translation: &mut translation,
|
||||||
prev_translation: &previous_translation,
|
prev_translation: &previous_translation,
|
||||||
@@ -447,6 +449,7 @@ use crate::stage::line::StagePolyType;
|
|||||||
velocity: &mut velocity,
|
velocity: &mut velocity,
|
||||||
surface: &mut surface,
|
surface: &mut surface,
|
||||||
ecb: &mut ecb,
|
ecb: &mut ecb,
|
||||||
|
ledge_info: &mut ledge_info,
|
||||||
prev_ecb: &previous_ecb,
|
prev_ecb: &previous_ecb,
|
||||||
stage_collision: &stage_collision,
|
stage_collision: &stage_collision,
|
||||||
leave_ground_ecb_lock_duration: 10,
|
leave_ground_ecb_lock_duration: 10,
|
||||||
@@ -456,7 +459,6 @@ use crate::stage::line::StagePolyType;
|
|||||||
origin: FGVec2::ZERO,
|
origin: FGVec2::ZERO,
|
||||||
end: FGVec2::ZERO,
|
end: FGVec2::ZERO,
|
||||||
},
|
},
|
||||||
ledge_grab_lockout_active: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use super::{CollisionContext, EnterContext, FighterStateImpl, InterruptContext,
|
|||||||
use crate::fighter::animation::AnimKind;
|
use crate::fighter::animation::AnimKind;
|
||||||
use crate::fighter::state::air::AirborneStateCommon;
|
use crate::fighter::state::air::AirborneStateCommon;
|
||||||
use crate::fighter::state::jump::{JumpState, JumpStateInitiator, JumpType};
|
use crate::fighter::state::jump::{JumpState, JumpStateInitiator, JumpType};
|
||||||
|
use crate::fighter::state::wait::WaitState;
|
||||||
use crate::fighter::state::{FighterState, ground};
|
use crate::fighter::state::{FighterState, ground};
|
||||||
use crate::fighter::FighterFacingDirection;
|
use crate::fighter::FighterFacingDirection;
|
||||||
use crate::input::FighterCommands;
|
use crate::input::FighterCommands;
|
||||||
@@ -11,7 +12,8 @@ use crate::stage::line::{StageLedgeSide, StageVertexID};
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum LedgeAction {
|
pub enum LedgeAction {
|
||||||
Jump(JumpStateInitiator)
|
Jump(JumpStateInitiator),
|
||||||
|
GetUp
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
@@ -29,6 +31,13 @@ impl LedgeActionState {
|
|||||||
is_in_grounded_phase: false,
|
is_in_grounded_phase: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn create_getup(ledge_id: StageVertexID) -> Self {
|
||||||
|
LedgeActionState {
|
||||||
|
ledge_id,
|
||||||
|
ledge_action: LedgeAction::GetUp,
|
||||||
|
is_in_grounded_phase: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterStateImpl for LedgeActionState {
|
impl FighterStateImpl for LedgeActionState {
|
||||||
@@ -50,6 +59,13 @@ impl FighterStateImpl for LedgeActionState {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
LedgeAction::GetUp => {
|
||||||
|
if state_context.is_current_animation_finished() {
|
||||||
|
Some(FighterState::Wait(WaitState))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +77,9 @@ impl FighterStateImpl for LedgeActionState {
|
|||||||
state_context.input.clear_command(FighterCommands::Jump);
|
state_context.input.clear_command(FighterCommands::Jump);
|
||||||
state_context.input.clear_command(FighterCommands::TapJump);
|
state_context.input.clear_command(FighterCommands::TapJump);
|
||||||
},
|
},
|
||||||
|
LedgeAction::GetUp => {
|
||||||
|
state_context.play_animation(AnimKind::LedgeGetUp, false);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
state_context.velocity.0 = FGVec2::ZERO;
|
state_context.velocity.0 = FGVec2::ZERO;
|
||||||
@@ -105,7 +124,7 @@ impl FighterStateImpl for LedgeActionState {
|
|||||||
|
|
||||||
self.is_in_grounded_phase = true;
|
self.is_in_grounded_phase = true;
|
||||||
state_context.translation.0 = ledge.position;
|
state_context.translation.0 = ledge.position;
|
||||||
state_context.surface.land(segment_id);
|
state_context.surface.land(segment_id, state_context.ledge_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
use super::{
|
||||||
|
EnterContext, FighterStateImpl, InterruptContext, UpdateContext,
|
||||||
|
};
|
||||||
|
use crate::fighter::{FighterFacingDirection, FighterInvulnerabilityState};
|
||||||
|
use crate::fighter::animation::AnimKind;
|
||||||
|
use crate::fighter::state::{ExitContext, FighterState};
|
||||||
|
use crate::fighter::state::air::AirborneStateCommon;
|
||||||
|
use crate::fighter::state::ledge_wait::LedgeWaitState;
|
||||||
|
use crate::stage::line::{StageLedgeSide, StageVertexID};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct LedgeGrabState {
|
||||||
|
ledge_id: StageVertexID
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LedgeGrabState {
|
||||||
|
pub fn new(ledge_id: StageVertexID) -> Self {
|
||||||
|
Self {
|
||||||
|
ledge_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterStateImpl for LedgeGrabState {
|
||||||
|
const NAME: &'static str = "LedgeGrab";
|
||||||
|
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
||||||
|
if state_context.is_current_animation_finished() {
|
||||||
|
Some(FighterState::LedgeWait(LedgeWaitState {
|
||||||
|
airborne_state_common: AirborneStateCommon::default(),
|
||||||
|
ledge_id: self.ledge_id.clone()
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
||||||
|
state_context.play_animation(AnimKind::LedgeGrab, false);
|
||||||
|
|
||||||
|
*state_context.vulnerability = FighterInvulnerabilityState::Intangible {
|
||||||
|
frames_remaining: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(ledge) = state_context.stage_collision.find_ledge_by_vertex(self.ledge_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
state_context.translation.0 = ledge.position;
|
||||||
|
*state_context.facing_direction = match ledge.hang_side {
|
||||||
|
StageLedgeSide::Left => FighterFacingDirection::Right,
|
||||||
|
StageLedgeSide::Right => FighterFacingDirection::Left,
|
||||||
|
};
|
||||||
|
if let Some(root_position) = state_context.initial_animation_root_position() {
|
||||||
|
state_context.translation.0 = root_position;
|
||||||
|
}
|
||||||
|
state_context.presentation_snap.0 = true;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_exit(&mut self, state_context: &mut ExitContext) {
|
||||||
|
*state_context.vulnerability = FighterInvulnerabilityState::Vulnerable;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_visually_grounded<'a>(&'a self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, state_context: &mut UpdateContext) {
|
||||||
|
if let Some(root_position) = state_context.animation_root_position {
|
||||||
|
state_context.translation.0 = root_position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,13 @@ impl FighterStateImpl for LedgeWaitState {
|
|||||||
} else if state_context.input.has_command(FighterCommands::TapJump) {
|
} else if state_context.input.has_command(FighterCommands::TapJump) {
|
||||||
return Some(FighterState::LedgeAction(LedgeActionState::create_jump(self.ledge_id, JumpStateInitiator::Tap)));
|
return Some(FighterState::LedgeAction(LedgeActionState::create_jump(self.ledge_id, JumpStateInitiator::Tap)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_ledge_getup_input(state_context.input.get_last_frame().movement.x,
|
||||||
|
state_context.game_settings.input_common.run_stick_threshold,
|
||||||
|
state_context.facing_direction) {
|
||||||
|
return Some(FighterState::LedgeAction(LedgeActionState::create_getup(self.ledge_id)));
|
||||||
|
}
|
||||||
|
|
||||||
if is_ledge_drop_input(state_context.input.get_last_frame().movement.x,
|
if is_ledge_drop_input(state_context.input.get_last_frame().movement.x,
|
||||||
state_context.game_settings.input_common.run_stick_threshold,
|
state_context.game_settings.input_common.run_stick_threshold,
|
||||||
state_context.facing_direction) {
|
state_context.facing_direction) {
|
||||||
@@ -58,6 +65,8 @@ impl FighterStateImpl for LedgeWaitState {
|
|||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
||||||
state_context.velocity.0 = FGVec2::ZERO;
|
state_context.velocity.0 = FGVec2::ZERO;
|
||||||
state_context.knockback_velocity.0 = FGVec2::ZERO;
|
state_context.knockback_velocity.0 = FGVec2::ZERO;
|
||||||
|
|
||||||
|
if state_context.ledge_info.grab_count < state_context.game_settings.fighter_common.max_ledge_regrab_count {
|
||||||
*state_context.vulnerability = FighterInvulnerabilityState::Intangible {
|
*state_context.vulnerability = FighterInvulnerabilityState::Intangible {
|
||||||
frames_remaining: Some(
|
frames_remaining: Some(
|
||||||
state_context
|
state_context
|
||||||
@@ -66,6 +75,10 @@ impl FighterStateImpl for LedgeWaitState {
|
|||||||
.ledge_grab_invulnerability_duration,
|
.ledge_grab_invulnerability_duration,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
state_context.ledge_info.grab_count = state_context.ledge_info.grab_count.saturating_add(1);
|
||||||
|
|
||||||
let Some(ledge) = state_context.stage_collision.find_ledge_by_vertex(self.ledge_id) else {
|
let Some(ledge) = state_context.stage_collision.find_ledge_by_vertex(self.ledge_id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -135,3 +148,18 @@ fn is_ledge_drop_input(
|
|||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_ledge_getup_input(
|
||||||
|
horizontal_input: crate::math::int::FGi32,
|
||||||
|
threshold: crate::math::int::FGi32,
|
||||||
|
facing_dir: &FighterFacingDirection,
|
||||||
|
) -> bool {
|
||||||
|
let dir = facing_dir.to_sign();
|
||||||
|
if horizontal_input.abs() > threshold {
|
||||||
|
if dir == horizontal_input.signum() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|||||||
+16
-10
@@ -20,7 +20,7 @@ use crate::fighter::visual::FighterAnimations;
|
|||||||
use crate::fighter::{
|
use crate::fighter::{
|
||||||
Fighter, FighterDamage, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
Fighter, FighterDamage, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
||||||
FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole,
|
FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole,
|
||||||
FighterInvulnerabilityState, FighterLedgeGrabLockout, FighterPendingRespawn,
|
FighterInvulnerabilityState, FighterLedgeInformation, FighterPendingRespawn,
|
||||||
FighterPresentationSnap, FighterPreviousTranslation, FighterSurfaceState, FighterTranslation,
|
FighterPresentationSnap, FighterPreviousTranslation, FighterSurfaceState, FighterTranslation,
|
||||||
FighterVelocity,
|
FighterVelocity,
|
||||||
};
|
};
|
||||||
@@ -56,6 +56,7 @@ pub mod wait;
|
|||||||
pub mod walk;
|
pub mod walk;
|
||||||
pub mod ledge_wait;
|
pub mod ledge_wait;
|
||||||
pub mod ledge_action;
|
pub mod ledge_action;
|
||||||
|
pub mod ledge_grab;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||||
pub enum SurfacePolicy {
|
pub enum SurfacePolicy {
|
||||||
@@ -139,7 +140,7 @@ pub struct AnimationStatus {
|
|||||||
pub struct InterruptContext<'a> {
|
pub struct InterruptContext<'a> {
|
||||||
pub input: &'a FighterInput,
|
pub input: &'a FighterInput,
|
||||||
pub facing_direction: &'a mut FighterFacingDirection,
|
pub facing_direction: &'a mut FighterFacingDirection,
|
||||||
pub ledge_grab_lockout: &'a mut FighterLedgeGrabLockout,
|
pub ledge_grab_lockout: &'a mut FighterLedgeInformation,
|
||||||
pub game_settings: &'a GameSettings,
|
pub game_settings: &'a GameSettings,
|
||||||
pub fighter_attributes: &'a FighterAttributes,
|
pub fighter_attributes: &'a FighterAttributes,
|
||||||
pub surface: &'a FighterSurfaceState,
|
pub surface: &'a FighterSurfaceState,
|
||||||
@@ -171,6 +172,7 @@ pub struct EnterContext<'a> {
|
|||||||
pub presentation_snap: &'a mut FighterPresentationSnap,
|
pub presentation_snap: &'a mut FighterPresentationSnap,
|
||||||
pub surface: &'a mut FighterSurfaceState,
|
pub surface: &'a mut FighterSurfaceState,
|
||||||
pub vulnerability: &'a mut FighterInvulnerabilityState,
|
pub vulnerability: &'a mut FighterInvulnerabilityState,
|
||||||
|
pub ledge_info: &'a mut FighterLedgeInformation,
|
||||||
pub fighter_attributes: &'a FighterAttributes,
|
pub fighter_attributes: &'a FighterAttributes,
|
||||||
pub attack_script_assets: &'a FighterAttackScriptAssets,
|
pub attack_script_assets: &'a FighterAttackScriptAssets,
|
||||||
pub game_settings: &'a GameSettings,
|
pub game_settings: &'a GameSettings,
|
||||||
@@ -217,6 +219,7 @@ pub struct UpdateContext<'a> {
|
|||||||
pub surface: &'a mut FighterSurfaceState,
|
pub surface: &'a mut FighterSurfaceState,
|
||||||
pub knockback_velocity: &'a mut FighterKnockbackVelocity,
|
pub knockback_velocity: &'a mut FighterKnockbackVelocity,
|
||||||
pub vulnerability: &'a mut FighterInvulnerabilityState,
|
pub vulnerability: &'a mut FighterInvulnerabilityState,
|
||||||
|
pub ledge_info: &'a mut FighterLedgeInformation,
|
||||||
pub game_settings: &'a GameSettings,
|
pub game_settings: &'a GameSettings,
|
||||||
/// Root motion in the fighter's gameplay plane: X is world-right and Y is
|
/// Root motion in the fighter's gameplay plane: X is world-right and Y is
|
||||||
/// world-up. Animation +Z is forward and is mirrored by facing direction.
|
/// world-up. Animation +Z is forward and is mirrored by facing direction.
|
||||||
@@ -256,8 +259,8 @@ pub struct CollisionContext<'a> {
|
|||||||
pub stage_collision: &'a StageCollision,
|
pub stage_collision: &'a StageCollision,
|
||||||
pub leave_ground_ecb_lock_duration: u32,
|
pub leave_ground_ecb_lock_duration: u32,
|
||||||
pub presentation_snap: &'a mut FighterPresentationSnap,
|
pub presentation_snap: &'a mut FighterPresentationSnap,
|
||||||
|
pub ledge_info: &'a mut FighterLedgeInformation,
|
||||||
pub fighter_ledge_collision_box: FGRect,
|
pub fighter_ledge_collision_box: FGRect,
|
||||||
pub ledge_grab_lockout_active: bool,
|
|
||||||
pub downward_stick_threshold: FGi32,
|
pub downward_stick_threshold: FGi32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +285,7 @@ impl CollisionContext<'_> {
|
|||||||
pub fn air_collide_with_ledges(
|
pub fn air_collide_with_ledges(
|
||||||
&self,
|
&self,
|
||||||
) -> Option<crate::fighter::collision::AirCollisionWithLedgeResult> {
|
) -> Option<crate::fighter::collision::AirCollisionWithLedgeResult> {
|
||||||
if self.ledge_grab_lockout_active {
|
if self.ledge_info.is_regrab_lockout_active() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
crate::fighter::collision::air_collide_with_ledges(
|
crate::fighter::collision::air_collide_with_ledges(
|
||||||
@@ -305,6 +308,7 @@ fighter_states! {
|
|||||||
Land => land::LandingState,
|
Land => land::LandingState,
|
||||||
LedgeWait => ledge_wait::LedgeWaitState,
|
LedgeWait => ledge_wait::LedgeWaitState,
|
||||||
LedgeAction => ledge_action::LedgeActionState,
|
LedgeAction => ledge_action::LedgeActionState,
|
||||||
|
LedgeGrab => ledge_grab::LedgeGrabState,
|
||||||
AirDodge => air_dodge::AirDodgeState,
|
AirDodge => air_dodge::AirDodgeState,
|
||||||
Turn => turn::TurnState,
|
Turn => turn::TurnState,
|
||||||
GroundAttack => ground_attack::GroundAttackState,
|
GroundAttack => ground_attack::GroundAttackState,
|
||||||
@@ -330,7 +334,7 @@ pub struct FighterFrameQuery {
|
|||||||
pub facing_direction: &'static mut FighterFacingDirection,
|
pub facing_direction: &'static mut FighterFacingDirection,
|
||||||
pub animation_frame: &'static mut FighterAnimationFrame,
|
pub animation_frame: &'static mut FighterAnimationFrame,
|
||||||
pub vulnerability: &'static mut FighterInvulnerabilityState,
|
pub vulnerability: &'static mut FighterInvulnerabilityState,
|
||||||
pub ledge_grab_lockout: &'static mut FighterLedgeGrabLockout,
|
pub ledge_information: &'static mut FighterLedgeInformation,
|
||||||
pub fighter: &'static Fighter,
|
pub fighter: &'static Fighter,
|
||||||
pub bone_matrices: &'static FighterBoneMatrices,
|
pub bone_matrices: &'static FighterBoneMatrices,
|
||||||
}
|
}
|
||||||
@@ -366,7 +370,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
InterruptContext {
|
InterruptContext {
|
||||||
input: &self.input,
|
input: &self.input,
|
||||||
facing_direction: &mut self.facing_direction,
|
facing_direction: &mut self.facing_direction,
|
||||||
ledge_grab_lockout: &mut self.ledge_grab_lockout,
|
ledge_grab_lockout: &mut self.ledge_information,
|
||||||
game_settings,
|
game_settings,
|
||||||
fighter_attributes: attributes,
|
fighter_attributes: attributes,
|
||||||
animation,
|
animation,
|
||||||
@@ -398,6 +402,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
surface: &mut self.surface,
|
surface: &mut self.surface,
|
||||||
vulnerability: &mut self.vulnerability,
|
vulnerability: &mut self.vulnerability,
|
||||||
presentation_snap: &mut self.presentation_snap,
|
presentation_snap: &mut self.presentation_snap,
|
||||||
|
ledge_info: &mut self.ledge_information,
|
||||||
fighter_attributes: attributes,
|
fighter_attributes: attributes,
|
||||||
attack_script_assets,
|
attack_script_assets,
|
||||||
game_settings,
|
game_settings,
|
||||||
@@ -445,6 +450,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
animation_root_position,
|
animation_root_position,
|
||||||
animation_frame: self.animation_frame.frame,
|
animation_frame: self.animation_frame.frame,
|
||||||
vulnerability: &mut self.vulnerability,
|
vulnerability: &mut self.vulnerability,
|
||||||
|
ledge_info: &mut self.ledge_information,
|
||||||
fighter_attributes: attributes,
|
fighter_attributes: attributes,
|
||||||
attack_scripts,
|
attack_scripts,
|
||||||
attack_script_assets,
|
attack_script_assets,
|
||||||
@@ -471,6 +477,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
velocity: &mut self.velocity,
|
velocity: &mut self.velocity,
|
||||||
surface: &mut self.surface,
|
surface: &mut self.surface,
|
||||||
ecb: &mut self.ecb,
|
ecb: &mut self.ecb,
|
||||||
|
ledge_info: &mut self.ledge_information,
|
||||||
prev_ecb: &self.prev_ecb,
|
prev_ecb: &self.prev_ecb,
|
||||||
stage_collision,
|
stage_collision,
|
||||||
leave_ground_ecb_lock_duration: game_settings
|
leave_ground_ecb_lock_duration: game_settings
|
||||||
@@ -478,7 +485,6 @@ 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,
|
||||||
ledge_grab_lockout_active: self.ledge_grab_lockout.is_active(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -829,15 +835,17 @@ mod tests {
|
|||||||
let mut velocity = FighterVelocity::default();
|
let mut velocity = FighterVelocity::default();
|
||||||
let mut surface = FighterSurfaceState::default();
|
let mut surface = FighterSurfaceState::default();
|
||||||
let mut ecb = FighterECB::default();
|
let mut ecb = FighterECB::default();
|
||||||
|
let mut ledge_info = FighterLedgeInformation::default();
|
||||||
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,
|
||||||
velocity: &mut velocity,
|
velocity: &mut velocity,
|
||||||
surface: &mut surface,
|
surface: &mut surface,
|
||||||
ecb: &mut ecb,
|
ecb: &mut ecb,
|
||||||
|
ledge_info: &mut ledge_info,
|
||||||
prev_ecb: &previous_ecb,
|
prev_ecb: &previous_ecb,
|
||||||
stage_collision: &stage_collision,
|
stage_collision: &stage_collision,
|
||||||
leave_ground_ecb_lock_duration: 0,
|
leave_ground_ecb_lock_duration: 0,
|
||||||
@@ -846,12 +854,10 @@ mod tests {
|
|||||||
origin: FGVec2::lit("-1", "1"),
|
origin: FGVec2::lit("-1", "1"),
|
||||||
end: FGVec2::lit("1", "-1"),
|
end: FGVec2::lit("1", "-1"),
|
||||||
},
|
},
|
||||||
ledge_grab_lockout_active: true,
|
|
||||||
downward_stick_threshold: FGi32::ONE,
|
downward_stick_threshold: FGi32::ONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(context.air_collide_with_ledges().is_none());
|
assert!(context.air_collide_with_ledges().is_none());
|
||||||
context.ledge_grab_lockout_active = false;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
context.air_collide_with_ledges().map(|result| result.ledge),
|
context.air_collide_with_ledges().map(|result| result.ledge),
|
||||||
Some(ledge_id)
|
Some(ledge_id)
|
||||||
|
|||||||
+13
-5
@@ -1,6 +1,6 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::stage::line::StageLineID;
|
use crate::{fighter::FighterLedgeInformation, stage::line::StageLineID};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||||
pub enum EcbLockPolicy {
|
pub enum EcbLockPolicy {
|
||||||
@@ -53,8 +53,9 @@ impl FighterSurfaceState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn land(&mut self, line_id: StageLineID) {
|
pub(crate) fn land(&mut self, line_id: StageLineID, ledge_info: &mut FighterLedgeInformation) {
|
||||||
*self = Self::Grounded { line_id };
|
*self = Self::Grounded { line_id };
|
||||||
|
ledge_info.grab_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_polygon_ignore(&mut self, polygon: usize) {
|
pub fn clear_polygon_ignore(&mut self, polygon: usize) {
|
||||||
@@ -151,7 +152,11 @@ mod tests {
|
|||||||
polygon: 2,
|
polygon: 2,
|
||||||
segment: 3,
|
segment: 3,
|
||||||
};
|
};
|
||||||
surface.land(line_id);
|
let mut ledge_info = FighterLedgeInformation {
|
||||||
|
regrab_lockout_remaining_frames: 0,
|
||||||
|
grab_count: 0,
|
||||||
|
};
|
||||||
|
surface.land(line_id, &mut ledge_info);
|
||||||
assert!(surface.is_grounded());
|
assert!(surface.is_grounded());
|
||||||
assert_eq!(surface.line_id(), Some(line_id));
|
assert_eq!(surface.line_id(), Some(line_id));
|
||||||
|
|
||||||
@@ -220,8 +225,11 @@ mod tests {
|
|||||||
surface.ignore_polygon(3, 5);
|
surface.ignore_polygon(3, 5);
|
||||||
surface.apply_airborne_policy(EcbLockPolicy::Restart, 2);
|
surface.apply_airborne_policy(EcbLockPolicy::Restart, 2);
|
||||||
assert!(surface.is_polygon_ignored(3));
|
assert!(surface.is_polygon_ignored(3));
|
||||||
|
let mut ledge_info = FighterLedgeInformation {
|
||||||
surface.land(StageLineID { polygon: 0, segment: 0 });
|
regrab_lockout_remaining_frames: 0,
|
||||||
|
grab_count: 0,
|
||||||
|
};
|
||||||
|
surface.land(StageLineID { polygon: 0, segment: 0 }, &mut ledge_info);
|
||||||
assert!(!surface.is_polygon_ignored(3));
|
assert!(!surface.is_polygon_ignored(3));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,14 +45,18 @@ pub struct FighterSettingsCommon {
|
|||||||
pub leave_ground_ecb_frame_lock_duration: u32,
|
pub leave_ground_ecb_frame_lock_duration: u32,
|
||||||
pub platform_pass_platform_ignore_duration_frames: u32,
|
pub platform_pass_platform_ignore_duration_frames: u32,
|
||||||
pub platform_pass_initial_downwards_velocity: FGi32,
|
pub platform_pass_initial_downwards_velocity: FGi32,
|
||||||
/// Frames during which a fighter cannot grab any ledge after dropping from one.
|
|
||||||
pub ledge_drop_regrab_lockout_duration_frames: u32,
|
|
||||||
|
|
||||||
pub intangible_color: Color,
|
pub intangible_color: Color,
|
||||||
pub intangible_pulse_duration: u32,
|
pub intangible_pulse_duration: u32,
|
||||||
|
|
||||||
pub angel_platform_invulnerability_duration: u32,
|
pub angel_platform_invulnerability_duration: u32,
|
||||||
|
|
||||||
|
/// Frames during which a fighter cannot grab any ledge after dropping from one.
|
||||||
|
pub ledge_drop_regrab_lockout_duration_frames: u32,
|
||||||
|
/// How many frames (+ the ledge animation duration) the fighter will be invulnerable after grabbing the ledge
|
||||||
pub ledge_grab_invulnerability_duration: u32,
|
pub ledge_grab_invulnerability_duration: u32,
|
||||||
|
/// How many times the fighter can regrab the ledge and still get invulnerability
|
||||||
|
pub max_ledge_regrab_count: u32,
|
||||||
|
|
||||||
/// When hit while the receiver is in the air, use this angle
|
/// When hit while the receiver is in the air, use this angle
|
||||||
pub sakurai_angle_aerial_angle: FGi32,
|
pub sakurai_angle_aerial_angle: FGi32,
|
||||||
|
|||||||
Reference in New Issue
Block a user