From c3c5ae6dfb34e6a684a4138a7371d75fe6673a22 Mon Sep 17 00:00:00 2001 From: PROMETHIA-27 <42193387+PROMETHIA-27@users.noreply.github.com> Date: Sun, 21 Dec 2025 12:01:50 -0500 Subject: [PATCH] Target UI multiplayer (#95) --- crates/hedz_reloaded/src/aim/mod.rs | 46 +++++++++++-------- crates/hedz_reloaded/src/client/aim.rs | 8 ++++ .../src/{ => client}/aim/marker.rs | 11 ++--- .../src/{ => client}/aim/target_ui.rs | 42 ++++++++--------- crates/hedz_reloaded/src/client/mod.rs | 4 +- 5 files changed, 61 insertions(+), 50 deletions(-) create mode 100644 crates/hedz_reloaded/src/client/aim.rs rename crates/hedz_reloaded/src/{ => client}/aim/marker.rs (88%) rename crates/hedz_reloaded/src/{ => client}/aim/target_ui.rs (80%) diff --git a/crates/hedz_reloaded/src/aim/mod.rs b/crates/hedz_reloaded/src/aim/mod.rs index 7d89edb..aecb180 100644 --- a/crates/hedz_reloaded/src/aim/mod.rs +++ b/crates/hedz_reloaded/src/aim/mod.rs @@ -1,13 +1,15 @@ -mod marker; -mod target_ui; - use crate::{ - GameState, control::Inputs, head::ActiveHead, heads_database::HeadsDatabase, - hitpoints::Hitpoints, physics_layers::GameLayer, player::Player, tb_entities::EnemySpawn, + GameState, + control::Inputs, + head::ActiveHead, + heads_database::HeadsDatabase, + hitpoints::Hitpoints, + physics_layers::GameLayer, + player::{LocalPlayer, Player}, + tb_entities::EnemySpawn, }; use avian3d::prelude::*; use bevy::prelude::*; -use marker::MarkerEvent; use serde::{Deserialize, Serialize}; use std::f32::consts::PI; @@ -21,7 +23,12 @@ pub struct AimTarget(pub Option); pub struct AimState { pub range: f32, pub max_angle: f32, - pub spawn_marker: bool, +} + +#[derive(Event)] +pub enum MarkerEvent { + Spawn(Entity), + Despawn, } impl Default for AimState { @@ -29,7 +36,6 @@ impl Default for AimState { Self { range: 80., max_angle: PI / 8., - spawn_marker: true, } } } @@ -38,20 +44,12 @@ pub fn plugin(app: &mut App) { app.register_type::(); app.register_type::(); - app.add_plugins(target_ui::plugin); - app.add_plugins(marker::plugin); + app.register_required_components::(); app.add_systems( Update, (update_player_aim, update_npc_aim, head_change).run_if(in_state(GameState::Playing)), ); - app.add_systems(Update, add_aim); -} - -fn add_aim(mut commands: Commands, query: Query>) { - for e in query.iter() { - commands.entity(e).insert(AimState::default()); - } } fn head_change( @@ -70,12 +68,19 @@ fn update_player_aim( mut commands: Commands, potential_targets: Query<(Entity, &Transform), With>, mut player_aim: Query< - (Entity, &AimState, &mut AimTarget, &GlobalTransform, &Inputs), + ( + Entity, + &AimState, + &mut AimTarget, + &GlobalTransform, + &Inputs, + Has, + ), With, >, spatial_query: SpatialQuery, ) { - for (player, state, mut aim_target, global_tf, inputs) in player_aim.iter_mut() { + for (player, state, mut aim_target, global_tf, inputs, is_local) in player_aim.iter_mut() { let (player_pos, player_forward) = (global_tf.translation(), inputs.look_dir); let mut new_target = None; @@ -114,13 +119,14 @@ fn update_player_aim( } if new_target != aim_target.0 { - if state.spawn_marker { + if is_local { if let Some(target) = new_target { commands.trigger(MarkerEvent::Spawn(target)); } else { commands.trigger(MarkerEvent::Despawn); } } + aim_target.0 = new_target; } } diff --git a/crates/hedz_reloaded/src/client/aim.rs b/crates/hedz_reloaded/src/client/aim.rs new file mode 100644 index 0000000..cbd61db --- /dev/null +++ b/crates/hedz_reloaded/src/client/aim.rs @@ -0,0 +1,8 @@ +use bevy::prelude::*; + +pub mod marker; +pub mod target_ui; + +pub fn plugin(app: &mut App) { + app.add_plugins((marker::plugin, target_ui::plugin)); +} diff --git a/crates/hedz_reloaded/src/aim/marker.rs b/crates/hedz_reloaded/src/client/aim/marker.rs similarity index 88% rename from crates/hedz_reloaded/src/aim/marker.rs rename to crates/hedz_reloaded/src/client/aim/marker.rs index fd69ae2..d792044 100644 --- a/crates/hedz_reloaded/src/aim/marker.rs +++ b/crates/hedz_reloaded/src/client/aim/marker.rs @@ -1,4 +1,7 @@ -use crate::{GameState, global_observer, loading_assets::UIAssets, utils::billboards::Billboard}; +use crate::{ + GameState, aim::MarkerEvent, global_observer, loading_assets::UIAssets, + utils::billboards::Billboard, +}; use bevy::prelude::*; use bevy_sprite3d::Sprite3d; use ops::sin; @@ -7,12 +10,6 @@ use ops::sin; #[reflect(Component)] struct TargetMarker; -#[derive(Event)] -pub enum MarkerEvent { - Spawn(Entity), - Despawn, -} - pub fn plugin(app: &mut App) { app.add_systems(Update, move_marker.run_if(in_state(GameState::Playing))); global_observer!(app, marker_event); diff --git a/crates/hedz_reloaded/src/aim/target_ui.rs b/crates/hedz_reloaded/src/client/aim/target_ui.rs similarity index 80% rename from crates/hedz_reloaded/src/aim/target_ui.rs rename to crates/hedz_reloaded/src/client/aim/target_ui.rs index 900e00b..f75e5c2 100644 --- a/crates/hedz_reloaded/src/aim/target_ui.rs +++ b/crates/hedz_reloaded/src/client/aim/target_ui.rs @@ -1,12 +1,12 @@ -use super::AimTarget; use crate::{ GameState, + aim::AimTarget, backpack::UiHeadState, heads::{ActiveHeads, HeadsImages}, hitpoints::Hitpoints, loading_assets::UIAssets, npc::Npc, - player::Player, + player::LocalPlayer, }; use bevy::prelude::*; @@ -18,12 +18,14 @@ struct HeadImage; #[reflect(Component)] struct HeadDamage; -#[derive(Resource, Default, PartialEq)] -struct TargetUi { +#[derive(Component, Default, PartialEq)] +pub struct TargetUi { head: Option, } pub fn plugin(app: &mut App) { + app.register_required_components::(); + app.add_systems(OnEnter(GameState::Playing), setup); app.add_systems(Update, (sync, update).run_if(in_state(GameState::Playing))); } @@ -44,8 +46,6 @@ fn setup(mut commands: Commands, assets: Res) { assets.head_damage.clone(), )], )); - - commands.insert_resource(TargetUi::default()); } fn spawn_head_ui(bg: Handle, regular: Handle, damage: Handle) -> impl Bundle { @@ -110,7 +110,7 @@ fn spawn_head_ui(bg: Handle, regular: Handle, damage: Handle, + target: Single<&TargetUi, (Changed, With)>, heads_images: Res, mut head_image: Query< (&mut Visibility, &mut ImageNode), @@ -118,30 +118,28 @@ fn update( >, mut head_damage: Query<&mut Node, (With, Without)>, ) { - if target.is_changed() { - if let Ok((mut vis, mut image)) = head_image.single_mut() { - if let Some(head) = target.head { - *vis = Visibility::Visible; - image.image = heads_images.heads[head.head].clone(); - } else { - *vis = Visibility::Hidden; - } + if let Ok((mut vis, mut image)) = head_image.single_mut() { + if let Some(head) = target.head { + *vis = Visibility::Visible; + image.image = heads_images.heads[head.head].clone(); + } else { + *vis = Visibility::Hidden; } + } - if let Ok(mut node) = head_damage.single_mut() { - node.height = Val::Percent(target.head.map(|head| head.damage()).unwrap_or(0.) * 100.); - } + if let Ok(mut node) = head_damage.single_mut() { + node.height = Val::Percent(target.head.map(|head| head.damage()).unwrap_or(0.) * 100.); } } fn sync( - mut target: ResMut, - player_target: Query<&AimTarget, With>, + mut target: Single<&mut TargetUi, With>, + player_target: Single<&AimTarget, With>, target_data: Query<(&Hitpoints, &ActiveHeads), With>, ) { let mut new_state = None; - if let Some(e) = player_target.iter().next().and_then(|target| target.0) - && let Ok((hp, heads)) = target_data.get(e) + if let Some(target) = player_target.0 + && let Ok((hp, heads)) = target_data.get(target) { let head = heads.current().expect("target must have a head on"); new_state = Some(UiHeadState { diff --git a/crates/hedz_reloaded/src/client/mod.rs b/crates/hedz_reloaded/src/client/mod.rs index 933d71c..a21741a 100644 --- a/crates/hedz_reloaded/src/client/mod.rs +++ b/crates/hedz_reloaded/src/client/mod.rs @@ -22,6 +22,7 @@ use bevy_replicon_renet::{ use bevy_steamworks::Client; use bevy_trenchbroom::geometry::Brushes; +pub mod aim; pub mod audio; pub mod backpack; pub mod control; @@ -36,6 +37,8 @@ pub mod ui; pub fn plugin(app: &mut App) { app.add_plugins(( + aim::plugin, + audio::plugin, backpack::plugin, control::plugin, debug::plugin, @@ -43,7 +46,6 @@ pub fn plugin(app: &mut App) { heal_effect::plugin, player::plugin, setup::plugin, - audio::plugin, steam::plugin, ui::plugin, settings::plugin,