mod incoming_parts; mod incoming_planets; mod key_input; mod starfield; use crate::client::incoming_parts::incoming_parts_plugin; use crate::client::incoming_planets::incoming_planets_plugin; use crate::client::key_input::key_input_plugin; use crate::client::starfield::starfield_plugin; use crate::ecs::{CursorWorldCoordinates, MainCamera, Player}; use bevy::core_pipeline::fxaa::Fxaa; use bevy::prelude::*; use bevy::window::PrimaryWindow; use bevy_replicon::prelude::RepliconChannels; use bevy_replicon::shared::server_entity_map::ServerEntityMap; use bevy_replicon_renet2::RenetChannelsExt; #[cfg(not(target_arch = "wasm32"))] use bevy_replicon_renet2::netcode::NativeSocket; use bevy_replicon_renet2::netcode::{ClientAuthentication, NetcodeClientTransport}; #[cfg(target_arch = "wasm32")] use bevy_replicon_renet2::netcode::{ClientSocket, WebSocketClient, WebSocketClientConfig}; use bevy_replicon_renet2::renet2::{ConnectionConfig, RenetClient}; use std::net::{IpAddr, SocketAddr, UdpSocket}; use std::time::SystemTime; pub struct ClientPlugin { #[cfg(target_arch = "wasm32")] pub server: url::Url, #[cfg(not(target_arch = "wasm32"))] pub server: SocketAddr, } impl Plugin for ClientPlugin { fn build(&self, app: &mut App) { let server = self.server; app.insert_resource(CursorWorldCoordinates(None)) .add_systems( Startup, move |mut commands: Commands, channels: Res| { let current_time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap(); let client_id = current_time.as_millis() as u64; #[cfg(target_arch = "wasm32")] { let socket_config = WebSocketClientConfig { server_url: server.clone(), }; let socket = WebSocketClient::new(socket_config).unwrap(); let client = RenetClient::new( ConnectionConfig::from_channels( channels.server_configs(), channels.client_configs(), ), socket.is_reliable(), ); let authentication = ClientAuthentication::Unsecure { socket_id: 1, server_addr: socket.server_address(), client_id: current_time.as_millis() as u64, user_data: None, protocol_id: 0, }; let mut transport = NetcodeClientTransport::new(current_time, authentication, socket) .unwrap(); commands.insert_resource(client); commands.insert_resource(transport); } #[cfg(not(target_arch = "wasm32"))] { let bind = match server.ip() { IpAddr::V4(_) => "127.0.0.1:0", IpAddr::V6(_) => "[::1]:0", }; let client_socket = NativeSocket::new(UdpSocket::bind(bind).unwrap()).unwrap(); let authentication = ClientAuthentication::Unsecure { socket_id: 0, server_addr: server, client_id: current_time.as_millis() as u64, user_data: None, protocol_id: 0, }; let client = RenetClient::new( ConnectionConfig::from_channels( channels.server_configs(), channels.client_configs(), ), false, ); let transport = NetcodeClientTransport::new( current_time, authentication, client_socket, ) .unwrap(); commands.insert_resource(client); commands.insert_resource(transport); } }, ) .add_systems(Startup, setup_graphics) .add_systems(Update, update_cursor_position) .add_systems(Update, follow_camera) .add_systems(Update, find_me) .add_plugins(incoming_planets_plugin) .add_plugins(incoming_parts_plugin) .add_plugins(key_input_plugin) .add_plugins(starfield_plugin); } } #[derive(Component)] pub struct Me; fn find_me( mut commands: Commands, q_clients: Query<(Entity, &Player), Added>, entity_map: Res, ) { for (entity, player) in q_clients.iter() { let this_id_clientside = entity_map.to_client().get(&player.client).unwrap(); if *this_id_clientside == entity { commands.entity(entity).insert(Me); } } } fn setup_graphics(mut commands: Commands) { commands .spawn(Camera2d) .insert(Camera { ..default() }) .insert(Fxaa::default()) .insert(MainCamera); } fn follow_camera( mut camera: Query<&mut Transform, (With, Without)>, player: Query<&Transform, With>, ) { let mut camera = camera.single_mut().unwrap(); let Ok(player) = player.single() else { return; }; camera.translation = player.translation; } fn update_cursor_position( q_windows: Query<&Window, With>, q_camera: Query<(&Camera, &GlobalTransform), With>, mut coords: ResMut, ) { let (camera, camera_transform) = q_camera.single().unwrap(); let window = q_windows.single().unwrap(); if let Some(world_position) = window .cursor_position() .and_then(|cursor| camera.viewport_to_world(camera_transform, cursor).ok()) .map(|ray| ray.origin.truncate()) { coords.0 = Some(world_position); } else { coords.0 = None; } }