158 lines
4.3 KiB
Rust
158 lines
4.3 KiB
Rust
use crate::config::ServerConfig;
|
|
use bevy::prelude::*;
|
|
use lightyear::{
|
|
connection::client::PeerMetadata,
|
|
link::LinkConditioner,
|
|
prelude::{
|
|
server::{ClientOf, NetcodeConfig, NetcodeServer, ServerUdpIo, Started},
|
|
*,
|
|
},
|
|
};
|
|
use shared::{
|
|
GameState, global_observer,
|
|
heads_database::HeadsDatabase,
|
|
protocol::{
|
|
ClientEnteredPlaying, channels::UnorderedReliableChannel, messages::AssignClientPlayer,
|
|
},
|
|
tb_entities::SpawnPoint,
|
|
};
|
|
use std::{
|
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
|
time::Duration,
|
|
};
|
|
|
|
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)),
|
|
);
|
|
|
|
global_observer!(app, handle_new_client);
|
|
global_observer!(app, on_client_connected);
|
|
global_observer!(app, on_client_playing);
|
|
global_observer!(app, close_on_disconnect);
|
|
global_observer!(app, cancel_timeout);
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct ClientPlayerId(u8);
|
|
|
|
fn handle_new_client(
|
|
trigger: Trigger<OnAdd, Linked>,
|
|
mut commands: Commands,
|
|
id: Query<&PeerAddr>,
|
|
) -> Result {
|
|
let Ok(id) = id.get(trigger.target()) 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.target()).insert((
|
|
ReplicationSender::default(),
|
|
Link::new(Some(conditioner)),
|
|
ClientPlayerId(0),
|
|
));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn on_client_connected(
|
|
trigger: Trigger<OnAdd, 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.target())?;
|
|
sender.send::<UnorderedReliableChannel>(AssignClientPlayer(id.0));
|
|
Ok(())
|
|
}
|
|
|
|
fn on_client_playing(
|
|
trigger: Trigger<RemoteTrigger<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 {
|
|
return Ok(());
|
|
};
|
|
|
|
crate::player::spawn(commands, client, query, heads_db).ok_or("failed to spawn player")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn close_on_disconnect(
|
|
_trigger: Trigger<OnRemove, Connected>,
|
|
config: Res<ServerConfig>,
|
|
mut writer: EventWriter<AppExit>,
|
|
) {
|
|
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.trigger(server::Start);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn notify_started(started: Query<&Started>, mut notified: Local<bool>) {
|
|
if !*notified && !started.is_empty() {
|
|
println!("hedz.server_started");
|
|
*notified = true;
|
|
}
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
struct TimeoutTimer(f32);
|
|
|
|
fn setup_timeout_timer(mut commands: Commands, config: Res<ServerConfig>) {
|
|
commands.insert_resource(TimeoutTimer(config.timeout));
|
|
}
|
|
|
|
fn run_timeout(mut timer: ResMut<TimeoutTimer>, mut writer: EventWriter<AppExit>, time: Res<Time>) {
|
|
timer.0 -= time.delta_secs();
|
|
|
|
if timer.0 <= 0.0 {
|
|
info!("client timed out, exiting");
|
|
writer.write(AppExit::Success);
|
|
}
|
|
}
|
|
|
|
fn cancel_timeout(_trigger: Trigger<OnAdd, Connected>, mut timer: ResMut<TimeoutTimer>) {
|
|
info!("client connected, cancelling timeout");
|
|
timer.0 = f32::INFINITY;
|
|
}
|