Switch to replicon (#80)
This commit is contained in:
@@ -1,48 +1,50 @@
|
||||
use crate::config::ClientConfig;
|
||||
use avian3d::prelude::{
|
||||
Collider, ColliderAabb, ColliderDensity, ColliderMarker, ColliderOf, ColliderTransform,
|
||||
CollisionEventsEnabled, CollisionLayers, Position, Rotation, Sensor,
|
||||
CollisionEventsEnabled, CollisionLayers, Sensor,
|
||||
};
|
||||
use bevy::{
|
||||
ecs::bundle::BundleFromComponents, platform::cell::SyncCell, prelude::*, scene::SceneInstance,
|
||||
};
|
||||
use bevy_trenchbroom::geometry::Brushes;
|
||||
use lightyear::{
|
||||
frame_interpolation::FrameInterpolate,
|
||||
link::{LinkConditioner, prelude::*},
|
||||
netcode::Key,
|
||||
prelude::{
|
||||
client::{Input, InputDelayConfig, NetcodeConfig},
|
||||
input::native::InputMarker,
|
||||
*,
|
||||
},
|
||||
use bevy_replicon::{
|
||||
RepliconPlugins,
|
||||
client::{ClientSystems, confirm_history::ConfirmHistory},
|
||||
prelude::{ClientState, ClientTriggerExt, RepliconChannels},
|
||||
};
|
||||
use bevy_replicon_renet::{
|
||||
RenetChannelsExt, RepliconRenetPlugins,
|
||||
netcode::{ClientAuthentication, NetcodeClientTransport, NetcodeError},
|
||||
renet::{ConnectionConfig, RenetClient},
|
||||
};
|
||||
use bevy_trenchbroom::geometry::Brushes;
|
||||
use nil::prelude::Mutex;
|
||||
use shared::{
|
||||
GameState,
|
||||
control::ControlState,
|
||||
global_observer,
|
||||
player::Player,
|
||||
GameState, global_observer,
|
||||
protocol::{
|
||||
ClientEnteredPlaying, TbMapEntityId, TbMapEntityMapping,
|
||||
channels::UnorderedReliableChannel, messages::DespawnTbMapEntity,
|
||||
ClientEnteredPlaying, TbMapEntityId, TbMapEntityMapping, messages::DespawnTbMapEntity,
|
||||
},
|
||||
tb_entities::{Platform, PlatformTarget},
|
||||
tb_entities::{Movable, Platform, PlatformTarget},
|
||||
};
|
||||
use std::{
|
||||
env::current_exe,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
net::{Ipv4Addr, UdpSocket},
|
||||
process::Stdio,
|
||||
sync::{LazyLock, mpsc},
|
||||
time::Duration,
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
/// Cache of server processes to be cleared at process exit
|
||||
static SERVER_PROCESSES: LazyLock<Mutex<Vec<std::process::Child>>> = LazyLock::new(Mutex::default);
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Connecting), attempt_connection);
|
||||
app.add_plugins((RepliconPlugins, RepliconRenetPlugins));
|
||||
|
||||
app.add_systems(
|
||||
OnEnter(GameState::Connecting),
|
||||
connect_to_server.run_if(|config: Res<ClientConfig>| config.server.is_some()),
|
||||
);
|
||||
app.add_systems(
|
||||
Update,
|
||||
parse_local_server_stdout.run_if(resource_exists::<LocalServerStdout>),
|
||||
@@ -53,15 +55,31 @@ pub fn plugin(app: &mut App) {
|
||||
PreUpdate,
|
||||
(migrate_remote_entities, ApplyDeferred)
|
||||
.chain()
|
||||
.after(ReplicationSystems::Receive),
|
||||
.after(ClientSystems::Receive),
|
||||
);
|
||||
|
||||
// TODO: migrate this to connect_on_local_server_started
|
||||
app.add_systems(OnEnter(ClientState::Connected), on_connected_state);
|
||||
app.add_systems(OnExit(ClientState::Connected), on_disconnect);
|
||||
app.add_systems(
|
||||
OnEnter(GameState::Connecting),
|
||||
host_local_server.run_if(|config: Res<ClientConfig>| config.server.is_none()),
|
||||
);
|
||||
|
||||
global_observer!(app, on_connecting);
|
||||
global_observer!(app, on_connection_failed);
|
||||
global_observer!(app, on_connection_succeeded);
|
||||
global_observer!(app, temp_give_player_marker);
|
||||
global_observer!(app, connect_on_local_server_started);
|
||||
global_observer!(app, add_visual_interpolation_components);
|
||||
}
|
||||
|
||||
//
|
||||
// Client logic
|
||||
//
|
||||
|
||||
fn on_connected_state(mut commands: Commands, mut game_state: ResMut<NextState<GameState>>) {
|
||||
commands.client_trigger(ClientEnteredPlaying::default());
|
||||
game_state.set(GameState::Playing);
|
||||
}
|
||||
|
||||
fn on_disconnect() {
|
||||
info!("disconnected from the server");
|
||||
}
|
||||
|
||||
fn close_server_processes(mut app_exit: MessageReader<AppExit>) {
|
||||
@@ -75,142 +93,88 @@ fn close_server_processes(mut app_exit: MessageReader<AppExit>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn attempt_connection(mut commands: Commands) -> Result {
|
||||
let mut args = std::env::args();
|
||||
let client_port = loop {
|
||||
match args.next().as_deref() {
|
||||
Some("--port") => {
|
||||
break args.next().unwrap().parse::<u16>().unwrap();
|
||||
}
|
||||
Some(_) => (),
|
||||
None => break 25564,
|
||||
}
|
||||
};
|
||||
//
|
||||
// Renet
|
||||
//
|
||||
|
||||
let client_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), client_port);
|
||||
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25565);
|
||||
let auth = Authentication::Manual {
|
||||
server_addr,
|
||||
client_id: client_port as u64,
|
||||
private_key: Key::default(),
|
||||
protocol_id: 0,
|
||||
};
|
||||
let sync_config = SyncConfig {
|
||||
jitter_multiple: 5,
|
||||
jitter_margin: Duration::from_millis(15),
|
||||
..default()
|
||||
};
|
||||
let conditioner = LinkConditioner::new(LinkConditionerConfig {
|
||||
incoming_latency: Duration::from_millis(10),
|
||||
incoming_jitter: Duration::from_millis(0),
|
||||
incoming_loss: 0.0,
|
||||
fn connect_to_server(
|
||||
mut commands: Commands,
|
||||
config: Res<ClientConfig>,
|
||||
channels: Res<RepliconChannels>,
|
||||
) -> Result {
|
||||
let server_channels_config = channels.server_configs();
|
||||
let client_channels_config = channels.client_configs();
|
||||
|
||||
let client = RenetClient::new(ConnectionConfig {
|
||||
server_channels_config,
|
||||
client_channels_config,
|
||||
..Default::default()
|
||||
});
|
||||
commands
|
||||
.spawn((
|
||||
Name::from("Client"),
|
||||
Client::default(),
|
||||
Link::new(Some(conditioner)),
|
||||
LocalAddr(client_addr),
|
||||
PeerAddr(server_addr),
|
||||
ReplicationReceiver::default(),
|
||||
client::NetcodeClient::new(
|
||||
auth,
|
||||
NetcodeConfig {
|
||||
client_timeout_secs: 3,
|
||||
..default()
|
||||
},
|
||||
)?,
|
||||
UdpIo::default(),
|
||||
PredictionManager::default(),
|
||||
InputTimeline(Timeline::from(Input::new(
|
||||
sync_config,
|
||||
InputDelayConfig::balanced(),
|
||||
))),
|
||||
))
|
||||
.trigger(|entity| Connect { entity });
|
||||
|
||||
commands.insert_resource(client);
|
||||
commands.insert_resource(client_transport(&config)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_connection_succeeded(
|
||||
_trigger: On<Add, Connected>,
|
||||
state: Res<State<GameState>>,
|
||||
mut change_state: ResMut<NextState<GameState>>,
|
||||
mut sender: Single<&mut EventSender<ClientEnteredPlaying>>,
|
||||
) {
|
||||
if *state == GameState::Connecting {
|
||||
change_state.set(GameState::Playing);
|
||||
sender.trigger::<UnorderedReliableChannel>(ClientEnteredPlaying);
|
||||
}
|
||||
}
|
||||
fn client_transport(config: &ClientConfig) -> 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 = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, config.port))?;
|
||||
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,
|
||||
user_data: None,
|
||||
};
|
||||
|
||||
/// A client starts `Disconnected`, so in order to tell if it *actually* failed to connect/disconnected
|
||||
/// vs. simply having been created, we need some extra state.
|
||||
#[derive(Component)]
|
||||
struct ClientActive;
|
||||
|
||||
fn on_connecting(trigger: On<Add, Connecting>, mut commands: Commands) {
|
||||
commands.entity(trigger.event().entity).insert(ClientActive);
|
||||
info!("attempting connection to {server_addr}");
|
||||
NetcodeClientTransport::new(current_time, authentication, socket)
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct LocalServerStdout(SyncCell<mpsc::Receiver<String>>);
|
||||
|
||||
fn on_connection_failed(
|
||||
trigger: On<Add, Disconnected>,
|
||||
disconnected: Query<&Disconnected>,
|
||||
mut commands: Commands,
|
||||
client_active: Query<&ClientActive>,
|
||||
mut opened_server: Local<bool>,
|
||||
) -> Result {
|
||||
let disconnected = disconnected.get(trigger.event().entity).unwrap();
|
||||
if *opened_server {
|
||||
panic!(
|
||||
"failed to connect to local server: {:?}",
|
||||
disconnected.reason
|
||||
);
|
||||
}
|
||||
fn host_local_server(mut commands: Commands) -> Result {
|
||||
// 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(server_log_file)
|
||||
.spawn()
|
||||
.expect("failed to start server");
|
||||
let server_stdout = server_process.stdout.take().unwrap();
|
||||
SERVER_PROCESSES.lock().push(server_process);
|
||||
|
||||
let client = trigger.event().entity;
|
||||
if client_active.contains(client) {
|
||||
commands.entity(client).remove::<ClientActive>();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
// 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(server_log_file)
|
||||
.spawn()
|
||||
.expect("failed to start server");
|
||||
let server_stdout = server_process.stdout.take().unwrap();
|
||||
SERVER_PROCESSES.lock().push(server_process);
|
||||
let stdout = BufReader::new(server_stdout).lines();
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
let stdout = BufReader::new(server_stdout).lines();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for line in stdout {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
tx.send(line).unwrap();
|
||||
}
|
||||
Err(error) => {
|
||||
error!("error reading local server stdout: `{error}`");
|
||||
}
|
||||
std::thread::spawn(move || {
|
||||
for line in stdout {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
tx.send(line).unwrap();
|
||||
}
|
||||
Err(error) => {
|
||||
error!("error reading local server stdout: `{error}`");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
commands.insert_resource(LocalServerStdout(SyncCell::new(rx)));
|
||||
|
||||
*opened_server = true;
|
||||
}
|
||||
commands.insert_resource(LocalServerStdout(SyncCell::new(rx)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -231,27 +195,22 @@ fn parse_local_server_stdout(mut commands: Commands, mut stdout: ResMut<LocalSer
|
||||
}
|
||||
|
||||
fn connect_on_local_server_started(
|
||||
_trigger: On<LocalServerStarted>,
|
||||
_: On<LocalServerStarted>,
|
||||
commands: Commands,
|
||||
state: Res<State<GameState>>,
|
||||
mut commands: Commands,
|
||||
client: Single<Entity, With<Client>>,
|
||||
) {
|
||||
channels: Res<RepliconChannels>,
|
||||
config: Res<ClientConfig>,
|
||||
) -> Result<()> {
|
||||
if *state == GameState::Connecting {
|
||||
commands
|
||||
.entity(*client)
|
||||
.trigger(|entity| Connect { entity });
|
||||
connect_to_server(commands, config, channels)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn temp_give_player_marker(trigger: On<Add, Player>, mut commands: Commands) {
|
||||
commands
|
||||
.entity(trigger.event().entity)
|
||||
.insert(InputMarker::<ControlState>::default());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn migrate_remote_entities(
|
||||
query: Query<(Entity, &TbMapEntityId), (Added<TbMapEntityId>, With<Replicated>)>,
|
||||
query: Query<(Entity, &TbMapEntityId), (Added<TbMapEntityId>, With<ConfirmHistory>)>,
|
||||
children: Query<&Children>,
|
||||
mut commands: Commands,
|
||||
mut mapping: ResMut<TbMapEntityMapping>,
|
||||
@@ -286,6 +245,7 @@ fn received_remote_map_entity(
|
||||
move_component::<ColliderOf>(commands, clientside, serverside);
|
||||
move_component::<ColliderTransform>(commands, clientside, serverside);
|
||||
move_component::<CollisionEventsEnabled>(commands, clientside, serverside);
|
||||
move_component::<Movable>(commands, clientside, serverside);
|
||||
move_component::<Platform>(commands, clientside, serverside);
|
||||
move_component::<PlatformTarget>(commands, clientside, serverside);
|
||||
move_component::<SceneInstance>(commands, clientside, serverside);
|
||||
@@ -316,37 +276,16 @@ fn move_component<B: Bundle + BundleFromComponents>(
|
||||
|
||||
fn despawn_absent_map_entities(
|
||||
mut commands: Commands,
|
||||
mut messages: Query<&mut MessageReceiver<DespawnTbMapEntity>>,
|
||||
mut messages: MessageReader<DespawnTbMapEntity>,
|
||||
mut map: ResMut<TbMapEntityMapping>,
|
||||
) {
|
||||
for mut recv in messages.iter_mut() {
|
||||
for msg in recv.receive() {
|
||||
// the server may double-send DespawnTbMapEntity for a given ID, so ignore it if the entity
|
||||
// was already despawned.
|
||||
let Some(entity) = map.0.remove(&msg.0) else {
|
||||
continue;
|
||||
};
|
||||
for msg in messages.read() {
|
||||
// the server may double-send DespawnTbMapEntity for a given ID, so ignore it if the entity
|
||||
// was already despawned.
|
||||
let Some(entity) = map.0.remove(&msg.0) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
|
||||
fn add_visual_interpolation_components(trigger: On<Add, Predicted>, mut commands: Commands) {
|
||||
commands.entity(trigger.entity).insert((
|
||||
FrameInterpolate::<Position> {
|
||||
// We must trigger change detection on visual interpolation
|
||||
// to make sure that child entities (sprites, meshes, text)
|
||||
// are also interpolated
|
||||
trigger_change_detection: true,
|
||||
..default()
|
||||
},
|
||||
FrameInterpolate::<Rotation> {
|
||||
// We must trigger change detection on visual interpolation
|
||||
// to make sure that child entities (sprites, meshes, text)
|
||||
// are also interpolated
|
||||
trigger_change_detection: true,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
23
crates/client/src/config.rs
Normal file
23
crates/client/src/config.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use bevy::prelude::*;
|
||||
use clap::Parser;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
let config = ClientConfig::parse();
|
||||
|
||||
app.insert_resource(config);
|
||||
}
|
||||
|
||||
#[derive(Resource, Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct ClientConfig {
|
||||
/// The port to use when connecting.
|
||||
#[arg(long, default_value_t = 0)]
|
||||
pub port: u16,
|
||||
/// 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
|
||||
#[arg(long)]
|
||||
pub server: Option<Option<SocketAddr>>,
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::input::native::ActionState;
|
||||
use shared::{
|
||||
GameState,
|
||||
control::{ControlState, ControllerSet, LookDirMovement},
|
||||
@@ -18,13 +17,11 @@ pub fn plugin(app: &mut App) {
|
||||
}
|
||||
|
||||
fn rotate_rig(
|
||||
actions: Query<&ActionState<ControlState>>,
|
||||
controls: Res<ControlState>,
|
||||
look_dir: Res<LookDirMovement>,
|
||||
mut player: Query<(&mut Transform, &ChildOf), With<PlayerBodyMesh>>,
|
||||
mut player: Query<&mut Transform, With<PlayerBodyMesh>>,
|
||||
) {
|
||||
for (mut rig_transform, child_of) in player.iter_mut() {
|
||||
let controls = actions.get(child_of.parent()).unwrap();
|
||||
|
||||
for mut rig_transform in player.iter_mut() {
|
||||
if controls.view_mode {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ use bevy::{
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
use lightyear::{
|
||||
input::client::InputSystems,
|
||||
prelude::input::native::{ActionState, InputMarker},
|
||||
};
|
||||
use shared::{
|
||||
control::{ControllerSet, LookDirMovement},
|
||||
player::PlayerBodyMesh,
|
||||
@@ -27,7 +23,7 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_systems(PreUpdate, (cache_keyboard_state, cache_gamepad_state));
|
||||
|
||||
app.add_systems(
|
||||
FixedPreUpdate,
|
||||
PreUpdate,
|
||||
(
|
||||
reset_lookdir,
|
||||
gamepad_controls,
|
||||
@@ -39,18 +35,14 @@ pub fn plugin(app: &mut App) {
|
||||
get_lookdir,
|
||||
clear_keyboard_just,
|
||||
clear_gamepad_just,
|
||||
send_inputs,
|
||||
)
|
||||
.chain()
|
||||
.in_set(ControllerSet::CollectInputs)
|
||||
.before(InputSystems::WriteClientInputs)
|
||||
.run_if(
|
||||
in_state(GameState::Playing)
|
||||
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
FixedPreUpdate,
|
||||
buffer_inputs.in_set(InputSystems::WriteClientInputs),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
@@ -61,11 +53,8 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
/// Write inputs from combined keyboard/gamepad state into the networked input buffer
|
||||
/// for the local player.
|
||||
fn buffer_inputs(
|
||||
mut player: Single<&mut ActionState<ControlState>, With<InputMarker<ControlState>>>,
|
||||
controls: Res<ControlState>,
|
||||
) {
|
||||
player.0 = *controls;
|
||||
fn send_inputs(mut writer: MessageWriter<ControlState>, controls: Res<ControlState>) {
|
||||
writer.write(*controls);
|
||||
}
|
||||
|
||||
fn reset_lookdir(mut look_dir: ResMut<LookDirMovement>) {
|
||||
@@ -177,7 +166,7 @@ fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<Contr
|
||||
let keyboard = controls.keyboard_state;
|
||||
let gamepad = controls.gamepad_state.unwrap_or_default();
|
||||
|
||||
combined_controls.look_dir = Dir3::NEG_Z;
|
||||
combined_controls.look_dir = Vec3::NEG_Z;
|
||||
combined_controls.move_dir = gamepad.move_dir + keyboard.move_dir;
|
||||
combined_controls.jump = gamepad.jump | keyboard.jump;
|
||||
combined_controls.view_mode = gamepad.view_mode | keyboard.view_mode;
|
||||
@@ -197,9 +186,9 @@ fn get_lookdir(
|
||||
rig_transform: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
|
||||
) {
|
||||
controls.look_dir = if let Some(ref rig_transform) = rig_transform {
|
||||
rig_transform.forward()
|
||||
rig_transform.forward().as_vec3()
|
||||
} else {
|
||||
Dir3::NEG_Z
|
||||
Vec3::NEG_Z
|
||||
};
|
||||
}
|
||||
|
||||
@@ -252,7 +241,7 @@ fn gamepad_controls(
|
||||
|
||||
let state = ControlState {
|
||||
move_dir,
|
||||
look_dir: Dir3::NEG_Z,
|
||||
look_dir: Vec3::NEG_Z,
|
||||
jump: gamepad.pressed(GamepadButton::South),
|
||||
view_mode: gamepad.pressed(GamepadButton::LeftTrigger2),
|
||||
trigger: gamepad.pressed(GamepadButton::RightTrigger2),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::GameState;
|
||||
use bevy::prelude::*;
|
||||
use bevy_replicon::client::ClientSystems;
|
||||
use shared::control::{ControlState, ControllerSet};
|
||||
|
||||
mod controller_flying;
|
||||
@@ -23,7 +24,9 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_plugins((controller_flying::plugin, controls::plugin));
|
||||
|
||||
app.configure_sets(
|
||||
FixedPreUpdate,
|
||||
ControllerSet::CollectInputs.run_if(in_state(GameState::Playing)),
|
||||
PreUpdate,
|
||||
ControllerSet::CollectInputs
|
||||
.before(ClientSystems::Receive)
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod backpack;
|
||||
mod client;
|
||||
mod config;
|
||||
mod control;
|
||||
mod debug;
|
||||
mod enemy;
|
||||
@@ -22,13 +23,8 @@ use bevy_trenchbroom::prelude::*;
|
||||
use bevy_trenchbroom_avian::AvianPhysicsBackend;
|
||||
use camera::MainCamera;
|
||||
use heads_database::HeadDatabaseAsset;
|
||||
use lightyear::{
|
||||
avian3d::plugin::LightyearAvianPlugin, frame_interpolation::FrameInterpolationPlugin,
|
||||
prelude::client::ClientPlugins,
|
||||
};
|
||||
use loading_assets::AudioAssets;
|
||||
use shared::*;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let mut app = App::new();
|
||||
@@ -68,25 +64,7 @@ fn main() {
|
||||
bevy_debug_log::LogViewerPlugin::default()
|
||||
.auto_open_threshold(bevy::log::tracing::level_filters::LevelFilter::OFF),
|
||||
);
|
||||
app.add_plugins(
|
||||
PhysicsPlugins::default()
|
||||
.build()
|
||||
// TODO: This plugin is *not* disabled on the server. This is to solve a bug related to collider transform inheritance. See the
|
||||
// LightyearAvianPlugin below.
|
||||
// Periwink is looking into it at the moment. This **must** be disabled on the client, or positions break for NPCs and some other things.
|
||||
.disable::<PhysicsTransformPlugin>()
|
||||
// FrameInterpolation handles interpolating Position and Rotation
|
||||
.disable::<PhysicsInterpolationPlugin>(),
|
||||
);
|
||||
// TODO: This plugin is *not* inserted on the server. This is to solve a bug related to collider transform inheritance. See the
|
||||
// `.disable::<PhysicsTransformPlugin>()` above.
|
||||
// Periwink is looking into it at the moment. This **must** be inserted on the client, or positions break for NPCs and some other things.
|
||||
app.add_plugins(LightyearAvianPlugin::default());
|
||||
app.add_plugins(FrameInterpolationPlugin::<Position>::default());
|
||||
app.add_plugins(FrameInterpolationPlugin::<Rotation>::default());
|
||||
app.add_plugins(ClientPlugins {
|
||||
tick_duration: Duration::from_secs_f64(1.0 / 60.0),
|
||||
});
|
||||
app.add_plugins(PhysicsPlugins::default());
|
||||
app.add_plugins(Sprite3dPlugin);
|
||||
app.add_plugins(TrenchBroomPlugins(
|
||||
TrenchBroomConfig::new("hedz")
|
||||
@@ -116,7 +94,6 @@ fn main() {
|
||||
app.add_plugins(shared::player::plugin);
|
||||
app.add_plugins(shared::gates::plugin);
|
||||
app.add_plugins(shared::platforms::plugin);
|
||||
app.add_plugins(shared::protocol::plugin);
|
||||
app.add_plugins(shared::movables::plugin);
|
||||
app.add_plugins(shared::utils::billboards::plugin);
|
||||
app.add_plugins(shared::aim::plugin);
|
||||
@@ -140,10 +117,16 @@ fn main() {
|
||||
app.add_plugins(shared::utils::trail::plugin);
|
||||
app.add_plugins(shared::utils::auto_rotate::plugin);
|
||||
app.add_plugins(shared::tb_entities::plugin);
|
||||
app.add_plugins(shared::tick::plugin);
|
||||
app.add_plugins(shared::utils::explosions::plugin);
|
||||
|
||||
app.add_plugins(backpack::plugin);
|
||||
// Networking
|
||||
// The client/server plugin must go before the protocol, or else `ProtocolHasher` will not be available.
|
||||
app.add_plugins(client::plugin);
|
||||
app.add_plugins(shared::protocol::plugin);
|
||||
|
||||
app.add_plugins(backpack::plugin);
|
||||
app.add_plugins(config::plugin);
|
||||
app.add_plugins(control::plugin);
|
||||
app.add_plugins(debug::plugin);
|
||||
app.add_plugins(enemy::plugin);
|
||||
|
||||
@@ -4,14 +4,13 @@ use crate::{
|
||||
loading_assets::AudioAssets,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::MessageReceiver;
|
||||
use shared::{
|
||||
player::PlayerBodyMesh,
|
||||
player::{LocalPlayerId, PlayerBodyMesh},
|
||||
protocol::{PlaySound, PlayerId, events::ClientHeadChanged, messages::AssignClientPlayer},
|
||||
};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.register_type::<ClientPlayerId>();
|
||||
app.register_type::<LocalPlayerId>();
|
||||
app.register_type::<LocalPlayer>();
|
||||
|
||||
app.init_state::<PlayerAssignmentState>();
|
||||
@@ -28,35 +27,32 @@ pub fn plugin(app: &mut App) {
|
||||
global_observer!(app, on_update_head_mesh);
|
||||
}
|
||||
|
||||
#[derive(Resource, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct ClientPlayerId {
|
||||
id: u8,
|
||||
}
|
||||
|
||||
fn receive_player_id(
|
||||
mut commands: Commands,
|
||||
mut recv: Single<&mut MessageReceiver<AssignClientPlayer>>,
|
||||
mut client_assignments: MessageReader<AssignClientPlayer>,
|
||||
mut next: ResMut<NextState<PlayerAssignmentState>>,
|
||||
) {
|
||||
for AssignClientPlayer(id) in recv.receive() {
|
||||
commands.insert_resource(ClientPlayerId { id });
|
||||
for &AssignClientPlayer(id) in client_assignments.read() {
|
||||
commands.insert_resource(LocalPlayerId { id });
|
||||
next.set(PlayerAssignmentState::IdReceived);
|
||||
info!("player id `{id}` received");
|
||||
info!("player id `{}` received", id.id);
|
||||
}
|
||||
}
|
||||
|
||||
fn match_player_id(
|
||||
mut commands: Commands,
|
||||
players: Query<(Entity, &PlayerId), Changed<PlayerId>>,
|
||||
client: Res<ClientPlayerId>,
|
||||
client: Res<LocalPlayerId>,
|
||||
mut next: ResMut<NextState<PlayerAssignmentState>>,
|
||||
) {
|
||||
for (entity, player) in players.iter() {
|
||||
if player.id == client.id {
|
||||
for (entity, player_id) in players.iter() {
|
||||
if *player_id == client.id {
|
||||
commands.entity(entity).insert(LocalPlayer);
|
||||
next.set(PlayerAssignmentState::Confirmed);
|
||||
info!("player entity {entity:?} confirmed with id `{}`", player.id);
|
||||
info!(
|
||||
"player entity {entity:?} confirmed with id `{}`",
|
||||
player_id.id
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user