player can take damage
This commit is contained in:
@@ -4,7 +4,7 @@ mod thrown;
|
||||
use crate::{
|
||||
GameState,
|
||||
heads::ActiveHeads,
|
||||
npc::Hit,
|
||||
hitpoints::Hit,
|
||||
player::{Player, PlayerRig},
|
||||
sounds::PlaySound,
|
||||
tb_entities::EnemySpawn,
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::{
|
||||
GameState,
|
||||
aim::AimState,
|
||||
billboards::Billboard,
|
||||
hitpoints::Hit,
|
||||
loading_assets::GameAssets,
|
||||
npc::Hit,
|
||||
physics_layers::GameLayer,
|
||||
player::{Player, PlayerRig},
|
||||
sounds::PlaySound,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use super::AimState;
|
||||
use crate::{
|
||||
GameState,
|
||||
backpack::UiHeadState,
|
||||
heads::HeadsImages,
|
||||
loading_assets::UIAssets,
|
||||
npc::{Hitpoints, NpcHead},
|
||||
GameState, backpack::UiHeadState, heads::HeadsImages, hitpoints::Hitpoints,
|
||||
loading_assets::UIAssets, npc::NpcHead,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ use crate::{
|
||||
GameState,
|
||||
abilities::HeadAbility,
|
||||
backpack::{BackbackSwapEvent, Backpack},
|
||||
player::head_id_to_str,
|
||||
hitpoints::Hitpoints,
|
||||
player::{Player, head_id_to_str},
|
||||
sounds::PlaySound,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
@@ -92,6 +93,15 @@ impl ActiveHeads {
|
||||
|
||||
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)]
|
||||
@@ -118,7 +128,10 @@ pub fn plugin(app: &mut App) {
|
||||
});
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
app.add_systems(Update, reload.run_if(in_state(GameState::Playing)));
|
||||
app.add_systems(
|
||||
Update,
|
||||
(reload, sync_hp).run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
app.add_observer(on_select_active_head);
|
||||
app.add_observer(on_swap_backpack);
|
||||
@@ -133,6 +146,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
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;
|
||||
@@ -154,6 +175,7 @@ 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 => {
|
||||
@@ -164,6 +186,12 @@ fn on_select_active_head(
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
@@ -173,6 +201,7 @@ fn on_swap_backpack(
|
||||
mut commands: Commands,
|
||||
mut res: ResMut<ActiveHeads>,
|
||||
mut backpack: ResMut<Backpack>,
|
||||
mut query: Query<&mut Hitpoints, With<Player>>,
|
||||
) {
|
||||
let backpack_slot = trigger.event().0;
|
||||
|
||||
@@ -189,5 +218,11 @@ fn on_swap_backpack(
|
||||
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));
|
||||
}
|
||||
|
||||
61
src/hitpoints.rs
Normal file
61
src/hitpoints.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::sounds::PlaySound;
|
||||
|
||||
#[derive(Event, Reflect)]
|
||||
pub struct Kill;
|
||||
|
||||
#[derive(Event, Reflect)]
|
||||
pub struct Hit {
|
||||
pub damage: u32,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
pub struct Hitpoints {
|
||||
max: u32,
|
||||
current: u32,
|
||||
}
|
||||
|
||||
impl Hitpoints {
|
||||
pub fn new(v: u32) -> Self {
|
||||
Self { max: v, current: v }
|
||||
}
|
||||
|
||||
pub fn health(&self) -> f32 {
|
||||
self.current as f32 / self.max as f32
|
||||
}
|
||||
|
||||
pub fn set_health(&mut self, v: u32) {
|
||||
self.current = v;
|
||||
}
|
||||
|
||||
pub fn get(&self) -> (u32, u32) {
|
||||
(self.current, self.max)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(Update, on_hp_added);
|
||||
}
|
||||
|
||||
fn on_hp_added(mut commands: Commands, query: Query<Entity, Added<Hitpoints>>) {
|
||||
for e in query.iter() {
|
||||
commands.entity(e).observe(on_hit);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_hit(trigger: Trigger<Hit>, mut commands: Commands, mut query: Query<&mut Hitpoints>) {
|
||||
let Hit { damage } = trigger.event();
|
||||
|
||||
let Ok(mut hp) = query.get_mut(trigger.entity()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
commands.trigger(PlaySound::Hit);
|
||||
|
||||
hp.current = hp.current.saturating_sub(*damage);
|
||||
|
||||
if hp.current == 0 {
|
||||
commands.trigger_targets(Kill, trigger.entity());
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ mod control;
|
||||
mod cutscene;
|
||||
mod gates;
|
||||
mod heads;
|
||||
mod hitpoints;
|
||||
mod keys;
|
||||
mod loading_assets;
|
||||
mod loading_map;
|
||||
@@ -116,6 +117,7 @@ fn main() {
|
||||
app.add_plugins(sprite_3d_animation::plugin);
|
||||
app.add_plugins(abilities::plugin);
|
||||
app.add_plugins(heads::plugin);
|
||||
app.add_plugins(hitpoints::plugin);
|
||||
|
||||
app.init_state::<GameState>();
|
||||
|
||||
|
||||
49
src/npc.rs
49
src/npc.rs
@@ -1,31 +1,14 @@
|
||||
use crate::{
|
||||
GameState, heads::HEAD_COUNT, keys::KeySpawn, player::head_id_to_str, sounds::PlaySound,
|
||||
GameState,
|
||||
heads::HEAD_COUNT,
|
||||
hitpoints::{Hitpoints, Kill},
|
||||
keys::KeySpawn,
|
||||
player::head_id_to_str,
|
||||
tb_entities::EnemySpawn,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Event, Reflect)]
|
||||
pub struct Hit {
|
||||
pub damage: u32,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
pub struct Hitpoints {
|
||||
max: i32,
|
||||
current: i32,
|
||||
}
|
||||
|
||||
impl Hitpoints {
|
||||
fn new(v: i32) -> Self {
|
||||
Self { max: v, current: v }
|
||||
}
|
||||
|
||||
pub fn health(&self) -> f32 {
|
||||
self.current as f32 / self.max as f32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct NpcHead(pub usize);
|
||||
|
||||
@@ -33,7 +16,7 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Playing), init);
|
||||
}
|
||||
|
||||
fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn), Without<Hitpoints>>) {
|
||||
fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>) {
|
||||
let mut names: HashMap<String, usize> = HashMap::default();
|
||||
for i in 0..HEAD_COUNT {
|
||||
names.insert(head_id_to_str(i).to_string(), i);
|
||||
@@ -43,32 +26,22 @@ fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn), Without<Hitp
|
||||
commands
|
||||
.entity(e)
|
||||
.insert((Hitpoints::new(100), NpcHead(id)))
|
||||
.observe(on_hit);
|
||||
.observe(on_kill);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_hit(
|
||||
trigger: Trigger<Hit>,
|
||||
fn on_kill(
|
||||
trigger: Trigger<Kill>,
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&mut Hitpoints, &Transform, &EnemySpawn)>,
|
||||
query: Query<(&Transform, &EnemySpawn)>,
|
||||
) {
|
||||
let Hit { damage } = trigger.event();
|
||||
|
||||
let Ok((mut hp, transform, enemy)) = query.get_mut(trigger.entity()) else {
|
||||
let Ok((transform, enemy)) = query.get(trigger.entity()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
commands.trigger(PlaySound::Hit);
|
||||
|
||||
hp.current = hp.current.saturating_sub(*damage as i32);
|
||||
|
||||
info!("npc hp changed: {} [{}]", hp.current, trigger.entity());
|
||||
|
||||
if hp.current <= 0 {
|
||||
commands.entity(trigger.entity()).despawn_recursive();
|
||||
|
||||
if !enemy.key.is_empty() {
|
||||
commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
controller::{CharacterControllerBundle, MovementBundle, PlayerMovement},
|
||||
},
|
||||
heads::HeadChanged,
|
||||
hitpoints::Hitpoints,
|
||||
loading_assets::GameAssets,
|
||||
physics_layers::GameLayer,
|
||||
sounds::PlaySound,
|
||||
@@ -95,6 +96,7 @@ fn spawn(
|
||||
.spawn((
|
||||
Name::from("player"),
|
||||
Player(0),
|
||||
Hitpoints::new(100),
|
||||
CameraTarget,
|
||||
transform,
|
||||
Visibility::default(),
|
||||
|
||||
Reference in New Issue
Block a user