Improve ECB behaviour, add ECB interpolation, wall/ceiling support etc (#94)
Reviewed-on: #94 Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #94.
This commit is contained in:
+75
-54
@@ -4,16 +4,18 @@
|
||||
|
||||
use crate::fighter::state::air_dodge::AirDodgeState;
|
||||
use crate::fighter::state::{
|
||||
CollisionContext, FighterState, InterruptContext, air, crouch, dash, fall, ground_attack, turn, wait, walk,
|
||||
};
|
||||
use crate::fighter::{
|
||||
FighterAttributes, FighterTranslation, FighterVelocity,
|
||||
CollisionContext, FighterState, InterruptContext, air, crouch, dash, fall, ground_attack, turn,
|
||||
wait, walk,
|
||||
};
|
||||
use crate::fighter::{collision, ecb};
|
||||
use crate::fighter::{FighterAttributes, FighterTranslation, FighterVelocity};
|
||||
use crate::game_settings::GameSettings;
|
||||
use crate::input::FighterInput;
|
||||
use crate::math::int::{FGWide, FGi32};
|
||||
use crate::math::vec::FGVec2;
|
||||
use crate::stage::line::{StageLineID, StagePoly, StagePolyLineSegment, StagePolyLineSegmentType};
|
||||
use crate::stage::line::{StagePoly, StagePolyLineSegment, StagePolyLineSegmentType};
|
||||
#[cfg(test)]
|
||||
use crate::stage::line::StageLineID;
|
||||
|
||||
pub fn apply_grounded_friction(friction: FGi32, ground_vel: FGi32) -> FGi32 {
|
||||
if friction.abs() > ground_vel.abs() {
|
||||
@@ -148,10 +150,7 @@ fn resolve_grounded_position(
|
||||
GroundedPositionResult::InAir
|
||||
}
|
||||
|
||||
pub fn apply_grounded_motion(
|
||||
velocity: &FighterVelocity,
|
||||
translation: &mut FighterTranslation,
|
||||
) {
|
||||
pub fn apply_grounded_motion(velocity: &FighterVelocity, translation: &mut FighterTranslation) {
|
||||
// Vertical velocity should be 0 on the ground
|
||||
translation.0 += FGVec2::new(velocity.0.x, FGi32::ZERO);
|
||||
}
|
||||
@@ -176,61 +175,80 @@ pub fn collide_with_stage_grounded(
|
||||
state_context: &mut CollisionContext,
|
||||
can_walk_off: bool,
|
||||
) -> GroundedMotionResult {
|
||||
let translation = &mut state_context.translation;
|
||||
let ecb = &state_context.ecb;
|
||||
let stage_collision = state_context.stage_collision;
|
||||
|
||||
let Some(current_line_id) = state_context.surface.line_id() else {
|
||||
let Some(mut line_id) = state_context.surface.line_id() else {
|
||||
state_context.apply_desired_ecb();
|
||||
return GroundedMotionResult::InAir;
|
||||
};
|
||||
let requested_translation = state_context.translation.0;
|
||||
let movement = requested_translation - state_context.prev_translation.0;
|
||||
let largest_change = movement
|
||||
.x
|
||||
.abs()
|
||||
.max(movement.y.abs())
|
||||
.max(ecb::ecb_max_delta(state_context.ecb, state_context.desired_ecb));
|
||||
let steps = collision::collision_step_count(largest_change);
|
||||
let movement_per_step = movement / FGi32::from_num(steps);
|
||||
let mut current_position = state_context.prev_translation.0;
|
||||
let mut current_ecb = *state_context.ecb;
|
||||
|
||||
let stage_poly = stage_collision.stage_polys.get(current_line_id.polygon);
|
||||
for step in 0..steps {
|
||||
ecb::interpolate_ecb(
|
||||
&mut current_ecb,
|
||||
*state_context.desired_ecb,
|
||||
FGi32::ONE / FGi32::from_num(steps - step),
|
||||
);
|
||||
current_position = if step + 1 == steps {
|
||||
requested_translation
|
||||
} else {
|
||||
state_context.prev_translation.0
|
||||
+ movement_per_step * FGi32::from_num(step + 1)
|
||||
};
|
||||
|
||||
if let Some(stage_poly) = stage_poly {
|
||||
let ecb_bottom_pos = translation.0 + ecb.get_bottom_point();
|
||||
let Some(stage_poly) = state_context.stage_collision.stage_polys.get(line_id.polygon) else {
|
||||
*state_context.ecb = current_ecb;
|
||||
state_context.translation.0 = current_position;
|
||||
state_context.surface.apply_airborne_policy(
|
||||
crate::fighter::surface::EcbLockPolicy::StartOnDetach,
|
||||
state_context.leave_ground_ecb_lock_duration,
|
||||
);
|
||||
state_context.ecb_advanced = true;
|
||||
return GroundedMotionResult::InAir;
|
||||
};
|
||||
match resolve_grounded_position(
|
||||
stage_poly,
|
||||
current_line_id.segment,
|
||||
ecb_bottom_pos,
|
||||
line_id.segment,
|
||||
current_position + current_ecb.bottom,
|
||||
can_walk_off,
|
||||
) {
|
||||
GroundedPositionResult::InGround {
|
||||
point,
|
||||
segment_idx,
|
||||
blocked,
|
||||
} => {
|
||||
let current_segment = stage_poly.get_segment(segment_idx).unwrap();
|
||||
translation.0 = point - ecb.get_bottom_point();
|
||||
GroundedPositionResult::InGround { point, segment_idx, blocked } => {
|
||||
line_id.segment = segment_idx;
|
||||
current_position = point - current_ecb.bottom;
|
||||
let segment = stage_poly.get_segment(segment_idx).unwrap();
|
||||
state_context.velocity.0 = if blocked {
|
||||
FGVec2::ZERO
|
||||
} else {
|
||||
state_context.velocity.0
|
||||
- state_context
|
||||
.velocity
|
||||
.0
|
||||
.project_onto_normalized(current_segment.normal)
|
||||
- state_context.velocity.0.project_onto_normalized(segment.normal)
|
||||
};
|
||||
state_context.surface.land(StageLineID {
|
||||
polygon: current_line_id.polygon,
|
||||
segment: segment_idx,
|
||||
}, state_context.ledge_info);
|
||||
GroundedMotionResult::InGround
|
||||
}
|
||||
GroundedPositionResult::InAir => {
|
||||
*state_context.ecb = current_ecb;
|
||||
state_context.translation.0 = current_position;
|
||||
state_context.surface.apply_airborne_policy(
|
||||
crate::fighter::surface::EcbLockPolicy::StartOnDetach,
|
||||
state_context.leave_ground_ecb_lock_duration,
|
||||
);
|
||||
GroundedMotionResult::InAir
|
||||
state_context.ecb_advanced = true;
|
||||
return GroundedMotionResult::InAir;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state_context.surface.apply_airborne_policy(
|
||||
crate::fighter::surface::EcbLockPolicy::StartOnDetach,
|
||||
state_context.leave_ground_ecb_lock_duration,
|
||||
);
|
||||
GroundedMotionResult::InAir
|
||||
}
|
||||
|
||||
*state_context.ecb = current_ecb;
|
||||
state_context.translation.0 = current_position;
|
||||
state_context.surface.land(line_id, state_context.ledge_info);
|
||||
state_context.ecb_advanced = true;
|
||||
GroundedMotionResult::InGround
|
||||
}
|
||||
|
||||
pub fn collision_interrupt(ctx: &mut CollisionContext, can_walk_off: bool) -> Option<FighterState> {
|
||||
@@ -245,11 +263,13 @@ pub fn collision_interrupt(ctx: &mut CollisionContext, can_walk_off: bool) -> Op
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::fighter::ecb::FighterPreviousECB;
|
||||
use crate::fighter::{FighterECB, FighterLedgeInformation, FighterPreviousTranslation, FighterSurfaceState};
|
||||
use crate::math::rect::FGRect;
|
||||
use crate::stage::line::StagePolyType;
|
||||
use crate::fighter::ecb::{FighterDesiredECB, FighterPreviousECB};
|
||||
use crate::fighter::{
|
||||
FighterECB, FighterLedgeInformation, FighterPreviousTranslation, FighterSurfaceState,
|
||||
};
|
||||
use crate::input::FighterInput;
|
||||
use crate::math::rect::FGRect;
|
||||
use crate::stage::line::StagePolyType;
|
||||
|
||||
fn two_segment_floor() -> StagePoly {
|
||||
StagePoly::build(
|
||||
@@ -379,6 +399,7 @@ use crate::stage::line::StagePolyType;
|
||||
right: FGVec2::lit("0.5", "0.0"),
|
||||
};
|
||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||
let mut desired_ecb = FighterDesiredECB::from(ecb);
|
||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
||||
let mut context = CollisionContext {
|
||||
translation: &mut translation,
|
||||
@@ -387,6 +408,7 @@ use crate::stage::line::StagePolyType;
|
||||
velocity: &mut velocity,
|
||||
surface: &mut surface,
|
||||
ecb: &mut ecb,
|
||||
desired_ecb: &mut desired_ecb,
|
||||
ledge_info: &mut ledge_info,
|
||||
prev_ecb: &previous_ecb,
|
||||
stage_collision: &stage_collision,
|
||||
@@ -397,6 +419,7 @@ use crate::stage::line::StagePolyType;
|
||||
origin: FGVec2::ZERO,
|
||||
end: FGVec2::ZERO,
|
||||
},
|
||||
ecb_advanced: false,
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
@@ -440,6 +463,7 @@ use crate::stage::line::StagePolyType;
|
||||
right: FGVec2::lit("0.5", "0.0"),
|
||||
};
|
||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||
let mut desired_ecb = FighterDesiredECB::from(ecb);
|
||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
||||
let mut ledge_info = FighterLedgeInformation::default();
|
||||
let mut context = CollisionContext {
|
||||
@@ -449,6 +473,7 @@ use crate::stage::line::StagePolyType;
|
||||
velocity: &mut velocity,
|
||||
surface: &mut surface,
|
||||
ecb: &mut ecb,
|
||||
desired_ecb: &mut desired_ecb,
|
||||
ledge_info: &mut ledge_info,
|
||||
prev_ecb: &previous_ecb,
|
||||
stage_collision: &stage_collision,
|
||||
@@ -459,6 +484,7 @@ use crate::stage::line::StagePolyType;
|
||||
origin: FGVec2::ZERO,
|
||||
end: FGVec2::ZERO,
|
||||
},
|
||||
ecb_advanced: false,
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
@@ -498,15 +524,10 @@ pub fn grounded_movement_standstill_common_interrupts(
|
||||
pub fn perform_wavedash(input: &FighterInput, game_settings: &GameSettings) -> FighterState {
|
||||
// If aiming sideways and in the deadzone, do a perfect wavedash
|
||||
// TODO: Improve this so we don't have to go through the airborne state first
|
||||
let mut wavedash_dir =
|
||||
input
|
||||
.get_last_frame()
|
||||
.movement
|
||||
.normalize_or_zero();
|
||||
let mut wavedash_dir = input.get_last_frame().movement.normalize_or_zero();
|
||||
let movement = input.get_last_frame().movement;
|
||||
if movement.y.is_zero() && !movement.x.is_zero() {
|
||||
wavedash_dir.y =
|
||||
-game_settings.input_common.stick_deadzone - FGi32::lit("-0.01");
|
||||
wavedash_dir.y = -game_settings.input_common.stick_deadzone - FGi32::lit("-0.01");
|
||||
wavedash_dir.x = movement.x.signum();
|
||||
}
|
||||
|
||||
@@ -517,4 +538,4 @@ pub fn perform_wavedash(input: &FighterInput, game_settings: &GameSettings) -> F
|
||||
FighterState::AirDodge(AirDodgeState {
|
||||
direction: wavedash_dir.normalize_or_zero(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user