Input replication (#62)

This commit is contained in:
PROMETHIA-27
2025-08-25 03:41:28 -04:00
committed by GitHub
parent c650924d68
commit 7f6c00b5d6
20 changed files with 345 additions and 280 deletions

View File

@@ -27,6 +27,7 @@ happy_feet = { git = "https://github.com/atornity/happy_feet.git", rev = "1b24ed
"serde", "serde",
] } ] }
lightyear = { version = "0.22.4", default-features = false, features = [ lightyear = { version = "0.22.4", default-features = false, features = [
"input_native",
"interpolation", "interpolation",
"netcode", "netcode",
"prediction", "prediction",

View File

@@ -2,9 +2,12 @@ use bevy::prelude::*;
use lightyear::{ use lightyear::{
connection::client::ClientState, connection::client::ClientState,
netcode::Key, netcode::Key,
prelude::{client::NetcodeConfig, *}, prelude::{client::NetcodeConfig, input::native::InputMarker, *},
};
use shared::{
GameState, control::ControlState, global_observer, heads_database::HeadsDatabase,
player::Player, tb_entities::SpawnPoint,
}; };
use shared::{GameState, heads_database::HeadsDatabase, tb_entities::SpawnPoint};
use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::net::{IpAddr, Ipv4Addr, SocketAddr};
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
@@ -13,6 +16,8 @@ pub fn plugin(app: &mut App) {
FixedUpdate, FixedUpdate,
spawn_disconnected_player.run_if(in_state(GameState::Playing)), spawn_disconnected_player.run_if(in_state(GameState::Playing)),
); );
global_observer!(app, temp_give_player_marker);
} }
fn temp_connect_on_startup(mut commands: Commands) -> Result { fn temp_connect_on_startup(mut commands: Commands) -> Result {
@@ -68,3 +73,9 @@ fn spawn_disconnected_player(
shared::player::spawn(commands, Entity::PLACEHOLDER, query, asset_server, heads_db) shared::player::spawn(commands, Entity::PLACEHOLDER, query, asset_server, heads_db)
} }
} }
fn temp_give_player_marker(trigger: Trigger<OnAdd, Player>, mut commands: Commands) {
commands
.entity(trigger.target())
.insert(InputMarker::<ControlState>::default());
}

View File

@@ -47,7 +47,7 @@ fn main() {
..default() ..default()
}) })
.set(bevy::log::LogPlugin { .set(bevy::log::LogPlugin {
filter: "info,lightyear_replication=warn".into(), filter: "info,lightyear_replication=off".into(),
level: bevy::log::Level::INFO, level: bevy::log::Level::INFO,
// provide custom log layer to receive logging events // provide custom log layer to receive logging events
custom_layer: bevy_debug_log::log_capture_layer, custom_layer: bevy_debug_log::log_capture_layer,

View File

@@ -9,6 +9,7 @@ use crate::{
GameState, GameState,
aim::AimTarget, aim::AimTarget,
character::CharacterHierarchy, character::CharacterHierarchy,
control::ControlState,
global_observer, global_observer,
head::ActiveHead, head::ActiveHead,
heads::ActiveHeads, heads::ActiveHeads,
@@ -17,23 +18,18 @@ use crate::{
physics_layers::GameLayer, physics_layers::GameLayer,
player::{Player, PlayerBodyMesh}, player::{Player, PlayerBodyMesh},
sounds::PlaySound, sounds::PlaySound,
utils::{billboards::Billboard, sprite_3d_animation::AnimationTimer}, utils::{billboards::Billboard, commands::IsServer, sprite_3d_animation::AnimationTimer},
}; };
use bevy::{pbr::NotShadowCaster, prelude::*}; use bevy::{pbr::NotShadowCaster, prelude::*};
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams}; use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
pub use healing::Healing; pub use healing::Healing;
use healing::HealingStateChanged; use healing::HealingStateChanged;
use lightyear::{
connection::client::ClientState,
prelude::{Client, input::native::ActionState},
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Event, Reflect)]
pub enum TriggerState {
Active,
Inactive,
}
#[derive(Event, Reflect)]
pub struct TriggerCashHeal;
#[derive(Debug, Copy, Clone, PartialEq, Reflect, Default, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Reflect, Default, Serialize, Deserialize)]
pub enum HeadAbility { pub enum HeadAbility {
#[default] #[default]
@@ -117,24 +113,36 @@ pub fn plugin(app: &mut App) {
Update, Update,
(update, update_heal_ability).run_if(in_state(GameState::Playing)), (update, update_heal_ability).run_if(in_state(GameState::Playing)),
); );
app.add_systems(
FixedUpdate,
on_trigger_state.run_if(in_state(GameState::Playing)),
);
global_observer!(app, on_trigger_state);
global_observer!(app, build_explosion_sprite); global_observer!(app, build_explosion_sprite);
} }
fn on_trigger_state( fn on_trigger_state(
trigger: Trigger<TriggerState>,
mut res: ResMut<TriggerStateRes>, mut res: ResMut<TriggerStateRes>,
player_head: Single<&ActiveHead, With<Player>>, player: Query<(&ActiveHead, &ActionState<ControlState>), With<Player>>,
headdb: Res<HeadsDatabase>, headdb: Res<HeadsDatabase>,
time: Res<Time>, time: Res<Time>,
is_server: Option<Res<IsServer>>,
client: Query<&Client>,
) { ) {
res.active = matches!(trigger.event(), TriggerState::Active); if let Ok(client) = client.single()
if res.active { && (client.state == ClientState::Connected && is_server.is_none())
{
return;
}
for (player_head, controls) in player.iter() {
res.active = controls.trigger;
if controls.just_triggered {
let head_stats = headdb.head_stats(player_head.0); let head_stats = headdb.head_stats(player_head.0);
res.next_trigger_timestamp = time.elapsed_secs() + head_stats.shoot_offset; res.next_trigger_timestamp = time.elapsed_secs() + head_stats.shoot_offset;
} }
} }
}
fn update( fn update(
mut res: ResMut<TriggerStateRes>, mut res: ResMut<TriggerStateRes>,

View File

@@ -46,11 +46,9 @@ fn on_trigger_thrown(
let pos = state.pos; let pos = state.pos;
let vel = if let Some(target) = state.target { let vel = if let Some(target) = state.target
let t = query_transform && let Ok(t) = query_transform.get(target)
.get(target) {
.expect("target must have transform");
launch_velocity(pos, t.translation, SPEED, 9.81) launch_velocity(pos, t.translation, SPEED, 9.81)
.map(|(low, _)| low) .map(|(low, _)| low)
.unwrap() .unwrap()

View File

@@ -1,20 +1,13 @@
use super::{BackbackSwapEvent, Backpack, UiHeadState}; use super::{Backpack, UiHeadState};
use crate::{ use crate::{
GameState, HEDZ_GREEN, global_observer, heads::HeadsImages, loading_assets::UIAssets, GameState, HEDZ_GREEN, backpack::BackbackSwapEvent, control::ControlState, heads::HeadsImages,
sounds::PlaySound, loading_assets::UIAssets, sounds::PlaySound,
}; };
use bevy::{ecs::spawn::SpawnIter, prelude::*}; use bevy::{ecs::spawn::SpawnIter, prelude::*};
use lightyear::prelude::input::native::ActionState;
static HEAD_SLOTS: usize = 5; static HEAD_SLOTS: usize = 5;
#[derive(Event, Clone, Copy, Reflect, PartialEq)]
pub enum BackpackAction {
Left,
Right,
Swap,
OpenClose,
}
#[derive(Component, Default)] #[derive(Component, Default)]
struct BackpackMarker; struct BackpackMarker;
@@ -30,7 +23,8 @@ struct HeadImage(pub usize);
#[derive(Component, Default)] #[derive(Component, Default)]
struct HeadDamage(pub usize); struct HeadDamage(pub usize);
#[derive(Resource, Default, Debug)] #[derive(Resource, Default, Debug, Reflect)]
#[reflect(Resource, Default)]
struct BackpackUiState { struct BackpackUiState {
heads: [Option<UiHeadState>; 5], heads: [Option<UiHeadState>; 5],
scroll: usize, scroll: usize,
@@ -46,6 +40,7 @@ impl BackpackUiState {
} }
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.register_type::<BackpackUiState>();
app.init_resource::<BackpackUiState>(); app.init_resource::<BackpackUiState>();
app.add_systems(OnEnter(GameState::Playing), setup); app.add_systems(OnEnter(GameState::Playing), setup);
app.add_systems( app.add_systems(
@@ -53,8 +48,7 @@ pub fn plugin(app: &mut App) {
(update, sync_on_change, update_visibility, update_count) (update, sync_on_change, update_visibility, update_count)
.run_if(in_state(GameState::Playing)), .run_if(in_state(GameState::Playing)),
); );
app.add_systems(FixedUpdate, swap_head_inputs);
global_observer!(app, swap_head_inputs);
} }
fn setup(mut commands: Commands, assets: Res<UIAssets>) { fn setup(mut commands: Commands, assets: Res<UIAssets>) {
@@ -262,18 +256,17 @@ fn update(
} }
fn swap_head_inputs( fn swap_head_inputs(
trigger: Trigger<BackpackAction>, player: Query<(&ActionState<ControlState>, Ref<Backpack>)>,
backpack: Res<Backpack>,
mut commands: Commands, mut commands: Commands,
mut state: ResMut<BackpackUiState>, mut state: ResMut<BackpackUiState>,
time: Res<Time>, time: Res<Time>,
) { ) {
for (controls, backpack) in player.iter() {
if state.count == 0 { if state.count == 0 {
return; return;
} }
let action = *trigger.event(); if controls.backpack_toggle {
if action == BackpackAction::OpenClose {
state.open = !state.open; state.open = !state.open;
commands.trigger(PlaySound::Backpack { open: state.open }); commands.trigger(PlaySound::Backpack { open: state.open });
} }
@@ -283,15 +276,15 @@ fn swap_head_inputs(
} }
let mut changed = false; let mut changed = false;
if action == BackpackAction::Left && state.current_slot > 0 { if controls.backpack_left && state.current_slot > 0 {
state.current_slot -= 1; state.current_slot -= 1;
changed = true; changed = true;
} }
if action == BackpackAction::Right && state.current_slot < state.count.saturating_sub(1) { if controls.backpack_right && state.current_slot < state.count.saturating_sub(1) {
state.current_slot += 1; state.current_slot += 1;
changed = true; changed = true;
} }
if action == BackpackAction::Swap { if controls.backpack_swap {
commands.trigger(BackbackSwapEvent(state.current_slot)); commands.trigger(BackbackSwapEvent(state.current_slot));
} }
@@ -300,14 +293,21 @@ fn swap_head_inputs(
sync(&backpack, &mut state, time.elapsed_secs()); sync(&backpack, &mut state, time.elapsed_secs());
} }
} }
}
fn sync_on_change(backpack: Res<Backpack>, mut state: ResMut<BackpackUiState>, time: Res<Time>) { fn sync_on_change(
backpack: Query<Ref<Backpack>>,
mut state: ResMut<BackpackUiState>,
time: Res<Time>,
) {
for backpack in backpack.iter() {
if backpack.is_changed() || backpack.reloading() { if backpack.is_changed() || backpack.reloading() {
sync(&backpack, &mut state, time.elapsed_secs()); sync(&backpack, &mut state, time.elapsed_secs());
} }
} }
}
fn sync(backpack: &Res<Backpack>, state: &mut ResMut<BackpackUiState>, time: f32) { fn sync(backpack: &Backpack, state: &mut ResMut<BackpackUiState>, time: f32) {
state.count = backpack.heads.len(); state.count = backpack.heads.len();
state.scroll = state.scroll.min(state.count.saturating_sub(HEAD_SLOTS)); state.scroll = state.scroll.min(state.count.saturating_sub(HEAD_SLOTS));

View File

@@ -5,11 +5,12 @@ use crate::{
cash::CashCollectEvent, global_observer, head_drop::HeadCollected, heads::HeadState, cash::CashCollectEvent, global_observer, head_drop::HeadCollected, heads::HeadState,
heads_database::HeadsDatabase, heads_database::HeadsDatabase,
}; };
pub use backpack_ui::BackpackAction;
use bevy::prelude::*; use bevy::prelude::*;
use serde::{Deserialize, Serialize};
pub use ui_head_state::UiHeadState; pub use ui_head_state::UiHeadState;
#[derive(Resource, Default)] #[derive(Component, Default, Reflect, Serialize, Deserialize, PartialEq)]
#[reflect(Component)]
pub struct Backpack { pub struct Backpack {
pub heads: Vec<HeadState>, pub heads: Vec<HeadState>,
} }
@@ -38,7 +39,7 @@ impl Backpack {
pub struct BackbackSwapEvent(pub usize); pub struct BackbackSwapEvent(pub usize);
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.init_resource::<Backpack>(); app.register_type::<Backpack>();
app.add_plugins(backpack_ui::plugin); app.add_plugins(backpack_ui::plugin);
@@ -48,14 +49,18 @@ pub fn plugin(app: &mut App) {
fn on_head_collect( fn on_head_collect(
trigger: Trigger<HeadCollected>, trigger: Trigger<HeadCollected>,
mut cmds: Commands, mut cmds: Commands,
mut backpack: ResMut<Backpack>, mut backpack: Query<&mut Backpack>,
heads_db: Res<HeadsDatabase>, heads_db: Res<HeadsDatabase>,
) { ) -> Result {
let HeadCollected(head) = *trigger.event(); let HeadCollected(head) = *trigger.event();
let mut backpack = backpack.get_mut(trigger.target())?;
if backpack.contains(head) { if backpack.contains(head) {
cmds.trigger(CashCollectEvent); cmds.trigger(CashCollectEvent);
} else { } else {
backpack.insert(head, heads_db.as_ref()); backpack.insert(head, heads_db.as_ref());
} }
Ok(())
} }

View File

@@ -1,11 +1,12 @@
use crate::{ use crate::{
abilities::TriggerCashHeal, cash::CashResource, global_observer, hitpoints::Hitpoints, cash::CashResource, control::ControlState, hitpoints::Hitpoints, player::Player,
player::Player, sounds::PlaySound, sounds::PlaySound,
}; };
use bevy::prelude::*; use bevy::prelude::*;
use lightyear::prelude::input::native::ActionState;
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
global_observer!(app, on_heal_trigger); app.add_systems(FixedUpdate, on_heal_trigger);
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@@ -15,14 +16,14 @@ struct HealAction {
} }
fn on_heal_trigger( fn on_heal_trigger(
_trigger: Trigger<TriggerCashHeal>,
mut cmds: Commands, mut cmds: Commands,
mut cash: ResMut<CashResource>, mut cash: ResMut<CashResource>,
mut query: Query<&mut Hitpoints, With<Player>>, mut query: Query<(&mut Hitpoints, &ActionState<ControlState>), With<Player>>,
) { ) {
let Ok(mut hp) = query.single_mut() else { for (mut hp, controls) in query.iter_mut() {
return; if !controls.cash_heal {
}; continue;
}
if hp.max() || cash.cash == 0 { if hp.max() || cash.cash == 0 {
return; return;
@@ -37,6 +38,7 @@ fn on_heal_trigger(
//TODO: trigger ui cost animation //TODO: trigger ui cost animation
cmds.trigger(PlaySound::CashHeal); cmds.trigger(PlaySound::CashHeal);
} }
}
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;

View File

@@ -84,7 +84,7 @@ pub fn plugin(app: &mut App) {
fn spawn( fn spawn(
mut commands: Commands, mut commands: Commands,
query: Query<(Entity, &AnimatedCharacter), Added<AnimatedCharacter>>, query: Query<(Entity, &AnimatedCharacter), Changed<AnimatedCharacter>>,
gltf_assets: Res<Assets<Gltf>>, gltf_assets: Res<Assets<Gltf>>,
assets: Res<GameAssets>, assets: Res<GameAssets>,
heads_db: Res<HeadsDatabase>, heads_db: Res<HeadsDatabase>,

View File

@@ -3,7 +3,7 @@ use crate::{
GameState, GameState,
abilities::TriggerStateRes, abilities::TriggerStateRes,
animation::AnimationFlags, animation::AnimationFlags,
control::{Controls, SelectedController, controls::ControllerSettings}, control::{ControlState, SelectedController, controls::ControllerSettings},
head::ActiveHead, head::ActiveHead,
heads_database::{HeadControls, HeadsDatabase}, heads_database::{HeadControls, HeadsDatabase},
physics_layers::GameLayer, physics_layers::GameLayer,
@@ -16,7 +16,7 @@ use happy_feet::prelude::{
GroundFriction, Grounding, GroundingConfig, KinematicVelocity, MoveInput, SteppingBehaviour, GroundFriction, Grounding, GroundingConfig, KinematicVelocity, MoveInput, SteppingBehaviour,
SteppingConfig, SteppingConfig,
}; };
use lightyear::prelude::Replicated; use lightyear::prelude::{Replicated, input::native::ActionState};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
@@ -26,42 +26,36 @@ pub fn plugin(app: &mut App) {
app.add_systems( app.add_systems(
PreUpdate, PreUpdate,
reset_upon_switch reset_upon_switch.run_if(in_state(GameState::Playing)),
.run_if(in_state(GameState::Playing))
.before(ControllerSet::ApplyControlsRun)
.before(ControllerSet::ApplyControlsFly),
); );
app.add_systems( app.add_systems(
PreUpdate, PreUpdate,
set_animation_flags set_animation_flags.run_if(in_state(GameState::Playing)),
.run_if(in_state(GameState::Playing))
.after(ControllerSet::ApplyControlsRun)
.after(ControllerSet::ApplyControlsFly),
); );
app.add_systems( app.add_systems(
FixedPreUpdate, FixedUpdate,
decelerate.run_if(in_state(GameState::Playing)), decelerate
.after(ControllerSet::ApplyControlsRun)
.after(ControllerSet::ApplyControlsFly)
.run_if(in_state(GameState::Playing)),
); );
app.add_systems(Update, add_controller_bundle); app.add_systems(Update, add_controller_bundle);
} }
fn set_animation_flags( fn set_animation_flags(
controls: Res<Controls>,
trigger: Res<TriggerStateRes>, trigger: Res<TriggerStateRes>,
player: Single<(&Grounding, &mut AnimationFlags), (With<Player>, Without<Replicated>)>, mut player: Query<
(&Grounding, &mut AnimationFlags, &ActionState<ControlState>),
(With<Player>, Without<Replicated>),
>,
) { ) {
let mut direction = controls.keyboard_state.move_dir; for (grounding, mut flags, controls) in player.iter_mut() {
let direction = controls.move_dir;
let deadzone = 0.2; let deadzone = 0.2;
let (grounding, mut flags) = player.into_inner();
if let Some(gamepad) = controls.gamepad_state {
direction += gamepad.move_dir;
}
if flags.any_direction { if flags.any_direction {
if direction.length_squared() < deadzone { if direction.length_squared() < deadzone {
flags.any_direction = false; flags.any_direction = false;
@@ -70,9 +64,7 @@ fn set_animation_flags(
flags.any_direction = true; flags.any_direction = true;
} }
if flags.shooting != trigger.is_active() {
flags.shooting = trigger.is_active(); flags.shooting = trigger.is_active();
}
// `apply_controls` sets the jump flag when the player actually jumps. // `apply_controls` sets the jump flag when the player actually jumps.
// Unset the flag on hitting the ground // Unset the flag on hitting the ground
@@ -80,6 +72,7 @@ fn set_animation_flags(
flags.jumping = false; flags.jumping = false;
} }
} }
}
/// Reset the pitch and velocity of the character if the controller was switched. /// Reset the pitch and velocity of the character if the controller was switched.
pub fn reset_upon_switch( pub fn reset_upon_switch(

View File

@@ -2,6 +2,7 @@ use super::{ControlState, ControllerSet};
use crate::{GameState, control::controller_common::MovementSpeedFactor, player::PlayerBodyMesh}; use crate::{GameState, control::controller_common::MovementSpeedFactor, player::PlayerBodyMesh};
use bevy::prelude::*; use bevy::prelude::*;
use happy_feet::prelude::MoveInput; use happy_feet::prelude::MoveInput;
use lightyear::prelude::input::native::ActionState;
use std::f32::consts::PI; use std::f32::consts::PI;
pub struct CharacterControllerPlugin; pub struct CharacterControllerPlugin;
@@ -9,7 +10,7 @@ pub struct CharacterControllerPlugin;
impl Plugin for CharacterControllerPlugin { impl Plugin for CharacterControllerPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(
PreUpdate, FixedUpdate,
(rotate_rig, apply_controls) (rotate_rig, apply_controls)
.chain() .chain()
.in_set(ControllerSet::ApplyControlsFly) .in_set(ControllerSet::ApplyControlsFly)
@@ -19,16 +20,18 @@ impl Plugin for CharacterControllerPlugin {
} }
fn rotate_rig( fn rotate_rig(
mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>, actions: Query<&ActionState<ControlState>>,
controls: Res<ControlState>, mut player: Query<(&mut Transform, &ChildOf), With<PlayerBodyMesh>>,
) { ) {
for (mut rig_transform, child_of) in player.iter_mut() {
let controls = actions.get(child_of.parent()).unwrap();
if controls.view_mode { if controls.view_mode {
return; continue;
} }
let look_dir = controls.look_dir; let look_dir = controls.look_dir;
if let Some(ref mut rig_transform) = rig_transform_q {
// todo: Make consistent with the running controller // todo: Make consistent with the running controller
let sensitivity = 0.001; let sensitivity = 0.001;
let max_pitch = 35.0 * PI / 180.0; let max_pitch = 35.0 * PI / 180.0;

View File

@@ -8,13 +8,14 @@ use crate::{
}; };
use bevy::prelude::*; use bevy::prelude::*;
use happy_feet::prelude::{Grounding, KinematicVelocity, MoveInput}; use happy_feet::prelude::{Grounding, KinematicVelocity, MoveInput};
use lightyear::prelude::input::native::ActionState;
pub struct CharacterControllerPlugin; pub struct CharacterControllerPlugin;
impl Plugin for CharacterControllerPlugin { impl Plugin for CharacterControllerPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(
PreUpdate, FixedUpdate,
(rotate_view, apply_controls) (rotate_view, apply_controls)
.chain() .chain()
.in_set(ControllerSet::ApplyControlsRun) .in_set(ControllerSet::ApplyControlsRun)
@@ -24,20 +25,21 @@ impl Plugin for CharacterControllerPlugin {
} }
fn rotate_view( fn rotate_view(
controls: Res<ControlState>, actions: Query<&ActionState<ControlState>>,
mut player: Query<&mut Transform, With<PlayerBodyMesh>>, mut player: Query<(&mut Transform, &ChildOf), With<PlayerBodyMesh>>,
) { ) {
for (mut tr, child_of) in player.iter_mut() {
let controls = actions.get(child_of.parent()).unwrap();
if controls.view_mode { if controls.view_mode {
return; continue;
} }
for mut tr in player.iter_mut() {
tr.rotate_y(controls.look_dir.x * -0.001); tr.rotate_y(controls.look_dir.x * -0.001);
} }
} }
fn apply_controls( fn apply_controls(
controls: Res<ControlState>,
mut character: Query<( mut character: Query<(
&mut MoveInput, &mut MoveInput,
&mut Grounding, &mut Grounding,
@@ -45,12 +47,20 @@ fn apply_controls(
&mut AnimationFlags, &mut AnimationFlags,
&ControllerSettings, &ControllerSettings,
&MovementSpeedFactor, &MovementSpeedFactor,
&ActionState<ControlState>,
)>, )>,
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>, rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
is_server: Option<Res<IsServer>>, is_server: Option<Res<IsServer>>,
) { ) {
let Ok((mut move_input, mut grounding, mut velocity, mut flags, settings, move_factor)) = let Ok((
character.single_mut() mut move_input,
mut grounding,
mut velocity,
mut flags,
settings,
move_factor,
controls,
)) = character.single_mut()
else { else {
return; return;
}; };

View File

@@ -1,10 +1,8 @@
use super::{ControlState, Controls}; use super::{ControlState, Controls};
use crate::{ use crate::{
GameState, GameState,
abilities::{TriggerCashHeal, TriggerState},
backpack::BackpackAction,
control::{CharacterInputEnabled, ControllerSet}, control::{CharacterInputEnabled, ControllerSet},
heads::SelectActiveHead, player::Player,
}; };
use bevy::{ use bevy::{
input::{ input::{
@@ -14,6 +12,7 @@ use bevy::{
}, },
prelude::*, prelude::*,
}; };
use lightyear::prelude::{client::input::InputSet::WriteClientInputs, input::native::ActionState};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
@@ -22,12 +21,12 @@ pub fn plugin(app: &mut App) {
app.register_type::<ControllerSettings>(); app.register_type::<ControllerSettings>();
app.add_systems( app.add_systems(
PreUpdate, FixedUpdate,
( (
gamepad_controls, gamepad_controls,
keyboard_controls, keyboard_controls,
mouse_rotate, mouse_rotate,
mouse_click.run_if(on_event::<MouseButtonInput>), mouse_click,
gamepad_connections.run_if(on_event::<GamepadEvent>), gamepad_connections.run_if(on_event::<GamepadEvent>),
combine_controls, combine_controls,
) )
@@ -38,9 +37,12 @@ pub fn plugin(app: &mut App) {
.and(resource_exists_and_equals(CharacterInputEnabled::On)), .and(resource_exists_and_equals(CharacterInputEnabled::On)),
), ),
); );
app.add_systems(FixedPreUpdate, buffer_inputs.in_set(WriteClientInputs));
app.add_systems( app.add_systems(
Update, Update,
char_controls_state.run_if(in_state(GameState::Playing)), reset_control_state_on_disable.run_if(in_state(GameState::Playing)),
); );
} }
@@ -51,7 +53,17 @@ pub struct ControllerSettings {
pub jump_force: f32, pub jump_force: f32,
} }
fn char_controls_state( /// Write inputs from combined keyboard/gamepad state into the networked input buffer
/// for the local player.
fn buffer_inputs(
mut player: Single<&mut ActionState<ControlState>, With<Player>>,
controls: Res<ControlState>,
) {
player.0 = *controls;
}
/// Reset character inputs to default when character input is disabled.
fn reset_control_state_on_disable(
state: Res<CharacterInputEnabled>, state: Res<CharacterInputEnabled>,
mut controls: ResMut<Controls>, mut controls: ResMut<Controls>,
mut control_state: ResMut<ControlState>, mut control_state: ResMut<ControlState>,
@@ -62,20 +74,24 @@ fn char_controls_state(
} }
} }
/// Take keyboard and gamepad state and combine them into unified input state
fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<ControlState>) { fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<ControlState>) {
let keyboard = controls.keyboard_state; let keyboard = controls.keyboard_state;
let gamepad = controls.gamepad_state.unwrap_or_default();
if let Some(gamepad) = controls.gamepad_state {
combined_controls.look_dir = gamepad.look_dir + keyboard.look_dir; combined_controls.look_dir = gamepad.look_dir + keyboard.look_dir;
combined_controls.move_dir = gamepad.move_dir + keyboard.move_dir; combined_controls.move_dir = gamepad.move_dir + keyboard.move_dir;
combined_controls.jump = gamepad.jump | keyboard.jump; combined_controls.jump = gamepad.jump | keyboard.jump;
combined_controls.view_mode = gamepad.view_mode | keyboard.view_mode; combined_controls.view_mode = gamepad.view_mode | keyboard.view_mode;
} else { combined_controls.trigger = keyboard.trigger | gamepad.trigger;
combined_controls.look_dir = keyboard.look_dir; combined_controls.just_triggered = keyboard.just_triggered | gamepad.just_triggered;
combined_controls.move_dir = keyboard.move_dir; combined_controls.select_left = gamepad.select_left | keyboard.select_left;
combined_controls.jump = keyboard.jump; combined_controls.select_right = gamepad.select_right | keyboard.select_right;
combined_controls.view_mode = keyboard.view_mode; 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;
} }
/// Applies a square deadzone to a Vec2 /// Applies a square deadzone to a Vec2
@@ -86,11 +102,8 @@ fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
) )
} }
fn gamepad_controls( /// Collect gamepad inputs
mut commands: Commands, fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Controls>) {
gamepads: Query<(Entity, &Gamepad)>,
mut controls: ResMut<Controls>,
) {
let Some((_e, gamepad)) = gamepads.iter().next() else { let Some((_e, gamepad)) = gamepads.iter().next() else {
if controls.gamepad_state.is_some() { if controls.gamepad_state.is_some() {
controls.gamepad_state = None; controls.gamepad_state = None;
@@ -101,8 +114,6 @@ fn gamepad_controls(
let deadzone_left_stick = 0.15; let deadzone_left_stick = 0.15;
let deadzone_right_stick = 0.15; let deadzone_right_stick = 0.15;
// info!("gamepad: {:?}", gamepad);
let rotate = gamepad let rotate = gamepad
.get(GamepadButton::RightTrigger2) .get(GamepadButton::RightTrigger2)
.unwrap_or_default(); .unwrap_or_default();
@@ -129,47 +140,28 @@ fn gamepad_controls(
look_dir, look_dir,
jump: gamepad.pressed(GamepadButton::South), jump: gamepad.pressed(GamepadButton::South),
view_mode: gamepad.pressed(GamepadButton::LeftTrigger2), view_mode: gamepad.pressed(GamepadButton::LeftTrigger2),
trigger: gamepad.pressed(GamepadButton::RightTrigger2),
just_triggered: gamepad.just_pressed(GamepadButton::RightTrigger2),
select_left: gamepad.just_pressed(GamepadButton::LeftTrigger),
select_right: gamepad.just_pressed(GamepadButton::RightTrigger),
backpack_left: gamepad.just_pressed(GamepadButton::DPadLeft),
backpack_right: gamepad.just_pressed(GamepadButton::DPadRight),
backpack_swap: gamepad.just_pressed(GamepadButton::DPadDown),
backpack_toggle: gamepad.just_pressed(GamepadButton::DPadUp),
cash_heal: gamepad.just_pressed(GamepadButton::East),
}; };
if gamepad.just_pressed(GamepadButton::RightTrigger2) {
commands.trigger(TriggerState::Active);
}
if gamepad.just_released(GamepadButton::RightTrigger2) {
commands.trigger(TriggerState::Inactive);
}
if gamepad.just_pressed(GamepadButton::LeftTrigger) {
commands.trigger(SelectActiveHead::Left);
}
if gamepad.just_pressed(GamepadButton::RightTrigger) {
commands.trigger(SelectActiveHead::Right);
}
if gamepad.just_pressed(GamepadButton::DPadLeft) {
commands.trigger(BackpackAction::Left);
}
if gamepad.just_pressed(GamepadButton::DPadRight) {
commands.trigger(BackpackAction::Right);
}
if gamepad.just_pressed(GamepadButton::DPadDown) {
commands.trigger(BackpackAction::Swap);
}
if gamepad.just_pressed(GamepadButton::DPadUp) {
commands.trigger(BackpackAction::OpenClose);
}
if gamepad.just_pressed(GamepadButton::East) {
commands.trigger(TriggerCashHeal);
}
if controls if controls
.gamepad_state .gamepad_state
.as_ref() .as_ref()
.map(|last_state| *last_state != state) .map(|last_state| *last_state != state)
.unwrap_or(true) .unwrap_or(true)
{ {
// info!("gamepad state changed: {:?}", state);
controls.gamepad_state = Some(state); controls.gamepad_state = Some(state);
} }
} }
/// Collect mouse movement input
fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) { fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) {
controls.keyboard_state.look_dir = Vec2::ZERO; controls.keyboard_state.look_dir = Vec2::ZERO;
@@ -178,11 +170,8 @@ fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Contro
} }
} }
fn keyboard_controls( /// Collect keyboard input
mut commands: Commands, fn keyboard_controls(keyboard: Res<ButtonInput<KeyCode>>, mut controls: ResMut<Controls>) {
keyboard: Res<ButtonInput<KeyCode>>,
mut controls: ResMut<Controls>,
) {
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];
let left_binds = [KeyCode::KeyA, KeyCode::ArrowLeft]; let left_binds = [KeyCode::KeyA, KeyCode::ArrowLeft];
@@ -197,35 +186,22 @@ 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);
if keyboard.just_pressed(KeyCode::KeyB) {
commands.trigger(BackpackAction::OpenClose);
}
if keyboard.just_pressed(KeyCode::Enter) {
commands.trigger(BackpackAction::Swap);
}
if keyboard.just_pressed(KeyCode::Comma) {
commands.trigger(BackpackAction::Left);
}
if keyboard.just_pressed(KeyCode::Period) {
commands.trigger(BackpackAction::Right);
}
if keyboard.just_pressed(KeyCode::KeyQ) {
commands.trigger(SelectActiveHead::Left);
}
if keyboard.just_pressed(KeyCode::KeyE) {
commands.trigger(SelectActiveHead::Right);
}
if keyboard.just_pressed(KeyCode::Enter) {
commands.trigger(TriggerCashHeal);
}
controls.keyboard_state.move_dir = direction; controls.keyboard_state.move_dir = direction;
controls.keyboard_state.jump = keyboard.pressed(KeyCode::Space); controls.keyboard_state.jump = keyboard.pressed(KeyCode::Space);
controls.keyboard_state.view_mode = keyboard.pressed(KeyCode::Tab); controls.keyboard_state.view_mode = keyboard.pressed(KeyCode::Tab);
controls.keyboard_state.backpack_toggle = keyboard.just_pressed(KeyCode::KeyB);
controls.keyboard_state.backpack_swap = keyboard.just_pressed(KeyCode::Enter);
controls.keyboard_state.backpack_left = keyboard.just_pressed(KeyCode::Comma);
controls.keyboard_state.backpack_right = keyboard.just_pressed(KeyCode::Period);
controls.keyboard_state.select_left = keyboard.just_pressed(KeyCode::KeyQ);
controls.keyboard_state.select_right = keyboard.just_pressed(KeyCode::KeyE);
controls.keyboard_state.cash_heal = keyboard.just_pressed(KeyCode::Enter);
} }
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut commands: Commands) { /// Collect mouse button input when pressed
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut controls: ResMut<Controls>) {
controls.keyboard_state.just_triggered = false;
for ev in events.read() { for ev in events.read() {
match ev { match ev {
MouseButtonInput { MouseButtonInput {
@@ -233,20 +209,23 @@ fn mouse_click(mut events: EventReader<MouseButtonInput>, mut commands: Commands
state: ButtonState::Pressed, state: ButtonState::Pressed,
.. ..
} => { } => {
commands.trigger(TriggerState::Active); controls.keyboard_state.trigger = true;
controls.keyboard_state.just_triggered = true;
} }
MouseButtonInput { MouseButtonInput {
button: MouseButton::Left, button: MouseButton::Left,
state: ButtonState::Released, state: ButtonState::Released,
.. ..
} => { } => {
commands.trigger(TriggerState::Inactive); controls.keyboard_state.trigger = false;
controls.keyboard_state.just_triggered = false;
} }
_ => {} _ => {}
} }
} }
} }
/// Receive gamepad connections and disconnections
fn gamepad_connections(mut evr_gamepad: EventReader<GamepadEvent>) { fn gamepad_connections(mut evr_gamepad: EventReader<GamepadEvent>) {
for ev in evr_gamepad.read() { for ev in evr_gamepad.read() {
if let GamepadEvent::Connection(connection) = ev { if let GamepadEvent::Connection(connection) = ev {

View File

@@ -4,7 +4,8 @@ use crate::{
heads_database::{HeadControls, HeadsDatabase}, heads_database::{HeadControls, HeadsDatabase},
player::Player, player::Player,
}; };
use bevy::prelude::*; use bevy::{ecs::entity::MapEntities, prelude::*};
use serde::{Deserialize, Serialize};
pub mod controller_common; pub mod controller_common;
pub mod controller_flying; pub mod controller_flying;
@@ -19,7 +20,7 @@ enum ControllerSet {
ApplyControlsRun, ApplyControlsRun,
} }
#[derive(Resource, Debug, Clone, Copy, Default, PartialEq)] #[derive(Resource, Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, Reflect)]
pub struct ControlState { pub struct ControlState {
/// 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,
@@ -27,6 +28,19 @@ pub struct ControlState {
pub jump: bool, pub jump: bool,
/// 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 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 MapEntities for ControlState {
fn map_entities<E: EntityMapper>(&mut self, _entity_mapper: &mut E) {}
} }
#[derive(Resource, Debug, Default)] #[derive(Resource, Debug, Default)]
@@ -60,7 +74,7 @@ pub fn plugin(app: &mut App) {
app.add_event::<ControllerSwitchEvent>(); app.add_event::<ControllerSwitchEvent>();
app.configure_sets( app.configure_sets(
PreUpdate, FixedUpdate,
( (
ControllerSet::CollectInputs, ControllerSet::CollectInputs,
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController( ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController(

View File

@@ -183,7 +183,9 @@ fn on_collect_head(
commands.trigger(PlaySound::HeadCollect); commands.trigger(PlaySound::HeadCollect);
} }
commands.trigger(HeadCollected(drop.head_id)); commands
.entity(collider)
.trigger(HeadCollected(drop.head_id));
commands.entity(child_of.parent()).despawn(); commands.entity(child_of.parent()).despawn();
} }
} }

View File

@@ -4,6 +4,7 @@ use crate::{
GameState, GameState,
animation::AnimationFlags, animation::AnimationFlags,
backpack::{BackbackSwapEvent, Backpack}, backpack::{BackbackSwapEvent, Backpack},
control::ControlState,
global_observer, global_observer,
heads_database::HeadsDatabase, heads_database::HeadsDatabase,
hitpoints::Hitpoints, hitpoints::Hitpoints,
@@ -11,6 +12,7 @@ use crate::{
sounds::PlaySound, sounds::PlaySound,
}; };
use bevy::prelude::*; use bevy::prelude::*;
use lightyear::prelude::input::native::ActionState;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub static HEAD_COUNT: usize = 18; pub static HEAD_COUNT: usize = 18;
@@ -171,12 +173,6 @@ impl ActiveHeads {
} }
} }
#[derive(Event, Reflect)]
pub enum SelectActiveHead {
Left,
Right,
}
#[derive(Event)] #[derive(Event)]
pub struct HeadChanged(pub usize); pub struct HeadChanged(pub usize);
@@ -188,8 +184,8 @@ pub fn plugin(app: &mut App) {
Update, Update,
(reload, sync_hp).run_if(in_state(GameState::Playing)), (reload, sync_hp).run_if(in_state(GameState::Playing)),
); );
app.add_systems(FixedUpdate, on_select_active_head);
global_observer!(app, on_select_active_head);
global_observer!(app, on_swap_backpack); global_observer!(app, on_swap_backpack);
} }
@@ -237,23 +233,22 @@ fn reload(
} }
fn on_select_active_head( fn on_select_active_head(
trigger: Trigger<SelectActiveHead>,
mut commands: Commands, mut commands: Commands,
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>, mut query: Query<(&mut ActiveHeads, &mut Hitpoints, &ActionState<ControlState>), With<Player>>,
) { ) {
let Ok((mut active_heads, mut hp)) = query.single_mut() else { for (mut active_heads, mut hp, controls) in query.iter_mut() {
return; if !controls.select_right && !controls.select_left {
}; continue;
}
match trigger.event() { if controls.select_right {
SelectActiveHead::Right => {
active_heads.selected_slot = (active_heads.selected_slot + 1) % HEAD_SLOTS; active_heads.selected_slot = (active_heads.selected_slot + 1) % HEAD_SLOTS;
} }
SelectActiveHead::Left => {
if controls.select_left {
active_heads.selected_slot = active_heads.selected_slot =
(active_heads.selected_slot + (HEAD_SLOTS - 1)) % HEAD_SLOTS; (active_heads.selected_slot + (HEAD_SLOTS - 1)) % HEAD_SLOTS;
} }
}
commands.trigger(PlaySound::Selection); commands.trigger(PlaySound::Selection);
@@ -266,21 +261,21 @@ fn on_select_active_head(
)); ));
} }
} }
}
fn on_swap_backpack( fn on_swap_backpack(
trigger: Trigger<BackbackSwapEvent>, trigger: Trigger<BackbackSwapEvent>,
mut commands: Commands, mut commands: Commands,
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>, mut query: Query<(&mut ActiveHeads, &mut Hitpoints, &mut Backpack), With<Player>>,
mut backpack: ResMut<Backpack>,
) { ) {
let backpack_slot = trigger.event().0; let backpack_slot = trigger.event().0;
let head = backpack.heads.get(backpack_slot).unwrap(); let Ok((mut active_heads, mut hp, mut backpack)) = query.single_mut() else {
let Ok((mut active_heads, mut hp)) = query.single_mut() else {
return; return;
}; };
let head = backpack.heads.get(backpack_slot).unwrap();
let selected_slot = active_heads.selected_slot; let selected_slot = active_heads.selected_slot;
let selected_head = active_heads.heads[selected_slot]; let selected_head = active_heads.heads[selected_slot];

View File

@@ -18,7 +18,7 @@ use crate::{
}, },
}; };
use bevy::{pbr::NotShadowCaster, prelude::*}; use bevy::{pbr::NotShadowCaster, prelude::*};
use lightyear::prelude::{NetworkTarget, Replicate}; use lightyear::prelude::{Client, Connected, Disconnected, NetworkTarget, Replicate};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
@@ -38,34 +38,58 @@ struct NpcSpawning {
pub struct SpawningBeam(pub f32); pub struct SpawningBeam(pub f32);
#[derive(Event)] #[derive(Event)]
struct OnCheckSpawns; struct OnCheckSpawns {
on_client: bool,
}
#[derive(Event)] #[derive(Event)]
pub struct SpawnCharacter(pub Vec3); pub struct SpawnCharacter(pub Vec3);
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.init_resource::<NpcSpawning>(); app.init_resource::<NpcSpawning>();
app.add_systems(OnEnter(GameState::Playing), setup); app.add_systems(FixedUpdate, setup.run_if(in_state(GameState::Playing)));
app.add_systems(Update, update_beams.run_if(in_state(GameState::Playing))); app.add_systems(Update, update_beams.run_if(in_state(GameState::Playing)));
global_observer!(app, on_spawn_check); global_observer!(app, on_spawn_check);
global_observer!(app, on_spawn); global_observer!(app, on_spawn);
} }
fn setup(mut commands: Commands) { fn setup(
mut commands: Commands,
is_server: Option<Res<IsServer>>,
client: Query<(Option<&Connected>, Option<&Disconnected>), With<Client>>,
mut spawned: Local<bool>,
) {
if *spawned {
return;
}
if is_server.is_some() {
commands.init_resource::<NpcSpawning>(); commands.init_resource::<NpcSpawning>();
commands.trigger(OnCheckSpawns); commands.trigger(OnCheckSpawns { on_client: false });
*spawned = true;
} else if let Ok((connected, disconnected)) = client.single()
&& (connected.is_some() || disconnected.is_some())
{
commands.init_resource::<NpcSpawning>();
commands.trigger(OnCheckSpawns {
on_client: disconnected.is_some(),
});
*spawned = true;
}
} }
fn on_spawn_check( fn on_spawn_check(
_trigger: Trigger<OnCheckSpawns>, trigger: Trigger<OnCheckSpawns>,
mut commands: Commands, mut commands: Commands,
query: Query<(Entity, &EnemySpawn, &Transform), Without<Npc>>, query: Query<(Entity, &EnemySpawn, &Transform), Without<Npc>>,
heads_db: Res<HeadsDatabase>, heads_db: Res<HeadsDatabase>,
spawning: Res<NpcSpawning>, spawning: Res<NpcSpawning>,
is_server: Option<Res<IsServer>>, is_server: Option<Res<IsServer>>,
) { ) {
if is_server.is_none() { if is_server.is_none() && !trigger.event().on_client {
return; return;
} }
@@ -109,6 +133,8 @@ fn on_spawn_check(
fn on_kill( fn on_kill(
trigger: Trigger<Kill>, trigger: Trigger<Kill>,
is_server: Option<Res<IsServer>>,
disconnected: Option<Single<&Disconnected>>,
mut commands: Commands, mut commands: Commands,
query: Query<(&Transform, &EnemySpawn, &ActiveHead)>, query: Query<(&Transform, &EnemySpawn, &ActiveHead)>,
) { ) {
@@ -123,7 +149,9 @@ fn on_kill(
} }
commands.trigger(HeadDrops::new(transform.translation, head.0)); commands.trigger(HeadDrops::new(transform.translation, head.0));
commands.trigger(OnCheckSpawns); commands.trigger(OnCheckSpawns {
on_client: is_server.is_some() || disconnected.is_some(),
});
commands.entity(trigger.target()).despawn(); commands.entity(trigger.target()).despawn();

View File

@@ -1,9 +1,10 @@
use crate::{ use crate::{
GameState, GameState,
backpack::Backpack,
camera::{CameraArmRotation, CameraTarget}, camera::{CameraArmRotation, CameraTarget},
cash::{Cash, CashCollectEvent}, cash::{Cash, CashCollectEvent},
character::{AnimatedCharacter, Character}, character::{AnimatedCharacter, Character},
control::controller_common::PlayerCharacterController, control::{ControlState, controller_common::PlayerCharacterController},
global_observer, global_observer,
head::ActiveHead, head::ActiveHead,
head_drop::HeadDrops, head_drop::HeadDrops,
@@ -22,7 +23,9 @@ use bevy::{
prelude::*, prelude::*,
window::{CursorGrabMode, PrimaryWindow}, window::{CursorGrabMode, PrimaryWindow},
}; };
use lightyear::prelude::{ControlledBy, Lifetime, NetworkTarget, PredictionTarget, Replicate}; use lightyear::prelude::{
ControlledBy, Lifetime, NetworkTarget, PredictionTarget, Replicate, input::native::ActionState,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Component, Default, Serialize, Deserialize, PartialEq)] #[derive(Component, Default, Serialize, Deserialize, PartialEq)]
@@ -98,6 +101,8 @@ fn player_bundle(transform: Transform, heads_db: &Res<HeadsDatabase>) -> impl Bu
transform, transform,
Visibility::default(), Visibility::default(),
PlayerCharacterController, PlayerCharacterController,
ActionState::<ControlState>::default(),
Backpack::default(),
children![( children![(
Name::new("player-rig"), Name::new("player-rig"),
PlayerBodyMesh, PlayerBodyMesh,
@@ -197,6 +202,7 @@ fn on_update_head_mesh(
mut player: Single<&mut ActiveHead, With<Player>>, mut player: Single<&mut ActiveHead, With<Player>>,
head_db: Res<HeadsDatabase>, head_db: Res<HeadsDatabase>,
audio_assets: Res<AudioAssets>, audio_assets: Res<AudioAssets>,
sfx: Query<&AudioPlayer>,
) -> Result { ) -> Result {
let (body_mesh, mesh_children) = *body_mesh; let (body_mesh, mesh_children) = *body_mesh;
@@ -213,10 +219,12 @@ fn on_update_head_mesh(
commands commands
.entity(animated_char) .entity(animated_char)
.remove::<AnimatedCharacter>()
.insert(AnimatedCharacter::new(trigger.0)); .insert(AnimatedCharacter::new(trigger.0));
//TODO: make part of full character mesh later //TODO: make part of full character mesh later
for child in mesh_children.iter().filter(|child| sfx.contains(*child)) {
commands.entity(child).despawn();
}
if head_db.head_stats(trigger.0).controls == HeadControls::Plane { if head_db.head_stats(trigger.0).controls == HeadControls::Plane {
commands.entity(body_mesh).with_child(( commands.entity(body_mesh).with_child((
Name::new("sfx"), Name::new("sfx"),

View File

@@ -1,9 +1,11 @@
use crate::{ use crate::{
abilities::BuildExplosionSprite, abilities::BuildExplosionSprite,
animation::AnimationFlags, animation::AnimationFlags,
backpack::Backpack,
camera::{CameraArmRotation, CameraTarget}, camera::{CameraArmRotation, CameraTarget},
character::{self, AnimatedCharacter}, character::{self, AnimatedCharacter},
control::{ control::{
ControlState,
controller_common::{MovementSpeedFactor, PlayerCharacterController}, controller_common::{MovementSpeedFactor, PlayerCharacterController},
controls::ControllerSettings, controls::ControllerSettings,
}, },
@@ -19,12 +21,13 @@ use bevy::prelude::*;
use happy_feet::{ use happy_feet::{
grounding::GroundingState, grounding::GroundingState,
prelude::{ prelude::{
Character, CharacterDrag, CharacterGravity, CharacterMovement, GroundFriction, Grounding, CharacterDrag, CharacterGravity, CharacterMovement, GroundFriction, Grounding,
GroundingConfig, KinematicVelocity, MoveInput, SteppingConfig, GroundingConfig, KinematicVelocity, MoveInput, SteppingConfig,
}, },
}; };
use lightyear::prelude::{ use lightyear::prelude::{
ActionsChannel, AppComponentExt, PredictionMode, PredictionRegistrationExt, ActionsChannel, AppComponentExt, PredictionMode, PredictionRegistrationExt,
input::native::InputPlugin,
}; };
use lightyear_serde::{ use lightyear_serde::{
SerializationError, reader::ReadInteger, registry::SerializeFns, writer::WriteInteger, SerializationError, reader::ReadInteger, registry::SerializeFns, writer::WriteInteger,
@@ -32,14 +35,17 @@ use lightyear_serde::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.add_plugins(InputPlugin::<ControlState>::default());
app.register_component::<ActiveHead>(); app.register_component::<ActiveHead>();
app.register_component::<ActiveHeads>(); app.register_component::<ActiveHeads>();
app.register_component::<AngularVelocity>(); app.register_component::<AngularVelocity>();
app.register_component::<AnimatedCharacter>(); app.register_component::<AnimatedCharacter>();
app.register_component::<AnimationFlags>(); app.register_component::<AnimationFlags>();
app.register_component::<Backpack>();
app.register_component::<CameraArmRotation>(); app.register_component::<CameraArmRotation>();
app.register_component::<CameraTarget>(); app.register_component::<CameraTarget>();
app.register_component::<Character>(); app.register_component::<happy_feet::prelude::Character>();
app.register_component::<character::Character>(); app.register_component::<character::Character>();
app.register_component::<CharacterDrag>(); app.register_component::<CharacterDrag>();
app.register_component::<CharacterGravity>(); app.register_component::<CharacterGravity>();

View File

@@ -5,6 +5,7 @@ use bevy::ecs::{
system::{Commands, EntityCommands}, system::{Commands, EntityCommands},
world::{EntityWorldMut, World}, world::{EntityWorldMut, World},
}; };
use lightyear::prelude::Disconnected;
#[derive(Default, Resource)] #[derive(Default, Resource)]
pub struct IsServer; pub struct IsServer;
@@ -16,7 +17,8 @@ pub trait CommandExt {
impl<'w, 's> CommandExt for Commands<'w, 's> { impl<'w, 's> CommandExt for Commands<'w, 's> {
fn trigger_server(&mut self, event: impl Event) -> &mut Self { fn trigger_server(&mut self, event: impl Event) -> &mut Self {
self.queue(|world: &mut World| { self.queue(|world: &mut World| {
if world.contains_resource::<IsServer>() { let mut query_state = world.query::<&Disconnected>();
if world.contains_resource::<IsServer>() || !query_state.query(world).is_empty() {
world.trigger(event); world.trigger(event);
} }
}); });