Files
HEDZReloaded/src/heads/mod.rs

223 lines
5.4 KiB
Rust

mod heads_ui;
use crate::{
GameState,
backpack::{BackbackSwapEvent, Backpack},
global_observer,
head_asset::HeadsDatabase,
hitpoints::Hitpoints,
player::Player,
sounds::PlaySound,
};
use bevy::prelude::*;
pub static HEAD_COUNT: usize = 18;
pub static HEAD_SLOTS: usize = 5;
#[derive(Resource, Default)]
pub struct HeadsImages {
pub heads: Vec<Handle<Image>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
pub struct HeadState {
pub head: usize,
pub health: u32,
pub health_max: u32,
pub ammo: u32,
pub ammo_max: u32,
pub reload_duration: f32,
pub last_use: f32,
}
impl HeadState {
pub fn new(head: usize, ammo: u32) -> Self {
Self {
head,
health: 100,
health_max: 100,
ammo,
ammo_max: ammo,
reload_duration: 5.,
last_use: 0.,
}
}
pub fn has_ammo(&self) -> bool {
self.ammo > 0
}
}
#[derive(Resource, Default, Reflect)]
#[reflect(Resource)]
pub struct ActiveHeads {
heads: [Option<HeadState>; 5],
current_slot: usize,
}
impl ActiveHeads {
pub fn current(&self) -> Option<HeadState> {
self.heads[self.current_slot]
}
pub fn use_ammo(&mut self, time: f32) {
let Some(head) = &mut self.heads[self.current_slot] else {
error!("cannot use ammo of empty head");
return;
};
head.last_use = time;
head.ammo = head.ammo.saturating_sub(1);
}
pub fn slot(&self) -> usize {
self.current_slot
}
pub fn head(&self, slot: usize) -> Option<HeadState> {
self.heads[slot]
}
pub fn reloading(&self) -> bool {
for head in self.heads {
if head.map(|head| head.ammo == 0).unwrap_or(false) {
return true;
}
}
false
}
pub fn set_hitpoint(&mut self, hp: &Hitpoints) {
let Some(head) = &mut self.heads[self.current_slot] else {
error!("cannot use ammo of empty head");
return;
};
(head.health, head.health_max) = hp.get()
}
}
#[derive(Event, Reflect)]
pub enum SelectActiveHead {
Left,
Right,
}
#[derive(Event)]
pub struct HeadChanged(pub usize);
pub fn plugin(app: &mut App) {
app.add_plugins(heads_ui::plugin);
app.insert_resource(ActiveHeads {
heads: [
Some(HeadState::new(0, 10)),
Some(HeadState::new(3, 10)),
Some(HeadState::new(6, 10)),
Some(HeadState::new(8, 10)),
Some(HeadState::new(9, 10)),
],
current_slot: 0,
});
app.add_systems(OnEnter(GameState::Playing), setup);
app.add_systems(
Update,
(reload, sync_hp).run_if(in_state(GameState::Playing)),
);
global_observer!(app, on_select_active_head);
global_observer!(app, on_swap_backpack);
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, heads: Res<HeadsDatabase>) {
// TODO: load via asset loader
let heads = (0usize..HEAD_COUNT)
.map(|i| asset_server.load(format!("ui/heads/{}.png", heads.head_key(i))))
.collect();
commands.insert_resource(HeadsImages { heads });
}
fn sync_hp(mut active: ResMut<ActiveHeads>, query: Query<&Hitpoints, With<Player>>) {
let Ok(hp) = query.get_single() else {
return;
};
active.set_hitpoint(hp);
}
fn reload(mut commands: Commands, mut active: ResMut<ActiveHeads>, time: Res<Time>) {
if !active.reloading() {
return;
}
for head in active.heads.iter_mut() {
let Some(head) = head else {
continue;
};
if !head.has_ammo() && (head.last_use + head.reload_duration <= time.elapsed_secs()) {
commands.trigger(PlaySound::Reloaded);
head.ammo = head.ammo_max;
}
}
}
fn on_select_active_head(
trigger: Trigger<SelectActiveHead>,
mut commands: Commands,
mut res: ResMut<ActiveHeads>,
mut query: Query<&mut Hitpoints, With<Player>>,
) {
match trigger.event() {
SelectActiveHead::Right => {
res.current_slot = (res.current_slot + 1) % HEAD_SLOTS;
}
SelectActiveHead::Left => {
res.current_slot = (res.current_slot + (HEAD_SLOTS - 1)) % HEAD_SLOTS;
}
}
// sync health back into Hitpoints
let Ok(mut hp) = query.get_single_mut() else {
return;
};
hp.set_health(res.current().unwrap().health);
commands.trigger(PlaySound::Selection);
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
}
fn on_swap_backpack(
trigger: Trigger<BackbackSwapEvent>,
mut commands: Commands,
mut res: ResMut<ActiveHeads>,
mut backpack: ResMut<Backpack>,
mut query: Query<&mut Hitpoints, With<Player>>,
) {
let backpack_slot = trigger.event().0;
let head = backpack.heads.get(backpack_slot).unwrap();
let current_active_slot = res.current_slot;
let current_active_head = res.heads[current_active_slot];
res.heads[current_active_slot] = Some(*head);
if let Some(old_active) = current_active_head {
backpack.heads[backpack_slot] = old_active;
} else {
backpack.heads.remove(backpack_slot);
}
// sync health back into Hitpoints
let Ok(mut hp) = query.get_single_mut() else {
return;
};
hp.set_health(res.current().unwrap().health);
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
}