255 lines
8.1 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|