make backwork in multiplayer
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
use super::UiHeadState;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub static BACKPACK_HEAD_SLOTS: usize = 5;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct BackpackMarker;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct BackpackCountText;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadSelector(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadImage(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadDamage(pub usize);
|
||||
|
||||
#[derive(Component, Default, Debug, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct BackpackUiState {
|
||||
pub heads: [Option<UiHeadState>; 5],
|
||||
pub scroll: usize,
|
||||
pub count: usize,
|
||||
pub current_slot: usize,
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl BackpackUiState {
|
||||
pub fn relative_current_slot(&self) -> usize {
|
||||
self.current_slot.saturating_sub(self.scroll)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<BackpackUiState>();
|
||||
}
|
||||
@@ -6,13 +6,7 @@ use crate::{
|
||||
heads_database::HeadsDatabase,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
#[cfg(feature = "client")]
|
||||
use bevy_replicon::prelude::ClientTriggerExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use ui_head_state::UiHeadState;
|
||||
|
||||
pub mod backpack_ui;
|
||||
pub mod ui_head_state;
|
||||
|
||||
#[derive(Component, Default, Reflect, Serialize, Deserialize, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
@@ -40,120 +34,15 @@ impl Backpack {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event, Serialize, Deserialize)]
|
||||
#[derive(Event, Debug, Serialize, Deserialize)]
|
||||
pub struct BackpackSwapEvent(pub usize);
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<Backpack>();
|
||||
|
||||
app.add_plugins(backpack_ui::plugin);
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
app.add_systems(FixedUpdate, (backpack_inputs, sync_on_change));
|
||||
|
||||
global_observer!(app, on_head_collect);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn backpack_inputs(
|
||||
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() {
|
||||
match input {
|
||||
BackpackButtonPress::Toggle => {
|
||||
if state.count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
state.open = !state.open;
|
||||
commands.trigger(PlaySound::Backpack { open: state.open });
|
||||
}
|
||||
BackpackButtonPress::Swap => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
commands.client_trigger(BackpackSwapEvent(state.current_slot));
|
||||
}
|
||||
BackpackButtonPress::Left => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.current_slot > 0 {
|
||||
state.current_slot -= 1;
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync_backpack_ui(backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
BackpackButtonPress::Right => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.current_slot < state.count.saturating_sub(1) {
|
||||
state.current_slot += 1;
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync_backpack_ui(backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn sync_on_change(
|
||||
backpack: Query<Ref<Backpack>>,
|
||||
mut state: Single<&mut backpack_ui::BackpackUiState>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for backpack in backpack.iter() {
|
||||
if backpack.is_changed() || backpack.reloading() {
|
||||
sync_backpack_ui(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
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();
|
||||
|
||||
state.scroll = state
|
||||
.scroll
|
||||
.min(state.count.saturating_sub(BACKPACK_HEAD_SLOTS));
|
||||
|
||||
if state.current_slot >= state.scroll + BACKPACK_HEAD_SLOTS {
|
||||
state.scroll = state.current_slot.saturating_sub(BACKPACK_HEAD_SLOTS - 1);
|
||||
}
|
||||
if state.current_slot < state.scroll {
|
||||
state.scroll = state.current_slot;
|
||||
}
|
||||
|
||||
for i in 0..BACKPACK_HEAD_SLOTS {
|
||||
if let Some(head) = backpack.heads.get(i + state.scroll) {
|
||||
use crate::backpack::ui_head_state::UiHeadState;
|
||||
|
||||
state.heads[i] = Some(UiHeadState::new(*head, time));
|
||||
} else {
|
||||
state.heads[i] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_head_collect(
|
||||
trigger: On<HeadCollected>,
|
||||
mut cmds: Commands,
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
use crate::heads::HeadState;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Default, Serialize, Deserialize)]
|
||||
pub struct UiHeadState {
|
||||
pub head: usize,
|
||||
pub health: f32,
|
||||
pub ammo: f32,
|
||||
pub reloading: Option<f32>,
|
||||
}
|
||||
|
||||
impl UiHeadState {
|
||||
pub fn damage(&self) -> f32 {
|
||||
1. - self.health
|
||||
}
|
||||
|
||||
pub fn ammo_used(&self) -> f32 {
|
||||
1. - self.ammo
|
||||
}
|
||||
|
||||
pub fn reloading(&self) -> Option<f32> {
|
||||
self.reloading
|
||||
}
|
||||
|
||||
pub fn new(value: HeadState, time: f32) -> Self {
|
||||
let reloading = if value.has_ammo() {
|
||||
None
|
||||
} else {
|
||||
Some((time - value.last_use) / value.reload_duration)
|
||||
};
|
||||
|
||||
Self {
|
||||
head: value.head,
|
||||
ammo: value.ammo as f32 / value.ammo_max as f32,
|
||||
health: value.health as f32 / value.health_max as f32,
|
||||
reloading,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
use crate::{
|
||||
GameState, aim::AimTarget, backpack::UiHeadState, client::ui::HeadsImages, heads::ActiveHeads,
|
||||
hitpoints::Hitpoints, loading_assets::UIAssets, npc::Npc, player::LocalPlayer,
|
||||
GameState,
|
||||
aim::AimTarget,
|
||||
client::ui::{HeadsImages, UiHeadState},
|
||||
heads::ActiveHeads,
|
||||
hitpoints::Hitpoints,
|
||||
loading_assets::UIAssets,
|
||||
npc::Npc,
|
||||
player::LocalPlayer,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
|
||||
98
crates/hedz_reloaded/src/client/backpack.rs
Normal file
98
crates/hedz_reloaded/src/client/backpack.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use crate::{
|
||||
backpack::{Backpack, BackpackSwapEvent},
|
||||
client::ui::{BACKPACK_HEAD_SLOTS, BackpackUiState, UiHeadState},
|
||||
control::BackpackButtonPress,
|
||||
player::LocalPlayer,
|
||||
protocol::PlaySound,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use bevy_replicon::prelude::ClientTriggerExt;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(FixedUpdate, (backpack_inputs, sync_on_change));
|
||||
}
|
||||
|
||||
fn backpack_inputs(
|
||||
backpack: Single<&Backpack, With<LocalPlayer>>,
|
||||
mut state: ResMut<BackpackUiState>,
|
||||
mut backpack_inputs: MessageReader<crate::control::BackpackButtonPress>,
|
||||
mut commands: Commands,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for input in backpack_inputs.read() {
|
||||
match input {
|
||||
BackpackButtonPress::Toggle => {
|
||||
if state.count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
state.open = !state.open;
|
||||
commands.trigger(PlaySound::Backpack { open: state.open });
|
||||
}
|
||||
BackpackButtonPress::Swap => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
commands.client_trigger(BackpackSwapEvent(state.current_slot));
|
||||
}
|
||||
BackpackButtonPress::Left => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.current_slot > 0 {
|
||||
state.current_slot -= 1;
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync_backpack_ui(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
BackpackButtonPress::Right => {
|
||||
if !state.open {
|
||||
return;
|
||||
}
|
||||
|
||||
if state.current_slot < state.count.saturating_sub(1) {
|
||||
state.current_slot += 1;
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
sync_backpack_ui(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_on_change(
|
||||
backpack: Single<Ref<Backpack>, With<LocalPlayer>>,
|
||||
mut state: ResMut<BackpackUiState>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if backpack.is_changed() || backpack.reloading() {
|
||||
sync_backpack_ui(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_backpack_ui(backpack: &Backpack, state: &mut BackpackUiState, time: f32) {
|
||||
state.count = backpack.heads.len();
|
||||
|
||||
state.scroll = state
|
||||
.scroll
|
||||
.min(state.count.saturating_sub(BACKPACK_HEAD_SLOTS));
|
||||
|
||||
if state.current_slot >= state.scroll + BACKPACK_HEAD_SLOTS {
|
||||
state.scroll = state.current_slot.saturating_sub(BACKPACK_HEAD_SLOTS - 1);
|
||||
}
|
||||
if state.current_slot < state.scroll {
|
||||
state.scroll = state.current_slot;
|
||||
}
|
||||
|
||||
for i in 0..BACKPACK_HEAD_SLOTS {
|
||||
if let Some(head) = backpack.heads.get(i + state.scroll) {
|
||||
state.heads[i] = Some(UiHeadState::new(*head, time));
|
||||
} else {
|
||||
state.heads[i] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ use bevy_trenchbroom::geometry::Brushes;
|
||||
|
||||
pub mod aim;
|
||||
pub mod audio;
|
||||
mod backpack;
|
||||
pub mod control;
|
||||
pub mod debug;
|
||||
pub mod enemy;
|
||||
@@ -47,6 +48,7 @@ pub fn plugin(app: &mut App) {
|
||||
steam::plugin,
|
||||
ui::plugin,
|
||||
settings::plugin,
|
||||
backpack::plugin,
|
||||
));
|
||||
|
||||
app.add_systems(
|
||||
|
||||
@@ -1,15 +1,47 @@
|
||||
use crate::{
|
||||
GameState, HEDZ_GREEN,
|
||||
backpack::backpack_ui::{
|
||||
BACKPACK_HEAD_SLOTS, BackpackCountText, BackpackMarker, BackpackUiState, HeadDamage,
|
||||
HeadImage, HeadSelector,
|
||||
},
|
||||
client::ui::heads_ui::HeadsImages,
|
||||
client::ui::heads_ui::{HeadsImages, UiHeadState},
|
||||
loading_assets::UIAssets,
|
||||
};
|
||||
use bevy::{ecs::spawn::SpawnIter, prelude::*};
|
||||
|
||||
pub static BACKPACK_HEAD_SLOTS: usize = 5;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct BackpackMarker;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct BackpackCountText;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadSelector(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadImage(pub usize);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct HeadDamage(pub usize);
|
||||
|
||||
#[derive(Resource, Default, Debug, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct BackpackUiState {
|
||||
pub heads: [Option<UiHeadState>; 5],
|
||||
pub scroll: usize,
|
||||
pub count: usize,
|
||||
pub current_slot: usize,
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl BackpackUiState {
|
||||
pub fn relative_current_slot(&self) -> usize {
|
||||
self.current_slot.saturating_sub(self.scroll)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<BackpackUiState>();
|
||||
app.init_resource::<BackpackUiState>();
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
@@ -152,10 +184,14 @@ fn spawn_head_ui(
|
||||
}
|
||||
|
||||
fn update_visibility(
|
||||
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||
state: Res<BackpackUiState>,
|
||||
mut backpack: Single<&mut Visibility, (With<BackpackMarker>, Without<BackpackCountText>)>,
|
||||
mut count: Single<&mut Visibility, (Without<BackpackMarker>, With<BackpackCountText>)>,
|
||||
) {
|
||||
if !state.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
**backpack = if state.open {
|
||||
Visibility::Visible
|
||||
} else {
|
||||
@@ -170,10 +206,14 @@ fn update_visibility(
|
||||
}
|
||||
|
||||
fn update_count(
|
||||
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||
state: Res<BackpackUiState>,
|
||||
text: Option<Single<Entity, With<BackpackCountText>>>,
|
||||
mut writer: TextUiWriter,
|
||||
) {
|
||||
if !state.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(text) = text else {
|
||||
return;
|
||||
};
|
||||
@@ -182,12 +222,16 @@ fn update_count(
|
||||
}
|
||||
|
||||
fn update(
|
||||
state: Single<&BackpackUiState, Changed<BackpackUiState>>,
|
||||
state: Res<BackpackUiState>,
|
||||
heads_images: Res<HeadsImages>,
|
||||
mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
|
||||
mut head_damage: Query<(&HeadDamage, &mut Node), Without<HeadSelector>>,
|
||||
mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
|
||||
) {
|
||||
if !state.is_changed() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
|
||||
if let Some(head) = &state.heads[*head] {
|
||||
*vis = Visibility::Inherited;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
backpack::UiHeadState,
|
||||
heads::{ActiveHeads, HEAD_COUNT, HEAD_SLOTS},
|
||||
heads::{ActiveHeads, HEAD_COUNT, HEAD_SLOTS, HeadState},
|
||||
heads_database::HeadsDatabase,
|
||||
loading_assets::UIAssets,
|
||||
player::LocalPlayer,
|
||||
@@ -34,6 +33,43 @@ struct UiActiveHeads {
|
||||
selected_slot: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect, Default, Serialize, Deserialize)]
|
||||
pub struct UiHeadState {
|
||||
pub head: usize,
|
||||
pub health: f32,
|
||||
pub ammo: f32,
|
||||
pub reloading: Option<f32>,
|
||||
}
|
||||
|
||||
impl UiHeadState {
|
||||
pub fn damage(&self) -> f32 {
|
||||
1. - self.health
|
||||
}
|
||||
|
||||
pub fn ammo_used(&self) -> f32 {
|
||||
1. - self.ammo
|
||||
}
|
||||
|
||||
pub fn reloading(&self) -> Option<f32> {
|
||||
self.reloading
|
||||
}
|
||||
|
||||
pub fn new(value: HeadState, time: f32) -> Self {
|
||||
let reloading = if value.has_ammo() {
|
||||
None
|
||||
} else {
|
||||
Some((time - value.last_use) / value.reload_duration)
|
||||
};
|
||||
|
||||
Self {
|
||||
head: value.head,
|
||||
ammo: value.ammo as f32 / value.ammo_max as f32,
|
||||
health: value.health as f32 / value.health_max as f32,
|
||||
reloading,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<HeadDamage>();
|
||||
app.register_type::<UiActiveHeads>();
|
||||
|
||||
@@ -2,7 +2,8 @@ mod backpack_ui;
|
||||
mod heads_ui;
|
||||
mod pause;
|
||||
|
||||
pub use heads_ui::HeadsImages;
|
||||
pub use backpack_ui::{BACKPACK_HEAD_SLOTS, BackpackUiState};
|
||||
pub use heads_ui::{HeadsImages, UiHeadState};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
|
||||
@@ -292,12 +292,15 @@ fn on_select_active_head(
|
||||
|
||||
fn on_swap_backpack(
|
||||
trigger: On<FromClient<BackpackSwapEvent>>,
|
||||
clients: ClientToController,
|
||||
mut commands: Commands,
|
||||
mut query: Query<(Entity, &mut ActiveHeads, &mut Hitpoints, &mut Backpack), With<Player>>,
|
||||
) {
|
||||
let player = clients.get_controller(trigger.client_id);
|
||||
|
||||
let backpack_slot = trigger.event().0;
|
||||
|
||||
let Ok((player, mut active_heads, mut hp, mut backpack)) = query.single_mut() else {
|
||||
let Ok((player, mut active_heads, mut hp, mut backpack)) = query.get_mut(player) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::PlayerTriggerState,
|
||||
backpack::{Backpack, backpack_ui::BackpackUiState},
|
||||
backpack::Backpack,
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
cash::{Cash, CashCollectEvent, CashInventory},
|
||||
character::{AnimatedCharacter, HedzCharacter},
|
||||
@@ -32,7 +32,7 @@ pub struct Player;
|
||||
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(LocalInputs, BackpackUiState)]
|
||||
#[require(LocalInputs)]
|
||||
pub struct LocalPlayer;
|
||||
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
@@ -95,7 +95,6 @@ pub fn spawn(
|
||||
id,
|
||||
),
|
||||
Backpack::default(),
|
||||
BackpackUiState::default(),
|
||||
Inputs::default(),
|
||||
Replicated,
|
||||
))
|
||||
@@ -165,7 +164,7 @@ fn on_update_head_mesh(
|
||||
animated_characters: Query<&AnimatedCharacter>,
|
||||
mut active_head: Query<&mut ActiveHead>,
|
||||
) -> Result {
|
||||
let player_id = player_id.get(trigger.entity)?.clone();
|
||||
let player_id = *(player_id.get(trigger.entity)?);
|
||||
|
||||
let player_body_mesh = children
|
||||
.get(trigger.entity)?
|
||||
|
||||
Reference in New Issue
Block a user