From fb0913510828b67928683abcb4c307c1cc5ac5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Rom=C3=A1n=20N=C3=BA=C3=B1ez?= Date: Wed, 7 Oct 2026 21:41:38 +0200 Subject: [PATCH] Implement up smash, dacus and further improve input system. --- src/fighter/animation.rs | 1 + src/fighter/attack/mod.rs | 2 +- src/fighter/attributes.rs | 2 + src/fighter/debug.rs | 2 + src/fighter/state/air.rs | 1 + src/fighter/state/dash.rs | 2 +- src/fighter/state/ground_attack.rs | 13 + src/fighter/state/jump.rs | 16 +- src/input/buffer.rs | 423 ++++++++++++++++++++++++----- src/input/gamepad.rs | 17 ++ src/input/keyboard.rs | 6 + src/input/map.rs | 3 + src/input/mod.rs | 25 +- src/menus/navigation.rs | 4 +- 14 files changed, 429 insertions(+), 88 deletions(-) diff --git a/src/fighter/animation.rs b/src/fighter/animation.rs index 4dfab3a..6020a95 100644 --- a/src/fighter/animation.rs +++ b/src/fighter/animation.rs @@ -101,4 +101,5 @@ pub enum AnimKind { AttackNeutralAir, AttackForwardAir, AttackDash, + AttackUpSmash, } diff --git a/src/fighter/attack/mod.rs b/src/fighter/attack/mod.rs index 1e03d9d..c17d3aa 100644 --- a/src/fighter/attack/mod.rs +++ b/src/fighter/attack/mod.rs @@ -236,7 +236,7 @@ impl AttackKind { AttackKind::UpAir => AnimKind::AttackUpTilt, AttackKind::DownTilt => AnimKind::AttackDownTilt, AttackKind::ForwardTilt => AnimKind::AttackForwardTilt, - AttackKind::UpSmash => AnimKind::AttackUpTilt, + AttackKind::UpSmash => AnimKind::AttackUpSmash, AttackKind::DownSmash => AnimKind::AttackUpTilt, AttackKind::ForwardSmash => AnimKind::AttackUpTilt, AttackKind::DashAttack => AnimKind::AttackDash, diff --git a/src/fighter/attributes.rs b/src/fighter/attributes.rs index 93dff36..4f22b4d 100644 --- a/src/fighter/attributes.rs +++ b/src/fighter/attributes.rs @@ -51,6 +51,8 @@ pub struct FighterAttributes { pub air_dodge_intangible_duration: u32, pub air_dodge_intangible_start: u32, + + pub dash_attack_cancel_window: u32, } impl FighterAttributes { diff --git a/src/fighter/debug.rs b/src/fighter/debug.rs index 2ed16a0..1b3ee62 100644 --- a/src/fighter/debug.rs +++ b/src/fighter/debug.rs @@ -149,6 +149,8 @@ pub fn fighter_debug( last_frame.movement.x, last_frame.movement.y )); }); + + ui.label(format!("Flick age Y {}", input.peek_movement_flick_dbg())); } FighterDebugRowKind::Position => { ui.label(format!("Position: {:?}", translation.0)); diff --git a/src/fighter/state/air.rs b/src/fighter/state/air.rs index bf853cd..0695dab 100644 --- a/src/fighter/state/air.rs +++ b/src/fighter/state/air.rs @@ -182,6 +182,7 @@ mod tests { air_dodge_decay: FGi32::ZERO, air_dodge_intangible_duration: 29, air_dodge_intangible_start: 4, + dash_attack_cancel_window: 3, } } diff --git a/src/fighter/state/dash.rs b/src/fighter/state/dash.rs index 523fae0..44db045 100644 --- a/src/fighter/state/dash.rs +++ b/src/fighter/state/dash.rs @@ -23,7 +23,7 @@ impl FighterStateImpl for DashState { fn check_interrupt(&self, ctx: &mut InterruptContext) -> Option { let attributes = ctx.fighter_attributes; - if ctx.get_current_animation_frame() <= 2 { + if ctx.get_current_animation_frame() <= 2 || ctx.input.get_last_frame().movement.x.signum() != self.direction.to_sign() { if let Some(state) = ground_attack::check_interrupt(ctx) { return Some(state); } diff --git a/src/fighter/state/ground_attack.rs b/src/fighter/state/ground_attack.rs index 854679c..d569fda 100644 --- a/src/fighter/state/ground_attack.rs +++ b/src/fighter/state/ground_attack.rs @@ -21,6 +21,19 @@ impl FighterStateImpl for GroundAttackState { const NAME: &'static str = "GroundAttack"; fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option { + // Allow interrupting dash attack only into an up/down smash + if let AttackKind::DashAttack = self.attack_kind { + if state_context.get_current_animation_frame() < state_context.fighter_attributes.dash_attack_cancel_window { + if let Some(state) = check_interrupt(state_context) { + if let FighterState::GroundAttack(state) = state { + if matches!(state.attack_kind, AttackKind::UpSmash) || matches!(state.attack_kind, AttackKind::DownSmash) { + return Some(FighterState::GroundAttack(state)); + } + } + } + } + } + if state_context.is_current_animation_finished() { Some(FighterState::Wait(WaitState)) } else { diff --git a/src/fighter/state/jump.rs b/src/fighter/state/jump.rs index 09bd57f..10a9eef 100644 --- a/src/fighter/state/jump.rs +++ b/src/fighter/state/jump.rs @@ -1,17 +1,10 @@ use crate::{ fighter::{ - animation::AnimKind, - state::{ - CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, - UpdateContext, - air::{self, AirborneStateCommon}, - air_dodge::AirDodgeState, - ground, + animation::AnimKind, state::{ + CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air::{self, AirborneStateCommon}, air_dodge::AirDodgeState, ground, ground_attack, }, - }, - input::{DigitalAction, FighterCommands}, - math::{int::FGi32, vec::FGVec2}, + }, input::{DigitalAction, FighterCommands}, math::{int::FGi32, vec::FGVec2}, }; #[derive(Debug, Hash, Clone, Copy)] pub enum JumpType { @@ -52,6 +45,9 @@ impl FighterStateImpl for JumpSquatState { const NAME: &'static str = "JumpSquat"; fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option { + if let Some(state) = ground_attack::check_interrupt(state_context) { + return Some(state); + } // Special handling for wavedash from jump squat if state_context.input.has_command(FighterCommands::Shield) { // If aiming sideways and in the deadzone, do a perfect wavedash diff --git a/src/input/buffer.rs b/src/input/buffer.rs index 6a3f606..8b6e183 100644 --- a/src/input/buffer.rs +++ b/src/input/buffer.rs @@ -9,6 +9,8 @@ use crate::fighter::FighterFacingDirection; use crate::fighter::attack::AttackKind; use crate::game_settings::GameSettings; use crate::input::{DigitalAction, FighterInputFrame}; +use crate::math::int::FGi32; +use crate::math::vec::FGVec2; use crate::netcode::GGRSCfg; use crate::player::Player; @@ -64,6 +66,34 @@ pub enum FighterAttackCommandType { TiltAttackRight, } +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum FlickDirection { + Up, + Down, + Left, + Right, +} + +impl FlickDirection { + const fn smash_move_command(self) -> FighterCommands { + match self { + Self::Up => FighterCommands::SmashMoveUp, + Self::Down => FighterCommands::SmashMoveDown, + Self::Left => FighterCommands::SmashMoveLeft, + Self::Right => FighterCommands::SmashMoveRight, + } + } + + const fn smash_attack_command(self) -> FighterAttackCommandType { + match self { + Self::Up => FighterAttackCommandType::SmashAttackUp, + Self::Down => FighterAttackCommandType::SmashAttackDown, + Self::Left => FighterAttackCommandType::SmashAttackLeft, + Self::Right => FighterAttackCommandType::SmashAttackRight, + } + } +} + impl FighterAttackCommandType { pub fn to_attack_kind_grounded(&self) -> AttackKind { match self { @@ -193,6 +223,149 @@ impl ButtonHistory { } } +#[derive(Clone, Copy, Debug, Hash)] +struct FlickAxisState { + age: u8, + eligible: bool, + consumed: bool, + command_emitted: bool, +} + +impl Default for FlickAxisState { + fn default() -> Self { + Self { + age: u8::MAX, + eligible: false, + consumed: false, + command_emitted: false, + } + } +} + +impl FlickAxisState { + fn update( + &mut self, + current: FGi32, + previous: FGi32, + reset_threshold: FGi32, + axis_threshold: FGi32, + frame_threshold: u8, + ) { + if current.abs() < reset_threshold { + *self = Self::default(); + return; + } + + if previous.abs() < reset_threshold || previous.signum() != current.signum() { + self.age = 1; + self.consumed = false; + self.command_emitted = false; + } else { + self.age = self.age.saturating_add(1); + } + + self.eligible = + !self.consumed && current.abs() >= axis_threshold && self.age <= frame_threshold; + } +} + +#[derive(Clone, Copy, Debug, Default, Hash)] +struct StickFlickState { + x: FlickAxisState, + y: FlickAxisState, +} + +impl StickFlickState { + fn update( + &mut self, + current: FGVec2, + previous: FGVec2, + reset_threshold: FGi32, + axis_threshold: FGi32, + frame_threshold: u8, + ) { + self.x.update( + current.x, + previous.x, + reset_threshold, + axis_threshold, + frame_threshold, + ); + self.y.update( + current.y, + previous.y, + reset_threshold, + axis_threshold, + frame_threshold, + ); + } + + fn peek(&self, value: FGVec2) -> Option { + self.resolve(value, self.x.eligible, self.y.eligible) + } + + /// Returns each axis that has not produced a passive command during this + /// gesture. This does not consume the flick, so an attack can still use + /// the remainder of its smash-input window. + fn next_commands(&mut self, value: FGVec2) -> [Option; 2] { + let use_x = self.x.eligible && !self.x.command_emitted; + let use_y = self.y.eligible && !self.y.command_emitted; + + if use_x { + self.x.command_emitted = true; + } + if use_y { + self.y.command_emitted = true; + } + + [ + use_x.then_some(if value.x.is_positive() { + FlickDirection::Right + } else { + FlickDirection::Left + }), + use_y.then_some(if value.y.is_positive() { + FlickDirection::Up + } else { + FlickDirection::Down + }), + ] + } + + fn consume(&mut self, value: FGVec2) -> Option { + let direction = self.peek(value)?; + // Simultaneous eligible axes are one diagonal gesture. Consuming only + // the selected axis would expose the other axis on the next frame. + if self.x.eligible { + self.x.consumed = true; + self.x.eligible = false; + } + if self.y.eligible { + self.y.consumed = true; + self.y.eligible = false; + } + Some(direction) + } + + fn resolve(&self, value: FGVec2, use_x: bool, use_y: bool) -> Option { + if use_x { + Some(if value.x.is_positive() { + FlickDirection::Right + } else { + FlickDirection::Left + }) + } else if use_y { + Some(if value.y.is_positive() { + FlickDirection::Up + } else { + FlickDirection::Down + }) + } else { + None + } + } +} + /// Per-fighter input state. Every field is deterministic rollback state. #[derive(Component, Clone, Copy, Hash)] #[require(Rollback)] @@ -203,8 +376,8 @@ pub struct FighterInput { button_history: [ButtonHistory; 3], prev_frame: FighterInputFrame, current_frame: FighterInputFrame, - frames_in_x_flick_deadzone: u8, - frames_in_y_flick_deadzone: u8, + movement_flick: StickFlickState, + directional_attack_flick: StickFlickState, attack_command: FighterAttackCommandType, } @@ -217,8 +390,8 @@ impl Default for FighterInput { button_history: [ButtonHistory::default(); 3], prev_frame: FighterInputFrame::default(), current_frame: FighterInputFrame::default(), - frames_in_x_flick_deadzone: 0xFE, - frames_in_y_flick_deadzone: 0xFE, + movement_flick: StickFlickState::default(), + directional_attack_flick: StickFlickState::default(), attack_command: FighterAttackCommandType::Neutral, } } @@ -368,8 +541,26 @@ impl FighterInput { self.current_frame.bindings_for(action).count() } - pub fn get_frames_in_smash_move_deadzone(&self) -> u8 { - self.frames_in_x_flick_deadzone + pub fn peek_movement_flick(&self) -> Option { + self.movement_flick.peek(self.current_frame.movement) + } + + pub fn peek_movement_flick_dbg(&self) -> u8 { + self.movement_flick.y.age + } + + pub fn consume_movement_flick(&mut self) -> Option { + self.movement_flick.consume(self.current_frame.movement) + } + + pub fn peek_directional_attack_flick(&self) -> Option { + self.directional_attack_flick + .peek(self.current_frame.directional_attack) + } + + pub fn consume_directional_attack_flick(&mut self) -> Option { + self.directional_attack_flick + .consume(self.current_frame.directional_attack) } pub fn set_attack_command_type(&mut self, command: FighterAttackCommandType) { self.attack_command = command; @@ -441,31 +632,30 @@ pub fn postprocess_input( ); } - let x_abs = input.current_frame.movement.x.abs(); - let y_abs = input.current_frame.movement.y.abs(); + let movement = input.current_frame.movement; + let previous_movement = input.prev_frame.movement; + let directional_attack = input.current_frame.directional_attack; + let previous_directional_attack = input.prev_frame.directional_attack; + let x_abs = movement.x.abs(); + let y_abs = movement.y.abs(); let reset_deadzone = game_settings.input_common.smash_input_reset_axis_threshold; let axis_threshold = game_settings.input_common.smash_input_axis_threshold; let frame_threshold = game_settings.input_common.smash_input_frame_threshold; - if x_abs < reset_deadzone { - input.frames_in_x_flick_deadzone = 0xFE; - } else if input.prev_frame.movement.x.abs() < reset_deadzone - || input.prev_frame.movement.x.signum() != input.current_frame.movement.x.signum() - { - input.frames_in_x_flick_deadzone = 1; - } else { - input.frames_in_x_flick_deadzone = input.frames_in_x_flick_deadzone.saturating_add(1); - } - - if y_abs < reset_deadzone { - input.frames_in_y_flick_deadzone = 0xFE; - } else if input.prev_frame.movement.y.abs() < reset_deadzone - || input.prev_frame.movement.y.signum() != input.current_frame.movement.y.signum() - { - input.frames_in_y_flick_deadzone = 1; - } else { - input.frames_in_y_flick_deadzone = input.frames_in_y_flick_deadzone.saturating_add(1); - } + input.movement_flick.update( + movement, + previous_movement, + reset_deadzone, + axis_threshold, + frame_threshold, + ); + input.directional_attack_flick.update( + directional_attack, + previous_directional_attack, + reset_deadzone, + axis_threshold, + frame_threshold, + ); let tap = game_settings.input_common.input_buffer_size; let jump_pressed = input.is_pressed(DigitalAction::Jump); @@ -485,62 +675,40 @@ pub fn postprocess_input( input.set_lifetime(FighterCommands::GuardHold, tap); } - let mut y_flick_detected = - y_abs >= axis_threshold && input.frames_in_y_flick_deadzone <= frame_threshold; - 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); - input.frames_in_y_flick_deadzone = 0xFF; - let command = if input.current_frame.movement.y > 0.0 { - FighterCommands::SmashMoveUp - } else { - FighterCommands::SmashMoveDown - }; + for flick in input + .movement_flick + .next_commands(movement) + .into_iter() + .flatten() + { + let command = flick.smash_move_command(); + match flick { + FlickDirection::Up | FlickDirection::Down => { + input.clear_command(FighterCommands::SmashMoveUp); + input.clear_command(FighterCommands::SmashMoveDown); + } + FlickDirection::Left | FlickDirection::Right => { + input.clear_command(FighterCommands::SmashMoveLeft); + input.clear_command(FighterCommands::SmashMoveRight); + } + } input.set_lifetime(command, tap); - } - if x_flick_detected { - input.clear_command(FighterCommands::SmashMoveLeft); - input.clear_command(FighterCommands::SmashMoveRight); - input.frames_in_x_flick_deadzone = 0xFF; - let command = if input.current_frame.movement.x > 0.0 { - FighterCommands::SmashMoveRight - } else { - FighterCommands::SmashMoveLeft - }; - input.set_lifetime(command, tap); - } - if x_flick_detected && y_flick_detected { - x_flick_detected = x_abs > y_abs; - y_flick_detected = !x_flick_detected; - } - - if y_flick_detected && input.current_frame.movement.y > 0.0 { - input.set_lifetime(FighterCommands::TapJump, tap); + if flick == FlickDirection::Up { + input.set_lifetime(FighterCommands::TapJump, tap); + } } if attack_pressed { - let attack_type = if y_flick_detected { - if input.current_frame.movement.y > 0.0 { - FighterAttackCommandType::SmashAttackUp - } else { - FighterAttackCommandType::SmashAttackDown - } - } else if x_flick_detected { - if input.current_frame.movement.x > 0.0 { - FighterAttackCommandType::SmashAttackRight - } else { - FighterAttackCommandType::SmashAttackLeft - } + let attack_type = if let Some(flick) = input.consume_movement_flick() { + flick.smash_attack_command() } else if x_abs > y_abs { - if input.current_frame.movement.x.is_positive() { + if movement.x.is_positive() { FighterAttackCommandType::TiltAttackRight } else { FighterAttackCommandType::TiltAttackLeft } } else if y_abs > x_abs { - if input.current_frame.movement.y.is_positive() { + if movement.y.is_positive() { FighterAttackCommandType::TiltAttackUp } else { FighterAttackCommandType::TiltAttackDown @@ -548,10 +716,17 @@ pub fn postprocess_input( } else { FighterAttackCommandType::Neutral }; + + dbg!(input.movement_flick.y, attack_type); input.set_lifetime(FighterCommands::Attack, tap); input.set_attack_command_type(attack_type); } + if let Some(flick) = input.consume_directional_attack_flick() { + input.set_lifetime(FighterCommands::Attack, tap); + input.set_attack_command_type(flick.smash_attack_command()); + } + for command in FighterCommands::iter() { input.update_hold(command); } @@ -563,6 +738,20 @@ mod tests { use super::*; use crate::input::ActionBindingMask; + const RESET_THRESHOLD: FGi32 = FGi32::lit("0.2"); + const AXIS_THRESHOLD: FGi32 = FGi32::lit("0.8"); + const FRAME_THRESHOLD: u8 = 3; + + fn update_flick(state: &mut StickFlickState, current: FGVec2, previous: FGVec2) { + state.update( + current, + previous, + RESET_THRESHOLD, + AXIS_THRESHOLD, + FRAME_THRESHOLD, + ); + } + fn advance_command(input: &mut FighterInput, command: FighterCommands) { input.reduce_command_lifetime(command); input.update_hold(command); @@ -583,6 +772,94 @@ mod tests { assert!(second_binding_pressed(current, previous)); } + #[test] + fn flick_remains_available_for_the_configured_window() { + let mut flick = StickFlickState::default(); + let right = FGVec2::lit("1", "0"); + + update_flick(&mut flick, right, FGVec2::ZERO); + assert_eq!(flick.peek(right), Some(FlickDirection::Right)); + + for _ in 2..=FRAME_THRESHOLD { + update_flick(&mut flick, right, right); + assert_eq!(flick.peek(right), Some(FlickDirection::Right)); + } + + update_flick(&mut flick, right, right); + assert_eq!(flick.peek(right), None); + } + + #[test] + fn passive_command_does_not_consume_flick() { + let mut flick = StickFlickState::default(); + let right = FGVec2::lit("1", "0"); + + update_flick(&mut flick, right, FGVec2::ZERO); + assert_eq!( + flick.next_commands(right), + [Some(FlickDirection::Right), None] + ); + assert_eq!(flick.next_commands(right), [None, None]); + assert_eq!(flick.peek(right), Some(FlickDirection::Right)); + + update_flick(&mut flick, right, right); + assert_eq!(flick.consume(right), Some(FlickDirection::Right)); + assert_eq!(flick.peek(right), None); + } + + #[test] + fn consumed_flick_rearms_after_returning_below_reset_threshold() { + let mut flick = StickFlickState::default(); + let right = FGVec2::lit("1", "0"); + + update_flick(&mut flick, right, FGVec2::ZERO); + assert_eq!(flick.consume(right), Some(FlickDirection::Right)); + update_flick(&mut flick, right, right); + assert_eq!(flick.peek(right), None); + + update_flick(&mut flick, FGVec2::ZERO, right); + update_flick(&mut flick, right, FGVec2::ZERO); + assert_eq!(flick.peek(right), Some(FlickDirection::Right)); + } + + #[test] + fn flick_can_reach_activation_threshold_after_entering_reset_deadzone() { + let mut flick = StickFlickState::default(); + let partial = FGVec2::lit("0.4", "0"); + let right = FGVec2::lit("1", "0"); + + update_flick(&mut flick, partial, FGVec2::ZERO); + assert_eq!(flick.peek(partial), None); + update_flick(&mut flick, right, partial); + assert_eq!(flick.peek(right), Some(FlickDirection::Right)); + } + + #[test] + fn diagonal_flick_prioritizes_horizontal_and_consumes_the_gesture() { + let mut flick = StickFlickState::default(); + let diagonal = FGVec2::lit("0.9", "1"); + + update_flick(&mut flick, diagonal, FGVec2::ZERO); + assert_eq!( + flick.next_commands(diagonal), + [Some(FlickDirection::Right), Some(FlickDirection::Up)] + ); + assert_eq!(flick.consume(diagonal), Some(FlickDirection::Right)); + assert_eq!(flick.peek(diagonal), None); + } + + #[test] + fn direction_reversal_starts_a_new_flick() { + let mut flick = StickFlickState::default(); + let right = FGVec2::lit("1", "0"); + let left = FGVec2::lit("-1", "0"); + + update_flick(&mut flick, right, FGVec2::ZERO); + assert_eq!(flick.consume(right), Some(FlickDirection::Right)); + update_flick(&mut flick, left, right); + assert_eq!(flick.peek(left), Some(FlickDirection::Left)); + } + #[test] fn command_can_be_consumed_without_erasing_raw_input() { let mut input = FighterInput::default(); diff --git a/src/input/gamepad.rs b/src/input/gamepad.rs index 6113f77..99e529a 100644 --- a/src/input/gamepad.rs +++ b/src/input/gamepad.rs @@ -9,6 +9,7 @@ use crate::math::vec::FGVec2; pub fn sample_gamepad(pad: &Gamepad, input_map: &BaseInputMap) -> FighterInputFrame { let mut input_frame = FighterInputFrame::default(); let mut movement = Vec2::ZERO; + let mut directional_attack = Vec2::ZERO; for gamepad_element in &input_map.gamepad { match &gamepad_element.binding { @@ -34,12 +35,24 @@ pub fn sample_gamepad(pad: &Gamepad, input_map: &BaseInputMap) -> FighterInputFr movement.y += sign as f32 } } + InputMapAction::AttackXDir(sign) => { + if pressed { + directional_attack.x += sign as f32; + } + } + InputMapAction::AttackYDir(sign) => { + if pressed { + directional_attack.y += sign as f32; + } + } } } // gamepad does not have a separate axis for left and right, so sign here should be 1 GamepadBinding::Axis(axis, sign) => { let value = pad.get(*axis).unwrap_or(0.0) * (*sign as f32); match gamepad_element.action { + InputMapAction::AttackXDir(_) => directional_attack.x += value, + InputMapAction::AttackYDir(_) => directional_attack.y += value, InputMapAction::MovementXDir(_) => movement.x += value, InputMapAction::MovementYDir(_) => movement.y += value, _ => {} @@ -49,6 +62,10 @@ pub fn sample_gamepad(pad: &Gamepad, input_map: &BaseInputMap) -> FighterInputFr } input_frame.movement = FGVec2::new(FGi32::from_num(movement.x), FGi32::from_num(movement.y)); + input_frame.directional_attack = FGVec2::new( + FGi32::from_num(directional_attack.x), + FGi32::from_num(directional_attack.y), + ); input_frame } diff --git a/src/input/keyboard.rs b/src/input/keyboard.rs index 21ebce4..0f96b1b 100644 --- a/src/input/keyboard.rs +++ b/src/input/keyboard.rs @@ -39,6 +39,12 @@ pub fn sample_keyboard(key: &ButtonInput, input_map: &BaseInputMap) -> ); } } + InputMapAction::AttackXDir(sign) => { + input_frame.directional_attack.x += (sign) * action_strength; + } + InputMapAction::AttackYDir(sign) => { + input_frame.directional_attack.y += (sign) * action_strength; + } } } diff --git a/src/input/map.rs b/src/input/map.rs index afecc89..b6bf23e 100644 --- a/src/input/map.rs +++ b/src/input/map.rs @@ -12,6 +12,8 @@ pub enum InputActionState { #[derive(Debug)] pub enum InputMapAction { + AttackXDir(i32), + AttackYDir(i32), MovementXDir(i32), MovementYDir(i32), Jump, @@ -26,6 +28,7 @@ impl InputMapAction { Self::Shield => Some(crate::input::DigitalAction::Shield), Self::Attack => Some(crate::input::DigitalAction::Attack), Self::MovementXDir(_) | Self::MovementYDir(_) => None, + Self::AttackYDir(_) | Self::AttackXDir(_) => None, } } } diff --git a/src/input/mod.rs b/src/input/mod.rs index 1878bd8..0859c48 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -14,7 +14,7 @@ pub mod gamepad; pub mod keyboard; pub mod map; -pub use buffer::{FighterCommands, FighterInput}; +pub use buffer::{FighterCommands, FighterInput, FlickDirection}; pub use frame::{ActionBindingMask, DigitalAction, DigitalActionBindings, FighterInputFrame}; pub use map::{ BaseInputMap, GamepadBinding, GamepadInputMapElement, InputActionState, InputMapAction, @@ -136,6 +136,11 @@ impl Plugin for FighterInputPlugin { action: InputMapAction::Jump, binding_slot: 0, }, + GamepadInputMapElement { + binding: GamepadBinding::Button(GamepadButton::West), + action: InputMapAction::Jump, + binding_slot: 1, + }, GamepadInputMapElement { binding: GamepadBinding::Button(GamepadButton::LeftTrigger2), action: InputMapAction::Shield, @@ -152,6 +157,17 @@ impl Plugin for FighterInputPlugin { action: InputMapAction::Attack, binding_slot: 0, }, + //C-stick + GamepadInputMapElement { + binding: GamepadBinding::Axis(GamepadAxis::RightStickX, 1), + action: InputMapAction::AttackXDir(1), + binding_slot: 0, + }, + GamepadInputMapElement { + binding: GamepadBinding::Axis(GamepadAxis::RightStickY, 1), + action: InputMapAction::AttackYDir(1), + binding_slot: 0, + }, ], }) .insert_resource(MenuInputMap { @@ -259,6 +275,13 @@ fn preprocess_input_frame(frame: &mut FighterInputFrame, game_settings: &GameSet if frame.movement.y.abs() < game_settings.input_common.stick_deadzone { frame.movement.y = FGi32::ZERO; } + + if frame.directional_attack.x.abs() < game_settings.input_common.stick_deadzone { + frame.directional_attack.x = FGi32::ZERO; + } + if frame.directional_attack.y.abs() < game_settings.input_common.stick_deadzone { + frame.directional_attack.y = FGi32::ZERO; + } } fn read_local_inputs( diff --git a/src/menus/navigation.rs b/src/menus/navigation.rs index 70739f2..9401c57 100644 --- a/src/menus/navigation.rs +++ b/src/menus/navigation.rs @@ -153,8 +153,8 @@ pub fn navigate( Ok(_entity) => { // Successfully navigated } - Err(e) => { - error!("Navigation error: {}", e) + Err(_e) => { + //error!("Navigation error: {}", e) } } }