npc reloading (#28)
This commit is contained in:
@@ -114,11 +114,15 @@ fn on_trigger_state(
|
||||
mut commands: Commands,
|
||||
player_rot: Query<&Transform, With<PlayerBodyMesh>>,
|
||||
player_query: Query<(&Transform, &AimTarget), With<Player>>,
|
||||
mut active_heads: ResMut<ActiveHeads>,
|
||||
mut active_heads: Query<&mut ActiveHeads, With<Player>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if matches!(trigger.event(), TriggerState::Active) {
|
||||
let Ok(mut active_heads) = active_heads.get_single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(state) = active_heads.current() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -4,8 +4,8 @@ use crate::{
|
||||
GameState,
|
||||
abilities::{HeadAbility, TriggerData, TriggerThrow},
|
||||
aim::AimTarget,
|
||||
heads::ActiveHeads,
|
||||
heads_database::HeadsDatabase,
|
||||
npc::Npc,
|
||||
};
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
@@ -18,7 +18,7 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
fn update(
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&mut Npc, &AimTarget, &Transform), With<Ai>>,
|
||||
mut query: Query<(&mut ActiveHeads, &AimTarget, &Transform), With<Ai>>,
|
||||
time: Res<Time>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
@@ -27,18 +27,21 @@ fn update(
|
||||
continue;
|
||||
}
|
||||
|
||||
let ability = heads_db.head_stats(npc.head).ability;
|
||||
let Some(npc_head) = npc.current() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let ability = heads_db.head_stats(npc_head.head).ability;
|
||||
|
||||
//TODO: support other abilities
|
||||
if ability != HeadAbility::Thrown {
|
||||
continue;
|
||||
}
|
||||
|
||||
let can_shoot_again = npc.last_use + 1. < time.elapsed_secs();
|
||||
let can_shoot_again = npc_head.last_use + 1. < time.elapsed_secs();
|
||||
|
||||
if can_shoot_again && npc.has_ammo() {
|
||||
npc.last_use = time.elapsed_secs();
|
||||
npc.ammo -= 1;
|
||||
if can_shoot_again && npc_head.has_ammo() {
|
||||
npc.use_ammo(time.elapsed_secs());
|
||||
|
||||
let dir = t.forward();
|
||||
|
||||
@@ -48,7 +51,7 @@ fn update(
|
||||
t.rotation,
|
||||
t.translation,
|
||||
crate::physics_layers::GameLayer::Player,
|
||||
npc.head,
|
||||
npc_head.head,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
use super::AimTarget;
|
||||
use crate::{
|
||||
GameState, backpack::UiHeadState, heads::HeadsImages, hitpoints::Hitpoints,
|
||||
loading_assets::UIAssets, npc::Npc, player::Player,
|
||||
GameState,
|
||||
backpack::UiHeadState,
|
||||
heads::{ActiveHeads, HeadsImages},
|
||||
hitpoints::Hitpoints,
|
||||
loading_assets::UIAssets,
|
||||
npc::Npc,
|
||||
player::Player,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -142,11 +147,12 @@ fn update(
|
||||
fn sync(
|
||||
mut target: ResMut<TargetUi>,
|
||||
player_target: Query<&AimTarget, With<Player>>,
|
||||
target_data: Query<(&Hitpoints, &Npc)>,
|
||||
target_data: Query<(&Hitpoints, &ActiveHeads), With<Npc>>,
|
||||
) {
|
||||
let mut new_state = None;
|
||||
if let Some(e) = player_target.iter().next().and_then(|target| target.0) {
|
||||
if let Ok((hp, head)) = target_data.get(e) {
|
||||
if let Ok((hp, heads)) = target_data.get(e) {
|
||||
let head = heads.current().expect("target must have a head on");
|
||||
new_state = Some(UiHeadState {
|
||||
head: head.head,
|
||||
health: hp.health(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{GameState, backpack::UiHeadState, loading_assets::UIAssets};
|
||||
use crate::{GameState, backpack::UiHeadState, loading_assets::UIAssets, player::Player};
|
||||
use bevy::prelude::*;
|
||||
use bevy_ui_gradients::{AngularColorStop, BackgroundGradient, ConicGradient, Gradient, Position};
|
||||
use std::f32::consts::PI;
|
||||
@@ -213,11 +213,19 @@ fn update_health(res: Res<UiActiveHeads>, mut query: Query<(&mut Node, &HeadDama
|
||||
}
|
||||
}
|
||||
|
||||
fn sync(active: Res<ActiveHeads>, mut state: ResMut<UiActiveHeads>, time: Res<Time>) {
|
||||
if active.is_changed() || active.reloading() {
|
||||
state.current_slot = active.slot();
|
||||
fn sync(
|
||||
active_heads: Query<Ref<ActiveHeads>, With<Player>>,
|
||||
mut state: ResMut<UiActiveHeads>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let Ok(active_heads) = active_heads.get_single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if active_heads.is_changed() || active_heads.reloading() {
|
||||
state.current_slot = active_heads.slot();
|
||||
for i in 0..HEAD_SLOTS {
|
||||
state.heads[i] = active
|
||||
state.heads[i] = active_heads
|
||||
.head(i)
|
||||
.map(|state| UiHeadState::new(state, time.elapsed_secs()));
|
||||
}
|
||||
|
||||
117
src/heads/mod.rs
117
src/heads/mod.rs
@@ -48,14 +48,21 @@ impl HeadState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
#[derive(Component, Default, Reflect, Debug)]
|
||||
#[reflect(Component)]
|
||||
pub struct ActiveHeads {
|
||||
heads: [Option<HeadState>; 5],
|
||||
current_slot: usize,
|
||||
}
|
||||
|
||||
impl ActiveHeads {
|
||||
pub fn new(heads: [Option<HeadState>; 5]) -> Self {
|
||||
Self {
|
||||
heads,
|
||||
current_slot: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Option<HeadState> {
|
||||
self.heads[self.current_slot]
|
||||
}
|
||||
@@ -80,7 +87,11 @@ impl ActiveHeads {
|
||||
|
||||
pub fn reloading(&self) -> bool {
|
||||
for head in self.heads {
|
||||
if head.map(|head| head.ammo == 0).unwrap_or(false) {
|
||||
let Some(head) = head else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if head.ammo == 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -88,6 +99,14 @@ impl ActiveHeads {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn hp(&self) -> Hitpoints {
|
||||
if let Some(head) = &self.heads[self.current_slot] {
|
||||
Hitpoints::new(head.health_max).with_health(head.health)
|
||||
} else {
|
||||
Hitpoints::new(0)
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -110,17 +129,6 @@ 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,
|
||||
@@ -140,27 +148,30 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, heads: Res<Head
|
||||
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 sync_hp(mut query: Query<(&mut ActiveHeads, &Hitpoints)>) {
|
||||
for (mut active_heads, hp) in query.iter_mut() {
|
||||
if active_heads.hp() != *hp {
|
||||
active_heads.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 {
|
||||
fn reload(mut commands: Commands, mut active: Query<&mut ActiveHeads>, time: Res<Time>) {
|
||||
for mut active in active.iter_mut() {
|
||||
if !active.reloading() {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
if !head.has_ammo() && (head.last_use + head.reload_duration <= time.elapsed_secs()) {
|
||||
commands.trigger(PlaySound::Reloaded);
|
||||
head.ammo = head.ammo_max;
|
||||
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()) {
|
||||
// only for player?
|
||||
commands.trigger(PlaySound::Reloaded);
|
||||
head.ammo = head.ammo_max;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,43 +179,47 @@ fn reload(mut commands: Commands, mut active: ResMut<ActiveHeads>, time: Res<Tim
|
||||
fn on_select_active_head(
|
||||
trigger: Trigger<SelectActiveHead>,
|
||||
mut commands: Commands,
|
||||
mut res: ResMut<ActiveHeads>,
|
||||
mut query: Query<&mut Hitpoints, With<Player>>,
|
||||
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>,
|
||||
) {
|
||||
let Ok((mut active_heads, mut hp)) = query.get_single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match trigger.event() {
|
||||
SelectActiveHead::Right => {
|
||||
res.current_slot = (res.current_slot + 1) % HEAD_SLOTS;
|
||||
active_heads.current_slot = (active_heads.current_slot + 1) % HEAD_SLOTS;
|
||||
}
|
||||
SelectActiveHead::Left => {
|
||||
res.current_slot = (res.current_slot + (HEAD_SLOTS - 1)) % HEAD_SLOTS;
|
||||
active_heads.current_slot = (active_heads.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);
|
||||
hp.set_health(active_heads.current().unwrap().health);
|
||||
|
||||
commands.trigger(PlaySound::Selection);
|
||||
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
|
||||
commands.trigger(HeadChanged(
|
||||
active_heads.heads[active_heads.current_slot].unwrap().head,
|
||||
));
|
||||
}
|
||||
|
||||
fn on_swap_backpack(
|
||||
trigger: Trigger<BackbackSwapEvent>,
|
||||
mut commands: Commands,
|
||||
mut res: ResMut<ActiveHeads>,
|
||||
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>,
|
||||
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 Ok((mut active_heads, mut hp)) = query.get_single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let current_active_head = res.heads[current_active_slot];
|
||||
res.heads[current_active_slot] = Some(*head);
|
||||
let current_active_slot = active_heads.current_slot;
|
||||
|
||||
let current_active_head = active_heads.heads[current_active_slot];
|
||||
active_heads.heads[current_active_slot] = Some(*head);
|
||||
|
||||
if let Some(old_active) = current_active_head {
|
||||
backpack.heads[backpack_slot] = old_active;
|
||||
@@ -212,11 +227,9 @@ 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);
|
||||
hp.set_health(active_heads.current().unwrap().health);
|
||||
|
||||
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
|
||||
commands.trigger(HeadChanged(
|
||||
active_heads.heads[active_heads.current_slot].unwrap().head,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub struct Hit {
|
||||
pub damage: u32,
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Debug)]
|
||||
#[derive(Component, Reflect, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct Hitpoints {
|
||||
max: u32,
|
||||
current: u32,
|
||||
@@ -21,6 +21,11 @@ impl Hitpoints {
|
||||
Self { max: v, current: v }
|
||||
}
|
||||
|
||||
pub fn with_health(mut self, v: u32) -> Self {
|
||||
self.current = v;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn health(&self) -> f32 {
|
||||
self.current as f32 / self.max as f32
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
GameState,
|
||||
ai::Ai,
|
||||
head::ActiveHead,
|
||||
heads::{HEAD_COUNT, HeadState},
|
||||
heads::{ActiveHeads, HEAD_COUNT, HeadState},
|
||||
heads_database::HeadsDatabase,
|
||||
hitpoints::{Hitpoints, Kill},
|
||||
keys::KeySpawn,
|
||||
@@ -11,9 +11,9 @@ use crate::{
|
||||
use bevy::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Component, Reflect, Deref, DerefMut)]
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct Npc(HeadState);
|
||||
pub struct Npc;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Playing), init);
|
||||
@@ -32,8 +32,9 @@ fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>, heads_db: R
|
||||
.entity(e)
|
||||
.insert((
|
||||
Hitpoints::new(100),
|
||||
Npc(HeadState::new(id, 10)),
|
||||
Npc,
|
||||
ActiveHead(id),
|
||||
ActiveHeads::new([Some(HeadState::new(id, 10)), None, None, None, None]),
|
||||
Ai,
|
||||
))
|
||||
.observe(on_kill);
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
control::controller_common::{CharacterControllerBundle, PlayerMovement},
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
heads::HeadChanged,
|
||||
heads::{ActiveHeads, HeadChanged, HeadState},
|
||||
heads_database::{HeadControls, HeadsDatabase},
|
||||
hitpoints::Hitpoints,
|
||||
loading_assets::{AudioAssets, GameAssets},
|
||||
@@ -75,6 +75,13 @@ fn spawn(
|
||||
Name::from("player"),
|
||||
Player,
|
||||
ActiveHead(0),
|
||||
ActiveHeads::new([
|
||||
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)),
|
||||
]),
|
||||
Hitpoints::new(100),
|
||||
CameraTarget,
|
||||
transform,
|
||||
|
||||
Reference in New Issue
Block a user