diff options
-rw-r--r-- | src/client.rs | 20 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/protocol.rs | 2 | ||||
-rw-r--r-- | src/server.rs | 54 | ||||
-rw-r--r-- | src/shared.rs | 4 | ||||
-rw-r--r-- | src/shared/health.rs | 12 | ||||
-rw-r--r-- | src/shared/projectile.rs | 1 |
7 files changed, 89 insertions, 10 deletions
diff --git a/src/client.rs b/src/client.rs index a5a7845..174d615 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,6 @@ use crate::client::network::*; use crate::protocol::*; +use crate::shared::health::*; use crate::shared::imperative::*; use crate::shared::projectile::*; use crate::shared::*; @@ -45,9 +46,16 @@ impl Plugin for ClientPlugin { transport: self.transport.clone(), }) .add_systems(Startup, setup) - .add_systems(Update, (render_players, render_projectiles)) .add_systems(Update, (move_players, move_projectiles)) .add_systems( + Update, + ( + render_players.after(move_players), + render_projectiles.after(move_projectiles), + render_health, + ), + ) + .add_systems( FixedPreUpdate, buffer_input.in_set(InputSystemSet::BufferInputs), ) @@ -214,3 +222,13 @@ fn player_position( } None } + +const HEALTH_OFFSET: f32 = 4.; + +fn render_health(players: Query<(&Health, &PlayerPosition)>, mut gizmos: Gizmos) { + for (health, position) in players.iter() { + let start = position.0 + Vec2::new(-PLAYER_RADIUS, PLAYER_RADIUS + HEALTH_OFFSET); + let end = position.0 + Vec2::new(PLAYER_RADIUS, PLAYER_RADIUS + HEALTH_OFFSET); + gizmos.line_2d(start, start.lerp(end, health.0 / MAX_HEALTH), Color::RED); + } +} diff --git a/src/main.rs b/src/main.rs index f213197..b615ddc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,11 @@ fn main() { server::main(TransportConfig::UdpSocket(server_addr)); } else if let Some(server_addr) = cli.connect_to { let client_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), SERVER_PORT + client_id); - client::main(Some(server_addr), client_id as u64, TransportConfig::UdpSocket(client_addr)); + client::main( + Some(server_addr), + client_id as u64, + TransportConfig::UdpSocket(client_addr), + ); } else { let (from_server_send, from_server_recv) = crossbeam_channel::unbounded(); let (to_server_send, to_server_recv) = crossbeam_channel::unbounded(); diff --git a/src/protocol.rs b/src/protocol.rs index 7672489..3d4567d 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,4 +1,5 @@ use crate::shared::cooldown::*; +use crate::shared::health::*; use crate::shared::imperative::*; use crate::shared::projectile::*; use crate::shared::*; @@ -31,6 +32,7 @@ pub enum Components { Projectile(Projectile), ProjectilePosition(ProjectilePosition), Cooldown(Cooldown), + Health(Health), } #[derive(Channel)] diff --git a/src/server.rs b/src/server.rs index c70b39e..fab529f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,7 @@ use crate::protocol::*; use crate::server::network::*; use crate::shared::cooldown::*; +use crate::shared::health::*; use crate::shared::imperative::*; use crate::shared::projectile::*; use crate::shared::*; @@ -26,14 +27,28 @@ struct ServerPlugin { pub transport: TransportConfig, } +const HEALTH_REGEN_RATE: f32 = 1.; + +#[derive(Resource)] +struct HealthRegenTimer(pub Timer); + +impl Default for HealthRegenTimer { + fn default() -> Self { + HealthRegenTimer(Timer::from_seconds(HEALTH_REGEN_RATE, TimerMode::Repeating)) + } +} + impl Plugin for ServerPlugin { fn build(&self, app: &mut App) { app.insert_resource(EntityMap::default()) + .insert_resource(HealthRegenTimer::default()) .add_plugins(NetworkPlugin { transport: self.transport.clone(), }) .add_systems(Startup, setup) .add_systems(Update, (connects, disconnects)) + .add_systems(Update, timers_ticket) + .add_systems(Update, health_regen.after(timers_ticket)) .add_systems( Update, ( @@ -186,6 +201,7 @@ fn imperative_attack( projectile: Projectile { target_player, source_player: *id, + damage: 4., }, position: ProjectilePosition(position.0), replicate: Replicate::default(), @@ -236,19 +252,29 @@ fn projectile_move( fn projectile_despawn( entity_map: Res<EntityMap>, mut commands: Commands, - projectile_positions: Query<&ProjectilePosition>, + mut healths: Query<&mut Health>, player_positions: Query<&PlayerPosition>, + projectile_positions: Query<&ProjectilePosition>, projectiles: Query<(Entity, &mut Projectile)>, ) { for (entity, projectile) in projectiles.iter() { - if let Some(target_entity) = entity_map.0.get(&projectile.target_player.0) { - if let Ok(position) = projectile_positions.get(entity) { - if let Ok(target_position) = player_positions.get(*target_entity) { - if position.0.distance(target_position.0) <= f32::EPSILON { - commands.entity(entity).despawn(); - } - } + let Some(target_entity) = entity_map.0.get(&projectile.target_player.0) else { + commands.entity(entity).despawn(); + return; + }; + let Ok(position) = projectile_positions.get(entity) else { + commands.entity(entity).despawn(); + return; + }; + let Ok(target_position) = player_positions.get(*target_entity) else { + commands.entity(entity).despawn(); + return; + }; + if position.0.distance(target_position.0) <= f32::EPSILON { + if let Ok(mut health) = healths.get_mut(*target_entity) { + health.0 = (health.0 - projectile.damage).max(0.); } + commands.entity(entity).despawn(); } } } @@ -263,3 +289,15 @@ fn cooldown_decrement(mut cooldowns: Query<&mut Cooldown>, time: Res<Time>) { cooldown.f_cooldown = cooldown.f_cooldown.saturating_sub(time.delta()); } } + +fn timers_ticket(mut health_regen_timer: ResMut<HealthRegenTimer>, time: Res<Time>) { + health_regen_timer.0.tick(time.delta()); +} + +fn health_regen(health_regen_timer: Res<HealthRegenTimer>, mut healths: Query<&mut Health>) { + if health_regen_timer.0.just_finished() { + for mut health in healths.iter_mut() { + health.0 = (health.0 + 1.).min(MAX_HEALTH); + } + } +} diff --git a/src/shared.rs b/src/shared.rs index b3a7cc4..92b6a5b 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -1,5 +1,6 @@ use crate::protocol::Replicate; use crate::shared::cooldown::*; +use crate::shared::health::*; use crate::shared::imperative::*; use bevy::prelude::*; use lightyear::prelude::*; @@ -8,6 +9,7 @@ use serde::Serialize; use std::default::Default; pub mod cooldown; +pub mod health; pub mod imperative; pub mod projectile; @@ -25,6 +27,7 @@ pub struct PlayerBundle { replicate: Replicate, imperative: Imperative, cooldown: Cooldown, + health: Health, } #[derive(Component, Message, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] @@ -45,6 +48,7 @@ impl PlayerBundle { replicate: Replicate::default(), imperative: Imperative::Idle, cooldown: Cooldown::default(), + health: Health::default(), } } } diff --git a/src/shared/health.rs b/src/shared/health.rs new file mode 100644 index 0000000..f4a288a --- /dev/null +++ b/src/shared/health.rs @@ -0,0 +1,12 @@ +use crate::shared::*; + +#[derive(Component, Message, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +pub struct Health(pub f32); + +pub const MAX_HEALTH: f32 = 100.; + +impl Default for Health { + fn default() -> Self { + Health(MAX_HEALTH) + } +} diff --git a/src/shared/projectile.rs b/src/shared/projectile.rs index 8dc9da7..c42173a 100644 --- a/src/shared/projectile.rs +++ b/src/shared/projectile.rs @@ -13,6 +13,7 @@ pub struct ProjectileBundle { pub struct Projectile { pub target_player: PlayerId, pub source_player: PlayerId, + pub damage: f32, } #[derive(Component, Message, Serialize, Deserialize, Clone, Debug, PartialEq)] |