Files
HEDZReloaded/crates/client/src/control/controls.rs
2025-12-10 18:31:42 -05:00

255 lines
8.1 KiB
Rust

use crate::{GameState, control::CharacterInputEnabled};
use bevy::{
input::{
gamepad::{GamepadConnection, GamepadEvent},
mouse::MouseMotion,
},
prelude::*,
};
use shared::{
control::{
BackpackButtonPress, CashHealPressed, ClientInputs, ControllerSet, Inputs, LocalInputs,
LookDirMovement, SelectLeftPressed, SelectRightPressed,
},
player::{LocalPlayer, PlayerBodyMesh},
};
pub fn plugin(app: &mut App) {
app.add_systems(
PreUpdate,
(
gamepad_connections.run_if(on_message::<GamepadEvent>),
reset_lookdir,
keyboard_controls,
gamepad_controls,
mouse_rotate,
get_lookdir,
send_inputs,
)
.chain()
.in_set(ControllerSet::CollectInputs)
.run_if(
in_state(GameState::Playing)
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
),
)
.add_systems(PreUpdate, overwrite_local_inputs);
app.add_systems(
Update,
reset_control_state_on_disable.run_if(in_state(GameState::Playing)),
);
}
/// Overwrite inputs for this client that were replicated from the server with the local inputs
fn overwrite_local_inputs(
mut inputs: Single<&mut Inputs, With<LocalPlayer>>,
local_inputs: Single<&LocalInputs>,
) {
**inputs = local_inputs.0;
}
/// Write inputs from combined keyboard/gamepad state into the networked input buffer
/// for the local player.
fn send_inputs(mut writer: MessageWriter<ClientInputs>, local_inputs: Single<&LocalInputs>) {
writer.write(ClientInputs(local_inputs.0));
}
fn reset_lookdir(mut look_dir: ResMut<LookDirMovement>) {
look_dir.0 = Vec2::ZERO;
}
/// Reset character inputs to default when character input is disabled.
fn reset_control_state_on_disable(
state: Res<CharacterInputEnabled>,
mut inputs: Single<&mut LocalInputs>,
) {
if state.is_changed() && matches!(*state, CharacterInputEnabled::Off) {
inputs.0 = Inputs {
look_dir: inputs.0.look_dir,
..default()
};
}
}
fn get_lookdir(
mut inputs: Single<&mut LocalInputs>,
rig_transform: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
) {
inputs.0.look_dir = if let Some(ref rig_transform) = rig_transform {
rig_transform.forward().as_vec3()
} else {
Vec3::NEG_Z
};
}
/// Applies a square deadzone to a Vec2
fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
Vec2::new(
if v.x.abs() < min { 0. } else { v.x },
if v.y.abs() < min { 0. } else { v.y },
)
}
/// Collect gamepad inputs
#[allow(clippy::too_many_arguments)]
fn gamepad_controls(
gamepads: Query<&Gamepad>,
mut inputs: Single<&mut LocalInputs>,
mut look_dir: ResMut<LookDirMovement>,
mut backpack_inputs: MessageWriter<BackpackButtonPress>,
mut select_left_pressed: MessageWriter<SelectLeftPressed>,
mut select_right_pressed: MessageWriter<SelectRightPressed>,
mut cash_heal_pressed: MessageWriter<CashHealPressed>,
) {
let deadzone_left_stick = 0.15;
let deadzone_right_stick = 0.15;
for gamepad in gamepads.iter() {
let rotate = gamepad
.get(GamepadButton::RightTrigger2)
.unwrap_or_default();
// 8BitDo Ultimate wireless Controller for PC
look_dir.0 = if gamepad.vendor_id() == Some(11720) && gamepad.product_id() == Some(12306) {
const EPSILON: f32 = 0.015;
Vec2::new(
if rotate < 0.5 - EPSILON {
40. * (rotate - 0.5)
} else if rotate > 0.5 + EPSILON {
-40. * (rotate - 0.5)
} else {
0.
},
0.,
)
} else {
deadzone_square(gamepad.right_stick(), deadzone_right_stick) * 40.
};
let move_dir = deadzone_square(gamepad.left_stick(), deadzone_left_stick);
inputs.0.move_dir += move_dir.clamp_length_max(1.0);
inputs.0.jump |= gamepad.pressed(GamepadButton::South);
inputs.0.view_mode |= gamepad.pressed(GamepadButton::LeftTrigger2);
inputs.0.trigger |= gamepad.pressed(GamepadButton::RightTrigger2);
if gamepad.just_pressed(GamepadButton::DPadUp) {
backpack_inputs.write(BackpackButtonPress::Toggle);
}
if gamepad.just_pressed(GamepadButton::DPadDown) {
backpack_inputs.write(BackpackButtonPress::Swap);
}
if gamepad.just_pressed(GamepadButton::DPadLeft) {
backpack_inputs.write(BackpackButtonPress::Left);
}
if gamepad.just_pressed(GamepadButton::DPadRight) {
backpack_inputs.write(BackpackButtonPress::Right);
}
if gamepad.just_pressed(GamepadButton::LeftTrigger) {
select_left_pressed.write(SelectLeftPressed);
}
if gamepad.just_pressed(GamepadButton::RightTrigger) {
select_right_pressed.write(SelectRightPressed);
}
if gamepad.just_pressed(GamepadButton::East) {
cash_heal_pressed.write(CashHealPressed);
}
}
}
/// Collect mouse movement input
fn mouse_rotate(mut mouse: MessageReader<MouseMotion>, mut look_dir: ResMut<LookDirMovement>) {
for ev in mouse.read() {
look_dir.0 += ev.delta;
}
}
/// Collect keyboard input
#[allow(clippy::too_many_arguments)]
fn keyboard_controls(
keyboard: Res<ButtonInput<KeyCode>>,
mouse: Res<ButtonInput<MouseButton>>,
mut inputs: Single<&mut LocalInputs>,
mut backpack_inputs: MessageWriter<BackpackButtonPress>,
mut select_left_pressed: MessageWriter<SelectLeftPressed>,
mut select_right_pressed: MessageWriter<SelectRightPressed>,
mut cash_heal_pressed: MessageWriter<CashHealPressed>,
) {
let up_binds = [KeyCode::KeyW, KeyCode::ArrowUp];
let down_binds = [KeyCode::KeyS, KeyCode::ArrowDown];
let left_binds = [KeyCode::KeyA, KeyCode::ArrowLeft];
let right_binds = [KeyCode::KeyD, KeyCode::ArrowRight];
let up = keyboard.any_pressed(up_binds);
let down = keyboard.any_pressed(down_binds);
let left = keyboard.any_pressed(left_binds);
let right = keyboard.any_pressed(right_binds);
let horizontal = right as i8 - left as i8;
let vertical = up as i8 - down as i8;
let direction = Vec2::new(horizontal as f32, vertical as f32).clamp_length_max(1.0);
inputs.0.move_dir = direction;
inputs.0.jump = keyboard.pressed(KeyCode::Space);
inputs.0.view_mode = keyboard.pressed(KeyCode::Tab);
inputs.0.trigger = mouse.pressed(MouseButton::Left);
if keyboard.just_pressed(KeyCode::KeyB) {
backpack_inputs.write(BackpackButtonPress::Toggle);
}
if keyboard.just_pressed(KeyCode::Enter) {
backpack_inputs.write(BackpackButtonPress::Swap);
}
if keyboard.just_pressed(KeyCode::Comma) {
backpack_inputs.write(BackpackButtonPress::Left);
}
if keyboard.just_pressed(KeyCode::Period) {
backpack_inputs.write(BackpackButtonPress::Right);
}
if keyboard.just_pressed(KeyCode::KeyQ) {
select_left_pressed.write(SelectLeftPressed);
}
if keyboard.just_pressed(KeyCode::KeyE) {
select_right_pressed.write(SelectRightPressed);
}
if keyboard.just_pressed(KeyCode::Enter) {
cash_heal_pressed.write(CashHealPressed);
}
}
/// Receive gamepad connections and disconnections
fn gamepad_connections(mut evr_gamepad: MessageReader<GamepadEvent>) {
for ev in evr_gamepad.read() {
if let GamepadEvent::Connection(connection) = ev {
match &connection.connection {
GamepadConnection::Connected {
name,
vendor_id,
product_id,
} => {
info!(
"New gamepad connected: {:?}, name: {name}, vendor: {vendor_id:?}, product: {product_id:?}",
connection.gamepad,
);
}
GamepadConnection::Disconnected => {
info!("Lost connection with gamepad: {:?}", connection.gamepad);
}
}
}
}
}