Switch to replicon (#80)
This commit is contained in:
@@ -12,12 +12,13 @@ avian3d = { workspace = true }
|
||||
bevy = { workspace = true, default-features = false }
|
||||
bevy-steamworks = { workspace = true }
|
||||
bevy_common_assets = { workspace = true }
|
||||
bevy_replicon = { workspace = true, features = ["server"] }
|
||||
bevy_replicon_renet = { workspace = true, features = ["server"] }
|
||||
bevy_sprite3d = { workspace = true }
|
||||
bevy_trenchbroom = { workspace = true }
|
||||
bevy_trenchbroom_avian = { workspace = true }
|
||||
clap = { version = "=4.5.47", features = ["derive"] }
|
||||
clap = { workspace = true }
|
||||
happy_feet = { workspace = true }
|
||||
lightyear = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
ron = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::input::native::ActionState;
|
||||
use bevy_replicon::prelude::{ClientState, FromClient, SendMode, ServerTriggerExt, ToClients};
|
||||
use shared::{
|
||||
GameState,
|
||||
backpack::{
|
||||
@@ -7,7 +7,7 @@ use shared::{
|
||||
backpack_ui::{BackpackUiState, HEAD_SLOTS},
|
||||
},
|
||||
control::ControlState,
|
||||
protocol::PlaySound,
|
||||
protocol::{ClientToController, PlaySound},
|
||||
};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
@@ -15,23 +15,34 @@ pub fn plugin(app: &mut App) {
|
||||
FixedUpdate,
|
||||
sync_on_change.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
app.add_systems(FixedUpdate, swap_head_inputs);
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
swap_head_inputs.run_if(in_state(ClientState::Disconnected)),
|
||||
);
|
||||
}
|
||||
|
||||
fn swap_head_inputs(
|
||||
player: Query<(&ActionState<ControlState>, Ref<Backpack>)>,
|
||||
backpacks: Query<Ref<Backpack>>,
|
||||
clients: ClientToController,
|
||||
mut inputs: MessageReader<FromClient<ControlState>>,
|
||||
mut commands: Commands,
|
||||
mut state: Single<&mut BackpackUiState>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for (controls, backpack) in player.iter() {
|
||||
for controls in inputs.read() {
|
||||
let player = clients.get_controller(controls.client_id);
|
||||
let backpack = backpacks.get(player).unwrap();
|
||||
|
||||
if state.count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if controls.backpack_toggle {
|
||||
state.open = !state.open;
|
||||
commands.trigger(PlaySound::Backpack { open: state.open });
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::Backpack { open: state.open },
|
||||
});
|
||||
}
|
||||
|
||||
if !state.open {
|
||||
@@ -52,7 +63,10 @@ fn swap_head_inputs(
|
||||
}
|
||||
|
||||
if changed {
|
||||
commands.trigger(PlaySound::Selection);
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::Selection,
|
||||
});
|
||||
sync(&backpack, &mut state, time.elapsed_secs());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use bevy::{
|
||||
ecs::{relationship::RelatedSpawner, spawn::SpawnWith},
|
||||
prelude::*,
|
||||
};
|
||||
use lightyear::prelude::{NetworkTarget, Replicate};
|
||||
use bevy_replicon::prelude::{Replicated, SendMode, ServerTriggerExt, ToClients};
|
||||
use shared::{
|
||||
global_observer,
|
||||
head_drop::{HeadCollected, HeadDrop, HeadDropEnableTime, HeadDrops, SecretHeadMarker},
|
||||
@@ -39,7 +39,10 @@ fn on_head_drop(
|
||||
};
|
||||
|
||||
if drop.impulse {
|
||||
commands.trigger(PlaySound::HeadDrop);
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::HeadDrop,
|
||||
});
|
||||
}
|
||||
|
||||
let mesh_addr = format!("{:?}", heads_db.head_stats(drop.head_id).ability).to_lowercase();
|
||||
@@ -70,16 +73,18 @@ fn on_head_drop(
|
||||
CollisionEventsEnabled,
|
||||
HeadDrop { head_id },
|
||||
HeadDropEnableTime(now + 1.2),
|
||||
Replicated,
|
||||
))
|
||||
.observe(on_collect_head);
|
||||
}
|
||||
})),
|
||||
Replicate::to_clients(NetworkTarget::All),
|
||||
Replicated,
|
||||
))
|
||||
.with_child((
|
||||
Billboard::All,
|
||||
SquishAnimation(2.6),
|
||||
GltfSceneRoot::HeadDrop(mesh_addr),
|
||||
Replicated,
|
||||
));
|
||||
|
||||
Ok(())
|
||||
@@ -101,9 +106,15 @@ fn on_collect_head(
|
||||
let is_secret = query_secret.contains(collectable);
|
||||
|
||||
if is_secret {
|
||||
commands.trigger(PlaySound::SecretHeadCollect);
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::SecretHeadCollect,
|
||||
});
|
||||
} else {
|
||||
commands.trigger(PlaySound::HeadCollect);
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::HeadCollect,
|
||||
});
|
||||
}
|
||||
|
||||
commands.entity(collider).trigger(|entity| HeadCollected {
|
||||
|
||||
@@ -4,9 +4,7 @@ use bevy_common_assets::ron::RonAssetPlugin;
|
||||
use bevy_sprite3d::Sprite3dPlugin;
|
||||
use bevy_trenchbroom::prelude::*;
|
||||
use bevy_trenchbroom_avian::AvianPhysicsBackend;
|
||||
use lightyear::prelude::server::ServerPlugins;
|
||||
use shared::{DebugVisuals, GameState, heads_database::HeadDatabaseAsset};
|
||||
use std::time::Duration;
|
||||
|
||||
mod backpack;
|
||||
mod config;
|
||||
@@ -74,15 +72,7 @@ fn main() {
|
||||
..default()
|
||||
}));
|
||||
|
||||
app.add_plugins(
|
||||
PhysicsPlugins::default()
|
||||
.build()
|
||||
// FrameInterpolation handles interpolating Position and Rotation
|
||||
.disable::<PhysicsInterpolationPlugin>(),
|
||||
);
|
||||
app.add_plugins(ServerPlugins {
|
||||
tick_duration: Duration::from_secs_f32(1.0 / 60.0),
|
||||
});
|
||||
app.add_plugins(PhysicsPlugins::default());
|
||||
app.add_plugins(Sprite3dPlugin);
|
||||
app.add_plugins(TrenchBroomPlugins(
|
||||
TrenchBroomConfig::new("hedz").icon(None),
|
||||
@@ -112,9 +102,9 @@ fn main() {
|
||||
app.add_plugins(shared::npc::plugin);
|
||||
app.add_plugins(shared::platforms::plugin);
|
||||
app.add_plugins(shared::player::plugin);
|
||||
app.add_plugins(shared::protocol::plugin);
|
||||
app.add_plugins(shared::steam::plugin);
|
||||
app.add_plugins(shared::tb_entities::plugin);
|
||||
app.add_plugins(shared::tick::plugin);
|
||||
app.add_plugins(shared::utils::auto_rotate::plugin);
|
||||
app.add_plugins(shared::utils::billboards::plugin);
|
||||
app.add_plugins(shared::utils::explosions::plugin);
|
||||
@@ -124,12 +114,16 @@ fn main() {
|
||||
app.add_plugins(shared::utils::plugin);
|
||||
app.add_plugins(shared::water::plugin);
|
||||
|
||||
// Networking
|
||||
// The client/server plugin must go before the protocol, or else `ProtocolHasher` will not be available.
|
||||
app.add_plugins(server::plugin);
|
||||
app.add_plugins(shared::protocol::plugin);
|
||||
|
||||
app.add_plugins(backpack::plugin);
|
||||
app.add_plugins(config::plugin);
|
||||
app.add_plugins(head_drop::plugin);
|
||||
app.add_plugins(platforms::plugin);
|
||||
app.add_plugins(player::plugin);
|
||||
app.add_plugins(server::plugin);
|
||||
app.add_plugins(tb_entities::plugin);
|
||||
app.add_plugins(utils::plugin);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_trenchbroom::prelude::Target;
|
||||
use lightyear::prelude::{NetworkTarget, PredictionTarget};
|
||||
use shared::{
|
||||
GameState,
|
||||
platforms::ActivePlatform,
|
||||
@@ -34,8 +33,6 @@ fn init(
|
||||
target,
|
||||
};
|
||||
|
||||
commands
|
||||
.entity(e)
|
||||
.insert((platform, PredictionTarget::to_clients(NetworkTarget::All)));
|
||||
commands.entity(e).insert(platform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::{input::native::ActionState, *};
|
||||
use bevy_replicon::prelude::{Replicated, SendMode, ServerTriggerExt, ToClients};
|
||||
use shared::{
|
||||
backpack::{Backpack, backpack_ui::BackpackUiState},
|
||||
camera::{CameraArmRotation, CameraTarget},
|
||||
cash::CashResource,
|
||||
character::AnimatedCharacter,
|
||||
control::{ControlState, controller_common::PlayerCharacterController},
|
||||
control::{Inputs, controller_common::PlayerCharacterController},
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
head_drop::HeadDrops,
|
||||
@@ -14,9 +14,7 @@ use shared::{
|
||||
hitpoints::{Hitpoints, Kill},
|
||||
npc::SpawnCharacter,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
protocol::{
|
||||
PlaySound, PlayerId, channels::UnorderedReliableChannel, events::ClientHeadChanged,
|
||||
},
|
||||
protocol::{PlaySound, PlayerId, events::ClientHeadChanged},
|
||||
tb_entities::SpawnPoint,
|
||||
};
|
||||
|
||||
@@ -26,7 +24,7 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
pub fn spawn(
|
||||
mut commands: Commands,
|
||||
owner: Entity,
|
||||
_owner: Entity,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) -> Option<Entity> {
|
||||
@@ -34,48 +32,53 @@ pub fn spawn(
|
||||
|
||||
let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.));
|
||||
|
||||
let mut player = commands.spawn((
|
||||
(
|
||||
Name::from("player"),
|
||||
Player,
|
||||
ActiveHead(0),
|
||||
ActiveHeads::new([
|
||||
Some(HeadState::new(0, heads_db.as_ref())),
|
||||
Some(HeadState::new(3, heads_db.as_ref())),
|
||||
Some(HeadState::new(6, heads_db.as_ref())),
|
||||
Some(HeadState::new(10, heads_db.as_ref())),
|
||||
Some(HeadState::new(9, heads_db.as_ref())),
|
||||
]),
|
||||
Hitpoints::new(100),
|
||||
CashResource::default(),
|
||||
CameraTarget,
|
||||
transform,
|
||||
Visibility::default(),
|
||||
PlayerCharacterController,
|
||||
PlayerId { id: 0 },
|
||||
),
|
||||
ActionState::<ControlState>::default(),
|
||||
Backpack::default(),
|
||||
BackpackUiState::default(),
|
||||
UiActiveHeads::default(),
|
||||
Replicate::to_clients(NetworkTarget::All),
|
||||
PredictionTarget::to_clients(NetworkTarget::All),
|
||||
ControlledBy {
|
||||
owner,
|
||||
lifetime: Lifetime::SessionBased,
|
||||
},
|
||||
children![(
|
||||
Name::new("player-rig"),
|
||||
PlayerBodyMesh,
|
||||
CameraArmRotation,
|
||||
children![AnimatedCharacter::new(0)],
|
||||
)],
|
||||
));
|
||||
player.observe(on_kill);
|
||||
let id = commands
|
||||
.spawn((
|
||||
(
|
||||
Name::from("player"),
|
||||
Player,
|
||||
ActiveHead(0),
|
||||
ActiveHeads::new([
|
||||
Some(HeadState::new(0, heads_db.as_ref())),
|
||||
Some(HeadState::new(3, heads_db.as_ref())),
|
||||
Some(HeadState::new(6, heads_db.as_ref())),
|
||||
Some(HeadState::new(10, heads_db.as_ref())),
|
||||
Some(HeadState::new(9, heads_db.as_ref())),
|
||||
]),
|
||||
Hitpoints::new(100),
|
||||
CashResource::default(),
|
||||
CameraTarget,
|
||||
transform,
|
||||
Visibility::default(),
|
||||
PlayerCharacterController,
|
||||
PlayerId { id: 0 },
|
||||
),
|
||||
Backpack::default(),
|
||||
BackpackUiState::default(),
|
||||
UiActiveHeads::default(),
|
||||
Inputs::default(),
|
||||
Replicated,
|
||||
))
|
||||
.with_children(|c| {
|
||||
c.spawn((
|
||||
Name::new("player-rig"),
|
||||
PlayerBodyMesh,
|
||||
CameraArmRotation,
|
||||
Replicated,
|
||||
))
|
||||
.with_child((
|
||||
Name::new("player-animated-character"),
|
||||
AnimatedCharacter::new(0),
|
||||
Replicated,
|
||||
));
|
||||
})
|
||||
.observe(on_kill)
|
||||
.id();
|
||||
|
||||
let id = player.id();
|
||||
|
||||
commands.trigger(PlaySound::Head("angry demonstrator".to_string()));
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::Head("angry demonstrator".to_string()),
|
||||
});
|
||||
commands.trigger(SpawnCharacter(transform.translation));
|
||||
|
||||
Some(id)
|
||||
@@ -103,7 +106,6 @@ fn on_update_head_mesh(
|
||||
trigger: On<HeadChanged>,
|
||||
mut commands: Commands,
|
||||
mesh_children: Single<&Children, With<PlayerBodyMesh>>,
|
||||
mut sender: Single<&mut EventSender<ClientHeadChanged>>,
|
||||
animated_characters: Query<&AnimatedCharacter>,
|
||||
mut player: Single<&mut ActiveHead, With<Player>>,
|
||||
) -> Result {
|
||||
@@ -118,7 +120,10 @@ fn on_update_head_mesh(
|
||||
.entity(animated_char)
|
||||
.insert(AnimatedCharacter::new(trigger.0));
|
||||
|
||||
sender.trigger::<UnorderedReliableChannel>(ClientHeadChanged(trigger.0 as u64));
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: ClientHeadChanged(trigger.0 as u64),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,91 +1,58 @@
|
||||
use crate::config::ServerConfig;
|
||||
use bevy::{ecs::component::Components, prelude::*};
|
||||
use lightyear::{
|
||||
connection::client::PeerMetadata,
|
||||
link::LinkConditioner,
|
||||
use bevy::prelude::*;
|
||||
use bevy_replicon::{
|
||||
RepliconPlugins,
|
||||
prelude::{
|
||||
server::{ClientOf, NetcodeConfig, NetcodeServer, ServerUdpIo, Started},
|
||||
*,
|
||||
ClientId, ConnectedClient, FromClient, RepliconChannels, SendMode, ServerState,
|
||||
ServerTriggerExt, ToClients,
|
||||
},
|
||||
server::AuthorizedClient,
|
||||
};
|
||||
use bevy_replicon_renet::{
|
||||
RenetChannelsExt, RepliconRenetPlugins,
|
||||
netcode::{NetcodeServerTransport, ServerAuthentication},
|
||||
renet::{ConnectionConfig, RenetServer},
|
||||
};
|
||||
use shared::{
|
||||
GameState, global_observer,
|
||||
heads_database::HeadsDatabase,
|
||||
protocol::{
|
||||
ClientEnteredPlaying, channels::UnorderedReliableChannel, messages::AssignClientPlayer,
|
||||
},
|
||||
player::ClientPlayerId,
|
||||
protocol::{ClientEnteredPlaying, PlayerId, SetGameTick, messages::AssignClientPlayer},
|
||||
tb_entities::SpawnPoint,
|
||||
tick::GameTick,
|
||||
};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
time::Duration,
|
||||
net::{Ipv4Addr, UdpSocket},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.add_systems(
|
||||
OnEnter(GameState::Playing),
|
||||
(start_server, setup_timeout_timer),
|
||||
);
|
||||
app.add_systems(
|
||||
Update,
|
||||
(notify_started, run_timeout).run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
app.add_plugins((RepliconPlugins, RepliconRenetPlugins));
|
||||
|
||||
global_observer!(app, handle_new_client);
|
||||
global_observer!(app, on_client_connected);
|
||||
global_observer!(app, on_client_playing);
|
||||
global_observer!(app, close_on_disconnect);
|
||||
app.add_systems(OnEnter(GameState::Playing), setup_timeout_timer);
|
||||
app.add_systems(OnEnter(ServerState::Running), notify_started);
|
||||
app.add_systems(Update, run_timeout.run_if(in_state(GameState::Playing)));
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), open_renet_server);
|
||||
|
||||
// Replicon
|
||||
global_observer!(app, on_connected);
|
||||
global_observer!(app, on_disconnected);
|
||||
|
||||
// Server logic
|
||||
global_observer!(app, cancel_timeout);
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ClientPlayerId(u8);
|
||||
|
||||
fn handle_new_client(
|
||||
trigger: On<Add, Linked>,
|
||||
mut commands: Commands,
|
||||
id: Query<&PeerAddr>,
|
||||
) -> Result {
|
||||
let Ok(id) = id.get(trigger.event().entity) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
info!("Client connected on IP: {}", id.ip());
|
||||
|
||||
let conditioner = LinkConditioner::new(LinkConditionerConfig {
|
||||
incoming_latency: Duration::from_millis(10),
|
||||
incoming_jitter: Duration::from_millis(0),
|
||||
incoming_loss: 0.0,
|
||||
});
|
||||
|
||||
commands.entity(trigger.event().entity).insert((
|
||||
ReplicationSender::default(),
|
||||
Link::new(Some(conditioner)),
|
||||
ClientPlayerId(0),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_client_connected(
|
||||
trigger: On<Add, ClientOf>,
|
||||
mut assign_player: Query<(&ClientPlayerId, &mut MessageSender<AssignClientPlayer>)>,
|
||||
) -> Result {
|
||||
// `Linked` happens before the `ClientOf` and `MessageSender` components are added, so the server can't
|
||||
// send the client player id until now.
|
||||
let (id, mut sender) = assign_player.get_mut(trigger.event().entity)?;
|
||||
sender.send::<UnorderedReliableChannel>(AssignClientPlayer(id.0));
|
||||
Ok(())
|
||||
global_observer!(app, on_client_playing);
|
||||
}
|
||||
|
||||
fn on_client_playing(
|
||||
trigger: On<RemoteEvent<ClientEnteredPlaying>>,
|
||||
trigger: On<FromClient<ClientEnteredPlaying>>,
|
||||
commands: Commands,
|
||||
query: Query<&Transform, With<SpawnPoint>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
peers: Res<PeerMetadata>,
|
||||
) -> Result {
|
||||
let Some(&client) = peers.mapping.get(&trigger.from) else {
|
||||
info!("Client has entered playing gamestate");
|
||||
|
||||
let Some(client) = trigger.client_id.entity() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
@@ -94,51 +61,85 @@ fn on_client_playing(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close_on_disconnect(
|
||||
_trigger: On<Remove, Connected>,
|
||||
//
|
||||
// Renet
|
||||
//
|
||||
|
||||
fn open_renet_server(
|
||||
mut commands: Commands,
|
||||
channels: Res<RepliconChannels>,
|
||||
) -> Result<(), BevyError> {
|
||||
let server_channels_config = channels.server_configs();
|
||||
let client_channels_config = channels.client_configs();
|
||||
|
||||
let server = RenetServer::new(ConnectionConfig {
|
||||
server_channels_config,
|
||||
client_channels_config,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
||||
let port = 31111;
|
||||
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, port))?;
|
||||
let server_config = bevy_replicon_renet::netcode::ServerConfig {
|
||||
current_time,
|
||||
max_clients: 1,
|
||||
protocol_id: 0,
|
||||
authentication: ServerAuthentication::Unsecure,
|
||||
public_addresses: Default::default(),
|
||||
};
|
||||
let transport = NetcodeServerTransport::new(server_config, socket)?;
|
||||
|
||||
commands.insert_resource(server);
|
||||
commands.insert_resource(transport);
|
||||
|
||||
info!("Hosting a server on port {port}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//
|
||||
// server logic
|
||||
//
|
||||
|
||||
fn on_connected(
|
||||
trigger: On<Add, AuthorizedClient>,
|
||||
game_tick: Res<GameTick>,
|
||||
mut commands: Commands,
|
||||
mut assign_id: MessageWriter<ToClients<AssignClientPlayer>>,
|
||||
) {
|
||||
let client = trigger.event_target();
|
||||
info!("{client} connected to server!");
|
||||
|
||||
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.0),
|
||||
});
|
||||
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Direct(ClientId::Client(trigger.entity)),
|
||||
message: SetGameTick(game_tick.0),
|
||||
});
|
||||
}
|
||||
|
||||
fn on_disconnected(
|
||||
on: On<Remove, ConnectedClient>,
|
||||
config: Res<ServerConfig>,
|
||||
mut writer: MessageWriter<AppExit>,
|
||||
) {
|
||||
info!("client {} disconnected", on.entity);
|
||||
|
||||
if config.close_on_client_disconnect {
|
||||
info!("client disconnected, exiting");
|
||||
writer.write(AppExit::Success);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_server(mut commands: Commands) -> Result {
|
||||
let server_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 25565);
|
||||
|
||||
let conditioner = LinkConditioner::new(LinkConditionerConfig {
|
||||
incoming_latency: Duration::from_millis(10),
|
||||
incoming_jitter: Duration::from_millis(0),
|
||||
incoming_loss: 0.0,
|
||||
});
|
||||
let mut commands = commands.spawn((
|
||||
Name::from("Server"),
|
||||
LocalAddr(server_addr),
|
||||
ServerUdpIo::default(),
|
||||
NetcodeServer::new(NetcodeConfig {
|
||||
client_timeout_secs: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
Link::new(Some(conditioner)),
|
||||
));
|
||||
commands
|
||||
.observe(|on: On<Add>, components: &Components| {
|
||||
for &comp in on.trigger().components {
|
||||
info!("Added {} to server", components.get_name(comp).unwrap());
|
||||
}
|
||||
})
|
||||
.trigger(|entity| server::Start { entity });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_started(started: Query<&Started>, mut notified: Local<bool>) {
|
||||
if !*notified && !started.is_empty() {
|
||||
println!("hedz.server_started");
|
||||
*notified = true;
|
||||
}
|
||||
fn notify_started() {
|
||||
println!("hedz.server_started");
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
@@ -161,7 +162,7 @@ fn run_timeout(
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_timeout(_trigger: On<Add, Connected>, mut timer: ResMut<TimeoutTimer>) {
|
||||
fn cancel_timeout(_trigger: On<Add, ConnectedClient>, mut timer: ResMut<TimeoutTimer>) {
|
||||
info!("client connected, cancelling timeout");
|
||||
timer.0 = f32::INFINITY;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use bevy::prelude::*;
|
||||
use lightyear::prelude::{Connected, MessageSender};
|
||||
use bevy_replicon::prelude::{ClientId, ConnectedClient, SendMode, ToClients};
|
||||
use shared::{
|
||||
GameState, global_observer,
|
||||
protocol::{TbMapEntityId, channels::UnorderedReliableChannel, messages::DespawnTbMapEntity},
|
||||
protocol::{TbMapEntityId, messages::DespawnTbMapEntity},
|
||||
};
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
@@ -32,12 +32,14 @@ fn add_despawned_entities_to_cache(
|
||||
pub struct DespawnedTbEntityCache(pub Vec<u64>);
|
||||
|
||||
fn send_new_client_despawned_cache(
|
||||
trigger: On<Add, Connected>,
|
||||
on: On<Add, ConnectedClient>,
|
||||
cache: Res<DespawnedTbEntityCache>,
|
||||
mut send: Query<&mut MessageSender<DespawnTbMapEntity>>,
|
||||
mut send: MessageWriter<ToClients<DespawnTbMapEntity>>,
|
||||
) {
|
||||
let mut send = send.get_mut(trigger.event().entity).unwrap();
|
||||
for &id in cache.0.iter() {
|
||||
send.send::<UnorderedReliableChannel>(DespawnTbMapEntity(id));
|
||||
send.write(ToClients {
|
||||
mode: SendMode::Direct(ClientId::Client(on.entity)),
|
||||
message: DespawnTbMapEntity(id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user