Rework input buffer to match HDR closer #79
@@ -45,6 +45,9 @@ impl FighterStateImpl for AirAttackState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
||||||
|
state_context
|
||||||
|
.input
|
||||||
|
.consume_command(crate::input::FighterCommands::Attack);
|
||||||
state_context.play_animation(self.attack_kind.get_animation(), false);
|
state_context.play_animation(self.attack_kind.get_animation(), false);
|
||||||
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
|
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
|
||||||
state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack {
|
state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack {
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ impl FighterStateImpl for GroundAttackState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
||||||
|
state_context
|
||||||
|
.input
|
||||||
|
.consume_command(crate::input::FighterCommands::Attack);
|
||||||
state_context.play_animation(self.attack_kind.get_animation(), false);
|
state_context.play_animation(self.attack_kind.get_animation(), false);
|
||||||
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
|
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
|
||||||
state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack {
|
state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack {
|
||||||
|
|||||||
+72
-20
@@ -101,6 +101,8 @@ impl FighterAttackCommandType {
|
|||||||
struct CommandSlot {
|
struct CommandSlot {
|
||||||
lifetime: u8,
|
lifetime: u8,
|
||||||
age: u16,
|
age: u16,
|
||||||
|
hold_remaining: u16,
|
||||||
|
hold_latched: bool,
|
||||||
consumed: bool,
|
consumed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +111,8 @@ impl Default for CommandSlot {
|
|||||||
Self {
|
Self {
|
||||||
lifetime: 0,
|
lifetime: 0,
|
||||||
age: 0,
|
age: 0,
|
||||||
|
hold_remaining: 0,
|
||||||
|
hold_latched: false,
|
||||||
consumed: false,
|
consumed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,6 +224,8 @@ impl FighterInput {
|
|||||||
pub fn clear_command(&mut self, command: FighterCommands) {
|
pub fn clear_command(&mut self, command: FighterCommands) {
|
||||||
let slot = &mut self.command_slots[command.index()];
|
let slot = &mut self.command_slots[command.index()];
|
||||||
slot.lifetime = 0;
|
slot.lifetime = 0;
|
||||||
|
slot.hold_remaining = 0;
|
||||||
|
slot.hold_latched = false;
|
||||||
slot.consumed = true;
|
slot.consumed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +242,10 @@ impl FighterInput {
|
|||||||
let slot = &mut self.command_slots[command.index()];
|
let slot = &mut self.command_slots[command.index()];
|
||||||
slot.lifetime = lifetime;
|
slot.lifetime = lifetime;
|
||||||
slot.age = 0;
|
slot.age = 0;
|
||||||
|
slot.hold_remaining = 0;
|
||||||
|
slot.hold_latched = false;
|
||||||
slot.consumed = false;
|
slot.consumed = false;
|
||||||
|
self.latch_if_hold_enabled(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_command(&self, command: FighterCommands) -> bool {
|
pub fn has_command(&self, command: FighterCommands) -> bool {
|
||||||
@@ -252,8 +261,9 @@ impl FighterInput {
|
|||||||
pub fn enable_hold_all(&mut self, max_frames: u16) {
|
pub fn enable_hold_all(&mut self, max_frames: u16) {
|
||||||
self.hold_all = HoldLimit {
|
self.hold_all = HoldLimit {
|
||||||
enabled: true,
|
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) {
|
pub fn disable_hold_all(&mut self) {
|
||||||
@@ -263,14 +273,35 @@ impl FighterInput {
|
|||||||
pub fn enable_hold(&mut self, command: FighterCommands, max_frames: u16) {
|
pub fn enable_hold(&mut self, command: FighterCommands, max_frames: u16) {
|
||||||
self.hold_limits[command.index()] = HoldLimit {
|
self.hold_limits[command.index()] = HoldLimit {
|
||||||
enabled: true,
|
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) {
|
pub fn disable_hold(&mut self, command: FighterCommands) {
|
||||||
self.hold_limits[command.index()] = HoldLimit::default();
|
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 {
|
pub fn is_pressed(&self, action: DigitalAction) -> bool {
|
||||||
self.current_frame
|
self.current_frame
|
||||||
.bindings_for(action)
|
.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 index = command.index();
|
||||||
let limit = self.hold_limit(command);
|
|
||||||
let slot = &mut self.command_slots[index];
|
let slot = &mut self.command_slots[index];
|
||||||
slot.age = slot.age.saturating_add(1);
|
slot.age = slot.age.saturating_add(1);
|
||||||
if slot.lifetime == 0 && source_held && !slot.consumed {
|
if slot.hold_latched && slot.hold_remaining != 0 {
|
||||||
if limit.enabled && (limit.max_frames == 0 || slot.age < limit.max_frames) {
|
if slot.hold_remaining != u16::MAX {
|
||||||
|
slot.hold_remaining = slot.hold_remaining.saturating_sub(1);
|
||||||
|
}
|
||||||
|
if slot.lifetime == 0 {
|
||||||
slot.lifetime = 1;
|
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
|
// Extend only commands whose source is still held. Recognition above
|
||||||
// has already reset age and cleared the consumed marker for new edges.
|
// has already reset age and cleared the consumed marker for new edges.
|
||||||
let jump_held = input.current_frame.is_held(DigitalAction::Jump);
|
input.update_hold(FighterCommands::Jump);
|
||||||
let shield_held = input.current_frame.is_held(DigitalAction::Shield);
|
input.update_hold(FighterCommands::Shield);
|
||||||
let attack_held = input.current_frame.is_held(DigitalAction::Attack);
|
input.update_hold(FighterCommands::GuardHold);
|
||||||
input.update_hold(FighterCommands::Jump, jump_held);
|
input.update_hold(FighterCommands::Attack);
|
||||||
input.update_hold(FighterCommands::Shield, shield_held);
|
input.update_hold(FighterCommands::SmashMoveUp);
|
||||||
input.update_hold(FighterCommands::GuardHold, shield_now.count() >= 2);
|
input.update_hold(FighterCommands::SmashMoveDown);
|
||||||
input.update_hold(FighterCommands::Attack, attack_held);
|
input.update_hold(FighterCommands::SmashMoveLeft);
|
||||||
input.update_hold(FighterCommands::SmashMoveUp, false);
|
input.update_hold(FighterCommands::SmashMoveRight);
|
||||||
input.update_hold(FighterCommands::SmashMoveDown, false);
|
|
||||||
input.update_hold(FighterCommands::SmashMoveLeft, false);
|
|
||||||
input.update_hold(FighterCommands::SmashMoveRight, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,12 +543,32 @@ mod tests {
|
|||||||
let mut input = FighterInput::default();
|
let mut input = FighterInput::default();
|
||||||
input.enable_hold(FighterCommands::Shield, 10);
|
input.enable_hold(FighterCommands::Shield, 10);
|
||||||
input.set_lifetime(FighterCommands::Shield, 1);
|
input.set_lifetime(FighterCommands::Shield, 1);
|
||||||
input.update_hold(FighterCommands::Shield, true);
|
input.update_hold(FighterCommands::Shield);
|
||||||
input.reduce_command_lifetime(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));
|
assert!(input.has_command(FighterCommands::Shield));
|
||||||
input.consume_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));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user