use std::net::SocketAddr; use bevy::{math::vec2, prelude::*}; use bevy_rapier2d::prelude::*; use hmac::{Hmac, Mac}; use jwt::VerifyWithKey; use rand::Rng; use sha2::Sha256; use starkingdoms_common::unpack_savefile; use crate::{config::StkConfig, module::{component::{Attach, CanAttach, LooseAttach, PartBundle, PartFlags, PartType}, save::load_savefile}, planet::PlanetType, proto_part_flags, proto_transform, ws::WsEvent, AppKeys, MessageType, Packet, Part, Planet, ProtoPartFlags, UserToken, CLIENT_SCALE}; use super::component::{Input, Player}; pub fn join_auth( jwt: Option, app_keys: AppKeys, from: &SocketAddr, event_queue: &mut Vec, server_config: StkConfig, ) -> Result<(), ()> { if let Some(token) = jwt { let key: Hmac = Hmac::new_from_slice(&app_keys.app_key).unwrap(); let claims: UserToken = match token.verify_with_key(&key) { Ok(c) => c, Err(e) => { event_queue.push(WsEvent::Send { to: *from, message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Token is invalid or verification failed: {e}. Please log in again, or contact StarKingdoms staff if the problem persists.") }.into(), }); event_queue.push(WsEvent::Close { addr: *from }); return Err(()); } }; if claims.permission_level < server_config.security.required_permission_level { event_queue.push(WsEvent::Send { to: *from, message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Permission level {} is too low, {} is required. If your permissions were just changed, you need to log out and log back in for the change to take effect. If you believe this is a mistake, contact StarKingdoms staff.", claims.permission_level, server_config.security.required_permission_level) }.into(), }); event_queue.push(WsEvent::Close { addr: *from }); return Err(()); } event_queue.push(WsEvent::Send { to: *from, message: Packet::Message { message_type: MessageType::Server, actor: "StarKingdoms Team".to_string(), content: "Thank you for participating in the StarKingdoms private alpha! Your feedback is essential to improving the game, so please give us any feedback you have in the Discord! <3".to_string() }.into(), }); } else if server_config.security.required_permission_level != 0 { event_queue.push(WsEvent::Send { to: *from, message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: "Authentication is required to join this server at the moment. Log in and try again, or try again later.".to_string() }.into(), }); event_queue.push(WsEvent::Close { addr: *from }); return Err(()); } Ok(()) } pub fn spawn_player( commands: &mut Commands, from: &SocketAddr, username: String, ) -> (Entity, Transform, Player) { // generate random angle let angle: f32 = { let mut rng = rand::thread_rng(); rng.gen::() * std::f32::consts::PI * 2. }; // convert to cartesian with 30.0 meter radius let mut transform = Transform::from_xyz(angle.cos() * 30.0, angle.sin() * 30.0, 0.0); transform.rotate_z(angle); let player_comp = Player { addr: *from, username, input: Input::default(), selected: None, save_eligibility: false, energy_capacity: part!(PartType::Hearty).energy_capacity, energy: part!(PartType::Hearty).energy_capacity, }; let mut entity_id = commands.spawn(PartBundle { part_type: PartType::Hearty, transform: TransformBundle::from(transform), flags: PartFlags { attached: false }, ..default() }); entity_id .insert(Collider::cuboid(0.5, 0.5)) .insert(AdditionalMassProperties::MassProperties(MassProperties { local_center_of_mass: vec2(0.0, 0.0), mass: part!(PartType::Hearty).mass, principal_inertia: 7.5, })); (entity_id.id(), transform, player_comp) } pub fn load_save( commands: &mut Commands, transform: Transform, id: Entity, save: Option, app_keys: AppKeys, attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach, &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags), (Without, Without), >, part_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Velocity, Option<&LooseAttach>, &mut PartFlags), (Without, Without, Without), >, player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity, &mut Attach, &mut PartFlags), Without, >, player_comp: &mut Player, attach: &mut Attach, from: &SocketAddr, event_queue: &mut Vec, ) { if let Some(save) = save { // attempt to decode the savefile if let Ok(savefile) = unpack_savefile(&app_keys.app_key, save) { // HEY! GHOSTLY! THIS SAVE FILE IS VALID! PLEASE LOAD IT! // THANKS! let children = load_savefile( commands, transform, id, id, savefile.children, attached_query, part_query, player_query, player_comp, ); // update energy and children player_comp.energy = player_comp.energy_capacity; attach.children = children; } else { let packet = Packet::Message { message_type: crate::packet::MessageType::Error, actor: "SERVER".to_string(), content: "Savefile signature corrupted or inner data invalid. Save was not loaded. Contact StarKingdoms staff for assistance.".to_string(), }; event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); } } else { // nothing to do } } pub fn packet_stream( planet_query: &Query<(Entity, &PlanetType, &Transform)>, event_queue: &mut Vec, from: &SocketAddr, player_query: &Query<(Entity, &mut Player, &Transform, &Velocity, &mut Attach, &mut PartFlags), Without, >, index: u32, username: String, part_query: &Query<(Entity, &PartType, &mut Transform, &mut Velocity, Option<&LooseAttach>, &mut PartFlags), (Without, Without, Without), >, attached_query: &Query<(Entity, &PartType, &mut Transform, &mut Attach, &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags), (Without, Without), >, transform: Transform, ) { // tell this player the planets let mut planets = Vec::new(); for (entity, planet_type, transform) in planet_query.iter() { let translation = transform.translation; planets.push(( entity.index(), Planet { planet_type: *planet_type, transform: proto_transform!(Transform::from_translation( translation * CLIENT_SCALE )), radius: match *planet_type { PlanetType::Earth => { planet!(PlanetType::Earth).size * CLIENT_SCALE } PlanetType::Moon => { planet!(PlanetType::Moon).size * CLIENT_SCALE } PlanetType::Mars => { planet!(PlanetType::Mars).size * CLIENT_SCALE } }, }, )); } let packet = Packet::PlanetPositions { planets }; event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); // tell the player already existing users let mut players = Vec::new(); for (entity, player, _, _, _, _) in player_query.iter() { players.push((entity.index(), player.username.clone())); } let packet = Packet::PlayerList { players }; event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); // tell other players that a player has spawned in let packet = Packet::SpawnPlayer { id: index, username: username.to_string(), }; event_queue.push(WsEvent::Broadcast { message: packet.into(), }); let packet = Packet::Message { message_type: crate::packet::MessageType::Server, actor: "SERVER".to_string(), content: format!("{} has joined the server!", username), }; event_queue.push(WsEvent::Broadcast { message: packet.into(), }); // tell the player where parts are let mut parts = Vec::new(); for (entity, part_type, transform, _, _, flags) in part_query.iter() { parts.push(( entity.index(), Part { part_type: *part_type, transform: proto_transform!(Transform::from_translation( transform.translation * CLIENT_SCALE )), flags: proto_part_flags!(flags), }, )); } for (entity, part_type, transform, _, _, _, _, flags) in attached_query.iter() { parts.push(( entity.index(), Part { part_type: *part_type, transform: proto_transform!(Transform::from_translation( transform.translation * CLIENT_SCALE )), flags: proto_part_flags!(flags), }, )); } parts.push(( index, Part { part_type: PartType::Hearty, transform: proto_transform!(Transform::from_translation( transform.translation ) .with_rotation(transform.rotation)), flags: ProtoPartFlags { attached: false }, }, )); let packet = Packet::PartPositions { parts }; event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); // and send the welcome message :) let packet = Packet::Message { message_type: crate::packet::MessageType::Server, actor: "SERVER".to_string(), content: format!( "starkingdoms-server v{} says hello", env!("CARGO_PKG_VERSION") ), }; event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); let packet = Packet::Message { message_type: crate::packet::MessageType::Server, actor: "SERVER".to_string(), content: "Welcome to StarKingdoms.IO! Have fun!".to_string(), }; event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); let packet = Packet::Message { message_type: crate::packet::MessageType::Server, actor: "SERVER".to_string(), content: "Found a bug? Have a feature request? Please bring this and all other feedback to the game's official Discord server! Join here: https://discord.gg/3u7Yw8DWtQ".to_string(), }; event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); }