Simpler + Better Inputs (#82)
* switch to events for instantaneous inputs * input simplification + improvements * fix trail crash * fix clippy warnings * qualify `Trail` in fn signature
This commit is contained in:
@@ -74,7 +74,7 @@ pub fn plugin(app: &mut App) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
fn on_connected_state(mut commands: Commands, mut game_state: ResMut<NextState<GameState>>) {
|
fn on_connected_state(mut commands: Commands, mut game_state: ResMut<NextState<GameState>>) {
|
||||||
commands.client_trigger(ClientEnteredPlaying::default());
|
commands.client_trigger(ClientEnteredPlaying);
|
||||||
game_state.set(GameState::Playing);
|
game_state.set(GameState::Playing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use shared::{
|
use shared::{
|
||||||
GameState,
|
GameState,
|
||||||
control::{ControlState, ControllerSet, LookDirMovement},
|
control::{ControllerSet, Inputs, LookDirMovement},
|
||||||
player::PlayerBodyMesh,
|
player::{LocalPlayer, PlayerBodyMesh},
|
||||||
};
|
};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
@@ -17,15 +17,17 @@ pub fn plugin(app: &mut App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_rig(
|
fn rotate_rig(
|
||||||
controls: Res<ControlState>,
|
inputs: Single<&Inputs, With<LocalPlayer>>,
|
||||||
look_dir: Res<LookDirMovement>,
|
look_dir: Res<LookDirMovement>,
|
||||||
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
|
local_player: Single<&Children, With<LocalPlayer>>,
|
||||||
|
mut player_mesh: Query<&mut Transform, With<PlayerBodyMesh>>,
|
||||||
) {
|
) {
|
||||||
for mut rig_transform in player.iter_mut() {
|
if inputs.view_mode {
|
||||||
if controls.view_mode {
|
return;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local_player.iter().find(|&child| {
|
||||||
|
if let Ok(mut rig_transform) = player_mesh.get_mut(child) {
|
||||||
let look_dir = look_dir.0;
|
let look_dir = look_dir.0;
|
||||||
|
|
||||||
// todo: Make consistent with the running controller
|
// todo: Make consistent with the running controller
|
||||||
@@ -47,5 +49,10 @@ fn rotate_rig(
|
|||||||
// * Quat::from_rotation_x(controls.keyboard_state.look_dir.y * sensitivity);
|
// * Quat::from_rotation_x(controls.keyboard_state.look_dir.y * sensitivity);
|
||||||
// let clamped_rotation = rig_transform.rotation.rotate_towards(target_rotation, 0.01);
|
// let clamped_rotation = rig_transform.rotation.rotate_towards(target_rotation, 0.01);
|
||||||
// rig_transform.rotation = clamped_rotation;
|
// rig_transform.rotation = clamped_rotation;
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,30 @@
|
|||||||
use super::{ControlState, Controls};
|
|
||||||
use crate::{GameState, control::CharacterInputEnabled};
|
use crate::{GameState, control::CharacterInputEnabled};
|
||||||
use bevy::{
|
use bevy::{
|
||||||
input::{
|
input::{
|
||||||
ButtonState,
|
|
||||||
gamepad::{GamepadConnection, GamepadEvent},
|
gamepad::{GamepadConnection, GamepadEvent},
|
||||||
mouse::{MouseButtonInput, MouseMotion},
|
mouse::MouseMotion,
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use shared::{
|
use shared::{
|
||||||
control::{ControllerSet, LookDirMovement},
|
control::{
|
||||||
player::PlayerBodyMesh,
|
BackpackLeftPressed, BackpackRightPressed, BackpackSwapPressed, BackpackTogglePressed,
|
||||||
|
CashHealPressed, ClientInputs, ControllerSet, Inputs, LocalInputs, LookDirMovement,
|
||||||
|
SelectLeftPressed, SelectRightPressed,
|
||||||
|
},
|
||||||
|
player::{LocalPlayer, PlayerBodyMesh},
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, hash::Hash};
|
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.init_resource::<Controls>();
|
|
||||||
app.init_resource::<InputStateCache<KeyCode>>();
|
|
||||||
|
|
||||||
app.register_required_components::<Gamepad, InputStateCache<GamepadButton>>();
|
|
||||||
|
|
||||||
app.add_systems(PreUpdate, (cache_keyboard_state, cache_gamepad_state));
|
|
||||||
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
(
|
(
|
||||||
reset_lookdir,
|
|
||||||
gamepad_controls,
|
|
||||||
keyboard_controls,
|
|
||||||
mouse_rotate,
|
|
||||||
mouse_click,
|
|
||||||
gamepad_connections.run_if(on_message::<GamepadEvent>),
|
gamepad_connections.run_if(on_message::<GamepadEvent>),
|
||||||
combine_controls,
|
reset_lookdir,
|
||||||
|
keyboard_controls,
|
||||||
|
gamepad_controls,
|
||||||
|
mouse_rotate,
|
||||||
get_lookdir,
|
get_lookdir,
|
||||||
clear_keyboard_just,
|
|
||||||
clear_gamepad_just,
|
|
||||||
send_inputs,
|
send_inputs,
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
@@ -43,7 +33,8 @@ pub fn plugin(app: &mut App) {
|
|||||||
in_state(GameState::Playing)
|
in_state(GameState::Playing)
|
||||||
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
|
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
|
.add_systems(PreUpdate, overwrite_local_inputs);
|
||||||
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
@@ -51,10 +42,18 @@ pub fn plugin(app: &mut App) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
/// Write inputs from combined keyboard/gamepad state into the networked input buffer
|
||||||
/// for the local player.
|
/// for the local player.
|
||||||
fn send_inputs(mut writer: MessageWriter<ControlState>, controls: Res<ControlState>) {
|
fn send_inputs(mut writer: MessageWriter<ClientInputs>, local_inputs: Single<&LocalInputs>) {
|
||||||
writer.write(*controls);
|
writer.write(ClientInputs(local_inputs.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_lookdir(mut look_dir: ResMut<LookDirMovement>) {
|
fn reset_lookdir(mut look_dir: ResMut<LookDirMovement>) {
|
||||||
@@ -64,128 +63,21 @@ fn reset_lookdir(mut look_dir: ResMut<LookDirMovement>) {
|
|||||||
/// Reset character inputs to default when character input is disabled.
|
/// Reset character inputs to default when character input is disabled.
|
||||||
fn reset_control_state_on_disable(
|
fn reset_control_state_on_disable(
|
||||||
state: Res<CharacterInputEnabled>,
|
state: Res<CharacterInputEnabled>,
|
||||||
mut controls: ResMut<Controls>,
|
mut inputs: Single<&mut LocalInputs>,
|
||||||
mut control_state: ResMut<ControlState>,
|
|
||||||
) {
|
) {
|
||||||
if state.is_changed() && matches!(*state, CharacterInputEnabled::Off) {
|
if state.is_changed() && matches!(*state, CharacterInputEnabled::Off) {
|
||||||
*controls = Controls::default();
|
inputs.0 = Inputs {
|
||||||
*control_state = ControlState {
|
look_dir: inputs.0.look_dir,
|
||||||
look_dir: control_state.look_dir,
|
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Caches information that depends on the Update schedule so that it can be read safely from the Fixed schedule
|
|
||||||
/// without losing/duplicating info
|
|
||||||
#[derive(Component, Resource)]
|
|
||||||
struct InputStateCache<Button> {
|
|
||||||
map: HashMap<Button, InputState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Button> Default for InputStateCache<Button> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { map: default() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Button: Hash + Eq> InputStateCache<Button> {
|
|
||||||
fn clear_just(&mut self) {
|
|
||||||
for state in self.map.values_mut() {
|
|
||||||
state.just_pressed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn just_pressed(&self, button: Button) -> bool {
|
|
||||||
self.map
|
|
||||||
.get(&button)
|
|
||||||
.map(|state| state.just_pressed)
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct InputState {
|
|
||||||
just_pressed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cache_keyboard_state(
|
|
||||||
mut cache: ResMut<InputStateCache<KeyCode>>,
|
|
||||||
keyboard: Res<ButtonInput<KeyCode>>,
|
|
||||||
) {
|
|
||||||
let mut cache_key = |key| {
|
|
||||||
cache.map.entry(key).or_default().just_pressed |= keyboard.just_pressed(key);
|
|
||||||
};
|
|
||||||
cache_key(KeyCode::Space);
|
|
||||||
cache_key(KeyCode::Tab);
|
|
||||||
cache_key(KeyCode::KeyB);
|
|
||||||
cache_key(KeyCode::Enter);
|
|
||||||
cache_key(KeyCode::Comma);
|
|
||||||
cache_key(KeyCode::Period);
|
|
||||||
cache_key(KeyCode::KeyQ);
|
|
||||||
cache_key(KeyCode::KeyE);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_keyboard_just(mut cache: ResMut<InputStateCache<KeyCode>>) {
|
|
||||||
cache.clear_just();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cache_gamepad_state(mut gamepads: Query<(&Gamepad, &mut InputStateCache<GamepadButton>)>) {
|
|
||||||
for (gamepad, mut cache) in gamepads.iter_mut() {
|
|
||||||
let mut cache_button = |button| {
|
|
||||||
cache.map.entry(button).or_default().just_pressed |= gamepad.just_pressed(button);
|
|
||||||
};
|
|
||||||
|
|
||||||
cache_button(GamepadButton::North);
|
|
||||||
cache_button(GamepadButton::East);
|
|
||||||
cache_button(GamepadButton::South);
|
|
||||||
cache_button(GamepadButton::West);
|
|
||||||
cache_button(GamepadButton::DPadUp);
|
|
||||||
cache_button(GamepadButton::DPadRight);
|
|
||||||
cache_button(GamepadButton::DPadDown);
|
|
||||||
cache_button(GamepadButton::DPadLeft);
|
|
||||||
cache_button(GamepadButton::LeftTrigger);
|
|
||||||
cache_button(GamepadButton::LeftTrigger2);
|
|
||||||
cache_button(GamepadButton::RightTrigger);
|
|
||||||
cache_button(GamepadButton::RightTrigger2);
|
|
||||||
cache_button(GamepadButton::Select);
|
|
||||||
cache_button(GamepadButton::Start);
|
|
||||||
cache_button(GamepadButton::LeftThumb);
|
|
||||||
cache_button(GamepadButton::RightThumb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_gamepad_just(mut caches: Query<&mut InputStateCache<GamepadButton>>) {
|
|
||||||
for mut cache in caches.iter_mut() {
|
|
||||||
cache.clear_just();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take keyboard and gamepad state and combine them into unified input state
|
|
||||||
fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<ControlState>) {
|
|
||||||
let keyboard = controls.keyboard_state;
|
|
||||||
let gamepad = controls.gamepad_state.unwrap_or_default();
|
|
||||||
|
|
||||||
combined_controls.look_dir = Vec3::NEG_Z;
|
|
||||||
combined_controls.move_dir = gamepad.move_dir + keyboard.move_dir;
|
|
||||||
combined_controls.jump = gamepad.jump | keyboard.jump;
|
|
||||||
combined_controls.view_mode = gamepad.view_mode | keyboard.view_mode;
|
|
||||||
combined_controls.trigger = keyboard.trigger | gamepad.trigger;
|
|
||||||
combined_controls.just_triggered = keyboard.just_triggered | gamepad.just_triggered;
|
|
||||||
combined_controls.select_left = gamepad.select_left | keyboard.select_left;
|
|
||||||
combined_controls.select_right = gamepad.select_right | keyboard.select_right;
|
|
||||||
combined_controls.backpack_toggle = gamepad.backpack_toggle | keyboard.backpack_toggle;
|
|
||||||
combined_controls.backpack_swap = gamepad.backpack_swap | keyboard.backpack_swap;
|
|
||||||
combined_controls.backpack_left = gamepad.backpack_left | keyboard.backpack_left;
|
|
||||||
combined_controls.backpack_right = gamepad.backpack_right | keyboard.backpack_right;
|
|
||||||
combined_controls.cash_heal = gamepad.cash_heal | keyboard.cash_heal;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_lookdir(
|
fn get_lookdir(
|
||||||
mut controls: ResMut<ControlState>,
|
mut inputs: Single<&mut LocalInputs>,
|
||||||
rig_transform: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
rig_transform: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
||||||
) {
|
) {
|
||||||
controls.look_dir = if let Some(ref rig_transform) = rig_transform {
|
inputs.0.look_dir = if let Some(ref rig_transform) = rig_transform {
|
||||||
rig_transform.forward().as_vec3()
|
rig_transform.forward().as_vec3()
|
||||||
} else {
|
} else {
|
||||||
Vec3::NEG_Z
|
Vec3::NEG_Z
|
||||||
@@ -201,21 +93,23 @@ fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Collect gamepad inputs
|
/// Collect gamepad inputs
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn gamepad_controls(
|
fn gamepad_controls(
|
||||||
gamepads: Query<(Entity, &Gamepad, &InputStateCache<GamepadButton>)>,
|
gamepads: Query<&Gamepad>,
|
||||||
mut controls: ResMut<Controls>,
|
mut inputs: Single<&mut LocalInputs>,
|
||||||
mut look_dir: ResMut<LookDirMovement>,
|
mut look_dir: ResMut<LookDirMovement>,
|
||||||
|
mut backpack_toggle_pressed: MessageWriter<BackpackTogglePressed>,
|
||||||
|
mut backpack_swap_pressed: MessageWriter<BackpackSwapPressed>,
|
||||||
|
mut backpack_left_pressed: MessageWriter<BackpackLeftPressed>,
|
||||||
|
mut backpack_right_pressed: MessageWriter<BackpackRightPressed>,
|
||||||
|
mut select_left_pressed: MessageWriter<SelectLeftPressed>,
|
||||||
|
mut select_right_pressed: MessageWriter<SelectRightPressed>,
|
||||||
|
mut cash_heal_pressed: MessageWriter<CashHealPressed>,
|
||||||
) {
|
) {
|
||||||
let Some((_e, gamepad, cache)) = gamepads.iter().next() else {
|
|
||||||
if controls.gamepad_state.is_some() {
|
|
||||||
controls.gamepad_state = None;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let deadzone_left_stick = 0.15;
|
let deadzone_left_stick = 0.15;
|
||||||
let deadzone_right_stick = 0.15;
|
let deadzone_right_stick = 0.15;
|
||||||
|
|
||||||
|
for gamepad in gamepads.iter() {
|
||||||
let rotate = gamepad
|
let rotate = gamepad
|
||||||
.get(GamepadButton::RightTrigger2)
|
.get(GamepadButton::RightTrigger2)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
@@ -239,29 +133,38 @@ fn gamepad_controls(
|
|||||||
|
|
||||||
let move_dir = deadzone_square(gamepad.left_stick(), deadzone_left_stick);
|
let move_dir = deadzone_square(gamepad.left_stick(), deadzone_left_stick);
|
||||||
|
|
||||||
let state = ControlState {
|
inputs.0.move_dir += move_dir.clamp_length_max(1.0);
|
||||||
move_dir,
|
inputs.0.jump |= gamepad.pressed(GamepadButton::South);
|
||||||
look_dir: Vec3::NEG_Z,
|
inputs.0.view_mode |= gamepad.pressed(GamepadButton::LeftTrigger2);
|
||||||
jump: gamepad.pressed(GamepadButton::South),
|
inputs.0.trigger |= gamepad.pressed(GamepadButton::RightTrigger2);
|
||||||
view_mode: gamepad.pressed(GamepadButton::LeftTrigger2),
|
|
||||||
trigger: gamepad.pressed(GamepadButton::RightTrigger2),
|
|
||||||
just_triggered: cache.just_pressed(GamepadButton::RightTrigger2),
|
|
||||||
select_left: cache.just_pressed(GamepadButton::LeftTrigger),
|
|
||||||
select_right: cache.just_pressed(GamepadButton::RightTrigger),
|
|
||||||
backpack_left: cache.just_pressed(GamepadButton::DPadLeft),
|
|
||||||
backpack_right: cache.just_pressed(GamepadButton::DPadRight),
|
|
||||||
backpack_swap: cache.just_pressed(GamepadButton::DPadDown),
|
|
||||||
backpack_toggle: cache.just_pressed(GamepadButton::DPadUp),
|
|
||||||
cash_heal: cache.just_pressed(GamepadButton::East),
|
|
||||||
};
|
|
||||||
|
|
||||||
if controls
|
if gamepad.just_pressed(GamepadButton::DPadUp) {
|
||||||
.gamepad_state
|
backpack_toggle_pressed.write(BackpackTogglePressed);
|
||||||
.as_ref()
|
}
|
||||||
.map(|last_state| *last_state != state)
|
|
||||||
.unwrap_or(true)
|
if gamepad.just_pressed(GamepadButton::DPadDown) {
|
||||||
{
|
backpack_swap_pressed.write(BackpackSwapPressed);
|
||||||
controls.gamepad_state = Some(state);
|
}
|
||||||
|
|
||||||
|
if gamepad.just_pressed(GamepadButton::DPadLeft) {
|
||||||
|
backpack_left_pressed.write(BackpackLeftPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if gamepad.just_pressed(GamepadButton::DPadRight) {
|
||||||
|
backpack_right_pressed.write(BackpackRightPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,10 +176,18 @@ fn mouse_rotate(mut mouse: MessageReader<MouseMotion>, mut look_dir: ResMut<Look
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Collect keyboard input
|
/// Collect keyboard input
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn keyboard_controls(
|
fn keyboard_controls(
|
||||||
keyboard: Res<ButtonInput<KeyCode>>,
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
cache: Res<InputStateCache<KeyCode>>,
|
mouse: Res<ButtonInput<MouseButton>>,
|
||||||
mut controls: ResMut<Controls>,
|
mut inputs: Single<&mut LocalInputs>,
|
||||||
|
mut backpack_toggle_pressed: MessageWriter<BackpackTogglePressed>,
|
||||||
|
mut backpack_swap_pressed: MessageWriter<BackpackSwapPressed>,
|
||||||
|
mut backpack_left_pressed: MessageWriter<BackpackLeftPressed>,
|
||||||
|
mut backpack_right_pressed: MessageWriter<BackpackRightPressed>,
|
||||||
|
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 up_binds = [KeyCode::KeyW, KeyCode::ArrowUp];
|
||||||
let down_binds = [KeyCode::KeyS, KeyCode::ArrowDown];
|
let down_binds = [KeyCode::KeyS, KeyCode::ArrowDown];
|
||||||
@@ -292,41 +203,37 @@ fn keyboard_controls(
|
|||||||
let vertical = up as i8 - down 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);
|
let direction = Vec2::new(horizontal as f32, vertical as f32).clamp_length_max(1.0);
|
||||||
|
|
||||||
controls.keyboard_state.move_dir = direction;
|
inputs.0.move_dir = direction;
|
||||||
controls.keyboard_state.jump = keyboard.pressed(KeyCode::Space);
|
inputs.0.jump = keyboard.pressed(KeyCode::Space);
|
||||||
controls.keyboard_state.view_mode = keyboard.pressed(KeyCode::Tab);
|
inputs.0.view_mode = keyboard.pressed(KeyCode::Tab);
|
||||||
controls.keyboard_state.backpack_toggle = cache.just_pressed(KeyCode::KeyB);
|
inputs.0.trigger = mouse.pressed(MouseButton::Left);
|
||||||
controls.keyboard_state.backpack_swap = cache.just_pressed(KeyCode::Enter);
|
|
||||||
controls.keyboard_state.backpack_left = cache.just_pressed(KeyCode::Comma);
|
if keyboard.just_pressed(KeyCode::KeyB) {
|
||||||
controls.keyboard_state.backpack_right = cache.just_pressed(KeyCode::Period);
|
backpack_toggle_pressed.write(BackpackTogglePressed);
|
||||||
controls.keyboard_state.select_left = cache.just_pressed(KeyCode::KeyQ);
|
|
||||||
controls.keyboard_state.select_right = cache.just_pressed(KeyCode::KeyE);
|
|
||||||
controls.keyboard_state.cash_heal = cache.just_pressed(KeyCode::Enter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect mouse button input when pressed
|
if keyboard.just_pressed(KeyCode::Enter) {
|
||||||
fn mouse_click(mut events: MessageReader<MouseButtonInput>, mut controls: ResMut<Controls>) {
|
backpack_swap_pressed.write(BackpackSwapPressed);
|
||||||
controls.keyboard_state.just_triggered = false;
|
}
|
||||||
|
|
||||||
for ev in events.read() {
|
if keyboard.just_pressed(KeyCode::Comma) {
|
||||||
match ev {
|
backpack_left_pressed.write(BackpackLeftPressed);
|
||||||
MouseButtonInput {
|
|
||||||
button: MouseButton::Left,
|
|
||||||
state: ButtonState::Pressed,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
controls.keyboard_state.trigger = true;
|
|
||||||
controls.keyboard_state.just_triggered = true;
|
|
||||||
}
|
}
|
||||||
MouseButtonInput {
|
|
||||||
button: MouseButton::Left,
|
if keyboard.just_pressed(KeyCode::Period) {
|
||||||
state: ButtonState::Released,
|
backpack_right_pressed.write(BackpackRightPressed);
|
||||||
..
|
|
||||||
} => {
|
|
||||||
controls.keyboard_state.trigger = false;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
use crate::GameState;
|
use crate::GameState;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_replicon::client::ClientSystems;
|
use bevy_replicon::client::ClientSystems;
|
||||||
use shared::control::{ControlState, ControllerSet};
|
use shared::control::ControllerSet;
|
||||||
|
|
||||||
mod controller_flying;
|
mod controller_flying;
|
||||||
pub mod controls;
|
pub mod controls;
|
||||||
|
|
||||||
#[derive(Resource, Debug, Default)]
|
|
||||||
struct Controls {
|
|
||||||
keyboard_state: ControlState,
|
|
||||||
gamepad_state: Option<ControlState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource, Debug, PartialEq, Eq)]
|
#[derive(Resource, Debug, PartialEq, Eq)]
|
||||||
pub enum CharacterInputEnabled {
|
pub enum CharacterInputEnabled {
|
||||||
On,
|
On,
|
||||||
|
|||||||
@@ -5,23 +5,16 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use shared::{
|
use shared::{
|
||||||
player::{LocalPlayerId, PlayerBodyMesh},
|
player::{LocalPlayer, PlayerBodyMesh},
|
||||||
protocol::{PlaySound, PlayerId, events::ClientHeadChanged, messages::AssignClientPlayer},
|
protocol::{PlaySound, PlayerId, events::ClientHeadChanged, messages::AssignClientPlayer},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.register_type::<LocalPlayerId>();
|
|
||||||
app.register_type::<LocalPlayer>();
|
|
||||||
|
|
||||||
app.init_state::<PlayerAssignmentState>();
|
app.init_state::<PlayerAssignmentState>();
|
||||||
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
receive_player_id.run_if(in_state(PlayerAssignmentState::Waiting)),
|
receive_player_id.run_if(not(in_state(PlayerAssignmentState::Confirmed))),
|
||||||
);
|
|
||||||
app.add_systems(
|
|
||||||
Update,
|
|
||||||
match_player_id.run_if(in_state(PlayerAssignmentState::IdReceived)),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
global_observer!(app, on_update_head_mesh);
|
global_observer!(app, on_update_head_mesh);
|
||||||
@@ -31,22 +24,18 @@ fn receive_player_id(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut client_assignments: MessageReader<AssignClientPlayer>,
|
mut client_assignments: MessageReader<AssignClientPlayer>,
|
||||||
mut next: ResMut<NextState<PlayerAssignmentState>>,
|
mut next: ResMut<NextState<PlayerAssignmentState>>,
|
||||||
|
mut local_id: Local<Option<PlayerId>>,
|
||||||
|
players: Query<(Entity, &PlayerId), Changed<PlayerId>>,
|
||||||
) {
|
) {
|
||||||
for &AssignClientPlayer(id) in client_assignments.read() {
|
for &AssignClientPlayer(id) in client_assignments.read() {
|
||||||
commands.insert_resource(LocalPlayerId { id });
|
|
||||||
next.set(PlayerAssignmentState::IdReceived);
|
|
||||||
info!("player id `{}` received", id.id);
|
info!("player id `{}` received", id.id);
|
||||||
}
|
|
||||||
|
*local_id = Some(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_player_id(
|
if let Some(local_id) = *local_id {
|
||||||
mut commands: Commands,
|
|
||||||
players: Query<(Entity, &PlayerId), Changed<PlayerId>>,
|
|
||||||
client: Res<LocalPlayerId>,
|
|
||||||
mut next: ResMut<NextState<PlayerAssignmentState>>,
|
|
||||||
) {
|
|
||||||
for (entity, player_id) in players.iter() {
|
for (entity, player_id) in players.iter() {
|
||||||
if *player_id == client.id {
|
if *player_id == local_id {
|
||||||
commands.entity(entity).insert(LocalPlayer);
|
commands.entity(entity).insert(LocalPlayer);
|
||||||
next.set(PlayerAssignmentState::Confirmed);
|
next.set(PlayerAssignmentState::Confirmed);
|
||||||
info!(
|
info!(
|
||||||
@@ -57,6 +46,7 @@ fn match_player_id(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Various states while trying to assign and match an ID to the player character.
|
/// Various states while trying to assign and match an ID to the player character.
|
||||||
/// Every client is given an ID (its player index in the match) and every character controller
|
/// Every client is given an ID (its player index in the match) and every character controller
|
||||||
@@ -64,19 +54,13 @@ fn match_player_id(
|
|||||||
/// controller it owns.
|
/// controller it owns.
|
||||||
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, States)]
|
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, States)]
|
||||||
pub enum PlayerAssignmentState {
|
pub enum PlayerAssignmentState {
|
||||||
/// Waiting for the server to send an [`AssignClientPlayer`] message
|
/// Waiting for the server to send an [`AssignClientPlayer`] message and replicate a [`PlayerId`]
|
||||||
#[default]
|
#[default]
|
||||||
Waiting,
|
Waiting,
|
||||||
/// Received an [`AssignClientPlayer`], querying for a matching controller
|
|
||||||
IdReceived,
|
|
||||||
/// Matching controller confirmed; a [`LocalPlayer`] exists
|
/// Matching controller confirmed; a [`LocalPlayer`] exists
|
||||||
Confirmed,
|
Confirmed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Reflect)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct LocalPlayer;
|
|
||||||
|
|
||||||
fn on_update_head_mesh(
|
fn on_update_head_mesh(
|
||||||
trigger: On<ClientHeadChanged>,
|
trigger: On<ClientHeadChanged>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ use shared::{
|
|||||||
BackbackSwapEvent, Backpack, UiHeadState,
|
BackbackSwapEvent, Backpack, UiHeadState,
|
||||||
backpack_ui::{BackpackUiState, HEAD_SLOTS},
|
backpack_ui::{BackpackUiState, HEAD_SLOTS},
|
||||||
},
|
},
|
||||||
control::ControlState,
|
control::{
|
||||||
|
BackpackLeftPressed, BackpackRightPressed, BackpackSwapPressed, BackpackTogglePressed,
|
||||||
|
},
|
||||||
protocol::{ClientToController, PlaySound},
|
protocol::{ClientToController, PlaySound},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,23 +23,23 @@ pub fn plugin(app: &mut App) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn swap_head_inputs(
|
fn swap_head_inputs(
|
||||||
backpacks: Query<Ref<Backpack>>,
|
backpacks: Query<Ref<Backpack>>,
|
||||||
clients: ClientToController,
|
clients: ClientToController,
|
||||||
mut inputs: MessageReader<FromClient<ControlState>>,
|
mut backpack_toggles: MessageReader<FromClient<BackpackTogglePressed>>,
|
||||||
|
mut backpack_lefts: MessageReader<FromClient<BackpackLeftPressed>>,
|
||||||
|
mut backpack_rights: MessageReader<FromClient<BackpackRightPressed>>,
|
||||||
|
mut backpack_swaps: MessageReader<FromClient<BackpackSwapPressed>>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut state: Single<&mut BackpackUiState>,
|
mut state: Single<&mut BackpackUiState>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
for controls in inputs.read() {
|
for _ in backpack_toggles.read() {
|
||||||
let player = clients.get_controller(controls.client_id);
|
|
||||||
let backpack = backpacks.get(player).unwrap();
|
|
||||||
|
|
||||||
if state.count == 0 {
|
if state.count == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if controls.backpack_toggle {
|
|
||||||
state.open = !state.open;
|
state.open = !state.open;
|
||||||
commands.server_trigger(ToClients {
|
commands.server_trigger(ToClients {
|
||||||
mode: SendMode::Broadcast,
|
mode: SendMode::Broadcast,
|
||||||
@@ -45,24 +47,17 @@ fn swap_head_inputs(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for press in backpack_lefts.read() {
|
||||||
|
let player = clients.get_controller(press.client_id);
|
||||||
|
let backpack = backpacks.get(player).unwrap();
|
||||||
|
|
||||||
if !state.open {
|
if !state.open {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut changed = false;
|
if state.current_slot > 0 {
|
||||||
if controls.backpack_left && state.current_slot > 0 {
|
|
||||||
state.current_slot -= 1;
|
state.current_slot -= 1;
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if controls.backpack_right && state.current_slot < state.count.saturating_sub(1) {
|
|
||||||
state.current_slot += 1;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if controls.backpack_swap {
|
|
||||||
commands.trigger(BackbackSwapEvent(state.current_slot));
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
commands.server_trigger(ToClients {
|
commands.server_trigger(ToClients {
|
||||||
mode: SendMode::Broadcast,
|
mode: SendMode::Broadcast,
|
||||||
message: PlaySound::Selection,
|
message: PlaySound::Selection,
|
||||||
@@ -70,6 +65,33 @@ fn swap_head_inputs(
|
|||||||
sync(&backpack, &mut state, time.elapsed_secs());
|
sync(&backpack, &mut state, time.elapsed_secs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for press in backpack_rights.read() {
|
||||||
|
let player = clients.get_controller(press.client_id);
|
||||||
|
let backpack = backpacks.get(player).unwrap();
|
||||||
|
|
||||||
|
if !state.open {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.current_slot < state.count.saturating_sub(1) {
|
||||||
|
state.current_slot += 1;
|
||||||
|
|
||||||
|
commands.server_trigger(ToClients {
|
||||||
|
mode: SendMode::Broadcast,
|
||||||
|
message: PlaySound::Selection,
|
||||||
|
});
|
||||||
|
sync(&backpack, &mut state, time.elapsed_secs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in backpack_swaps.read() {
|
||||||
|
if !state.open {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.trigger(BackbackSwapEvent(state.current_slot));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_on_change(
|
fn sync_on_change(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use bevy_replicon::prelude::{Replicated, SendMode, ServerTriggerExt, ToClients};
|
|||||||
use shared::{
|
use shared::{
|
||||||
backpack::{Backpack, backpack_ui::BackpackUiState},
|
backpack::{Backpack, backpack_ui::BackpackUiState},
|
||||||
camera::{CameraArmRotation, CameraTarget},
|
camera::{CameraArmRotation, CameraTarget},
|
||||||
cash::CashResource,
|
cash::CashInventory,
|
||||||
character::AnimatedCharacter,
|
character::AnimatedCharacter,
|
||||||
control::{Inputs, controller_common::PlayerCharacterController},
|
control::{Inputs, controller_common::PlayerCharacterController},
|
||||||
global_observer,
|
global_observer,
|
||||||
@@ -46,7 +46,7 @@ pub fn spawn(
|
|||||||
Some(HeadState::new(9, heads_db.as_ref())),
|
Some(HeadState::new(9, heads_db.as_ref())),
|
||||||
]),
|
]),
|
||||||
Hitpoints::new(100),
|
Hitpoints::new(100),
|
||||||
CashResource::default(),
|
CashInventory::default(),
|
||||||
CameraTarget,
|
CameraTarget,
|
||||||
transform,
|
transform,
|
||||||
Visibility::default(),
|
Visibility::default(),
|
||||||
|
|||||||
@@ -14,18 +14,10 @@ use crate::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use crate::{
|
use crate::{
|
||||||
aim::AimTarget,
|
aim::AimTarget, character::CharacterHierarchy, control::Inputs, head::ActiveHead,
|
||||||
character::CharacterHierarchy,
|
heads::ActiveHeads, heads_database::HeadsDatabase, player::Player,
|
||||||
control::{ControlState, Inputs},
|
|
||||||
head::ActiveHead,
|
|
||||||
heads::ActiveHeads,
|
|
||||||
heads_database::HeadsDatabase,
|
|
||||||
player::Player,
|
|
||||||
protocol::ClientToController,
|
|
||||||
};
|
};
|
||||||
use bevy::{light::NotShadowCaster, prelude::*};
|
use bevy::{light::NotShadowCaster, prelude::*};
|
||||||
#[cfg(feature = "server")]
|
|
||||||
use bevy_replicon::prelude::FromClient;
|
|
||||||
use bevy_replicon::prelude::{ClientState, SendMode, ServerTriggerExt, ToClients};
|
use bevy_replicon::prelude::{ClientState, SendMode, ServerTriggerExt, ToClients};
|
||||||
use bevy_sprite3d::Sprite3d;
|
use bevy_sprite3d::Sprite3d;
|
||||||
pub use healing::Healing;
|
pub use healing::Healing;
|
||||||
@@ -194,20 +186,10 @@ fn explode_projectiles(mut commands: Commands, query: Query<(Entity, &ExplodingP
|
|||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn on_trigger_state(
|
fn on_trigger_state(
|
||||||
mut res: ResMut<TriggerStateRes>,
|
mut res: ResMut<TriggerStateRes>,
|
||||||
players: Query<&ActiveHead, With<Player>>,
|
players: Query<(&ActiveHead, &Inputs), With<Player>>,
|
||||||
clients: ClientToController,
|
|
||||||
mut controls: MessageReader<FromClient<ControlState>>,
|
|
||||||
headdb: Res<HeadsDatabase>,
|
|
||||||
time: Res<Time>,
|
|
||||||
) {
|
) {
|
||||||
for controls in controls.read() {
|
for (_, inputs) in players.iter() {
|
||||||
let player = clients.get_controller(controls.client_id);
|
res.active = inputs.trigger;
|
||||||
let player_head = players.get(player).unwrap();
|
|
||||||
res.active = controls.trigger;
|
|
||||||
if controls.just_triggered {
|
|
||||||
let head_stats = headdb.head_stats(player_head.0);
|
|
||||||
res.next_trigger_timestamp = time.elapsed_secs() + head_stats.shoot_offset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::GameState;
|
use crate::GameState;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
|
use crate::control::Inputs;
|
||||||
|
#[cfg(feature = "client")]
|
||||||
use crate::physics_layers::GameLayer;
|
use crate::physics_layers::GameLayer;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
use crate::{
|
use crate::player::LocalPlayer;
|
||||||
control::{ControlState, LookDirMovement},
|
#[cfg(feature = "client")]
|
||||||
loading_assets::UIAssets,
|
use crate::{control::LookDirMovement, loading_assets::UIAssets};
|
||||||
};
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
use avian3d::prelude::SpatialQuery;
|
use avian3d::prelude::SpatialQuery;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
@@ -80,8 +81,11 @@ fn startup(mut commands: Commands) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
fn update_look_around(controls: Res<ControlState>, mut cam_state: ResMut<CameraState>) {
|
fn update_look_around(
|
||||||
let look_around = controls.view_mode;
|
inputs: Single<&Inputs, With<LocalPlayer>>,
|
||||||
|
mut cam_state: ResMut<CameraState>,
|
||||||
|
) {
|
||||||
|
let look_around = inputs.view_mode;
|
||||||
|
|
||||||
if look_around != cam_state.look_around {
|
if look_around != cam_state.look_around {
|
||||||
cam_state.look_around = look_around;
|
cam_state.look_around = look_around;
|
||||||
@@ -176,11 +180,11 @@ fn update(
|
|||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
fn rotate_view(
|
fn rotate_view(
|
||||||
controls: Res<ControlState>,
|
inputs: Single<&Inputs, With<LocalPlayer>>,
|
||||||
look_dir: Res<LookDirMovement>,
|
look_dir: Res<LookDirMovement>,
|
||||||
mut cam: Single<&mut CameraRotationInput>,
|
mut cam: Single<&mut CameraRotationInput>,
|
||||||
) {
|
) {
|
||||||
if !controls.view_mode {
|
if !inputs.view_mode {
|
||||||
cam.x = 0.0;
|
cam.x = 0.0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ pub struct Cash;
|
|||||||
struct CashText;
|
struct CashText;
|
||||||
|
|
||||||
#[derive(Component, Reflect, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Component, Reflect, Default, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct CashResource {
|
pub struct CashInventory {
|
||||||
pub cash: i32,
|
pub cash: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ pub fn plugin(app: &mut App) {
|
|||||||
fn on_cash_collect(
|
fn on_cash_collect(
|
||||||
_trigger: On<CashCollectEvent>,
|
_trigger: On<CashCollectEvent>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut cash: Single<&mut CashResource>,
|
mut cash: Single<&mut CashInventory>,
|
||||||
) {
|
) {
|
||||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ fn rotate(time: Res<Time>, mut query: Query<&mut Rotation, With<Cash>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_ui(
|
fn update_ui(
|
||||||
cash: Single<&CashResource, Changed<CashResource>>,
|
cash: Single<&CashInventory, Changed<CashInventory>>,
|
||||||
text: Query<Entity, With<CashText>>,
|
text: Query<Entity, With<CashText>>,
|
||||||
mut writer: TextUiWriter,
|
mut writer: TextUiWriter,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
cash::CashResource, control::ControlState, hitpoints::Hitpoints, player::Player,
|
cash::CashInventory,
|
||||||
protocol::PlaySound,
|
control::CashHealPressed,
|
||||||
|
hitpoints::Hitpoints,
|
||||||
|
player::Player,
|
||||||
|
protocol::{ClientToController, PlaySound},
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_replicon::prelude::{FromClient, SendMode, ServerTriggerExt, ToClients};
|
use bevy_replicon::prelude::{FromClient, SendMode, ServerTriggerExt, ToClients};
|
||||||
@@ -17,15 +20,13 @@ struct HealAction {
|
|||||||
|
|
||||||
fn on_heal_trigger(
|
fn on_heal_trigger(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut cash: Single<&mut CashResource>,
|
controllers: ClientToController,
|
||||||
mut query: Query<&mut Hitpoints, With<Player>>,
|
mut query: Query<(&mut Hitpoints, &mut CashInventory), With<Player>>,
|
||||||
mut controls: MessageReader<FromClient<ControlState>>,
|
mut inputs: MessageReader<FromClient<CashHealPressed>>,
|
||||||
) {
|
) {
|
||||||
for controls in controls.read() {
|
for press in inputs.read() {
|
||||||
for mut hp in query.iter_mut() {
|
let controller = controllers.get_controller(press.client_id);
|
||||||
if !controls.cash_heal {
|
let (mut hp, mut cash) = query.get_mut(controller).unwrap();
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if hp.max() || cash.cash == 0 {
|
if hp.max() || cash.cash == 0 {
|
||||||
return;
|
return;
|
||||||
@@ -44,7 +45,6 @@ fn on_heal_trigger(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn heal(cash: i32, damage: u32) -> HealAction {
|
fn heal(cash: i32, damage: u32) -> HealAction {
|
||||||
let cost = (damage as f32 / 10. * 25.) as i32;
|
let cost = (damage as f32 / 10. * 25.) as i32;
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ fn setup_once_loaded(
|
|||||||
#[cfg(feature = "dbg")]
|
#[cfg(feature = "dbg")]
|
||||||
fn debug_show_projectile_origin_and_trial(
|
fn debug_show_projectile_origin_and_trial(
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
query: Query<&GlobalTransform, Or<(With<ProjectileOrigin>, With<Trail>)>>,
|
query: Query<&GlobalTransform, Or<(With<ProjectileOrigin>, With<crate::utils::trail::Trail>)>>,
|
||||||
) {
|
) {
|
||||||
for projectile_origin in query.iter() {
|
for projectile_origin in query.iter() {
|
||||||
gizmos.sphere(
|
gizmos.sphere(
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
use crate::{
|
use crate::{
|
||||||
control::{ControlState, LookDirMovement},
|
control::LookDirMovement,
|
||||||
player::PlayerBodyMesh,
|
player::{LocalPlayer, PlayerBodyMesh},
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_replicon::prelude::ClientState;
|
use bevy_replicon::prelude::ClientState;
|
||||||
@@ -36,17 +36,24 @@ impl Plugin for CharacterControllerPlugin {
|
|||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
fn rotate_view(
|
fn rotate_view(
|
||||||
controls: Res<ControlState>,
|
controller: Single<(&Inputs, &Children), With<LocalPlayer>>,
|
||||||
|
mut player_mesh: Query<&mut Transform, With<PlayerBodyMesh>>,
|
||||||
look_dir: Res<LookDirMovement>,
|
look_dir: Res<LookDirMovement>,
|
||||||
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
|
|
||||||
) {
|
) {
|
||||||
for mut tr in player.iter_mut() {
|
let (inputs, children) = controller.into_inner();
|
||||||
if controls.view_mode {
|
|
||||||
continue;
|
if inputs.view_mode {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.rotate_y(look_dir.0.x * -0.001);
|
children.iter().find(|&child| {
|
||||||
|
if let Ok(mut body_transform) = player_mesh.get_mut(child) {
|
||||||
|
body_transform.rotate_y(look_dir.0.x * -0.001);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_controls(
|
fn apply_controls(
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use crate::{
|
|||||||
GameState,
|
GameState,
|
||||||
head::ActiveHead,
|
head::ActiveHead,
|
||||||
heads_database::{HeadControls, HeadsDatabase},
|
heads_database::{HeadControls, HeadsDatabase},
|
||||||
player::{LocalPlayerId, Player},
|
player::Player,
|
||||||
protocol::{ClientToController, PlayerIdMap},
|
protocol::ClientToController,
|
||||||
};
|
};
|
||||||
use bevy::{ecs::entity::MapEntities, prelude::*};
|
use bevy::{ecs::entity::MapEntities, prelude::*};
|
||||||
use bevy_replicon::{
|
use bevy_replicon::{
|
||||||
@@ -28,11 +28,13 @@ pub enum ControllerSet {
|
|||||||
pub struct SelectedController(pub ControllerSet);
|
pub struct SelectedController(pub ControllerSet);
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.register_type::<ControllerSettings>();
|
app.register_type::<ControllerSettings>()
|
||||||
app.register_type::<LookDirMovement>();
|
.register_type::<LookDirMovement>()
|
||||||
app.register_type::<Inputs>();
|
.register_type::<Inputs>();
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
app.register_type::<LocalInputs>();
|
||||||
|
|
||||||
app.init_resource::<ControlState>();
|
|
||||||
app.init_resource::<LookDirMovement>();
|
app.init_resource::<LookDirMovement>();
|
||||||
app.init_resource::<SelectedController>();
|
app.init_resource::<SelectedController>();
|
||||||
|
|
||||||
@@ -58,22 +60,17 @@ pub fn plugin(app: &mut App) {
|
|||||||
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
(
|
collect_player_inputs
|
||||||
collect_player_inputs.run_if(in_state(ClientState::Disconnected)),
|
.run_if(in_state(ClientState::Disconnected).and(in_state(GameState::Playing)))
|
||||||
update_local_player_inputs
|
|
||||||
.run_if(resource_exists::<LocalPlayerId>)
|
|
||||||
.run_if(in_state(ClientState::Connected)),
|
|
||||||
)
|
|
||||||
.chain()
|
|
||||||
.run_if(in_state(GameState::Playing))
|
|
||||||
.after(ClientSystems::Receive),
|
.after(ClientSystems::Receive),
|
||||||
);
|
);
|
||||||
app.add_systems(Update, head_change.run_if(in_state(GameState::Playing)));
|
app.add_systems(Update, head_change.run_if(in_state(GameState::Playing)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Split this into an enum of individual input commands, e.g. `JustJumped`
|
/// The continuous inputs of a client for a tick. The instant inputs are sent via messages like `BackpackTogglePressed`.
|
||||||
#[derive(Resource, Debug, Clone, Copy, Message, PartialEq, Serialize, Deserialize, Reflect)]
|
#[derive(Component, Clone, Copy, Debug, Serialize, Deserialize, Reflect)]
|
||||||
pub struct ControlState {
|
#[reflect(Component, Default)]
|
||||||
|
pub struct Inputs {
|
||||||
/// Movement direction with a maximum length of 1.0
|
/// Movement direction with a maximum length of 1.0
|
||||||
pub move_dir: Vec2,
|
pub move_dir: Vec2,
|
||||||
/// The current direction that the character is facing
|
/// The current direction that the character is facing
|
||||||
@@ -83,17 +80,9 @@ pub struct ControlState {
|
|||||||
/// Determines if the camera can rotate freely around the player
|
/// Determines if the camera can rotate freely around the player
|
||||||
pub view_mode: bool,
|
pub view_mode: bool,
|
||||||
pub trigger: bool,
|
pub trigger: bool,
|
||||||
pub just_triggered: bool,
|
|
||||||
pub select_left: bool,
|
|
||||||
pub select_right: bool,
|
|
||||||
pub backpack_toggle: bool,
|
|
||||||
pub backpack_swap: bool,
|
|
||||||
pub backpack_left: bool,
|
|
||||||
pub backpack_right: bool,
|
|
||||||
pub cash_heal: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ControlState {
|
impl Default for Inputs {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
move_dir: Default::default(),
|
move_dir: Default::default(),
|
||||||
@@ -101,25 +90,44 @@ impl Default for ControlState {
|
|||||||
jump: Default::default(),
|
jump: Default::default(),
|
||||||
view_mode: Default::default(),
|
view_mode: Default::default(),
|
||||||
trigger: Default::default(),
|
trigger: Default::default(),
|
||||||
just_triggered: Default::default(),
|
|
||||||
select_left: Default::default(),
|
|
||||||
select_right: Default::default(),
|
|
||||||
backpack_toggle: Default::default(),
|
|
||||||
backpack_swap: Default::default(),
|
|
||||||
backpack_left: Default::default(),
|
|
||||||
backpack_right: Default::default(),
|
|
||||||
cash_heal: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapEntities for ControlState {
|
impl MapEntities for Inputs {
|
||||||
fn map_entities<E: EntityMapper>(&mut self, _entity_mapper: &mut E) {}
|
fn map_entities<E: EntityMapper>(&mut self, _entity_mapper: &mut E) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Default, Deref, DerefMut, Reflect, Serialize, Deserialize)]
|
/// A message to tell the server what inputs the client pressed this tick
|
||||||
#[reflect(Component, Default)]
|
#[derive(Debug, Clone, Copy, Message, Serialize, Deserialize, Reflect)]
|
||||||
pub struct Inputs(pub ControlState);
|
pub struct ClientInputs(pub Inputs);
|
||||||
|
|
||||||
|
/// A cache to collect inputs into clientside, so that they don't get overwritten by replication from the server
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
#[derive(Component, Default, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct LocalInputs(pub Inputs);
|
||||||
|
|
||||||
|
#[derive(Message, Serialize, Deserialize)]
|
||||||
|
pub struct SelectLeftPressed;
|
||||||
|
|
||||||
|
#[derive(Message, Serialize, Deserialize)]
|
||||||
|
pub struct SelectRightPressed;
|
||||||
|
|
||||||
|
#[derive(Message, Serialize, Deserialize)]
|
||||||
|
pub struct BackpackTogglePressed;
|
||||||
|
|
||||||
|
#[derive(Message, Serialize, Deserialize)]
|
||||||
|
pub struct BackpackSwapPressed;
|
||||||
|
|
||||||
|
#[derive(Message, Serialize, Deserialize)]
|
||||||
|
pub struct BackpackLeftPressed;
|
||||||
|
|
||||||
|
#[derive(Message, Serialize, Deserialize)]
|
||||||
|
pub struct BackpackRightPressed;
|
||||||
|
|
||||||
|
#[derive(Message, Serialize, Deserialize)]
|
||||||
|
pub struct CashHealPressed;
|
||||||
|
|
||||||
#[derive(Resource, Default, Reflect)]
|
#[derive(Resource, Default, Reflect)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
@@ -147,29 +155,16 @@ pub struct ControllerSwitchEvent {
|
|||||||
fn collect_player_inputs(
|
fn collect_player_inputs(
|
||||||
mut players: Query<&mut Inputs>,
|
mut players: Query<&mut Inputs>,
|
||||||
clients: ClientToController,
|
clients: ClientToController,
|
||||||
mut input_messages: MessageReader<FromClient<ControlState>>,
|
mut input_messages: MessageReader<FromClient<ClientInputs>>,
|
||||||
) {
|
) {
|
||||||
for msg in input_messages.read() {
|
for msg in input_messages.read() {
|
||||||
let player = clients.get_controller(msg.client_id);
|
let player = clients.get_controller(msg.client_id);
|
||||||
let mut inputs = players.get_mut(player).unwrap();
|
let mut inputs = players.get_mut(player).unwrap();
|
||||||
|
|
||||||
inputs.0 = msg.message;
|
*inputs = msg.message.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overwrite the input cache replicated to the local player with the actual current inputs
|
|
||||||
fn update_local_player_inputs(
|
|
||||||
mut players: Query<&mut Inputs>,
|
|
||||||
player_ids: Res<PlayerIdMap>,
|
|
||||||
local_id: Res<LocalPlayerId>,
|
|
||||||
control_state: Res<ControlState>,
|
|
||||||
) {
|
|
||||||
let player = player_ids.get(&local_id.id).unwrap();
|
|
||||||
let mut inputs = players.get_mut(*player).unwrap();
|
|
||||||
|
|
||||||
inputs.0 = *control_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn head_change(
|
fn head_change(
|
||||||
//TODO: needs a 'LocalPlayer' at some point for multiplayer
|
//TODO: needs a 'LocalPlayer' at some point for multiplayer
|
||||||
query: Query<(Entity, &ActiveHead), (Changed<ActiveHead>, With<Player>)>,
|
query: Query<(Entity, &ActiveHead), (Changed<ActiveHead>, With<Player>)>,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use crate::animation::AnimationFlags;
|
use crate::protocol::PlaySound;
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState,
|
||||||
backpack::{BackbackSwapEvent, Backpack},
|
backpack::{BackbackSwapEvent, Backpack},
|
||||||
@@ -9,7 +9,11 @@ use crate::{
|
|||||||
player::Player,
|
player::Player,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use crate::{control::ControlState, protocol::PlaySound};
|
use crate::{
|
||||||
|
animation::AnimationFlags,
|
||||||
|
control::{SelectLeftPressed, SelectRightPressed},
|
||||||
|
protocol::ClientToController,
|
||||||
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use bevy_replicon::prelude::FromClient;
|
use bevy_replicon::prelude::FromClient;
|
||||||
@@ -249,24 +253,17 @@ fn reload(
|
|||||||
fn on_select_active_head(
|
fn on_select_active_head(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>,
|
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>,
|
||||||
mut controls: MessageReader<FromClient<ControlState>>,
|
mut select_lefts: MessageReader<FromClient<SelectLeftPressed>>,
|
||||||
|
mut select_rights: MessageReader<FromClient<SelectRightPressed>>,
|
||||||
|
controllers: ClientToController,
|
||||||
) {
|
) {
|
||||||
for controls in controls.read() {
|
for press in select_lefts.read() {
|
||||||
for (mut active_heads, mut hp) in query.iter_mut() {
|
|
||||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
||||||
|
|
||||||
if !controls.select_right && !controls.select_left {
|
let player = controllers.get_controller(press.client_id);
|
||||||
continue;
|
let (mut active_heads, mut hp) = query.get_mut(player).unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
if controls.select_right {
|
active_heads.selected_slot = (active_heads.selected_slot + (HEAD_SLOTS - 1)) % HEAD_SLOTS;
|
||||||
active_heads.selected_slot = (active_heads.selected_slot + 1) % HEAD_SLOTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if controls.select_left {
|
|
||||||
active_heads.selected_slot =
|
|
||||||
(active_heads.selected_slot + (HEAD_SLOTS - 1)) % HEAD_SLOTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.server_trigger(ToClients {
|
commands.server_trigger(ToClients {
|
||||||
mode: SendMode::Broadcast,
|
mode: SendMode::Broadcast,
|
||||||
@@ -282,6 +279,28 @@ fn on_select_active_head(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for press in select_rights.read() {
|
||||||
|
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
||||||
|
|
||||||
|
let player = controllers.get_controller(press.client_id);
|
||||||
|
let (mut active_heads, mut hp) = query.get_mut(player).unwrap();
|
||||||
|
|
||||||
|
active_heads.selected_slot = (active_heads.selected_slot + 1) % HEAD_SLOTS;
|
||||||
|
|
||||||
|
commands.server_trigger(ToClients {
|
||||||
|
mode: SendMode::Broadcast,
|
||||||
|
message: PlaySound::Selection,
|
||||||
|
});
|
||||||
|
|
||||||
|
if active_heads.head(active_heads.selected_slot).is_some() {
|
||||||
|
active_heads.current_slot = active_heads.selected_slot;
|
||||||
|
hp.set_health(active_heads.current().unwrap().health);
|
||||||
|
|
||||||
|
commands.trigger(HeadChanged(
|
||||||
|
active_heads.heads[active_heads.current_slot].unwrap().head,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#[cfg(feature = "client")]
|
||||||
|
use crate::control::LocalInputs;
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState,
|
||||||
cash::{Cash, CashCollectEvent},
|
cash::{Cash, CashCollectEvent},
|
||||||
@@ -17,6 +19,12 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[require(HedzCharacter, DebugInput = DebugInput)]
|
#[require(HedzCharacter, DebugInput = DebugInput)]
|
||||||
pub struct Player;
|
pub struct Player;
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
#[derive(Component, Debug, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
#[require(LocalInputs)]
|
||||||
|
pub struct LocalPlayer;
|
||||||
|
|
||||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||||
#[require(Transform, Visibility)]
|
#[require(Transform, Visibility)]
|
||||||
pub struct PlayerBodyMesh;
|
pub struct PlayerBodyMesh;
|
||||||
@@ -25,13 +33,6 @@ pub struct PlayerBodyMesh;
|
|||||||
#[derive(Component, Clone, Copy)]
|
#[derive(Component, Clone, Copy)]
|
||||||
pub struct ClientPlayerId(pub PlayerId);
|
pub struct ClientPlayerId(pub PlayerId);
|
||||||
|
|
||||||
/// Client-side only; stores this client's id
|
|
||||||
#[derive(Resource, Reflect)]
|
|
||||||
#[reflect(Resource)]
|
|
||||||
pub struct LocalPlayerId {
|
|
||||||
pub id: PlayerId,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
OnEnter(GameState::Playing),
|
OnEnter(GameState::Playing),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
#[cfg(feature = "client")]
|
||||||
|
use crate::player::LocalPlayer;
|
||||||
use crate::{
|
use crate::{
|
||||||
loading_assets::{GameAssets, HeadDropAssets},
|
loading_assets::{GameAssets, HeadDropAssets},
|
||||||
player::{ClientPlayerId, LocalPlayerId},
|
player::ClientPlayerId,
|
||||||
protocol::TbMapEntityMapping,
|
protocol::TbMapEntityMapping,
|
||||||
};
|
};
|
||||||
use avian3d::prelude::Collider;
|
use avian3d::prelude::Collider;
|
||||||
@@ -101,7 +103,8 @@ pub struct PlayerIdMap {
|
|||||||
pub struct ClientToController<'w, 's> {
|
pub struct ClientToController<'w, 's> {
|
||||||
clients: Query<'w, 's, &'static ClientPlayerId>,
|
clients: Query<'w, 's, &'static ClientPlayerId>,
|
||||||
players: Res<'w, PlayerIdMap>,
|
players: Res<'w, PlayerIdMap>,
|
||||||
local_id: Option<Res<'w, LocalPlayerId>>,
|
#[cfg(feature = "client")]
|
||||||
|
local_id: Option<Single<'w, 's, &'static PlayerId, With<LocalPlayer>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientToController<'_, '_> {
|
impl ClientToController<'_, '_> {
|
||||||
@@ -109,7 +112,16 @@ impl ClientToController<'_, '_> {
|
|||||||
pub fn get_controller(&self, client: ClientId) -> Entity {
|
pub fn get_controller(&self, client: ClientId) -> Entity {
|
||||||
let player_id = match client.entity() {
|
let player_id = match client.entity() {
|
||||||
Some(client) => self.clients.get(client).unwrap().0,
|
Some(client) => self.clients.get(client).unwrap().0,
|
||||||
None => self.local_id.as_ref().unwrap().id,
|
None => {
|
||||||
|
#[cfg(not(feature = "client"))]
|
||||||
|
{
|
||||||
|
error!("attempted to look up the local controller on a dedicated server");
|
||||||
|
PlayerId { id: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
***self.local_id.as_ref().unwrap()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
*self.players.get(&player_id).unwrap()
|
*self.players.get(&player_id).unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ use crate::{
|
|||||||
animation::AnimationFlags,
|
animation::AnimationFlags,
|
||||||
backpack::{Backpack, backpack_ui::BackpackUiState},
|
backpack::{Backpack, backpack_ui::BackpackUiState},
|
||||||
camera::{CameraArmRotation, CameraTarget},
|
camera::{CameraArmRotation, CameraTarget},
|
||||||
cash::CashResource,
|
cash::CashInventory,
|
||||||
character::{AnimatedCharacter, HedzCharacter},
|
character::{AnimatedCharacter, HedzCharacter},
|
||||||
control::{
|
control::{
|
||||||
ControlState, ControllerSettings, Inputs,
|
BackpackLeftPressed, BackpackRightPressed, BackpackSwapPressed, BackpackTogglePressed,
|
||||||
|
CashHealPressed, ClientInputs, ControllerSettings, Inputs, SelectLeftPressed,
|
||||||
|
SelectRightPressed,
|
||||||
controller_common::{MovementSpeedFactor, PlayerCharacterController},
|
controller_common::{MovementSpeedFactor, PlayerCharacterController},
|
||||||
},
|
},
|
||||||
cutscene::StartCutscene,
|
cutscene::StartCutscene,
|
||||||
@@ -49,7 +51,14 @@ pub mod events;
|
|||||||
pub mod messages;
|
pub mod messages;
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.add_client_message::<ControlState>(Channel::Unreliable);
|
app.add_client_message::<ClientInputs>(Channel::Unreliable)
|
||||||
|
.add_client_message::<SelectLeftPressed>(Channel::Ordered)
|
||||||
|
.add_client_message::<SelectRightPressed>(Channel::Ordered)
|
||||||
|
.add_client_message::<BackpackTogglePressed>(Channel::Ordered)
|
||||||
|
.add_client_message::<BackpackSwapPressed>(Channel::Ordered)
|
||||||
|
.add_client_message::<BackpackLeftPressed>(Channel::Ordered)
|
||||||
|
.add_client_message::<BackpackRightPressed>(Channel::Ordered)
|
||||||
|
.add_client_message::<CashHealPressed>(Channel::Ordered);
|
||||||
|
|
||||||
app.add_client_event::<ClientEnteredPlaying>(Channel::Ordered);
|
app.add_client_event::<ClientEnteredPlaying>(Channel::Ordered);
|
||||||
|
|
||||||
@@ -89,7 +98,7 @@ pub fn plugin(app: &mut App) {
|
|||||||
.replicate::<Billboard>()
|
.replicate::<Billboard>()
|
||||||
.replicate_once::<CameraArmRotation>()
|
.replicate_once::<CameraArmRotation>()
|
||||||
.replicate_once::<CameraTarget>()
|
.replicate_once::<CameraTarget>()
|
||||||
.replicate::<CashResource>()
|
.replicate::<CashInventory>()
|
||||||
.replicate_once::<HedzCharacter>()
|
.replicate_once::<HedzCharacter>()
|
||||||
.replicate_once::<Healing>()
|
.replicate_once::<Healing>()
|
||||||
.replicate::<Hitpoints>()
|
.replicate::<Hitpoints>()
|
||||||
|
|||||||
@@ -91,7 +91,11 @@ fn attach_trail(
|
|||||||
))
|
))
|
||||||
.id();
|
.id();
|
||||||
|
|
||||||
commands.entity(entity).add_child(id);
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.queue_silenced(move |mut world: EntityWorldMut| {
|
||||||
|
world.add_child(id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user