Compare commits

..
3 Commits
Author SHA1 Message Date
EIREXE 540b0cda8d Fix build and clamp DI angle change
CI / build-and-test (pull_request) Successful in 1m15s
2026-09-18 22:35:35 +02:00
EIREXE 158e8b6a66 Implement directional influence 2026-09-18 18:49:20 +02:00
EIREXE 400d8afaad Keeep debug trajectories around 2026-09-18 18:13:02 +02:00
12 changed files with 55 additions and 123 deletions
+1 -2
View File
@@ -22,8 +22,7 @@
air_jump_horizontal_velocity: "0.96",
air_jump_multiplier: "1.0",
dash_initial_velocity: "1.4",
terminal_velocity: "3.0",
fast_fall_speed: "3.5",
terminal_velocity: "1.6",
gravity: "0.084",
weight: "100.0",
jumpsquat_duration: 5,
-1
View File
@@ -37,7 +37,6 @@ pub struct FighterAttributes {
pub ground_friction: FGi32,
pub terminal_velocity: FGi32,
pub gravity: FGi32,
pub fast_fall_speed: FGi32,
pub weight: FGi32,
pub air_friction: FGi32,
+1 -1
View File
@@ -412,7 +412,7 @@ pub fn attack_debug_gather(
points.reserve(hit.hitstun_frames as usize);
for _ in 1..hit.hitstun_frames {
air::integrate_gravity(&mut fighter_vel, &manifest.attributes, false);
air::integrate_gravity(&mut fighter_vel, &manifest.attributes);
let vel = knockback_vel.length();
let new_vel = vel
+2 -2
View File
@@ -38,7 +38,7 @@ use crate::{
AttackKind, FighterAttackPlugin, FighterAttackScriptAssets, FighterDamage,
FighterKnockbackVelocity, FighterPendingHitReaction, FighterSolvedHurtboxes,
FighterStaleMoveQueue,
}, baked_animation::{FighterBoneIndexMap, FighterBoneMatrices}, manifest::FighterManifest, state::{FighterState, air::AirborneStateCommon, fall::FallState}, visual::FighterAnimations,
}, baked_animation::{FighterBoneIndexMap, FighterBoneMatrices}, manifest::FighterManifest, state::{FighterState, fall::FallState}, visual::FighterAnimations,
}, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, stage::line::StageCollision,
};
use state::state_interrupt_system;
@@ -299,7 +299,7 @@ pub fn spawn_fighter(
visual_root,
animations,
attack_scripts,
FighterState::Fall(FallState::new(AirborneStateCommon::default())),
FighterState::Fall(FallState),
animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true),
(
baked_animation::FighterBoneMatrices::default(),
+4 -43
View File
@@ -2,44 +2,10 @@ use crate::fighter::state::ground::GroundedStateCommon;
use crate::fighter::state::land::{LandingState, LandingType};
use crate::fighter::state::{CollisionContext, FighterState};
use crate::fighter::*;
use crate::input::FighterCommands;
#[derive(Default, Debug, Clone, Copy, Hash)]
pub struct AirborneStateCommon {
pub is_fast_falling: bool,
}
pub fn process_fast_fall(
airborne_state_common: &mut AirborneStateCommon,
input: &mut FighterInput,
velocity: &mut FighterVelocity,
attributes: &FighterAttributes,
) {
if velocity.y.is_negative() {
if input.has_command(FighterCommands::SmashMoveDown) {
airborne_state_common.is_fast_falling = true;
}
input.clear_command(FighterCommands::SmashMoveDown);
}
if airborne_state_common.is_fast_falling {
velocity.y = -attributes.fast_fall_speed;
}
}
pub fn integrate_gravity(
velocity: &mut FighterVelocity,
attributes: &FighterAttributes,
fast_fall: bool
) {
pub fn integrate_gravity(velocity: &mut FighterVelocity, attributes: &FighterAttributes) {
velocity.y -= attributes.gravity;
let terminal_vel = if fast_fall {
-attributes.fast_fall_speed
} else {
-attributes.terminal_velocity
};
velocity.y = velocity.y.max(terminal_vel);
velocity.y = velocity.y.max(-attributes.terminal_velocity);
}
pub fn apply_air_drift(
@@ -90,12 +56,8 @@ pub fn apply_air_motion(
translation.0 += velocity.0;
}
pub fn update_standard_motion(
ctx: &mut crate::fighter::state::UpdateContext,
airborne_common: &mut AirborneStateCommon,
) {
process_fast_fall(airborne_common, ctx.input, &mut ctx.velocity, ctx.fighter_attributes);
integrate_gravity(ctx.velocity, ctx.fighter_attributes, airborne_common.is_fast_falling);
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);
}
@@ -148,7 +110,6 @@ mod tests {
air_drift_max: FGi32::lit("1.2"),
ground_friction: FGi32::ZERO,
terminal_velocity: FGi32::ZERO,
fast_fall_speed: FGi32::ZERO,
gravity: FGi32::ZERO,
weight: FGi32::lit("1"),
air_friction: FGi32::lit("0.001"),
+8 -4
View File
@@ -1,9 +1,13 @@
use crate::{
fighter::{
animation::AnimKind, state::{
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air::{self, AirborneStateCommon}, fall::FallState, land::LandingType,
animation::AnimKind,
state::{
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext,
UpdateContext, air, fall::FallState, land::LandingType,
},
}, input, math::vec::FGVec2,
},
input,
math::vec::FGVec2,
};
#[derive(Debug, Clone, Hash)]
@@ -16,7 +20,7 @@ impl FighterStateImpl for AirDodgeState {
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
if state_context.is_current_animation_finished() {
Some(FighterState::Fall(FallState::new(AirborneStateCommon::default())))
Some(FighterState::Fall(FallState))
} else {
None
}
+11 -15
View File
@@ -1,24 +1,12 @@
use super::{CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext};
use crate::fighter::animation::AnimKind;
use crate::fighter::state::air::AirborneStateCommon;
use crate::fighter::state::{FighterState, air, jump};
#[derive(Debug, Clone, Copy, Hash, Default)]
pub struct FallState {
airborne_state_common: AirborneStateCommon
}
impl FallState {
pub fn new(airborne_state_common: AirborneStateCommon) -> Self {
Self {
airborne_state_common
}
}
}
#[derive(Debug, Clone, Copy, Hash)]
pub struct FallState;
impl FighterStateImpl for FallState {
const NAME: &'static str = "Fall";
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
jump::check_input_double_jump(state_context)
}
@@ -28,7 +16,7 @@ impl FighterStateImpl for FallState {
}
fn update(&mut self, state_context: &mut UpdateContext) {
air::update_standard_motion(state_context, &mut self.airborne_state_common);
air::update_standard_motion(state_context);
}
fn check_collision_interrupt(
@@ -48,3 +36,11 @@ impl FighterStateImpl for FallState {
}
}
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
let input_frame = state_context.input.get_last_frame();
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
return Some(FighterState::Fall(FallState));
}
None
}
+2 -2
View File
@@ -3,7 +3,7 @@
//! bit-for-bit deterministic for rollback.
use crate::fighter::state::{
CollisionContext, FighterState, InterruptContext, air, dash, fall, ground_attack, turn, wait, walk,
CollisionContext, FighterState, InterruptContext, dash, fall, ground_attack, turn, wait, walk,
};
use crate::fighter::{
FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
@@ -233,7 +233,7 @@ pub fn collision_interrupt(
) -> Option<FighterState> {
match collide_with_stage_grounded(ctx, common, can_walk_off) {
GroundedMotionResult::InGround => None,
GroundedMotionResult::InAir => Some(FighterState::Fall(fall::FallState::new(air::AirborneStateCommon::default()))),
GroundedMotionResult::InAir => Some(FighterState::Fall(fall::FallState)),
}
}
+13 -22
View File
@@ -1,18 +1,14 @@
use crate::{
fighter::{
animation::AnimKind,
attack::{self, FighterHitReaction},
state::{
animation::AnimKind, attack::{self, FighterHitReaction}, state::{
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
InterruptContext, UpdateContext,
air::{self, AirborneStateCommon},
InterruptContext, UpdateContext, air,
fall::FallState,
ground::{GroundedStateCommon, collision_interrupt},
land::LandingType,
wait::WaitState,
},
},
math::int::FGi32,
}, math::int::FGi32,
};
#[derive(Clone, Debug, Hash)]
@@ -25,7 +21,7 @@ pub enum HitstopStateKind {
pub struct HitstunState {
pub hit_reaction: FighterHitReaction,
pub hitstop_kind: HitstopStateKind,
knockback_applied: bool,
knockback_applied: bool
}
impl HitstunState {
@@ -33,7 +29,7 @@ impl HitstunState {
Self {
hit_reaction,
hitstop_kind,
knockback_applied: false,
knockback_applied: false
}
}
}
@@ -63,9 +59,7 @@ impl FighterStateImpl for HitstunState {
grounded_common: grounded_state_common.clone(),
}))
}
HitstopStateKind::Airborne => {
Some(FighterState::Fall(FallState::new(AirborneStateCommon::default())))
}
HitstopStateKind::Airborne => Some(FighterState::Fall(FallState)),
}
} else {
None
@@ -75,21 +69,16 @@ impl FighterStateImpl for HitstunState {
fn update(&mut self, ctx: &mut UpdateContext) {
if !self.knockback_applied {
let launch_direction = attack::directional_influence::apply_directional_influence(
ctx.input.get_last_frame().movement,
ctx.input.get_last_frame().movement,
self.hit_reaction.launch_angle,
ctx.game_settings.fighter_common.di_max_angle,
ctx.game_settings.fighter_common.di_max_angle
);
ctx.knockback_velocity.0 = launch_direction * self.hit_reaction.launch_velocity;
self.knockback_applied = true;
}
match self.hitstop_kind {
HitstopStateKind::Grounded(_) => {}
HitstopStateKind::Airborne => {
air::integrate_gravity(ctx.velocity, ctx.fighter_attributes, false);
}
}
air::integrate_gravity(ctx.velocity, ctx.fighter_attributes);
let speed = ctx.knockback_velocity.length();
let next_speed = speed
@@ -124,8 +113,10 @@ impl FighterStateImpl for HitstunState {
}
}
}
fn is_visually_grounded<'a>(&'a self) -> bool {
fn is_visually_grounded<'a>(
&'a self,
) -> bool {
match self.hitstop_kind {
HitstopStateKind::Grounded(_) => true,
HitstopStateKind::Airborne => false,
+11 -8
View File
@@ -2,10 +2,16 @@ use bevy::prelude::*;
use crate::{
fighter::{
animation::AnimKind, state::{
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air::{self, AirborneStateCommon}, air_dodge::{self, AirDodgeState}, ground::{self, GroundedStateCommon},
animation::AnimKind,
state::{
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext,
UpdateContext, air,
air_dodge::{self, AirDodgeState},
ground::{self, GroundedStateCommon},
},
}, input::FighterCommands, math::{int::FGi32, vec::FGVec2},
},
input::FighterCommands,
math::{int::FGi32, vec::FGVec2},
};
#[derive(Debug, Hash, Clone, Copy)]
pub enum JumpType {
@@ -17,7 +23,6 @@ pub enum JumpType {
#[derive(Debug, Clone, Hash)]
pub struct JumpState {
jump_type: JumpType,
airborne_state_common: AirborneStateCommon,
}
#[derive(Debug, Clone, Hash)]
@@ -69,7 +74,7 @@ impl FighterStateImpl for JumpSquatState {
} else {
JumpType::ShortHop
};
Some(FighterState::Jump(JumpState { jump_type, airborne_state_common: AirborneStateCommon::default() }))
Some(FighterState::Jump(JumpState { jump_type }))
} else {
None
}
@@ -105,7 +110,6 @@ impl FighterStateImpl for JumpState {
if state_context.input.has_command(FighterCommands::Jump) {
Some(FighterState::Jump(JumpState {
jump_type: JumpType::DoubleJump,
airborne_state_common: AirborneStateCommon::default()
}))
} else {
air_dodge::check_input(state_context)
@@ -113,7 +117,7 @@ impl FighterStateImpl for JumpState {
}
fn update(&mut self, state_context: &mut UpdateContext) {
air::update_standard_motion(state_context, &mut self.airborne_state_common);
air::update_standard_motion(state_context);
}
fn on_enter(&mut self, state_context: &mut EnterContext) {
@@ -201,7 +205,6 @@ pub fn check_input_double_jump(state_context: &InterruptContext) -> Option<Fight
.then(|| {
FighterState::Jump(JumpState {
jump_type: JumpType::DoubleJump,
airborne_state_common: AirborneStateCommon::default()
})
})
}
+2 -2
View File
@@ -148,7 +148,7 @@ pub struct ExitContext<'a> {
pub struct UpdateContext<'a> {
pub translation: &'a mut FighterTranslation,
pub prev_translation: &'a mut FighterPreviousTranslation,
pub input: &'a mut FighterInput,
pub input: &'a FighterInput,
pub hitboxes: &'a mut FighterHitboxes,
pub velocity: &'a mut FighterVelocity,
pub knockback_velocity: &'a mut FighterKnockbackVelocity,
@@ -289,7 +289,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
UpdateContext {
translation: &mut self.translation,
prev_translation: &mut self.prev_translation,
input: &mut self.input,
input: &self.input,
hitboxes: &mut self.hitboxes,
velocity: &mut self.velocity,
knockback_velocity: &mut self.knockback_velocity,
-21
View File
@@ -20,8 +20,6 @@ pub enum FighterCommands {
Jump,
Shield,
Attack,
SmashMoveUp,
SmashMoveDown,
SmashMoveLeft,
SmashMoveRight,
}
@@ -62,8 +60,6 @@ impl FighterAttackCommandType {
pub struct FighterCommandLifetimes {
smash_move_left: u8,
smash_move_right: u8,
smash_move_down: u8,
smash_move_up: u8,
jump: u8,
shield: u8,
attack: u8,
@@ -76,8 +72,6 @@ impl Index<FighterCommands> for FighterCommandLifetimes {
match command {
FighterCommands::SmashMoveLeft => &self.smash_move_left,
FighterCommands::SmashMoveRight => &self.smash_move_right,
FighterCommands::SmashMoveDown => &self.smash_move_down,
FighterCommands::SmashMoveUp => &self.smash_move_up,
FighterCommands::Jump => &self.jump,
FighterCommands::Shield => &self.shield,
FighterCommands::Attack => &self.attack,
@@ -90,8 +84,6 @@ impl IndexMut<FighterCommands> for FighterCommandLifetimes {
match command {
FighterCommands::SmashMoveLeft => &mut self.smash_move_left,
FighterCommands::SmashMoveRight => &mut self.smash_move_right,
FighterCommands::SmashMoveDown => &mut self.smash_move_down,
FighterCommands::SmashMoveUp => &mut self.smash_move_up,
FighterCommands::Jump => &mut self.jump,
FighterCommands::Shield => &mut self.shield,
FighterCommands::Attack => &mut self.attack,
@@ -229,19 +221,6 @@ pub fn postprocess_input(
let mut x_flick_detected =
x_abs >= axis_threshold && input.frames_in_x_flick_deadzone <= frame_threshold;
if y_flick_detected {
input.clear_command(FighterCommands::SmashMoveUp);
input.clear_command(FighterCommands::SmashMoveDown);
let command_to_set = if input.current_frame.movement.y > 0.0 {
FighterCommands::SmashMoveUp
} else {
FighterCommands::SmashMoveDown
};
input.set_lifetime(command_to_set, game_settings.input_common.input_buffer_size);
}
// if both flicks are detected, only one wins
if x_flick_detected && y_flick_detected {