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, 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, mut assign_player: Query<(&ClientPlayerId, &mut MessageSender)>, ) -> 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::(AssignClientPlayer(id.0)); Ok(()) } fn on_client_playing( trigger: Trigger>, commands: Commands, query: Query<&Transform, With>, heads_db: Res, peers: Res, ) -> 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, config: Res, mut writer: EventWriter, ) { 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) { 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) { commands.insert_resource(TimeoutTimer(config.timeout)); } fn run_timeout(mut timer: ResMut, mut writer: EventWriter, time: Res