Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ bevy_egui = { version = "0.38.1", features = ["immutable_ctx"]}
bevy_diagnostic = "0.17.3"
bevy-inspector-egui = {version = "0.35", features = ["bevy_render"]}
bevy_renet = "3.0.0"
bevy_mod_billboard = "0.7.0"
renet_visualizer = {version="1.1.0", features=["bevy"]}
egui_plot = "0.34.0"

Expand All @@ -41,6 +42,7 @@ clap = { version = "4.5.54", features = ["derive"] }
renet_visualizer = {git = "https://github.com/cb341/renet.git" }
bevy_renet = {git = "https://github.com/cb341/renet.git" }
renet = {git = "https://github.com/cb341/renet.git" }
bevy_mod_billboard = {git = "https://github.com/kisya-games/bevy_mod_billboard.git", branch="bevy-0.17"} # TODO: Remove once https://github.com/kulkalkul/bevy_mod_billboard/pull/36 is merged

[profile.dev.package."*"]
opt-level = 3
Expand Down
6 changes: 3 additions & 3 deletions src/client/chat/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ pub fn add_message_to_chat_container_system(
Node::default(),
Name::new("chat_entry"),
chat_components::ChatMessageElement,
Text::new(event.0.message.clone()),
Text::new(event.0.format_string()),
));
});
}
Expand Down Expand Up @@ -277,7 +277,7 @@ mod tests {

event_writer.write(SingleChatSendEvent(ChatMessage {
message: "Hello World".to_string(),
client_id: 0,
sender: ChatMessageSender::Server,
message_id: 1,
timestamp: 0,
}));
Expand All @@ -292,7 +292,7 @@ mod tests {
assert_eq!(message_count, 1);
assert_eq!(
messages.iter(app.world()).next().unwrap().0 .0,
"Hello World"
"[1970-01-01 00:00:00 UTC] SERVER: Hello World"
);
}

Expand Down
1 change: 1 addition & 0 deletions src/client/gui/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub fn handle_debug_state_transition_system(
) {
if key_input.just_pressed(KeyCode::Tab) {
match *current_state.get() {
GameState::WaitingForServer => {}
GameState::Playing => next_state.set(GameState::Debugging),
GameState::Chatting => next_state.set(GameState::Debugging),
GameState::Debugging => next_state.set(GameState::Playing),
Expand Down
25 changes: 23 additions & 2 deletions src/client/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod states;
mod terrain;

use bevy_flair::FlairPlugin;
use clap::Parser;
use scene::setup_scene;

#[cfg(feature = "wireframe")]
Expand All @@ -32,7 +33,17 @@ mod wireframe_config {
}
}

#[derive(Debug, Parser)]
#[command(version)]
#[command(long_about = None)]
struct Cli {
#[command(flatten)]
networking_args: networking_commands::NetworkingArgs,
}

fn main() {
let cli = Cli::parse();

let window_plugin = WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(2.0),
Expand All @@ -47,6 +58,17 @@ fn main() {
.set(ImagePlugin::default_nearest());

let mut app = App::new();

match networking::NetworkingPlugin::from_args(cli.networking_args) {
Ok(plugin) => {
app.add_plugins(plugin);
}
Err(err) => {
eprintln!("Error: {}", err);
return;
}
}

app.add_plugins((
default_plugins,
FlairPlugin,
Expand All @@ -56,15 +78,14 @@ fn main() {
EntityCountDiagnosticsPlugin::default(),
SystemInformationDiagnosticsPlugin,
gui::GuiPlugin,
networking::NetworkingPlugin,
terrain::TerrainPlugin,
collider::ColliderPlugin,
player::PlayerPlugin,
remote_player::RemotePlayerPlugin,
#[cfg(feature = "chat")]
chat::ChatPlugin,
));
app.insert_state(GameState::Playing);
app.insert_state(GameState::WaitingForServer);

#[cfg(feature = "wireframe")]
app.insert_resource(wireframe_config::wireframe_config());
Expand Down
16 changes: 16 additions & 0 deletions src/client/networking/commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use clap::*;

use crate::prelude::NetworkingPlugin;

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct NetworkingArgs {
#[arg(short, long)]
username: String,
}

impl NetworkingPlugin {
pub fn from_args(args: NetworkingArgs) -> Result<Self, String> {
NetworkingPlugin::new(args.username)
}
}
44 changes: 37 additions & 7 deletions src/client/networking/mod.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
pub mod commands;
pub mod systems;

use crate::connection_config;
use bevy_renet::{
netcode::{ClientAuthentication, NetcodeClientPlugin, NetcodeClientTransport},
netcode::{
ClientAuthentication, NetcodeClientPlugin, NetcodeClientTransport, NetcodeTransportError,
},
RenetClientPlugin,
};

use crate::prelude::*;

const SERVER_ADDR: &str = "127.0.0.1:5000";
const DEFAULT_SERVER_ADDR: &str = "127.0.0.1:5000";

pub struct NetworkingPlugin {
username: Username,
}

impl NetworkingPlugin {
pub fn new(username: String) -> Result<NetworkingPlugin, String> {
Ok(Self {
username: Username::new(&username)?,
})
}
}

pub struct NetworkingPlugin;
impl Plugin for NetworkingPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((RenetClientPlugin, NetcodeClientPlugin));

let client = RenetClient::new(connection_config());
app.insert_resource(client);

let client_id = rand::random::<u64>();
let authentication = ClientAuthentication::Unsecure {
server_addr: SERVER_ADDR.parse().unwrap(),
client_id,
user_data: None,
server_addr: DEFAULT_SERVER_ADDR
.parse()
.expect("Hardcoded server address should be valid"),
client_id: rand::random::<u64>(),
user_data: Some(self.username.to_netcode_user_data()),
protocol_id: 0,
};
let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
Expand All @@ -32,6 +47,21 @@ impl Plugin for NetworkingPlugin {
let transport = NetcodeClientTransport::new(current_time, authentication, socket).unwrap();
app.insert_resource(transport);

app.add_systems(Last, networking_systems::exit_on_last_window_closed_system);
app.add_systems(Update, networking_systems::receive_message_system);

fn exit_on_transport_error(
mut renet_error: MessageReader<NetcodeTransportError>,
mut exit_events: MessageWriter<AppExit>,
) {
if !renet_error.is_empty() {
exit_events.write(AppExit::error());
}
for error in renet_error.read() {
eprintln!("{}", error);
}
}

app.add_systems(Update, exit_on_transport_error);
}
}
34 changes: 28 additions & 6 deletions src/client/networking/systems.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
use crate::prelude::*;

pub fn exit_on_last_window_closed_system(
close_events: MessageReader<WindowCloseRequested>,
windows: Query<(), With<Window>>,
mut client: ResMut<RenetClient>,
mut exit: MessageWriter<AppExit>,
) {
if !close_events.is_empty() && windows.iter().count() <= 1 {
client.disconnect();
exit.write(AppExit::Success);
}
}

#[allow(clippy::too_many_arguments)]
pub fn receive_message_system(
mut commands: Commands,
mut client: ResMut<RenetClient>,
mut player_spawn_events: ResMut<Messages<remote_player_events::RemotePlayerSpawnedEvent>>,
mut player_despawn_events: ResMut<Messages<remote_player_events::RemotePlayerDespawnedEvent>>,
Expand All @@ -15,20 +28,29 @@ pub fn receive_message_system(
Messages<chat_events::SingleChatSendEvent>,
>,
mut spawn_area_loaded: ResMut<terrain_resources::SpawnAreaLoaded>,
mut exit_events: MessageWriter<AppExit>,
mut next_state: ResMut<NextState<GameState>>,
) {
while let Some(message) = client.receive_message(DefaultChannel::ReliableOrdered) {
match bincode::deserialize(&message) {
Ok(message) => match message {
NetworkingMessage::PlayerJoin(event) => {
NetworkingMessage::PlayerReject(reject_reason) => {
eprintln!("Server connection rejected: {reject_reason}");
exit_events.write(AppExit::error());
}
NetworkingMessage::PlayerAccept(player_state) => {
commands.insert_resource(player_resources::LocalPlayerSpawnState(player_state));
next_state.set(GameState::Playing);
}
NetworkingMessage::PlayerJoin(username) => {
player_spawn_events.write(remote_player_events::RemotePlayerSpawnedEvent {
client_id: event,
username,
position: Vec3::ZERO,
});
}
NetworkingMessage::PlayerLeave(event) => {
player_despawn_events.write(remote_player_events::RemotePlayerDespawnedEvent {
client_id: event,
});
NetworkingMessage::PlayerLeave(username) => {
player_despawn_events
.write(remote_player_events::RemotePlayerDespawnedEvent { username });
}
NetworkingMessage::BlockUpdate { position, block } => {
debug!("Client received block update message: {:?}", position);
Expand Down
3 changes: 3 additions & 0 deletions src/client/player/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ impl LastPlayerPosition {
Self(Vec3::ZERO)
}
}

#[derive(Resource)]
pub struct LocalPlayerSpawnState(pub PlayerState);
27 changes: 11 additions & 16 deletions src/client/player/systems/controller.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
use crate::prelude::*;

#[cfg(feature = "skip_terrain")]
const SPAWN_POINT: Vec3 = Vec3::new(0.0, 1.0, 0.0);

#[cfg(all(not(feature = "skip_terrain"), not(feature = "lock_player")))]
const SPAWN_POINT: Vec3 = Vec3::new(0.0, 43.0, 0.0); // TODO: determine terrain height at 0,0

#[cfg(all(not(feature = "skip_terrain"), feature = "lock_player"))]
const SPAWN_POINT: Vec3 = Vec3::new(128.0, 96.0, -128.0);

pub fn setup_player_camera(mut commands: Commands) {
commands.spawn((
Name::new("Player cam?"),
Expand Down Expand Up @@ -38,8 +29,9 @@ pub fn setup_controller_on_area_ready_system(
mut commands: Commands,
mut player_spawned: ResMut<player_resources::PlayerSpawned>,
mut render_player: Query<&mut RenderPlayer>,
spawn_state: Res<player_resources::LocalPlayerSpawnState>,
) {
info!("Setting up controller");
info!("Setting up controller at {:?}", spawn_state.0.position);

let logical_entity = commands
.spawn((
Expand All @@ -62,14 +54,17 @@ pub fn setup_controller_on_area_ready_system(
LockedAxes::ROTATION_LOCKED,
AdditionalMassProperties::Mass(1.0),
GravityScale(0.0),
Ccd { enabled: true }, // Prevent clipping when going fast
Transform::from_translation(SPAWN_POINT),
Ccd { enabled: true },
Transform::from_translation(spawn_state.0.position),
LogicalPlayer,
#[cfg(not(feature = "lock_player"))]
FpsControllerInput {
pitch: -TAU / 20.0,
yaw: TAU * 5.0 / 12.0,
..default()
{
let (yaw, pitch, _roll) = spawn_state.0.rotation.to_euler(EulerRot::YXZ);
FpsControllerInput {
pitch,
yaw,
..default()
}
},
#[cfg(feature = "lock_player")]
FpsControllerInput {
Expand Down
1 change: 1 addition & 0 deletions src/client/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub use crate::collider::components as collider_components;
pub use crate::collider::events as collider_events;
pub use crate::collider::systems as collider_systems;

pub use crate::networking::commands as networking_commands;
pub use crate::networking::systems as networking_systems;
pub use crate::networking::NetworkingPlugin;

Expand Down
2 changes: 1 addition & 1 deletion src/client/remote_player/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::prelude::*;

#[derive(Component)]
pub struct RemotePlayer {
pub client_id: ClientId,
pub username: Username,
}

#[derive(Default, Reflect, GizmoConfigGroup)]
Expand Down
6 changes: 3 additions & 3 deletions src/client/remote_player/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ use crate::prelude::*;

#[derive(Message)]
pub struct RemotePlayerSpawnedEvent {
pub client_id: ClientId,
pub username: Username,
pub position: Vec3,
}

#[derive(Message)]
pub struct RemotePlayerDespawnedEvent {
pub client_id: ClientId,
pub username: Username,
}

#[derive(Message)]
pub struct RemotePlayerSyncEvent {
pub players: HashMap<ClientId, PlayerState>,
pub players: HashMap<Username, PlayerState>,
}
3 changes: 3 additions & 0 deletions src/client/remote_player/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use bevy_mod_billboard::prelude::BillboardPlugin;

pub mod components;
pub mod events;
pub mod systems;
Expand All @@ -8,6 +10,7 @@ pub struct RemotePlayerPlugin;

impl Plugin for RemotePlayerPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(BillboardPlugin);
app.add_message::<events::RemotePlayerSpawnedEvent>();
app.init_gizmo_group::<remote_player_components::RemotePlayerGizmos>();
app.add_message::<events::RemotePlayerDespawnedEvent>();
Expand Down
Loading