Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Indy2222 committed Sep 16, 2023
1 parent c2e9741 commit 528054f
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 75 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions crates/combat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ categories.workspace = true
[dependencies]
# DE
de_audio.workspace = true
de_behaviour.workspace = true
de_core.workspace = true
de_objects.workspace = true
de_terrain.workspace = true
de_index.workspace = true
de_spawner.workspace = true
de_behaviour.workspace = true
de_messages.workspace = true
de_multiplayer.workspace = true
de_objects.workspace = true
de_signs.workspace = true
de_spawner.workspace = true
de_terrain.workspace = true
de_types.workspace = true

# Other
bevy.workspace = true
Expand Down
128 changes: 128 additions & 0 deletions crates/combat/src/health.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use bevy::prelude::*;
use de_core::{gconfig::GameConfig, objects::Local, state::AppState};
use de_messages::ToPlayers;
use de_multiplayer::{NetEntities, NetRecvHealthEvent, ToPlayersEvent};
use de_objects::Health;
use de_signs::UpdateBarValueEvent;
use de_spawner::{DespawnActiveLocalEvent, DespawnerSet};

pub(crate) struct HealthPlugin;

impl Plugin for HealthPlugin {
fn build(&self, app: &mut App) {
app.add_event::<LocalUpdateHealthEvent>()
.add_event::<UpdateHealthEvent>()
.add_systems(
Update,
(
update_local_health
.run_if(on_event::<LocalUpdateHealthEvent>())
.before(update_health),
update_remote_health
.run_if(on_event::<NetRecvHealthEvent>())
.before(update_health),
update_health.run_if(on_event::<UpdateHealthEvent>()),
find_dead.after(update_health).before(DespawnerSet::Despawn),
)
.run_if(in_state(AppState::InGame))
.in_set(HealthSet::Update),
);
}
}

#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemSet)]
pub(crate) enum HealthSet {
Update,
}

/// Send this event to change health of a local entity.
// TODO better name (not local?)
// TODO better docs
#[derive(Event)]
pub(crate) struct LocalUpdateHealthEvent {
entity: Entity,
delta: f32,
}

impl LocalUpdateHealthEvent {
/// # Panics
///
/// Panics if health delta is not finite.
pub(crate) fn new(entity: Entity, delta: f32) -> Self {
assert!(delta.is_finite());
Self { entity, delta }
}
}

/// Send this event to change health of a local entity.
#[derive(Event)]
struct UpdateHealthEvent {
entity: Entity,
delta: f32,
}

impl UpdateHealthEvent {
/// # Panics
///
/// Panics if health delta is not finite.
fn new(entity: Entity, delta: f32) -> Self {
assert!(delta.is_finite());
Self { entity, delta }
}
}

fn update_local_health(
config: Res<GameConfig>,
net_entities: NetEntities,
mut in_events: EventReader<LocalUpdateHealthEvent>,
mut out_events: EventWriter<UpdateHealthEvent>,
mut net_events: EventWriter<ToPlayersEvent>,
) {
for event in in_events.iter() {
out_events.send(UpdateHealthEvent::new(event.entity, event.delta));

if config.multiplayer() {
net_events.send(ToPlayersEvent::new(ToPlayers::Health {
entity: net_entities.net_id(event.entity),
delta: event.delta.try_into().unwrap(),
}));
}
}
}

fn update_remote_health(
mut in_events: EventReader<NetRecvHealthEvent>,
mut out_events: EventWriter<UpdateHealthEvent>,
) {
for event in in_events.iter() {
out_events.send(UpdateHealthEvent::new(event.entity(), event.delta()));
}
}

fn update_health(
mut healths: Query<&mut Health>,
mut health_events: EventReader<UpdateHealthEvent>,
mut bar_events: EventWriter<UpdateBarValueEvent>,
) {
for event in health_events.iter() {
let Ok(mut health) = healths.get_mut(event.entity) else {
continue;
};
health.update(event.delta);
bar_events.send(UpdateBarValueEvent::new(event.entity, health.fraction()));
}
}

type LocallyChangedHealth<'w, 's> =
Query<'w, 's, (Entity, &'static Health), (With<Local>, Changed<Health>)>;

fn find_dead(
entities: LocallyChangedHealth,
mut event_writer: EventWriter<DespawnActiveLocalEvent>,
) {
for (entity, health) in entities.iter() {
if health.destroyed() {
event_writer.send(DespawnActiveLocalEvent::new(entity));
}
}
}
26 changes: 9 additions & 17 deletions crates/combat/src/laser.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use bevy::prelude::*;
use de_audio::spatial::{PlaySpatialAudioEvent, Sound};
use de_core::gamestate::GameState;
use de_objects::Health;
use de_signs::UpdateBarValueEvent;
use de_spawner::DespawnerSet;
use parry3d::query::Ray;

use crate::{sightline::LineOfSight, trail::TrailEvent, AttackingSet};
use crate::{
health::{HealthSet, LocalUpdateHealthEvent},
sightline::LineOfSight,
trail::TrailEvent,
AttackingSet,
};

pub(crate) struct LaserPlugin;

Expand All @@ -16,7 +18,7 @@ impl Plugin for LaserPlugin {
Update,
fire.run_if(in_state(GameState::Playing))
.in_set(AttackingSet::Fire)
.before(DespawnerSet::Destruction),
.before(HealthSet::Update),
);
}
}
Expand Down Expand Up @@ -77,19 +79,11 @@ impl LaserFireEvent {
fn fire(
mut fires: EventReader<LaserFireEvent>,
sightline: LineOfSight,
mut susceptible: Query<&mut Health>,
mut bar: EventWriter<UpdateBarValueEvent>,
mut health: EventWriter<LocalUpdateHealthEvent>,
mut trail: EventWriter<TrailEvent>,
mut start_sound: EventWriter<PlaySpatialAudioEvent>,
) {
for fire in fires.iter() {
if susceptible
.get(fire.attacker())
.map_or(true, |health| health.destroyed())
{
continue;
}

let observation = sightline.sight(fire.ray(), fire.max_toi(), fire.attacker());

trail.send(TrailEvent::new(Ray::new(
Expand All @@ -98,9 +92,7 @@ fn fire(
)));

if let Some(entity) = observation.entity() {
let mut health = susceptible.get_mut(entity).unwrap();
health.hit(fire.damage());
bar.send(UpdateBarValueEvent::new(entity, health.fraction()));
health.send(LocalUpdateHealthEvent::new(entity, -fire.damage()));
}

start_sound.send(PlaySpatialAudioEvent::new(
Expand Down
3 changes: 3 additions & 0 deletions crates/combat/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ use bevy::{
app::PluginGroupBuilder,
prelude::{PluginGroup, SystemSet},
};
use health::HealthPlugin;
use laser::LaserPlugin;
use trail::TrailPlugin;

mod attack;
mod health;
mod laser;
mod sightline;
mod trail;
Expand All @@ -20,6 +22,7 @@ impl PluginGroup for CombatPluginGroup {
.add(LaserPlugin)
.add(AttackPlugin)
.add(TrailPlugin)
.add(HealthPlugin)
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

pub use game::{FromGame, JoinError, Readiness, ToGame};
pub use players::{
BorrowedFromPlayers, ChatMessage, ChatMessageError, EntityNet, FromPlayers, ToPlayers,
MAX_CHAT_LEN,
BorrowedFromPlayers, ChatMessage, ChatMessageError, EntityNet, FromPlayers, HealthDelta,
ToPlayers, MAX_CHAT_LEN,
};
pub use server::{FromServer, GameOpenError, ToServer};

Expand Down
29 changes: 29 additions & 0 deletions crates/messages/src/players/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,33 @@ pub enum ToPlayers {
entity: EntityNet,
transform: TransformNet,
},
/// Changes entity health by an amount. Resulting health cannot go below 0.
/// or above a maximum.
Health {
entity: EntityNet,
delta: HealthDelta,
},
}

#[derive(Debug, Encode, Decode)]
pub struct HealthDelta(f32);

impl TryFrom<f32> for HealthDelta {
type Error = &'static str;

fn try_from(value: f32) -> Result<Self, Self::Error> {
if !value.is_finite() {
Err("Got non-finite health delta.")
} else if value == 0. {
Err("Got 0.0 health delta.")
} else {
Ok(Self(value))
}
}
}

impl From<&HealthDelta> for f32 {
fn from(delta: &HealthDelta) -> f32 {
delta.0
}
}
5 changes: 4 additions & 1 deletion crates/multiplayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ pub use crate::{
lifecycle::{MultiplayerShuttingDownEvent, ShutdownMultiplayerEvent, StartMultiplayerEvent},
messages::{MessagesSet, ToPlayersEvent},
netstate::NetState,
playermsg::{GameNetSet, NetEntities, NetRecvDespawnActiveEvent, NetRecvSpawnActiveEvent},
playermsg::{
GameNetSet, NetEntities, NetRecvDespawnActiveEvent, NetRecvHealthEvent,
NetRecvSpawnActiveEvent,
},
};
use crate::{netstate::NetStatePlugin, network::NetworkPlugin};

Expand Down
1 change: 1 addition & 0 deletions crates/multiplayer/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ impl ToMessage for ToPlayersEvent {
ToPlayers::Despawn { .. } => Reliability::SemiOrdered,
ToPlayers::SetPath { .. } => Reliability::SemiOrdered,
ToPlayers::Transform { .. } => Reliability::Unreliable,
ToPlayers::Health { .. } => Reliability::SemiOrdered,
}
}

Expand Down
37 changes: 33 additions & 4 deletions crates/multiplayer/src/playermsg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ impl Plugin for PlayerMsgPlugin {
fn build(&self, app: &mut App) {
app.add_event::<NetRecvSpawnActiveEvent>()
.add_event::<NetRecvDespawnActiveEvent>()
.add_event::<NetRecvHealthEvent>()
.add_systems(OnEnter(AppState::InGame), setup)
.add_systems(OnExit(AppState::InGame), cleanup)
.add_systems(
Expand Down Expand Up @@ -96,6 +97,30 @@ impl NetRecvDespawnActiveEvent {
}
}

#[derive(Event)]
pub struct NetRecvHealthEvent {
entity: Entity,
delta: f32,
}

impl NetRecvHealthEvent {
/// # Panics
///
/// Panics if delta is not a finite number.
fn new(entity: Entity, delta: f32) -> Self {
assert!(delta.is_finite());
Self { entity, delta }
}

pub fn entity(&self) -> Entity {
self.entity
}

pub fn delta(&self) -> f32 {
self.delta
}
}

#[derive(SystemParam)]
pub struct NetEntities<'w> {
config: Res<'w, GameConfig>,
Expand All @@ -114,10 +139,6 @@ impl<'w> NetEntities<'w> {
}
}

/// Translates a local entity ID to a remote entity ID. This works only for
/// locally simulated entities.
///
/// It is assumed that the entity exists.
pub fn local_net_id(&self, entity: Entity) -> EntityNet {
let player = self.config.locals().playable();
EntityNet::new(player, entity.index())
Expand Down Expand Up @@ -192,6 +213,7 @@ fn recv_messages(
mut inputs: EventReader<FromPlayersEvent>,
mut spawn_events: EventWriter<NetRecvSpawnActiveEvent>,
mut despawn_events: EventWriter<NetRecvDespawnActiveEvent>,
mut health_events: EventWriter<NetRecvHealthEvent>,
) {
for input in inputs.iter() {
match input.message() {
Expand All @@ -215,6 +237,13 @@ fn recv_messages(
let local = map.deregister(*entity);
despawn_events.send(NetRecvDespawnActiveEvent::new(local));
}
ToPlayers::Health { entity, delta } => {
let Some(local) = map.remote_to_local(*entity) else {
warn!("Received health update of a non-existent entity: {entity:?}");
continue;
};
health_events.send(NetRecvHealthEvent::new(local, delta.into()));
}
_ => (),
}
}
Expand Down
Loading

0 comments on commit 528054f

Please sign in to comment.