Input replication (#62)
This commit is contained in:
@@ -9,6 +9,7 @@ use crate::{
|
||||
GameState,
|
||||
aim::AimTarget,
|
||||
character::CharacterHierarchy,
|
||||
control::ControlState,
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
heads::ActiveHeads,
|
||||
@@ -17,23 +18,18 @@ use crate::{
|
||||
physics_layers::GameLayer,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
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_sprite3d::{Sprite3dBuilder, Sprite3dParams};
|
||||
pub use healing::Healing;
|
||||
use healing::HealingStateChanged;
|
||||
use lightyear::{
|
||||
connection::client::ClientState,
|
||||
prelude::{Client, input::native::ActionState},
|
||||
};
|
||||
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)]
|
||||
pub enum HeadAbility {
|
||||
#[default]
|
||||
@@ -117,22 +113,34 @@ pub fn plugin(app: &mut App) {
|
||||
Update,
|
||||
(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);
|
||||
}
|
||||
|
||||
fn on_trigger_state(
|
||||
trigger: Trigger<TriggerState>,
|
||||
mut res: ResMut<TriggerStateRes>,
|
||||
player_head: Single<&ActiveHead, With<Player>>,
|
||||
player: Query<(&ActiveHead, &ActionState<ControlState>), With<Player>>,
|
||||
headdb: Res<HeadsDatabase>,
|
||||
time: Res<Time>,
|
||||
is_server: Option<Res<IsServer>>,
|
||||
client: Query<&Client>,
|
||||
) {
|
||||
res.active = matches!(trigger.event(), TriggerState::Active);
|
||||
if res.active {
|
||||
let head_stats = headdb.head_stats(player_head.0);
|
||||
res.next_trigger_timestamp = time.elapsed_secs() + head_stats.shoot_offset;
|
||||
if let Ok(client) = client.single()
|
||||
&& (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);
|
||||
res.next_trigger_timestamp = time.elapsed_secs() + head_stats.shoot_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,11 +46,9 @@ fn on_trigger_thrown(
|
||||
|
||||
let pos = state.pos;
|
||||
|
||||
let vel = if let Some(target) = state.target {
|
||||
let t = query_transform
|
||||
.get(target)
|
||||
.expect("target must have transform");
|
||||
|
||||
let vel = if let Some(target) = state.target
|
||||
&& let Ok(t) = query_transform.get(target)
|
||||
{
|
||||
launch_velocity(pos, t.translation, SPEED, 9.81)
|
||||
.map(|(low, _)| low)
|
||||
.unwrap()
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
use super::{BackbackSwapEvent, Backpack, UiHeadState};
|
||||
use super::{Backpack, UiHeadState};
|
||||
use crate::{
|
||||
GameState, HEDZ_GREEN, global_observer, heads::HeadsImages, loading_assets::UIAssets,
|
||||
sounds::PlaySound,
|
||||
GameState, HEDZ_GREEN, backpack::BackbackSwapEvent, control::ControlState, heads::HeadsImages,
|
||||
loading_assets::UIAssets, sounds::PlaySound,
|
||||
};
|
||||
use bevy::{ecs::spawn::SpawnIter, prelude::*};
|
||||
use lightyear::prelude::input::native::ActionState;
|
||||
|
||||
static HEAD_SLOTS: usize = 5;
|
||||
|
||||
#[derive(Event, Clone, Copy, Reflect, PartialEq)]
|
||||
pub enum BackpackAction {
|
||||
Left,
|
||||
Right,
|
||||
Swap,
|
||||
OpenClose,
|
||||
}
|
||||
|
||||
#[derive(Component, Default)]
|
||||
struct BackpackMarker;
|
||||
|
||||
@@ -30,7 +23,8 @@ struct HeadImage(pub usize);
|
||||
#[derive(Component, Default)]
|
||||
struct HeadDamage(pub usize);
|
||||
|
||||
#[derive(Resource, Default, Debug)]
|
||||
#[derive(Resource, Default, Debug, Reflect)]
|
||||
#[reflect(Resource, Default)]
|
||||
struct BackpackUiState {
|
||||
heads: [Option<UiHeadState>; 5],
|
||||
scroll: usize,
|
||||
@@ -46,6 +40,7 @@ impl BackpackUiState {
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<BackpackUiState>();
|
||||
app.init_resource::<BackpackUiState>();
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(
|
||||
@@ -53,8 +48,7 @@ pub fn plugin(app: &mut App) {
|
||||
(update, sync_on_change, update_visibility, update_count)
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
global_observer!(app, swap_head_inputs);
|
||||
app.add_systems(FixedUpdate, swap_head_inputs);
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
||||
@@ -262,52 +256,58 @@ fn update(
|
||||
}
|
||||
|
||||
fn swap_head_inputs(
|
||||
trigger: Trigger<BackpackAction>,
|
||||
backpack: Res<Backpack>,
|
||||
player: Query<(&ActionState<ControlState>, Ref<Backpack>)>,
|
||||
mut commands: Commands,
|
||||
mut state: ResMut<BackpackUiState>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if state.count == 0 {
|
||||
return;
|
||||
}
|
||||
for (controls, backpack) in player.iter() {
|
||||
if state.count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let action = *trigger.event();
|
||||
if action == BackpackAction::OpenClose {
|
||||
state.open = !state.open;
|
||||
commands.trigger(PlaySound::Backpack { open: state.open });
|
||||
}
|
||||
if controls.backpack_toggle {
|
||||
state.open = !state.open;
|
||||
commands.trigger(PlaySound::Backpack { open: state.open });
|
||||
}
|
||||
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut changed = false;
|
||||
if action == BackpackAction::Left && state.current_slot > 0 {
|
||||
state.current_slot -= 1;
|
||||
changed = true;
|
||||
}
|
||||
if action == BackpackAction::Right && state.current_slot < state.count.saturating_sub(1) {
|
||||
state.current_slot += 1;
|
||||
changed = true;
|
||||
}
|
||||
if action == BackpackAction::Swap {
|
||||
commands.trigger(BackbackSwapEvent(state.current_slot));
|
||||
}
|
||||
let mut changed = false;
|
||||
if controls.backpack_left && state.current_slot > 0 {
|
||||
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.trigger(PlaySound::Selection);
|
||||
sync(&backpack, &mut state, time.elapsed_secs());
|
||||
if changed {
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_on_change(backpack: Res<Backpack>, mut state: ResMut<BackpackUiState>, time: Res<Time>) {
|
||||
if backpack.is_changed() || backpack.reloading() {
|
||||
sync(&backpack, &mut state, time.elapsed_secs());
|
||||
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() {
|
||||
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.scroll = state.scroll.min(state.count.saturating_sub(HEAD_SLOTS));
|
||||
|
||||
@@ -5,11 +5,12 @@ use crate::{
|
||||
cash::CashCollectEvent, global_observer, head_drop::HeadCollected, heads::HeadState,
|
||||
heads_database::HeadsDatabase,
|
||||
};
|
||||
pub use backpack_ui::BackpackAction;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use ui_head_state::UiHeadState;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
#[derive(Component, Default, Reflect, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
pub struct Backpack {
|
||||
pub heads: Vec<HeadState>,
|
||||
}
|
||||
@@ -38,7 +39,7 @@ impl Backpack {
|
||||
pub struct BackbackSwapEvent(pub usize);
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.init_resource::<Backpack>();
|
||||
app.register_type::<Backpack>();
|
||||
|
||||
app.add_plugins(backpack_ui::plugin);
|
||||
|
||||
@@ -48,14 +49,18 @@ pub fn plugin(app: &mut App) {
|
||||
fn on_head_collect(
|
||||
trigger: Trigger<HeadCollected>,
|
||||
mut cmds: Commands,
|
||||
mut backpack: ResMut<Backpack>,
|
||||
mut backpack: Query<&mut Backpack>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
) -> Result {
|
||||
let HeadCollected(head) = *trigger.event();
|
||||
|
||||
let mut backpack = backpack.get_mut(trigger.target())?;
|
||||
|
||||
if backpack.contains(head) {
|
||||
cmds.trigger(CashCollectEvent);
|
||||
} else {
|
||||
backpack.insert(head, heads_db.as_ref());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::{
|
||||
abilities::TriggerCashHeal, cash::CashResource, global_observer, hitpoints::Hitpoints,
|
||||
player::Player, sounds::PlaySound,
|
||||
cash::CashResource, control::ControlState, hitpoints::Hitpoints, player::Player,
|
||||
sounds::PlaySound,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::input::native::ActionState;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
global_observer!(app, on_heal_trigger);
|
||||
app.add_systems(FixedUpdate, on_heal_trigger);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -15,27 +16,28 @@ struct HealAction {
|
||||
}
|
||||
|
||||
fn on_heal_trigger(
|
||||
_trigger: Trigger<TriggerCashHeal>,
|
||||
mut cmds: Commands,
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
for (mut hp, controls) in query.iter_mut() {
|
||||
if !controls.cash_heal {
|
||||
continue;
|
||||
}
|
||||
|
||||
if hp.max() || cash.cash == 0 {
|
||||
return;
|
||||
if hp.max() || cash.cash == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let action = heal(cash.cash, hp.get().1 - hp.get().0);
|
||||
|
||||
hp.heal(action.damage_healed);
|
||||
|
||||
cash.cash = cash.cash.saturating_sub(action.cost);
|
||||
|
||||
//TODO: trigger ui cost animation
|
||||
cmds.trigger(PlaySound::CashHeal);
|
||||
}
|
||||
|
||||
let action = heal(cash.cash, hp.get().1 - hp.get().0);
|
||||
|
||||
hp.heal(action.damage_healed);
|
||||
|
||||
cash.cash = cash.cash.saturating_sub(action.cost);
|
||||
|
||||
//TODO: trigger ui cost animation
|
||||
cmds.trigger(PlaySound::CashHeal);
|
||||
}
|
||||
|
||||
fn heal(cash: i32, damage: u32) -> HealAction {
|
||||
|
||||
@@ -84,7 +84,7 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
fn spawn(
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &AnimatedCharacter), Added<AnimatedCharacter>>,
|
||||
query: Query<(Entity, &AnimatedCharacter), Changed<AnimatedCharacter>>,
|
||||
gltf_assets: Res<Assets<Gltf>>,
|
||||
assets: Res<GameAssets>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
GameState,
|
||||
abilities::TriggerStateRes,
|
||||
animation::AnimationFlags,
|
||||
control::{Controls, SelectedController, controls::ControllerSettings},
|
||||
control::{ControlState, SelectedController, controls::ControllerSettings},
|
||||
head::ActiveHead,
|
||||
heads_database::{HeadControls, HeadsDatabase},
|
||||
physics_layers::GameLayer,
|
||||
@@ -16,7 +16,7 @@ use happy_feet::prelude::{
|
||||
GroundFriction, Grounding, GroundingConfig, KinematicVelocity, MoveInput, SteppingBehaviour,
|
||||
SteppingConfig,
|
||||
};
|
||||
use lightyear::prelude::Replicated;
|
||||
use lightyear::prelude::{Replicated, input::native::ActionState};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
@@ -26,58 +26,51 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
reset_upon_switch
|
||||
.run_if(in_state(GameState::Playing))
|
||||
.before(ControllerSet::ApplyControlsRun)
|
||||
.before(ControllerSet::ApplyControlsFly),
|
||||
reset_upon_switch.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
set_animation_flags
|
||||
.run_if(in_state(GameState::Playing))
|
||||
.after(ControllerSet::ApplyControlsRun)
|
||||
.after(ControllerSet::ApplyControlsFly),
|
||||
set_animation_flags.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
FixedPreUpdate,
|
||||
decelerate.run_if(in_state(GameState::Playing)),
|
||||
FixedUpdate,
|
||||
decelerate
|
||||
.after(ControllerSet::ApplyControlsRun)
|
||||
.after(ControllerSet::ApplyControlsFly)
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
app.add_systems(Update, add_controller_bundle);
|
||||
}
|
||||
|
||||
fn set_animation_flags(
|
||||
controls: Res<Controls>,
|
||||
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;
|
||||
let deadzone = 0.2;
|
||||
for (grounding, mut flags, controls) in player.iter_mut() {
|
||||
let direction = controls.move_dir;
|
||||
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 direction.length_squared() < deadzone {
|
||||
flags.any_direction = false;
|
||||
if flags.any_direction {
|
||||
if direction.length_squared() < deadzone {
|
||||
flags.any_direction = false;
|
||||
}
|
||||
} else if direction.length_squared() > deadzone {
|
||||
flags.any_direction = true;
|
||||
}
|
||||
} else if direction.length_squared() > deadzone {
|
||||
flags.any_direction = true;
|
||||
}
|
||||
|
||||
if flags.shooting != trigger.is_active() {
|
||||
flags.shooting = trigger.is_active();
|
||||
}
|
||||
|
||||
// `apply_controls` sets the jump flag when the player actually jumps.
|
||||
// Unset the flag on hitting the ground
|
||||
if grounding.is_grounded() {
|
||||
flags.jumping = false;
|
||||
// `apply_controls` sets the jump flag when the player actually jumps.
|
||||
// Unset the flag on hitting the ground
|
||||
if grounding.is_grounded() {
|
||||
flags.jumping = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::{ControlState, ControllerSet};
|
||||
use crate::{GameState, control::controller_common::MovementSpeedFactor, player::PlayerBodyMesh};
|
||||
use bevy::prelude::*;
|
||||
use happy_feet::prelude::MoveInput;
|
||||
use lightyear::prelude::input::native::ActionState;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
pub struct CharacterControllerPlugin;
|
||||
@@ -9,7 +10,7 @@ pub struct CharacterControllerPlugin;
|
||||
impl Plugin for CharacterControllerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
FixedUpdate,
|
||||
(rotate_rig, apply_controls)
|
||||
.chain()
|
||||
.in_set(ControllerSet::ApplyControlsFly)
|
||||
@@ -19,16 +20,18 @@ impl Plugin for CharacterControllerPlugin {
|
||||
}
|
||||
|
||||
fn rotate_rig(
|
||||
mut rig_transform_q: Option<Single<&mut Transform, With<PlayerBodyMesh>>>,
|
||||
controls: Res<ControlState>,
|
||||
actions: Query<&ActionState<ControlState>>,
|
||||
mut player: Query<(&mut Transform, &ChildOf), With<PlayerBodyMesh>>,
|
||||
) {
|
||||
if controls.view_mode {
|
||||
return;
|
||||
}
|
||||
for (mut rig_transform, child_of) in player.iter_mut() {
|
||||
let controls = actions.get(child_of.parent()).unwrap();
|
||||
|
||||
let look_dir = controls.look_dir;
|
||||
if controls.view_mode {
|
||||
continue;
|
||||
}
|
||||
|
||||
let look_dir = controls.look_dir;
|
||||
|
||||
if let Some(ref mut rig_transform) = rig_transform_q {
|
||||
// todo: Make consistent with the running controller
|
||||
let sensitivity = 0.001;
|
||||
let max_pitch = 35.0 * PI / 180.0;
|
||||
|
||||
@@ -8,13 +8,14 @@ use crate::{
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use happy_feet::prelude::{Grounding, KinematicVelocity, MoveInput};
|
||||
use lightyear::prelude::input::native::ActionState;
|
||||
|
||||
pub struct CharacterControllerPlugin;
|
||||
|
||||
impl Plugin for CharacterControllerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
FixedUpdate,
|
||||
(rotate_view, apply_controls)
|
||||
.chain()
|
||||
.in_set(ControllerSet::ApplyControlsRun)
|
||||
@@ -24,20 +25,21 @@ impl Plugin for CharacterControllerPlugin {
|
||||
}
|
||||
|
||||
fn rotate_view(
|
||||
controls: Res<ControlState>,
|
||||
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
|
||||
actions: Query<&ActionState<ControlState>>,
|
||||
mut player: Query<(&mut Transform, &ChildOf), With<PlayerBodyMesh>>,
|
||||
) {
|
||||
if controls.view_mode {
|
||||
return;
|
||||
}
|
||||
for (mut tr, child_of) in player.iter_mut() {
|
||||
let controls = actions.get(child_of.parent()).unwrap();
|
||||
|
||||
if controls.view_mode {
|
||||
continue;
|
||||
}
|
||||
|
||||
for mut tr in player.iter_mut() {
|
||||
tr.rotate_y(controls.look_dir.x * -0.001);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_controls(
|
||||
controls: Res<ControlState>,
|
||||
mut character: Query<(
|
||||
&mut MoveInput,
|
||||
&mut Grounding,
|
||||
@@ -45,12 +47,20 @@ fn apply_controls(
|
||||
&mut AnimationFlags,
|
||||
&ControllerSettings,
|
||||
&MovementSpeedFactor,
|
||||
&ActionState<ControlState>,
|
||||
)>,
|
||||
rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
||||
is_server: Option<Res<IsServer>>,
|
||||
) {
|
||||
let Ok((mut move_input, mut grounding, mut velocity, mut flags, settings, move_factor)) =
|
||||
character.single_mut()
|
||||
let Ok((
|
||||
mut move_input,
|
||||
mut grounding,
|
||||
mut velocity,
|
||||
mut flags,
|
||||
settings,
|
||||
move_factor,
|
||||
controls,
|
||||
)) = character.single_mut()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use super::{ControlState, Controls};
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::{TriggerCashHeal, TriggerState},
|
||||
backpack::BackpackAction,
|
||||
control::{CharacterInputEnabled, ControllerSet},
|
||||
heads::SelectActiveHead,
|
||||
player::Player,
|
||||
};
|
||||
use bevy::{
|
||||
input::{
|
||||
@@ -14,6 +12,7 @@ use bevy::{
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
use lightyear::prelude::{client::input::InputSet::WriteClientInputs, input::native::ActionState};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
@@ -22,12 +21,12 @@ pub fn plugin(app: &mut App) {
|
||||
app.register_type::<ControllerSettings>();
|
||||
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
FixedUpdate,
|
||||
(
|
||||
gamepad_controls,
|
||||
keyboard_controls,
|
||||
mouse_rotate,
|
||||
mouse_click.run_if(on_event::<MouseButtonInput>),
|
||||
mouse_click,
|
||||
gamepad_connections.run_if(on_event::<GamepadEvent>),
|
||||
combine_controls,
|
||||
)
|
||||
@@ -38,9 +37,12 @@ pub fn plugin(app: &mut App) {
|
||||
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
|
||||
),
|
||||
);
|
||||
|
||||
app.add_systems(FixedPreUpdate, buffer_inputs.in_set(WriteClientInputs));
|
||||
|
||||
app.add_systems(
|
||||
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,
|
||||
}
|
||||
|
||||
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>,
|
||||
mut controls: ResMut<Controls>,
|
||||
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>) {
|
||||
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.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;
|
||||
} else {
|
||||
combined_controls.look_dir = keyboard.look_dir;
|
||||
combined_controls.move_dir = keyboard.move_dir;
|
||||
combined_controls.jump = keyboard.jump;
|
||||
combined_controls.view_mode = keyboard.view_mode;
|
||||
};
|
||||
combined_controls.look_dir = gamepad.look_dir + keyboard.look_dir;
|
||||
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;
|
||||
}
|
||||
|
||||
/// Applies a square deadzone to a Vec2
|
||||
@@ -86,11 +102,8 @@ fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
|
||||
)
|
||||
}
|
||||
|
||||
fn gamepad_controls(
|
||||
mut commands: Commands,
|
||||
gamepads: Query<(Entity, &Gamepad)>,
|
||||
mut controls: ResMut<Controls>,
|
||||
) {
|
||||
/// Collect gamepad inputs
|
||||
fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Controls>) {
|
||||
let Some((_e, gamepad)) = gamepads.iter().next() else {
|
||||
if controls.gamepad_state.is_some() {
|
||||
controls.gamepad_state = None;
|
||||
@@ -101,8 +114,6 @@ fn gamepad_controls(
|
||||
let deadzone_left_stick = 0.15;
|
||||
let deadzone_right_stick = 0.15;
|
||||
|
||||
// info!("gamepad: {:?}", gamepad);
|
||||
|
||||
let rotate = gamepad
|
||||
.get(GamepadButton::RightTrigger2)
|
||||
.unwrap_or_default();
|
||||
@@ -129,47 +140,28 @@ fn gamepad_controls(
|
||||
look_dir,
|
||||
jump: gamepad.pressed(GamepadButton::South),
|
||||
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
|
||||
.gamepad_state
|
||||
.as_ref()
|
||||
.map(|last_state| *last_state != state)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
// info!("gamepad state changed: {:?}", state);
|
||||
controls.gamepad_state = Some(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect mouse movement input
|
||||
fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) {
|
||||
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(
|
||||
mut commands: Commands,
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
mut controls: ResMut<Controls>,
|
||||
) {
|
||||
/// Collect keyboard input
|
||||
fn keyboard_controls(keyboard: Res<ButtonInput<KeyCode>>, mut controls: ResMut<Controls>) {
|
||||
let up_binds = [KeyCode::KeyW, KeyCode::ArrowUp];
|
||||
let down_binds = [KeyCode::KeyS, KeyCode::ArrowDown];
|
||||
let left_binds = [KeyCode::KeyA, KeyCode::ArrowLeft];
|
||||
@@ -197,35 +186,22 @@ fn keyboard_controls(
|
||||
let vertical = up as i8 - down as i8;
|
||||
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.jump = keyboard.pressed(KeyCode::Space);
|
||||
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() {
|
||||
match ev {
|
||||
MouseButtonInput {
|
||||
@@ -233,20 +209,23 @@ fn mouse_click(mut events: EventReader<MouseButtonInput>, mut commands: Commands
|
||||
state: ButtonState::Pressed,
|
||||
..
|
||||
} => {
|
||||
commands.trigger(TriggerState::Active);
|
||||
controls.keyboard_state.trigger = true;
|
||||
controls.keyboard_state.just_triggered = true;
|
||||
}
|
||||
MouseButtonInput {
|
||||
button: MouseButton::Left,
|
||||
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>) {
|
||||
for ev in evr_gamepad.read() {
|
||||
if let GamepadEvent::Connection(connection) = ev {
|
||||
|
||||
@@ -4,7 +4,8 @@ use crate::{
|
||||
heads_database::{HeadControls, HeadsDatabase},
|
||||
player::Player,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy::{ecs::entity::MapEntities, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod controller_common;
|
||||
pub mod controller_flying;
|
||||
@@ -19,7 +20,7 @@ enum ControllerSet {
|
||||
ApplyControlsRun,
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug, Clone, Copy, Default, PartialEq)]
|
||||
#[derive(Resource, Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, Reflect)]
|
||||
pub struct ControlState {
|
||||
/// Movement direction with a maximum length of 1.0
|
||||
pub move_dir: Vec2,
|
||||
@@ -27,6 +28,19 @@ pub struct ControlState {
|
||||
pub jump: bool,
|
||||
/// Determines if the camera can rotate freely around the player
|
||||
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)]
|
||||
@@ -60,7 +74,7 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_event::<ControllerSwitchEvent>();
|
||||
|
||||
app.configure_sets(
|
||||
PreUpdate,
|
||||
FixedUpdate,
|
||||
(
|
||||
ControllerSet::CollectInputs,
|
||||
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController(
|
||||
|
||||
@@ -183,7 +183,9 @@ fn on_collect_head(
|
||||
commands.trigger(PlaySound::HeadCollect);
|
||||
}
|
||||
|
||||
commands.trigger(HeadCollected(drop.head_id));
|
||||
commands
|
||||
.entity(collider)
|
||||
.trigger(HeadCollected(drop.head_id));
|
||||
commands.entity(child_of.parent()).despawn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
GameState,
|
||||
animation::AnimationFlags,
|
||||
backpack::{BackbackSwapEvent, Backpack},
|
||||
control::ControlState,
|
||||
global_observer,
|
||||
heads_database::HeadsDatabase,
|
||||
hitpoints::Hitpoints,
|
||||
@@ -11,6 +12,7 @@ use crate::{
|
||||
sounds::PlaySound,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::input::native::ActionState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub static HEAD_COUNT: usize = 18;
|
||||
@@ -171,12 +173,6 @@ impl ActiveHeads {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event, Reflect)]
|
||||
pub enum SelectActiveHead {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct HeadChanged(pub usize);
|
||||
|
||||
@@ -188,8 +184,8 @@ pub fn plugin(app: &mut App) {
|
||||
Update,
|
||||
(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);
|
||||
}
|
||||
|
||||
@@ -237,50 +233,49 @@ fn reload(
|
||||
}
|
||||
|
||||
fn on_select_active_head(
|
||||
trigger: Trigger<SelectActiveHead>,
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
for (mut active_heads, mut hp, controls) in query.iter_mut() {
|
||||
if !controls.select_right && !controls.select_left {
|
||||
continue;
|
||||
}
|
||||
|
||||
match trigger.event() {
|
||||
SelectActiveHead::Right => {
|
||||
if controls.select_right {
|
||||
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 + (HEAD_SLOTS - 1)) % HEAD_SLOTS;
|
||||
}
|
||||
}
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
commands.trigger(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);
|
||||
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,
|
||||
));
|
||||
commands.trigger(HeadChanged(
|
||||
active_heads.heads[active_heads.current_slot].unwrap().head,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_swap_backpack(
|
||||
trigger: Trigger<BackbackSwapEvent>,
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>,
|
||||
mut backpack: ResMut<Backpack>,
|
||||
mut query: Query<(&mut ActiveHeads, &mut Hitpoints, &mut Backpack), With<Player>>,
|
||||
) {
|
||||
let backpack_slot = trigger.event().0;
|
||||
|
||||
let head = backpack.heads.get(backpack_slot).unwrap();
|
||||
|
||||
let Ok((mut active_heads, mut hp)) = query.single_mut() else {
|
||||
let Ok((mut active_heads, mut hp, mut backpack)) = query.single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let head = backpack.heads.get(backpack_slot).unwrap();
|
||||
|
||||
let selected_slot = active_heads.selected_slot;
|
||||
|
||||
let selected_head = active_heads.heads[selected_slot];
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use bevy::{pbr::NotShadowCaster, prelude::*};
|
||||
use lightyear::prelude::{NetworkTarget, Replicate};
|
||||
use lightyear::prelude::{Client, Connected, Disconnected, NetworkTarget, Replicate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -38,34 +38,58 @@ struct NpcSpawning {
|
||||
pub struct SpawningBeam(pub f32);
|
||||
|
||||
#[derive(Event)]
|
||||
struct OnCheckSpawns;
|
||||
struct OnCheckSpawns {
|
||||
on_client: bool,
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct SpawnCharacter(pub Vec3);
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
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)));
|
||||
|
||||
global_observer!(app, on_spawn_check);
|
||||
global_observer!(app, on_spawn);
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.init_resource::<NpcSpawning>();
|
||||
commands.trigger(OnCheckSpawns);
|
||||
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.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(
|
||||
_trigger: Trigger<OnCheckSpawns>,
|
||||
trigger: Trigger<OnCheckSpawns>,
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &EnemySpawn, &Transform), Without<Npc>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
spawning: Res<NpcSpawning>,
|
||||
is_server: Option<Res<IsServer>>,
|
||||
) {
|
||||
if is_server.is_none() {
|
||||
if is_server.is_none() && !trigger.event().on_client {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,6 +133,8 @@ fn on_spawn_check(
|
||||
|
||||
fn on_kill(
|
||||
trigger: Trigger<Kill>,
|
||||
is_server: Option<Res<IsServer>>,
|
||||
disconnected: Option<Single<&Disconnected>>,
|
||||
mut commands: Commands,
|
||||
query: Query<(&Transform, &EnemySpawn, &ActiveHead)>,
|
||||
) {
|
||||
@@ -123,7 +149,9 @@ fn on_kill(
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
backpack::Backpack,
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
cash::{Cash, CashCollectEvent},
|
||||
character::{AnimatedCharacter, Character},
|
||||
control::controller_common::PlayerCharacterController,
|
||||
control::{ControlState, controller_common::PlayerCharacterController},
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
head_drop::HeadDrops,
|
||||
@@ -22,7 +23,9 @@ use bevy::{
|
||||
prelude::*,
|
||||
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};
|
||||
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
@@ -98,6 +101,8 @@ fn player_bundle(transform: Transform, heads_db: &Res<HeadsDatabase>) -> impl Bu
|
||||
transform,
|
||||
Visibility::default(),
|
||||
PlayerCharacterController,
|
||||
ActionState::<ControlState>::default(),
|
||||
Backpack::default(),
|
||||
children![(
|
||||
Name::new("player-rig"),
|
||||
PlayerBodyMesh,
|
||||
@@ -197,6 +202,7 @@ fn on_update_head_mesh(
|
||||
mut player: Single<&mut ActiveHead, With<Player>>,
|
||||
head_db: Res<HeadsDatabase>,
|
||||
audio_assets: Res<AudioAssets>,
|
||||
sfx: Query<&AudioPlayer>,
|
||||
) -> Result {
|
||||
let (body_mesh, mesh_children) = *body_mesh;
|
||||
|
||||
@@ -213,10 +219,12 @@ fn on_update_head_mesh(
|
||||
|
||||
commands
|
||||
.entity(animated_char)
|
||||
.remove::<AnimatedCharacter>()
|
||||
.insert(AnimatedCharacter::new(trigger.0));
|
||||
|
||||
//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 {
|
||||
commands.entity(body_mesh).with_child((
|
||||
Name::new("sfx"),
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::{
|
||||
abilities::BuildExplosionSprite,
|
||||
animation::AnimationFlags,
|
||||
backpack::Backpack,
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
character::{self, AnimatedCharacter},
|
||||
control::{
|
||||
ControlState,
|
||||
controller_common::{MovementSpeedFactor, PlayerCharacterController},
|
||||
controls::ControllerSettings,
|
||||
},
|
||||
@@ -19,12 +21,13 @@ use bevy::prelude::*;
|
||||
use happy_feet::{
|
||||
grounding::GroundingState,
|
||||
prelude::{
|
||||
Character, CharacterDrag, CharacterGravity, CharacterMovement, GroundFriction, Grounding,
|
||||
CharacterDrag, CharacterGravity, CharacterMovement, GroundFriction, Grounding,
|
||||
GroundingConfig, KinematicVelocity, MoveInput, SteppingConfig,
|
||||
},
|
||||
};
|
||||
use lightyear::prelude::{
|
||||
ActionsChannel, AppComponentExt, PredictionMode, PredictionRegistrationExt,
|
||||
input::native::InputPlugin,
|
||||
};
|
||||
use lightyear_serde::{
|
||||
SerializationError, reader::ReadInteger, registry::SerializeFns, writer::WriteInteger,
|
||||
@@ -32,14 +35,17 @@ use lightyear_serde::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(InputPlugin::<ControlState>::default());
|
||||
|
||||
app.register_component::<ActiveHead>();
|
||||
app.register_component::<ActiveHeads>();
|
||||
app.register_component::<AngularVelocity>();
|
||||
app.register_component::<AnimatedCharacter>();
|
||||
app.register_component::<AnimationFlags>();
|
||||
app.register_component::<Backpack>();
|
||||
app.register_component::<CameraArmRotation>();
|
||||
app.register_component::<CameraTarget>();
|
||||
app.register_component::<Character>();
|
||||
app.register_component::<happy_feet::prelude::Character>();
|
||||
app.register_component::<character::Character>();
|
||||
app.register_component::<CharacterDrag>();
|
||||
app.register_component::<CharacterGravity>();
|
||||
|
||||
@@ -5,6 +5,7 @@ use bevy::ecs::{
|
||||
system::{Commands, EntityCommands},
|
||||
world::{EntityWorldMut, World},
|
||||
};
|
||||
use lightyear::prelude::Disconnected;
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
pub struct IsServer;
|
||||
@@ -16,7 +17,8 @@ pub trait CommandExt {
|
||||
impl<'w, 's> CommandExt for Commands<'w, 's> {
|
||||
fn trigger_server(&mut self, event: impl Event) -> &mut Self {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user