From a16ee231cc032687618a62a5f18dd63acd84ca8e Mon Sep 17 00:00:00 2001 From: PROMETHIA-27 <42193387+PROMETHIA-27@users.noreply.github.com> Date: Mon, 29 Sep 2025 14:46:38 -0400 Subject: [PATCH] Replicate Sounds (#68) --- Cargo.toml | 1 - crates/client/Cargo.toml | 1 + crates/client/src/backpack/backpack_ui.rs | 208 +++++++++++ crates/client/src/backpack/mod.rs | 7 + crates/client/src/client.rs | 20 +- crates/{shared => client}/src/heal_effect.rs | 72 +++- crates/client/src/main.rs | 77 ++-- crates/client/src/player.rs | 115 ++++++ crates/{shared => client}/src/sounds.rs | 25 +- crates/server/src/backpack/backpack_ui.rs | 92 +++++ crates/server/src/backpack/mod.rs | 7 + crates/server/src/main.rs | 59 +-- crates/server/src/player.rs | 48 ++- crates/server/src/server.rs | 74 +++- crates/server/src/tb_entities.rs | 6 +- crates/server/src/utils.rs | 37 ++ crates/shared/src/abilities/arrow.rs | 2 +- crates/shared/src/abilities/gun.rs | 2 +- crates/shared/src/abilities/healing.rs | 37 +- crates/shared/src/abilities/missile.rs | 3 +- crates/shared/src/abilities/mod.rs | 21 +- crates/shared/src/abilities/thrown.rs | 3 +- crates/shared/src/backpack/backpack_ui.rs | 338 +----------------- crates/shared/src/backpack/ui_head_state.rs | 4 +- crates/shared/src/cash.rs | 2 +- crates/shared/src/cash_heal.rs | 2 +- crates/shared/src/character.rs | 19 +- .../shared/src/control/controller_flying.rs | 8 +- crates/shared/src/control/controls.rs | 6 +- crates/shared/src/gates.rs | 2 +- crates/shared/src/head_drop.rs | 12 +- crates/shared/src/heads/mod.rs | 7 +- crates/shared/src/hitpoints.rs | 2 +- crates/shared/src/keys.rs | 8 +- crates/shared/src/lib.rs | 2 - crates/shared/src/loading_assets.rs | 19 +- crates/shared/src/npc.rs | 7 +- crates/shared/src/player.rs | 57 +-- crates/shared/src/protocol/channels.rs | 1 + crates/shared/src/protocol/components.rs | 82 +++++ crates/shared/src/protocol/events.rs | 31 ++ crates/shared/src/protocol/messages.rs | 9 + .../src/{protocol.rs => protocol/mod.rs} | 135 ++----- crates/shared/src/tb_entities.rs | 9 +- crates/shared/src/utils/observers.rs | 23 +- crates/shared/src/utils/triggers.rs | 5 +- justfile | 6 + 47 files changed, 992 insertions(+), 721 deletions(-) create mode 100644 crates/client/src/backpack/backpack_ui.rs create mode 100644 crates/client/src/backpack/mod.rs rename crates/{shared => client}/src/heal_effect.rs (53%) create mode 100644 crates/client/src/player.rs rename crates/{shared => client}/src/sounds.rs (85%) create mode 100644 crates/server/src/backpack/backpack_ui.rs create mode 100644 crates/server/src/backpack/mod.rs create mode 100644 crates/server/src/utils.rs create mode 100644 crates/shared/src/protocol/channels.rs create mode 100644 crates/shared/src/protocol/components.rs create mode 100644 crates/shared/src/protocol/events.rs create mode 100644 crates/shared/src/protocol/messages.rs rename crates/shared/src/{protocol.rs => protocol/mod.rs} (58%) diff --git a/Cargo.toml b/Cargo.toml index 7af6516..53a9f49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ bevy = { version = "0.16.0", default-features = false, features = [ "animation", "async_executor", "bevy_asset", - "bevy_audio", "bevy_color", "bevy_core_pipeline", "bevy_gilrs", diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index da5ef21..1a8953e 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -11,6 +11,7 @@ dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui", "shared/dbg"] [dependencies] avian3d = { workspace = true } bevy = { workspace = true, default-features = false, features = [ + "bevy_audio", "bevy_window", "bevy_winit", ] } diff --git a/crates/client/src/backpack/backpack_ui.rs b/crates/client/src/backpack/backpack_ui.rs new file mode 100644 index 0000000..8a31140 --- /dev/null +++ b/crates/client/src/backpack/backpack_ui.rs @@ -0,0 +1,208 @@ +use crate::{GameState, HEDZ_GREEN, heads::HeadsImages, loading_assets::UIAssets}; +use bevy::{ecs::spawn::SpawnIter, prelude::*}; +use shared::backpack::backpack_ui::{ + BackpackCountText, BackpackMarker, BackpackUiState, HEAD_SLOTS, HeadDamage, HeadImage, + HeadSelector, +}; + +pub fn plugin(app: &mut App) { + app.add_systems(OnEnter(GameState::Playing), setup); + app.add_systems( + FixedUpdate, + (update, update_visibility, update_count).run_if(in_state(GameState::Playing)), + ); +} + +fn setup(mut commands: Commands, assets: Res) { + commands.spawn(( + Name::new("backpack-ui"), + BackpackMarker, + Visibility::Hidden, + Node { + position_type: PositionType::Absolute, + top: Val::Px(20.0), + right: Val::Px(20.0), + height: Val::Px(74.0), + ..default() + }, + Children::spawn(SpawnIter((0..HEAD_SLOTS).map({ + let bg = assets.head_bg.clone(); + let regular = assets.head_regular.clone(); + let selector = assets.head_selector.clone(); + let damage = assets.head_damage.clone(); + + move |i| { + spawn_head_ui( + bg.clone(), + regular.clone(), + selector.clone(), + damage.clone(), + i, + ) + } + }))), + )); + + commands.spawn(( + Name::new("backpack-head-count-ui"), + Text::new("0"), + TextShadow::default(), + BackpackCountText, + TextFont { + font: assets.font.clone(), + font_size: 34.0, + ..default() + }, + TextColor(HEDZ_GREEN.into()), + TextLayout::new_with_justify(JustifyText::Center), + Node { + position_type: PositionType::Absolute, + top: Val::Px(20.0), + right: Val::Px(20.0), + ..default() + }, + )); +} + +fn spawn_head_ui( + bg: Handle, + regular: Handle, + selector: Handle, + damage: Handle, + head_slot: usize, +) -> impl Bundle { + const SIZE: f32 = 90.0; + const DAMAGE_SIZE: f32 = 74.0; + + ( + Node { + position_type: PositionType::Relative, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + width: Val::Px(SIZE), + ..default() + }, + children![ + ( + Name::new("selector"), + Node { + position_type: PositionType::Absolute, + bottom: Val::Px(-30.0), + ..default() + }, + Visibility::Hidden, + ImageNode::new(selector).with_flip_y(), + HeadSelector(head_slot), + ), + ( + Name::new("bg"), + Node { + position_type: PositionType::Absolute, + ..default() + }, + ImageNode::new(bg), + ), + ( + Name::new("head"), + Node { + position_type: PositionType::Absolute, + ..default() + }, + ImageNode::default(), + Visibility::Hidden, + HeadImage(head_slot), + ), + ( + Name::new("rings"), + Node { + position_type: PositionType::Absolute, + ..default() + }, + ImageNode::new(regular), + ), + ( + Name::new("health"), + Node { + height: Val::Px(DAMAGE_SIZE), + width: Val::Px(DAMAGE_SIZE), + ..default() + }, + children![( + Name::new("damage_ring"), + HeadDamage(head_slot), + Node { + position_type: PositionType::Absolute, + display: Display::Block, + overflow: Overflow::clip(), + top: Val::Px(0.), + left: Val::Px(0.), + right: Val::Px(0.), + height: Val::Percent(0.), + ..default() + }, + children![ImageNode::new(damage)] + )] + ) + ], + ) +} + +fn update_visibility( + state: Single<&BackpackUiState, Changed>, + mut backpack: Single<&mut Visibility, (With, Without)>, + mut count: Single<&mut Visibility, (Without, With)>, +) { + **backpack = if state.open { + Visibility::Visible + } else { + Visibility::Hidden + }; + + **count = if !state.open { + Visibility::Visible + } else { + Visibility::Hidden + }; +} + +fn update_count( + state: Single<&BackpackUiState, Changed>, + text: Option>>, + mut writer: TextUiWriter, +) { + let Some(text) = text else { + return; + }; + + *writer.text(*text, 0) = state.count.to_string(); +} + +fn update( + state: Single<&BackpackUiState, Changed>, + heads_images: Res, + mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without>, + mut head_damage: Query<(&HeadDamage, &mut Node), Without>, + mut head_selector: Query<(&HeadSelector, &mut Visibility), Without>, +) { + for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() { + if let Some(head) = &state.heads[*head] { + *vis = Visibility::Inherited; + image.image = heads_images.heads[head.head].clone(); + } else { + *vis = Visibility::Hidden; + } + } + for (HeadDamage(head), mut node) in head_damage.iter_mut() { + if let Some(head) = &state.heads[*head] { + node.height = Val::Percent(head.damage() * 100.0); + } + } + + for (HeadSelector(head), mut vis) in head_selector.iter_mut() { + *vis = if *head == state.relative_current_slot() { + Visibility::Inherited + } else { + Visibility::Hidden + }; + } +} diff --git a/crates/client/src/backpack/mod.rs b/crates/client/src/backpack/mod.rs new file mode 100644 index 0000000..87b3199 --- /dev/null +++ b/crates/client/src/backpack/mod.rs @@ -0,0 +1,7 @@ +pub mod backpack_ui; + +use bevy::prelude::*; + +pub fn plugin(app: &mut App) { + app.add_plugins(backpack_ui::plugin); +} diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 0720800..7051569 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -21,11 +21,15 @@ use shared::{ control::ControlState, global_observer, player::Player, - protocol::{DespawnTbMapEntity, TbMapEntityId, TbMapEntityMapping}, + protocol::{ + ClientEnteredPlaying, TbMapEntityId, TbMapEntityMapping, + channels::UnorderedReliableChannel, messages::DespawnTbMapEntity, + }, tb_entities::{Platform, PlatformTarget}, }; use std::{ env::current_exe, + fs::File, io::{BufRead, BufReader}, net::{IpAddr, Ipv4Addr, SocketAddr}, process::Stdio, @@ -43,7 +47,7 @@ pub fn plugin(app: &mut App) { parse_local_server_stdout.run_if(resource_exists::), ); app.add_systems(Last, close_server_processes); - app.add_systems(FixedUpdate, despawn_absent_map_entities); + app.add_systems(Update, despawn_absent_map_entities); global_observer!(app, on_connecting); global_observer!(app, on_connection_failed); @@ -124,9 +128,11 @@ fn on_connection_succeeded( _trigger: Trigger, state: Res>, mut change_state: ResMut>, + mut sender: Single<&mut TriggerSender>, ) { if *state == GameState::Connecting { change_state.set(GameState::Playing); + sender.trigger::(ClientEnteredPlaying); } } @@ -148,7 +154,7 @@ fn on_connection_failed( mut commands: Commands, client_active: Query<&ClientActive>, mut opened_server: Local, -) { +) -> Result { let disconnected = disconnected.get(trigger.target()).unwrap(); if *opened_server { panic!( @@ -164,11 +170,13 @@ fn on_connection_failed( // the server executable is assumed to be adjacent to the client executable let mut exe_path = current_exe().expect("failed to get path of client executable"); exe_path.set_file_name("server"); + let server_log_file = File::create("server.log")?; let mut server_process = std::process::Command::new(exe_path) .args(["--timeout", "60", "--close-on-client-disconnect"]) + .env("NO_COLOR", "1") .stdin(Stdio::null()) .stdout(Stdio::piped()) - .stderr(Stdio::null()) + .stderr(server_log_file) .spawn() .expect("failed to start server"); let server_stdout = server_process.stdout.take().unwrap(); @@ -195,6 +203,8 @@ fn on_connection_failed( *opened_server = true; } + + Ok(()) } #[derive(Event)] @@ -206,6 +216,8 @@ fn parse_local_server_stdout(mut commands: Commands, mut stdout: ResMut>) { - for healing in query.iter() { - cmds.entity(healing.0).insert(( - Name::new("heal-particle-effect"), - HealParticleEffect::default(), - )); +fn on_added( + mut commands: Commands, + query: Query>, + assets: Res, +) { + for entity in query.iter() { + let effects = commands + .spawn(( + Name::new("heal-particle-effect"), + HealParticleEffect::default(), + AudioPlayer::new(assets.healing.clone()), + PlaybackSettings { + mode: bevy::audio::PlaybackMode::Loop, + ..Default::default() + }, + HealingEffectsOf { of: entity }, + )) + .id(); + commands + .entity(entity) + .insert(HasHealingEffects { effects }); } } +fn on_removed( + trigger: Trigger, + mut commands: Commands, + effects: Query<&HasHealingEffects>, +) { + let Ok(has_effects) = effects.get(trigger.target()) else { + return; + }; + commands.entity(has_effects.effects).try_despawn(); + commands + .entity(trigger.target()) + .remove::(); +} + fn update_effects( - mut cmds: Commands, - mut query: Query<(&mut HealParticleEffect, Entity)>, + mut commands: Commands, + mut query: Query<(&mut HealParticleEffect, &HealingEffectsOf, Entity)>, + mut transforms: Query<&mut Transform>, time: Res