use bevy::{ecs::event::ManualEventReader, math::{vec2, vec3}, prelude::*}; use bevy_rapier2d::prelude::*; use component::Player; use hmac::{Hmac, Mac}; use rand::Rng; use sha2::Sha256; use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData}; use jwt::VerifyWithKey; use crate::{config::StkConfig, err_or_cont, mathutil::rot2d, module::{component::{Attach, CanAttach, LooseAttach, PartBundle, PartFlags, PartType}, save::load_savefile, PART_HALF_SIZE}, part, planet::PlanetType, proto_part_flags, proto_transform, ws::WsEvent, AppKeys, MessageType, Packet, Part, Planet, ProtoPartFlags, UserToken, CLIENT_SCALE}; pub mod component; pub mod packet; pub fn on_message( mut commands: Commands, planet_query: Query<(Entity, &PlanetType, &Transform)>, mut part_query: Query< ( Entity, &PartType, &mut Transform, &mut Velocity, Option<&LooseAttach>, &mut PartFlags, ), (Without, Without, Without), >, mut attached_query: Query< ( Entity, &PartType, &mut Transform, &mut Attach, &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags, ), (Without, Without), >, mut player_query: Query< ( Entity, &mut Player, &Transform, &Velocity, &mut Attach, &mut PartFlags, ), Without, >, mut packet_recv: Local>, mut packet_event_send: ResMut>, app_keys: Res, server_config: Res, ) { let mut event_queue = Vec::new(); for ev in packet_recv.read(&packet_event_send) { if let WsEvent::Recv { from, message } = ev { let packet: Packet = err_or_cont!(message.try_into()); match packet { Packet::ClientLogin { username, save, jwt, } => { // auth // plz no remove 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 }); continue; } }; 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 }); continue; } 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 }); continue; } let angle: f32 = { let mut rng = rand::thread_rng(); rng.gen::() * std::f32::consts::PI * 2. }; let mut transform = Transform::from_xyz(angle.cos() * 30.0, angle.sin() * 30.0, 0.0); transform.rotate_z(angle); let mut player_comp = Player { addr: *from, username: username.to_string(), input: component::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 }, }); 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, })) .insert(ExternalImpulse { impulse: Vec2::ZERO, torque_impulse: 0.0, }) .insert(ExternalForce::default()) .insert(ReadMassProperties::default()) .insert(Velocity::default()) .insert(RigidBody::Dynamic); let id = entity_id.id().index(); let entity = entity_id.id(); let mut attach = Attach { associated_player: None, parent: None, children: [None, None, None, None], }; 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( &mut commands, transform, entity, entity, savefile.children, &mut attached_query, &mut part_query, &mut player_query, &mut player_comp, ); 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 } let mut entity_id = commands.entity(entity); entity_id.insert(player_comp); entity_id.insert(attach); // 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 { 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, 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 { 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 { 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(( id, 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(), }); } Packet::SendMessage { target, content } => { // find our player let mut player = None; for (_, q_player, _, _, _, _) in &player_query { if q_player.addr == *from { player = Some(q_player); } } let player = player.unwrap(); if let Some(target_username) = target { let mut target_player = None; for (_, q_player, _, _, _, _) in &player_query { if q_player.username == target_username { target_player = Some(q_player); } } let target_player = target_player.unwrap(); let packet = Packet::Message { message_type: crate::packet::MessageType::Direct, actor: player.username.clone(), content, }; event_queue.push(WsEvent::Send { to: target_player.addr, message: packet.clone().into(), }); event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); } else { // send to general chat let packet = Packet::Message { message_type: crate::packet::MessageType::Chat, actor: player.username.clone(), content, }; event_queue.push(WsEvent::Broadcast { message: packet.into(), }); } } Packet::PlayerInput { up, down, left, right, } => { for (_, mut q_player, _, _, _, _) in &mut player_query { if q_player.addr == *from { q_player.input.up = up; q_player.input.down = down; q_player.input.left = left; q_player.input.right = right; } } } Packet::PlayerMouseInput { x, y, released, button: _, } => { let x = x / CLIENT_SCALE; let y = y / CLIENT_SCALE; for (entity, mut q_player, _transform, _velocity, _attach, _) in &mut player_query { if q_player.addr == *from { if released { let select = if let Some(s) = q_player.selected { s } else { break; }; q_player.selected = None; if attached_query.contains(select) { let module = attached_query.get(select).unwrap(); let attach = module.3.clone(); let lost_energy_capacity = crate::module::detach_recursive( &mut commands, module.0, &mut attached_query, &mut player_query, ); let mut module = attached_query.get_mut(select).unwrap(); module.2.translation = vec3(x, y, 0.); if *module.1 == PartType::LandingThruster { let sub_entity = attach.children[2].unwrap(); let mut suspension = attached_query.get_mut(sub_entity).unwrap(); suspension.2.translation = vec3(x, y, 0.); } let mut player = player_query.get_mut(entity).unwrap().1; player.energy_capacity -= lost_energy_capacity; player.energy = std::cmp::min(player.energy, player.energy_capacity); break; } if crate::module::attach_on_module_tree( x, y, &mut commands, entity, select, entity, &mut attached_query, &mut part_query, &mut player_query, ) { let mut part = part_query.get_mut(select).unwrap(); part.5.attached = true; // all of this code is cursed. what the hell is it actually doing break; } // move module to cursor since no attach let mut part = part_query.get_mut(select).unwrap(); part.2.translation = vec3(x, y, 0.); if *part.1 == PartType::LandingThruster { if let Some(loose_attach) = part.4 { let sub_entity = loose_attach.children[2].unwrap(); let mut part = part_query.get_mut(sub_entity).unwrap(); part.2.translation = vec3(x, y, 0.); } } break; } for (m_entity, part_type, transform, m_attach, _velocity, _, _, _) in &attached_query { if *part_type == PartType::LandingThrusterSuspension { continue; } let pos = transform.translation; let rel_x = pos.x - x; let rel_y = pos.y - y; let angle = -transform.rotation.z; let x = rel_x * angle.cos() - rel_y * angle.sin(); let y = rel_x * angle.sin() + rel_y * angle.cos(); let mut bound = [-0.5, 0.5, -0.5, 0.5]; // left, right, top, bottom if let PartType::Cargo = part_type { bound = [-0.375, 0.375, -0.5, 0.4375]; } if bound[0] < x && x < bound[1] && bound[2] < y && y < bound[3] && m_attach.associated_player.unwrap() == entity { q_player.selected = Some(m_entity); break; } } for (entity, part_type, transform, _, _, _) in &part_query { if *part_type == PartType::LandingThrusterSuspension { continue; } let pos = transform.translation; let rel_x = pos.x - x; let rel_y = pos.y - y; let angle = -transform.rotation.z; let x = rel_x * angle.cos() - rel_y * angle.sin(); let y = rel_x * angle.sin() + rel_y * angle.cos(); let mut bound = [-0.5, 0.5, -0.5, 0.5]; // left, right, top, bottom if let PartType::Cargo = part_type { bound = [-0.375, 0.375, -0.5, 0.4375]; } if bound[0] < x && x < bound[1] && bound[2] < y && y < bound[3] { q_player.selected = Some(entity); break; } } } } } Packet::RequestSave { old_save } => { for (_, q_player, _, _, attach, _) in &mut player_query { if q_player.addr == *from { // HEY! GHOSTLY! PLEASE FILL THIS STRUCT WITH DATA! // THANKS! let unused_modules = if let Some(ref old_save) = old_save { if let Ok(old_savedata) = unpack_savefile(&app_keys.app_key, old_save.to_string()) { old_savedata.unused_modules } else { Vec::new() } } else { Vec::new() }; let save = SaveData { children: crate::module::save::construct_save_data(attach.clone(), &attached_query), unused_modules, }; let save_string = pack_savefile(&app_keys.app_key, save); let packet = Packet::SaveData { payload: save_string, }; event_queue.push(WsEvent::Send { to: *from, message: packet.into(), }); } } } _ => continue, } } } for event in event_queue { packet_event_send.send(event); } } pub fn player_input_update( mut player_and_body_query: Query<( Entity, &mut Player, &Attach, &mut ExternalForce, &Transform, )>, mut attached_query: Query< (&Attach, &PartType, &mut ExternalForce, &Transform), Without, >, ) { for (_, mut player, attach, mut forces, transform) in &mut player_and_body_query { //forces.torque = 0.0; //forces.force = Vec2::ZERO; if !(player.input.up || player.input.down || player.input.right || player.input.left) { continue; } let mut fmul_bottom_left_thruster: f32 = 0.0; let mut fmul_bottom_right_thruster: f32 = 0.0; let mut fmul_top_left_thruster: f32 = 0.0; let mut fmul_top_right_thruster: f32 = 0.0; if player.input.up { fmul_bottom_left_thruster -= 1.0; fmul_bottom_right_thruster -= 1.0; } if player.input.down { fmul_top_left_thruster += 1.0; fmul_top_right_thruster += 1.0; } if player.input.left { fmul_top_left_thruster += 1.0; fmul_bottom_right_thruster -= 1.0; } if player.input.right { fmul_top_right_thruster += 1.0; fmul_bottom_left_thruster -= 1.0; } fmul_top_left_thruster = fmul_top_left_thruster.clamp(-1.0, 1.0); fmul_top_right_thruster = fmul_top_right_thruster.clamp(-1.0, 1.0); fmul_bottom_left_thruster = fmul_bottom_left_thruster.clamp(-1.0, 1.0); fmul_bottom_right_thruster = fmul_bottom_right_thruster.clamp(-1.0, 1.0); if player.input.up { fmul_bottom_left_thruster -= 2.0; fmul_bottom_right_thruster -= 2.0; } if player.input.down { fmul_top_left_thruster += 2.0; fmul_top_right_thruster += 2.0; } let rot = transform.rotation.to_euler(EulerRot::ZYX).0; let thrusters = [ (fmul_bottom_left_thruster, -PART_HALF_SIZE, -PART_HALF_SIZE), (fmul_bottom_right_thruster, PART_HALF_SIZE, -PART_HALF_SIZE), (fmul_top_left_thruster, -PART_HALF_SIZE, PART_HALF_SIZE), (fmul_top_right_thruster, PART_HALF_SIZE, PART_HALF_SIZE), ]; for (force_multiplier, x_offset, y_offset) in thrusters { if force_multiplier != 0.0 && player.energy >= part!(PartType::Hearty).thruster_energy { player.energy -= part!(PartType::Hearty).thruster_energy; let thruster_pos_uncast = vec2(x_offset, y_offset); let thruster_pos_cast = rot2d(thruster_pos_uncast, rot) + transform.translation.xy(); let thruster_force = force_multiplier * part!(PartType::Hearty).thruster_force; let thruster_vec = vec2(-thruster_force * rot.sin(), thruster_force * rot.cos()); let thruster_force = ExternalForce::at_point( thruster_vec, thruster_pos_cast, transform.translation.xy(), ); forces.force += thruster_force.force; forces.torque += thruster_force.torque; } } // change to support other thruster types later if player.energy >= part!(PartType::LandingThruster).thruster_energy { crate::module::thruster::search_thrusters( player.input, attach.clone(), *transform, &mut player.energy, &mut attached_query, ); } } }