use std::{collections::HashMap, 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::{packet::{MessageType, Packet, Part, Planet, ProtoPartFlags}, proto_part_flags, proto_transform, unpack_savefile, PartType as c_PartType}; use crate::{ config::StkConfig, crafting::components::{IsMining, VarietyMaterialStorage}, module::{ component::{Attach, CanAttach, LooseAttach, PartBundle, PartFlags, PartType}, save::load_savefile, }, planet::PlanetType, ws::{PacketMessageConvert, WsEvent}, AppKeys, 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, ) -> bool { 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_message(), }); event_queue.push(WsEvent::Close { addr: *from }); return false; } }; 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_message(), }); event_queue.push(WsEvent::Close { addr: *from }); return false; } 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_message(), }); } 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_message(), }); event_queue.push(WsEvent::Close { addr: *from }); return false; } true } 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); transform.translation += Vec3::new(6000.0, 0.0, 0.0); let player_comp = Player { addr: *from, username, input: Input::default(), selected: None, save_eligibility: false, energy_capacity: part!(c_PartType::Hearty.into()).energy_capacity, energy: part!(c_PartType::Hearty.into()).energy_capacity, }; let mut entity_id = commands.spawn(PartBundle { part_type: c_PartType::Hearty.into(), transform: TransformBundle::from(transform), flags: PartFlags { attached: false }, ..default() }); entity_id .insert(VarietyMaterialStorage { materials: HashMap::new(), capacity: 200, }) .insert(IsMining(false)) .insert(Collider::cuboid(0.5, 0.5)) .insert(AdditionalMassProperties::MassProperties(MassProperties { local_center_of_mass: vec2(0.0, 0.0), mass: part!(c_PartType::Hearty.into()).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, module_counts) = 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: 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_message(), }); } } 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, ) { // response in the handshake let packet = Packet::LoginResponse { id: index, }; event_queue.push(WsEvent::Send { to: *from, message: packet.into_message(), }); // 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.0, transform: proto_transform!(Transform::from_translation( translation * CLIENT_SCALE )), radius: planet!(*planet_type).size * CLIENT_SCALE, }, )); } let packet = Packet::PlanetPositions { planets }; event_queue.push(WsEvent::Send { to: *from, message: packet.into_message(), }); // 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_message(), }); // 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_message(), }); let packet = Packet::Message { message_type: MessageType::Server, actor: "SERVER".to_string(), content: format!("{} has joined the server!", username), }; event_queue.push(WsEvent::Broadcast { message: packet.into_message(), }); // 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.0, 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.0, transform: proto_transform!(Transform::from_translation( transform.translation * CLIENT_SCALE )), flags: proto_part_flags!(flags), }, )); } parts.push(( index, Part { part_type: c_PartType::Hearty.into(), transform: proto_transform!(Transform::from_translation(transform.translation) .with_rotation(transform.rotation)), flags: ProtoPartFlags { attached: false }, }, )); for part in parts { let packet = Packet::SpawnPart { id: part.0, part: part.1 }; event_queue.push(WsEvent::Send { to: *from, message: packet.into_message(), }); } // and send the welcome message :) let packet = Packet::Message { message_type: 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_message(), }); let packet = Packet::Message { message_type: 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_message(), }); let packet = Packet::Message { message_type: 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_message(), }); }