heads module

This commit is contained in:
2025-04-02 01:43:56 +08:00
parent 01e6e944d3
commit 9aaa015bd6
11 changed files with 36 additions and 33 deletions

160
src/heads/mod.rs Normal file
View File

@@ -0,0 +1,160 @@
mod heads_ui;
use crate::{
GameState,
abilities::HeadAbility,
backpack::{BackbackSwapEvent, Backpack},
player::head_id_to_str,
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 ability: HeadAbility,
pub health: u32,
pub health_max: u32,
pub ammo: u32,
pub ammo_max: u32,
}
impl HeadState {
pub fn new(head: usize, ammo: u32) -> Self {
Self {
head,
health: 100,
health_max: 100,
ammo,
ammo_max: ammo,
ability: HeadAbility::None,
}
}
fn with_ability(mut self, ability: HeadAbility) -> Self {
self.ability = ability;
self
}
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) {
let Some(head) = &mut self.heads[self.current_slot] else {
error!("cannot use ammo of empty head");
return;
};
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]
}
}
#[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).with_ability(HeadAbility::Thrown)),
Some(HeadState::new(3, 10).with_ability(HeadAbility::Gun)),
Some(HeadState::new(6, 10).with_ability(HeadAbility::Arrow)),
Some(HeadState::new(8, 10).with_ability(HeadAbility::Thrown)),
Some(HeadState::new(9, 10).with_ability(HeadAbility::Gun)),
],
current_slot: 0,
});
app.add_systems(OnEnter(GameState::Playing), setup);
app.add_observer(on_select_active_head);
app.add_observer(on_swap_backpack);
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// TODO: load via asset loader
let heads = (0usize..HEAD_COUNT)
.map(|i| asset_server.load(format!("ui/heads/{}.png", head_id_to_str(i))))
.collect();
commands.insert_resource(HeadsImages { heads });
}
fn on_select_active_head(
trigger: Trigger<SelectActiveHead>,
mut commands: Commands,
mut res: ResMut<ActiveHeads>,
) {
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;
}
}
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>,
) {
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);
}
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
}