player can take damage
This commit is contained in:
@@ -4,7 +4,7 @@ mod thrown;
|
|||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState,
|
||||||
heads::ActiveHeads,
|
heads::ActiveHeads,
|
||||||
npc::Hit,
|
hitpoints::Hit,
|
||||||
player::{Player, PlayerRig},
|
player::{Player, PlayerRig},
|
||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
tb_entities::EnemySpawn,
|
tb_entities::EnemySpawn,
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::{
|
|||||||
GameState,
|
GameState,
|
||||||
aim::AimState,
|
aim::AimState,
|
||||||
billboards::Billboard,
|
billboards::Billboard,
|
||||||
|
hitpoints::Hit,
|
||||||
loading_assets::GameAssets,
|
loading_assets::GameAssets,
|
||||||
npc::Hit,
|
|
||||||
physics_layers::GameLayer,
|
physics_layers::GameLayer,
|
||||||
player::{Player, PlayerRig},
|
player::{Player, PlayerRig},
|
||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
use super::AimState;
|
use super::AimState;
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState, backpack::UiHeadState, heads::HeadsImages, hitpoints::Hitpoints,
|
||||||
backpack::UiHeadState,
|
loading_assets::UIAssets, npc::NpcHead,
|
||||||
heads::HeadsImages,
|
|
||||||
loading_assets::UIAssets,
|
|
||||||
npc::{Hitpoints, NpcHead},
|
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ use crate::{
|
|||||||
GameState,
|
GameState,
|
||||||
abilities::HeadAbility,
|
abilities::HeadAbility,
|
||||||
backpack::{BackbackSwapEvent, Backpack},
|
backpack::{BackbackSwapEvent, Backpack},
|
||||||
player::head_id_to_str,
|
hitpoints::Hitpoints,
|
||||||
|
player::{Player, head_id_to_str},
|
||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -92,6 +93,15 @@ impl ActiveHeads {
|
|||||||
|
|
||||||
false
|
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)]
|
#[derive(Event, Reflect)]
|
||||||
@@ -118,7 +128,10 @@ pub fn plugin(app: &mut App) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
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_select_active_head);
|
||||||
app.add_observer(on_swap_backpack);
|
app.add_observer(on_swap_backpack);
|
||||||
@@ -133,6 +146,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
commands.insert_resource(HeadsImages { heads });
|
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>) {
|
fn reload(mut commands: Commands, mut active: ResMut<ActiveHeads>, time: Res<Time>) {
|
||||||
if !active.reloading() {
|
if !active.reloading() {
|
||||||
return;
|
return;
|
||||||
@@ -154,6 +175,7 @@ fn on_select_active_head(
|
|||||||
trigger: Trigger<SelectActiveHead>,
|
trigger: Trigger<SelectActiveHead>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut res: ResMut<ActiveHeads>,
|
mut res: ResMut<ActiveHeads>,
|
||||||
|
mut query: Query<&mut Hitpoints, With<Player>>,
|
||||||
) {
|
) {
|
||||||
match trigger.event() {
|
match trigger.event() {
|
||||||
SelectActiveHead::Right => {
|
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(PlaySound::Selection);
|
||||||
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
|
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
|
||||||
}
|
}
|
||||||
@@ -173,6 +201,7 @@ fn on_swap_backpack(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut res: ResMut<ActiveHeads>,
|
mut res: ResMut<ActiveHeads>,
|
||||||
mut backpack: ResMut<Backpack>,
|
mut backpack: ResMut<Backpack>,
|
||||||
|
mut query: Query<&mut Hitpoints, With<Player>>,
|
||||||
) {
|
) {
|
||||||
let backpack_slot = trigger.event().0;
|
let backpack_slot = trigger.event().0;
|
||||||
|
|
||||||
@@ -189,5 +218,11 @@ fn on_swap_backpack(
|
|||||||
backpack.heads.remove(backpack_slot);
|
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));
|
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 cutscene;
|
||||||
mod gates;
|
mod gates;
|
||||||
mod heads;
|
mod heads;
|
||||||
|
mod hitpoints;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod loading_assets;
|
mod loading_assets;
|
||||||
mod loading_map;
|
mod loading_map;
|
||||||
@@ -116,6 +117,7 @@ fn main() {
|
|||||||
app.add_plugins(sprite_3d_animation::plugin);
|
app.add_plugins(sprite_3d_animation::plugin);
|
||||||
app.add_plugins(abilities::plugin);
|
app.add_plugins(abilities::plugin);
|
||||||
app.add_plugins(heads::plugin);
|
app.add_plugins(heads::plugin);
|
||||||
|
app.add_plugins(hitpoints::plugin);
|
||||||
|
|
||||||
app.init_state::<GameState>();
|
app.init_state::<GameState>();
|
||||||
|
|
||||||
|
|||||||
49
src/npc.rs
49
src/npc.rs
@@ -1,31 +1,14 @@
|
|||||||
use crate::{
|
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,
|
tb_entities::EnemySpawn,
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use std::collections::HashMap;
|
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)]
|
#[derive(Component)]
|
||||||
pub struct NpcHead(pub usize);
|
pub struct NpcHead(pub usize);
|
||||||
|
|
||||||
@@ -33,7 +16,7 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.add_systems(OnEnter(GameState::Playing), init);
|
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();
|
let mut names: HashMap<String, usize> = HashMap::default();
|
||||||
for i in 0..HEAD_COUNT {
|
for i in 0..HEAD_COUNT {
|
||||||
names.insert(head_id_to_str(i).to_string(), i);
|
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
|
commands
|
||||||
.entity(e)
|
.entity(e)
|
||||||
.insert((Hitpoints::new(100), NpcHead(id)))
|
.insert((Hitpoints::new(100), NpcHead(id)))
|
||||||
.observe(on_hit);
|
.observe(on_kill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_hit(
|
fn on_kill(
|
||||||
trigger: Trigger<Hit>,
|
trigger: Trigger<Kill>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<(&mut Hitpoints, &Transform, &EnemySpawn)>,
|
query: Query<(&Transform, &EnemySpawn)>,
|
||||||
) {
|
) {
|
||||||
let Hit { damage } = trigger.event();
|
let Ok((transform, enemy)) = query.get(trigger.entity()) else {
|
||||||
|
|
||||||
let Ok((mut hp, transform, enemy)) = query.get_mut(trigger.entity()) else {
|
|
||||||
return;
|
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();
|
commands.entity(trigger.entity()).despawn_recursive();
|
||||||
|
|
||||||
if !enemy.key.is_empty() {
|
if !enemy.key.is_empty() {
|
||||||
commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));
|
commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use crate::{
|
|||||||
controller::{CharacterControllerBundle, MovementBundle, PlayerMovement},
|
controller::{CharacterControllerBundle, MovementBundle, PlayerMovement},
|
||||||
},
|
},
|
||||||
heads::HeadChanged,
|
heads::HeadChanged,
|
||||||
|
hitpoints::Hitpoints,
|
||||||
loading_assets::GameAssets,
|
loading_assets::GameAssets,
|
||||||
physics_layers::GameLayer,
|
physics_layers::GameLayer,
|
||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
@@ -95,6 +96,7 @@ fn spawn(
|
|||||||
.spawn((
|
.spawn((
|
||||||
Name::from("player"),
|
Name::from("player"),
|
||||||
Player(0),
|
Player(0),
|
||||||
|
Hitpoints::new(100),
|
||||||
CameraTarget,
|
CameraTarget,
|
||||||
transform,
|
transform,
|
||||||
Visibility::default(),
|
Visibility::default(),
|
||||||
|
|||||||
Reference in New Issue
Block a user