Input replication (#62)
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,22 +113,34 @@ 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())
|
||||||
let head_stats = headdb.head_stats(player_head.0);
|
{
|
||||||
res.next_trigger_timestamp = time.elapsed_secs() + head_stats.shoot_offset;
|
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 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()
|
||||||
|
|||||||
@@ -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,52 +256,58 @@ 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>,
|
||||||
) {
|
) {
|
||||||
if state.count == 0 {
|
for (controls, backpack) in player.iter() {
|
||||||
return;
|
if state.count == 0 {
|
||||||
}
|
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 });
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !state.open {
|
if !state.open {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
commands.trigger(PlaySound::Selection);
|
commands.trigger(PlaySound::Selection);
|
||||||
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(
|
||||||
if backpack.is_changed() || backpack.reloading() {
|
backpack: Query<Ref<Backpack>>,
|
||||||
sync(&backpack, &mut state, time.elapsed_secs());
|
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.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));
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,27 +16,28 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
fn heal(cash: i32, damage: u32) -> HealAction {
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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,58 +26,51 @@ 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 deadzone = 0.2;
|
let direction = controls.move_dir;
|
||||||
|
let deadzone = 0.2;
|
||||||
|
|
||||||
let (grounding, mut flags) = player.into_inner();
|
if flags.any_direction {
|
||||||
|
if direction.length_squared() < deadzone {
|
||||||
if let Some(gamepad) = controls.gamepad_state {
|
flags.any_direction = false;
|
||||||
direction += gamepad.move_dir;
|
}
|
||||||
}
|
} else if direction.length_squared() > deadzone {
|
||||||
|
flags.any_direction = true;
|
||||||
if flags.any_direction {
|
|
||||||
if direction.length_squared() < deadzone {
|
|
||||||
flags.any_direction = false;
|
|
||||||
}
|
}
|
||||||
} else if direction.length_squared() > deadzone {
|
|
||||||
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
|
||||||
if grounding.is_grounded() {
|
if grounding.is_grounded() {
|
||||||
flags.jumping = false;
|
flags.jumping = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>>,
|
||||||
) {
|
) {
|
||||||
if controls.view_mode {
|
for (mut rig_transform, child_of) in player.iter_mut() {
|
||||||
return;
|
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
|
// 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;
|
||||||
|
|||||||
@@ -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>>,
|
||||||
) {
|
) {
|
||||||
if controls.view_mode {
|
for (mut tr, child_of) in player.iter_mut() {
|
||||||
return;
|
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);
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
combined_controls.trigger = keyboard.trigger | gamepad.trigger;
|
||||||
} else {
|
combined_controls.just_triggered = keyboard.just_triggered | gamepad.just_triggered;
|
||||||
combined_controls.look_dir = keyboard.look_dir;
|
combined_controls.select_left = gamepad.select_left | keyboard.select_left;
|
||||||
combined_controls.move_dir = keyboard.move_dir;
|
combined_controls.select_right = gamepad.select_right | keyboard.select_right;
|
||||||
combined_controls.jump = keyboard.jump;
|
combined_controls.backpack_toggle = gamepad.backpack_toggle | keyboard.backpack_toggle;
|
||||||
combined_controls.view_mode = keyboard.view_mode;
|
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 {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,50 +233,49 @@ 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);
|
||||||
|
|
||||||
if active_heads.head(active_heads.selected_slot).is_some() {
|
if active_heads.head(active_heads.selected_slot).is_some() {
|
||||||
active_heads.current_slot = active_heads.selected_slot;
|
active_heads.current_slot = active_heads.selected_slot;
|
||||||
hp.set_health(active_heads.current().unwrap().health);
|
hp.set_health(active_heads.current().unwrap().health);
|
||||||
|
|
||||||
commands.trigger(HeadChanged(
|
commands.trigger(HeadChanged(
|
||||||
active_heads.heads[active_heads.current_slot].unwrap().head,
|
active_heads.heads[active_heads.current_slot].unwrap().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];
|
||||||
|
|||||||
@@ -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(
|
||||||
commands.init_resource::<NpcSpawning>();
|
mut commands: Commands,
|
||||||
commands.trigger(OnCheckSpawns);
|
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(
|
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();
|
||||||
|
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user