Fix match state remaining mutable after and of match and implement initial work on aerials (#77)
CI / build-and-test (push) Successful in 2m7s
CI / production-build (push) Successful in 11m36s

Reviewed-on: #77
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
This commit was merged in pull request #77.
This commit is contained in:
2026-09-29 12:40:14 +00:00
committed by EIREXE
parent 88ad9ed951
commit adf83ba03f
10 changed files with 300 additions and 26 deletions
+13 -1
View File
@@ -1,5 +1,6 @@
use crate::fighter::state::jump::{JumpState, JumpType};
use crate::fighter::state::land::{LandingState, LandingType};
use crate::fighter::state::{CollisionContext, FighterState};
use crate::fighter::state::{CollisionContext, FighterState, InterruptContext, air_attack, air_dodge};
use crate::fighter::*;
use crate::input::FighterCommands;
@@ -112,6 +113,17 @@ pub fn update_standard_motion(
);
}
pub fn air_interrupts_common(ctx: &mut InterruptContext, airborne_state_common: &AirborneStateCommon) -> Option<FighterState> {
if ctx.input.has_command(FighterCommands::Jump) {
Some(FighterState::Jump(JumpState {
jump_type: JumpType::DoubleJump,
airborne_state_common: AirborneStateCommon::default(),
}))
} else {
air_dodge::check_input(ctx).or_else(|| air_attack::check_interrupt(ctx, airborne_state_common))
}
}
pub fn collision_interrupt(
ctx: &mut CollisionContext,
landing_type: LandingType,
+124
View File
@@ -0,0 +1,124 @@
use bevy::log::error;
use crate::fighter::{
attack::{AttackKind, FighterAttackScriptAssets, update_active_hitbox_list}, state::{
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air::{self, AirborneStateCommon}, fall::FallState,
},
};
#[derive(Clone, Debug, Hash)]
pub struct AirAttackState {
pub attack_kind: AttackKind,
pub airborne_state_common: AirborneStateCommon
}
impl FighterStateImpl for AirAttackState {
const NAME: &'static str = "AirAttack";
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
if state_context.is_current_animation_finished() {
Some(FighterState::Fall(FallState::new(self.airborne_state_common)))
} else {
None
}
}
fn update(&mut self, state_context: &mut UpdateContext) {
let animation_frame = state_context.get_current_animation_frame();
let script = state_context
.attack_scripts
.get(&state_context.attack_script_assets.scripts[&self.attack_kind]);
if let Some(script) = script {
update_active_hitbox_list(
script,
&mut state_context
.hitboxes
.0
.as_mut()
.expect("ground attack must have active hitboxes")
.active_hitboxes,
animation_frame,
);
}
air::update_standard_motion(state_context, &mut self.airborne_state_common);
}
fn on_enter(&mut self, state_context: &mut EnterContext) {
state_context.play_animation(self.attack_kind.get_animation(), false);
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack {
kind: self.attack_kind,
script,
active_hitboxes: Vec::new(),
solved_hitboxes: Vec::new(),
hit_fighters: Vec::new(),
stalled: false,
});
}
fn on_exit(&mut self, state_context: &mut ExitContext) {
state_context.hitboxes.0 = None;
}
fn check_collision_interrupt(
&mut self,
state_context: &mut CollisionContext,
) -> Option<FighterState> {
air::collision_interrupt(
state_context,
crate::fighter::state::land::LandingType::Normal,
false,
)
}
fn is_visually_grounded<'a>(&'a self) -> bool {
false
}
}
#[derive(thiserror::Error, Debug)]
pub enum AirAttackStateCreateError {
#[error("Attack script: {attack:?} not found")]
AttackScriptNotFound{attack: AttackKind},
}
impl AirAttackState {
pub fn create(attack_kind: AttackKind, attack_script_assets: &FighterAttackScriptAssets, airborne_state_common: &AirborneStateCommon) -> Result<Self, AirAttackStateCreateError> {
if attack_script_assets.scripts.contains_key(&attack_kind) {
Ok(AirAttackState {
airborne_state_common: airborne_state_common.clone(),
attack_kind
})
} else {
Err(AirAttackStateCreateError::AttackScriptNotFound { attack: attack_kind })
}
}
}
pub fn check_interrupt(state_context: &InterruptContext, airborne_state_common: &AirborneStateCommon) -> Option<FighterState> {
if state_context
.input
.has_command(crate::input::FighterCommands::Attack)
{
let attack_kind = state_context
.input
.get_attack_command_type()
.to_attack_kind_airborne(state_context.facing_direction);
let state = AirAttackState::create(attack_kind, state_context.attack_script_assets, airborne_state_common);
match state {
Ok(state) => {
Some(FighterState::AirAttack(state))
},
Err(create_error) => {
error!("{}", create_error);
None
},
}
} else {
None
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
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};
use crate::fighter::state::{FighterState, air, air_attack, jump};
#[derive(Debug, Clone, Copy, Hash, Default)]
pub struct FallState {
@@ -20,7 +20,7 @@ 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)
jump::check_input_double_jump(state_context).or_else(|| air_attack::check_interrupt(state_context, &self.airborne_state_common))
}
fn on_enter(&mut self, state_context: &mut EnterContext) {
+4 -11
View File
@@ -7,7 +7,7 @@ use crate::{
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext,
UpdateContext,
air::{self, AirborneStateCommon},
air_dodge::{self, AirDodgeState},
air_dodge::AirDodgeState,
ground,
},
},
@@ -23,8 +23,8 @@ pub enum JumpType {
#[derive(Debug, Clone, Hash)]
pub struct JumpState {
jump_type: JumpType,
airborne_state_common: AirborneStateCommon,
pub jump_type: JumpType,
pub airborne_state_common: AirborneStateCommon,
}
#[derive(Debug, Clone, Hash)]
@@ -108,14 +108,7 @@ impl FighterStateImpl for JumpSquatState {
impl FighterStateImpl for JumpState {
const NAME: &'static str = "Jump";
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
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)
}
air::air_interrupts_common(state_context, &self.airborne_state_common)
}
fn update(&mut self, state_context: &mut UpdateContext) {
+6
View File
@@ -47,6 +47,7 @@ pub mod run;
pub mod turn;
pub mod wait;
pub mod walk;
pub mod air_attack;
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum SurfacePolicy {
@@ -135,6 +136,7 @@ pub struct InterruptContext<'a> {
pub surface: &'a FighterSurfaceState,
pub animation: AnimationStatus,
pub stage_collision: &'a StageCollision,
pub attack_script_assets: &'a FighterAttackScriptAssets,
}
impl InterruptContext<'_> {
@@ -246,6 +248,7 @@ fighter_states! {
GroundAttack => ground_attack::GroundAttackState,
Hitstun => hitstun::HitstunState,
Angel => angel::AngelState,
AirAttack => air_attack::AirAttackState,
}
#[derive(QueryData)]
@@ -292,6 +295,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
baked_animations: &'a Assets<BakedFighterAnimations>,
attributes: &'a FighterAttributes,
stage_collision: &'a StageCollision,
attack_script_assets: &'a FighterAttackScriptAssets,
) -> InterruptContext<'a> {
let animation = self.animation_status(animations, baked_animations);
InterruptContext {
@@ -302,6 +306,7 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
animation,
surface: &self.surface,
stage_collision,
attack_script_assets: attack_script_assets
}
}
@@ -535,6 +540,7 @@ pub fn state_interrupt_system(
&baked_animations,
&manifest.attributes,
&stage_collision,
attack_script_assets
))
};