M server/src/main.rs => server/src/main.rs +18 -760
@@ 17,51 17,44 @@
#![allow(clippy::too_many_arguments)] // bevy :(
#![allow(clippy::only_used_in_recursion)] // todo: remove this
-use crate::mathutil::rot2d;
-use crate::ws::{StkTungsteniteServerConfig, StkTungsteniteServerPlugin, WsEvent};
-use bevy::math::{vec2, vec3};
+use crate::ws::{StkTungsteniteServerConfig, StkTungsteniteServerPlugin};
use bevy::{
app::{PluginGroupBuilder, ScheduleRunnerPlugin},
- ecs::event::ManualEventReader,
prelude::*,
time::TimePlugin,
};
use bevy_rapier2d::prelude::*;
-use component::*;
-use hmac::{Hmac, Mac};
-use jwt::VerifyWithKey;
-use module::component::{Attach, CanAttach, LooseAttach, ModuleTimer, PartBundle, PartFlags, PartType};
-use module::save::{construct_save_data, load_savefile};
-use module::thruster::search_thrusters;
-use module::{attach_on_module_tree, despawn_module_tree, detach_recursive};
+use module::component::{
+ Attach, CanAttach, LooseAttach, ModuleTimer, PartBundle, PartFlags, PartType,
+};
use packet::*;
use planet::PlanetType;
-use rand::Rng;
use serde::{Deserialize, Serialize};
-use sha2::Sha256;
-use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData};
use std::fs;
use crate::config::{PartsConfig, PhysicsSolver, PlanetsConfig, StkConfig};
use std::sync::OnceLock;
use std::time::Duration;
-pub mod component;
-mod config;
+#[macro_use]
+pub mod config;
pub mod macros;
pub mod mathutil;
+pub mod module;
pub mod packet;
-pub mod ws;
pub mod planet;
-pub mod module;
pub mod player;
+pub mod ws;
struct StkPluginGroup;
+#[derive(Resource)]
+pub struct AppKeys {
+ pub app_key: Vec<u8>,
+}
+
// factor to multiply positions by to send to the client
pub static CLIENT_SCALE: f32 = 50.0;
-// half size of hearty
-pub static PART_HALF_SIZE: f32 = 25.0;
// good ol' classic almost useless but still necessary code
static _SERVER_CONFIG: OnceLock<StkConfig> = OnceLock::new();
@@ 160,13 153,13 @@ fn main() {
.add_systems(Startup, setup_integration_parameters)
.add_systems(Startup, planet::spawn_planets)
.add_systems(FixedUpdate, module::module_spawn)
- .add_systems(Update, on_message)
- .add_systems(Update, on_close)
- .add_systems(FixedUpdate, send_player_energy)
- .add_systems(FixedUpdate, on_position_change)
+ .add_systems(Update, player::on_message)
+ .add_systems(Update, player::packet::on_close)
+ .add_systems(FixedUpdate, player::packet::send_player_energy)
+ .add_systems(FixedUpdate, player::packet::on_position_change)
.add_systems(
FixedUpdate,
- (module::break_modules, gravity_update, player_input_update).chain(),
+ (module::break_modules, gravity_update, player::player_input_update).chain(),
)
.add_systems(FixedUpdate, module::save::save_eligibility)
.add_systems(FixedUpdate, module::convert_modules)
@@ 196,741 189,6 @@ fn setup_integration_parameters(mut context: ResMut<RapierContext>, server_confi
}
}
-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<PlanetType>, Without<Player>, Without<Attach>),
- >,
- mut attached_query: Query<
- (
- Entity,
- &PartType,
- &mut Transform,
- &mut Attach,
- &Velocity,
- Option<&CanAttach>,
- Option<&LooseAttach>,
- &mut PartFlags,
- ),
- (Without<PlanetType>, Without<Player>),
- >,
- mut player_query: Query<
- (
- Entity,
- &mut Player,
- &Transform,
- &Velocity,
- &mut Attach,
- &mut PartFlags,
- ),
- Without<PlanetType>,
- >,
- mut packet_recv: Local<ManualEventReader<WsEvent>>,
- mut packet_event_send: ResMut<Events<WsEvent>>,
- app_keys: Res<AppKeys>,
- server_config: Res<StkConfig>,
-) {
- 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<Sha256> = 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::<f32>() * 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: 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: 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: 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: 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: 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: 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: 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 = 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 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: 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);
- }
-}
-
-fn on_close(
- player_query: Query<(Entity, &Player, &Attach)>,
- attached_query: Query<&Attach, With<PartType>>,
- part_query: Query<&PartType>,
- mut commands: Commands,
- mut packet_recv: Local<ManualEventReader<WsEvent>>,
- mut packet_send: ResMut<Events<WsEvent>>,
-) {
- let mut packets = Vec::new();
- for packet in packet_recv.read(&packet_send) {
- if let WsEvent::Close { addr } = packet {
- for (entity, player, attach) in &player_query {
- if player.addr == *addr {
- despawn_module_tree(
- &mut commands,
- attach,
- &attached_query,
- &part_query,
- &mut packets,
- );
- commands.entity(entity).despawn_recursive();
-
- let packet = Packet::PlayerLeave { id: entity.index() };
-
- for (in_entity, player, _) in &player_query {
- if entity != in_entity {
- packets.push(WsEvent::Send {
- to: player.addr,
- message: packet.clone().into(),
- });
- }
- }
- }
- }
- }
- }
- for packet in packets {
- packet_send.send(packet);
- }
-}
-
-
-fn send_player_energy(player_query: Query<&Player>, mut packet_send: EventWriter<WsEvent>) {
- for player in &player_query {
- let packet = Packet::EnergyUpdate {
- amount: player.energy,
- max: player.energy_capacity,
- };
-
- packet_send.send(WsEvent::Send {
- to: player.addr,
- message: packet.into(),
- });
- }
-}
-
-fn on_position_change(
- mut commands: Commands,
- part_query: Query<(Entity, &PartType, &Transform, &PartFlags), Changed<Transform>>,
- planet_query: Query<(Entity, &PlanetType, &Transform), Changed<Transform>>,
- mut packet_send: EventWriter<WsEvent>,
-) {
- let mut updated_parts = Vec::new();
- for (entity, part_type, transform, flags) in part_query.iter() {
- let id = commands.entity(entity).id().index();
- updated_parts.push((
- id,
- Part {
- part_type: *part_type,
- transform: proto_transform!(Transform::from_translation(
- transform.translation * CLIENT_SCALE,
- )
- .with_rotation(transform.rotation)),
- flags: proto_part_flags!(flags),
- },
- ));
- }
-
- if !updated_parts.is_empty() {
- let packet = Packet::PartPositions {
- parts: updated_parts,
- };
-
- packet_send.send(WsEvent::Broadcast {
- message: packet.into(),
- });
- }
-
- let mut planets = Vec::new();
- for (entity, planet_type, transform) in planet_query.iter() {
- let id = commands.entity(entity).id().index();
- planets.push((
- id,
- Planet {
- planet_type: *planet_type,
- transform: proto_transform!(Transform::from_translation(
- transform.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,
- },
- },
- ));
- }
-
- if !planets.is_empty() {
- let packet = Packet::PlanetPositions { planets };
-
- packet_send.send(WsEvent::Broadcast {
- message: packet.into(),
- });
- }
-}
-
-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<Player>,
- >,
-) {
- 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 {
- search_thrusters(
- player.input,
- attach.clone(),
- *transform,
- &mut player.energy,
- &mut attached_query,
- );
- }
- }
-}
-
fn gravity_update(
mut part_query: Query<
(
M server/src/module/component.rs => server/src/module/component.rs +0 -1
@@ 75,4 75,3 @@ impl Default for ModuleTimer {
Self::new()
}
}
-
M server/src/module/mod.rs => server/src/module/mod.rs +7 -2
@@ 5,11 5,16 @@ use bevy_rapier2d::prelude::*;
use component::*;
use rand::Rng;
-use crate::{capacity, config::StkConfig, part, planet::PlanetType, proto_part_flags, proto_transform, ws::WsEvent, Packet, Part, Player};
+use crate::{
+ capacity, config::StkConfig, part, planet::PlanetType, player::component::Player, proto_part_flags, proto_transform, ws::WsEvent, Packet, Part
+};
-pub mod thruster;
pub mod component;
pub mod save;
+pub mod thruster;
+
+// half size of hearty
+pub static PART_HALF_SIZE: f32 = 25.0;
pub fn module_spawn(
mut commands: Commands,
M server/src/module/save.rs => server/src/module/save.rs +3 -1
@@ 4,7 4,9 @@ use bevy::{math::vec2, prelude::*};
use bevy_rapier2d::prelude::*;
use starkingdoms_common::SaveModule;
-use crate::{capacity, mass, planet::PlanetType, ws::WsEvent, Attach, CanAttach, LooseAttach, Packet, PartBundle, PartFlags, PartType, Player};
+use crate::{
+ capacity, mass, planet::PlanetType, player::component::Player, ws::WsEvent, Attach, CanAttach, LooseAttach, Packet, PartBundle, PartFlags, PartType
+};
pub fn load_savefile(
commands: &mut Commands,
M server/src/module/thruster.rs => server/src/module/thruster.rs +1 -1
@@ 1,8 1,8 @@
use std::f32::consts::PI;
+use crate::{part, player::component::{Input, Player}, Attach, PartType};
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
-use crate::{part, Attach, Input, PartType, Player};
pub fn search_thrusters(
input: Input,
R server/src/component.rs => server/src/player/component.rs +2 -22
@@ 1,25 1,11 @@
-// StarKingdoms.IO, a browser game about drifting through space
-// Copyright (C) 2023 ghostly_zsh, TerraMaster85, core
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::net::SocketAddr;
-use bevy::prelude::*;
+use bevy::prelude::{Bundle, Component, Entity};
use serde::{Deserialize, Serialize};
use crate::module::component::{Attach, PartBundle};
+
#[derive(Component, Clone, Copy, Serialize, Deserialize, Debug, Default)]
pub struct Input {
pub up: bool,
@@ 39,15 25,9 @@ pub struct Player {
pub energy: u32,
}
-
#[derive(Bundle)]
pub struct PlayerBundle {
pub part: PartBundle,
pub player: Player,
pub attach: Attach,
}
-
-#[derive(Resource)]
-pub struct AppKeys {
- pub app_key: Vec<u8>,
-}
M server/src/player/mod.rs => server/src/player/mod.rs +636 -0
@@ 0,0 1,636 @@
+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<PlanetType>, Without<Player>, Without<Attach>),
+ >,
+ mut attached_query: Query<
+ (
+ Entity,
+ &PartType,
+ &mut Transform,
+ &mut Attach,
+ &Velocity,
+ Option<&CanAttach>,
+ Option<&LooseAttach>,
+ &mut PartFlags,
+ ),
+ (Without<PlanetType>, Without<Player>),
+ >,
+ mut player_query: Query<
+ (
+ Entity,
+ &mut Player,
+ &Transform,
+ &Velocity,
+ &mut Attach,
+ &mut PartFlags,
+ ),
+ Without<PlanetType>,
+ >,
+ mut packet_recv: Local<ManualEventReader<WsEvent>>,
+ mut packet_event_send: ResMut<Events<WsEvent>>,
+ app_keys: Res<AppKeys>,
+ server_config: Res<StkConfig>,
+) {
+ 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<Sha256> = 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::<f32>() * 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<Player>,
+ >,
+) {
+ 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,
+ );
+ }
+ }
+}
A server/src/player/packet.rs => server/src/player/packet.rs +120 -0
@@ 0,0 1,120 @@
+use bevy::{ecs::event::ManualEventReader, prelude::*};
+
+use crate::{module::component::{Attach, PartFlags, PartType}, planet::PlanetType, proto_part_flags, proto_transform, ws::WsEvent, Packet, Part, Planet, CLIENT_SCALE};
+
+use super::component::Player;
+
+pub fn send_player_energy(player_query: Query<&Player>, mut packet_send: EventWriter<WsEvent>) {
+ for player in &player_query {
+ let packet = Packet::EnergyUpdate {
+ amount: player.energy,
+ max: player.energy_capacity,
+ };
+
+ packet_send.send(WsEvent::Send {
+ to: player.addr,
+ message: packet.into(),
+ });
+ }
+}
+
+pub fn on_position_change(
+ mut commands: Commands,
+ part_query: Query<(Entity, &PartType, &Transform, &PartFlags), Changed<Transform>>,
+ planet_query: Query<(Entity, &PlanetType, &Transform), Changed<Transform>>,
+ mut packet_send: EventWriter<WsEvent>,
+) {
+ let mut updated_parts = Vec::new();
+ for (entity, part_type, transform, flags) in part_query.iter() {
+ let id = commands.entity(entity).id().index();
+ updated_parts.push((
+ id,
+ Part {
+ part_type: *part_type,
+ transform: proto_transform!(Transform::from_translation(
+ transform.translation * CLIENT_SCALE,
+ )
+ .with_rotation(transform.rotation)),
+ flags: proto_part_flags!(flags),
+ },
+ ));
+ }
+
+ if !updated_parts.is_empty() {
+ let packet = Packet::PartPositions {
+ parts: updated_parts,
+ };
+
+ packet_send.send(WsEvent::Broadcast {
+ message: packet.into(),
+ });
+ }
+
+ let mut planets = Vec::new();
+ for (entity, planet_type, transform) in planet_query.iter() {
+ let id = commands.entity(entity).id().index();
+ planets.push((
+ id,
+ Planet {
+ planet_type: *planet_type,
+ transform: proto_transform!(Transform::from_translation(
+ transform.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,
+ },
+ },
+ ));
+ }
+
+ if !planets.is_empty() {
+ let packet = Packet::PlanetPositions { planets };
+
+ packet_send.send(WsEvent::Broadcast {
+ message: packet.into(),
+ });
+ }
+}
+
+pub fn on_close(
+ player_query: Query<(Entity, &Player, &Attach)>,
+ attached_query: Query<&Attach, With<PartType>>,
+ part_query: Query<&PartType>,
+ mut commands: Commands,
+ mut packet_recv: Local<ManualEventReader<WsEvent>>,
+ mut packet_send: ResMut<Events<WsEvent>>,
+) {
+ let mut packets = Vec::new();
+ for packet in packet_recv.read(&packet_send) {
+ if let WsEvent::Close { addr } = packet {
+ for (entity, player, attach) in &player_query {
+ if player.addr == *addr {
+ crate::module::despawn_module_tree(
+ &mut commands,
+ attach,
+ &attached_query,
+ &part_query,
+ &mut packets,
+ );
+ commands.entity(entity).despawn_recursive();
+
+ let packet = Packet::PlayerLeave { id: entity.index() };
+
+ for (in_entity, player, _) in &player_query {
+ if entity != in_entity {
+ packets.push(WsEvent::Send {
+ to: player.addr,
+ message: packet.clone().into(),
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+ for packet in packets {
+ packet_send.send(packet);
+ }
+}