use crate::{ GameState, config::NetworkingConfig, protocol::{ ClientEnteredPlaying, TbMapEntityId, TbMapEntityMapping, messages::DespawnTbMapEntity, }, tb_entities::{Movable, Platform, PlatformTarget}, }; use avian3d::prelude::{ Collider, ColliderAabb, ColliderDensity, ColliderMarker, ColliderOf, ColliderTransform, CollisionEventsEnabled, CollisionLayers, Sensor, }; use bevy::{ecs::bundle::BundleFromComponents, prelude::*, scene::SceneInstance}; use bevy_replicon::{ client::{ClientSystems, confirm_history::ConfirmHistory}, prelude::{ClientState, ClientTriggerExt, RepliconChannels}, }; use bevy_replicon_renet::{ RenetChannelsExt, netcode::{ClientAuthentication, NetcodeClientTransport, NetcodeError}, renet::{ConnectionConfig, RenetClient}, }; use bevy_trenchbroom::geometry::Brushes; use std::{ net::{Ipv4Addr, UdpSocket}, time::SystemTime, }; pub mod audio; pub mod backpack; pub mod control; pub mod debug; pub mod enemy; pub mod heal_effect; pub mod player; mod settings; pub mod setup; pub mod steam; pub mod ui; pub fn plugin(app: &mut App) { app.add_plugins(( backpack::plugin, control::plugin, debug::plugin, enemy::plugin, heal_effect::plugin, player::plugin, setup::plugin, audio::plugin, steam::plugin, ui::plugin, settings::plugin, )); app.add_systems( OnEnter(GameState::Connecting), connect_to_server.run_if(|config: Res| config.server.is_some()), ); app.add_systems(Update, despawn_absent_map_entities); app.add_systems( PreUpdate, (migrate_remote_entities, ApplyDeferred) .chain() .after(ClientSystems::Receive), ); app.add_systems(OnEnter(ClientState::Connected), on_connected_state); app.add_systems(OnExit(ClientState::Connected), on_disconnect); } // // Client logic // fn on_connected_state(mut commands: Commands, mut game_state: ResMut>) { info!("sent entered playing signal"); commands.client_trigger(ClientEnteredPlaying); game_state.set(GameState::Playing); } fn on_disconnect() { info!("disconnected from the server"); } // // Renet // fn connect_to_server( mut commands: Commands, config: Res, channels: Res, ) -> 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.insert_resource(client); commands.insert_resource(client_transport(&config)?); Ok(()) } fn client_transport(config: &NetworkingConfig) -> Result { 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, 0))?; 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, }; info!("attempting connection to {server_addr}"); NetcodeClientTransport::new(current_time, authentication, socket) } #[allow(clippy::type_complexity)] fn migrate_remote_entities( query: Query<(Entity, &TbMapEntityId), (Added, With)>, children: Query<&Children>, mut commands: Commands, mut mapping: ResMut, ) { for (serverside, tb_id) in query.iter() { received_remote_map_entity(serverside, tb_id.id, &children, &mut mapping, &mut commands); } } fn received_remote_map_entity( serverside: Entity, tb_id: u64, children: &Query<&Children>, mapping: &mut TbMapEntityMapping, commands: &mut Commands, ) { let Some(clientside) = mapping.0.remove(&tb_id) else { warn!("received unknown MapEntity ID `{tb_id:?}`"); return; }; // cannot just use `take` directly with a bundle because then any missing component would cause // the entire bundle to fail move_component::(commands, clientside, serverside); move_component::<( Collider, ColliderAabb, ColliderDensity, ColliderMarker, CollisionLayers, )>(commands, clientside, serverside); move_component::(commands, clientside, serverside); move_component::(commands, clientside, serverside); move_component::(commands, clientside, serverside); move_component::(commands, clientside, serverside); move_component::(commands, clientside, serverside); move_component::(commands, clientside, serverside); move_component::(commands, clientside, serverside); move_component::(commands, clientside, serverside); move_component::(commands, clientside, serverside); if let Ok(children) = children.get(clientside) { for child in children.iter() { commands.entity(child).insert(ChildOf(serverside)); } } commands.entity(clientside).despawn(); } fn move_component( commands: &mut Commands, from: Entity, to: Entity, ) { commands.queue(move |world: &mut World| { let comp = world.entity_mut(from).take::(); if let Some(comp) = comp { world.entity_mut(to).insert(comp); } }); } fn despawn_absent_map_entities( mut commands: Commands, mut messages: MessageReader, mut map: ResMut, ) { 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(); } }