aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-17 16:32:22 +0100
committerLibravatar Alexander Foremny <aforemny@posteo.de>2024-03-17 16:32:22 +0100
commit312aa627052c24e0aa1948e584c046bc15f18c96 (patch)
treeff74e110ae4481979352962985e75fb5ae99dfe9 /src
parente228208de62f8abf1744f389271168d996a10b25 (diff)
feat: add damage indicators
Diffstat (limited to 'src')
-rw-r--r--src/client.rs108
-rw-r--r--src/protocol.rs5
-rw-r--r--src/server.rs12
-rw-r--r--src/shared.rs1
-rw-r--r--src/shared/health_event.rs7
5 files changed, 132 insertions, 1 deletions
diff --git a/src/client.rs b/src/client.rs
index 1805cae..8220047 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -4,6 +4,7 @@ use crate::shared::ability::*;
use crate::shared::champion::*;
use crate::shared::cooldown::*;
use crate::shared::health::*;
+use crate::shared::health_event::*;
use crate::shared::imperative::*;
use crate::shared::projectile::*;
use crate::shared::*;
@@ -11,6 +12,7 @@ use bevy::input::keyboard::*;
use bevy::input::mouse::MouseButton;
use bevy::prelude::*;
use bevy::sprite::{MaterialMesh2dBundle, Mesh2dHandle};
+use lightyear::client::events::*;
use lightyear::client::input::InputSystemSet;
use lightyear::prelude::*;
use std::net::SocketAddr;
@@ -79,6 +81,13 @@ impl Plugin for ClientPlugin {
(
(hotbar_hotbar_display, hotbar_hotbar_highlight),
hotbar_cooldown,
+ (
+ health_indicator_despawn,
+ health_indicator_tick,
+ health_indicator_move_up,
+ health_indicator_spawn,
+ )
+ .chain(),
),
)
.add_systems(
@@ -355,6 +364,11 @@ fn cursor_world_position(
camera.viewport_to_world_2d(camera_transform, cursor_position)
}
+fn world_to_viewport(cameras: &Query<(&Camera, &GlobalTransform)>, position: Vec2) -> Option<Vec2> {
+ let (camera, camera_transform) = cameras.single();
+ camera.world_to_viewport(camera_transform, Vec3::new(position.x, position.y, 0.))
+}
+
fn hovered_other_player(
cameras: &Query<(&Camera, &GlobalTransform)>,
client_id: &Res<ClientId>,
@@ -550,3 +564,97 @@ fn hotbar_hotbar_highlight(attack: Res<Attack>, mut hotbars: Query<(&Hotbar, &mu
}
}
}
+
+#[derive(Component)]
+pub struct HealthIndicator {
+ position: Vec2,
+ timer: Timer,
+}
+
+fn health_indicator_spawn(
+ cameras: Query<(&Camera, &GlobalTransform)>,
+ mut commands: Commands,
+ mut event_reader: EventReader<MessageEvent<HealthChanged>>,
+ players: Query<(&PlayerId, &PlayerPosition)>,
+) {
+ for event in event_reader.read() {
+ let HealthChanged(HealthEvent {
+ target_player,
+ health_gained,
+ }) = event.message();
+ let Some(position) = any_player_position(*target_player, &players) else {
+ continue;
+ };
+ let health_gained_or_lost = health_gained.abs();
+ let Some(mut screen_position) = world_to_viewport(&cameras, position.0) else {
+ continue;
+ };
+ screen_position += Vec2::new(12.5, -12.5);
+ commands.spawn((
+ TextBundle::from_section(
+ format!("{health_gained_or_lost}"),
+ TextStyle {
+ font_size: 6.,
+ color: Color::RED,
+ ..Default::default()
+ },
+ )
+ .with_style(Style {
+ position_type: PositionType::Absolute,
+ top: Val::Px(screen_position.y),
+ left: Val::Px(screen_position.x),
+ ..Default::default()
+ }),
+ HealthIndicator {
+ position: position.0,
+ timer: Timer::from_seconds(1., TimerMode::Once),
+ },
+ ));
+ }
+}
+
+fn any_player_position(
+ player_id: PlayerId,
+ players: &Query<(&PlayerId, &PlayerPosition)>,
+) -> Option<PlayerPosition> {
+ for (id, position) in players.iter() {
+ if *id == player_id {
+ return Some(*position);
+ }
+ }
+ None
+}
+
+fn health_indicator_move_up(
+ cameras: Query<(&Camera, &GlobalTransform)>,
+ mut health_indicators: Query<(&mut Style, &HealthIndicator)>,
+) {
+ for (mut style, health_indicator) in health_indicators.iter_mut() {
+ let Some(mut screen_position) = world_to_viewport(&cameras, health_indicator.position)
+ else {
+ continue;
+ };
+ screen_position += Vec2::new(12.5, -12.5);
+ let s = health_indicator.timer.fraction();
+ style.top = Val::Px((1. - s) * screen_position.y + s * (screen_position.y - 20.));
+ style.left = Val::Px(screen_position.x);
+ }
+}
+
+fn health_indicator_tick(mut health_indicators: Query<&mut HealthIndicator>, time: Res<Time>) {
+ for mut health_indicator in health_indicators.iter_mut() {
+ health_indicator.timer.tick(time.delta());
+ }
+}
+
+fn health_indicator_despawn(
+ health_indicators: Query<(Entity, &HealthIndicator)>,
+ mut commands: Commands,
+) {
+ for (entity, health_indicator) in health_indicators.iter() {
+ if !health_indicator.timer.finished() {
+ continue;
+ }
+ commands.entity(entity).despawn();
+ }
+}
diff --git a/src/protocol.rs b/src/protocol.rs
index 70f4f64..c1c18e4 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -1,6 +1,7 @@
use crate::shared::champion::*;
use crate::shared::cooldown::*;
use crate::shared::health::*;
+use crate::shared::health_event::*;
use crate::shared::imperative::*;
use crate::shared::projectile::*;
use crate::shared::*;
@@ -19,9 +20,13 @@ impl UserAction for Inputs {}
#[derive(Message, Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct SelectChampion(pub Champion);
+#[derive(Message, Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct HealthChanged(pub HealthEvent);
+
#[message_protocol(protocol = "MyProtocol")]
pub enum Messages {
SelectChampion(SelectChampion),
+ HealthChanged(HealthChanged),
}
#[component_protocol(protocol = "MyProtocol")]
diff --git a/src/server.rs b/src/server.rs
index f6cbac3..7d50d9f 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -3,6 +3,7 @@ use crate::server::network::*;
use crate::shared::champion::*;
use crate::shared::cooldown::*;
use crate::shared::health::*;
+use crate::shared::health_event::*;
use crate::shared::imperative::*;
use crate::shared::projectile::*;
use crate::shared::*;
@@ -350,10 +351,11 @@ fn projectile_move(
fn projectile_despawn(
entity_map: Res<EntityMap>,
mut commands: Commands,
+ mut connection_manager: ResMut<ServerConnectionManager>,
mut healths: Query<&mut Health>,
player_positions: Query<&PlayerPosition>,
- projectile_targets: Query<(&PlayerId, &PlayerPosition)>,
projectiles: Query<(Entity, &mut Projectile)>,
+ projectile_targets: Query<(&PlayerId, &PlayerPosition)>,
) {
for (entity, projectile) in projectiles.iter() {
let (despawn, maybe_target_player): (bool, Option<PlayerId>) = (|| match &projectile.type_ {
@@ -414,6 +416,14 @@ fn projectile_despawn(
if let Some(target_player) = maybe_target_player {
if let Some(target_entity) = entity_map.0.get(&target_player.0) {
if let Ok(mut health) = healths.get_mut(*target_entity) {
+ let _ = connection_manager
+ .send_message_to_target::<Channel1, HealthChanged>(
+ HealthChanged(HealthEvent {
+ target_player,
+ health_gained: -projectile.damage,
+ }),
+ NetworkTarget::All,
+ );
health.0 = (health.0 - projectile.damage).max(0.);
}
}
diff --git a/src/shared.rs b/src/shared.rs
index 6f01f85..f5a813e 100644
--- a/src/shared.rs
+++ b/src/shared.rs
@@ -13,6 +13,7 @@ pub mod ability;
pub mod champion;
pub mod cooldown;
pub mod health;
+pub mod health_event;
pub mod imperative;
pub mod projectile;
diff --git a/src/shared/health_event.rs b/src/shared/health_event.rs
new file mode 100644
index 0000000..9932599
--- /dev/null
+++ b/src/shared/health_event.rs
@@ -0,0 +1,7 @@
+use crate::shared::*;
+
+#[derive(Message, Serialize, Deserialize, PartialEq, Clone, Debug)]
+pub struct HealthEvent {
+ pub target_player: PlayerId,
+ pub health_gained: f32,
+}