diff --git a/Cargo.lock b/Cargo.lock index 738ecfa..1b4ce07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "ace-of-the-heavens" -version = "0.2.4" +version = "0.2.5" dependencies = [ "bevy", "bevy_asset_loader", diff --git a/Cargo.toml b/Cargo.toml index 1a69c29..2211168 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ace-of-the-heavens" -version = "0.2.4" +version = "0.2.5" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/input.rs b/src/input/gamepad.rs similarity index 54% rename from src/input.rs rename to src/input/gamepad.rs index 5634718..fe77207 100644 --- a/src/input.rs +++ b/src/input/gamepad.rs @@ -1,69 +1,27 @@ -use bevy::{app::AppExit, input::gamepad::*, prelude::*}; +use std::time::Duration; + +use bevy::{input::gamepad::*, prelude::*}; use bevy_ggrs::*; +use super::*; use crate::player::Player; -const INPUT_FORWARD: u8 = 1 << 0; -const INPUT_BACKWARD: u8 = 1 << 1; -const INPUT_LEFT: u8 = 1 << 2; -const INPUT_RIGHT: u8 = 1 << 3; -const INPUT_FIRE: u8 = 1 << 4; -const INPUT_DODGE: u8 = 1 << 5; -const INPUT_ROCKET: u8 = 1 << 6; -const INPUT_REMATCH: u8 = 1 << 7; - -pub fn input( - In(local_handle): In, - keys: Res>, - mouse_buttons: Res>, - gamepads: Res, - button_inputs: Res>, - button_axes: Res>, - axes: Res>, - players: Query<(&Transform, &Player)>, -) -> u8 { - let mut input = 0u8; +#[derive(Resource, Default, Reflect)] +pub struct GamepadRumble { + just_added: bool, + intensity: f32, + duration: f32, +} - if keys.any_pressed([KeyCode::Up, KeyCode::W, KeyCode::K]) { - input |= INPUT_FORWARD; - } - if keys.any_pressed([KeyCode::Down, KeyCode::S, KeyCode::J]) { - input |= INPUT_BACKWARD; - } - if keys.any_pressed([KeyCode::Left, KeyCode::A]) { - input |= INPUT_LEFT; - } - if keys.any_pressed([KeyCode::Right, KeyCode::D, KeyCode::F]) { - input |= INPUT_RIGHT; - } - if keys.any_pressed([KeyCode::Space, KeyCode::Return]) { - input |= INPUT_FIRE; - } - if keys.any_pressed([KeyCode::E, KeyCode::L]) || mouse_buttons.pressed(MouseButton::Right) { - input |= INPUT_DODGE; - } - if keys.any_pressed([KeyCode::Q, KeyCode::Semicolon]) - || mouse_buttons.pressed(MouseButton::Left) - { - input |= INPUT_ROCKET; - } - if keys.pressed(KeyCode::R) { - input |= INPUT_REMATCH; +impl GamepadRumble { + pub fn add_rumble(&mut self, intensity: f32, duration: f32) { + self.just_added = true; + self.intensity = intensity.clamp(0.0, 1.0); + self.duration = duration; } - - let controller_input = get_gamepad_input( - &gamepads, - &button_inputs, - &button_axes, - &axes, - &players, - local_handle, - ); - input |= controller_input; - input } -fn get_gamepad_input( +pub fn get_gamepad_input( gamepads: &Res, button_inputs: &Res>, button_axes: &Res>, @@ -146,61 +104,6 @@ fn get_gamepad_input( input } -pub fn steer_direction(input: u8) -> f32 { - let mut steer_direction: f32 = 0.0; - if input & INPUT_LEFT != 0 { - steer_direction += 1.0; - } - if input & INPUT_RIGHT != 0 { - steer_direction -= 1.0; - } - steer_direction -} - -pub fn accelerate_direction(input: u8) -> f32 { - let mut accelerate_direction: f32 = 0.0; - if input & INPUT_FORWARD != 0 { - accelerate_direction += 1.0; - } - if input & INPUT_BACKWARD != 0 { - accelerate_direction -= 1.0; - } - accelerate_direction -} - -pub fn fire(input: u8) -> bool { - input & INPUT_FIRE != 0 -} - -pub fn dodge(input: u8) -> bool { - input & INPUT_DODGE != 0 -} - -pub fn rocket(input: u8) -> bool { - input & INPUT_ROCKET != 0 -} - -pub fn rematch(input: u8) -> bool { - input & INPUT_REMATCH != 0 -} - -pub fn quit( - mut exit: EventWriter, - keys: Res>, - gamepads: Res, - button_inputs: Res>, -) { - let mut pressed = keys.pressed(KeyCode::Q); - for gamepad in gamepads.iter() { - if button_inputs.pressed(GamepadButton::new(gamepad, GamepadButtonType::East)) { - pressed = true; - } - } - if pressed { - exit.send(AppExit); - } -} - pub fn configure_gamepads(mut settings: ResMut) { // add a larger default dead-zone to all axes (ignore small inputs, round to zero) settings.default_axis_settings.set_deadzone_lowerbound(-0.2); @@ -213,3 +116,31 @@ pub fn configure_gamepads(mut settings: ResMut) { settings.default_button_settings = button_settings; } + +pub fn rumble_gamepads( + gamepads: Res, + mut rumble_requests: EventWriter, + mut gamepad_rumble: ResMut, +) { + if !gamepad_rumble.just_added { + return; + } + gamepad_rumble.just_added = false; + + for gamepad in gamepads.iter() { + rumble_requests.send(GamepadRumbleRequest::Add { + gamepad, + intensity: GamepadRumbleIntensity { + // intensity low-frequency motor, usually on the left-hand side + strong_motor: gamepad_rumble.intensity, + // intensity of high-frequency motor, usually on the right-hand side + weak_motor: gamepad_rumble.intensity, + }, + duration: Duration::from_secs_f32(gamepad_rumble.duration), + }); + + if gamepad_rumble.duration == 0.0 || gamepad_rumble.intensity == 0.0 { + rumble_requests.send(GamepadRumbleRequest::Stop { gamepad }); + } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 0000000..d09c012 --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,146 @@ +pub mod gamepad; + +pub use gamepad::GamepadRumble; + +use bevy::{app::AppExit, input::gamepad::*, prelude::*}; +use bevy_ggrs::*; + +use crate::{player::Player, GameState, RollbackState}; + +pub const INPUT_FORWARD: u8 = 1 << 0; +pub const INPUT_BACKWARD: u8 = 1 << 1; +pub const INPUT_LEFT: u8 = 1 << 2; +pub const INPUT_RIGHT: u8 = 1 << 3; +pub const INPUT_FIRE: u8 = 1 << 4; +pub const INPUT_DODGE: u8 = 1 << 5; +pub const INPUT_ROCKET: u8 = 1 << 6; +pub const INPUT_REMATCH: u8 = 1 << 7; + +pub fn input( + In(local_handle): In, + keys: Res>, + mouse_buttons: Res>, + gamepads: Res, + button_inputs: Res>, + button_axes: Res>, + axes: Res>, + players: Query<(&Transform, &Player)>, +) -> u8 { + let mut input = 0u8; + + if keys.any_pressed([KeyCode::Up, KeyCode::W, KeyCode::K]) { + input |= INPUT_FORWARD; + } + if keys.any_pressed([KeyCode::Down, KeyCode::S, KeyCode::J]) { + input |= INPUT_BACKWARD; + } + if keys.any_pressed([KeyCode::Left, KeyCode::A]) { + input |= INPUT_LEFT; + } + if keys.any_pressed([KeyCode::Right, KeyCode::D, KeyCode::F]) { + input |= INPUT_RIGHT; + } + if keys.any_pressed([KeyCode::Space, KeyCode::Return]) { + input |= INPUT_FIRE; + } + if keys.any_pressed([KeyCode::E, KeyCode::L]) || mouse_buttons.pressed(MouseButton::Right) { + input |= INPUT_DODGE; + } + if keys.any_pressed([KeyCode::Q, KeyCode::Semicolon]) + || mouse_buttons.pressed(MouseButton::Left) + { + input |= INPUT_ROCKET; + } + if keys.pressed(KeyCode::R) { + input |= INPUT_REMATCH; + } + + let controller_input = gamepad::get_gamepad_input( + &gamepads, + &button_inputs, + &button_axes, + &axes, + &players, + local_handle, + ); + input |= controller_input; + input +} + +pub fn steer_direction(input: u8) -> f32 { + let mut steer_direction: f32 = 0.0; + if input & INPUT_LEFT != 0 { + steer_direction += 1.0; + } + if input & INPUT_RIGHT != 0 { + steer_direction -= 1.0; + } + steer_direction +} + +pub fn accelerate_direction(input: u8) -> f32 { + let mut accelerate_direction: f32 = 0.0; + if input & INPUT_FORWARD != 0 { + accelerate_direction += 1.0; + } + if input & INPUT_BACKWARD != 0 { + accelerate_direction -= 1.0; + } + accelerate_direction +} + +pub fn fire(input: u8) -> bool { + input & INPUT_FIRE != 0 +} + +pub fn dodge(input: u8) -> bool { + input & INPUT_DODGE != 0 +} + +pub fn rocket(input: u8) -> bool { + input & INPUT_ROCKET != 0 +} + +pub fn rematch(input: u8) -> bool { + input & INPUT_REMATCH != 0 +} + +pub fn quit( + mut exit: EventWriter, + keys: Res>, + gamepads: Res, + button_inputs: Res>, +) { + let mut pressed = keys.pressed(KeyCode::Q); + for gamepad in gamepads.iter() { + if button_inputs.pressed(GamepadButton::new(gamepad, GamepadButtonType::East)) { + pressed = true; + } + } + if pressed { + exit.send(AppExit); + } +} + +pub struct AceInputPlugin; + +impl Plugin for AceInputPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + ( + gamepad::rumble_gamepads, + quit.run_if( + in_state(GameState::MainMenu) + .or_else(in_state(GameState::Matchmaking)) + .or_else( + in_state(GameState::InRollbackGame) + .and_then(in_state(RollbackState::GameOver)), + ), + ), + ), + ) + .init_resource::() + .add_systems(OnExit(GameState::AssetLoading), gamepad::configure_gamepads); + } +} diff --git a/src/main.rs b/src/main.rs index 08b3733..ecf7ea7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,6 +99,7 @@ fn main() { audio::GameAudioPlugin, world::GameLogicPlugin, network::AceNetworkPlugin, + input::AceInputPlugin, camera::AceCameraPlugin, ui::AceUiPlugin, console::AceConsolePlugin, @@ -108,11 +109,5 @@ fn main() { .insert_resource(ClearColor(Color::BLACK)) .init_resource::() .init_resource::() - .add_systems(OnExit(GameState::AssetLoading), input::configure_gamepads) - .add_systems( - Update, - input::quit.run_if(in_state(GameState::MainMenu) - .or_else(in_state(GameState::Matchmaking)) - .or_else(in_state(GameState::InRollbackGame).and_then(in_state(RollbackState::GameOver))))) .run(); } diff --git a/src/player/effect/bullet.rs b/src/player/effect/bullet.rs index 7ced308..13bcf63 100644 --- a/src/player/effect/bullet.rs +++ b/src/player/effect/bullet.rs @@ -3,6 +3,7 @@ use bevy_hanabi::prelude::*; use crate::{ camera::CameraShake, + input::GamepadRumble, player::{ shooting::bullet::{BulletCollided, BulletFired}, LocalPlayerHandle, @@ -122,3 +123,15 @@ pub fn add_camera_shake_bullet_fired( } } } + +pub fn add_gamepad_rumble_bullet_fired( + mut gamepad_rumble: ResMut, + local_handle: Res, + mut ev_bullet_fired: EventReader, +) { + for ev in ev_bullet_fired.iter() { + if ev.handle == local_handle.0 { + gamepad_rumble.add_rumble(0.1, 0.1); + } + } +} diff --git a/src/player/effect/damage.rs b/src/player/effect/damage.rs index c8e62c3..2b3ab81 100644 --- a/src/player/effect/damage.rs +++ b/src/player/effect/damage.rs @@ -3,13 +3,11 @@ use bevy_ggrs::AddRollbackCommandExtension; use bevy_hanabi::prelude::*; use super::super::{P1_COLOR, P2_COLOR}; - -use crate::{ - audio::RollbackSound, - camera::CameraShake, - player::{health::PlayerTookDamage, LocalPlayerHandle}, - GameAssets, -}; +use crate::audio::RollbackSound; +use crate::camera::CameraShake; +use crate::input::GamepadRumble; +use crate::player::{health::PlayerTookDamage, LocalPlayerHandle}; +use crate::GameAssets; #[derive(Component)] pub struct DamageEffectSpawner; @@ -147,3 +145,16 @@ pub fn add_camera_shake_damage( } } } + +pub fn add_gamepad_rumble( + mut gamepad_rumble: ResMut, + mut ev_player_took_damage: EventReader, + local_handle: Res, +) { + for ev in ev_player_took_damage.iter() { + // Local player took damage, time to shake it + if ev.handle == local_handle.0 { + gamepad_rumble.add_rumble(0.15, 0.2); + } + } +} diff --git a/src/player/effect/mod.rs b/src/player/effect/mod.rs index 4b37fdc..1fa44ea 100644 --- a/src/player/effect/mod.rs +++ b/src/player/effect/mod.rs @@ -2,6 +2,7 @@ pub mod damage; pub mod trail; mod bullet; +mod rocket; mod super_sonic; use bevy::prelude::*; @@ -33,6 +34,11 @@ impl Plugin for EffectPlugin { super_sonic::spawn_super_sonic_effects, super_sonic::animate_super_sonic_effects, super_sonic::despawn_super_sonic_effects, + damage::add_camera_shake_damage, + damage::add_gamepad_rumble, + bullet::add_camera_shake_bullet_fired, + bullet::add_gamepad_rumble_bullet_fired, + rocket::add_rockets_gamepad_rumble, ) .chain() .run_if(in_state(GameState::InRollbackGame)), @@ -42,8 +48,6 @@ impl Plugin for EffectPlugin { ( damage::spawn_damage_effect, damage::spawn_damage_effect_sound, - damage::add_camera_shake_damage, - bullet::add_camera_shake_bullet_fired, bullet::spawn_collision_effect, trail::disable_trails, trail::toggle_plane_trail_visibilities, diff --git a/src/player/effect/rocket.rs b/src/player/effect/rocket.rs new file mode 100644 index 0000000..f953fe8 --- /dev/null +++ b/src/player/effect/rocket.rs @@ -0,0 +1,22 @@ +use bevy::prelude::*; + +use crate::{ + input::GamepadRumble, + player::{shooting::rocket::Rocket, LocalPlayerHandle}, +}; + +pub fn add_rockets_gamepad_rumble( + mut gamepad_rumble: ResMut, + query: Query<&Rocket>, + local_handle: Res, +) { + for rocket in &query { + if rocket.handle != local_handle.0 { + continue; + } + + if rocket.start_timer.percent() == 0.0 { + gamepad_rumble.add_rumble(0.1, 0.1); + } + } +} diff --git a/src/player/mod.rs b/src/player/mod.rs index 90a88e8..5c74b3b 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -202,6 +202,7 @@ impl Plugin for PlayerPlugin { ( spawning::despawn_players_sound, spawning::despawn_players_camera_shake, + spawning::despawn_players_gamepad_rumble, spawning::despawn_players, ) .chain() diff --git a/src/player/shooting/rocket.rs b/src/player/shooting/rocket.rs index 3aca052..864168c 100644 --- a/src/player/shooting/rocket.rs +++ b/src/player/shooting/rocket.rs @@ -10,7 +10,7 @@ use bevy_hanabi::EffectAsset; use crate::audio::RollbackSound; use crate::camera::CameraShake; use crate::debug::DebugTransform; -use crate::input; +use crate::input::{self, GamepadRumble}; use crate::misc::utils::quat_from_vec3; use crate::network::ggrs_config::GGRS_FPS; use crate::network::GgrsConfig; @@ -41,9 +41,9 @@ pub struct DummyRocket; pub struct Rocket { left_side: bool, current_speed: f32, - start_timer: Timer, // The target we are aiming at (if it is in sight) target: Option, + pub start_timer: Timer, pub handle: usize, } @@ -236,6 +236,7 @@ pub fn disable_rockets( mut players: Query<(&Transform, &mut Player)>, mut rockets: Query<(&mut CollisionEntity, &Rocket, &Transform)>, mut camera_shake: ResMut, + mut gamepad_rumble: ResMut, local_handle: Res, ) { for (mut collision_entity, rocket, rocket_transform) in &mut rockets { @@ -260,6 +261,7 @@ pub fn disable_rockets( } else { if player.handle == local_handle.0 { camera_shake.add_trauma(0.35); + gamepad_rumble.add_rumble(0.5, 0.5); } player.health -= player.stats.max_health / 2; } @@ -274,6 +276,7 @@ pub fn destroy_rockets( assets: Res, frame: Res, mut camera_shake: ResMut, + mut gamepad_rumble: ResMut, rockets: Query<(Entity, &Rocket, &Transform, &CollisionEntity)>, ) { for (entity, rocket, rocket_transform, collision_entity) in &rockets { @@ -289,6 +292,7 @@ pub fn destroy_rockets( rocket.handle, ); camera_shake.add_trauma(0.5); + gamepad_rumble.add_rumble(0.3, 0.15); commands.entity(entity).despawn(); } } diff --git a/src/player/spawning.rs b/src/player/spawning.rs index 9c20a1a..4ce45d4 100644 --- a/src/player/spawning.rs +++ b/src/player/spawning.rs @@ -17,6 +17,7 @@ use super::PlayerStats; use crate::audio::RollbackSound; use crate::camera::CameraShake; use crate::debug::DebugTransform; +use crate::input::GamepadRumble; use crate::world::CollisionEntity; use crate::GameAssets; use crate::RollbackState; @@ -133,3 +134,19 @@ pub fn despawn_players_camera_shake( } } } + +pub fn despawn_players_gamepad_rumble( + mut gamepad_rumble: ResMut, + players: Query<(&Player, &CollisionEntity)>, + local_handle: Res, +) { + for (player, collision_entity) in &players { + if player.handle != local_handle.0 { + continue; + } + + if player.health == 0 || collision_entity.disabled { + gamepad_rumble.add_rumble(0.8, 0.35); + } + } +}