server.rs (6957B)
1 use bevy::app::PluginGroupBuilder; 2 use bevy::prelude::*; 3 use bevy::utils::Duration; 4 5 use lightyear::prelude::server::*; 6 use lightyear::prelude::*; 7 8 use crate::networking::shared_movement_behaviour; 9 10 use super::{protocol::*, shared_config, SharedSettings}; 11 12 // Plugin group to add all server-related plugins 13 pub struct ServerPluginGroup { 14 pub(crate) lightyear: ServerPlugin<MatrixRPGGameProto>, 15 } 16 17 impl ServerPluginGroup { 18 pub(crate) fn new( 19 transport_configs: Vec<TransportConfig>, 20 shared_settings: SharedSettings, 21 ) -> ServerPluginGroup { 22 // Step 1: create the io (transport + link conditioner) 23 let link_conditioner = LinkConditionerConfig { 24 incoming_latency: Duration::from_millis(200), 25 incoming_jitter: Duration::from_millis(20), 26 incoming_loss: 0.05, 27 }; 28 let mut net_configs = vec![]; 29 for transport_config in transport_configs { 30 net_configs.push(NetConfig::Netcode { 31 config: NetcodeConfig::default() 32 .with_protocol_id(shared_settings.protocol_id) 33 .with_key(shared_settings.private_key), 34 io: IoConfig::from_transport(transport_config) 35 .with_conditioner(link_conditioner.clone()), 36 }); 37 } 38 39 // Step 2: define the server configuration 40 let config = ServerConfig { 41 shared: shared_config(), 42 net: net_configs, 43 ..default() 44 }; 45 46 // Step 3: create the plugin 47 let plugin_config = PluginConfig::new(config, protocol()); 48 ServerPluginGroup { 49 lightyear: ServerPlugin::new(plugin_config), 50 } 51 } 52 } 53 54 impl PluginGroup for ServerPluginGroup { 55 fn build(self) -> PluginGroupBuilder { 56 PluginGroupBuilder::start::<Self>() 57 .add(self.lightyear) 58 .add(MatrixRPGServerPlugin) 59 .add(super::SharedPlugin) 60 } 61 } 62 63 // Plugin for server-specific logic 64 pub struct MatrixRPGServerPlugin; 65 66 impl Plugin for MatrixRPGServerPlugin { 67 fn build(&self, app: &mut App) { 68 app.add_systems(Startup, init); 69 // Re-adding Replicate components to client-replicated entities must be done in this set for proper handling. 70 app.add_systems( 71 PreUpdate, 72 (replicate_players).in_set(MainSet::ClientReplication), 73 ); 74 // the physics/FixedUpdates systems that consume inputs should be run in this set 75 app.add_systems(FixedUpdate, movement); 76 //app.add_systems(Update, send_message); 77 app.add_systems(Update, handle_disconnections); 78 } 79 } 80 81 pub(crate) fn init(mut commands: Commands) { 82 commands.spawn(Camera2dBundle::default()); 83 commands.spawn(TextBundle::from_section( 84 "Server", 85 TextStyle { 86 font_size: 30.0, 87 color: Color::WHITE, 88 ..default() 89 }, 90 )); 91 } 92 93 /// Server disconnection system, delete all player entities upon disconnection 94 pub(crate) fn handle_disconnections( 95 mut disconnections: EventReader<DisconnectEvent>, 96 mut commands: Commands, 97 player_entities: Query<(Entity, &PlayerId)>, 98 ) { 99 for disconnection in disconnections.read() { 100 let client_id = disconnection.context(); 101 for (entity, player_id) in player_entities.iter() { 102 if player_id.0 == *client_id { 103 commands.entity(entity).despawn(); 104 } 105 } 106 } 107 } 108 109 /// Read client inputs and move players 110 pub(crate) fn movement( 111 mut position_query: Query<(&mut PlayerPosition, &PlayerId)>, 112 mut input_reader: EventReader<InputEvent<Inputs>>, 113 tick_manager: Res<TickManager>, 114 ) { 115 for input in input_reader.read() { 116 let client_id = input.context(); 117 if let Some(input) = input.input() { 118 debug!( 119 "Receiving input: {:?} from client: {:?} on tick: {:?}", 120 input, 121 client_id, 122 tick_manager.tick() 123 ); 124 125 for (position, player_id) in position_query.iter_mut() { 126 if player_id.0 == *client_id { 127 // NOTE: be careful to directly pass Mut<PlayerPosition> 128 // getting a mutable reference triggers change detection, unless you use `as_deref_mut()` 129 shared_movement_behaviour(position, input); 130 } 131 } 132 } 133 } 134 } 135 136 // // NOTE: you can use either: 137 // // - ServerMut (which is a wrapper around a bunch of resources used in lightyear) 138 // // - ResMut<ConnectionManager>, which is the actual resource used to send the message in this case. This is more optimized 139 // // because it enables more parallelism 140 // /// Send messages from server to clients (only in non-headless mode, because otherwise we run with minimal plugins 141 // /// and cannot do input handling) 142 // pub(crate) fn send_message( 143 // mut server: ResMut<ServerConnectionManager>, 144 // input: Option<Res<ButtonInput<KeyCode>>>, 145 // ) { 146 // if input.is_some_and(|input| input.pressed(KeyCode::KeyM)) { 147 // let message = Message1(5); 148 // info!("Send message: {:?}", message); 149 // server 150 // .send_message_to_target::<Channel1, Message1>(Message1(5), NetworkTarget::All) 151 // .unwrap_or_else(|e| { 152 // error!("Failed to send message: {:?}", e); 153 // }); 154 // } 155 // } 156 157 // Replicate the pre-spawned entities back to the client 158 // Note that this needs to run before FixedUpdate, since we handle client inputs in the FixedUpdate schedule (subject to change) 159 // And we want to handle deletion properly 160 pub(crate) fn replicate_players( 161 mut commands: Commands, 162 mut player_spawn_reader: EventReader<ComponentInsertEvent<PlayerPosition>>, 163 ) { 164 for event in player_spawn_reader.read() { 165 debug!("received player spawn event: {:?}", event); 166 let client_id = event.context(); 167 let entity = event.entity(); 168 169 // for all cursors we have received, add a Replicate component so that we can start replicating it 170 // to other clients 171 if let Some(mut e) = commands.get_entity(entity) { 172 e.insert(Replicate { 173 // we want to replicate back to the original client, since they are using a pre-spawned entity 174 replication_target: NetworkTarget::All, 175 // NOTE: even with a pre-spawned Predicted entity, we need to specify who will run prediction 176 // NOTE: Be careful to not override the pre-spawned prediction! we do not need to enable prediction 177 // because there is a pre-spawned predicted entity 178 prediction_target: NetworkTarget::Only(vec![*client_id]), 179 // we want the other clients to apply interpolation for the player 180 interpolation_target: NetworkTarget::AllExcept(vec![*client_id]), 181 ..default() 182 }); 183 } 184 } 185 }