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