replication for client side projectiles (#87)
This commit is contained in:
1748
Cargo.lock
generated
1748
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -55,8 +55,8 @@ bevy_asset_loader = "=0.24.0-rc.1"
|
||||
bevy_ballistic = { git = "https://github.com/rustunit/bevy_ballistic.git", rev = "b08ffec" }
|
||||
bevy_common_assets = { version = "0.14.0", features = ["ron"] }
|
||||
bevy_debug_log = { git = "https://github.com/rustunit/bevy_debug_log.git", rev = "86051a0" }
|
||||
bevy_replicon = "0.36.1"
|
||||
bevy_replicon_renet = "0.12.0"
|
||||
bevy_replicon = "0.37.1"
|
||||
bevy_replicon_renet = "0.13.0"
|
||||
bevy_sprite3d = "7.0.0"
|
||||
bevy_trenchbroom = { version = "0.10", default-features = false, features = [
|
||||
"physics-integration",
|
||||
|
||||
@@ -6,6 +6,7 @@ use bevy::{
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_replicon::client::ClientSystems;
|
||||
use shared::{
|
||||
control::{
|
||||
BackpackButtonPress, CashHealPressed, ClientInputs, ControllerSet, Inputs, LocalInputs,
|
||||
@@ -28,12 +29,21 @@ pub fn plugin(app: &mut App) {
|
||||
)
|
||||
.chain()
|
||||
.in_set(ControllerSet::CollectInputs)
|
||||
.before(ClientSystems::Receive)
|
||||
.run_if(
|
||||
in_state(GameState::Playing)
|
||||
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
|
||||
),
|
||||
)
|
||||
.add_systems(PreUpdate, overwrite_local_inputs);
|
||||
);
|
||||
|
||||
// run this deliberately after local input processing ended
|
||||
// TODO: can and should be ordered using a set to guarantee it gets send out ASAP but after local input processing
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
overwrite_local_inputs.after(ClientSystems::Receive).run_if(
|
||||
in_state(GameState::Playing).and(resource_exists_and_equals(CharacterInputEnabled::On)),
|
||||
),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
Update,
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::{BuildExplosionSprite, TriggerCurver},
|
||||
abilities::{BuildExplosionSprite, ProjectileId, TriggerCurver},
|
||||
heads_database::HeadsDatabase,
|
||||
hitpoints::Hit,
|
||||
physics_layers::GameLayer,
|
||||
protocol::GltfSceneRoot,
|
||||
tb_entities::EnemySpawn,
|
||||
utils::{auto_rotate::AutoRotation, global_observer},
|
||||
utils::global_observer,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy_replicon::prelude::{Replicated, SendMode, ServerTriggerExt, ToClients};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
const MAX_SHOT_AGES: f32 = 15.;
|
||||
|
||||
@@ -21,6 +19,7 @@ const MAX_SHOT_AGES: f32 = 15.;
|
||||
pub struct CurverProjectile {
|
||||
time: f32,
|
||||
damage: u32,
|
||||
projectile: String,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
@@ -30,6 +29,8 @@ pub fn plugin(app: &mut App) {
|
||||
Update,
|
||||
(shot_collision, enemy_hit).run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
#[cfg(feature = "client")]
|
||||
app.add_systems(Update, shot_visuals.run_if(in_state(GameState::Playing)));
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
(update, timeout).run_if(in_state(GameState::Playing)),
|
||||
@@ -38,6 +39,27 @@ pub fn plugin(app: &mut App) {
|
||||
global_observer!(app, on_trigger_curver);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn shot_visuals(
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &CurverProjectile), Added<CurverProjectile>>,
|
||||
) {
|
||||
for (entity, projectile) in query.iter() {
|
||||
if commands.get_entity(entity).is_ok() {
|
||||
let child = commands
|
||||
.spawn((
|
||||
crate::utils::auto_rotate::AutoRotation(
|
||||
Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3),
|
||||
),
|
||||
crate::protocol::GltfSceneRoot::Projectile(projectile.projectile.clone()),
|
||||
))
|
||||
.id();
|
||||
|
||||
commands.entity(entity).add_child(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_trigger_curver(
|
||||
trigger: On<TriggerCurver>,
|
||||
mut commands: Commands,
|
||||
@@ -63,30 +85,30 @@ fn on_trigger_curver(
|
||||
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
|
||||
transform.translation += transform.forward().as_vec3() * 2.0;
|
||||
|
||||
commands.spawn((
|
||||
Name::new("projectile-missile"),
|
||||
CurverProjectile {
|
||||
time: time.elapsed_secs(),
|
||||
damage: head.damage,
|
||||
},
|
||||
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
|
||||
CollisionLayers::new(
|
||||
LayerMask(GameLayer::Projectile.to_bits()),
|
||||
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
|
||||
),
|
||||
Sensor,
|
||||
CollisionEventsEnabled,
|
||||
Visibility::default(),
|
||||
transform,
|
||||
Replicated,
|
||||
//TODO: put in client only system
|
||||
children![(
|
||||
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
|
||||
AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
|
||||
GltfSceneRoot::Projectile(head.projectile.clone()),
|
||||
let id = commands
|
||||
.spawn((
|
||||
Name::new("projectile-curver"),
|
||||
CurverProjectile {
|
||||
time: time.elapsed_secs(),
|
||||
damage: head.damage,
|
||||
projectile: head.projectile.clone(),
|
||||
},
|
||||
Collider::capsule_endpoints(0.5, Vec3::new(0., 0., 1.), Vec3::new(0., 0., -1.)),
|
||||
CollisionLayers::new(
|
||||
LayerMask(GameLayer::Projectile.to_bits()),
|
||||
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
|
||||
),
|
||||
Sensor,
|
||||
RigidBody::Kinematic,
|
||||
CollisionEventsEnabled,
|
||||
Visibility::default(),
|
||||
transform,
|
||||
Replicated,
|
||||
),],
|
||||
));
|
||||
ProjectileId(state.trigger_id),
|
||||
))
|
||||
.id();
|
||||
|
||||
debug!(id=?id, trigger_id = state.trigger_id, "Curver");
|
||||
}
|
||||
|
||||
fn enemy_hit(
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use super::TriggerGun;
|
||||
use crate::{
|
||||
GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase,
|
||||
hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer, tb_entities::EnemySpawn,
|
||||
utils::sprite_3d_animation::AnimationTimer,
|
||||
GameState, abilities::ProjectileId, billboards::Billboard, global_observer,
|
||||
heads_database::HeadsDatabase, hitpoints::Hit, loading_assets::GameAssets,
|
||||
physics_layers::GameLayer, tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
use bevy_replicon::prelude::Replicated;
|
||||
use bevy_sprite3d::Sprite3d;
|
||||
|
||||
#[derive(Component)]
|
||||
@@ -123,6 +124,8 @@ fn on_trigger_gun(
|
||||
CollisionEventsEnabled,
|
||||
Visibility::default(),
|
||||
transform,
|
||||
Replicated,
|
||||
ProjectileId(state.trigger_id),
|
||||
Children::spawn(Spawn(Gizmo {
|
||||
handle: gizmo_assets.add({
|
||||
let mut g = GizmoAsset::default();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::TriggerMissile;
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::{ExplodingProjectile, ExplodingProjectileSet},
|
||||
abilities::{ExplodingProjectile, ExplodingProjectileSet, ProjectileId},
|
||||
heads_database::HeadsDatabase,
|
||||
physics_layers::GameLayer,
|
||||
protocol::{GltfSceneRoot, PlaySound},
|
||||
@@ -60,36 +60,41 @@ fn on_trigger_missile(
|
||||
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
|
||||
transform.translation += transform.forward().as_vec3() * 2.0;
|
||||
|
||||
commands.spawn((
|
||||
Name::new("projectile-missile"),
|
||||
MissileProjectile {
|
||||
time: time.elapsed_secs(),
|
||||
damage: head.damage,
|
||||
},
|
||||
SpawnTrail::new(
|
||||
12,
|
||||
LinearRgba::rgb(1., 0.0, 0.),
|
||||
LinearRgba::rgb(0.9, 0.9, 0.),
|
||||
10.,
|
||||
)
|
||||
.init_with_pos(),
|
||||
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
|
||||
CollisionLayers::new(
|
||||
LayerMask(GameLayer::Projectile.to_bits()),
|
||||
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
|
||||
),
|
||||
Sensor,
|
||||
RigidBody::Kinematic,
|
||||
CollisionEventsEnabled,
|
||||
Visibility::default(),
|
||||
transform,
|
||||
Replicated,
|
||||
children![(
|
||||
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
|
||||
GltfSceneRoot::Projectile("missile".to_string()),
|
||||
Replicated
|
||||
)],
|
||||
));
|
||||
let id = commands
|
||||
.spawn((
|
||||
Name::new("projectile-missile"),
|
||||
MissileProjectile {
|
||||
time: time.elapsed_secs(),
|
||||
damage: head.damage,
|
||||
},
|
||||
SpawnTrail::new(
|
||||
12,
|
||||
LinearRgba::rgb(1., 0.0, 0.),
|
||||
LinearRgba::rgb(0.9, 0.9, 0.),
|
||||
10.,
|
||||
)
|
||||
.init_with_pos(),
|
||||
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
|
||||
CollisionLayers::new(
|
||||
LayerMask(GameLayer::Projectile.to_bits()),
|
||||
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
|
||||
),
|
||||
Sensor,
|
||||
RigidBody::Kinematic,
|
||||
CollisionEventsEnabled,
|
||||
Visibility::default(),
|
||||
transform,
|
||||
Replicated,
|
||||
ProjectileId(state.trigger_id),
|
||||
children![(
|
||||
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
|
||||
GltfSceneRoot::Projectile("missile".to_string()),
|
||||
Replicated
|
||||
)],
|
||||
))
|
||||
.id();
|
||||
|
||||
debug!(id=?id, trigger_id = state.trigger_id, "Missile");
|
||||
}
|
||||
|
||||
fn update(mut query: Query<&mut Transform, With<MissileProjectile>>) {
|
||||
|
||||
@@ -6,18 +6,21 @@ pub mod missile;
|
||||
pub mod thrown;
|
||||
|
||||
use crate::{
|
||||
GameState, global_observer,
|
||||
GameState,
|
||||
control::ControllerSet,
|
||||
global_observer,
|
||||
loading_assets::GameAssets,
|
||||
physics_layers::GameLayer,
|
||||
player::Player,
|
||||
protocol::PlaySound,
|
||||
utils::{billboards::Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer},
|
||||
};
|
||||
use crate::{
|
||||
aim::AimTarget, character::CharacterHierarchy, control::Inputs, heads::ActiveHeads,
|
||||
heads_database::HeadsDatabase, player::Player,
|
||||
heads_database::HeadsDatabase,
|
||||
};
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, Signature, ToClients};
|
||||
use bevy_sprite3d::Sprite3d;
|
||||
pub use healing::Healing;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -44,6 +47,7 @@ pub struct TriggerData {
|
||||
pos: Vec3,
|
||||
target_layer: GameLayer,
|
||||
head: usize,
|
||||
trigger_id: usize,
|
||||
}
|
||||
|
||||
impl TriggerData {
|
||||
@@ -53,6 +57,7 @@ impl TriggerData {
|
||||
pos: Vec3,
|
||||
target_layer: GameLayer,
|
||||
head: usize,
|
||||
trigger_id: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
target,
|
||||
@@ -60,6 +65,7 @@ impl TriggerData {
|
||||
pos,
|
||||
target_layer,
|
||||
head,
|
||||
trigger_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,14 +91,13 @@ pub struct TriggerCurver(pub TriggerData);
|
||||
#[reflect(Component)]
|
||||
pub struct PlayerTriggerState {
|
||||
next_trigger_timestamp: f32,
|
||||
active: bool,
|
||||
projectile_count: usize,
|
||||
}
|
||||
|
||||
impl PlayerTriggerState {
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.active
|
||||
}
|
||||
}
|
||||
#[derive(Component, Reflect, Deserialize, Serialize, Hash)]
|
||||
#[reflect(Component)]
|
||||
#[require(Signature::of::<ProjectileId>())]
|
||||
pub struct ProjectileId(pub usize);
|
||||
|
||||
#[derive(Component)]
|
||||
#[component(storage = "SparseSet")]
|
||||
@@ -107,6 +112,7 @@ pub struct ExplodingProjectile {
|
||||
anim_time: f32,
|
||||
}
|
||||
|
||||
// TODO: move explosions into separate modul
|
||||
#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ExplodingProjectileSet {
|
||||
Mark,
|
||||
@@ -128,12 +134,14 @@ pub fn plugin(app: &mut App) {
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
(on_trigger_state, update, update_heal_ability)
|
||||
(update, update_heal_ability)
|
||||
.chain()
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
explode_projectiles.in_set(ExplodingProjectileSet::Explode),
|
||||
@@ -144,6 +152,8 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
fn explode_projectiles(mut commands: Commands, query: Query<(Entity, &ExplodingProjectile)>) {
|
||||
for (shot_entity, projectile) in query.iter() {
|
||||
debug!(id=?shot_entity, "Projectile explosion");
|
||||
|
||||
if let Ok(mut entity) = commands.get_entity(shot_entity) {
|
||||
entity.try_despawn();
|
||||
} else {
|
||||
@@ -176,12 +186,6 @@ fn explode_projectiles(mut commands: Commands, query: Query<(Entity, &ExplodingP
|
||||
}
|
||||
}
|
||||
|
||||
fn on_trigger_state(mut players: Query<(&mut PlayerTriggerState, &Inputs), With<Player>>) {
|
||||
for (mut trigger_state, inputs) in players.iter_mut() {
|
||||
trigger_state.active = inputs.trigger;
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
mut commands: Commands,
|
||||
mut query: Query<
|
||||
@@ -200,11 +204,15 @@ fn update(
|
||||
character: CharacterHierarchy,
|
||||
) {
|
||||
for (player, mut active_heads, mut trigger_state, target, inputs) in query.iter_mut() {
|
||||
if trigger_state.active && trigger_state.next_trigger_timestamp < time.elapsed_secs() {
|
||||
if inputs.trigger && trigger_state.next_trigger_timestamp < time.elapsed_secs() {
|
||||
let Some(state) = active_heads.current() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !state.has_ammo() {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = if let Some(target) = target.0
|
||||
&& query_transform.get(target).is_ok()
|
||||
{
|
||||
@@ -229,6 +237,7 @@ fn update(
|
||||
active_heads.use_ammo(time.elapsed_secs());
|
||||
|
||||
trigger_state.next_trigger_timestamp = time.elapsed_secs() + (1. / head.aps);
|
||||
trigger_state.projectile_count += 1;
|
||||
|
||||
let trigger_state = TriggerData {
|
||||
dir: Dir3::try_from(inputs.look_dir).unwrap_or(Dir3::NEG_Z),
|
||||
@@ -236,6 +245,7 @@ fn update(
|
||||
target,
|
||||
target_layer: GameLayer::Npc,
|
||||
head: state.head,
|
||||
trigger_id: trigger_state.projectile_count,
|
||||
};
|
||||
|
||||
match head.ability {
|
||||
@@ -252,11 +262,11 @@ fn update(
|
||||
|
||||
fn update_heal_ability(
|
||||
mut commands: Commands,
|
||||
players: Query<(Entity, &ActiveHeads, Ref<PlayerTriggerState>), With<Player>>,
|
||||
players: Query<(Entity, &ActiveHeads, Ref<Inputs>), With<Player>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
for (player, active_heads, trigger_state) in players.iter() {
|
||||
if trigger_state.is_changed() {
|
||||
for (player, active_heads, inputs) in players.iter() {
|
||||
if inputs.is_changed() {
|
||||
let Some(state) = active_heads.current() else {
|
||||
return;
|
||||
};
|
||||
@@ -268,7 +278,7 @@ fn update_heal_ability(
|
||||
}
|
||||
|
||||
use crate::abilities::healing::HealingState;
|
||||
if trigger_state.active {
|
||||
if inputs.trigger {
|
||||
use crate::abilities::healing::HealingStateChanged;
|
||||
|
||||
commands.trigger(HealingStateChanged {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::TriggerThrow;
|
||||
use crate::{
|
||||
abilities::{ExplodingProjectile, ExplodingProjectileSet},
|
||||
GameState,
|
||||
abilities::{ExplodingProjectile, ExplodingProjectileSet, ProjectileId},
|
||||
heads_database::HeadsDatabase,
|
||||
physics_layers::GameLayer,
|
||||
protocol::{GltfSceneRoot, PlaySound},
|
||||
utils::{auto_rotate::AutoRotation, global_observer},
|
||||
protocol::PlaySound,
|
||||
utils::global_observer,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
@@ -16,17 +17,38 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct ThrownProjectile {
|
||||
impact_animation: bool,
|
||||
damage: u32,
|
||||
projectile: String,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
shot_collision.in_set(ExplodingProjectileSet::Mark),
|
||||
shot_collision
|
||||
.in_set(ExplodingProjectileSet::Mark)
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
app.add_systems(Update, shot_visuals.run_if(in_state(GameState::Playing)));
|
||||
|
||||
global_observer!(app, on_trigger_thrown);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn shot_visuals(
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &ThrownProjectile), Added<ThrownProjectile>>,
|
||||
) {
|
||||
for (entity, thrown) in query.iter() {
|
||||
commands.entity(entity).try_insert((
|
||||
crate::utils::auto_rotate::AutoRotation(
|
||||
Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3),
|
||||
),
|
||||
crate::protocol::GltfSceneRoot::Projectile(thrown.projectile.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn on_trigger_thrown(
|
||||
trigger: On<TriggerThrow>,
|
||||
mut commands: Commands,
|
||||
@@ -57,13 +79,14 @@ fn on_trigger_thrown(
|
||||
//TODO: projectile db?
|
||||
let explosion_animation = !matches!(state.head, 8 | 16);
|
||||
|
||||
commands
|
||||
let id = commands
|
||||
.spawn((
|
||||
Transform::from_translation(pos),
|
||||
Name::new("projectile-thrown"),
|
||||
ThrownProjectile {
|
||||
impact_animation: explosion_animation,
|
||||
damage: head.damage,
|
||||
projectile: head.projectile.clone(),
|
||||
},
|
||||
Collider::sphere(0.4),
|
||||
CollisionLayers::new(
|
||||
@@ -77,12 +100,11 @@ fn on_trigger_thrown(
|
||||
Visibility::default(),
|
||||
Sensor,
|
||||
Replicated,
|
||||
ProjectileId(state.trigger_id),
|
||||
))
|
||||
.with_child((
|
||||
AutoRotation(Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3)),
|
||||
GltfSceneRoot::Projectile(head.projectile.clone()),
|
||||
Replicated,
|
||||
));
|
||||
.id();
|
||||
|
||||
debug!(id=?id, trigger_id = state.trigger_id, "Thrown");
|
||||
}
|
||||
|
||||
fn shot_collision(
|
||||
|
||||
@@ -169,6 +169,8 @@ fn engage_and_throw(
|
||||
t.translation,
|
||||
crate::physics_layers::GameLayer::Player,
|
||||
npc_head.head,
|
||||
// TODO: we probably need to make sure the ai's projectile does not get deduped, zero should not be used by anyone though
|
||||
0,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
#[cfg(feature = "client")]
|
||||
use crate::{
|
||||
backpack::backpack_ui::BackpackUiState, control::BackpackButtonPress, player::LocalPlayer,
|
||||
protocol::PlaySound,
|
||||
};
|
||||
use crate::{
|
||||
cash::CashCollectEvent,
|
||||
global_observer,
|
||||
@@ -62,11 +57,16 @@ pub fn plugin(app: &mut App) {
|
||||
#[cfg(feature = "client")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn backpack_inputs(
|
||||
backpacks: Single<(&Backpack, &mut BackpackUiState), With<LocalPlayer>>,
|
||||
mut backpack_inputs: MessageReader<BackpackButtonPress>,
|
||||
backpacks: Single<
|
||||
(&Backpack, &mut backpack_ui::BackpackUiState),
|
||||
With<crate::player::LocalPlayer>,
|
||||
>,
|
||||
mut backpack_inputs: MessageReader<crate::control::BackpackButtonPress>,
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
use crate::{control::BackpackButtonPress, protocol::PlaySound};
|
||||
|
||||
let (backpack, mut state) = backpacks.into_inner();
|
||||
|
||||
for input in backpack_inputs.read() {
|
||||
@@ -117,7 +117,7 @@ fn backpack_inputs(
|
||||
#[cfg(feature = "client")]
|
||||
fn sync_on_change(
|
||||
backpack: Query<Ref<Backpack>>,
|
||||
mut state: Single<&mut BackpackUiState>,
|
||||
mut state: Single<&mut backpack_ui::BackpackUiState>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for backpack in backpack.iter() {
|
||||
@@ -128,7 +128,7 @@ fn sync_on_change(
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn sync_backpack_ui(backpack: &Backpack, state: &mut BackpackUiState, time: f32) {
|
||||
fn sync_backpack_ui(backpack: &Backpack, state: &mut backpack_ui::BackpackUiState, time: f32) {
|
||||
use crate::backpack::backpack_ui::BACKPACK_HEAD_SLOTS;
|
||||
|
||||
state.count = backpack.heads.len();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use super::{ControllerSet, ControllerSwitchEvent};
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::PlayerTriggerState,
|
||||
animation::AnimationFlags,
|
||||
control::{ControllerSettings, Inputs, SelectedController},
|
||||
physics_layers::GameLayer,
|
||||
@@ -43,16 +42,11 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
fn set_animation_flags(
|
||||
mut player: Query<
|
||||
(
|
||||
&Grounding,
|
||||
&mut AnimationFlags,
|
||||
&Inputs,
|
||||
&PlayerTriggerState,
|
||||
),
|
||||
(&Grounding, &mut AnimationFlags, &Inputs),
|
||||
(With<Player>, Without<ConfirmHistory>),
|
||||
>,
|
||||
) {
|
||||
for (grounding, mut flags, inputs, trigger_state) in player.iter_mut() {
|
||||
for (grounding, mut flags, inputs) in player.iter_mut() {
|
||||
let direction = inputs.move_dir;
|
||||
let deadzone = 0.2;
|
||||
|
||||
@@ -64,7 +58,7 @@ fn set_animation_flags(
|
||||
flags.any_direction = true;
|
||||
}
|
||||
|
||||
flags.shooting = trigger_state.is_active();
|
||||
flags.shooting = inputs.trigger;
|
||||
|
||||
// `apply_controls` sets the jump flag when the player actually jumps.
|
||||
// Unset the flag on hitting the ground
|
||||
@@ -97,14 +91,13 @@ pub fn reset_upon_switch(
|
||||
let flat_look_dir = inputs.look_dir.with_y(0.0).normalize();
|
||||
rig_transform.look_to(flat_look_dir, Dir3::Y);
|
||||
|
||||
match selected_controller.0 {
|
||||
ControllerSet::ApplyControlsFly => {
|
||||
match *selected_controller {
|
||||
SelectedController::Flying => {
|
||||
c.entity(controller).insert(FLYING_MOVEMENT_CONFIG);
|
||||
}
|
||||
ControllerSet::ApplyControlsRun => {
|
||||
SelectedController::Running => {
|
||||
c.entity(controller).insert(RUNNING_MOVEMENT_CONFIG);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,19 @@ pub mod controller_common;
|
||||
pub mod controller_flying;
|
||||
pub mod controller_running;
|
||||
|
||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Default)]
|
||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub enum ControllerSet {
|
||||
CollectInputs,
|
||||
ApplyControlsFly,
|
||||
#[default]
|
||||
ApplyControlsRun,
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug, Default, PartialEq)]
|
||||
pub struct SelectedController(pub ControllerSet);
|
||||
#[derive(Resource, Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub enum SelectedController {
|
||||
Flying,
|
||||
#[default]
|
||||
Running,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<ControllerSettings>()
|
||||
@@ -48,12 +51,8 @@ pub fn plugin(app: &mut App) {
|
||||
app.configure_sets(
|
||||
FixedUpdate,
|
||||
(
|
||||
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController(
|
||||
ControllerSet::ApplyControlsFly,
|
||||
))),
|
||||
ControllerSet::ApplyControlsRun.run_if(resource_equals(SelectedController(
|
||||
ControllerSet::ApplyControlsRun,
|
||||
))),
|
||||
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController::Flying)),
|
||||
ControllerSet::ApplyControlsRun.run_if(resource_equals(SelectedController::Running)),
|
||||
)
|
||||
.chain()
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
@@ -104,7 +103,6 @@ impl MapEntities for Inputs {
|
||||
pub struct ClientInputs(pub Inputs);
|
||||
|
||||
/// A cache to collect inputs into clientside, so that they don't get overwritten by replication from the server
|
||||
#[cfg(feature = "client")]
|
||||
#[derive(Component, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct LocalInputs(pub Inputs);
|
||||
@@ -172,14 +170,14 @@ fn head_change(
|
||||
for (entity, head) in query.iter() {
|
||||
let stats = heads_db.head_stats(head.0);
|
||||
let controller = match stats.controls {
|
||||
HeadControls::Plane => ControllerSet::ApplyControlsFly,
|
||||
HeadControls::Walk => ControllerSet::ApplyControlsRun,
|
||||
HeadControls::Plane => SelectedController::Flying,
|
||||
HeadControls::Walk => SelectedController::Running,
|
||||
};
|
||||
|
||||
if selected_controller.0 != controller {
|
||||
if *selected_controller != controller {
|
||||
event_controller_switch.write(ControllerSwitchEvent { controller: entity });
|
||||
|
||||
selected_controller.0 = controller;
|
||||
*selected_controller = controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::{
|
||||
character::HedzCharacter,
|
||||
protocol::PlayerId,
|
||||
};
|
||||
#[cfg(feature = "client")]
|
||||
use crate::{backpack::backpack_ui::BackpackUiState, control::LocalInputs};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{
|
||||
@@ -20,7 +19,6 @@ use serde::{Deserialize, Serialize};
|
||||
#[require(HedzCharacter, DebugInput = DebugInput, PlayerTriggerState)]
|
||||
pub struct Player;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(LocalInputs, BackpackUiState)]
|
||||
|
||||
@@ -26,8 +26,9 @@ pub enum NetworkedCollider {
|
||||
half_extents: Vec3,
|
||||
},
|
||||
Capsule {
|
||||
a: Vec3,
|
||||
b: Vec3,
|
||||
radius: f32,
|
||||
length: f32,
|
||||
},
|
||||
/// If a collider value wasn't set up to be replicated, it is replicated as unknown
|
||||
/// and a warning is logged, and unwraps to `sphere(0.1)` on the other side. Likely
|
||||
@@ -47,8 +48,9 @@ impl From<Collider> for NetworkedCollider {
|
||||
}
|
||||
} else if let Some(value) = value.shape().as_capsule() {
|
||||
NetworkedCollider::Capsule {
|
||||
a: value.segment.a.into(),
|
||||
b: value.segment.b.into(),
|
||||
radius: value.radius,
|
||||
length: value.height(),
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
@@ -66,7 +68,9 @@ impl From<NetworkedCollider> for Collider {
|
||||
NetworkedCollider::Cuboid { half_extents } => {
|
||||
Collider::cuboid(half_extents.x, half_extents.y, half_extents.z)
|
||||
}
|
||||
NetworkedCollider::Capsule { radius, length } => Collider::capsule(radius, length),
|
||||
NetworkedCollider::Capsule { a, b, radius } => {
|
||||
Collider::capsule_endpoints(radius, a, b)
|
||||
}
|
||||
NetworkedCollider::Unknown => Collider::sphere(0.1),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::{BuildExplosionSprite, curver::CurverProjectile, healing::Healing},
|
||||
abilities::{
|
||||
BuildExplosionSprite, curver::CurverProjectile, healing::Healing, thrown::ThrownProjectile,
|
||||
},
|
||||
animation::AnimationFlags,
|
||||
backpack::{Backpack, BackpackSwapEvent},
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
@@ -79,7 +81,7 @@ pub fn plugin(app: &mut App) {
|
||||
app.replicate::<ChildOf>();
|
||||
app.sync_related_entities::<ChildOf>();
|
||||
|
||||
app.replicate::<components::GltfSceneRoot>()
|
||||
app.replicate_once::<components::GltfSceneRoot>()
|
||||
.replicate_once::<components::PlayerId>()
|
||||
.replicate::<components::TbMapEntityId>()
|
||||
.replicate::<ActiveHead>()
|
||||
@@ -88,7 +90,6 @@ pub fn plugin(app: &mut App) {
|
||||
.replicate::<AnimatedCharacter>()
|
||||
.replicate::<AnimationFlags>()
|
||||
.replicate_once::<AutoRotation>()
|
||||
.replicate_once::<CurverProjectile>()
|
||||
.replicate::<Backpack>()
|
||||
.replicate::<Billboard>()
|
||||
.replicate_once::<CameraArmRotation>()
|
||||
@@ -108,6 +109,9 @@ pub fn plugin(app: &mut App) {
|
||||
.replicate::<UiActiveHeads>()
|
||||
.replicate_as::<Visibility, SerVisibility>();
|
||||
|
||||
app.replicate_once::<ThrownProjectile>()
|
||||
.replicate_once::<CurverProjectile>();
|
||||
|
||||
// Physics components
|
||||
app.replicate::<AngularInertia>()
|
||||
.replicate::<AngularVelocity>()
|
||||
|
||||
Reference in New Issue
Block a user