Add shifting ECB based on bone positions (melee-like)
This commit is contained in:
+76
-8
@@ -1,21 +1,38 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::stage::line::StageLineID;
|
||||
use crate::{stage::line::StageLineID};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum EcbLockPolicy {
|
||||
StartOnDetach,
|
||||
Restart,
|
||||
Preserve,
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// The stage surface currently supporting a fighter.
|
||||
///
|
||||
/// This is physics state rather than fighter-state-machine state. Keeping it
|
||||
/// separate means grounded, landing, and hitstun states all share one source
|
||||
/// of truth for stage contact.
|
||||
#[derive(Component, Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
|
||||
#[derive(Component, Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum FighterSurfaceState {
|
||||
#[default]
|
||||
Airborne,
|
||||
Airborne {
|
||||
ecb_lock_frames_remaining: u32
|
||||
},
|
||||
Grounded {
|
||||
line_id: StageLineID,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for FighterSurfaceState {
|
||||
fn default() -> Self {
|
||||
Self::Airborne {
|
||||
ecb_lock_frames_remaining: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FighterSurfaceState {
|
||||
pub fn is_grounded(self) -> bool {
|
||||
matches!(self, Self::Grounded { .. })
|
||||
@@ -23,7 +40,7 @@ impl FighterSurfaceState {
|
||||
|
||||
pub fn line_id(self) -> Option<StageLineID> {
|
||||
match self {
|
||||
Self::Airborne => None,
|
||||
Self::Airborne { .. } => None,
|
||||
Self::Grounded { line_id } => Some(line_id),
|
||||
}
|
||||
}
|
||||
@@ -32,8 +49,29 @@ impl FighterSurfaceState {
|
||||
*self = Self::Grounded { line_id };
|
||||
}
|
||||
|
||||
pub(crate) fn leave_ground(&mut self) {
|
||||
*self = Self::Airborne;
|
||||
pub(crate) fn apply_airborne_policy(
|
||||
&mut self,
|
||||
policy: EcbLockPolicy,
|
||||
ecb_lock_duration: u32,
|
||||
) {
|
||||
let current_lock = match *self {
|
||||
Self::Airborne { ecb_lock_frames_remaining } => ecb_lock_frames_remaining,
|
||||
Self::Grounded { .. } => 0,
|
||||
};
|
||||
let ecb_lock_frames_remaining = match policy {
|
||||
EcbLockPolicy::StartOnDetach if self.is_grounded() => ecb_lock_duration,
|
||||
EcbLockPolicy::StartOnDetach => current_lock,
|
||||
EcbLockPolicy::Restart => ecb_lock_duration,
|
||||
EcbLockPolicy::Preserve => current_lock,
|
||||
EcbLockPolicy::Clear => 0,
|
||||
};
|
||||
*self = Self::Airborne { ecb_lock_frames_remaining };
|
||||
}
|
||||
|
||||
pub(crate) fn tick_ecb_lock(&mut self) {
|
||||
if let Self::Airborne { ecb_lock_frames_remaining } = self {
|
||||
*ecb_lock_frames_remaining = ecb_lock_frames_remaining.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +93,38 @@ mod tests {
|
||||
assert!(surface.is_grounded());
|
||||
assert_eq!(surface.line_id(), Some(line_id));
|
||||
|
||||
surface.leave_ground();
|
||||
surface.apply_airborne_policy(EcbLockPolicy::Clear, 0);
|
||||
assert!(!surface.is_grounded());
|
||||
assert_eq!(surface.line_id(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_on_detach_does_not_reset_an_existing_lock() {
|
||||
let mut surface = FighterSurfaceState::default();
|
||||
surface.apply_airborne_policy(EcbLockPolicy::Restart, 5);
|
||||
surface.tick_ecb_lock();
|
||||
surface.apply_airborne_policy(EcbLockPolicy::StartOnDetach, 5);
|
||||
|
||||
assert_eq!(
|
||||
surface,
|
||||
FighterSurfaceState::Airborne {
|
||||
ecb_lock_frames_remaining: 4,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart_retriggers_an_existing_lock() {
|
||||
let mut surface = FighterSurfaceState::default();
|
||||
surface.apply_airborne_policy(EcbLockPolicy::Restart, 5);
|
||||
surface.tick_ecb_lock();
|
||||
surface.apply_airborne_policy(EcbLockPolicy::Restart, 5);
|
||||
|
||||
assert_eq!(
|
||||
surface,
|
||||
FighterSurfaceState::Airborne {
|
||||
ecb_lock_frames_remaining: 5,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user