medic healing ability
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
/*01*/(key:"carnival knife thrower", range:60, ammo:5),
|
/*01*/(key:"carnival knife thrower", range:60, ammo:5),
|
||||||
/*02*/(key:"chicago gangster", ability:Gun, ammo:25, range:60),
|
/*02*/(key:"chicago gangster", ability:Gun, ammo:25, range:60),
|
||||||
/*03*/(key:"commando", ability:Gun, aps:7.4, ammo:26, range:60, damage:12),
|
/*03*/(key:"commando", ability:Gun, aps:7.4, ammo:26, range:60, damage:12),
|
||||||
/*04*/(key:"field medic"),
|
/*04*/(key:"field medic", ability:Medic, aps:20),
|
||||||
/*05*/(key:"geisha"),
|
/*05*/(key:"geisha"),
|
||||||
/*06*/(key:"goblin", ability:Arrow, aps:2, ammo:5, range:90, damage:50),
|
/*06*/(key:"goblin", ability:Arrow, aps:2, ammo:5, range:90, damage:50),
|
||||||
/*07*/(key:"green grocer", range:60, ammo:10, damage:25),
|
/*07*/(key:"green grocer", range:60, ammo:10, damage:25),
|
||||||
|
|||||||
Binary file not shown.
76
src/abilities/healing.rs
Normal file
76
src/abilities/healing.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod arrow;
|
mod arrow;
|
||||||
mod gun;
|
mod gun;
|
||||||
|
mod healing;
|
||||||
mod missile;
|
mod missile;
|
||||||
mod thrown;
|
mod thrown;
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ use crate::{
|
|||||||
sounds::PlaySound,
|
sounds::PlaySound,
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use healing::HealingStateChanged;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Event, Reflect)]
|
#[derive(Event, Reflect)]
|
||||||
@@ -34,6 +36,7 @@ pub enum HeadAbility {
|
|||||||
Thrown,
|
Thrown,
|
||||||
Gun,
|
Gun,
|
||||||
Missile,
|
Missile,
|
||||||
|
Medic,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Reflect, Clone, Copy)]
|
#[derive(Debug, Reflect, Clone, Copy)]
|
||||||
@@ -88,8 +91,12 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.add_plugins(thrown::plugin);
|
app.add_plugins(thrown::plugin);
|
||||||
app.add_plugins(arrow::plugin);
|
app.add_plugins(arrow::plugin);
|
||||||
app.add_plugins(missile::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);
|
global_observer!(app, on_trigger_state);
|
||||||
}
|
}
|
||||||
@@ -136,6 +143,16 @@ fn update(
|
|||||||
return;
|
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 {
|
let trigger_state = TriggerData {
|
||||||
dir,
|
dir,
|
||||||
rot,
|
rot,
|
||||||
@@ -145,18 +162,39 @@ fn update(
|
|||||||
head: state.head,
|
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 {
|
match head.ability {
|
||||||
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
|
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
|
||||||
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
|
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
|
||||||
HeadAbility::Missile => commands.trigger(TriggerMissile(trigger_state)),
|
HeadAbility::Missile => commands.trigger(TriggerMissile(trigger_state)),
|
||||||
HeadAbility::Arrow => commands.trigger(TriggerArrow(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,6 +78,39 @@ impl ActiveHeads {
|
|||||||
head.ammo = head.ammo.saturating_sub(1);
|
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 {
|
pub fn slot(&self) -> usize {
|
||||||
self.current_slot
|
self.current_slot
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ pub struct AudioAssets {
|
|||||||
pub ambient: Handle<AudioSource>,
|
pub ambient: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/effects/key_collect.ogg")]
|
#[asset(path = "sfx/effects/key_collect.ogg")]
|
||||||
pub key_collect: Handle<AudioSource>,
|
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")]
|
#[asset(path = "sfx/effects/gate.ogg")]
|
||||||
pub gate: Handle<AudioSource>,
|
pub gate: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/effects/cash.ogg")]
|
#[asset(path = "sfx/effects/cash.ogg")]
|
||||||
@@ -38,6 +34,15 @@ pub struct AudioAssets {
|
|||||||
pub throw_explosion: Handle<AudioSource>,
|
pub throw_explosion: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/abilities/jet.ogg")]
|
#[asset(path = "sfx/abilities/jet.ogg")]
|
||||||
pub jet: Handle<AudioSource>,
|
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")]
|
#[asset(path = "sfx/ui/backpack_open.ogg")]
|
||||||
pub backpack_open: Handle<AudioSource>,
|
pub backpack_open: Handle<AudioSource>,
|
||||||
#[asset(path = "sfx/ui/backpack_close.ogg")]
|
#[asset(path = "sfx/ui/backpack_close.ogg")]
|
||||||
@@ -48,9 +53,6 @@ pub struct AudioAssets {
|
|||||||
#[asset(path = "sfx/effects/head_drop.ogg")]
|
#[asset(path = "sfx/effects/head_drop.ogg")]
|
||||||
pub head_drop: Handle<AudioSource>,
|
pub head_drop: Handle<AudioSource>,
|
||||||
|
|
||||||
#[asset(path = "sfx/abilities/missile-explosion.ogg")]
|
|
||||||
pub missile_explosion: Handle<AudioSource>,
|
|
||||||
|
|
||||||
#[asset(path = "sfx/hit", collection(typed))]
|
#[asset(path = "sfx/hit", collection(typed))]
|
||||||
pub hit: Vec<Handle<AudioSource>>,
|
pub hit: Vec<Handle<AudioSource>>,
|
||||||
#[asset(path = "sfx/heads", collection(mapped, typed))]
|
#[asset(path = "sfx/heads", collection(mapped, typed))]
|
||||||
|
|||||||
Reference in New Issue
Block a user