~starkingdoms/starkingdoms

f09a962cd10ee3b70177bd86c7c8a88f662c49f5 — ghostlyzsh 1 year, 4 months ago 66e5b4b
comments? and formal grammar for kabel
M Cargo.toml => Cargo.toml +1 -1
@@ 4,7 4,7 @@ members = [
    "starkingdoms-backplane",
    "starkingdoms-common",
    "savefile_decoder"
, "starkingdoms-api"]
, "starkingdoms-api", "kabel"]
resolver = "2"

[profile.dev.package."*"]

A kabel/Cargo.toml => kabel/Cargo.toml +6 -0
@@ 0,0 1,6 @@
[package]
name = "kabel"
version = "0.1.0"
edition = "2021"

[dependencies]

A kabel/grammar.ebnf => kabel/grammar.ebnf +35 -0
@@ 0,0 1,35 @@
program = { statement } ;

statement = if | while | ( expression , ";" ) ;

while = "while" , expression , block ;

if = "if" , expression , block ;

block = "{" , { statement } , "}" ;

expression = assignment | declaration ;

declaration = "var" , identifier , "=" , expression ;

assignment = { identifier , "=" , } logical_and ;

logical_and = logical_or { , "||" , logical_or } ;

logical_or = equality { , "&&" , equality } ;

equality = comparison { , ( "==" | "!=" ) , comparison } ;

comparison = term { , ( ">" | "<" | ">=" | "<=" ) , term } ;

term = factor { , ( "+" | "-" ) , factor } ;

factor = primary { , ( "*" | "/" ) , primary } ;

primary = identifier | number | string | group ;

group = "(" , expression , ")" ;

identifier = alphabetic , { alphabetic | digit } ;
string = ( '"' | "'" ) , { character - ( '"' | "'" ) } , ( '"' | "'" ) ;
number = digit , { digit } , [ "." , digit , { digit } ] ;

A kabel/src/main.rs => kabel/src/main.rs +3 -0
@@ 0,0 1,3 @@
fn main() {
    println!("Hello, world!");
}

M server/src/main.rs => server/src/main.rs +14 -51
@@ 48,7 48,7 @@ pub mod ws;

struct StkPluginGroup;

#[derive(Resource)]
#[derive(Resource, Clone)]
pub struct AppKeys {
    pub app_key: Vec<u8>,
}


@@ 150,19 150,21 @@ fn main() {
            server_config.world.pixels_per_meter,
        ))
        .add_plugins(StkTungsteniteServerPlugin)
        .add_systems(Startup, setup_integration_parameters)
        .add_systems(Startup, planet::spawn_planets)
        .add_systems(FixedUpdate, module::module_spawn)
        .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(Startup, (setup_integration_parameters, planet::spawn_planets))
        .add_systems(Update, (player::on_message, player::packet::on_close))
        .add_systems(FixedUpdate,
            (module::module_spawn, player::packet::send_player_energy,
                player::packet::on_position_change, module::save::save_eligibility,
                module::convert_modules))
        .add_systems(
            FixedUpdate,
            (module::break_modules, gravity_update, player::player_input_update).chain(),
            (
                module::break_modules,
                planet::gravity_update,
                player::player_input_update,
            )
                .chain(),
        )
        .add_systems(FixedUpdate, module::save::save_eligibility)
        .add_systems(FixedUpdate, module::convert_modules)
        .insert_resource(Time::<Fixed>::from_seconds(
            server_config.server.world_fixed_timestep,
        ))


@@ 172,6 174,7 @@ fn main() {
    info!("Goodbye!");
}

// set settings in physics engine
fn setup_integration_parameters(mut context: ResMut<RapierContext>, server_config: Res<StkConfig>) {
    context.integration_parameters = server_config.physics.parameters;



@@ 188,43 191,3 @@ fn setup_integration_parameters(mut context: ResMut<RapierContext>, server_confi
        }
    }
}

fn gravity_update(
    mut part_query: Query<
        (
            &Transform,
            &ReadMassProperties,
            &mut ExternalForce,
            &mut ExternalImpulse,
        ),
        With<PartType>,
    >,
    planet_query: Query<(&Transform, &ReadMassProperties), With<PlanetType>>,
    server_config: Res<StkConfig>,
) {
    for (part_transform, part_mp, mut forces, mut impulses) in &mut part_query {
        impulses.impulse = Vec2::ZERO;
        forces.force = Vec2::ZERO;
        forces.torque = 0.;
        let part_mp = part_mp.get();
        let part_mass = part_mp.mass;
        let part_translate = part_transform.translation;
        for (planet_transform, planet_mp) in &planet_query {
            let planet_mp = planet_mp.get();
            let planet_mass = planet_mp.mass;
            let planet_translate = planet_transform.translation;
            let distance = planet_translate.distance(part_translate);
            let force =
                server_config.world.gravity * ((part_mass * planet_mass) / (distance * distance));
            let direction = (planet_translate - part_translate).normalize() * force;
            /*let gravity_force = ExternalForce::at_point(
                direction.xy(),
                part_transform.translation.xy(),
                part_transform.translation.xy(),
            );
            forces.force += gravity_force.force;
            forces.torque += gravity_force.torque;*/
            impulses.impulse += direction.xy();
        }
    }
}

M server/src/module/component.rs => server/src/module/component.rs +24 -2
@@ 1,9 1,11 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::{ExternalForce, ExternalImpulse, ReadMassProperties, RigidBody, Velocity};
use serde::{Deserialize, Serialize};
use starkingdoms_common::PartType as c_PartType;

#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, Default)]
pub enum PartType {
    #[default]
    Placeholder,
    Hearty,
    Cargo,


@@ 51,7 53,7 @@ pub struct LooseAttach {
#[derive(Component, Clone, Copy, PartialEq, Debug)]
pub struct CanAttach(pub u8); // each bit means a slot able to attach to

#[derive(Component, Copy, Clone)]
#[derive(Component, Copy, Clone, Default)]
pub struct PartFlags {
    pub attached: bool,
}


@@ 61,6 63,26 @@ pub struct PartBundle {
    pub transform: TransformBundle,
    pub part_type: PartType,
    pub flags: PartFlags,
    pub velocity: Velocity,
    pub force: ExternalForce,
    pub impulse: ExternalImpulse,
    pub rigidbody: RigidBody,
    pub read_mass_properties: ReadMassProperties,
}

impl Default for PartBundle {
    fn default() -> Self {
        Self {
            transform: TransformBundle::default(),
            part_type: PartType::default(),
            flags: PartFlags::default(),
            velocity: Velocity::default(),
            force: ExternalForce::default(),
            impulse: ExternalImpulse::default(),
            rigidbody: RigidBody::Dynamic,
            read_mass_properties: ReadMassProperties::default(),
        }
    }
}

#[derive(Resource)]

M server/src/module/mod.rs => server/src/module/mod.rs +30 -114
@@ 6,7 6,8 @@ use component::*;
use rand::Rng;

use crate::{
    capacity, config::StkConfig, part, planet::PlanetType, player::component::Player, proto_part_flags, proto_transform, ws::WsEvent, Packet, Part
    capacity, config::StkConfig, part, planet::PlanetType, player::component::Player,
    proto_part_flags, proto_transform, ws::WsEvent, Packet, Part,
};

pub mod component;


@@ 38,9 39,9 @@ pub fn module_spawn(
            part_type: PartType::Cargo,
            transform: TransformBundle::from(transform),
            flags,
            ..default()
        });
        entity
            .insert(RigidBody::Dynamic)
            .with_children(|children| {
                children
                    .spawn(Collider::cuboid(0.375, 0.46875))


@@ 50,11 51,7 @@ pub fn module_spawn(
                local_center_of_mass: vec2(0.0, 0.0),
                mass: part!(PartType::Cargo).mass,
                principal_inertia: 7.5,
            }))
            .insert(ExternalForce::default())
            .insert(ExternalImpulse::default())
            .insert(Velocity::default())
            .insert(ReadMassProperties::default());
            }));

        let packet = Packet::SpawnPart {
            id: entity.id().index(),


@@ 74,28 71,12 @@ pub fn module_spawn(
pub fn detach_recursive(
    commands: &mut Commands,
    this: Entity,
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
    attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>),
    >,
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
    player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
        Without<PlanetType>,
    >,
) -> u32 {


@@ 182,45 163,22 @@ pub fn despawn_module_tree(
}

pub fn attach_on_module_tree(
    x: f32,
    y: f32,
    x: f32, y: f32,
    commands: &mut Commands,
    this: Entity,
    select: Entity,
    player_id: Entity,
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
    attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>,
            &mut PartFlags),
        (Without<PlanetType>, Without<Player>),
    >,
    part_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Velocity,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
    part_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
    player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
        Without<PlanetType>,
    >,
) -> bool {


@@ 236,15 194,8 @@ pub fn attach_on_module_tree(
        let part_type = *module.1;
        ret |= if part_type != PartType::LandingThrusterSuspension {
            attach_on_module_tree(
                x,
                y,
                commands,
                *this,
                select,
                player_id,
                attached_query,
                part_query,
                player_query,
                x, y, commands, *this, select, player_id, attached_query,
                part_query, player_query,
            )
        } else {
            false


@@ 373,16 324,9 @@ pub fn convert_modules(
    rapier_context: Res<RapierContext>,
    planet_query: Query<(Entity, &PlanetType, &Children)>,
    mut player_query: Query<(&Attach, &mut Player)>,
    mut attached_query: Query<
        (
            Entity,
            &mut PartType,
            &mut Attach,
            &mut AdditionalMassProperties,
            &Children,
            &Transform,
            &PartFlags,
        ),
    mut attached_query: Query<(Entity, &mut PartType, &mut Attach,
            &mut AdditionalMassProperties, &Children, &Transform,
            &PartFlags),
        Without<Player>,
    >,
    mut collider_query: Query<


@@ 445,16 389,8 @@ fn convert_modules_recursive(
    commands: &mut Commands,
    planet_type: PlanetType,
    attach: Attach,
    attached_query: &mut Query<
        (
            Entity,
            &mut PartType,
            &mut Attach,
            &mut AdditionalMassProperties,
            &Children,
            &Transform,
            &PartFlags,
        ),
    attached_query: &mut Query<(Entity, &mut PartType, &mut Attach,
            &mut AdditionalMassProperties, &Children, &Transform, &PartFlags),
        Without<Player>,
    >,
    collider_query: &mut Query<


@@ 539,23 475,19 @@ fn convert_modules_recursive(
                        transform: TransformBundle::from(*module_transform),
                        part_type: PartType::LandingThrusterSuspension,
                        flags: PartFlags { attached: false },
                        ..default()
                    });
                    suspension
                        .insert(RigidBody::Dynamic)
                        .with_children(|children| {
                            children
                                .spawn(Collider::cuboid(0.5, 0.02))
                                .insert(TransformBundle::from(Transform::from_xyz(0., -0.48, 0.)));
                        })
                        .insert(ImpulseJoint::new(module_entity, joint))
                        .insert(ExternalForce::default())
                        .insert(ExternalImpulse::default())
                        .insert(Velocity::default())
                        .insert(ReadMassProperties::default())
                        .insert(AdditionalMassProperties::MassProperties(MassProperties {
                            local_center_of_mass: vec2(0.0, 0.0),
                            mass: 0.00000000000001,
                            principal_inertia: 0.0000000000001,
                            mass:               0.00000000000001,
                            principal_inertia:  0.00000000000001,
                        }))
                        .insert(Attach {
                            associated_player: attach.associated_player,


@@ 618,28 550,12 @@ fn convert_modules_recursive(
pub fn break_modules(
    mut commands: Commands,
    rapier_context: Res<RapierContext>,
    mut attached_query: Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
    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,
        ),
    mut player_query: Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
        Without<PlanetType>,
    >,
) {

M server/src/module/save.rs => server/src/module/save.rs +5 -12
@@ 5,7 5,8 @@ use bevy_rapier2d::prelude::*;
use starkingdoms_common::SaveModule;

use crate::{
    capacity, mass, planet::PlanetType, player::component::Player, ws::WsEvent, Attach, CanAttach, LooseAttach, Packet, PartBundle, PartFlags, PartType
    capacity, mass, planet::PlanetType, player::component::Player, ws::WsEvent, Attach, CanAttach,
    LooseAttach, Packet, PartBundle, PartFlags, PartType,
};

pub fn load_savefile(


@@ 95,6 96,7 @@ pub fn load_savefile(
                    transform: TransformBundle::from(transform),
                    part_type: child.part_type.into(),
                    flags: PartFlags { attached: true },
                    ..default()
                });
                module.id()
            };


@@ 126,7 128,6 @@ pub fn load_savefile(
            ret[i] = Some(module.id());

            module
                .insert(RigidBody::Dynamic)
                .with_children(|children| {
                    children
                        .spawn(if part_type == PartType::Cargo {


@@ 156,11 157,7 @@ pub fn load_savefile(
                    local_center_of_mass: vec2(0.0, 0.0),
                    mass: mass!(part_type),
                    principal_inertia: 7.5,
                }))
                .insert(ExternalForce::default())
                .insert(ExternalImpulse::default())
                .insert(Velocity::default())
                .insert(ReadMassProperties::default());
                }));
            if part_type == PartType::Hub {
                module.insert(CanAttach(15));
            }


@@ 192,19 189,15 @@ pub fn load_savefile(
                    ),
                    part_type: PartType::LandingThrusterSuspension,
                    flags: PartFlags { attached: true },
                    ..default()
                });
                suspension
                    .insert(RigidBody::Dynamic)
                    .with_children(|children| {
                        children
                            .spawn(Collider::cuboid(0.5, 0.02))
                            .insert(TransformBundle::from(Transform::from_xyz(0., -0.48, 0.)));
                    })
                    .insert(ImpulseJoint::new(module_id, joint))
                    .insert(ExternalForce::default())
                    .insert(ExternalImpulse::default())
                    .insert(Velocity::default())
                    .insert(ReadMassProperties::default())
                    .insert(AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: vec2(0.0, 0.0),
                        mass: 0.00000000000001,

M server/src/module/thruster.rs => server/src/module/thruster.rs +5 -1
@@ 1,6 1,10 @@
use std::f32::consts::PI;

use crate::{part, player::component::{Input, Player}, Attach, PartType};
use crate::{
    part,
    player::component::{Input, Player},
    Attach, PartType,
};
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;


M server/src/planet.rs => server/src/planet.rs +37 -1
@@ 2,7 2,7 @@ use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use serde::{Deserialize, Serialize};

use crate::planet;
use crate::{config::StkConfig, module::component::PartType, planet};

#[derive(Component, Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
pub enum PlanetType {


@@ 74,3 74,39 @@ pub fn spawn_planets(mut commands: Commands) {
        })
        .insert(RigidBody::Fixed);
}

pub fn gravity_update(
    mut part_query: Query<
        (
            &Transform,
            &ReadMassProperties,
            &mut ExternalForce,
            &mut ExternalImpulse,
        ),
        With<PartType>,
    >,
    planet_query: Query<(&Transform, &ReadMassProperties), With<PlanetType>>,
    server_config: Res<StkConfig>,
) {
    for (part_transform, part_mp, mut forces, mut impulses) in &mut part_query {
        impulses.impulse = Vec2::ZERO;
        forces.force = Vec2::ZERO;
        forces.torque = 0.;
        let part_mp = part_mp.get();
        let part_mass = part_mp.mass;
        let part_translate = part_transform.translation;
        for (planet_transform, planet_mp) in &planet_query {
            let planet_mp = planet_mp.get();
            let planet_mass = planet_mp.mass;
            let planet_translate = planet_transform.translation;
            let distance = planet_translate.distance(part_translate);
            // gravity equation
            let force =
                server_config.world.gravity * ((part_mass * planet_mass) / (distance * distance));
            // gravity vector
            let direction = (planet_translate - part_translate).normalize() * force;
            // apply gravity vector as impulse to body
            impulses.impulse += direction.xy();
        }
    }
}

A server/src/player/client_login.rs => server/src/player/client_login.rs +301 -0
@@ 0,0 1,301 @@
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<String>, app_keys: AppKeys, from: &SocketAddr,
    event_queue: &mut Vec<WsEvent>, server_config: StkConfig,
) -> Result<(), ()> {
    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 });
                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::<f32>() * 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<String>, app_keys: AppKeys,
    attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>),
    >,
    part_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
        Without<PlanetType>,
    >,
    player_comp: &mut Player, attach: &mut Attach, from: &SocketAddr,
    event_queue: &mut Vec<WsEvent>,
) {
    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<WsEvent>, from: &SocketAddr,
    player_query: &Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
        Without<PlanetType>,
    >,
    index: u32, username: String,
    part_query: &Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    attached_query: &Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>),
    >,
    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(),
    });
}

M server/src/player/component.rs => server/src/player/component.rs +0 -1
@@ 5,7 5,6 @@ use serde::{Deserialize, Serialize};

use crate::module::component::{Attach, PartBundle};


#[derive(Component, Clone, Copy, Serialize, Deserialize, Debug, Default)]
pub struct Input {
    pub up: bool,

M server/src/player/mod.rs => server/src/player/mod.rs +55 -413
@@ 1,20 1,28 @@
use bevy::{ecs::event::ManualEventReader, math::{vec2, vec3}, prelude::*};
use bevy::{
    ecs::event::ManualEventReader,
    math::vec2,
    prelude::*,
};
use bevy_rapier2d::prelude::*;
use client_login::packet_stream;
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 player_mouse_input::{attach_or_detach, mouse_picking};
use request_save::request_save;
use send_message::send_message;

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};
use crate::{
    config::StkConfig, err_or_cont, mathutil::rot2d, module::{
        component::{Attach, CanAttach, LooseAttach, PartFlags, PartType},
        PART_HALF_SIZE,
    }, part, planet::PlanetType, ws::WsEvent, AppKeys, Packet, CLIENT_SCALE
};

pub mod component;
pub mod packet;
pub mod client_login;
pub mod send_message;
pub mod player_mouse_input;
pub mod request_save;

pub fn on_message(
    mut commands: Commands,


@@ 71,301 79,38 @@ pub fn on_message(
                    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;
                        }
                    err_or_cont!(client_login::join_auth(jwt, app_keys.clone(),
                        from, &mut event_queue, server_config.clone()));
                    
                    // create player in world
                    let (id, transform, mut player_comp) =
                        client_login::spawn_player(&mut commands, from, username.clone());

                        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 index = 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);
                    // create ship from potential save
                    client_login::load_save(&mut commands, transform, id, save,
                        app_keys.clone(), &mut attached_query, &mut part_query,
                        &mut player_query, &mut player_comp, &mut attach,
                        from, &mut event_queue);
                    // finish player entity
                    let mut entity_id = commands.entity(id);
                    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(),
                    });
                    // send packets that tell player initial world state
                    packet_stream(&planet_query, &mut event_queue, from,
                        &player_query, index, username, &part_query,
                        &attached_query, transform);
                }
                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(),
                        });
                    }
                    // a player sent a message
                    send_message(&player_query, from, &mut event_queue, target, content);
                }
                Packet::PlayerInput {
                    up,


@@ 401,103 146,13 @@ pub fn on_message(
                                    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.);
                                    }
                                }
                                // process if module was attach or detached
                                attach_or_detach(select, &mut attached_query,
                                    &mut player_query, &mut part_query, &mut commands, x, y, entity);
                                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;
                                }
                            }
                            // check if mouse touched a module
                            mouse_picking(&attached_query, &part_query, &mut q_player, x, y, entity);
                        }
                    }
                }


@@ 506,31 161,11 @@ pub fn on_message(
                        if q_player.addr == *from {
                            // HEY! GHOSTLY! PLEASE FILL THIS STRUCT WITH DATA!
                            // THANKS!
                            // mhm yeah done

                            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(),
                            });
                            // client asked to save, generate save data and send it back
                            request_save(&attached_query, old_save.clone(), app_keys.clone(),
                                attach.clone(), &mut event_queue, from);
                        }
                    }
                }


@@ 567,6 202,7 @@ pub fn player_input_update(
        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;
        // figure out directions to use thrusters
        if player.input.up {
            fmul_bottom_left_thruster -= 1.0;
            fmul_bottom_right_thruster -= 1.0;


@@ 583,10 219,12 @@ pub fn player_input_update(
            fmul_top_right_thruster += 1.0;
            fmul_bottom_left_thruster -= 1.0;
        }
        // normalize
        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);
        // increase power for up and down
        if player.input.up {
            fmul_bottom_left_thruster -= 2.0;
            fmul_bottom_right_thruster -= 2.0;


@@ 598,6 236,7 @@ pub fn player_input_update(

        let rot = transform.rotation.to_euler(EulerRot::ZYX).0;

        // hearty thruster forces and respective positions
        let thrusters = [
            (fmul_bottom_left_thruster, -PART_HALF_SIZE, -PART_HALF_SIZE),
            (fmul_bottom_right_thruster, PART_HALF_SIZE, -PART_HALF_SIZE),


@@ 605,6 244,7 @@ pub fn player_input_update(
            (fmul_top_right_thruster, PART_HALF_SIZE, PART_HALF_SIZE),
        ];

        // process each thruster on hearty
        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;


@@ 623,7 263,9 @@ pub fn player_input_update(
            }
        }
        // change to support other thruster types later
        // check if the player has enough energy to use thruster
        if player.energy >= part!(PartType::LandingThruster).thruster_energy {
            // go through all thrusters and apply force
            crate::module::thruster::search_thrusters(
                player.input,
                attach.clone(),

M server/src/player/packet.rs => server/src/player/packet.rs +7 -1
@@ 1,6 1,12 @@
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 crate::{
    module::component::{Attach, PartFlags, PartType},
    planet::PlanetType,
    proto_part_flags, proto_transform,
    ws::WsEvent,
    Packet, Part, Planet, CLIENT_SCALE,
};

use super::component::Player;


A server/src/player/player_mouse_input.rs => server/src/player/player_mouse_input.rs +138 -0
@@ 0,0 1,138 @@
use bevy::{math::vec3, prelude::*};
use bevy_rapier2d::prelude::*;

use crate::{module::component::{Attach, CanAttach, LooseAttach, PartFlags, PartType}, planet::PlanetType};

use super::component::Player;

pub fn attach_or_detach(
    select: Entity,
    attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>),
    >,
    player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
        Without<PlanetType>,
    >,
    part_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    commands: &mut Commands,
    x: f32,
    y: f32,
    entity: Entity,
) {
    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(
            commands,
            module.0,
            attached_query,
            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);
        return;
    }
    if crate::module::attach_on_module_tree(
        x,
        y,
        commands,
        entity,
        select,
        entity,
        attached_query,
        part_query,
        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
        return;
    }
    // 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.);
        }
    }
}

pub fn mouse_picking(
    attached_query: &Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>),
    >,
    part_query: &Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    q_player: &mut Player,
    x: f32,
    y: f32,
    entity: Entity,
) {
    for (m_entity, part_type, transform, m_attach, _velocity, _, _, _) in
        attached_query.iter()
    {
        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.iter() {
        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;
        }
    }
}

A server/src/player/request_save.rs => server/src/player/request_save.rs +49 -0
@@ 0,0 1,49 @@
use std::net::SocketAddr;

use bevy::prelude::*;
use bevy_rapier2d::prelude::Velocity;
use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData};

use crate::{module::component::{Attach, CanAttach, LooseAttach, PartFlags, PartType}, planet::PlanetType, ws::WsEvent, AppKeys, Packet};

use super::component::Player;

pub fn request_save(
    attached_query: &Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
        (Without<PlanetType>, Without<Player>),
    >,
    old_save: Option<String>,
    app_keys: AppKeys,
    attach: Attach,
    event_queue: &mut Vec<WsEvent>,
    from: &SocketAddr
) {
    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(),
    });
}

A server/src/player/send_message.rs => server/src/player/send_message.rs +61 -0
@@ 0,0 1,61 @@
use std::net::SocketAddr;

use bevy::prelude::*;
use bevy_rapier2d::prelude::Velocity;

use crate::{module::component::{Attach, PartFlags}, planet::PlanetType, ws::WsEvent, Packet};

use super::component::Player;

pub fn send_message(
    player_query: &Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
        Without<PlanetType>,
    >,
    from: &SocketAddr,
    event_queue: &mut Vec<WsEvent>,
    target: Option<String>,
    content: String,
) {
    // find our player
    let mut player = None;
    for (_, q_player, _, _, _, _) in player_query.iter() {
        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.iter() {
            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(),
        });
    }
}