~starkingdoms/starkingdoms

e54fae7165cfad2269e971159960a03285d9d25a — ghostly_zsh 10 months ago e796ffb
client networking probably working
M Cargo.lock => Cargo.lock +4 -0
@@ 6083,6 6083,9 @@ dependencies = [
 "nalgebra 0.33.2",
 "pollster",
 "reqwest",
 "serde",
 "serde_json",
 "starkingdoms-common",
 "thiserror 2.0.9",
 "tracing",
 "tracing-subscriber",


@@ 6103,6 6106,7 @@ dependencies = [
 "hmac",
 "rmp-serde",
 "serde",
 "serde_json",
 "sha2",
]


M crates/client/Cargo.toml => crates/client/Cargo.toml +4 -1
@@ 21,13 21,16 @@ egui-wgpu = "0.30"
web-time = "1"
futures = "0.3"
nalgebra = "0.33"
starkingdoms-common = { version = "0.1", path = "../common" }
serde = "1"
serde_json = "1"

# WASM dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
tracing-web = "0.1" # Log output
console_error_panic_hook = "0.1" # Give useful information in the panic response, other than the useless "entered unreachable code"
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Window", "Location"] }
web-sys = { version = "0.3", features = ["Window", "Location", "WebSocket", "MessageEvent"] }
wasm-bindgen-futures = "0.4"
reqwest = "0.11"


M crates/client/src/ecs.rs => crates/client/src/ecs.rs +7 -0
@@ 1,7 1,9 @@
use bevy_ecs::bundle::Bundle;
use bevy_ecs::component::Component;
use bevy_ecs::event::Event;
use bevy_ecs::system::Resource;
use nalgebra::Matrix3;
use starkingdoms_common::packet::Packet;

#[derive(Component, Debug, Clone, Copy)]
pub struct Translation {


@@ 85,3 87,8 @@ pub struct Camera {
    pub shear_y: f32,
    pub zoom: f32,
}

#[derive(Event, Clone, PartialEq)]
pub struct SendPacket(pub Packet);
#[derive(Event, Clone, PartialEq)]
pub struct RecvPacket(pub Packet);

M crates/client/src/lib.rs => crates/client/src/lib.rs +9 -2
@@ 3,11 3,13 @@ use crate::input::MouseWheelEvent;
use crate::rendering::ui::UiRenderable;
use crate::rendering::App;
use bevy_ecs::event::{EventReader, Events};
use bevy_ecs::observer::Trigger;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::system::ResMut;
use bevy_ecs::world::World;
use ecs::Shear;
use ecs::{RecvPacket, SendPacket, Shear};
use egui::{Context, DragValue};
use networking::ws::Ws;
use rendering::assets::Assets;
use tracing::info;
use winit::event_loop::{ControlFlow, EventLoop};


@@ 22,6 24,7 @@ pub mod platform;
pub mod ecs;
pub mod input;
pub mod rendering;
pub mod networking;

// Hi, you've found the real main function! This is called AFTER platform-specific initialization code.
pub fn start() {


@@ 47,6 50,10 @@ pub fn start() {
        zoom: 1.0,
    });
    world.insert_resource(Assets::new());
    world.insert_resource(Ws::new().expect("Couldn't connect to server"));

    let send_packet_events = Events::<SendPacket>::default();
    let recv_packet_events = Events::<RecvPacket>::default();

    let mut start_schedule = Schedule::default();
    // Add startup things here


@@ 82,7 89,7 @@ pub fn start() {
    let event_loop = EventLoop::new().unwrap();
    event_loop.set_control_flow(ControlFlow::Poll);
    event_loop
        .run_app(&mut App::new(world, update_schedule, Gui {}))
        .run_app(&mut App::new(world, send_packet_events, recv_packet_events, update_schedule, Gui {}))
        .unwrap();
}


A crates/client/src/networking/mod.rs => crates/client/src/networking/mod.rs +6 -0
@@ 0,0 1,6 @@
#[cfg(target_arch = "wasm32")]
#[path = "ws_wasm.rs"]
pub mod ws;
#[cfg(not(target_arch = "wasm32"))]
#[path = "ws_native.rs"]
pub mod ws;

A crates/client/src/networking/ws_native.rs => crates/client/src/networking/ws_native.rs +0 -0
A crates/client/src/networking/ws_wasm.rs => crates/client/src/networking/ws_wasm.rs +67 -0
@@ 0,0 1,67 @@
use std::{any::Any, cell::{OnceCell, RefCell}, rc::Rc, sync::{Arc, Mutex, RwLock}};

use bevy_ecs::{system::Resource, world::World};
use futures::{channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, SinkExt};
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use wasm_bindgen_futures::spawn_local;
use web_sys::{MessageEvent, WebSocket};
use starkingdoms_common::packet::Packet;

use crate::ecs::RecvPacket;

const PORT: u16 = 3000;

#[derive(Debug)]
pub struct Socket(WebSocket);
unsafe impl Send for Socket {}
unsafe impl Sync for Socket {}

#[derive(Resource, Debug)]
pub struct Ws {
    socket: Socket,
    pub sender: UnboundedSender<Packet>,
    pub receiver: UnboundedReceiver<Packet>,
    packet_receiver: UnboundedReceiver<Packet>,
}

impl Ws {
    pub fn new() -> Result<Self, JsValue> {
        let window = web_sys::window().unwrap();
        let ws = WebSocket::new(&format!("ws://{}:{}",
                window.location().hostname().unwrap(), PORT))?;
        let (packet_sender, receiver) = unbounded();
        let packet_sender = Rc::new(RwLock::new(packet_sender));
        let (sender, packet_receiver) = unbounded();

        /*let onopen_callback = Closure::<dyn FnMut()>::new(move || {

        });
        ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
        onopen_callback.forget();*/
        let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
            //tracing::error!("{}", ws.ready_state());
            let data = e.data().as_string().expect("Expected string, found some other type");
            let data: Packet = serde_json::from_str(&data).expect("Received invalid json from server");
            let sender_clone = packet_sender.clone();
            spawn_local(async move {
                sender_clone.write().unwrap().send(data).await.expect("Couldn't transmit packet to client");
            });
        });
        ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
        onmessage_callback.forget();
        Ok(Ws {
            socket: Socket(ws),
            sender,
            receiver,
            packet_receiver,
        })
    }
    pub fn send_all_packets_from_channel(&mut self) {
        while let Ok(Some(packet)) = self.packet_receiver.try_next() {
            self.socket.0.send_with_str(&serde_json::to_string(&packet).expect("Couldn't convert packet to json")).expect("Couldn't send packet to server");
        }
    }
    pub fn send_packet(&mut self, packet: &Packet) {
        self.socket.0.send_with_str(&serde_json::to_string(packet).expect("Couldn't convert packet to json")).expect("Couldn't send packet to server");
    }
}

M crates/client/src/rendering/mod.rs => crates/client/src/rendering/mod.rs +22 -7
@@ 9,11 9,13 @@ pub mod assets;
pub mod assets;
pub mod ui;

use crate::ecs::{RecvPacket, SendPacket};
use crate::input::MouseWheelEvent;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
#[allow(unused_imports)]
use crate::rendering::renderer::{Renderer, RenderInitRes};
use crate::rendering::ui::UiRenderable;
use bevy_ecs::event::Events;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use std::ops::Add;


@@ 38,10 40,13 @@ pub struct App<T: UiRenderable> {
    world: Option<World>,
    update_schedule: Option<Schedule>,
    ui_renderable: Option<T>,
    send_packet_events: Option<Events<SendPacket>>,
    recv_packet_events: Option<Events<RecvPacket>>,
}

impl<T: UiRenderable> App<T> {
    pub fn new(world: World, update_schedule: Schedule, ui_renderable: T) -> Self {
    pub fn new(world: World, send_packet_events: Events<SendPacket>,
            recv_packet_events: Events<RecvPacket>, update_schedule: Schedule, ui_renderable: T) -> Self {
        Self {
            window: None,
            renderer: None,


@@ 50,6 55,8 @@ impl<T: UiRenderable> App<T> {
            world: Some(world),
            update_schedule: Some(update_schedule),
            ui_renderable: Some(ui_renderable),
            send_packet_events: Some(send_packet_events),
            recv_packet_events: Some(recv_packet_events),
        }
    }
}


@@ 64,20 71,25 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
            let world = self.world.take().unwrap();
            let update_schedule = self.update_schedule.take().unwrap();
            let ui_renderable = self.ui_renderable.take().unwrap();
            let send_packet_events = self.send_packet_events.take().unwrap();
            let recv_packet_events = self.recv_packet_events.take().unwrap();

            #[cfg(not(target_arch = "wasm32"))]
            {
                let renderer = pollster::block_on(async move {
                    Renderer::try_init(window.clone(), world, update_schedule, ui_renderable).await
                    Renderer::try_init(window.clone(), world,
                        update_schedule, ui_renderable, send_packet_events, recv_packet_events).await
                });
                match renderer {
                    Initialized(r) => {
                        self.renderer = Some(r);
                    }
                    NotReadyYet(w, u, t) => {
                    NotReadyYet(w, u, t, send, recv) => {
                        self.world = Some(w);
                        self.update_schedule = Some(u);
                        self.ui_renderable = Some(t);
                        self.send_packet_events = Some(send);
                        self.recv_packet_events = Some(recv);
                    }
                }
            }


@@ 98,7 110,8 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                self.renderer_rx = Some(rx);
                wasm_bindgen_futures::spawn_local(async move {
                    let renderer =
                        Renderer::try_init(window.clone(), world, update_schedule, ui_renderable).await;
                        Renderer::try_init(window.clone(), world,
                            update_schedule, ui_renderable, send_packet_events, recv_packet_events).await;
                    tx.send(renderer).unwrap();
                });
            }


@@ 177,11 190,11 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                                self.renderer = Some(r);
                                renderer_rxd = true;
                            }
                            NotReadyYet(w, u, t) => {
                            NotReadyYet(w, u, t, send, recv) => {
                                let (tx, rx) = futures::channel::oneshot::channel();
                                self.renderer_rx = Some(rx);
                                wasm_bindgen_futures::spawn_local(async move {
                                    let renderer = Renderer::try_init(window.clone(), w, u, t).await;
                                    let renderer = Renderer::try_init(window.clone(), w, u, t, send, recv).await;
                                    tx.send(renderer).unwrap();
                                });
                            }


@@ 210,10 223,12 @@ impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
                            Initialized(r) => {
                                self.renderer = Some(r);
                            }
                            NotReadyYet(w, u, t) => {
                            NotReadyYet(w, u, t, send, recv) => {
                                self.world = Some(w);
                                self.update_schedule = Some(u);
                                self.ui_renderable = Some(t);
                                self.send_packet_events = Some(send);
                                self.recv_packet_events = Some(recv);
                            }
                        }


M crates/client/src/rendering/renderer.rs => crates/client/src/rendering/renderer.rs +24 -4
@@ 1,12 1,15 @@
use crate::ecs::{Camera, Rotation, Scale, Shear, SpriteTexture, Translation};
use crate::ecs::{Camera, RecvPacket, Rotation, Scale, SendPacket, Shear, SpriteTexture, Translation};
use crate::networking::ws::Ws;
use crate::rendering::assets::Assets;
use crate::rendering::mipmap::MipGenerator;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
use crate::rendering::texture;
use crate::rendering::ui::UiRenderable;
use bevy_ecs::event::Events;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use egui::ViewportId;
use futures::SinkExt;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;


@@ 39,6 42,8 @@ pub struct Renderer<T: UiRenderable> {

    pub world: World,
    pub update_schedule: Schedule,
    pub send_packet_events: Events<SendPacket>,
    pub recv_packet_events: Events<RecvPacket>,

    pub gui_ctx: egui::Context,
    pub gui_winit: egui_winit::State,


@@ 61,7 66,7 @@ pub struct Renderer<T: UiRenderable> {
#[allow(clippy::large_enum_variant)]
pub enum RenderInitRes<T: UiRenderable> {
    Initialized(Renderer<T>),
    NotReadyYet(World, Schedule, T),
    NotReadyYet(World, Schedule, T, Events<SendPacket>, Events<RecvPacket>),
}
impl<T: UiRenderable> Debug for RenderInitRes<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {


@@ 78,10 83,13 @@ impl<T: UiRenderable> Renderer<T> {
        world: World,
        update_schedule: Schedule,
        gui_renderable: T,
        send_packet_events: Events<SendPacket>,
        recv_packet_events: Events<RecvPacket>,
    ) -> RenderInitRes<T> {
        let size = window.inner_size();
        if size.width == 0 || size.height == 0 {
            return NotReadyYet(world, update_schedule, gui_renderable);
            return NotReadyYet(world, update_schedule, gui_renderable,
                send_packet_events, recv_packet_events);
        }

        // First, create an instance. This is our handle to wgpu, and is the equivalent of navigator.gpu in WebGPU


@@ 190,6 198,8 @@ impl<T: UiRenderable> Renderer<T> {
            sprite_pipeline,
            world,
            update_schedule,
            send_packet_events,
            recv_packet_events,
            gui_ctx: gui_context,
            gui_winit,
            gui_renderer,


@@ 205,8 215,14 @@ impl<T: UiRenderable> Renderer<T> {
    }

    pub fn render(&mut self) {
        let mut ws = self.world.get_resource_mut::<Ws>().expect("Failed to get Ws resource");
        while let Ok(Some(packet)) = ws.receiver.try_next() {
            self.recv_packet_events.send(RecvPacket(packet));
        }
        // update the world
        self.update_schedule.run(&mut self.world);
        self.send_packet_events.update();
        self.recv_packet_events.update();
        // update the UI
        let egui_output = self
            .gui_ctx


@@ 285,7 301,6 @@ impl<T: UiRenderable> Renderer<T> {
                    }
                    u => panic!("unknown texture {u}, has it been added in rendering::renderer::<impl Renderer>::render()?")
                };
                tracing::warn!("{:?}", b);
                /*let b: &[u8] = match tex.texture.as_str() {
                    "f" => include_bytes!("../textures/f.png"),
                    "happy-tree" => include_bytes!("../textures/happy-tree.png"),


@@ 398,6 413,11 @@ impl<T: UiRenderable> Renderer<T> {
                self.gui_renderer.free_texture(&id);
            }
        }
        let mut ws = self.world.get_resource_mut::<Ws>().expect("Failed to get Ws resource");
        let mut send_event_cursor = self.send_packet_events.get_cursor();
        for event in send_event_cursor.read(&self.send_packet_events) {
            ws.send_packet(&event.0);
        }

        let buffer = encoder.finish();
        self.queue.submit(std::iter::once(buffer));

M crates/common/Cargo.toml => crates/common/Cargo.toml +2 -1
@@ 7,7 7,8 @@ edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
rmp-serde = "1"
hmac = "0.12"
sha2 = "0.10"
base64 = "0.21"
\ No newline at end of file
base64 = "0.21"

M crates/common/src/lib.rs => crates/common/src/lib.rs +19 -1
@@ 20,6 20,8 @@ use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::error::Error;

pub mod packet;

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct SaveData {
    // ----------------------------------------------------------------------


@@ 39,8 41,9 @@ pub struct SaveModule {
    pub children: Vec<Option<SaveModule>>,
}

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


@@ 49,6 52,21 @@ pub enum PartType {
    LandingThrusterSuspension,
}

#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
pub enum PlanetType {
    Sun,
    Mercury,
    Venus,
    Earth,
    Moon,
    Mars,
    Jupiter,
    Saturn,
    Uranus,
    Neptune,
    Pluto,
}

// no touchy. this is the struct that savefiles are actually represented in
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Savefile {

R crates/server/src/packet.rs => crates/common/src/packet.rs +24 -15
@@ 1,6 1,6 @@
use std::fmt::{Display, Formatter};
// StarKingdoms.IO, a browser game about drifting through space
//     Copyright (C) 2023 ghostly_zsh, TerraMaster85, core
//     Copyright (C) 2024 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


@@ 15,9 15,8 @@ use std::fmt::{Display, Formatter};
//     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 serde::{Deserialize, Serialize};
use tungstenite::Message;

use crate::{module::component::PartType, planet::PlanetType};
use crate::{PartType, PlanetType};

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct ProtoTransform {


@@ 147,9 146,9 @@ pub enum Packet {
    },
}

impl From<Packet> for Message {
impl From<Packet> for String {
    fn from(val: Packet) -> Self {
        Message::Text(serde_json::to_string(&val).expect("failed to serialize packet to json"))
        serde_json::to_string(&val).expect("failed to serialize packet to json")
    }
}



@@ 163,18 162,28 @@ impl Display for MsgFromError {
        write!(f, "{:?}", self)
    }
}
/*
match value {
    Message::Text(s) => serde_json::from_str(s).map_err(MsgFromError::JSONError),
    Message::Binary(b) => serde_json::from_slice(b).map_err(MsgFromError::JSONError),
    Message::Close(_) => Ok(Packet::_SpecialDisconnect {}),
    Message::Frame(_) | Message::Pong(_) | Message::Ping(_) => {
        Err(MsgFromError::InvalidMessageType)
    }
}
*/

impl TryFrom<&Message> for Packet {
impl TryFrom<&String> for Packet {
    type Error = MsgFromError;

    fn try_from(value: &Message) -> Result<Self, Self::Error> {
        match value {
            Message::Text(s) => serde_json::from_str(s).map_err(MsgFromError::JSONError),
            Message::Binary(b) => serde_json::from_slice(b).map_err(MsgFromError::JSONError),
            Message::Close(_) => Ok(Packet::_SpecialDisconnect {}),
            Message::Frame(_) | Message::Pong(_) | Message::Ping(_) => {
                Err(MsgFromError::InvalidMessageType)
            }
        }
    fn try_from(value: &String) -> Result<Self, Self::Error> {
        serde_json::from_str(value).map_err(MsgFromError::JSONError)
    }
}
impl TryFrom<&Vec<u8>> for Packet {
    type Error = MsgFromError;

    fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
        serde_json::from_slice(value).map_err(MsgFromError::JSONError)
    }
}

M crates/server/src/main.rs => crates/server/src/main.rs +0 -2
@@ 27,7 27,6 @@ use bevy_rapier2d::prelude::*;
use module::component::{
    Attach, CanAttach, LooseAttach, ModuleTimer, PartBundle, PartFlags, PartType,
};
use packet::*;
use serde::{Deserialize, Serialize};
use std::fs;



@@ 41,7 40,6 @@ pub mod crafting;
pub mod macros;
pub mod mathutil;
pub mod module;
pub mod packet;
pub mod planet;
pub mod player;
pub mod ws;

M crates/server/src/module/component.rs => crates/server/src/module/component.rs +4 -25
@@ 6,37 6,16 @@ use serde::{Deserialize, Serialize};
use starkingdoms_common::PartType as c_PartType;

#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, Default)]
pub enum PartType {
    #[default]
    Placeholder,
    Hearty,
    Cargo,
    Hub,
    LandingThruster,
    LandingThrusterSuspension,
}
pub struct PartType(pub c_PartType);

impl From<PartType> for c_PartType {
    fn from(value: PartType) -> Self {
        match value {
            PartType::Placeholder => c_PartType::Placeholder,
            PartType::Hearty => c_PartType::Hearty,
            PartType::Cargo => c_PartType::Cargo,
            PartType::Hub => c_PartType::Hub,
            PartType::LandingThruster => c_PartType::LandingThruster,
            PartType::LandingThrusterSuspension => c_PartType::LandingThrusterSuspension,
        }
        value.0
    }
}
impl From<c_PartType> for PartType {
    fn from(value: c_PartType) -> Self {
        match value {
            c_PartType::Placeholder => PartType::Placeholder,
            c_PartType::Hearty => PartType::Hearty,
            c_PartType::Cargo => PartType::Cargo,
            c_PartType::Hub => PartType::Hub,
            c_PartType::LandingThruster => PartType::LandingThruster,
            c_PartType::LandingThrusterSuspension => PartType::LandingThrusterSuspension,
        }
        PartType(value)
    }
}


M crates/server/src/module/mod.rs => crates/server/src/module/mod.rs +39 -32
@@ 4,11 4,18 @@ use bevy::{math::vec2, prelude::*};
use bevy_rapier2d::prelude::*;
use component::*;
use rand::Rng;
use starkingdoms_common::packet::Packet;
use starkingdoms_common::packet::Part;
use starkingdoms_common::proto_part_flags;
use starkingdoms_common::proto_transform;

use crate::ws::PacketMessageConvert;
use crate::{
    capacity, config::StkConfig, part, planet::PlanetType, player::component::Player,
    proto_part_flags, proto_transform, ws::WsEvent, Packet, Part,
    ws::WsEvent,
};
use starkingdoms_common::PartType as c_PartType;
use starkingdoms_common::PlanetType as c_PlanetType;

pub mod component;
pub mod save;


@@ 37,7 44,7 @@ pub fn module_spawn(
        transform.translation += Vec3::new(6000.0, 0.0, 0.0);
        let flags = PartFlags { attached: false };
        let mut entity = commands.spawn(PartBundle {
            part_type: PartType::Cargo,
            part_type: c_PartType::Cargo.into(),
            transform: TransformBundle::from(transform),
            flags,
            ..default()


@@ 50,21 57,21 @@ pub fn module_spawn(
            })
            .insert(AdditionalMassProperties::MassProperties(MassProperties {
                local_center_of_mass: vec2(0.0, 0.0),
                mass: part!(PartType::Cargo).mass,
                mass: part!(c_PartType::Cargo.into()).mass,
                principal_inertia: 7.5,
            }));

        let packet = Packet::SpawnPart {
            id: entity.id().index(),
            part: Part {
                part_type: PartType::Cargo,
                part_type: c_PartType::Cargo.into(),
                transform: proto_transform!(transform),
                flags: proto_part_flags!(flags),
            },
        };

        packet_send.send(WsEvent::Broadcast {
            message: packet.into(),
            message: packet.into_message(),
        });
    }
}


@@ 111,12 118,12 @@ pub fn detach_recursive(
        (entity, part_type, attach, flags)
    } else {
        let (entity, _, _, _, attach, part_flags) = player_query.get_mut(this).unwrap();
        (entity, &PartType::Hearty, attach, part_flags)
        (entity, &c_PartType::Hearty.into(), attach, part_flags)
    };
    energy += capacity!(*part_type);
    commands.entity(entity).remove::<Attach>();
    flags.attached = false;
    if *part_type == PartType::LandingThrusterSuspension {
    if *part_type == c_PartType::LandingThrusterSuspension .into(){
        let parent = attach.parent.unwrap();
        let parent_attach = attached_query.get(parent).unwrap().3;
        commands.entity(parent).insert(LooseAttach {


@@ 163,7 170,7 @@ pub fn despawn_module_tree(
        };

        packets.push(WsEvent::Broadcast {
            message: packet.into(),
            message: packet.into_message(),
        });

        let attach = match attached_query.get(*child) {


@@ 232,7 239,7 @@ pub fn attach_on_module_tree(
    for this in attach.clone().children.iter().flatten() {
        let module = part_query.get(select).unwrap();
        let part_type = *module.1;
        ret |= if part_type != PartType::LandingThrusterSuspension {
        ret |= if part_type != c_PartType::LandingThrusterSuspension.into() {
            attach_on_module_tree(
                x,
                y,


@@ 329,7 336,7 @@ pub fn attach_on_module_tree(
            .local_basis1(angle_offset);
        let mut children = [None, None, None, None];
        if let Some(loose_attach) = module.4 {
            if *module.1 == PartType::LandingThruster {
            if *module.1 == c_PartType::LandingThruster.into() {
                commands
                    .entity(loose_attach.children[2].unwrap())
                    .insert(Attach {


@@ 352,7 359,7 @@ pub fn attach_on_module_tree(
        attach.children[attachment_slot] = Some(module.0);
        module.5.attached = true;
        player_query.get_mut(player_id).unwrap().1.energy_capacity += capacity!(*module.1);
        if *module.1 == PartType::LandingThruster {
        if *module.1 == c_PartType::LandingThruster.into() {
            let loose_attach = module.4.unwrap().clone();
            let mut transform = part_query
                .get_mut(loose_attach.children[2].unwrap())


@@ 472,13 479,13 @@ fn convert_modules_recursive(
            module_transform,
            part_flags,
        ) = attached_query.get_mut(*child).unwrap();
        if *part_type == PartType::Cargo {
            match planet_type {
                PlanetType::Mars => {
                    *part_type = PartType::Hub;
        if *part_type == c_PartType::Cargo.into() {
            match planet_type.0 {
                c_PlanetType::Mars => {
                    *part_type = c_PartType::Hub.into();
                    *mass_prop = AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: Vec2::new(0.0, 0.0),
                        mass: part!(PartType::Hub).mass,
                        mass: part!(c_PartType::Hub.into()).mass,
                        principal_inertia: 7.5,
                    });
                    let (mut collider, mut transform, _) =


@@ 494,32 501,32 @@ fn convert_modules_recursive(
                        .unwrap()
                        .insert(CanAttach(15));

                    increase_capacity_by += part!(PartType::Hub).energy_capacity;
                    increase_capacity_by += part!(c_PartType::Hub.into()).energy_capacity;

                    let packet = Packet::DespawnPart { id: child.index() };

                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                        message: packet.into_message(),
                    });

                    let packet = Packet::SpawnPart {
                        id: child.index(),
                        part: Part {
                            part_type: PartType::Hub,
                            part_type: c_PartType::Hub.into(),
                            transform: proto_transform!(transform),
                            flags: proto_part_flags!(part_flags),
                        },
                    };

                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                        message: packet.into_message(),
                    });
                }
                PlanetType::Moon => {
                    *part_type = PartType::LandingThruster;
                c_PlanetType::Moon => {
                    *part_type = c_PartType::LandingThruster.into();
                    *mass_prop = AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: Vec2::new(0.0, 0.0),
                        mass: part!(PartType::LandingThruster).mass,
                        mass: part!(c_PartType::LandingThruster.into()).mass,
                        principal_inertia: 7.5,
                    });
                    let (mut collider, mut transform, _) =


@@ 535,7 542,7 @@ fn convert_modules_recursive(
                        .build();
                    let mut suspension = commands.spawn(PartBundle {
                        transform: TransformBundle::from(*module_transform),
                        part_type: PartType::LandingThrusterSuspension,
                        part_type: c_PartType::LandingThrusterSuspension.into(),
                        flags: PartFlags { attached: false },
                        ..default()
                    });


@@ 558,44 565,44 @@ fn convert_modules_recursive(
                        });
                    attach.children[2] = Some(suspension.id());

                    increase_capacity_by += part!(PartType::LandingThruster).energy_capacity;
                    increase_capacity_by += part!(c_PartType::LandingThruster.into()).energy_capacity;

                    let packet = Packet::DespawnPart { id: child.index() };

                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                        message: packet.into_message(),
                    });

                    let packet = Packet::SpawnPart {
                        id: child.index(),
                        part: Part {
                            part_type: PartType::LandingThruster,
                            part_type: c_PartType::LandingThruster.into(),
                            transform: proto_transform!(transform),
                            flags: proto_part_flags!(part_flags),
                        },
                    };

                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                        message: packet.into_message(),
                    });

                    let packet = Packet::SpawnPart {
                        id: suspension.id().index(),
                        part: Part {
                            part_type: PartType::LandingThrusterSuspension,
                            part_type: c_PartType::LandingThrusterSuspension.into(),
                            transform: proto_transform!(transform),
                            flags: proto_part_flags!(part_flags),
                        },
                    };

                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                        message: packet.into_message(),
                    });
                }
                _ => {}
            }
        }
        if *part_type != PartType::LandingThruster {
        if part_type.0 != c_PartType::LandingThruster {
            increase_capacity_by += convert_modules_recursive(
                commands,
                planet_type,


@@ 640,7 647,7 @@ pub fn break_modules(
    let joints = rapier_context.entity2impulse_joint();
    let mut detach_list = Vec::new();
    for (entity, part_type, _, attach, _, _, _, mut flags) in &mut attached_query {
        if *part_type == PartType::LandingThrusterSuspension {
        if part_type.0 == c_PartType::LandingThrusterSuspension {
            continue;
        }
        let handle = joints.get(&entity).unwrap();

M crates/server/src/module/save.rs => crates/server/src/module/save.rs +15 -15
@@ 2,11 2,11 @@ use std::{collections::HashMap, f32::consts::PI};

use bevy::{math::vec2, prelude::*};
use bevy_rapier2d::prelude::*;
use starkingdoms_common::SaveModule;
use starkingdoms_common::{packet::Packet, PartType as c_PartType, 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::{PacketMessageConvert, WsEvent}, Attach, CanAttach,
    LooseAttach, PartBundle, PartFlags, PartType,
};

pub fn load_savefile(


@@ 102,7 102,7 @@ pub fn load_savefile(
                module.id()
            };

            let children = if part_type != PartType::LandingThruster {
            let children = if part_type.0 != c_PartType::LandingThruster {
                let (children, count) = load_savefile(
                    commands,
                    transform,


@@ 150,22 150,22 @@ pub fn load_savefile(
            module
                .with_children(|children| {
                    children
                        .spawn(if part_type == PartType::Cargo {
                        .spawn(if part_type.0 == c_PartType::Cargo {
                            Collider::cuboid(0.375, 0.46875)
                        } else if part_type == PartType::Hub {
                        } else if part_type.0 == c_PartType::Hub {
                            Collider::cuboid(0.5, 0.5)
                        } else if part_type == PartType::LandingThruster {
                        } else if part_type.0 == c_PartType::LandingThruster {
                            Collider::cuboid(0.5, 0.375)
                        } else {
                            Collider::cuboid(0.5, 0.5)
                        })
                        .insert(TransformBundle::from(Transform::from_xyz(
                            0.,
                            if part_type == PartType::Cargo {
                            if part_type.0 == c_PartType::Cargo {
                                0.03125
                            } else if part_type == PartType::Hub {
                            } else if part_type.0 == c_PartType::Hub {
                                0.
                            } else if part_type == PartType::LandingThruster {
                            } else if part_type.0 == c_PartType::LandingThruster {
                                0.125
                            } else {
                                0.


@@ 178,7 178,7 @@ pub fn load_savefile(
                    mass: mass!(part_type),
                    principal_inertia: 7.5,
                }));
            if part_type == PartType::Hub {
            if part_type.0 == c_PartType::Hub {
                module.insert(CanAttach(15));
            }



@@ 188,7 188,7 @@ pub fn load_savefile(

            module.insert(ImpulseJoint::new(parent, joint));

            if part_type == PartType::LandingThruster {
            if part_type.0 == c_PartType::LandingThruster {
                let joint = PrismaticJointBuilder::new(Vec2::new(0., 1.))
                    .set_motor(0., 0., 3000., 3000.)
                    .limits([0., 1.])


@@ 207,7 207,7 @@ pub fn load_savefile(
                            0.,
                        )),
                    ),
                    part_type: PartType::LandingThrusterSuspension,
                    part_type: c_PartType::LandingThrusterSuspension.into(),
                    flags: PartFlags { attached: true },
                    ..default()
                });


@@ 263,7 263,7 @@ pub fn construct_save_data(
    for (i, child) in attach.children.iter().enumerate() {
        if let Some(child) = child {
            let (_, part_type, _, attach, _, _, _, _) = attached_query.get(*child).unwrap();
            if *part_type == PartType::LandingThrusterSuspension {
            if part_type.0 == c_PartType::LandingThrusterSuspension {
                continue;
            }
            let child_save_module = construct_save_data(attach.clone(), attached_query);


@@ 352,7 352,7 @@ pub fn save_eligibility(

        packet_send.send(WsEvent::Send {
            to: player.addr,
            message: packet.into(),
            message: packet.into_message(),
        });
    }
}

M crates/server/src/module/thruster.rs => crates/server/src/module/thruster.rs +5 -4
@@ 7,6 7,7 @@ use crate::{
};
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use starkingdoms_common::PartType as c_PartType;

pub fn search_thrusters(
    input: Input,


@@ 35,9 36,9 @@ pub fn search_thrusters(

        let mut force_mult = 0.;
        let mut energy_lose_by = 0;
        if *part_type == PartType::LandingThruster {
            force_mult = part!(PartType::LandingThruster).thruster_force;
            energy_lose_by = part!(PartType::LandingThruster).thruster_energy;
        if part_type.0 == c_PartType::LandingThruster {
            force_mult = part!(c_PartType::LandingThruster.into()).thruster_force;
            energy_lose_by = part!(c_PartType::LandingThruster.into()).thruster_energy;
        }
        if input.up && 3. * PI / 4. < relative_angle && relative_angle < 5. * PI / 4. {
            let thruster_force = ExternalForce::at_point(


@@ 165,7 166,7 @@ pub fn search_thrusters(
            }
        }

        if *part_type != PartType::LandingThruster && *energy >= energy_lose_by {
        if part_type.0 != c_PartType::LandingThruster && *energy >= energy_lose_by {
            search_thrusters(input, attach.clone(), p_transform, energy, attached_query);
        }
    }

M crates/server/src/planet.rs => crates/server/src/planet.rs +46 -57
@@ 3,21 3,10 @@ use bevy_rapier2d::prelude::*;
use serde::{Deserialize, Serialize};

use crate::{config::StkConfig, module::component::PartType, planet};
use starkingdoms_common::PlanetType as c_PlanetType;

#[derive(Component, Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
pub enum PlanetType {
    Sun,
    Mercury,
    Venus,
    Earth,
    Moon,
    Mars,
    Jupiter,
    Saturn,
    Uranus,
    Neptune,
    Pluto,
}
pub struct PlanetType(pub c_PlanetType);

#[derive(Bundle)]
pub struct PlanetBundle {


@@ 30,17 19,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let sun_pos = Transform::from_xyz(0.0, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Sun,
            planet_type: PlanetType(c_PlanetType::Sun),
            transform: TransformBundle::from(sun_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Sun).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Sun)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Sun).mass,
            planet!(PlanetType(c_PlanetType::Sun)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Sun).size + 0.3))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Sun)).size + 0.3))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 48,17 37,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let mercury_pos = Transform::from_xyz(2322.588, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Mercury,
            planet_type: PlanetType(c_PlanetType::Mercury),
            transform: TransformBundle::from(mercury_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Mercury).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Mercury)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Mercury).mass,
            planet!(PlanetType(c_PlanetType::Mercury)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Mercury).size + 0.3))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Mercury)).size + 0.3))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 66,17 55,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let venus_pos = Transform::from_xyz(4339.992, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Venus,
            planet_type: PlanetType(c_PlanetType::Venus),
            transform: TransformBundle::from(venus_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Venus).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Venus)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Venus).mass,
            planet!(PlanetType(c_PlanetType::Venus)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Venus).size + 0.3))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Venus)).size + 0.3))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 84,17 73,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let earth_pos = Transform::from_xyz(6000.0, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Earth,
            planet_type: PlanetType(c_PlanetType::Earth),
            transform: TransformBundle::from(earth_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Earth).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Earth)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Earth).mass,
            planet!(PlanetType(c_PlanetType::Earth)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Earth).size + 0.3))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Earth)).size + 0.3))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 102,17 91,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let moon_pos = Transform::from_xyz(6030.828, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Moon,
            planet_type: PlanetType(c_PlanetType::Moon),
            transform: TransformBundle::from(moon_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Moon).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Moon)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Moon).mass,
            planet!(PlanetType(c_PlanetType::Moon)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Moon).size + 0.1))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Moon)).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 120,17 109,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let mars_pos = Transform::from_xyz(9142.0833, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Mars,
            planet_type: PlanetType(c_PlanetType::Mars),
            transform: TransformBundle::from(mars_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Mars).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Mars)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Mars).mass,
            planet!(PlanetType(c_PlanetType::Mars)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Mars).size + 0.1))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Mars)).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 138,17 127,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let jupiter_pos = Transform::from_xyz(31222.8, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Jupiter,
            planet_type: PlanetType(c_PlanetType::Jupiter),
            transform: TransformBundle::from(jupiter_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Jupiter).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Jupiter)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Jupiter).mass,
            planet!(PlanetType(c_PlanetType::Jupiter)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Jupiter).size + 0.1))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Jupiter)).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 156,17 145,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let saturn_pos = Transform::from_xyz(57495.6, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Saturn,
            planet_type: PlanetType(c_PlanetType::Saturn),
            transform: TransformBundle::from(saturn_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Saturn).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Saturn)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Saturn).mass,
            planet!(PlanetType(c_PlanetType::Saturn)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Saturn).size + 0.1))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Saturn)).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 174,17 163,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let uranus_pos = Transform::from_xyz(115147.56, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Uranus,
            planet_type: PlanetType(c_PlanetType::Uranus),
            transform: TransformBundle::from(uranus_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Uranus).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Uranus)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Uranus).mass,
            planet!(PlanetType(c_PlanetType::Uranus)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Uranus).size + 0.1))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Uranus)).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 192,17 181,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let neptune_pos = Transform::from_xyz(180420.0, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Neptune,
            planet_type: PlanetType(c_PlanetType::Neptune),
            transform: TransformBundle::from(neptune_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Neptune).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Neptune)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Neptune).mass,
            planet!(PlanetType(c_PlanetType::Neptune)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Neptune).size + 0.1))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Neptune)).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })


@@ 210,17 199,17 @@ pub fn spawn_planets(mut commands: Commands) {
    let pluto_pos = Transform::from_xyz(236892.0, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Pluto,
            planet_type: PlanetType(c_PlanetType::Pluto),
            transform: TransformBundle::from(pluto_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Pluto).size))
        .insert(Collider::ball(planet!(PlanetType(c_PlanetType::Pluto)).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Pluto).mass,
            planet!(PlanetType(c_PlanetType::Pluto)).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Pluto).size + 0.1))
                .spawn(Collider::ball(planet!(PlanetType(c_PlanetType::Pluto)).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })

M crates/server/src/player/client_login.rs => crates/server/src/player/client_login.rs +29 -30
@@ 6,7 6,7 @@ use hmac::{Hmac, Mac};
use jwt::VerifyWithKey;
use rand::Rng;
use sha2::Sha256;
use starkingdoms_common::unpack_savefile;
use starkingdoms_common::{packet::{MessageType, Packet, Part, Planet, ProtoPartFlags}, proto_part_flags, proto_transform, unpack_savefile, PartType as c_PartType, PlanetType as c_PlanetType};

use crate::{
    config::StkConfig,


@@ 15,9 15,8 @@ use crate::{
        save::load_savefile,
    },
    planet::PlanetType,
    proto_part_flags, proto_transform,
    ws::WsEvent,
    AppKeys, MessageType, Packet, Part, Planet, ProtoPartFlags, UserToken, CLIENT_SCALE,
    ws::{PacketMessageConvert, WsEvent},
    AppKeys, UserToken, CLIENT_SCALE,
};

use super::component::{Input, Player};


@@ 36,7 35,7 @@ pub fn join_auth(
            Err(e) => {
                event_queue.push(WsEvent::Send {
                    to: *from,
                    message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Token is invalid or verification failed: {e}. Please log in again, or contact StarKingdoms staff if the problem persists.") }.into(),
                    message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Token is invalid or verification failed: {e}. Please log in again, or contact StarKingdoms staff if the problem persists.") }.into_message(),
                });
                event_queue.push(WsEvent::Close { addr: *from });
                return false;


@@ 46,7 45,7 @@ pub fn join_auth(
        if claims.permission_level < server_config.security.required_permission_level {
            event_queue.push(WsEvent::Send {
                to: *from,
                message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Permission level {} is too low, {} is required. If your permissions were just changed, you need to log out and log back in for the change to take effect. If you believe this is a mistake, contact StarKingdoms staff.", claims.permission_level, server_config.security.required_permission_level) }.into(),
                message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Permission level {} is too low, {} is required. If your permissions were just changed, you need to log out and log back in for the change to take effect. If you believe this is a mistake, contact StarKingdoms staff.", claims.permission_level, server_config.security.required_permission_level) }.into_message(),
            });
            event_queue.push(WsEvent::Close { addr: *from });
            return false;


@@ 54,12 53,12 @@ pub fn join_auth(

        event_queue.push(WsEvent::Send {
            to: *from,
            message: Packet::Message { message_type: MessageType::Server, actor: "StarKingdoms Team".to_string(), content: "Thank you for participating in the StarKingdoms private alpha! Your feedback is essential to improving the game, so please give us any feedback you have in the Discord! <3".to_string() }.into(),
            message: Packet::Message { message_type: MessageType::Server, actor: "StarKingdoms Team".to_string(), content: "Thank you for participating in the StarKingdoms private alpha! Your feedback is essential to improving the game, so please give us any feedback you have in the Discord! <3".to_string() }.into_message(),
        });
    } else if server_config.security.required_permission_level != 0 {
        event_queue.push(WsEvent::Send {
            to: *from,
            message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: "Authentication is required to join this server at the moment. Log in and try again, or try again later.".to_string() }.into(),
            message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: "Authentication is required to join this server at the moment. Log in and try again, or try again later.".to_string() }.into_message(),
        });
        event_queue.push(WsEvent::Close { addr: *from });
        return false;


@@ 87,11 86,11 @@ pub fn spawn_player(
        input: Input::default(),
        selected: None,
        save_eligibility: false,
        energy_capacity: part!(PartType::Hearty).energy_capacity,
        energy: part!(PartType::Hearty).energy_capacity,
        energy_capacity: part!(c_PartType::Hearty.into()).energy_capacity,
        energy: part!(c_PartType::Hearty.into()).energy_capacity,
    };
    let mut entity_id = commands.spawn(PartBundle {
        part_type: PartType::Hearty,
        part_type: c_PartType::Hearty.into(),
        transform: TransformBundle::from(transform),
        flags: PartFlags { attached: false },
        ..default()


@@ 100,7 99,7 @@ pub fn spawn_player(
        .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,
            mass: part!(c_PartType::Hearty.into()).mass,
            principal_inertia: 7.5,
        }));
    (entity_id.id(), transform, player_comp)


@@ 174,13 173,13 @@ pub fn load_save(
            attach.children = children;
        } else {
            let packet = Packet::Message {
                message_type: crate::packet::MessageType::Error,
                message_type: MessageType::Error,
                actor: "SERVER".to_string(),
                content: "Savefile signature corrupted or inner data invalid. Save was not loaded. Contact StarKingdoms staff for assistance.".to_string(),
            };
            event_queue.push(WsEvent::Send {
                to: *from,
                message: packet.into(),
                message: packet.into_message(),
            });
        }
    } else {


@@ 238,7 237,7 @@ pub fn packet_stream(
        planets.push((
            entity.index(),
            Planet {
                planet_type: *planet_type,
                planet_type: planet_type.0,
                transform: proto_transform!(Transform::from_translation(
                    translation * CLIENT_SCALE
                )),


@@ 249,7 248,7 @@ pub fn packet_stream(
    let packet = Packet::PlanetPositions { planets };
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into(),
        message: packet.into_message(),
    });

    // tell the player already existing users


@@ 260,7 259,7 @@ pub fn packet_stream(
    let packet = Packet::PlayerList { players };
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into(),
        message: packet.into_message(),
    });

    // tell other players that a player has spawned in


@@ 269,15 268,15 @@ pub fn packet_stream(
        username: username.to_string(),
    };
    event_queue.push(WsEvent::Broadcast {
        message: packet.into(),
        message: packet.into_message(),
    });
    let packet = Packet::Message {
        message_type: crate::packet::MessageType::Server,
        message_type: MessageType::Server,
        actor: "SERVER".to_string(),
        content: format!("{} has joined the server!", username),
    };
    event_queue.push(WsEvent::Broadcast {
        message: packet.into(),
        message: packet.into_message(),
    });

    // tell the player where parts are


@@ 286,7 285,7 @@ pub fn packet_stream(
        parts.push((
            entity.index(),
            Part {
                part_type: *part_type,
                part_type: part_type.0,
                transform: proto_transform!(Transform::from_translation(
                    transform.translation * CLIENT_SCALE
                )),


@@ 298,7 297,7 @@ pub fn packet_stream(
        parts.push((
            entity.index(),
            Part {
                part_type: *part_type,
                part_type: part_type.0,
                transform: proto_transform!(Transform::from_translation(
                    transform.translation * CLIENT_SCALE
                )),


@@ 309,7 308,7 @@ pub fn packet_stream(
    parts.push((
        index,
        Part {
            part_type: PartType::Hearty,
            part_type: c_PartType::Hearty.into(),
            transform: proto_transform!(Transform::from_translation(transform.translation)
                .with_rotation(transform.rotation)),
            flags: ProtoPartFlags { attached: false },


@@ 318,12 317,12 @@ pub fn packet_stream(
    let packet = Packet::PartPositions { parts };
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into(),
        message: packet.into_message(),
    });

    // and send the welcome message :)
    let packet = Packet::Message {
        message_type: crate::packet::MessageType::Server,
        message_type: MessageType::Server,
        actor: "SERVER".to_string(),
        content: format!(
            "starkingdoms-server v{} says hello",


@@ 332,24 331,24 @@ pub fn packet_stream(
    };
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into(),
        message: packet.into_message(),
    });
    let packet = Packet::Message {
        message_type: crate::packet::MessageType::Server,
        message_type: MessageType::Server,
        actor: "SERVER".to_string(),
        content: "Welcome to StarKingdoms.IO! Have fun!".to_string(),
    };
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into(),
        message: packet.into_message(),
    });
    let packet = Packet::Message {
        message_type: crate::packet::MessageType::Server,
        message_type: MessageType::Server,
        actor: "SERVER".to_string(),
        content: "Found a bug? Have a feature request? Please bring this and all other feedback to the game's official Discord server! Join here: https://discord.gg/3u7Yw8DWtQ".to_string(),
    };
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into(),
        message: packet.into_message(),
    });
}

M crates/server/src/player/mod.rs => crates/server/src/player/mod.rs +8 -7
@@ 5,6 5,7 @@ use component::Player;
use player_mouse_input::{attach_or_detach, mouse_picking};
use request_save::request_save;
use send_message::send_message;
use starkingdoms_common::{packet::Packet, PartType as c_PartType};

use crate::{
    config::StkConfig,


@@ 16,8 17,8 @@ use crate::{
    },
    part,
    planet::PlanetType,
    ws::WsEvent,
    AppKeys, Packet, CLIENT_SCALE,
    ws::{PacketMessageConvert, WsEvent},
    AppKeys, CLIENT_SCALE,
};

pub mod client_login;


@@ 73,7 74,7 @@ pub fn on_message(
    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());
            let packet: Packet = err_or_cont!(Packet::from_message(message));

            match packet {
                Packet::ClientLogin {


@@ 295,12 296,12 @@ pub fn player_input_update(

        // 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;
            if force_multiplier != 0.0 && player.energy >= part!(c_PartType::Hearty.into()).thruster_energy {
                player.energy -= part!(c_PartType::Hearty.into()).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_force = force_multiplier * part!(c_PartType::Hearty.into()).thruster_force;
                let thruster_vec = vec2(-thruster_force * rot.sin(), thruster_force * rot.cos());
                let thruster_force = ExternalForce::at_point(
                    thruster_vec,


@@ 313,7 314,7 @@ 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 {
        if player.energy >= part!(c_PartType::LandingThruster.into()).thruster_energy {
            // go through all thrusters and apply force
            crate::module::thruster::search_thrusters(
                player.input,

M crates/server/src/player/packet.rs => crates/server/src/player/packet.rs +9 -9
@@ 1,11 1,11 @@
use bevy::{ecs::event::ManualEventReader, prelude::*};
use starkingdoms_common::{packet::{Packet, Part, Planet}, proto_part_flags, proto_transform};

use crate::{
    module::component::{Attach, PartFlags, PartType},
    planet::PlanetType,
    proto_part_flags, proto_transform,
    ws::WsEvent,
    Packet, Part, Planet, CLIENT_SCALE,
    ws::{PacketMessageConvert, WsEvent},
    CLIENT_SCALE,
};

use super::component::Player;


@@ 19,7 19,7 @@ pub fn send_player_energy(player_query: Query<&Player>, mut packet_send: EventWr

        packet_send.send(WsEvent::Send {
            to: player.addr,
            message: packet.into(),
            message: packet.into_message(),
        });
    }
}


@@ 36,7 36,7 @@ pub fn on_position_change(
        updated_parts.push((
            id,
            Part {
                part_type: *part_type,
                part_type: part_type.0,
                transform: proto_transform!(Transform::from_translation(
                    transform.translation * CLIENT_SCALE,
                )


@@ 52,7 52,7 @@ pub fn on_position_change(
        };

        packet_send.send(WsEvent::Broadcast {
            message: packet.into(),
            message: packet.into_message(),
        });
    }



@@ 62,7 62,7 @@ pub fn on_position_change(
        planets.push((
            id,
            Planet {
                planet_type: *planet_type,
                planet_type: planet_type.0,
                transform: proto_transform!(Transform::from_translation(
                    transform.translation * CLIENT_SCALE
                )),


@@ 75,7 75,7 @@ pub fn on_position_change(
        let packet = Packet::PlanetPositions { planets };

        packet_send.send(WsEvent::Broadcast {
            message: packet.into(),
            message: packet.into_message(),
        });
    }
}


@@ 108,7 108,7 @@ pub fn on_close(
                        if entity != in_entity {
                            packets.push(WsEvent::Send {
                                to: player.addr,
                                message: packet.clone().into(),
                                message: packet.clone().into_message(),
                            });
                        }
                    }

M crates/server/src/player/player_mouse_input.rs => crates/server/src/player/player_mouse_input.rs +7 -6
@@ 5,6 5,7 @@ use crate::{
    module::component::{Attach, CanAttach, LooseAttach, PartFlags, PartType},
    planet::PlanetType,
};
use starkingdoms_common::PartType as c_PartType;

use super::component::Player;



@@ 57,7 58,7 @@ pub fn attach_or_detach(
            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 {
        if *module.1 == c_PartType::LandingThruster.into() {
            let sub_entity = attach.children[2].unwrap();
            let mut suspension = attached_query.get_mut(sub_entity).unwrap();
            suspension.2.translation = vec3(x, y, 0.);


@@ 85,7 86,7 @@ pub fn attach_or_detach(
    // 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 *part.1 == c_PartType::LandingThruster.into() {
        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();


@@ 125,7 126,7 @@ pub fn mouse_picking(
    entity: Entity,
) {
    for (m_entity, part_type, transform, m_attach, _velocity, _, _, _) in attached_query.iter() {
        if *part_type == PartType::LandingThrusterSuspension {
        if *part_type == c_PartType::LandingThrusterSuspension.into() {
            continue;
        }
        let pos = transform.translation;


@@ 135,7 136,7 @@ pub fn mouse_picking(
        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 {
        if let c_PartType::Cargo = part_type.0 {
            bound = [-0.375, 0.375, -0.5, 0.4375];
        }



@@ 150,7 151,7 @@ pub fn mouse_picking(
        }
    }
    for (entity, part_type, transform, _, _, _) in part_query.iter() {
        if *part_type == PartType::LandingThrusterSuspension {
        if *part_type == c_PartType::LandingThrusterSuspension.into() {
            continue;
        }
        let pos = transform.translation;


@@ 160,7 161,7 @@ pub fn mouse_picking(
        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 {
        if let c_PartType::Cargo = part_type.0 {
            bound = [-0.375, 0.375, -0.5, 0.4375];
        }


M crates/server/src/player/request_save.rs => crates/server/src/player/request_save.rs +4 -4
@@ 2,13 2,13 @@ use std::net::SocketAddr;

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

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

use super::component::Player;


@@ 53,6 53,6 @@ pub fn request_save(

    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into(),
        message: packet.into_message(),
    });
}

M crates/server/src/player/send_message.rs => crates/server/src/player/send_message.rs +7 -7
@@ 2,12 2,12 @@ use std::net::SocketAddr;

use bevy::prelude::*;
use bevy_rapier2d::prelude::Velocity;
use starkingdoms_common::packet::Packet;

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

use super::component::Player;


@@ 46,28 46,28 @@ pub fn send_message(
        }
        let target_player = target_player.unwrap();
        let packet = Packet::Message {
            message_type: crate::packet::MessageType::Direct,
            message_type: starkingdoms_common::packet::MessageType::Direct,
            actor: player.username.clone(),
            content,
        };
        event_queue.push(WsEvent::Send {
            to: target_player.addr,
            message: packet.clone().into(),
            message: packet.clone().into_message(),
        });
        event_queue.push(WsEvent::Send {
            to: *from,
            message: packet.into(),
            message: packet.into_message(),
        });
    } else {
        // send to general chat
        let packet = Packet::Message {
            message_type: crate::packet::MessageType::Chat,
            message_type: starkingdoms_common::packet::MessageType::Chat,
            actor: player.username.clone(),
            content,
        };

        event_queue.push(WsEvent::Broadcast {
            message: packet.into(),
            message: packet.into_message(),
        });
    }
}

M crates/server/src/ws.rs => crates/server/src/ws.rs +22 -0
@@ 19,6 19,7 @@ use bevy::ecs::event::ManualEventReader;
use bevy::log::{error, warn};
use bevy::prelude::{Commands, Event, Events, Local, Res, ResMut, Resource};
use crossbeam_channel::{unbounded, Receiver};
use starkingdoms_common::packet::{MsgFromError, Packet};
use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr, TcpListener};
use std::sync::{Arc, RwLock};


@@ 39,6 40,27 @@ impl Plugin for StkTungsteniteServerPlugin {
    }
}

pub trait PacketMessageConvert {
    fn from_message(value: &Message) -> Result<Packet, MsgFromError>;
    fn into_message(self) -> Message;
}

impl PacketMessageConvert for Packet {
    fn from_message(value: &Message) -> Result<Packet, MsgFromError> {
        match value {
            Message::Text(s) => serde_json::from_str(s).map_err(MsgFromError::JSONError),
            Message::Binary(b) => serde_json::from_slice(b).map_err(MsgFromError::JSONError),
            Message::Close(_) => Ok(Packet::_SpecialDisconnect {}),
            Message::Frame(_) | Message::Pong(_) | Message::Ping(_) => {
                Err(MsgFromError::InvalidMessageType)
            }
        }
    }
    fn into_message(self) -> Message {
        Message::Text(serde_json::to_string(&self).expect("failed to serialize packet to json"))
    }
}

#[derive(Event, Clone, Debug)]
pub enum WsEvent {
    Connection { from: SocketAddr },