CI / build-and-test (push) Failing after 10m51s
Fixes #22 Extracts shared grounded collision/friction and airborne movement/landing policies while preserving state-specific behavior. Running fighters now pass `can_walk_off = true` so they can run off platform edges. Includes grounded and airborne hitstun call sites. Reviewed-on: #36 Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
145 lines
5.1 KiB
Rust
145 lines
5.1 KiB
Rust
use crate::fighter::state::ground::GroundedStateCommon;
|
|
use crate::fighter::state::land::{LandingState, LandingType};
|
|
use crate::fighter::state::{CollisionContext, FighterState};
|
|
use crate::fighter::*;
|
|
|
|
pub fn integrate_gravity(velocity: &mut FighterVelocity, attributes: &FighterAttributes) {
|
|
velocity.y -= attributes.gravity;
|
|
velocity.y = velocity.y.max(-attributes.terminal_velocity);
|
|
}
|
|
|
|
pub fn apply_air_drift(
|
|
velocity: &mut FighterVelocity,
|
|
attribs: &FighterAttributes,
|
|
input: &FighterInput,
|
|
) {
|
|
if input.get_last_frame().movement.x.is_zero() {
|
|
if velocity.x.abs() < attribs.air_friction {
|
|
velocity.x = FGi32::ZERO;
|
|
} else {
|
|
let velocity_sign = velocity.x.signum();
|
|
velocity.x += (-velocity_sign) * attribs.air_friction;
|
|
}
|
|
} else {
|
|
let target_velocity = input.get_last_frame().movement.x * attribs.air_drift_max;
|
|
let mut acceleration = input.get_last_frame().movement.x.signum()
|
|
* attribs.air_acceleration_base
|
|
+ input.get_last_frame().movement.x * attribs.air_acceleration_stick;
|
|
|
|
// Acceleration opposes velocity
|
|
if acceleration * velocity.x < 0 {
|
|
// do nothing
|
|
} else {
|
|
let predicted_vel = velocity.x + acceleration;
|
|
let will_go_past = predicted_vel.abs() > target_velocity.abs();
|
|
|
|
if velocity.x.abs() < target_velocity.abs() && will_go_past {
|
|
// Prevent overshoot
|
|
acceleration = target_velocity - velocity.x
|
|
} else if velocity.x.abs() > target_velocity.abs() {
|
|
let diff = (velocity.x - target_velocity).abs();
|
|
acceleration = velocity.x.signum() * (-(attribs.air_friction).min(diff));
|
|
}
|
|
}
|
|
|
|
velocity.x += acceleration;
|
|
}
|
|
}
|
|
|
|
pub fn apply_air_motion(
|
|
velocity: &FighterVelocity,
|
|
translation: &mut FighterTranslation,
|
|
prev_translation: &mut FighterPreviousTranslation,
|
|
) {
|
|
// Vertical velocity should be 0 on the ground
|
|
prev_translation.0 = translation.0;
|
|
translation.0 += velocity.0;
|
|
}
|
|
|
|
pub fn update_standard_motion(ctx: &mut crate::fighter::state::UpdateContext) {
|
|
integrate_gravity(ctx.velocity, ctx.fighter_attributes);
|
|
apply_air_drift(ctx.velocity, ctx.fighter_attributes, ctx.input);
|
|
apply_air_motion(ctx.velocity, ctx.translation, ctx.prev_translation);
|
|
}
|
|
|
|
pub fn collision_interrupt(
|
|
ctx: &mut CollisionContext,
|
|
landing_type: LandingType,
|
|
) -> Option<FighterState> {
|
|
let res = ctx.air_collide_with_stage()?;
|
|
ctx.translation.0 = res.hit_position - ctx.ecb.get_bottom_point();
|
|
let grounded_common = GroundedStateCommon {
|
|
current_line_id: res.line_id,
|
|
};
|
|
Some(FighterState::Land(LandingState::create(
|
|
grounded_common,
|
|
landing_type,
|
|
)))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{
|
|
input::{FighterInput, FighterInputFrame},
|
|
math::vec::FGVec2,
|
|
};
|
|
|
|
fn test_attributes() -> FighterAttributes {
|
|
FighterAttributes {
|
|
base_walk_accel: FGi32::ZERO,
|
|
stick_walk_accel: FGi32::ZERO,
|
|
max_walk_vel: FGi32::ZERO,
|
|
dash_acceleration_duration: 0,
|
|
dash_duration: 0,
|
|
dash_initial_velocity: FGi32::ZERO,
|
|
base_dash_accel: FGi32::ZERO,
|
|
stick_dash_accel: FGi32::ZERO,
|
|
max_dash_vel: FGi32::ZERO,
|
|
jumpsquat_duration: 0,
|
|
short_hop_vertical_velocity: FGi32::ZERO,
|
|
full_jump_vertical_velocity: FGi32::ZERO,
|
|
jump_horizontal_velocity: FGi32::ZERO,
|
|
air_jump_multiplier: FGi32::ZERO,
|
|
air_jump_horizontal_velocity: FGi32::ZERO,
|
|
landing_iasa: 0,
|
|
landing_duration: 0,
|
|
air_acceleration_base: FGi32::ZERO,
|
|
air_acceleration_stick: FGi32::ZERO,
|
|
max_air_horizontal_velocity: FGi32::lit("1.2"),
|
|
air_drift_max: FGi32::lit("1.2"),
|
|
ground_friction: FGi32::ZERO,
|
|
terminal_velocity: FGi32::ZERO,
|
|
gravity: FGi32::ZERO,
|
|
weight: FGi32::lit("1"),
|
|
air_friction: FGi32::lit("0.001"),
|
|
air_dodge_duration: 0,
|
|
air_dodge_velocity: FGi32::ZERO,
|
|
air_dodge_decay: FGi32::ZERO,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn zero_air_acceleration_is_symmetric() {
|
|
let attributes = test_attributes();
|
|
let mut right_input = FighterInput::default();
|
|
right_input.push_input_frame(FighterInputFrame {
|
|
movement: FGVec2::new(FGi32::ONE, FGi32::ZERO),
|
|
..default()
|
|
});
|
|
let mut left_input = FighterInput::default();
|
|
left_input.push_input_frame(FighterInputFrame {
|
|
movement: FGVec2::new(-FGi32::ONE, FGi32::ZERO),
|
|
..default()
|
|
});
|
|
|
|
let mut right_velocity = FighterVelocity(FGVec2::ZERO);
|
|
let mut left_velocity = FighterVelocity(FGVec2::ZERO);
|
|
apply_air_drift(&mut right_velocity, &attributes, &right_input);
|
|
apply_air_drift(&mut left_velocity, &attributes, &left_input);
|
|
|
|
assert_eq!(right_velocity.x, FGi32::ZERO);
|
|
assert_eq!(left_velocity.x, FGi32::ZERO);
|
|
}
|
|
}
|