use crate::api::{load_player_data_from_api, save_player_data_to_api}; use crate::components::{PlayerSpawnEvent, PlayerJoinEvent}; use crate::entity::{get_entity_id, Entity, EntityHandler}; use crate::manager::{ClientHandlerMessage, ClientManager, PhysicsData, Player, PlayerInput}; use crate::module::{AttachedModule, Module}; use crate::{recv, send, SCALE, ServerPingResponse, ServerPingResponseVersion}; use async_std::net::{TcpStream, TcpListener}; use async_std::{channel::Receiver, sync::RwLock}; use async_tungstenite::WebSocketStream; use bevy::prelude::EventWriter; use futures::stream::{SplitSink, SplitStream}; use futures::{FutureExt, SinkExt, StreamExt}; use log::{error, info, warn}; use nalgebra::{point, vector, Vector2}; use rand::Rng; use rapier2d_f64::prelude::{ Collider, ColliderBuilder, Isometry, MassProperties, RigidBodyBuilder, RigidBodyType, }; use starkingdoms_protocol::goodbye_reason::GoodbyeReason; use starkingdoms_protocol::message_s2c::{ MessageS2CChat, MessageS2CGoodbye, MessageS2CHello, MessageS2CModuleAdd, MessageS2CModuleRemove, MessageS2CModuleTreeUpdate, MessageS2CModulesUpdate, MessageS2CPlanetData, MessageS2CPlayersUpdate, MessageS2CPong, }; use protobuf::SpecialFields; use starkingdoms_protocol::module::ModuleType; use starkingdoms_protocol::state::State; use starkingdoms_protocol::{MessageC2S, MessageS2C, PROTOCOL_VERSION}; use std::error::Error; use std::f64::consts::PI; use std::net::SocketAddr; use std::sync::Arc; use std::time::{Duration, SystemTime}; use tungstenite::Message; use bevy::ecs::event::EventReader; pub fn handle_request( mut ev_playerspawn: EventWriter ) { async_std::task::spawn(async { let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); info!("Listening on {} for HTTP/WebSocket connections", addr); let try_socket = TcpListener::bind(&addr).await; let listener = match try_socket { Ok(l) => l, Err(e) => { error!("error binding to socket: {}", e); std::process::exit(1); } }; while let Ok((stream, addr)) = listener.accept().await { ev_playerspawn.send(PlayerSpawnEvent { stream, addr }); } Ok(()) }); } pub fn handle_spawn_event( mut events: EventReader ) { for join in events.iter() { let mut peek_buf = [0u8; 9]; loop { let read = join.stream.peek(&mut peek_buf).await?; if read == 9 { break; } } /* if peek_buf == *b"GET /ping" { info!("[{}] incoming http connection", addr); let ping_resp = serde_json::to_string(&ServerPingResponse { version: ServerPingResponseVersion { name: env!("STK_VERSION_NAME").to_string(), // Set by build.rs number: env!("STK_VERSION").to_string(), // Set by build.rs protocol: PROTOCOL_VERSION, channel: env!("STK_CHANNEL").to_string(), build: env!("STK_BUILD").to_string(), }, players: u32::try_from(CMGR.usernames.read().await.len())?, description: env!("STK_SLP_DESCRIPTION").to_string(), })?; let resp_str = format!( "HTTP/1.0 200 OK\nAccess-Control-Allow-Origin: *\nContent-Length: {}\n\n{}", ping_resp.len(), ping_resp ); let http_resp = resp_str.as_bytes(); conn.write_all(http_resp).await?; info!( "[{}] sent ping response (200 OK {} bytes)", remote_addr, ping_resp.len() ); return Ok(()); }*/ info!("[{}] incoming websocket connection", join.addr); // if its not GET /ping, assume its websocket and attempt to handshake with them let ws_stream; async_std::task::block_on(async { ws_stream = async_tungstenite::accept_async(join.stream).await?; }); let (ws_write, ws_read) = ws_stream.split(); let (tx, rx) = async_std::channel::unbounded(); let client = ClientHandler { tx }; // Acquire the write lock in a small scope, so it's dropped as quickly as possible { mgr.handlers.write().await.insert(addr, client); } info!("[{}] passing to client handler", addr); //forward the stream to the sink to achieve echo match handle_client( mgr.clone(), entities.clone(), physics_data.clone(), addr, rx, ws_write, ws_read, ) .await { Ok(_) => (), Err(e) if e.is::() => { #[allow(clippy::expect_used)] let e = { e.downcast::() .expect("unable to convert between types safely") }; if matches!(*e, async_tungstenite::tungstenite::Error::ConnectionClosed) { info!("[{}] connection closed normally", remote_addr); } else { error!("[{}] error in client thread: {}", remote_addr, e); } } Err(e) => { error!("[{}] error in client thread: {}", remote_addr, e); } } // clean up values left over { mgr.handlers.write().await.remove(&remote_addr); mgr.usernames.write().await.remove(&remote_addr); // remove player physics body let mut entity_write = entities.write().await; let mut data = physics_data.write().await; let mut rigid_body_set = data.rigid_body_set.clone(); let mut island_manager = data.island_manager.clone(); let mut collider_set = data.collider_set.clone(); let mut impulse_joint_set = data.impulse_joint_set.clone(); let mut multibody_joint_set = data.multibody_joint_set.clone(); let Some(player_id) = entity_write.get_player_id(remote_addr) else { warn!("[{}] player missing from entities.players", remote_addr); return Err("Player missing from entities.players".into()); }; if let Some(Entity::Player(player)) = entity_write.entities.get(&player_id) { rigid_body_set.remove( player.handle, &mut island_manager, &mut collider_set, &mut impulse_joint_set, &mut multibody_joint_set, true, ); for module in player.search_modules(&entity_write) { rigid_body_set.remove( module.handle, &mut island_manager, &mut collider_set, &mut impulse_joint_set, &mut multibody_joint_set, true, ); let module_id = entity_write .get_id_from_attached(&module) .ok_or("Tried to remove nonexistent module")?; entity_write.entities.remove(&module_id); } } else { warn!("tried to remove player that doesnt exist: #{}", player_id); } data.rigid_body_set = rigid_body_set; data.collider_set = collider_set; data.island_manager = island_manager; data.impulse_joint_set = impulse_joint_set; data.multibody_joint_set = multibody_joint_set; entity_write.entities.remove(&player_id); } } } pub async fn handle_client( mgr: ClientManager, entities: Arc>, data: Arc>, remote_addr: SocketAddr, rx: Receiver, mut client_tx: SplitSink, Message>, mut client_rx: SplitStream>, ) -> Result<(), Box> { let mut state = State::Handshake; let mut username = String::new(); let mut ping_timeout = SystemTime::now() + Duration::from_secs(10); loop { if let Ok(msg) = rx.recv().await { match msg { ClientHandlerMessage::Tick => {} // this intentionally does nothing, ClientHandlerMessage::ChatMessage { from, message } => { if matches!(state, State::Play) { let msg = MessageS2C::Chat(MessageS2CChat { from, message, special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; } } ClientHandlerMessage::PlayersUpdate { players } => { if matches!(state, State::Play) { let msg = MessageS2C::PlayersUpdate(MessageS2CPlayersUpdate { players, special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; } } ClientHandlerMessage::PlanetData { planets } => { if matches!(state, State::Play) { let msg = MessageS2C::PlanetData(MessageS2CPlanetData { planets, special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; } } ClientHandlerMessage::ModulesUpdate { modules } => { if matches!(state, State::Play) { let msg = MessageS2C::ModulesUpdate(MessageS2CModulesUpdate { modules, special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; } } ClientHandlerMessage::ModuleTreeUpdate { modules } => { if matches!(state, State::Play) { let msg = MessageS2C::ModuleTreeUpdate(MessageS2CModuleTreeUpdate { tree: modules, special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; } } } } else { info!("channel closed, shutting down"); break; } if ping_timeout < SystemTime::now() { error!("[{}] ping timeout", remote_addr); let msg = MessageS2C::Goodbye(MessageS2CGoodbye { reason: GoodbyeReason::PingPongTimeout.into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; break; } if let Some(pkt) = recv!(client_rx)? { match state { State::UNKNOWN => unreachable!(), State::Handshake => { match pkt { MessageC2S::Hello(pkt) => { info!("client sent hello"); // there is no way to not use unwrap here :/ #[allow(clippy::unwrap_used)] if !matches!(pkt.next_state.unwrap(), State::Play) { error!( "client sent unexpected state {:?} (expected: Play)", pkt.next_state ); let msg = MessageS2C::Goodbye(MessageS2CGoodbye { reason: GoodbyeReason::UnexpectedNextState.into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; break; } // check version if pkt.version != PROTOCOL_VERSION { error!( "client sent incompatible version {} (expected: {})", pkt.version, PROTOCOL_VERSION ); let msg = MessageS2C::Goodbye(MessageS2CGoodbye { reason: GoodbyeReason::UnsupportedProtocol.into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; break; } // determine if we can give them that username { if mgr .usernames .read() .await .values() .any(|u| *u == pkt.requested_username) { error!( "client requested username {} but it is in use", pkt.requested_username ); let msg: Vec = MessageS2C::Goodbye(MessageS2CGoodbye { reason: GoodbyeReason::UsernameTaken.into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; break; } } // username is fine { mgr.usernames .write() .await .insert(remote_addr, pkt.requested_username.clone()); } let msg = MessageS2C::Hello(MessageS2CHello { version: pkt.version, given_username: pkt.requested_username.clone(), special_fields: SpecialFields::default(), next_state: pkt.next_state, }) .try_into()?; send!(client_tx, msg).await?; state = pkt.next_state.unwrap(); username = pkt.requested_username; // make player rigidbody { let mut data_handle = data.write().await; let mut rigid_body_set = data_handle.rigid_body_set.clone(); let mut collider_set = data_handle.collider_set.clone(); let angle: f64 = { let mut rng = rand::thread_rng(); rng.gen::() * PI * 2. }; let player_body = RigidBodyBuilder::new(RigidBodyType::Dynamic) .translation(vector![ angle.cos() * 2050. / SCALE, angle.sin() * 2050.0 / SCALE ]) .rotation(angle + PI / 2.) .build(); let player_collider: Collider = ColliderBuilder::cuboid(25.0 / SCALE, 25.0 / SCALE) .mass_properties(MassProperties::new( point![0.0, 0.0], 0.0001, 0.005, )) .build(); let player_handle = rigid_body_set.insert(player_body); collider_set.insert_with_parent( player_collider, player_handle, &mut rigid_body_set, ); let mut player = Player { handle: player_handle, input: PlayerInput::default(), addr: remote_addr, auth_token: None, auth_user: None, children: [None, None, None, None], }; let mut e_write_handle = entities.write().await; if !pkt.user.is_empty() && !pkt.token.is_empty() { player.auth_token = Some(pkt.token.clone()); player.auth_user = Some(pkt.user.clone()); info!( "[{}] * Beamin: beaming in {} as {} with token {}", remote_addr, username, pkt.user, pkt.token ); let player_data = match load_player_data_from_api( &pkt.token, &pkt.user, &std::env::var("STK_API_KEY")?, ) .await { Ok(d) => d, Err(e) => { warn!( "[{}] * Beamin: ABORTED. API returned error: {}", remote_addr, e ); e_write_handle .entities .insert(get_entity_id(), Entity::Player(player)); continue; } }; info!( "[{}] Beamin: loaded player data! {:?}", remote_addr, player_data ); player.load_api_data(&player_data); } let player_id = get_entity_id(); e_write_handle .entities .insert(player_id, Entity::Player(player)); data_handle.rigid_body_set = rigid_body_set; data_handle.collider_set = collider_set; } } MessageC2S::Goodbye(pkt) => { info!("client sent goodbye: {:?}", pkt.reason); break; } _ => { error!( "client sent unexpected packet {:?} for state {:?}", pkt, state ); let msg = MessageS2C::Goodbye(MessageS2CGoodbye { reason: GoodbyeReason::UnexpectedPacket.into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; break; } } } State::Play => match pkt { MessageC2S::Hello { .. } => { error!( "client sent unexpected packet {:?} for state {:?}", pkt, state ); let msg = MessageS2C::Goodbye(MessageS2CGoodbye { reason: GoodbyeReason::UnexpectedPacket.into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; break; } MessageC2S::Goodbye(pkt) => { info!("client sent goodbye: {:?}", pkt.reason); break; } MessageC2S::Chat(pkt) => { info!("[{}] CHAT: [{}] {}", remote_addr, username, pkt.message); let read_handle = mgr.handlers.read().await; #[allow(clippy::significant_drop_in_scrutinee)] // i know for (_addr, client_thread) in read_handle.iter() { match client_thread .tx .send(ClientHandlerMessage::ChatMessage { from: username.clone(), message: pkt.message.clone(), }) .await { Ok(_) => (), Err(e) => { error!("unable to update a client thread: {}", e); } } } } MessageC2S::Ping(_) => { let msg = MessageS2C::Pong(MessageS2CPong { special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; ping_timeout = SystemTime::now() + Duration::from_secs(10); } #[allow(clippy::significant_drop_tightening)] // TODO: Remove when this lint is developed more MessageC2S::Input(p) => { let mut handle = entities.write().await; let id = handle .get_player_id(remote_addr) .ok_or("could not get player id")?; if let Entity::Player(ref mut me) = handle .entities .get_mut(&id) .ok_or("player disconnected but continued to send packets")? { me.input.up = p.up_pressed; me.input.down = p.down_pressed; me.input.left = p.left_pressed; me.input.right = p.right_pressed; } } MessageC2S::AuthenticateAndBeamOut(p) => { info!( "[{}] * Beaming out {} as {} with realm token {}", remote_addr, username, p.user_id, p.token ); let player = entities .read() .await .get_player(remote_addr) .ok_or("Player sending messages after disconnect")?; if Some(p.token) != player.auth_token || Some(p.user_id) != player.auth_user { warn!("[{}] invalid beamout packet, ignoring", remote_addr); continue; } match save_player_data_to_api( &player.as_api_data(), &player .auth_token .ok_or("Tried to beamout without an auth token")?, &player .auth_user .ok_or("Tried to beamout without setting a user")?, &std::env::var("STK_API_KEY")?, ) .await { Ok(_) => { info!("[{}] * Beamed out successfully", remote_addr); let msg = MessageS2C::Goodbye(MessageS2CGoodbye { reason: GoodbyeReason::Done.into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; break; } Err(e) => { error!("[{}] error beaming out: {}", remote_addr, e); } } } MessageC2S::MouseInput(_p) => { //debug!("[{}] player input: {:?}", remote_addr, p); } #[allow(clippy::significant_drop_tightening)] // TODO: Remove when this lint is developed more MessageC2S::ModuleDetach(p) => { let mut entities = entities.write().await; let mut data_handle = data.write().await; let module: Option; //debug!("[{}] detach: {:?}", remote_addr, p); //debug!("[{}] {:?}", remote_addr, entities.entities); if let Some(Entity::AttachedModule(p_module)) = entities.entities.get_mut(&p.module_id) { module = Some(p_module.clone()); } else { warn!("[{}] attempted to detach nonexistent module", remote_addr); continue; } let player_id = entities .get_player_id(remote_addr) .ok_or("player does not exist")?; let module_id = AttachedModule::detach( &mut data_handle, &mut entities, player_id, &module.ok_or("cannot detach module that doesn't exist")?, ) .ok_or("detach failed")?; let module = entities .get_module_from_id(module_id) .ok_or("player does not exist")?; let body = data_handle .rigid_body_set .get(module.handle) .ok_or("module rigidbody does not exist")?; let children = module.children.iter().map(|c| { starkingdoms_protocol::module::Attachment { id: c.child, slot: 0, special_fields: SpecialFields::default(), } }).collect(); let prot_module = starkingdoms_protocol::module::Module { module_type: module.module_type.into(), rotation: body.rotation().angle(), x: body.translation().x * SCALE, y: body.translation().y * SCALE, id: module_id, flags: module.flags, children, special_fields: SpecialFields::default(), }; let msg = MessageS2C::ModuleRemove(MessageS2CModuleRemove { module: Some(prot_module).into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; } MessageC2S::ModuleGrabBegin(p) => { if let Entity::Module(_module) = entities .write() .await .entities .get_mut(&p.module_id) .ok_or("module does not exist")? { //debug!("[{}] grab begin: {:?}, flags: {}", remote_addr, p, module.flags); } } #[allow(clippy::significant_drop_tightening)] // TODO: Remove when this lint is developed more MessageC2S::ModuleGrabEnd(p) => { let mut entities = entities.write().await; let mut module: Option = None; let mut did_attach = false; let mut attached_id = None; if let Entity::Module(p_module) = entities .entities .get_mut(&p.module_id) .ok_or("module does not exist")? { module = Some(p_module.clone()); //debug!("[{}] grab end: {:?}", remote_addr, p); } let mut data_handle = data.write().await; let player_id = entities .get_player_id(remote_addr) .ok_or("player entity does not exist")?; let player = entities .get_player(remote_addr) .ok_or("player does not exist")?; let body = data_handle .rigid_body_set .get(player.handle) .ok_or("player rigidbody does not exist")?; let (x, y) = ( body.translation().x - p.worldpos_x / SCALE, body.translation().y - p.worldpos_y / SCALE, ); let angle = -body.rotation().angle(); let (x, y) = ( x.mul_add(angle.cos(), -y * angle.sin()), x.mul_add(angle.sin(), y * angle.cos()), ); let mut attachment_slot: Option = None; if 1.5 < y && y < 3. && -2. < x && x < 2. { attachment_slot = Some(2); } else if -3. < y && y < -1.5 && -2. < x && x < 2. { attachment_slot = Some(0); } else if -3. < x && x < -1.5 && -2. < y && y < 2. { attachment_slot = Some(3); } else if 1.5 < x && x < 3. && -2. < y && y < 2. { attachment_slot = Some(1); } if let Some(slot) = attachment_slot { attached_id = Some(AttachedModule::attach( &mut data_handle, &mut entities, player_id, player_id, &module.clone().ok_or("module is None")?, slot, )); did_attach = true; } let modules = player.search_modules(&entities); for attached in modules { let body = data_handle .rigid_body_set .get(attached.handle) .ok_or("attached module rigidbody does not exist")?; let (x, y) = ( body.translation().x - p.worldpos_x / SCALE, body.translation().y - p.worldpos_y / SCALE, ); let angle = -body.rotation().angle(); let (x, y) = ( x.mul_add(angle.cos(), -y * angle.sin()), x.mul_add(angle.sin(), y * angle.cos()), ); let parent_id = entities .get_id_from_attached(&attached) .ok_or("attached module does not exist")?; // ghostly: this is cursed as hell // please find a better way in the future lmao let mut attachment_slot: Option = None; if 1.5 < y && y < 3. && -2. < x && x < 2. { attachment_slot = Some(2); } else if -3. < x && x < -1.5 && -2. < y && y < 2. { attachment_slot = Some(3); } else if 1.5 < x && x < 3. && -2. < y && y < 2. { attachment_slot = Some(1); } if let Some(slot) = attachment_slot { if let Some(module) = module.clone() { match attached.module_type { ModuleType::Cargo => { continue; } ModuleType::LandingThruster => { continue; } ModuleType::LandingThrusterSuspension => { continue; } _ => {} }; attached_id = Some(AttachedModule::attach( &mut data_handle, &mut entities, parent_id, player_id, &module, slot, )); did_attach = true; } else { return Err("module is None")?; } } } if !did_attach { let body = data_handle .rigid_body_set .get_mut(module.as_ref().ok_or("module does not exist")?.handle) .ok_or("module rigidbody does not exist")?; body.set_position( Isometry::new( Vector2::new(p.worldpos_x / SCALE, p.worldpos_y / SCALE), body.rotation().angle(), ), true, ); if let Some(module) = module { if module.module_type == ModuleType::LandingThruster { let suspension = entities.get_module_from_id(module.children.get(0).ok_or("suspension child not found")?.child) .ok_or("suspension not found")?; let body = data_handle .rigid_body_set .get_mut(suspension.handle) .ok_or("suspension rigidbody does not exist")?; body.set_position( Isometry::new( Vector2::new(p.worldpos_x / SCALE, p.worldpos_y / SCALE), body.rotation().angle(), ), true, ); } } } else if let Some(Ok(id)) = attached_id { let prot_module = entities .get_attached_from_id(id) .ok_or("attached module does not exist")? .to_protocol(&entities, &data_handle.rigid_body_set); let msg = MessageS2C::ModuleAdd(MessageS2CModuleAdd { module: Some(prot_module.ok_or("attached module does not exist")?) .into(), special_fields: SpecialFields::default(), }) .try_into()?; send!(client_tx, msg).await?; } else { warn!("attached ID does not exist"); } } }, } } } Ok(()) }