medic healing ability

This commit is contained in:
2025-05-04 14:19:56 +02:00
parent 69f9d73d80
commit 812dcbac7c
6 changed files with 165 additions and 16 deletions

76
src/abilities/healing.rs Normal file
View File

@@ -0,0 +1,76 @@
use bevy::prelude::*;
use crate::{
GameState, global_observer, heads::ActiveHeads, heads_database::HeadsDatabase,
hitpoints::Hitpoints, loading_assets::AudioAssets,
};
#[derive(Component)]
struct Healing(Entity);
#[derive(Event, Debug)]
pub enum HealingStateChanged {
Started,
Stopped,
}
pub fn plugin(app: &mut App) {
app.add_systems(Update, update.run_if(in_state(GameState::Playing)));
global_observer!(app, on_heal_start_stop);
}
fn on_heal_start_stop(
trigger: Trigger<HealingStateChanged>,
mut cmds: Commands,
assets: Res<AudioAssets>,
query: Query<&Healing>,
) {
if matches!(trigger.event(), HealingStateChanged::Started) {
let e = cmds
.spawn((
Name::new("sfx-heal"),
AudioPlayer::new(assets.healing.clone()),
PlaybackSettings {
mode: bevy::audio::PlaybackMode::Loop,
..Default::default()
},
))
.id();
cmds.entity(trigger.target())
.add_child(e)
.insert(Healing(e));
} else {
if let Ok(healing) = query.single() {
cmds.entity(healing.0).despawn();
}
cmds.entity(trigger.target()).remove::<Healing>();
}
}
fn update(
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Healing>>,
time: Res<Time>,
heads_db: Res<HeadsDatabase>,
) {
for (mut heads, mut hitpoints) in query.iter_mut() {
let Some(current) = heads.current() else {
continue;
};
let head = heads_db.head_stats(current.head);
if current.last_use + (1. / head.aps) > time.elapsed_secs() {
continue;
}
let medic_hp = hitpoints.get().0;
if medic_hp > 0 {
if let Some(health) = heads.medic_heal(2, time.elapsed_secs()) {
hitpoints.set_health(health);
} else {
continue;
}
}
}
}

View File

@@ -1,5 +1,6 @@
mod arrow;
mod gun;
mod healing;
mod missile;
mod thrown;
@@ -15,6 +16,7 @@ use crate::{
sounds::PlaySound,
};
use bevy::prelude::*;
use healing::HealingStateChanged;
use serde::{Deserialize, Serialize};
#[derive(Event, Reflect)]
@@ -34,6 +36,7 @@ pub enum HeadAbility {
Thrown,
Gun,
Missile,
Medic,
}
#[derive(Debug, Reflect, Clone, Copy)]
@@ -88,8 +91,12 @@ pub fn plugin(app: &mut App) {
app.add_plugins(thrown::plugin);
app.add_plugins(arrow::plugin);
app.add_plugins(missile::plugin);
app.add_plugins(healing::plugin);
app.add_systems(Update, update.run_if(in_state(GameState::Playing)));
app.add_systems(
Update,
(update, update_heal_ability).run_if(in_state(GameState::Playing)),
);
global_observer!(app, on_trigger_state);
}
@@ -136,6 +143,16 @@ fn update(
return;
};
let head = heads_db.head_stats(state.head);
if matches!(head.ability, HeadAbility::None | HeadAbility::Medic) {
return;
}
active_heads.use_ammo(time.elapsed_secs());
res.cooldown = time.elapsed_secs() + (1. / head.aps);
let trigger_state = TriggerData {
dir,
rot,
@@ -145,18 +162,39 @@ fn update(
head: state.head,
};
active_heads.use_ammo(time.elapsed_secs());
let head = heads_db.head_stats(state.head);
res.cooldown = time.elapsed_secs() + (1. / head.aps);
match head.ability {
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
HeadAbility::Missile => commands.trigger(TriggerMissile(trigger_state)),
HeadAbility::Arrow => commands.trigger(TriggerArrow(trigger_state)),
HeadAbility::None => (),
_ => panic!("Unhandled head ability"),
};
}
}
fn update_heal_ability(
res: Res<TriggerStateRes>,
mut commands: Commands,
active_heads: Single<(Entity, &ActiveHeads), With<Player>>,
heads_db: Res<HeadsDatabase>,
) {
if res.is_changed() {
let Some(state) = active_heads.1.current() else {
return;
};
let player = active_heads.0;
let head = heads_db.head_stats(state.head);
if !matches!(head.ability, HeadAbility::Medic) {
return;
}
if res.active {
commands.trigger_targets(HealingStateChanged::Started, player);
} else {
commands.trigger_targets(HealingStateChanged::Stopped, player);
}
}
}

View File

@@ -78,6 +78,39 @@ impl ActiveHeads {
head.ammo = head.ammo.saturating_sub(1);
}
pub fn medic_heal(&mut self, heal_amount: u32, time: f32) -> Option<u32> {
let mut healed = false;
for (index, head) in self.heads.iter_mut().enumerate() {
if index == self.current_slot {
continue;
}
if let Some(head) = head {
if head.health < head.health_max {
head.health = head
.health
.saturating_add(heal_amount)
.clamp(0, head.health_max);
healed = true;
}
}
}
if healed {
let Some(head) = &mut self.heads[self.current_slot] else {
error!("cannot heal with empty head");
return None;
};
head.last_use = time;
head.health = head.health.saturating_sub(1);
Some(head.health)
} else {
None
}
}
pub fn slot(&self) -> usize {
self.current_slot
}

View File

@@ -13,10 +13,6 @@ pub struct AudioAssets {
pub ambient: Handle<AudioSource>,
#[asset(path = "sfx/effects/key_collect.ogg")]
pub key_collect: Handle<AudioSource>,
#[asset(path = "sfx/abilities/gun.ogg")]
pub gun: Handle<AudioSource>,
#[asset(path = "sfx/abilities/crossbow.ogg")]
pub crossbow: Handle<AudioSource>,
#[asset(path = "sfx/effects/gate.ogg")]
pub gate: Handle<AudioSource>,
#[asset(path = "sfx/effects/cash.ogg")]
@@ -38,6 +34,15 @@ pub struct AudioAssets {
pub throw_explosion: Handle<AudioSource>,
#[asset(path = "sfx/abilities/jet.ogg")]
pub jet: Handle<AudioSource>,
#[asset(path = "sfx/abilities/gun.ogg")]
pub gun: Handle<AudioSource>,
#[asset(path = "sfx/abilities/crossbow.ogg")]
pub crossbow: Handle<AudioSource>,
#[asset(path = "sfx/abilities/heal.ogg")]
pub healing: Handle<AudioSource>,
#[asset(path = "sfx/abilities/missile-explosion.ogg")]
pub missile_explosion: Handle<AudioSource>,
#[asset(path = "sfx/ui/backpack_open.ogg")]
pub backpack_open: Handle<AudioSource>,
#[asset(path = "sfx/ui/backpack_close.ogg")]
@@ -48,9 +53,6 @@ pub struct AudioAssets {
#[asset(path = "sfx/effects/head_drop.ogg")]
pub head_drop: Handle<AudioSource>,
#[asset(path = "sfx/abilities/missile-explosion.ogg")]
pub missile_explosion: Handle<AudioSource>,
#[asset(path = "sfx/hit", collection(typed))]
pub hit: Vec<Handle<AudioSource>>,
#[asset(path = "sfx/heads", collection(mapped, typed))]