diff --git a/src/fighter/animation.rs b/src/fighter/animation.rs index c555643..0d6f87f 100644 --- a/src/fighter/animation.rs +++ b/src/fighter/animation.rs @@ -89,4 +89,5 @@ pub enum AnimKind { JumpForward, JumpBack, Landing, + AirDodge, } diff --git a/src/fighter/attributes.rs b/src/fighter/attributes.rs index 188af05..bb02aea 100644 --- a/src/fighter/attributes.rs +++ b/src/fighter/attributes.rs @@ -35,6 +35,10 @@ pub struct FighterAttributes { pub terminal_velocity: FGi32, pub gravity: FGi32, pub air_friction: FGi32, + + pub air_dodge_duration: u32, + pub air_dodge_velocity: FGi32, + pub air_dodge_decay: FGi32 } impl FighterAttributes { diff --git a/src/fighter/state/air_dodge.rs b/src/fighter/state/air_dodge.rs new file mode 100644 index 0000000..d69a28d --- /dev/null +++ b/src/fighter/state/air_dodge.rs @@ -0,0 +1,70 @@ +use std::time::Duration; + +use bevy::prelude::*; + +use crate::{fighter::{ + animation::AnimKind, collision, state::{ + FighterState, FighterStateContext, FighterStateImpl, air, dash, fall::FallState, ground::{self, GroundedMotionResult, GroundedStateCommon}, land::LandingState, wait::WaitState, walk, + }, + }, input}; + +#[derive(Debug, Clone, Hash)] +pub struct AirDodgeState { + pub duration_counter: u32, +} + +impl FighterStateImpl for AirDodgeState { + const NAME: &'static str = "AirDodge"; + + fn check_interrupt(&self, state_context: &FighterStateContext) -> Option { + if self.duration_counter >= state_context.fighter_attribs.air_dodge_duration { + Some(FighterState::Fall(FallState)) + } else { + None + } + } + + fn update(&mut self, state_context: &mut FighterStateContext) { + self.duration_counter += 1; + + state_context.velocity.0 *= state_context.fighter_attribs.air_dodge_decay; + + air::apply_air_motion( + state_context.velocity, + state_context.translation, + state_context.prev_translation, + ); + } + + fn check_collision_interrupt( + &mut self, + state_context: &mut FighterStateContext, + ) -> Option { + if let Some(res) = collision::air_collide_with_stage(state_context) { + // Adjust translation + state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point(); + let grounded_common = GroundedStateCommon { + current_line_id: res.line_id, + }; + Some(FighterState::Land(LandingState { grounded_common, duration_counter: 0 })) + } else { + None + } + } + + fn on_enter(&mut self, state_context: &mut FighterStateContext) { + state_context.animation_transitions.play( + state_context.animation_player, + state_context.animations.clips[&AnimKind::AirDodge], + Duration::ZERO, + ); + + state_context.velocity.0 = state_context.input.get_last_frame().movement.normalize_or_zero() * state_context.fighter_attribs.air_dodge_velocity; + } +} + +pub fn check_input(state_context: &FighterStateContext) -> Option { + state_context.input.has_command(input::FighterCommands::Shield).then(|| + FighterState::AirDodge(AirDodgeState { duration_counter: 0 }) + ) +} \ No newline at end of file diff --git a/src/fighter/state/jump.rs b/src/fighter/state/jump.rs index 791c684..ad4d1c0 100644 --- a/src/fighter/state/jump.rs +++ b/src/fighter/state/jump.rs @@ -5,7 +5,7 @@ use bevy::prelude::*; use crate::{ fighter::{ animation::AnimKind, collision, state::{ - FighterState, FighterStateContext, FighterStateImpl, air, fall::FallState, ground::{self, GroundedMotionResult, GroundedStateCommon}, land::LandingState, + FighterState, FighterStateContext, FighterStateImpl, air, air_dodge, fall::FallState, ground::{self, GroundedMotionResult, GroundedStateCommon}, land::LandingState, }, }, input::FighterCommands, }; @@ -98,8 +98,8 @@ impl FighterStateImpl for JumpSquatState { impl FighterStateImpl for JumpState { const NAME: &'static str = "Jump"; - fn check_interrupt(&self, _state_context: &FighterStateContext) -> Option { - None + fn check_interrupt(&self, state_context: &FighterStateContext) -> Option { + air_dodge::check_input(state_context) } fn update(&mut self, state_context: &mut FighterStateContext) { diff --git a/src/fighter/state/mod.rs b/src/fighter/state/mod.rs index a7f33a8..9ee1cff 100644 --- a/src/fighter/state/mod.rs +++ b/src/fighter/state/mod.rs @@ -24,6 +24,7 @@ pub mod run; pub mod wait; pub mod walk; pub mod land; +pub mod air_dodge; macro_rules! fighter_states { ($($variant:ident => $ty:ty),* $(,)?) => { @@ -95,7 +96,8 @@ fighter_states! { Fall => fall::FallState, JumpSquat => jump::JumpSquatState, Jump => jump::JumpState, - Land => land::LandingState + Land => land::LandingState, + AirDodge => air_dodge::AirDodgeState } #[derive(QueryData)] diff --git a/src/input/buffer.rs b/src/input/buffer.rs index 00baf39..c1a40fa 100644 --- a/src/input/buffer.rs +++ b/src/input/buffer.rs @@ -19,6 +19,7 @@ pub enum FighterCommands { SmashMoveLeft, SmashMoveRight, Jump, + Shield, } /// Frames of life remaining for each buffered command. Indexed by @@ -28,6 +29,7 @@ pub struct FighterCommandLifetimes { smash_move_left: u8, smash_move_right: u8, jump: u8, + shield: u8, } impl Index for FighterCommandLifetimes { @@ -38,6 +40,7 @@ impl Index for FighterCommandLifetimes { FighterCommands::SmashMoveLeft => &self.smash_move_left, FighterCommands::SmashMoveRight => &self.smash_move_right, FighterCommands::Jump => &self.jump, + FighterCommands::Shield => &self.shield, } } } @@ -48,6 +51,7 @@ impl IndexMut for FighterCommandLifetimes { FighterCommands::SmashMoveLeft => &mut self.smash_move_left, FighterCommands::SmashMoveRight => &mut self.smash_move_right, FighterCommands::Jump => &mut self.jump, + FighterCommands::Shield => &mut self.shield, } } } @@ -133,6 +137,13 @@ pub fn postprocess_input( ); } + if !input.prev_frame.shield && input.current_frame.shield { + input.set_lifetime( + FighterCommands::Shield, + game_settings.input_common.input_buffer_size, + ); + } + if x_abs >= axis_threshold && input.frames_in_smash_move_deadzone <= frame_threshold { input.clear_command(FighterCommands::SmashMoveLeft); input.clear_command(FighterCommands::SmashMoveRight); diff --git a/src/input/frame.rs b/src/input/frame.rs index 4493dbb..c556ce0 100644 --- a/src/input/frame.rs +++ b/src/input/frame.rs @@ -9,4 +9,5 @@ pub struct FighterInputFrame { pub movement: FGVec2, pub directional_attack: FGVec2, pub jump: bool, + pub shield: bool, } diff --git a/src/input/keyboard.rs b/src/input/keyboard.rs index d8220ec..58acfdc 100644 --- a/src/input/keyboard.rs +++ b/src/input/keyboard.rs @@ -45,6 +45,7 @@ pub fn preprocess_keyboard_input( input_frame.movement.y += (sign) * action_strength } InputMapAction::Jump => input_frame.jump = action_strength != FGi32::ZERO, + InputMapAction::Shield => input_frame.shield = action_strength != FGi32::ZERO, } } diff --git a/src/input/map.rs b/src/input/map.rs index 2e521fd..192867b 100644 --- a/src/input/map.rs +++ b/src/input/map.rs @@ -19,6 +19,7 @@ pub enum InputMapAction { MovementXDir(i32), MovementYDir(i32), Jump, + Shield, } #[derive(Debug)] diff --git a/src/input/mod.rs b/src/input/mod.rs index d2501aa..7c12207 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -39,11 +39,26 @@ impl Plugin for FighterInputPlugin { key: KeyCode::KeyA, action: InputMapAction::MovementXDir(-1), }, + // up + KeyboardInputMapElement { + key: KeyCode::KeyW, + action: InputMapAction::MovementYDir(1), + }, + // down + KeyboardInputMapElement { + key: KeyCode::KeyS, + action: InputMapAction::MovementYDir(-1), + }, // jump KeyboardInputMapElement { key: KeyCode::Space, action: InputMapAction::Jump, }, + // shield/airdodge + KeyboardInputMapElement { + key: KeyCode::ShiftLeft, + action: InputMapAction::Shield, + }, ], }) .add_systems(ReadInputs, keyboard::preprocess_keyboard_input) diff --git a/src/lib.rs b/src/lib.rs index dbe7803..6e85fae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,13 @@ pub enum AppState { MatchLoadFailed, } +pub fn show_gamepads(q: Query<(&Name, &Gamepad)>) { + info!("GP:"); + for (name, gp) in q { + info!("Gamepad detected: {}", name); + } +} + /// Your game's Bevy plugin. The editor finds it by this name (override /// with `plugin = "..."` in jackdaw.toml) and runs it on Play; the /// standalone binary adds it in `main.rs`. @@ -99,6 +106,7 @@ impl Plugin for GamePlugin { .on_failure_continue_to_state(AppState::CommonAssetLoadFailed) .load_collection::(), ) + .add_systems(Startup, show_gamepads) .add_systems( OnEnter(AppState::PrepareFighterManifests), fighter::manifest::prepare_fighter_manifests, diff --git a/src/math/vec.rs b/src/math/vec.rs index 84d483a..3cef389 100644 --- a/src/math/vec.rs +++ b/src/math/vec.rs @@ -2,7 +2,7 @@ use bevy::math::Vec2; use bevy::prelude::*; use fixed::types::I32F32; use serde::{Deserialize, Serialize}; -use std::ops::{Add, AddAssign, Div, Mul, Sub}; +use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub}; use super::int::{FGWide, FGi32}; @@ -221,6 +221,24 @@ impl AddAssign for FGVec2 { } } +impl MulAssign for FGVec2 { + fn mul_assign(&mut self, rhs: Self) { + *self = Self { + x: self.x * rhs.x, + y: self.y * rhs.y, + } + } +} + +impl MulAssign for FGVec2 { + fn mul_assign(&mut self, rhs: FGi32) { + *self = Self { + x: self.x * rhs, + y: self.y * rhs, + } + } +} + #[cfg(test)] mod is_normalized_tests { use super::*;