Fix command persistence through jump squat

This commit is contained in:
2026-10-03 10:42:30 +02:00
parent 32428982ad
commit 97788b7df8
3 changed files with 78 additions and 20 deletions
+72 -20
View File
@@ -101,6 +101,8 @@ impl FighterAttackCommandType {
struct CommandSlot {
lifetime: u8,
age: u16,
hold_remaining: u16,
hold_latched: bool,
consumed: bool,
}
@@ -109,6 +111,8 @@ impl Default for CommandSlot {
Self {
lifetime: 0,
age: 0,
hold_remaining: 0,
hold_latched: false,
consumed: false,
}
}
@@ -220,6 +224,8 @@ impl FighterInput {
pub fn clear_command(&mut self, command: FighterCommands) {
let slot = &mut self.command_slots[command.index()];
slot.lifetime = 0;
slot.hold_remaining = 0;
slot.hold_latched = false;
slot.consumed = true;
}
@@ -236,7 +242,10 @@ impl FighterInput {
let slot = &mut self.command_slots[command.index()];
slot.lifetime = lifetime;
slot.age = 0;
slot.hold_remaining = 0;
slot.hold_latched = false;
slot.consumed = false;
self.latch_if_hold_enabled(command);
}
pub fn has_command(&self, command: FighterCommands) -> bool {
@@ -252,8 +261,9 @@ impl FighterInput {
pub fn enable_hold_all(&mut self, max_frames: u16) {
self.hold_all = HoldLimit {
enabled: true,
max_frames,
max_frames: if max_frames == 0 { u16::MAX } else { max_frames },
};
self.latch_active_commands();
}
pub fn disable_hold_all(&mut self) {
@@ -263,14 +273,35 @@ impl FighterInput {
pub fn enable_hold(&mut self, command: FighterCommands, max_frames: u16) {
self.hold_limits[command.index()] = HoldLimit {
enabled: true,
max_frames,
max_frames: if max_frames == 0 { u16::MAX } else { max_frames },
};
if self.has_command(command) {
self.latch_if_hold_enabled(command);
}
}
pub fn disable_hold(&mut self, command: FighterCommands) {
self.hold_limits[command.index()] = HoldLimit::default();
}
fn latch_if_hold_enabled(&mut self, command: FighterCommands) {
let limit = self.hold_limit(command);
if !limit.enabled {
return;
}
let slot = &mut self.command_slots[command.index()];
slot.hold_latched = true;
slot.hold_remaining = limit.max_frames;
}
fn latch_active_commands(&mut self) {
for command in FighterCommands::iter() {
if self.has_command(command) {
self.latch_if_hold_enabled(command);
}
}
}
pub fn is_pressed(&self, action: DigitalAction) -> bool {
self.current_frame
.bindings_for(action)
@@ -317,15 +348,19 @@ impl FighterInput {
}
}
fn update_hold(&mut self, command: FighterCommands, source_held: bool) {
fn update_hold(&mut self, command: FighterCommands) {
let index = command.index();
let limit = self.hold_limit(command);
let slot = &mut self.command_slots[index];
slot.age = slot.age.saturating_add(1);
if slot.lifetime == 0 && source_held && !slot.consumed {
if limit.enabled && (limit.max_frames == 0 || slot.age < limit.max_frames) {
if slot.hold_latched && slot.hold_remaining != 0 {
if slot.hold_remaining != u16::MAX {
slot.hold_remaining = slot.hold_remaining.saturating_sub(1);
}
if slot.lifetime == 0 {
slot.lifetime = 1;
}
} else if slot.hold_latched {
slot.hold_latched = false;
}
}
}
@@ -463,17 +498,14 @@ pub fn postprocess_input(
// Extend only commands whose source is still held. Recognition above
// has already reset age and cleared the consumed marker for new edges.
let jump_held = input.current_frame.is_held(DigitalAction::Jump);
let shield_held = input.current_frame.is_held(DigitalAction::Shield);
let attack_held = input.current_frame.is_held(DigitalAction::Attack);
input.update_hold(FighterCommands::Jump, jump_held);
input.update_hold(FighterCommands::Shield, shield_held);
input.update_hold(FighterCommands::GuardHold, shield_now.count() >= 2);
input.update_hold(FighterCommands::Attack, attack_held);
input.update_hold(FighterCommands::SmashMoveUp, false);
input.update_hold(FighterCommands::SmashMoveDown, false);
input.update_hold(FighterCommands::SmashMoveLeft, false);
input.update_hold(FighterCommands::SmashMoveRight, false);
input.update_hold(FighterCommands::Jump);
input.update_hold(FighterCommands::Shield);
input.update_hold(FighterCommands::GuardHold);
input.update_hold(FighterCommands::Attack);
input.update_hold(FighterCommands::SmashMoveUp);
input.update_hold(FighterCommands::SmashMoveDown);
input.update_hold(FighterCommands::SmashMoveLeft);
input.update_hold(FighterCommands::SmashMoveRight);
}
}
@@ -511,12 +543,32 @@ mod tests {
let mut input = FighterInput::default();
input.enable_hold(FighterCommands::Shield, 10);
input.set_lifetime(FighterCommands::Shield, 1);
input.update_hold(FighterCommands::Shield, true);
input.update_hold(FighterCommands::Shield);
input.reduce_command_lifetime(FighterCommands::Shield);
input.update_hold(FighterCommands::Shield, true);
input.update_hold(FighterCommands::Shield);
assert!(input.has_command(FighterCommands::Shield));
input.consume_command(FighterCommands::Shield);
input.update_hold(FighterCommands::Shield, true);
input.update_hold(FighterCommands::Shield);
assert!(!input.has_command(FighterCommands::Shield));
}
#[test]
fn hold_buffer_keeps_released_attack_through_jump_squat() {
let mut input = FighterInput::default();
input.set_lifetime(FighterCommands::Attack, 3);
input.enable_hold_all(10);
// The command remains latched even though its physical source is no
// longer held. Five simulation ticks cover the test fighter's squat.
for _ in 0..5 {
for command in FighterCommands::iter() {
input.reduce_command_lifetime(command);
}
input.update_hold(FighterCommands::Attack);
}
assert!(input.has_command(FighterCommands::Attack));
input.disable_hold_all();
assert!(input.has_command(FighterCommands::Attack));
}
}