Sync reloading and cash (#66)
This commit is contained in:
@@ -15,6 +15,7 @@ use shared::{DebugVisuals, GameState, heads_database::HeadDatabaseAsset};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod player;
|
||||||
mod server;
|
mod server;
|
||||||
mod tb_entities;
|
mod tb_entities;
|
||||||
|
|
||||||
|
|||||||
94
crates/server/src/player.rs
Normal file
94
crates/server/src/player.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use lightyear::prelude::{input::native::ActionState, *};
|
||||||
|
use shared::{
|
||||||
|
backpack::{Backpack, backpack_ui::BackpackUiState},
|
||||||
|
camera::{CameraArmRotation, CameraTarget},
|
||||||
|
cash::CashResource,
|
||||||
|
character::AnimatedCharacter,
|
||||||
|
control::{ControlState, controller_common::PlayerCharacterController},
|
||||||
|
head::ActiveHead,
|
||||||
|
head_drop::HeadDrops,
|
||||||
|
heads::{ActiveHeads, HeadChanged, HeadState, heads_ui::UiActiveHeads},
|
||||||
|
heads_database::HeadsDatabase,
|
||||||
|
hitpoints::{Hitpoints, Kill},
|
||||||
|
npc::SpawnCharacter,
|
||||||
|
player::{Player, PlayerBodyMesh},
|
||||||
|
tb_entities::SpawnPoint,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn spawn(
|
||||||
|
mut commands: Commands,
|
||||||
|
owner: Entity,
|
||||||
|
query: Query<&Transform, With<SpawnPoint>>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
heads_db: Res<HeadsDatabase>,
|
||||||
|
) {
|
||||||
|
let Some(spawn) = query.iter().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.));
|
||||||
|
|
||||||
|
let mut player = commands.spawn((
|
||||||
|
(
|
||||||
|
Name::from("player"),
|
||||||
|
Player,
|
||||||
|
ActiveHead(0),
|
||||||
|
ActiveHeads::new([
|
||||||
|
Some(HeadState::new(0, heads_db.as_ref())),
|
||||||
|
Some(HeadState::new(3, heads_db.as_ref())),
|
||||||
|
Some(HeadState::new(6, heads_db.as_ref())),
|
||||||
|
Some(HeadState::new(10, heads_db.as_ref())),
|
||||||
|
Some(HeadState::new(9, heads_db.as_ref())),
|
||||||
|
]),
|
||||||
|
Hitpoints::new(100),
|
||||||
|
CashResource::default(),
|
||||||
|
CameraTarget,
|
||||||
|
transform,
|
||||||
|
Visibility::default(),
|
||||||
|
PlayerCharacterController,
|
||||||
|
),
|
||||||
|
ActionState::<ControlState>::default(),
|
||||||
|
Backpack::default(),
|
||||||
|
BackpackUiState::default(),
|
||||||
|
UiActiveHeads::default(),
|
||||||
|
Replicate::to_clients(NetworkTarget::All),
|
||||||
|
PredictionTarget::to_clients(NetworkTarget::All),
|
||||||
|
ControlledBy {
|
||||||
|
owner,
|
||||||
|
lifetime: Lifetime::SessionBased,
|
||||||
|
},
|
||||||
|
children![(
|
||||||
|
Name::new("player-rig"),
|
||||||
|
PlayerBodyMesh,
|
||||||
|
CameraArmRotation,
|
||||||
|
children![AnimatedCharacter::new(0)],
|
||||||
|
)],
|
||||||
|
));
|
||||||
|
player.observe(on_kill);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
||||||
|
PlaybackSettings::DESPAWN,
|
||||||
|
));
|
||||||
|
|
||||||
|
commands.trigger(SpawnCharacter(transform.translation));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_kill(
|
||||||
|
trigger: Trigger<Kill>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut query: Query<(&Transform, &ActiveHead, &mut ActiveHeads, &mut Hitpoints)>,
|
||||||
|
) {
|
||||||
|
let Ok((transform, active, mut heads, mut hp)) = query.get_mut(trigger.target()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
commands.trigger(HeadDrops::new(transform.translation, active.0));
|
||||||
|
|
||||||
|
if let Some(new_head) = heads.loose_current() {
|
||||||
|
hp.set_health(heads.current().unwrap().health);
|
||||||
|
|
||||||
|
commands.trigger(HeadChanged(new_head));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ fn handle_new_client(
|
|||||||
.entity(trigger.target())
|
.entity(trigger.target())
|
||||||
.insert(ReplicationSender::default());
|
.insert(ReplicationSender::default());
|
||||||
|
|
||||||
shared::player::spawn(commands, trigger.target(), query, asset_server, heads_db);
|
crate::player::spawn(commands, trigger.target(), query, asset_server, heads_db);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
use super::{Backpack, UiHeadState};
|
#[cfg(feature = "server")]
|
||||||
|
use super::Backpack;
|
||||||
|
use super::UiHeadState;
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState, HEDZ_GREEN, backpack::BackbackSwapEvent, control::ControlState, heads::HeadsImages,
|
GameState, HEDZ_GREEN, loading_assets::UIAssets, protocol::PlayBackpackSound, sounds::PlaySound,
|
||||||
loading_assets::UIAssets, sounds::PlaySound,
|
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use crate::{backpack::BackbackSwapEvent, control::ControlState};
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
use crate::{global_observer, heads::HeadsImages};
|
||||||
use bevy::{ecs::spawn::SpawnIter, prelude::*};
|
use bevy::{ecs::spawn::SpawnIter, prelude::*};
|
||||||
use lightyear::prelude::input::native::ActionState;
|
#[cfg(feature = "server")]
|
||||||
|
use lightyear::prelude::{ActionsChannel, TriggerSender, input::native::ActionState};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
static HEAD_SLOTS: usize = 5;
|
static HEAD_SLOTS: usize = 5;
|
||||||
|
|
||||||
@@ -14,18 +21,21 @@ struct BackpackMarker;
|
|||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
struct BackpackCountText;
|
struct BackpackCountText;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
struct HeadSelector(pub usize);
|
struct HeadSelector(pub usize);
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
struct HeadImage(pub usize);
|
struct HeadImage(pub usize);
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
struct HeadDamage(pub usize);
|
struct HeadDamage(pub usize);
|
||||||
|
|
||||||
#[derive(Resource, Default, Debug, Reflect)]
|
#[derive(Component, Default, Debug, Reflect, Serialize, Deserialize, PartialEq)]
|
||||||
#[reflect(Resource, Default)]
|
#[reflect(Component, Default)]
|
||||||
struct BackpackUiState {
|
pub struct BackpackUiState {
|
||||||
heads: [Option<UiHeadState>; 5],
|
heads: [Option<UiHeadState>; 5],
|
||||||
scroll: usize,
|
scroll: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
@@ -33,6 +43,7 @@ struct BackpackUiState {
|
|||||||
open: bool,
|
open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
impl BackpackUiState {
|
impl BackpackUiState {
|
||||||
fn relative_current_slot(&self) -> usize {
|
fn relative_current_slot(&self) -> usize {
|
||||||
self.current_slot.saturating_sub(self.scroll)
|
self.current_slot.saturating_sub(self.scroll)
|
||||||
@@ -41,14 +52,22 @@ impl BackpackUiState {
|
|||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.register_type::<BackpackUiState>();
|
app.register_type::<BackpackUiState>();
|
||||||
app.init_resource::<BackpackUiState>();
|
|
||||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||||
|
#[cfg(feature = "server")]
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
FixedUpdate,
|
||||||
(update, sync_on_change, update_visibility, update_count)
|
sync_on_change.run_if(in_state(GameState::Playing)),
|
||||||
.run_if(in_state(GameState::Playing)),
|
|
||||||
);
|
);
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
app.add_systems(
|
||||||
|
FixedUpdate,
|
||||||
|
(update, update_visibility, update_count).run_if(in_state(GameState::Playing)),
|
||||||
|
);
|
||||||
|
#[cfg(feature = "server")]
|
||||||
app.add_systems(FixedUpdate, swap_head_inputs);
|
app.add_systems(FixedUpdate, swap_head_inputs);
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
global_observer!(app, play_backpack_sound);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
||||||
@@ -185,80 +204,75 @@ fn spawn_head_ui(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
fn update_visibility(
|
fn update_visibility(
|
||||||
state: Res<BackpackUiState>,
|
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||||
mut backpack: Query<&mut Visibility, (With<BackpackMarker>, Without<BackpackCountText>)>,
|
mut backpack: Single<&mut Visibility, (With<BackpackMarker>, Without<BackpackCountText>)>,
|
||||||
mut count: Query<&mut Visibility, (Without<BackpackMarker>, With<BackpackCountText>)>,
|
mut count: Single<&mut Visibility, (Without<BackpackMarker>, With<BackpackCountText>)>,
|
||||||
) {
|
) {
|
||||||
if state.is_changed() {
|
**backpack = if state.open {
|
||||||
for mut vis in backpack.iter_mut() {
|
Visibility::Visible
|
||||||
*vis = if state.open {
|
} else {
|
||||||
Visibility::Visible
|
Visibility::Hidden
|
||||||
} else {
|
};
|
||||||
Visibility::Hidden
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for mut vis in count.iter_mut() {
|
**count = if !state.open {
|
||||||
*vis = if !state.open {
|
Visibility::Visible
|
||||||
Visibility::Visible
|
} else {
|
||||||
} else {
|
Visibility::Hidden
|
||||||
Visibility::Hidden
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
fn update_count(
|
fn update_count(
|
||||||
state: Res<BackpackUiState>,
|
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||||
text: Query<Entity, With<BackpackCountText>>,
|
text: Option<Single<Entity, With<BackpackCountText>>>,
|
||||||
mut writer: TextUiWriter,
|
mut writer: TextUiWriter,
|
||||||
) {
|
) {
|
||||||
if state.is_changed() {
|
let Some(text) = text else {
|
||||||
let Some(text) = text.iter().next() else {
|
return;
|
||||||
return;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
*writer.text(text, 0) = state.count.to_string();
|
*writer.text(*text, 0) = state.count.to_string();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
fn update(
|
fn update(
|
||||||
state: Res<BackpackUiState>,
|
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||||
heads_images: Res<HeadsImages>,
|
heads_images: Res<HeadsImages>,
|
||||||
mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
|
mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
|
||||||
mut head_damage: Query<(&HeadDamage, &mut Node), Without<HeadSelector>>,
|
mut head_damage: Query<(&HeadDamage, &mut Node), Without<HeadSelector>>,
|
||||||
mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
|
mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
|
||||||
) {
|
) {
|
||||||
if state.is_changed() {
|
for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
|
||||||
for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
|
if let Some(head) = &state.heads[*head] {
|
||||||
if let Some(head) = &state.heads[*head] {
|
*vis = Visibility::Inherited;
|
||||||
*vis = Visibility::Inherited;
|
image.image = heads_images.heads[head.head].clone();
|
||||||
image.image = heads_images.heads[head.head].clone();
|
} else {
|
||||||
} else {
|
*vis = Visibility::Hidden;
|
||||||
*vis = Visibility::Hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (HeadDamage(head), mut node) in head_damage.iter_mut() {
|
}
|
||||||
if let Some(head) = &state.heads[*head] {
|
for (HeadDamage(head), mut node) in head_damage.iter_mut() {
|
||||||
node.height = Val::Percent(head.damage() * 100.0);
|
if let Some(head) = &state.heads[*head] {
|
||||||
}
|
node.height = Val::Percent(head.damage() * 100.0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (HeadSelector(head), mut vis) in head_selector.iter_mut() {
|
for (HeadSelector(head), mut vis) in head_selector.iter_mut() {
|
||||||
*vis = if *head == state.relative_current_slot() {
|
*vis = if *head == state.relative_current_slot() {
|
||||||
Visibility::Inherited
|
Visibility::Inherited
|
||||||
} else {
|
} else {
|
||||||
Visibility::Hidden
|
Visibility::Hidden
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
fn swap_head_inputs(
|
fn swap_head_inputs(
|
||||||
player: Query<(&ActionState<ControlState>, Ref<Backpack>)>,
|
player: Query<(&ActionState<ControlState>, Ref<Backpack>)>,
|
||||||
|
mut trigger: Single<&mut TriggerSender<PlayBackpackSound>>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut state: ResMut<BackpackUiState>,
|
mut state: Single<&mut BackpackUiState>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
for (controls, backpack) in player.iter() {
|
for (controls, backpack) in player.iter() {
|
||||||
@@ -268,7 +282,7 @@ fn swap_head_inputs(
|
|||||||
|
|
||||||
if controls.backpack_toggle {
|
if controls.backpack_toggle {
|
||||||
state.open = !state.open;
|
state.open = !state.open;
|
||||||
commands.trigger(PlaySound::Backpack { open: state.open });
|
trigger.trigger::<ActionsChannel>(PlayBackpackSound { open: state.open });
|
||||||
}
|
}
|
||||||
|
|
||||||
if !state.open {
|
if !state.open {
|
||||||
@@ -295,9 +309,17 @@ fn swap_head_inputs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
fn play_backpack_sound(trigger: Trigger<PlayBackpackSound>, mut commands: Commands) {
|
||||||
|
commands.trigger(PlaySound::Backpack {
|
||||||
|
open: trigger.event().open,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
fn sync_on_change(
|
fn sync_on_change(
|
||||||
backpack: Query<Ref<Backpack>>,
|
backpack: Query<Ref<Backpack>>,
|
||||||
mut state: ResMut<BackpackUiState>,
|
mut state: Single<&mut BackpackUiState>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
for backpack in backpack.iter() {
|
for backpack in backpack.iter() {
|
||||||
@@ -307,7 +329,8 @@ fn sync_on_change(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync(backpack: &Backpack, state: &mut ResMut<BackpackUiState>, time: f32) {
|
#[cfg(feature = "server")]
|
||||||
|
fn sync(backpack: &Backpack, state: &mut Single<&mut 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));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
mod backpack_ui;
|
pub mod backpack_ui;
|
||||||
mod ui_head_state;
|
pub mod ui_head_state;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cash::CashCollectEvent, global_observer, head_drop::HeadCollected, heads::HeadState,
|
cash::CashCollectEvent, global_observer, head_drop::HeadCollected, heads::HeadState,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
#[cfg(feature = "server")]
|
||||||
use crate::heads::HeadState;
|
use crate::heads::HeadState;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Default, Serialize, Deserialize)]
|
||||||
pub struct UiHeadState {
|
pub struct UiHeadState {
|
||||||
pub head: usize,
|
pub head: usize,
|
||||||
pub health: f32,
|
pub health: f32,
|
||||||
@@ -22,6 +24,7 @@ impl UiHeadState {
|
|||||||
self.reloading
|
self.reloading
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
pub(crate) fn new(value: HeadState, time: f32) -> Self {
|
pub(crate) fn new(value: HeadState, time: f32) -> Self {
|
||||||
let reloading = if value.has_ammo() {
|
let reloading = if value.has_ammo() {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use crate::{GameState, HEDZ_GREEN, global_observer, loading_assets::UIAssets, sounds::PlaySound};
|
use crate::{GameState, HEDZ_GREEN, loading_assets::UIAssets};
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use crate::{global_observer, sounds::PlaySound};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Component, Reflect, Default)]
|
#[derive(Component, Reflect, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
@@ -10,7 +13,7 @@ pub struct Cash;
|
|||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
struct CashText;
|
struct CashText;
|
||||||
|
|
||||||
#[derive(Resource, Reflect, Default)]
|
#[derive(Component, Reflect, Default, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct CashResource {
|
pub struct CashResource {
|
||||||
pub cash: i32,
|
pub cash: i32,
|
||||||
}
|
}
|
||||||
@@ -19,20 +22,21 @@ pub struct CashResource {
|
|||||||
pub struct CashCollectEvent;
|
pub struct CashCollectEvent;
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.init_resource::<CashResource>();
|
|
||||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(rotate, update_ui).run_if(in_state(GameState::Playing)),
|
(rotate, update_ui).run_if(in_state(GameState::Playing)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
global_observer!(app, on_cash_collect);
|
global_observer!(app, on_cash_collect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
fn on_cash_collect(
|
fn on_cash_collect(
|
||||||
_trigger: Trigger<CashCollectEvent>,
|
_trigger: Trigger<CashCollectEvent>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut cash: ResMut<CashResource>,
|
mut cash: Single<&mut CashResource>,
|
||||||
) {
|
) {
|
||||||
commands.trigger(PlaySound::CashCollect);
|
commands.trigger(PlaySound::CashCollect);
|
||||||
|
|
||||||
@@ -46,17 +50,15 @@ fn rotate(time: Res<Time>, mut query: Query<&mut Transform, With<Cash>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_ui(
|
fn update_ui(
|
||||||
cash: Res<CashResource>,
|
cash: Single<&CashResource, Changed<CashResource>>,
|
||||||
text: Query<Entity, With<CashText>>,
|
text: Query<Entity, With<CashText>>,
|
||||||
mut writer: TextUiWriter,
|
mut writer: TextUiWriter,
|
||||||
) {
|
) {
|
||||||
if cash.is_changed() {
|
let Some(text) = text.iter().next() else {
|
||||||
let Some(text) = text.iter().next() else {
|
return;
|
||||||
return;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
*writer.text(text, 0) = cash.cash.to_string();
|
*writer.text(text, 0) = cash.cash.to_string();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ struct HealAction {
|
|||||||
|
|
||||||
fn on_heal_trigger(
|
fn on_heal_trigger(
|
||||||
mut cmds: Commands,
|
mut cmds: Commands,
|
||||||
mut cash: ResMut<CashResource>,
|
mut cash: Single<&mut CashResource>,
|
||||||
mut query: Query<(&mut Hitpoints, &ActionState<ControlState>), With<Player>>,
|
mut query: Query<(&mut Hitpoints, &ActionState<ControlState>), With<Player>>,
|
||||||
) {
|
) {
|
||||||
for (mut hp, controls) in query.iter_mut() {
|
for (mut hp, controls) in query.iter_mut() {
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
use super::{ActiveHeads, HEAD_SLOTS, HeadsImages};
|
#[cfg(feature = "server")]
|
||||||
use crate::{GameState, backpack::UiHeadState, loading_assets::UIAssets, player::Player};
|
use super::ActiveHeads;
|
||||||
|
use super::HEAD_SLOTS;
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
use super::HeadsImages;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use crate::player::Player;
|
||||||
|
use crate::{GameState, backpack::UiHeadState, loading_assets::UIAssets};
|
||||||
use bevy::{ecs::spawn::SpawnIter, prelude::*};
|
use bevy::{ecs::spawn::SpawnIter, prelude::*};
|
||||||
use bevy_ui_gradients::{AngularColorStop, BackgroundGradient, ConicGradient, Gradient, Position};
|
#[cfg(feature = "client")]
|
||||||
|
use bevy_ui_gradients::Gradient;
|
||||||
|
use bevy_ui_gradients::{AngularColorStop, BackgroundGradient, ConicGradient, Position};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
#[derive(Component, Reflect, Default)]
|
#[derive(Component, Reflect, Default)]
|
||||||
@@ -16,9 +25,9 @@ struct HeadImage(pub usize);
|
|||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
struct HeadDamage(pub usize);
|
struct HeadDamage(pub usize);
|
||||||
|
|
||||||
#[derive(Resource, Default, Reflect)]
|
#[derive(Component, Default, Reflect, Serialize, Deserialize, PartialEq)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Component)]
|
||||||
struct UiActiveHeads {
|
pub struct UiActiveHeads {
|
||||||
heads: [Option<UiHeadState>; 5],
|
heads: [Option<UiHeadState>; 5],
|
||||||
selected_slot: usize,
|
selected_slot: usize,
|
||||||
}
|
}
|
||||||
@@ -28,9 +37,12 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.register_type::<UiActiveHeads>();
|
app.register_type::<UiActiveHeads>();
|
||||||
|
|
||||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
app.add_systems(FixedUpdate, sync.run_if(in_state(GameState::Playing)));
|
||||||
|
#[cfg(feature = "client")]
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
FixedUpdate,
|
||||||
(sync, update, update_ammo, update_health).run_if(in_state(GameState::Playing)),
|
(update, update_ammo, update_health).run_if(in_state(GameState::Playing)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +73,6 @@ fn setup(mut commands: Commands, assets: Res<UIAssets>) {
|
|||||||
}
|
}
|
||||||
}))),
|
}))),
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.init_resource::<UiActiveHeads>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_head_ui(
|
fn spawn_head_ui(
|
||||||
@@ -153,69 +163,69 @@ fn spawn_head_ui(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
fn update(
|
fn update(
|
||||||
res: Res<UiActiveHeads>,
|
res: Single<&UiActiveHeads, Changed<UiActiveHeads>>,
|
||||||
heads_images: Res<HeadsImages>,
|
heads_images: Res<HeadsImages>,
|
||||||
mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
|
mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
|
||||||
mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
|
mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
|
||||||
) {
|
) {
|
||||||
if res.is_changed() {
|
for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
|
||||||
for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
|
if let Some(head) = res.heads[*head] {
|
||||||
if let Some(head) = res.heads[*head] {
|
*vis = Visibility::Visible;
|
||||||
*vis = Visibility::Visible;
|
image.image = heads_images.heads[head.head].clone();
|
||||||
image.image = heads_images.heads[head.head].clone();
|
} else {
|
||||||
} else {
|
*vis = Visibility::Hidden;
|
||||||
*vis = Visibility::Hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (HeadSelector(head), mut vis) in head_selector.iter_mut() {
|
|
||||||
*vis = if *head == res.selected_slot {
|
|
||||||
Visibility::Visible
|
|
||||||
} else {
|
|
||||||
Visibility::Hidden
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (HeadSelector(head), mut vis) in head_selector.iter_mut() {
|
||||||
|
*vis = if *head == res.selected_slot {
|
||||||
|
Visibility::Visible
|
||||||
|
} else {
|
||||||
|
Visibility::Hidden
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
fn update_ammo(
|
fn update_ammo(
|
||||||
res: Res<UiActiveHeads>,
|
res: Single<&UiActiveHeads, Changed<UiActiveHeads>>,
|
||||||
mut gradients: Query<(&mut BackgroundGradient, &HeadImage)>,
|
mut gradients: Query<(&mut BackgroundGradient, &HeadImage)>,
|
||||||
) {
|
) {
|
||||||
if res.is_changed() {
|
for (mut gradient, HeadImage(head)) in gradients.iter_mut() {
|
||||||
for (mut gradient, HeadImage(head)) in gradients.iter_mut() {
|
if let Some(head) = res.heads[*head] {
|
||||||
if let Some(head) = res.heads[*head] {
|
let Gradient::Conic(gradient) = &mut gradient.0[0] else {
|
||||||
let Gradient::Conic(gradient) = &mut gradient.0[0] else {
|
continue;
|
||||||
continue;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let progress = if let Some(reloading) = head.reloading() {
|
let progress = if let Some(reloading) = head.reloading() {
|
||||||
1. - reloading
|
1. - reloading
|
||||||
} else {
|
} else {
|
||||||
head.ammo_used()
|
head.ammo_used()
|
||||||
};
|
};
|
||||||
|
|
||||||
let angle = progress * PI * 2.;
|
let angle = progress * PI * 2.;
|
||||||
|
|
||||||
gradient.stops[1].angle = Some(angle);
|
gradient.stops[1].angle = Some(angle);
|
||||||
gradient.stops[2].angle = Some(angle);
|
gradient.stops[2].angle = Some(angle);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_health(res: Res<UiActiveHeads>, mut query: Query<(&mut Node, &HeadDamage)>) {
|
#[cfg(feature = "client")]
|
||||||
if res.is_changed() {
|
fn update_health(
|
||||||
for (mut node, HeadDamage(head)) in query.iter_mut() {
|
res: Single<&UiActiveHeads, Changed<UiActiveHeads>>,
|
||||||
node.height =
|
mut query: Query<(&mut Node, &HeadDamage)>,
|
||||||
Val::Percent(res.heads[*head].map(|head| head.damage()).unwrap_or(0.) * 100.);
|
) {
|
||||||
}
|
for (mut node, HeadDamage(head)) in query.iter_mut() {
|
||||||
|
node.height = Val::Percent(res.heads[*head].map(|head| head.damage()).unwrap_or(0.) * 100.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
fn sync(
|
fn sync(
|
||||||
active_heads: Query<Ref<ActiveHeads>, With<Player>>,
|
active_heads: Query<Ref<ActiveHeads>, With<Player>>,
|
||||||
mut state: ResMut<UiActiveHeads>,
|
mut state: Single<&mut UiActiveHeads>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
) {
|
) {
|
||||||
let Ok(active_heads) = active_heads.single() else {
|
let Ok(active_heads) = active_heads.single() else {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
mod heads_ui;
|
pub mod heads_ui;
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use crate::animation::AnimationFlags;
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState,
|
||||||
animation::AnimationFlags,
|
|
||||||
backpack::{BackbackSwapEvent, Backpack},
|
backpack::{BackbackSwapEvent, Backpack},
|
||||||
control::ControlState,
|
control::ControlState,
|
||||||
global_observer,
|
global_observer,
|
||||||
@@ -179,9 +180,12 @@ pub struct HeadChanged(pub usize);
|
|||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.add_plugins(heads_ui::plugin);
|
app.add_plugins(heads_ui::plugin);
|
||||||
|
|
||||||
|
app.register_type::<ActiveHeads>();
|
||||||
|
|
||||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||||
|
#[cfg(feature = "server")]
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
FixedUpdate,
|
||||||
(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);
|
app.add_systems(FixedUpdate, on_select_active_head);
|
||||||
@@ -198,6 +202,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, heads: Res<Head
|
|||||||
commands.insert_resource(HeadsImages { heads });
|
commands.insert_resource(HeadsImages { heads });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
fn sync_hp(mut query: Query<(&mut ActiveHeads, &Hitpoints)>) {
|
fn sync_hp(mut query: Query<(&mut ActiveHeads, &Hitpoints)>) {
|
||||||
for (mut active_heads, hp) in query.iter_mut() {
|
for (mut active_heads, hp) in query.iter_mut() {
|
||||||
if active_heads.hp().get() != hp.get() {
|
if active_heads.hp().get() != hp.get() {
|
||||||
@@ -206,6 +211,7 @@ fn sync_hp(mut query: Query<(&mut ActiveHeads, &Hitpoints)>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
fn reload(
|
fn reload(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut active: Query<&mut ActiveHeads>,
|
mut active: Query<&mut ActiveHeads>,
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState,
|
||||||
backpack::Backpack,
|
|
||||||
camera::{CameraArmRotation, CameraTarget},
|
|
||||||
cash::{Cash, CashCollectEvent},
|
cash::{Cash, CashCollectEvent},
|
||||||
character::{AnimatedCharacter, Character},
|
character::{AnimatedCharacter, Character},
|
||||||
control::{ControlState, controller_common::PlayerCharacterController},
|
|
||||||
global_observer,
|
global_observer,
|
||||||
head::ActiveHead,
|
head::ActiveHead,
|
||||||
head_drop::HeadDrops,
|
heads::HeadChanged,
|
||||||
heads::{ActiveHeads, HeadChanged, HeadState},
|
|
||||||
heads_database::{HeadControls, HeadsDatabase},
|
heads_database::{HeadControls, HeadsDatabase},
|
||||||
hitpoints::{Hitpoints, Kill},
|
|
||||||
loading_assets::AudioAssets,
|
loading_assets::AudioAssets,
|
||||||
npc::SpawnCharacter,
|
|
||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
tb_entities::SpawnPoint,
|
|
||||||
};
|
};
|
||||||
use avian3d::prelude::*;
|
use avian3d::prelude::*;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
@@ -22,9 +15,6 @@ use bevy::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
window::{CursorGrabMode, PrimaryWindow},
|
window::{CursorGrabMode, PrimaryWindow},
|
||||||
};
|
};
|
||||||
use lightyear::prelude::input::native::ActionState;
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
use lightyear::prelude::{ControlledBy, Lifetime, NetworkTarget, PredictionTarget, Replicate};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||||
@@ -50,86 +40,6 @@ pub fn plugin(app: &mut App) {
|
|||||||
global_observer!(app, on_update_head_mesh);
|
global_observer!(app, on_update_head_mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(
|
|
||||||
mut commands: Commands,
|
|
||||||
#[cfg(feature = "server")] owner: Entity,
|
|
||||||
query: Query<&Transform, With<SpawnPoint>>,
|
|
||||||
asset_server: Res<AssetServer>,
|
|
||||||
heads_db: Res<HeadsDatabase>,
|
|
||||||
) {
|
|
||||||
let Some(spawn) = query.iter().next() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.));
|
|
||||||
|
|
||||||
let mut player = commands.spawn(player_bundle(transform, &heads_db));
|
|
||||||
player.observe(on_kill);
|
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
player.insert((
|
|
||||||
Replicate::to_clients(NetworkTarget::All),
|
|
||||||
PredictionTarget::to_clients(NetworkTarget::All),
|
|
||||||
ControlledBy {
|
|
||||||
owner,
|
|
||||||
lifetime: Lifetime::SessionBased,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
commands.spawn((
|
|
||||||
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
|
||||||
PlaybackSettings::DESPAWN,
|
|
||||||
));
|
|
||||||
|
|
||||||
commands.trigger(SpawnCharacter(transform.translation));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn player_bundle(transform: Transform, heads_db: &Res<HeadsDatabase>) -> impl Bundle {
|
|
||||||
(
|
|
||||||
Name::from("player"),
|
|
||||||
Player,
|
|
||||||
ActiveHead(0),
|
|
||||||
ActiveHeads::new([
|
|
||||||
Some(HeadState::new(0, heads_db.as_ref())),
|
|
||||||
Some(HeadState::new(3, heads_db.as_ref())),
|
|
||||||
Some(HeadState::new(6, heads_db.as_ref())),
|
|
||||||
Some(HeadState::new(10, heads_db.as_ref())),
|
|
||||||
Some(HeadState::new(9, heads_db.as_ref())),
|
|
||||||
]),
|
|
||||||
Hitpoints::new(100),
|
|
||||||
CameraTarget,
|
|
||||||
transform,
|
|
||||||
Visibility::default(),
|
|
||||||
PlayerCharacterController,
|
|
||||||
ActionState::<ControlState>::default(),
|
|
||||||
Backpack::default(),
|
|
||||||
children![(
|
|
||||||
Name::new("player-rig"),
|
|
||||||
PlayerBodyMesh,
|
|
||||||
CameraArmRotation,
|
|
||||||
children![AnimatedCharacter::new(0)],
|
|
||||||
)],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_kill(
|
|
||||||
trigger: Trigger<Kill>,
|
|
||||||
mut commands: Commands,
|
|
||||||
mut query: Query<(&Transform, &ActiveHead, &mut ActiveHeads, &mut Hitpoints)>,
|
|
||||||
) {
|
|
||||||
let Ok((transform, active, mut heads, mut hp)) = query.get_mut(trigger.target()) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
commands.trigger(HeadDrops::new(transform.translation, active.0));
|
|
||||||
|
|
||||||
if let Some(new_head) = heads.loose_current() {
|
|
||||||
hp.set_health(heads.current().unwrap().health);
|
|
||||||
|
|
||||||
commands.trigger(HeadChanged(new_head));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor_recenter(q_windows: Single<&mut Window, With<PrimaryWindow>>) {
|
fn cursor_recenter(q_windows: Single<&mut Window, With<PrimaryWindow>>) {
|
||||||
let mut primary_window = q_windows;
|
let mut primary_window = q_windows;
|
||||||
let center = Vec2::new(
|
let center = Vec2::new(
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use crate::{
|
|||||||
GameState,
|
GameState,
|
||||||
abilities::BuildExplosionSprite,
|
abilities::BuildExplosionSprite,
|
||||||
animation::AnimationFlags,
|
animation::AnimationFlags,
|
||||||
backpack::Backpack,
|
backpack::{Backpack, backpack_ui::BackpackUiState},
|
||||||
camera::{CameraArmRotation, CameraTarget},
|
camera::{CameraArmRotation, CameraTarget},
|
||||||
|
cash::CashResource,
|
||||||
character::{self, AnimatedCharacter},
|
character::{self, AnimatedCharacter},
|
||||||
control::{
|
control::{
|
||||||
ControlState,
|
ControlState,
|
||||||
@@ -13,7 +14,7 @@ use crate::{
|
|||||||
cutscene::StartCutscene,
|
cutscene::StartCutscene,
|
||||||
global_observer,
|
global_observer,
|
||||||
head::ActiveHead,
|
head::ActiveHead,
|
||||||
heads::ActiveHeads,
|
heads::{ActiveHeads, heads_ui::UiActiveHeads},
|
||||||
hitpoints::Hitpoints,
|
hitpoints::Hitpoints,
|
||||||
loading_assets::{GameAssets, HeadDropAssets},
|
loading_assets::{GameAssets, HeadDropAssets},
|
||||||
platforms::ActivePlatform,
|
platforms::ActivePlatform,
|
||||||
@@ -62,8 +63,10 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.register_component::<AnimatedCharacter>();
|
app.register_component::<AnimatedCharacter>();
|
||||||
app.register_component::<AnimationFlags>();
|
app.register_component::<AnimationFlags>();
|
||||||
app.register_component::<Backpack>();
|
app.register_component::<Backpack>();
|
||||||
|
app.register_component::<BackpackUiState>();
|
||||||
app.register_component::<CameraArmRotation>();
|
app.register_component::<CameraArmRotation>();
|
||||||
app.register_component::<CameraTarget>();
|
app.register_component::<CameraTarget>();
|
||||||
|
app.register_component::<CashResource>();
|
||||||
app.register_component::<happy_feet::prelude::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>();
|
||||||
@@ -90,6 +93,7 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.register_component::<Transform>()
|
app.register_component::<Transform>()
|
||||||
.add_prediction(PredictionMode::Full)
|
.add_prediction(PredictionMode::Full)
|
||||||
.add_should_rollback(transform_should_rollback);
|
.add_should_rollback(transform_should_rollback);
|
||||||
|
app.register_component::<UiActiveHeads>();
|
||||||
// `Visibility` isn't `(De)Serialize`, so we have to provide custom serde for it.
|
// `Visibility` isn't `(De)Serialize`, so we have to provide custom serde for it.
|
||||||
app.register_component_custom_serde::<Visibility>(SerializeFns {
|
app.register_component_custom_serde::<Visibility>(SerializeFns {
|
||||||
serialize: |comp, writer| writer.write_u8(*comp as u8).map_err(SerializationError::Io),
|
serialize: |comp, writer| writer.write_u8(*comp as u8).map_err(SerializationError::Io),
|
||||||
@@ -107,6 +111,8 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.replicate_trigger::<BuildExplosionSprite, ActionsChannel>();
|
app.replicate_trigger::<BuildExplosionSprite, ActionsChannel>();
|
||||||
app.replicate_trigger::<StartCutscene, ActionsChannel>();
|
app.replicate_trigger::<StartCutscene, ActionsChannel>();
|
||||||
|
|
||||||
|
app.replicate_trigger::<PlayBackpackSound, ActionsChannel>();
|
||||||
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
OnEnter(GameState::MapLoading),
|
OnEnter(GameState::MapLoading),
|
||||||
|mut counter: ResMut<TbMapIdCounter>| counter.reset(),
|
|mut counter: ResMut<TbMapIdCounter>| counter.reset(),
|
||||||
@@ -115,6 +121,10 @@ pub fn plugin(app: &mut App) {
|
|||||||
global_observer!(app, spawn_gltf_scene_roots);
|
global_observer!(app, spawn_gltf_scene_roots);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transform_should_rollback(this: &Transform, that: &Transform) -> bool {
|
||||||
|
this.translation.distance_squared(that.translation) >= 0.01f32.powf(2.)
|
||||||
|
}
|
||||||
|
|
||||||
/// Trenchbroom map entities (spawned during map loading) must be despawned manually if the server
|
/// Trenchbroom map entities (spawned during map loading) must be despawned manually if the server
|
||||||
/// has already despawned it but the client has just loaded the map and connected
|
/// has already despawned it but the client has just loaded the map and connected
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
@@ -167,8 +177,9 @@ impl TbMapIdCounter {
|
|||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
pub struct TbMapEntityMapping(pub HashMap<u64, Entity>);
|
pub struct TbMapEntityMapping(pub HashMap<u64, Entity>);
|
||||||
|
|
||||||
fn transform_should_rollback(this: &Transform, that: &Transform) -> bool {
|
#[derive(Clone, Event, Serialize, Deserialize)]
|
||||||
this.translation.distance_squared(that.translation) >= 0.01f32.powf(2.)
|
pub struct PlayBackpackSound {
|
||||||
|
pub open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Reflect, Serialize, Deserialize, PartialEq)]
|
#[derive(Component, Reflect, Serialize, Deserialize, PartialEq)]
|
||||||
|
|||||||
Reference in New Issue
Block a user