7 Commits

Author SHA1 Message Date
87d89c9885 more remakes 2025-12-20 11:38:27 -05:00
3cec532fcd new jet sound 2025-12-19 17:00:04 -05:00
b4db8eea0e Merge branch 'master' into use-remade-sfx
# Conflicts:
#	crates/client/src/main.rs
2025-12-19 16:48:23 -05:00
7086338255 better loops 2025-12-19 16:47:02 -05:00
10702d46b4 lower vol for music 2025-12-08 21:24:06 -05:00
a647edfa13 more sounds 2025-12-08 21:04:18 -05:00
da0d335c09 wip 2025-12-08 21:04:18 -05:00
66 changed files with 86 additions and 256 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ build/steamos/hedz_reloaded
build/steamos/.env
build/macos/src/HEDZReloaded.app/Contents/MacOS
build/macos/src/Applications
server.log

13
Cargo.lock generated
View File

@@ -1519,7 +1519,6 @@ dependencies = [
"bevy_time",
"renet",
"renet_netcode",
"renet_steam",
]
[[package]]
@@ -5565,18 +5564,6 @@ dependencies = [
"renetcode",
]
[[package]]
name = "renet_steam"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f6018afe469d3d2d49fab8fd1cecc46c588ac61498cad879d5781c44b277421"
dependencies = [
"bevy_ecs",
"log",
"renet",
"steamworks",
]
[[package]]
name = "renetcode"
version = "1.0.0"

View File

@@ -61,8 +61,7 @@ bevy_pkv = { version = "0.14", default-features = false, features = [
"redb",
] }
bevy_replicon = "0.37.1"
# TODO: i dont think we need this in dedicated server mode
bevy_replicon_renet = { version = "0.13.0", features = ["renet_steam"] }
bevy_replicon_renet = "0.13.0"
bevy_sprite3d = "7.0.0"
bevy_trenchbroom = { version = "0.10", default-features = false, features = [
"physics-integration",
@@ -77,7 +76,6 @@ rand = "=0.8.5"
ron = "0.8"
serde = { version = "1.0.219", features = ["derive"] }
shared = { path = "crates/shared" }
# TODO: i dont think we need this in dedicated server mode
steamworks = "0.12"
[profile.dev.package."*"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -20,7 +20,7 @@ client = [
"bevy_replicon_renet/client",
"bevy_trenchbroom/client",
]
dbg = ["avian3d/debug-plugin", "bevy/debug", "dep:bevy-inspector-egui"]
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui"]
[dependencies]
avian3d = { workspace = true }

View File

@@ -1,6 +1,6 @@
use crate::{
GameState,
config::NetConfig,
config::NetworkingConfig,
protocol::{
ClientEnteredPlaying, TbMapEntityId, TbMapEntityMapping, messages::DespawnTbMapEntity,
},
@@ -17,10 +17,14 @@ use bevy_replicon::{
};
use bevy_replicon_renet::{
RenetChannelsExt,
netcode::{ClientAuthentication, NetcodeClientTransport, NetcodeError},
renet::{ConnectionConfig, RenetClient},
};
use bevy_steamworks::Client;
use bevy_trenchbroom::geometry::Brushes;
use std::{
net::{Ipv4Addr, UdpSocket},
time::SystemTime,
};
pub mod audio;
pub mod backpack;
@@ -51,7 +55,7 @@ pub fn plugin(app: &mut App) {
app.add_systems(
OnEnter(GameState::Connecting),
connect_to_server.run_if(|config: Res<NetConfig>| config.is_client()),
connect_to_server.run_if(|config: Res<NetworkingConfig>| config.server.is_some()),
);
app.add_systems(Update, despawn_absent_map_entities);
app.add_systems(
@@ -85,9 +89,8 @@ fn on_disconnect() {
fn connect_to_server(
mut commands: Commands,
config: Res<NetConfig>,
config: Res<NetworkingConfig>,
channels: Res<RepliconChannels>,
steam_client: Option<Res<Client>>,
) -> Result {
let server_channels_config = channels.server_configs();
let client_channels_config = channels.client_configs();
@@ -99,45 +102,30 @@ fn connect_to_server(
});
commands.insert_resource(client);
commands.insert_resource(client_transport(&config)?);
if let NetConfig::SteamClient(host_steam_id) = &*config {
let Some(steam_client) = steam_client else {
return Err("Steam client not found".into());
};
info!("connecting to steam host: {host_steam_id:?}");
let transport = bevy_replicon_renet::steam::SteamClientTransport::new(
(**steam_client).clone(),
host_steam_id,
)?;
commands.insert_resource(transport);
} else if let NetConfig::NetcodeClient(host_addr) = &*config {
use std::time::SystemTime;
info!("connecting to netcode host: {host_addr:?}");
Ok(())
}
fn client_transport(config: &NetworkingConfig) -> Result<NetcodeClientTransport, NetcodeError> {
let current_time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let client_id = current_time.as_millis() as u64;
let socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))?;
let authentication = bevy_replicon_renet::netcode::ClientAuthentication::Unsecure {
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
let server_addr = config
.server
.flatten()
.unwrap_or_else(|| "127.0.0.1:31111".parse().unwrap());
let authentication = ClientAuthentication::Unsecure {
client_id,
protocol_id: 0,
server_addr: host_addr.clone(),
server_addr,
user_data: None,
};
let transport = bevy_replicon_renet::netcode::NetcodeClientTransport::new(
current_time,
authentication,
socket,
)?;
commands.insert_resource(transport);
}
Ok(())
info!("attempting connection to {server_addr}");
NetcodeClientTransport::new(current_time, authentication, socket)
}
#[allow(clippy::type_complexity)]

View File

@@ -4,11 +4,10 @@ use bevy_pkv::prelude::*;
use crate::{client::audio::SoundSettings, utils::Debounce};
pub fn plugin(app: &mut App) {
#[cfg(not(feature = "dbg"))]
app.insert_resource(PkvStore::new("Rustunit", "HEDZ"));
app.add_systems(Update, persist_settings.run_if(resource_exists::<PkvStore>));
app.add_systems(Startup, load_settings.run_if(resource_exists::<PkvStore>));
app.add_systems(Update, persist_settings);
app.add_systems(Startup, load_settings);
}
fn persist_settings(

View File

@@ -61,14 +61,6 @@ fn test_steam_system(steam_client: Res<Client>) {
},
);
let id = steam_client.user().steam_id();
info!("Steam ID: {:?}", id);
steam_client
.friends()
.set_rich_presence("connect", Some(id.raw().to_string().as_str()));
for friend in steam_client.friends().get_friends(FriendFlags::IMMEDIATE) {
info!(
"Steam Friend: {:?} - {}({:?})",

View File

@@ -1,92 +1,25 @@
use std::net::SocketAddr;
use bevy::prelude::*;
use clap::Parser;
use steamworks::SteamId;
use std::net::SocketAddr;
pub fn plugin(app: &mut App) {
let config = NetworkingConfig::parse();
let config: NetConfig = config.into();
info!("net config: {:?}", config);
app.insert_resource(config);
}
#[derive(Parser, Debug)]
#[derive(Resource, Parser, Debug)]
#[command(version, about, long_about = None)]
struct NetworkingConfig {
/// Steam id of the host to connect to
pub struct NetworkingConfig {
/// The IP/port to connect to.
/// If `None`, host a local server.
/// If Some(None), connect to the default server (`127.0.0.1:31111`)
/// Otherwise, connect to the given server.
/// Does nothing on the server.
#[arg(long)]
pub steam_host_id: Option<String>,
/// Act as steam host
pub server: Option<Option<SocketAddr>>,
/// Whether or not to open a port when opening the client, for other clients
/// to connect. Does nothing if `server` is set.
#[arg(long)]
pub steam_host: bool,
/// Act as host using netcode, so we have to define our port
#[arg(long)]
pub netcode_host: Option<Option<u16>>,
/// Host address we connect to as a client
#[arg(long)]
pub netcode_client: Option<Option<String>>,
}
#[derive(Resource, Debug)]
pub enum NetConfig {
Singleplayer,
SteamHost,
NetcodeHost { port: u16 },
SteamClient(SteamId),
NetcodeClient(SocketAddr),
}
impl NetConfig {
pub fn is_client(&self) -> bool {
matches!(
self,
NetConfig::SteamClient(_) | NetConfig::NetcodeClient(_)
)
}
pub fn is_host(&self) -> bool {
matches!(self, NetConfig::SteamHost | NetConfig::NetcodeHost { .. })
}
pub fn is_singleplayer(&self) -> bool {
!self.is_client() && !self.is_host()
}
}
impl From<NetworkingConfig> for NetConfig {
fn from(config: NetworkingConfig) -> Self {
match (
config.steam_host,
config.steam_host_id,
config.netcode_host,
config.netcode_client,
) {
(false, None, None, None) => Self::Singleplayer,
(true, None, None, None) => Self::SteamHost,
(false, Some(id), None, None) => Self::SteamClient(parse_steam_id(id)),
(false, None, Some(port), None) => Self::NetcodeHost {
port: port.unwrap_or(31111),
},
(false, None, None, Some(addr)) => Self::NetcodeClient(parse_addr(addr)),
_ => panic!("Invalid configuration"),
}
}
}
fn parse_addr(addr: Option<String>) -> SocketAddr {
addr.map(|addr| addr.parse().ok())
.flatten()
.unwrap_or_else(|| "127.0.0.1:31111".parse().unwrap())
}
fn parse_steam_id(id: String) -> SteamId {
let id: u64 = id.parse().unwrap();
SteamId::from_raw(id)
pub host: bool,
}

View File

@@ -34,9 +34,9 @@ pub mod utils;
pub mod water;
use crate::{
config::NetConfig,
config::NetworkingConfig,
heads_database::{HeadDatabaseAsset, HeadsDatabase},
protocol::{PlayerIdCounter, messages::AssignClientPlayer},
protocol::{PlayerId, messages::AssignClientPlayer},
tb_entities::SpawnPoint,
};
use avian3d::{PhysicsPlugins, prelude::TransformInterpolation};
@@ -47,7 +47,6 @@ use bevy_common_assets::ron::RonAssetPlugin;
use bevy_replicon::{RepliconPlugins, prelude::ClientId};
use bevy_replicon_renet::RepliconRenetPlugins;
use bevy_sprite3d::Sprite3dPlugin;
use bevy_steamworks::SteamworksEvent;
use bevy_trenchbroom::{
TrenchBroomPlugins, config::TrenchBroomConfig, prelude::TrenchBroomPhysicsPlugin,
};
@@ -149,21 +148,21 @@ pub fn plugin(app: &mut App) {
if cfg!(feature = "client") {
app.add_systems(
OnEnter(GameState::Waiting),
start_solo_client.run_if(|config: Res<NetConfig>| config.is_singleplayer()),
start_solo_client
.run_if(|config: Res<NetworkingConfig>| config.server.is_none() && !config.host),
);
app.add_systems(
OnEnter(GameState::Waiting),
start_listen_server.run_if(|config: Res<NetConfig>| config.is_host()),
start_listen_server
.run_if(|config: Res<NetworkingConfig>| config.server.is_none() && config.host),
);
app.add_systems(
OnEnter(GameState::Waiting),
start_client.run_if(|config: Res<NetConfig>| config.is_client()),
start_client.run_if(|config: Res<NetworkingConfig>| config.server.is_some()),
);
} else {
app.add_systems(OnEnter(GameState::Waiting), start_dedicated_server);
}
app.add_systems(Update, log_steam_events);
}
#[derive(Resource, Reflect, Debug)]
@@ -193,33 +192,18 @@ pub enum GameState {
Playing,
}
fn log_steam_events(events: Option<MessageReader<SteamworksEvent>>) {
let Some(mut events) = events else {
return;
};
for event in events.read() {
let SteamworksEvent::CallbackResult(result) = event;
info!("steam: {:?}", result);
}
}
fn start_solo_client(
commands: Commands,
mut next: ResMut<NextState<GameState>>,
query: Query<&Transform, With<SpawnPoint>>,
heads_db: Res<HeadsDatabase>,
mut assign_player_id: MessageWriter<AssignClientPlayer>,
mut ids: ResMut<PlayerIdCounter>,
) {
next.set(GameState::Playing);
ids.reset();
let id = ids.alloc();
player::spawn(commands, ClientId::Server, query, heads_db);
player::spawn(commands, ClientId::Server, id, query, heads_db);
assign_player_id.write(AssignClientPlayer(id));
assign_player_id.write(AssignClientPlayer(PlayerId { id: 0 }));
}
fn start_listen_server(
@@ -228,16 +212,12 @@ fn start_listen_server(
query: Query<&Transform, With<SpawnPoint>>,
heads_db: Res<HeadsDatabase>,
mut assign_player_id: MessageWriter<AssignClientPlayer>,
mut ids: ResMut<PlayerIdCounter>,
) {
next.set(GameState::Hosting);
ids.reset();
let id = ids.alloc();
player::spawn(commands, ClientId::Server, query, heads_db);
player::spawn(commands, ClientId::Server, id, query, heads_db);
assign_player_id.write(AssignClientPlayer(id));
assign_player_id.write(AssignClientPlayer(PlayerId { id: 0 }));
}
fn start_client(mut next: ResMut<NextState<GameState>>) {

View File

@@ -65,7 +65,6 @@ pub fn plugin(app: &mut App) {
pub fn spawn(
mut commands: Commands,
owner: ClientId,
id: PlayerId,
query: Query<&Transform, With<SpawnPoint>>,
heads_db: Res<HeadsDatabase>,
) -> Option<Entity> {
@@ -92,7 +91,7 @@ pub fn spawn(
transform,
Visibility::default(),
PlayerCharacterController,
id,
PlayerId { id: 0 },
),
Backpack::default(),
BackpackUiState::default(),

View File

@@ -76,23 +76,6 @@ impl From<NetworkedCollider> for Collider {
}
}
#[derive(Resource, Default)]
pub struct PlayerIdCounter {
next: u8,
}
impl PlayerIdCounter {
pub fn reset(&mut self) {
self.next = 0;
}
pub fn alloc(&mut self) -> PlayerId {
let id = PlayerId { id: self.next };
self.next += 1;
id
}
}
/// An ID, unique per player, inserted on the character controller. The `PlayerIdMap` maintains a mapping of ID -> controller entity
/// on the server
#[derive(Clone, Copy, Component, Hash, Reflect, Serialize, Deserialize, PartialEq, Eq)]

View File

@@ -74,7 +74,6 @@ pub fn plugin(app: &mut App) {
app.register_type::<TbMapIdCounter>();
app.register_type::<TbMapEntityMapping>();
app.init_resource::<PlayerIdCounter>();
app.init_resource::<PlayerIdMap>();
app.init_resource::<TbMapIdCounter>();
app.init_resource::<TbMapEntityMapping>();

View File

@@ -1,10 +1,8 @@
use crate::{
GameState,
config::NetConfig,
global_observer,
GameState, global_observer,
heads_database::HeadsDatabase,
player::ClientPlayerId,
protocol::{ClientEnteredPlaying, PlayerIdCounter, SetGameTick, messages::AssignClientPlayer},
protocol::{ClientEnteredPlaying, PlayerId, SetGameTick, messages::AssignClientPlayer},
tb_entities::SpawnPoint,
tick::GameTick,
};
@@ -18,8 +16,12 @@ use bevy_replicon::{
};
use bevy_replicon_renet::{
RenetChannelsExt,
netcode::{NetcodeServerTransport, ServerAuthentication},
renet::{ConnectionConfig, RenetServer},
steam::SteamServerTransport,
};
use std::{
net::{Ipv4Addr, UdpSocket},
time::SystemTime,
};
pub fn plugin(app: &mut App) {
@@ -36,14 +38,12 @@ pub fn plugin(app: &mut App) {
fn on_client_playing(
trigger: On<FromClient<ClientEnteredPlaying>>,
commands: Commands,
clients: Query<&ClientPlayerId>,
query: Query<&Transform, With<SpawnPoint>>,
heads_db: Res<HeadsDatabase>,
) -> Result {
info!("client has entered playing gamestate");
let id = clients.get(trigger.client_id.entity().unwrap()).unwrap();
crate::player::spawn(commands, trigger.client_id, id.0, query, heads_db)
crate::player::spawn(commands, trigger.client_id, query, heads_db)
.ok_or("failed to spawn player")?;
Ok(())
@@ -57,8 +57,6 @@ fn open_renet_server(
mut commands: Commands,
channels: Res<RepliconChannels>,
mut next: ResMut<NextState<GameState>>,
steam_client: Option<Res<bevy_steamworks::Client>>,
config: Res<NetConfig>,
) -> Result<(), BevyError> {
info!("opening server");
@@ -71,48 +69,22 @@ fn open_renet_server(
..Default::default()
});
if let NetConfig::SteamHost = *config {
let Some(steam_client) = steam_client else {
return Err("Steam client not found".into());
};
let steam_config = bevy_replicon_renet::steam::SteamServerConfig {
access_permission: bevy_replicon_renet::steam::AccessPermission::FriendsOnly,
max_clients: 16,
};
let client = (**steam_client).clone();
let transport = SteamServerTransport::new(client, steam_config)?;
commands.queue(|w: &mut World| {
w.insert_resource(server);
w.insert_non_send_resource(transport);
});
info!("hosting server: steam");
} else if let NetConfig::NetcodeHost { port } = *config {
use std::time::SystemTime;
let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
let socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, port))?;
let port = 31111;
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, port))?;
let server_config = bevy_replicon_renet::netcode::ServerConfig {
current_time,
max_clients: 8,
max_clients: 1,
protocol_id: 0,
authentication: bevy_replicon_renet::netcode::ServerAuthentication::Unsecure,
authentication: ServerAuthentication::Unsecure,
public_addresses: Default::default(),
};
let transport =
bevy_replicon_renet::netcode::NetcodeServerTransport::new(server_config, socket)?;
let transport = NetcodeServerTransport::new(server_config, socket)?;
commands.insert_resource(server);
commands.insert_resource(transport);
info!("hosting server: netcode on port {port}");
} else {
return Err("Invalid configuration, choose either steam or netcode".into());
}
info!("hosting a server on port {port}");
next.set(GameState::Playing);
@@ -128,17 +100,16 @@ fn on_connected(
game_tick: Res<GameTick>,
mut commands: Commands,
mut assign_id: MessageWriter<ToClients<AssignClientPlayer>>,
mut ids: ResMut<PlayerIdCounter>,
) {
let client = trigger.event_target();
info!("{client} connected to server!");
let id = ids.alloc();
commands.entity(client).insert(ClientPlayerId(id));
let id = ClientPlayerId(PlayerId { id: 0 });
commands.entity(client).insert(id);
assign_id.write(ToClients {
mode: SendMode::Direct(ClientId::Client(trigger.entity)),
message: AssignClientPlayer(id),
message: AssignClientPlayer(id.0),
});
commands.server_trigger(ToClients {

View File

@@ -13,7 +13,7 @@ run *args:
RUST_BACKTRACE=1 cargo r {{ client_args }} -- {{ args }}
server:
RUST_BACKTRACE=1 cargo r {{ server_args }} -- --netcode-host
RUST_BACKTRACE=1 cargo r {{ server_args }}
dbg *args:
RUST_BACKTRACE=1 cargo r {{ client_args }} --features dbg -- {{ args }}
@@ -22,7 +22,7 @@ dbg-server:
RUST_BACKTRACE=1 cargo r {{ server_args }} --features dbg
sort:
cargo sort --workspace
cargo sort --check --workspace
check:
cargo sort --check --workspace