Files
HEDZReloaded/crates/shared/src/backpack/mod.rs
2025-12-10 18:31:42 -05:00

173 lines
4.7 KiB
Rust

#[cfg(feature = "client")]
use crate::{
backpack::backpack_ui::BackpackUiState, control::BackpackButtonPress, player::LocalPlayer,
protocol::PlaySound,
};
use crate::{
cash::CashCollectEvent, global_observer, head_drop::HeadCollected, heads::HeadState,
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)]
pub struct Backpack {
pub heads: Vec<HeadState>,
}
impl Backpack {
pub fn reloading(&self) -> bool {
for head in &self.heads {
if !head.has_ammo() {
return true;
}
}
false
}
pub fn contains(&self, head_id: usize) -> bool {
self.heads.iter().any(|head| head.head == head_id)
}
pub fn insert(&mut self, head_id: usize, heads_db: &HeadsDatabase) {
self.heads.push(HeadState::new(head_id, heads_db));
}
}
#[derive(Event, 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")]
#[allow(clippy::too_many_arguments)]
fn backpack_inputs(
backpacks: Single<(&Backpack, &mut BackpackUiState), With<LocalPlayer>>,
mut backpack_inputs: MessageReader<BackpackButtonPress>,
mut commands: Commands,
time: Res<Time>,
) {
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 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 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,
mut backpack: Query<&mut Backpack>,
heads_db: Res<HeadsDatabase>,
) -> Result {
let HeadCollected { head, entity } = *trigger.event();
let mut backpack = backpack.get_mut(entity)?;
if backpack.contains(head) {
cmds.trigger(CashCollectEvent);
} else {
backpack.insert(head, heads_db.as_ref());
}
Ok(())
}