M .cargo/config.toml => .cargo/config.toml +2 -2
@@ 5,8 5,8 @@ xtask = "run --release --package xtask --"
#rustc-wrapper = "/usr/bin/sccache"
[target.x86_64-unknown-linux-gnu]
-#linker = "mold"
-rustflags = ["-C", "link-arg=-fuse-ld=mold"]
+linker = "clang"
+rustflags = ["-C", "link-arg=-fuse-ld=mold", "-Zshare-generics=y"]
[target.wasm32-unknown-unknown]
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']
M crates/unified/Cargo.toml => crates/unified/Cargo.toml +2 -2
@@ 4,8 4,8 @@ description = "A game about floating through space"
edition = "2024"
version = "0.1.0"
-[lib]
-crate-type = ["cdylib", "rlib"]
+#[lib]
+#crate-type = ["cdylib", "rlib"]
[dependencies]
bevy = { version = "0.17", default-features = false, features = [
M crates/unified/src/attachment.rs => crates/unified/src/attachment.rs +1 -1
@@ 33,7 33,7 @@ pub struct Peer {
#[derive(Component, Serialize, Deserialize, MapEntities)]
#[relationship(relationship_target = Joints)]
pub struct JointOf(#[entities] pub Entity);
-#[derive(Component, Serialize, Deserialize, MapEntities, Debug)]
+#[derive(Component, Serialize, Deserialize, MapEntities, Debug, Clone)]
#[relationship_target(relationship = JointOf)]
pub struct Joints(#[entities] Vec<Entity>);
impl Deref for Joints {
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +7 -4
@@ 26,7 26,7 @@ use bevy::window::PrimaryWindow;
use planet::incoming_planets::incoming_planets_plugin;
pub struct ClientPlugin {
- pub server: String,
+ pub server: Option<String>,
}
impl Plugin for ClientPlugin {
fn build(&self, app: &mut App) {
@@ 34,15 34,13 @@ impl Plugin for ClientPlugin {
app
.insert_resource(CursorWorldCoordinates(None))
.add_systems(Startup, move |mut commands: Commands| {
+ let Some(server) = server.as_ref() else { return };
let config = net::websocket_config();
commands
.spawn(Name::new("default-session"))
.queue(WebSocketClient::connect(config, server.clone()));
})
- .add_observer(net::on_connecting)
- .add_observer(net::on_connected)
- .add_observer(net::on_disconnected)
.add_systems(Startup, setup_graphics)
.add_systems(Update, update_cursor_position)
.add_systems(Update, follow_camera)
@@ 55,6 53,11 @@ impl Plugin for ClientPlugin {
.add_plugins(ui_plugin)
.add_plugins(zoom_plugin)
.insert_resource(DebugPickingMode::Disabled);
+ if self.server.is_some() {
+ app.add_observer(net::on_connecting)
+ .add_observer(net::on_connected)
+ .add_observer(net::on_disconnected);
+ }
}
}
M crates/unified/src/client_plugins.rs => crates/unified/src/client_plugins.rs +2 -5
@@ 11,22 11,19 @@ use bevy::ui::UiPlugin;
use bevy_replicon::RepliconPlugins;
pub struct ClientPluginGroup {
- pub server: String,
+ pub server: Option<String>,
}
impl PluginGroup for ClientPluginGroup {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
- .add_group(DefaultPlugins.build().disable::<LogPlugin>())
- .add_group(RepliconPlugins)
.add(WebSocketClientPlugin)
.add(AeronetRepliconClientPlugin)
.add(MeshPickingPlugin)
.add(DebugPickingPlugin)
- .add(UiPlugin)
.add(ClientPlugin {
server: self.server,
})
-
+ .add(UiPlugin)
//.add(PhysicsDebugPlugin) -- debug rendering
//.add(FpsOverlayPlugin::default())
//.add(EguiPlugin::default())
D crates/unified/src/lib.rs => crates/unified/src/lib.rs +0 -43
@@ 1,43 0,0 @@
-#![warn(clippy::pedantic)] // Be annoying, and disable specific irritating lints if needed
-#![deny(
- clippy::allow_attributes_without_reason,
- clippy::assertions_on_result_states
-)]
-#![warn(clippy::if_then_some_else_none)]
-#![allow(clippy::type_complexity, reason = "Bevy makes this a nightmare")]
-#![allow(clippy::needless_pass_by_value, reason = "Bevy makes this a nightmare")]
-#![allow(
- clippy::cast_precision_loss,
- clippy::cast_possible_truncation,
- clippy::cast_sign_loss,
- reason = "We cast ints to floats a lot"
-)]
-#![allow(clippy::missing_panics_doc, reason = "Gamedev! We panic a lot")]
-#![allow(clippy::too_many_arguments, reason = "Le Bevy:tm:")]
-#![allow(
- clippy::too_many_lines,
- reason = "With the three of us, this is impossible"
-)]
-
-//! Primary entrypoint for the lib... mostly useful for wasm
-#[cfg(target_arch = "wasm32")]
-pub mod wasm_entrypoint;
-#[cfg(target_arch = "wasm32")]
-pub use wasm_entrypoint::*;
-
-pub mod attachment;
-pub mod client;
-pub mod client_plugins;
-pub mod config;
-pub mod ecs;
-#[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))]
-pub mod particle_editor;
-pub mod particles;
-#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
-pub mod server;
-#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
-pub mod server_plugins;
-pub mod shared_plugins;
-
-pub mod physics;
-pub mod prelude;>
\ No newline at end of file
M crates/unified/src/main.rs => crates/unified/src/main.rs +97 -13
@@ 1,16 1,65 @@
-use bevy::log::tracing_subscriber;
-use starkingdoms::prelude::*;
+#![warn(clippy::pedantic)] // Be annoying, and disable specific irritating lints if needed
+#![deny(
+ clippy::allow_attributes_without_reason,
+ clippy::assertions_on_result_states
+)]
+#![warn(clippy::if_then_some_else_none)]
+#![allow(clippy::type_complexity, reason = "Bevy makes this a nightmare")]
+#![allow(clippy::needless_pass_by_value, reason = "Bevy makes this a nightmare")]
+#![allow(
+ clippy::cast_precision_loss,
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "We cast ints to floats a lot"
+)]
+#![allow(clippy::missing_panics_doc, reason = "Gamedev! We panic a lot")]
+#![allow(clippy::too_many_arguments, reason = "Le Bevy:tm:")]
+#![allow(
+ clippy::too_many_lines,
+ reason = "With the three of us, this is impossible"
+)]
+
+pub mod attachment;
+pub mod client;
+pub mod client_plugins;
+pub mod config;
+pub mod ecs;
+#[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))]
+pub mod particle_editor;
+pub mod particles;
+#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
+pub mod server;
+#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
+pub mod server_plugins;
+pub mod shared_plugins;
+
+pub mod physics;
+pub mod prelude;
+
+#[cfg(target_arch = "wasm32")]
+pub mod wasm_entrypoint;
+#[cfg(target_arch = "wasm32")]
+pub use wasm_entrypoint::*;
+
+use bevy::log::{tracing_subscriber, LogPlugin};
+use crate::prelude::*;
use clap::Parser;
-use starkingdoms::client_plugins::ClientPluginGroup;
+use crate::client_plugins::ClientPluginGroup;
#[cfg(not(target_arch = "wasm32"))]
-use starkingdoms::server_plugins::ServerPluginGroup;
-use starkingdoms::shared_plugins::SharedPluginGroup;
+use crate::server_plugins::ServerPluginGroup;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::process::exit;
use std::str::FromStr;
+use std::time::Duration;
+use bevy::app::ScheduleRunnerPlugin;
+use bevy::diagnostic::FrameCountPlugin;
+use bevy::state::app::StatesPlugin;
+use bevy::time::TimePlugin;
+use bevy::ui::UiPlugin;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::filter::Directive;
use tracing_subscriber::util::SubscriberInitExt;
+use crate::shared_plugins::SharedPluginGroup;
#[derive(Parser, Debug, Clone)]
#[command(version, about)]
@@ 48,7 97,12 @@ fn run(cli: Cli) -> AppExit {
warn!("This can result in segfaults and inconsistent behavior! If there is weirdness, try disabling it.");
warn!("-+-+-+-+-+-+- Starting with hotpatching enabled -+-+-+-+-+-+-");
}
- app.add_plugins(ClientPluginGroup { server });
+ app.add_plugins(
+ DefaultPlugins.build()
+ .disable::<LogPlugin>()
+ .disable::<UiPlugin>()
+ );
+ app.add_plugins(ClientPluginGroup { server: Some(server) });
app.add_plugins(SharedPluginGroup);
}
#[cfg(not(target_arch = "wasm32"))]
@@ 57,7 111,7 @@ fn run(cli: Cli) -> AppExit {
tick_rate,
max_clients,
hotpatching_enabled,
- ..
+ with_client
} => {
if hotpatching_enabled {
warn!("-+-+-+-+-+-+- Starting with hotpatching enabled -+-+-+-+-+-+-");
@@ 69,16 123,45 @@ fn run(cli: Cli) -> AppExit {
exit(1);
}
- app.add_plugins(ServerPluginGroup {
+
+ if with_client {
+ app.add_plugins(
+ DefaultPlugins.build()
+ .disable::<LogPlugin>()
+ .disable::<UiPlugin>()
+ );
+ app.add_plugins(|app: &mut App| {
+ app.add_systems(Startup, crate::server::player::join::ls_magically_invent_player);
+ });
+ } else {
+ app
+ .add_plugins(AssetPlugin::default())
+ .add_plugins(StatesPlugin)
+ .add_plugins(TaskPoolPlugin::default())
+ .add_plugins(FrameCountPlugin)
+ .add_plugins(TimePlugin)
+ .add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f32(
+ 1.0 / tick_rate,
+ )));
+ }
+
+ app.add_plugins(SharedPluginGroup);
+
+ let mut pg = ServerPluginGroup {
bind,
tick_rate,
max_clients,
- });
- app.add_plugins(SharedPluginGroup);
+ }.build();
+ if with_client {
+ pg = pg.add_group(ClientPluginGroup {
+ server: None
+ });
+ }
+ app.add_plugins(pg);
}
#[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))]
Cli::ParticleEditor {} => {
- app.add_plugins(starkingdoms::particle_editor::particle_editor_plugin);
+ app.add_plugins(crate::particle_editor::particle_editor_plugin);
}
}
@@ 104,6 187,8 @@ fn main() -> AppExit {
Cli::Client { .. } => { run(cli) },
Cli::ParticleEditor { .. } => { run(cli) },
Cli::Server { with_client, bind, hotpatching_enabled, .. } => {
+ run(cli)
+ /*
if !with_client {
run(cli)
} else {
@@ 162,8 247,7 @@ fn main() -> AppExit {
AppExit::Error(c) => AppExit::Error(c),
_ => AppExit::Success
}
- }
- }
+ }*/
}
}
}
M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +177 -64
@@ 1,3 1,5 @@
+pub mod join;
+
use crate::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::config::planet::Planet;
use crate::ecs::{DragRequestEvent, Part, Player, PlayerStorage, PlayerThrust, ThrustEvent};
@@ 8,20 10,168 @@ use crate::server::{ConnectedGameEntity, ConnectedNetworkEntity};
use crate::prelude::*;
use bevy_replicon::prelude::{ClientId, FromClient};
use std::f32::consts::PI;
+use crate::config::world::GlobalWorldConfig;
pub fn player_management_plugin(app: &mut App) {
app.add_systems(
Update,
(
- handle_new_players,
+ join::handle_pending_players,
+ join::handle_new_players,
player_thrust,
magic_fuel_regen,
dragging,
)
.in_set(PlayerInputSet),
);
+ app.add_systems(Update, complete_partial_disconnects);
+}
+
+#[derive(Component)]
+struct PartiallyDisconnected;
+
+/// Partial Disconnects are created when a part is disconnected to indicate that a disconnect has started.
+/// Disconnects cannot be completed until the next tick, after relevant Peer relationships have been removed.
+/// This system does this by performing a flood search to determine if the 'partial-disconnected' parts still have
+/// a valid path to hearty; if they do not, they are removed from the ship.
+fn complete_partial_disconnects(
+ partial_disconnected_parts: Query<Entity, With<PartiallyDisconnected>>,
+ mut q_joints: Query<&Joints>,
+ mut q_maybe_peer: Query<Option<&Peer>>,
+ mut q_joint_of_part: Query<&JointOf>,
+ mut q_is_hearty: Query<Option<&Player>>,
+ mut dc_q_joints: Query<(&Joint, &JointOf, &Transform, Option<&Peer>, Entity)>,
+ mut dc_q_only_joints: Query<&Joints>,
+
+ mut commands: Commands,
+) {
+ for partially_disconnected_part in &partial_disconnected_parts {
+
+ trace!(?partially_disconnected_part, "completing partial disconnect from previous tick");
+ commands.entity(partially_disconnected_part).remove::<PartiallyDisconnected>();
+
+ // is it still connected to hearty?
+ let mut search_visited_joints = vec![];
+ let can_reach_hearty = can_reach_hearty(
+ partially_disconnected_part,
+ q_joints.reborrow(),
+ q_maybe_peer.reborrow(),
+ q_joint_of_part.reborrow(),
+ q_is_hearty.reborrow(),
+ &mut search_visited_joints,
+ true
+ );
+ if can_reach_hearty {
+ // great, leave them alone
+ continue;
+ }
+ // this part cannot reach hearty
+ // trigger a disconnect on them to propagate the disconnection
+
+ let mut disconnect_queue = vec![];
+ disconnect_part(
+ (partially_disconnected_part, match q_joints.get(partially_disconnected_part) {
+ Ok(j) => j,
+ Err(e) => {
+ warn!(?partially_disconnected_part, "part does not have a Joints? this should be impossible...");
+ continue;
+ }
+ }),
+ &dc_q_joints,
+ &dc_q_only_joints,
+ &mut disconnect_queue,
+ commands.reborrow()
+ );
+ commands.entity(partially_disconnected_part).remove::<PartiallyDisconnected>();
+ }
}
+/// Determine if a part has a path to hearty by performing a depth-first search
+/// TODO: This can be very slow on large ships- the path propagation model will be significantly faster
+/// TODO: Ask core for an explanation of what the path propagation model is if you want to implement this
+fn can_reach_hearty(
+ part: Entity,
+
+ mut q_joints: Query<&Joints>,
+ mut q_maybe_peer: Query<Option<&Peer>>,
+ mut q_joint_of_part: Query<&JointOf>,
+ mut q_is_hearty: Query<Option<&Player>>,
+
+ mut visited_joints: &mut Vec<Entity>,
+
+ is_top_of_recursion: bool
+) -> bool {
+ // Get the joints of this entity
+ let Ok(our_joints) = q_joints.get(part).cloned() else {
+ warn!("part does not have a Joints? this should be impossible...");
+ return false;
+ };
+
+ // Iterate over each joint:
+ // if it's Hearty: great! we're connected
+ // if it's another part: recurse to it
+ // if it's not connected: lame, move on
+ 'to_next_joint: for joint in &**our_joints {
+ // mark that we've visited this joint, so we don't come back to it later
+ visited_joints.push(*joint);
+
+ // does this joint have a peer?
+ let maybe_peer = q_maybe_peer.get(*joint).expect("cannot fail");
+
+ if let Some(peer_info) = maybe_peer {
+ // we have a peer! figure out what it's connected to...
+ let other_parts_joint = peer_info.peer_joint_entity_id;
+ // have we visited this joint already?
+ if visited_joints.contains(&other_parts_joint) {
+ // if so, move on
+ continue 'to_next_joint;
+ }
+ // we have not, find it's parent part
+ let other_part = q_joint_of_part.get(other_parts_joint).expect("joint is missing JointOf").0;
+ // is this part Hearty?
+ let maybe_is_hearty = q_is_hearty.get(other_part).expect("cannot fail");
+ if maybe_is_hearty.is_some() {
+ // yay! found hearty
+ debug!("partial detach DFS: visited {} joints => found hearty @ {:?}", visited_joints.len(), other_part);
+ debug!("-> via {:?}", part);
+ return true;
+ } else {
+ // not hearty. but can the other part reach hearty?
+ let can_other_part_reach = can_reach_hearty(
+ other_part,
+ q_joints.reborrow(),
+ q_maybe_peer.reborrow(),
+ q_joint_of_part.reborrow(),
+ q_is_hearty.reborrow(),
+ visited_joints,
+ false
+ );
+ if can_other_part_reach {
+ // great, they are connected
+ // log that we're in the path, then bubble up
+ debug!("-> via {:?}", part);
+ return true;
+ } else {
+ // lame. continue to next part
+ continue 'to_next_joint;
+ }
+ }
+ } else {
+ // we do not have a peer. move on
+ continue 'to_next_joint;
+ }
+ }
+
+ // Exhausted all options; we are not connected to hearty bubble up
+ if is_top_of_recursion {
+ // print a debug message
+ debug!("partial detach DFS: visited {} joints => not connected", visited_joints.len());
+ }
+ false
+}
+
+
+
fn disconnect_part(
(entity, joints): (Entity, &Joints),
q_joints: &Query<(&Joint, &JointOf, &Transform, Option<&Peer>, Entity)>,
@@ 54,18 204,21 @@ fn disconnect_part(
};
let Some(other_peer) = other_peer else { continue; };
commands.entity(peer.peer_joint_entity_id).remove::<Peer>();
- processed_peers.push(peer.peer_joint_entity_id);
+
let Ok(other_joints) = q_only_joints.get(other_joint_of.0) else {
continue
};
+ commands.entity(other_joint_of.0).remove::<PartInShip>();
- if other_joint_of.0 != entity {
- disconnect_part((peer.peer_joint_entity_id, joints), q_joints,
+ if !processed_peers.contains(&peer.peer_joint_entity_id) {
+ disconnect_part((other_joint_of.0, joints), q_joints,
q_only_joints, processed_peers, commands.reborrow());
}
+ processed_peers.push(peer.peer_joint_entity_id);
}
// recursive disconnect part
+ commands.entity(entity).insert(PartiallyDisconnected);
commands.entity(entity).remove::<PartInShip>();
}
@@ 89,6 242,7 @@ fn dragging(
q_joints: Query<&Joints>,
q_joint: Query<&FixedJoint>,
clients: Query<&ConnectedNetworkEntity>,
+ q_ls_me: Query<Entity, With<crate::client::Me>>,
mut commands: Commands,
) {
for FromClient {
@@ 96,13 250,15 @@ fn dragging(
message: event,
} in events.read()
{
- let client_entity = match client_id {
- ClientId::Client(e) => e,
- _ => continue,
+ let player_hearty_entity = match client_id {
+ ClientId::Client(client_entity) => {
+ let ConnectedNetworkEntity {
+ game_entity: player_hearty_entity,
+ } = clients.get(*client_entity).unwrap();
+ player_hearty_entity
+ },
+ ClientId::Server => &q_ls_me.iter().next().unwrap()
};
- let ConnectedNetworkEntity {
- game_entity: player_hearty_entity,
- } = clients.get(*client_entity).unwrap();
debug!(?event, "got drag request event");
@@ 272,55 428,7 @@ fn dragging(
}
}
-fn handle_new_players(
- mut commands: Commands,
- q_new_clients: Query<Entity, Added<ConnectedGameEntity>>,
- world_config: Res<WorldConfigResource>,
- planets: Query<(&Transform, &Planet)>,
- asset_server: Res<AssetServer>,
-) {
- let Some(wc) = &world_config.config else {
- return;
- };
- for joined_player in &q_new_clients {
- trace!(?joined_player, "detected joined player!");
- // find earth
- let (spawn_planet_pos, spawn_planet) = planets
- .iter()
- .find(|p| p.1.name == wc.hearty.spawn_at)
- .unwrap_or_else(|| {
- panic!(
- "spawn planet {} is missing? (check that the planet is named exactly '{}')",
- wc.hearty.spawn_at, wc.hearty.spawn_at
- )
- });
- let angle = rand::random::<f32>() * std::f32::consts::TAU;
- let offset = spawn_planet.radius + 150.0;
- let mut new_transform =
- Transform::from_xyz(angle.cos() * offset, angle.sin() * offset, 0.0);
- new_transform.rotate_z(angle);
- new_transform.translation += spawn_planet_pos.translation;
-
- info!(?new_transform, ?joined_player, "set player's position!");
-
- commands
- .entity(joined_player)
- .insert(new_transform)
- .insert(SpawnPartRequest(
- asset_server.load("config/parts/hearty.part.toml"),
- ))
- .insert(PlayerThrust::default())
- .insert(PlayerStorage {
- fuel_capacity: 25.0,
- fuel: 25.0,
- power_capacity: 25.0,
- power: 25.0,
- })
- .insert(Player {
- client: joined_player,
- });
- }
-}
+
fn magic_fuel_regen(players: Query<&mut PlayerStorage, With<Player>>, time: Res<Time>) {
for mut storage in players {
@@ 338,6 446,7 @@ fn player_thrust(
)>,
clients: Query<&ConnectedNetworkEntity>,
mut thrust_event: MessageReader<FromClient<ThrustEvent>>,
+ q_ls_me: Query<Entity, With<crate::client::Me>>,
world_config: Res<WorldConfigResource>,
) {
for FromClient {
@@ 345,13 454,17 @@ fn player_thrust(
message: event,
} in thrust_event.read()
{
- let client_entity = match client_id {
- ClientId::Client(e) => e,
- _ => continue,
+ let player_hearty_entity = match client_id {
+ ClientId::Client(client_entity) => {
+ let ConnectedNetworkEntity {
+ game_entity: player_hearty_entity,
+ } = clients.get(*client_entity).unwrap();
+ player_hearty_entity
+ },
+ ClientId::Server => &q_ls_me.iter().next().unwrap()
};
- let ConnectedNetworkEntity { game_entity } = clients.get(*client_entity).unwrap();
- let Ok((_, _, _, mut thrust, _)) = players.get_mut(*game_entity) else {
+ let Ok((_, _, _, mut thrust, _)) = players.get_mut(*player_hearty_entity) else {
continue;
};
A crates/unified/src/server/player/join.rs => crates/unified/src/server/player/join.rs +110 -0
@@ 0,0 1,110 @@
+use crate::config::planet::Planet;
+use crate::config::world::GlobalWorldConfig;
+use crate::ecs::{Player, PlayerStorage, PlayerThrust};
+use crate::prelude::*;
+use crate::server::ConnectedGameEntity;
+use crate::server::part::SpawnPartRequest;
+use crate::server::world_config::WorldConfigResource;
+
+fn join_player(joined_player: Entity, mut commands: Commands, wc: &GlobalWorldConfig, planets: Query<(&Transform, &Planet)>, asset_server: &AssetServer) {
+ trace!(?joined_player, "detected joined player!");
+ // find earth
+ if planets.is_empty() {
+ warn!("planets have not loaded yet, setting this player to pending until they do!");
+ commands.entity(joined_player).insert(PendingPlayer);
+ return;
+ }
+ let (spawn_planet_pos, spawn_planet) = planets
+ .iter()
+ .find(|p| p.1.name == wc.hearty.spawn_at)
+ .unwrap_or_else(|| {
+ panic!(
+ "spawn planet {} is missing? (check that the planet is named exactly '{}')",
+ wc.hearty.spawn_at, wc.hearty.spawn_at
+ )
+ });
+ let angle = rand::random::<f32>() * std::f32::consts::TAU;
+ let offset = spawn_planet.radius + 150.0;
+ let mut new_transform =
+ Transform::from_xyz(angle.cos() * offset, angle.sin() * offset, 0.0);
+ new_transform.rotate_z(angle);
+ new_transform.translation += spawn_planet_pos.translation;
+
+ info!(?new_transform, ?joined_player, "set player's position!");
+
+ commands
+ .entity(joined_player)
+ .insert(new_transform)
+ .insert(SpawnPartRequest(
+ asset_server.load("config/parts/hearty.part.toml"),
+ ))
+ .insert(PlayerThrust::default())
+ .insert(PlayerStorage {
+ fuel_capacity: 25.0,
+ fuel: 25.0,
+ power_capacity: 25.0,
+ power: 25.0,
+ })
+ .insert(Player {
+ client: joined_player,
+ })
+ .remove::<PendingPlayer>();
+}
+
+#[derive(Component)]
+pub struct PendingPlayer;
+
+pub fn handle_new_players(
+ mut commands: Commands,
+ q_new_clients: Query<Entity, Added<ConnectedGameEntity>>,
+ world_config: Res<WorldConfigResource>,
+ planets: Query<(&Transform, &Planet)>,
+ asset_server: Res<AssetServer>,
+) {
+ let Some(wc) = &world_config.config else {
+ warn!("got a joined player, but world config is not loaded! waiting until it is...");
+ for joined_player in &q_new_clients {
+ commands.entity(joined_player).insert(PendingPlayer);
+ }
+ return;
+ };
+ for joined_player in &q_new_clients {
+ join_player(joined_player, commands.reborrow(), wc, planets, &asset_server);
+ }
+}
+pub fn handle_pending_players(
+ mut commands: Commands,
+ pending_players: Query<Entity, With<PendingPlayer>>,
+ world_config: Res<WorldConfigResource>,
+ planets: Query<(&Transform, &Planet)>,
+ asset_server: Res<AssetServer>,
+) {
+ let Some(wc) = &world_config.config else {
+ warn!("there are pending players, but world config is not loaded! waiting until it is...");
+ return;
+ };
+
+ for pending_player in &pending_players {
+ warn!(?pending_player, "reprocessing pending player");
+ join_player(pending_player, commands.reborrow(), wc, planets, &asset_server);
+ }
+}
+
+pub fn ls_magically_invent_player(
+ mut commands: Commands,
+ world_config: Res<WorldConfigResource>,
+ planets: Query<(&Transform, &Planet)>,
+ asset_server: Res<AssetServer>,
+) {
+ // Magically invent a player for listenserver
+ let fake_network_entity = commands.spawn(Replicated).id();
+ let local_player = commands.spawn((
+ ConnectedGameEntity {
+ network_entity: fake_network_entity,
+ },
+ Replicated,
+ crate::client::Me
+ )).id();
+ debug!(?fake_network_entity, ?local_player, "listenserver: magically invented a player");
+ //join_player(local_player, commands.reborrow(), wc, planets, &asset_server);
+}<
\ No newline at end of file
M crates/unified/src/server_plugins.rs => crates/unified/src/server_plugins.rs +1 -13
@@ 28,24 28,12 @@ impl PluginGroup for ServerPluginGroup {
.with_length_unit(100.0)
.set(PhysicsInterpolationPlugin::interpolate_all())
)
- .add(StatesPlugin)
- .add(TaskPoolPlugin::default())
- .add(FrameCountPlugin)
- .add(TimePlugin)
- .add(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f32(
- 1.0 / self.tick_rate,
- )))
- .add_group(RepliconPlugins)
.add(WebSocketServerPlugin)
.add(AeronetRepliconServerPlugin)
- /* Assets */
- .add(AssetPlugin::default())
- .add(TomlAssetPlugin::<GlobalWorldConfig>::new(&["wc.toml"]))
- .add(TomlAssetPlugin::<PlanetConfigCollection>::new(&["pc.toml"]))
- .add(TomlAssetPlugin::<PartConfig>::new(&["part.toml"]))
.add(crate::server::ServerPlugin {
bind: self.bind,
max_clients: self.max_clients,
})
+
}
}
M crates/unified/src/shared_plugins.rs => crates/unified/src/shared_plugins.rs +11 -1
@@ 1,11 1,15 @@
use avian2d::PhysicsPlugins;
use crate::attachment::{Joint, JointOf, PartInShip, Peer, Ship, SnapOf, SnapOfJoint};
-use crate::config::planet::Planet;
+use crate::config::planet::{Planet, PlanetConfigCollection};
use crate::ecs::{DragRequestEvent, Part, Particles, Player, PlayerStorage, ThrustEvent};
use bevy::app::{App, PluginGroup, PluginGroupBuilder};
+use bevy::ui::UiPlugin;
+use bevy_common_assets::toml::TomlAssetPlugin;
use crate::prelude::*;
//use bevy_rapier2d::prelude::*;
use bevy_replicon::prelude::{AppRuleExt, Channel, ClientMessageAppExt};
+use crate::config::part::PartConfig;
+use crate::config::world::GlobalWorldConfig;
use crate::physics::register_physics_components_for_replication;
pub struct SharedPluginGroup;
@@ 13,10 17,16 @@ pub struct SharedPluginGroup;
impl PluginGroup for SharedPluginGroup {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
+ .add_group(RepliconPlugins)
//.add(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0))
.add(physics_setup_plugin)
.add(register_everything)
.add(register_physics_components_for_replication)
+
+ /* Assets */
+ .add(TomlAssetPlugin::<GlobalWorldConfig>::new(&["wc.toml"]))
+ .add(TomlAssetPlugin::<PlanetConfigCollection>::new(&["pc.toml"]))
+ .add(TomlAssetPlugin::<PartConfig>::new(&["part.toml"]))
}
}
A rust-toolchain.toml => rust-toolchain.toml +2 -0
@@ 0,0 1,2 @@
+[toolchain]
+channel = "nightly"<
\ No newline at end of file