~starkingdoms/starkingdoms

361225ca8122e4ae48938069793570552a3c8dd1 — core 8 months ago b558c59
networking refactor
M crates/client/src/lib.rs => crates/client/src/lib.rs +2 -2
@@ 1,10 1,10 @@
use crate::components::PlayerResources;
use crate::networking::websocket::Websocket;
use bevy_ecs::{event::Events, world::World};
use components::{Camera, Chat, Part, Player, RecvPacket, SendPacket, Texture, Transform};
use nalgebra::{Rotation2, Scale3, Translation3};
use platform::websocket::Ws;
use crate::networking::websocket::Websocket;
use platform::assets::Assets;
use platform::websocket::Ws;
use rendering::assets::AssetLoader;
use rendering::App;
use tracing::info;

M crates/client/src/native/websocket.rs => crates/client/src/native/websocket.rs +3 -3
@@ 3,11 3,11 @@ use std::{
    sync::{Arc, Mutex},
};

use crate::networking::websocket::{PacketMessageConvert, Websocket};
use bevy_ecs::system::Resource;
use crossbeam::channel::{unbounded, Receiver, Sender};
use starkingdoms_common::packet::{MsgFromError, Packet};
use starkingdoms_common::packet::{MsgFromError, Packet, SpecialDisconnectPacket};
use tungstenite::{connect, stream::MaybeTlsStream, Message, WebSocket};
use crate::networking::websocket::{PacketMessageConvert, Websocket};

impl PacketMessageConvert for Packet {
    type M = Message;


@@ 16,7 16,7 @@ impl PacketMessageConvert for Packet {
        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::Close(_) => Ok(Packet::_SpecialDisconnect(SpecialDisconnectPacket {})),
            Message::Frame(_) | Message::Pong(_) | Message::Ping(_) => {
                Err(MsgFromError::InvalidMessageType)
            }

M crates/client/src/networking/mod.rs => crates/client/src/networking/mod.rs +24 -17
@@ 1,5 1,9 @@
use std::collections::HashMap;

use crate::components::{
    Camera, Chat, Menu, Part, Player, PlayerResources, RecvPacket, SendPacket, ServerId,
    SpriteBundle, Texture, Transform,
};
use bevy_ecs::{
    entity::Entity,
    event::Events,


@@ 7,12 11,16 @@ use bevy_ecs::{
    world::World,
};
use nalgebra::{Rotation2, Scale3, Translation3};
use starkingdoms_common::{packet::Packet, PartType, PlanetType};

use crate::components::{
    Camera, Chat, Menu, Part, Player, PlayerResources, RecvPacket, SendPacket, ServerId,
    SpriteBundle, Texture, Transform,
use starkingdoms_common::packet::Packet::{
    DespawnPart, EnergyUpdate, LoginResponse, Message, OpenCraftingUi, PartPositions,
    PlanetPositions, PlayerLeave, PlayerList, SpawnPart, SpawnPlayer,
};
use starkingdoms_common::packet::{
    DespawnPartPacket, EnergyUpdatePacket, LoginResponsePacket, MessagePacket,
    OpenCraftingUiPacket, PartPositionsPacket, PlanetPositionsPacket, PlayerLeavePacket,
    PlayerListPacket, SpawnPartPacket, SpawnPlayerPacket,
};
use starkingdoms_common::{packet::Packet, PartType, PlanetType};

pub mod websocket;



@@ 47,16 55,15 @@ pub fn process_packets(
    recv_packet_events: &mut Events<RecvPacket>,
    planet_types: &mut HashMap<PlanetType, (Entity, u32)>,
) {
    use Packet::*;
    let mut recv_cursor = recv_packet_events.get_cursor();
    for recv in recv_cursor.read(&recv_packet_events) {
        match &recv.0 {
            LoginResponse { id } => {
            LoginResponse(LoginResponsePacket { id }) => {
                let mut player_query = world.query_filtered::<Entity, With<Player>>();
                let entity = player_query.single(world);
                world.entity_mut(entity).insert(ServerId(*id));
            }
            PlayerList { players } => {
            PlayerList(PlayerListPacket { players }) => {
                // existing players
                // username sync, eventually
                for player in players {


@@ 74,7 81,7 @@ pub fn process_packets(
                    ));
                }
            }
            SpawnPlayer { id, .. } => {
            SpawnPlayer(SpawnPlayerPacket { id, .. }) => {
                // username sync, eventually
                world.spawn((
                    Transform {


@@ 89,7 96,7 @@ pub fn process_packets(
                    Part(false),
                ));
            }
            SpawnPart { id, part } => {
            SpawnPart(SpawnPartPacket { id, part }) => {
                world.spawn((
                    Transform {
                        translation: Translation3::new(part.transform.x, part.transform.y, 0.0),


@@ 103,7 110,7 @@ pub fn process_packets(
                    Part(part.flags.attached),
                ));
            }
            PartPositions { parts } => {
            PartPositions(PartPositionsPacket { parts }) => {
                for (id, part) in parts {
                    if part.part_type == PartType::Hearty {
                        let mut player_query = world.query_filtered::<&ServerId, With<Player>>();


@@ 147,7 154,7 @@ pub fn process_packets(
                    }
                }
            }
            PlanetPositions { planets } => {
            PlanetPositions(PlanetPositionsPacket { planets }) => {
                for (server_id, planet) in planets {
                    if !planet_types.contains_key(&planet.planet_type) {
                        let entity = world.spawn(SpriteBundle {


@@ 192,12 199,12 @@ pub fn process_packets(
                    }
                }
            }
            EnergyUpdate { amount, max } => {
            EnergyUpdate(EnergyUpdatePacket { amount, max }) => {
                let mut r = world.resource_mut::<PlayerResources>();
                r.fuel_amount = *amount;
                r.fuel_max = *max;
            }
            OpenCraftingUi { id } => {
            OpenCraftingUi(OpenCraftingUiPacket { id }) => {
                let mut query = world.query::<(Entity, &ServerId)>();
                let mut matching_id = None;
                for (entity, server_id) in query.iter(world) {


@@ 209,12 216,12 @@ pub fn process_packets(
                    world.entity_mut(id).insert(Menu);
                }
            }
            Message { actor, content, .. } => {
            Message(MessagePacket { actor, content, .. }) => {
                let mut chat = world.get_resource_mut::<Chat>().unwrap();
                chat.messages
                    .push(format!("{}: {}", actor.clone(), content.clone()));
            }
            PlayerLeave { id } => {
            PlayerLeave(PlayerLeavePacket { id }) => {
                let mut part_query = world.query_filtered::<(Entity, &ServerId), With<Part>>();
                let mut entity_to_remove = None;
                for (entity, server_id) in part_query.iter_mut(world) {


@@ 229,7 236,7 @@ pub fn process_packets(
                    None => {}
                }
            }
            DespawnPart { id } => {
            DespawnPart(DespawnPartPacket { id }) => {
                let mut part_query = world.query_filtered::<(Entity, &ServerId), With<Part>>();
                let mut entity_to_remove = None;
                for (entity, server_id) in part_query.iter_mut(world) {

M crates/client/src/networking/websocket.rs => crates/client/src/networking/websocket.rs +1 -1
@@ 10,4 10,4 @@ pub trait PacketMessageConvert {

    fn from_message(value: &Self::M) -> Result<Packet, MsgFromError>;
    fn as_message(&self) -> Self::M;
}
\ No newline at end of file
}

M crates/client/src/rendering/mod.rs => crates/client/src/rendering/mod.rs +35 -23
@@ 22,7 22,7 @@ use glutin::{
#[cfg(not(target_arch = "wasm32"))]
use glutin_winit::{DisplayBuilder, GlWindow};
use nalgebra::{Scale3, Translation3, Vector3};
use starkingdoms_common::packet::{ButtonType, Packet};
use starkingdoms_common::packet::{ButtonType, Packet, PlayerInputPacket, PlayerMouseInputPacket};
use starkingdoms_common::PlanetType;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::{prelude::Closure, JsCast};


@@ 41,12 41,12 @@ use winit::{

use crate::components::{Camera, Menu, Player, RecvPacket, SendPacket, Texture, Transform};
use crate::networking::process_packets;
use crate::networking::websocket::Websocket;
use crate::platform::websocket::Ws;
use crate::rendering::renderer::{RenderCreateContext, Renderer};
use crate::rendering::MaybeRenderer::{Initialized, Initializing};
use crate::ui::{draw_ui, init_ui};
use assets::AssetLoader;
use crate::networking::websocket::Websocket;
use crate::rendering::MaybeRenderer::{Initialized, Initializing};
use crate::rendering::renderer::{RenderCreateContext, Renderer};

pub mod assets;
mod renderer;


@@ 54,7 54,7 @@ mod renderer;
enum MaybeRenderer {
    Uninitialized(RenderCreateContext),
    Initializing,
    Initialized(Renderer)
    Initialized(Renderer),
}

pub struct App {


@@ 69,8 69,6 @@ pub struct App {
    gl: Option<Arc<glow::Context>>,
}



impl App {
    pub fn new(
        world: World,


@@ 243,7 241,9 @@ impl ApplicationHandler for App {

        let mut rcc = Initializing;
        swap(&mut self.renderer, &mut rcc);
        let MaybeRenderer::Uninitialized(rcc) = rcc else { unreachable!() };
        let MaybeRenderer::Uninitialized(rcc) = rcc else {
            unreachable!()
        };

        let renderer = unsafe { Renderer::new(gl.clone(), event_loop, rcc) };



@@ 257,7 257,9 @@ impl ApplicationHandler for App {
        _window_id: winit::window::WindowId,
        event: winit::event::WindowEvent,
    ) {
        let Initialized(renderer) = &mut self.renderer else {return};
        let Initialized(renderer) = &mut self.renderer else {
            return;
        };
        let Some(window) = &self.window else { return };

        if renderer.egui_glow.on_window_event(window, &event).consumed {


@@ 279,7 281,7 @@ impl ApplicationHandler for App {
                let mut camera = self.world.get_resource_mut::<Camera>().unwrap();
                camera.width = size.width;
                camera.height = size.height;
/*
                /*
                unsafe {
                    self.gl
                        .as_ref()


@@ 345,13 347,14 @@ impl ApplicationHandler for App {
                            },
                        }
                        if matched {
                            renderer.send_packet_events
                                .send(SendPacket(Packet::PlayerInput {
                            renderer
                                .send_packet_events
                                .send(SendPacket(Packet::PlayerInput(PlayerInputPacket {
                                    up: renderer.up,
                                    down: renderer.down,
                                    left: renderer.left,
                                    right: renderer.right,
                                }));
                                })));
                        }
                    }
                    PhysicalKey::Unidentified(_) => {} // unsupported


@@ 379,22 382,31 @@ impl ApplicationHandler for App {

                let camera = self.world.get_resource::<Camera>().unwrap();
                let view = camera.to_cursor_matrix();
                let pos =
                    view * Vector3::new(renderer.mouse_pos.x as f32, renderer.mouse_pos.y as f32, 1.0);
                let pos = view
                    * Vector3::new(
                        renderer.mouse_pos.x as f32,
                        renderer.mouse_pos.y as f32,
                        1.0,
                    );
                let pos = pos / pos.z;
                renderer.send_packet_events
                    .send(SendPacket(Packet::PlayerMouseInput {
                        x: pos.x,
                        y: pos.y,
                        released: !state.is_pressed(),
                        button,
                    }));
                renderer
                    .send_packet_events
                    .send(SendPacket(Packet::PlayerMouseInput(
                        PlayerMouseInputPacket {
                            x: pos.x,
                            y: pos.y,
                            released: !state.is_pressed(),
                            button,
                        },
                    )));
            }
            _ => {}
        }
    }
    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        let Initialized(renderer) = &mut self.renderer else { return };
        let Initialized(renderer) = &mut self.renderer else {
            return;
        };
        let Some(window) = &self.window else { return };

        let mut ws = self

M crates/client/src/rendering/renderer.rs => crates/client/src/rendering/renderer.rs +93 -88
@@ 1,5 1,6 @@
use std::collections::HashMap;
use std::sync::Arc;
use crate::components::{Camera, Player, RecvPacket, SendPacket, Texture, Transform};
use crate::rendering::assets::AssetLoader;
use crate::ui::draw_ui;
use bevy_ecs::entity::Entity;
use bevy_ecs::event::Events;
use bevy_ecs::prelude::With;


@@ 8,12 9,11 @@ use egui::Window;
use egui_glow::EguiGlow;
use glow::PixelUnpackData;
use nalgebra::{Scale3, Translation3};
use starkingdoms_common::PlanetType;
use std::collections::HashMap;
use std::sync::Arc;
use winit::dpi::PhysicalPosition;
use winit::event_loop::ActiveEventLoop;
use starkingdoms_common::PlanetType;
use crate::components::{Camera, Player, RecvPacket, SendPacket, Texture, Transform};
use crate::ui::draw_ui;
use crate::rendering::assets::AssetLoader;

pub struct Renderer {
    program: glow::Program,


@@ 37,7 37,7 @@ pub struct Renderer {
pub struct RenderCreateContext {
    pub send_packet_events: Events<SendPacket>,
    pub recv_packet_events: Events<RecvPacket>,
    pub planet_types: HashMap<PlanetType, (Entity, u32)>
    pub planet_types: HashMap<PlanetType, (Entity, u32)>,
}

const VERTICES: [f32; 16] = [


@@ 46,78 46,81 @@ const VERTICES: [f32; 16] = [
const INDICES: [u32; 6] = [0, 1, 2, 2, 3, 0];

impl Renderer {
    pub unsafe fn new(gl: Arc<glow::Context>, event_loop: &ActiveEventLoop, ctx: RenderCreateContext) -> Self {
    pub unsafe fn new(
        gl: Arc<glow::Context>,
        event_loop: &ActiveEventLoop,
        ctx: RenderCreateContext,
    ) -> Self {
        use glow::HasContext as _;

            let shaders = [
                (
                    "vertex",
                    include_str!("../assets/shaders/vertex.glsl"),
                    glow::VERTEX_SHADER,
                ),
                (
                    "fragment",
                    include_str!("../assets/shaders/fragment.glsl"),
                    glow::FRAGMENT_SHADER,
                ),
            ];
            let program = gl.create_program().expect("Failed to create program");

            for (name, source, shader_type) in shaders {
                let shader = gl
                    .create_shader(shader_type)
                    .expect("Failed to create vertex shader");
                gl.shader_source(shader, source);
                gl.compile_shader(shader);
                if !gl.get_shader_compile_status(shader) {
                    tracing::error!(
                        "error in {} shader: {}",
                        name,
                        gl.get_shader_info_log(shader)
                    );
                }
                gl.attach_shader(program, shader);
                gl.delete_shader(shader);
        let shaders = [
            (
                "vertex",
                include_str!("../assets/shaders/vertex.glsl"),
                glow::VERTEX_SHADER,
            ),
            (
                "fragment",
                include_str!("../assets/shaders/fragment.glsl"),
                glow::FRAGMENT_SHADER,
            ),
        ];
        let program = gl.create_program().expect("Failed to create program");

        for (name, source, shader_type) in shaders {
            let shader = gl
                .create_shader(shader_type)
                .expect("Failed to create vertex shader");
            gl.shader_source(shader, source);
            gl.compile_shader(shader);
            if !gl.get_shader_compile_status(shader) {
                tracing::error!(
                    "error in {} shader: {}",
                    name,
                    gl.get_shader_info_log(shader)
                );
            }
            gl.link_program(program);

            gl.use_program(Some(program));

            let vertex_array = gl
                .create_vertex_array()
                .expect("Failed to create vertex array");
            gl.bind_vertex_array(Some(vertex_array));
            let vertex_buffer = gl.create_buffer().expect("Failed to create vertex buffer");
            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
            let element_buffer = gl.create_buffer().expect("Failed to create element buffer");
            gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(element_buffer));
            gl.buffer_data_u8_slice(
                glow::ARRAY_BUFFER,
                std::slice::from_raw_parts(VERTICES.as_ptr() as *const u8, size_of_val(&VERTICES)),
                glow::STATIC_DRAW,
            );
            gl.buffer_data_u8_slice(
                glow::ELEMENT_ARRAY_BUFFER,
                std::slice::from_raw_parts(INDICES.as_ptr() as *const u8, size_of_val(&INDICES)),
                glow::STATIC_DRAW,
            );

            gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, 4 * size_of::<f32>() as i32, 0);
            gl.enable_vertex_attrib_array(0);
            gl.vertex_attrib_pointer_f32(
                1,
                2,
                glow::FLOAT,
                false,
                4 * size_of::<f32>() as i32,
                2 * size_of::<f32>() as i32,
            );
            gl.enable_vertex_attrib_array(1);

            gl.clear_color(0.1, 0.1, 0.1, 1.0);
            gl.enable(glow::BLEND);
            gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);

            gl.attach_shader(program, shader);
            gl.delete_shader(shader);
        }
        gl.link_program(program);

        gl.use_program(Some(program));

        let vertex_array = gl
            .create_vertex_array()
            .expect("Failed to create vertex array");
        gl.bind_vertex_array(Some(vertex_array));
        let vertex_buffer = gl.create_buffer().expect("Failed to create vertex buffer");
        gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
        let element_buffer = gl.create_buffer().expect("Failed to create element buffer");
        gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(element_buffer));
        gl.buffer_data_u8_slice(
            glow::ARRAY_BUFFER,
            std::slice::from_raw_parts(VERTICES.as_ptr() as *const u8, size_of_val(&VERTICES)),
            glow::STATIC_DRAW,
        );
        gl.buffer_data_u8_slice(
            glow::ELEMENT_ARRAY_BUFFER,
            std::slice::from_raw_parts(INDICES.as_ptr() as *const u8, size_of_val(&INDICES)),
            glow::STATIC_DRAW,
        );

        gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, 4 * size_of::<f32>() as i32, 0);
        gl.enable_vertex_attrib_array(0);
        gl.vertex_attrib_pointer_f32(
            1,
            2,
            glow::FLOAT,
            false,
            4 * size_of::<f32>() as i32,
            2 * size_of::<f32>() as i32,
        );
        gl.enable_vertex_attrib_array(1);

        gl.clear_color(0.1, 0.1, 0.1, 1.0);
        gl.enable(glow::BLEND);
        gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);

        let egui_glow = EguiGlow::new(event_loop, gl.clone(), None, None, true);



@@ 141,16 144,20 @@ impl Renderer {
        }
    }

    pub unsafe fn draw(&mut self, gl: &glow::Context, window: &winit::window::Window, world: &mut World) {
    pub unsafe fn draw(
        &mut self,
        gl: &glow::Context,
        window: &winit::window::Window,
        world: &mut World,
    ) {
        use glow::HasContext as _;

        let player = world.query_filtered::<&Transform, With<Player>>();

        // draw the UI
        self.egui_glow
            .run(window, |ctx| {
                draw_ui(ctx, &mut *world, &mut self.send_packet_events);
            });
        self.egui_glow.run(window, |ctx| {
            draw_ui(ctx, &mut *world, &mut self.send_packet_events);
        });

        let camera = world.get_resource::<Camera>().unwrap();
        let x_scale = camera.zoom / camera.width as f32 * 2.0;


@@ 238,10 245,9 @@ impl Renderer {
            let y_range = camera.height as f32 / camera.zoom / 400.0;
            for i in ((-x_range / 2.0) as i32 - 1)..=((x_range / 2.0) as i32 + 1) {
                for j in ((-y_range / 2.0) as i32 - 1)..=((y_range / 2.0) as i32 + 1) {
                    let model =
                        Translation3::new(x + (i * 400) as f32, y + (j * 400) as f32, 0.0)
                            .to_homogeneous()
                            * Scale3::new(200.0, 200.0, 1.0).to_homogeneous();
                    let model = Translation3::new(x + (i * 400) as f32, y + (j * 400) as f32, 0.0)
                        .to_homogeneous()
                        * Scale3::new(200.0, 200.0, 1.0).to_homogeneous();
                    gl.uniform_matrix_4_f32_slice(model_loc.as_ref(), false, model.as_slice());
                    gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0);
                }


@@ 291,7 297,6 @@ impl Renderer {
        }

        // paint UI
        self.egui_glow
            .paint(window);
        self.egui_glow.paint(window);
    }
}
\ No newline at end of file
}

M crates/client/src/ui/mod.rs => crates/client/src/ui/mod.rs +3 -3
@@ 10,7 10,7 @@ use bevy_ecs::event::Events;
use bevy_ecs::prelude::With;
use bevy_ecs::world::World;
use egui::{Align, Align2, CursorIcon, Layout, Margin, Order, RichText, Shadow, Visuals};
use starkingdoms_common::packet::Packet;
use starkingdoms_common::packet::{Packet, SendMessagePacket};

pub fn init_ui(ctx: egui::Context) {
    // set colors


@@ 139,10 139,10 @@ pub fn draw_chat(
                    || (ctx.input(|i| i.key_pressed(egui::Key::Enter))
                        && output.response.lost_focus())
                {
                    send_packet_events.send(SendPacket(Packet::SendMessage {
                    send_packet_events.send(SendPacket(Packet::SendMessage(SendMessagePacket {
                        target: None,
                        content: chat.textbox.clone(),
                    }));
                    })));
                    chat.textbox.clear();
                }
            });

M crates/client/src/wasm/websocket.rs => crates/client/src/wasm/websocket.rs +4 -4
@@ 2,15 2,15 @@ use std::thread::yield_now;

use bevy_ecs::system::Resource;
//use crossbeam::channel::{unbounded, Receiver, Sender};
use crate::networking::websocket::Websocket;
use futures::{
    channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
    SinkExt,
};
use starkingdoms_common::packet::Packet;
use starkingdoms_common::packet::{ClientLoginPacket, Packet};
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use wasm_bindgen_futures::spawn_local;
use web_sys::{MessageEvent, WebSocket};
use crate::networking::websocket::Websocket;

const PORT: u16 = 3000;



@@ 42,11 42,11 @@ impl Websocket for Ws {

        let ws_clone = ws.clone();
        let onopen_callback = Closure::<dyn FnMut()>::new(move || {
            let packet = Packet::ClientLogin {
            let packet = Packet::ClientLogin(ClientLoginPacket {
                username: String::new(),
                save: None,
                jwt: None,
            };
            });

            ws_clone
                .send_with_str(

M crates/common/src/packet.rs => crates/common/src/packet.rs +125 -79
@@ 77,79 77,135 @@ pub enum ButtonType {
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct ClientLoginPacket {
    pub username: String,
    pub save: Option<String>,
    pub jwt: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct SendMessagePacket {
    pub target: Option<String>,
    pub content: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PlayerInputPacket {
    pub up: bool,
    pub down: bool,
    pub left: bool,
    pub right: bool,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PlayerMouseInputPacket {
    pub x: f32,
    pub y: f32,
    pub released: bool,
    pub button: ButtonType,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct RequestSavePacket {
    pub old_save: Option<String>,
}

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

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct LoginResponsePacket {
    pub id: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct SpawnPlayerPacket {
    pub id: u32,
    pub username: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PlayerListPacket {
    pub players: Vec<(u32, String)>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PlanetPositionsPacket {
    pub planets: Vec<(u32, Planet)>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PartPositionsPacket {
    pub parts: Vec<(u32, Part)>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct SpawnPartPacket {
    pub id: u32,
    pub part: Part,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct DespawnPartPacket {
    pub id: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct PlayerLeavePacket {
    pub id: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct MessagePacket {
    pub message_type: MessageType,
    pub actor: String,
    pub content: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct SaveEligibilityPacket {
    pub eligible: bool,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct SaveDataPacket {
    pub payload: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct EnergyUpdatePacket {
    pub amount: u32,
    pub max: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct OpenCraftingUiPacket {
    pub id: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "t", content = "c")]
pub enum Packet {
    // serverbound
    ClientLogin {
        username: String,
        save: Option<String>,
        jwt: Option<String>,
    },
    SendMessage {
        target: Option<String>,
        content: String,
    },
    PlayerInput {
        up: bool,
        down: bool,
        left: bool,
        right: bool,
    },
    PlayerMouseInput {
        x: f32,
        y: f32,
        released: bool,
        button: ButtonType,
    },
    RequestSave {
        old_save: Option<String>,
    },
    _SpecialDisconnect {},
    ClientLogin(ClientLoginPacket),
    SendMessage(SendMessagePacket),
    PlayerInput(PlayerInputPacket),
    PlayerMouseInput(PlayerMouseInputPacket),
    RequestSave(RequestSavePacket),
    _SpecialDisconnect(SpecialDisconnectPacket),

    // clientbound
    LoginResponse {
        id: u32,
    },
    SpawnPlayer {
        id: u32,
        username: String,
    },
    PlayerList {
        players: Vec<(u32, String)>,
    },
    PlanetPositions {
        planets: Vec<(u32, Planet)>,
    },
    PartPositions {
        parts: Vec<(u32, Part)>,
    },
    SpawnPart {
        id: u32,
        part: Part,
    },
    DespawnPart {
        id: u32,
    },
    PlayerLeave {
        id: u32,
    },
    Message {
        message_type: MessageType,
        actor: String,
        content: String,
    },
    SaveEligibility {
        eligible: bool,
    },
    SaveData {
        payload: String,
    },
    EnergyUpdate {
        amount: u32,
        max: u32,
    },
    OpenCraftingUi {
        id: u32,
    },
    LoginResponse(LoginResponsePacket),
    SpawnPlayer(SpawnPlayerPacket),
    PlayerList(PlayerListPacket),
    PlanetPositions(PlanetPositionsPacket),
    PartPositions(PartPositionsPacket),
    SpawnPart(SpawnPartPacket),
    DespawnPart(DespawnPartPacket),
    PlayerLeave(PlayerLeavePacket),
    Message(MessagePacket),
    SaveEligibility(SaveEligibilityPacket),
    SaveData(SaveDataPacket),
    EnergyUpdate(EnergyUpdatePacket),
    OpenCraftingUi(OpenCraftingUiPacket),
}

impl From<Packet> for String {


@@ 168,16 224,6 @@ 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<&String> for Packet {
    type Error = MsgFromError;

M crates/kabel/src/parser.rs => crates/kabel/src/parser.rs +12 -12
@@ 201,20 201,20 @@ impl Parser {
            if let TokenType::Semicolon = self.peek()?.token_type {
                expression1 = None;
            } else if let TokenType::Var = self.peek()?.token_type {
                    expression1 = Some(self.declaration()?);
                expression1 = Some(self.declaration()?);
            } else {
                expression1 = Some(self.expression()?);
                let semicolon = self.read_token()?;
                if let TokenType::Semicolon = semicolon.token_type {
                } else {
                    expression1 = Some(self.expression()?);
                    let semicolon = self.read_token()?;
                    if let TokenType::Semicolon = semicolon.token_type {
                    } else {
                        self.current -= 1;
                        return Err(unexpected_token!(
                            self,
                            "Expected ; but found {}",
                            semicolon
                        ));
                    }
                    self.current -= 1;
                    return Err(unexpected_token!(
                        self,
                        "Expected ; but found {}",
                        semicolon
                    ));
                }
            }

            let expression2;
            if let TokenType::Semicolon = self.peek()?.token_type {

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


@@ 60,14 60,14 @@ pub fn module_spawn(
                principal_inertia: 7.5,
            }));

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

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


@@ 164,9 164,9 @@ pub fn despawn_module_tree(
) {
    for child in attach.children.iter().flatten() {
        commands.entity(*child).despawn_recursive();
        let packet = Packet::DespawnPart {
        let packet = Packet::DespawnPart(DespawnPartPacket {
            id: (*child).index(),
        };
        });

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


@@ 502,20 502,20 @@ fn convert_modules_recursive(

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

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

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

                    let packet = Packet::SpawnPart {
                    let packet = Packet::SpawnPart(SpawnPartPacket {
                        id: child.index(),
                        part: Part {
                            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(),


@@ 567,33 567,33 @@ fn convert_modules_recursive(
                    increase_capacity_by +=
                        part!(c_PartType::LandingThruster.into()).energy_capacity;

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

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

                    let packet = Packet::SpawnPart {
                    let packet = Packet::SpawnPart(SpawnPartPacket {
                        id: child.index(),
                        part: Part {
                            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(),
                    });

                    let packet = Packet::SpawnPart {
                    let packet = Packet::SpawnPart(SpawnPartPacket {
                        id: suspension.id().index(),
                        part: Part {
                            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(),

M crates/server/src/module/save.rs => crates/server/src/module/save.rs +3 -3
@@ 3,7 3,7 @@ use std::{collections::HashMap, f32::consts::PI};
use bevy::{math::vec2, prelude::*};
use bevy_rapier2d::prelude::*;
use starkingdoms_common::{packet::Packet, PartType as c_PartType, SaveModule};

use starkingdoms_common::packet::SaveEligibilityPacket;
use crate::{
    capacity, mass,
    planet::PlanetType,


@@ 349,9 349,9 @@ pub fn save_eligibility(
    for (other, eligible) in player_eligibilities.iter() {
        let mut player = player_query.get_mut(*other).unwrap();
        player.save_eligibility = *eligible;
        let packet = Packet::SaveEligibility {
        let packet = Packet::SaveEligibility(SaveEligibilityPacket {
            eligible: *eligible,
        };
        });

        packet_send.send(WsEvent::Send {
            to: player.addr,

M crates/server/src/player/client_login.rs => crates/server/src/player/client_login.rs +24 -23
@@ 10,7 10,7 @@ use starkingdoms_common::{
    packet::{MessageType, Packet, Part, Planet, ProtoPartFlags},
    proto_part_flags, proto_transform, unpack_savefile, PartType as c_PartType,
};

use starkingdoms_common::packet::{LoginResponsePacket, MessagePacket, PlanetPositionsPacket, PlayerListPacket, SpawnPartPacket, SpawnPlayerPacket};
use crate::{
    config::StkConfig,
    crafting::components::{IsMining, VarietyMaterialStorage},


@@ 39,7 39,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(),
                    message: Packet::Message(MessagePacket { 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;


@@ 49,7 49,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(),
                message: Packet::Message(MessagePacket { 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;


@@ 57,12 57,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(),
            message: Packet::Message(MessagePacket { 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(),
            message: Packet::Message(MessagePacket { 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;


@@ 166,7 166,8 @@ pub fn load_save(
            // HEY! GHOSTLY! THIS SAVE FILE IS VALID! PLEASE LOAD IT!
            // THANKS!

            let (children, module_counts) = load_savefile(
            let (children,
                _module_counts) = load_savefile(
                commands,
                transform,
                id,


@@ 181,11 182,11 @@ pub fn load_save(
            player_comp.energy = player_comp.energy_capacity;
            attach.children = children;
        } else {
            let packet = Packet::Message {
            let packet = Packet::Message(MessagePacket {
                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(),


@@ 240,7 241,7 @@ pub fn packet_stream(
    transform: Transform,
) {
    // response in the handshake
    let packet = Packet::LoginResponse { id: index };
    let packet = Packet::LoginResponse(LoginResponsePacket { id: index });
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into_message(),


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


@@ 271,25 272,25 @@ pub fn packet_stream(
    for (entity, player, _, _, _, _) in player_query.iter() {
        players.push((entity.index(), player.username.clone()));
    }
    let packet = Packet::PlayerList { players };
    let packet = Packet::PlayerList(PlayerListPacket { players });
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into_message(),
    });

    // tell other players that a player has spawned in
    let packet = Packet::SpawnPlayer {
    let packet = Packet::SpawnPlayer(SpawnPlayerPacket {
        id: index,
        username: username.to_string(),
    };
    });
    event_queue.push(WsEvent::Broadcast {
        message: packet.into_message(),
    });
    let packet = Packet::Message {
    let packet = Packet::Message(MessagePacket {
        message_type: MessageType::Server,
        actor: "SERVER".to_string(),
        content: format!("{} has joined the server!", username),
    };
    });
    event_queue.push(WsEvent::Broadcast {
        message: packet.into_message(),
    });


@@ 330,10 331,10 @@ pub fn packet_stream(
        },
    ));
    for part in parts {
        let packet = Packet::SpawnPart {
        let packet = Packet::SpawnPart(SpawnPartPacket {
            id: part.0,
            part: part.1,
        };
        });
        event_queue.push(WsEvent::Send {
            to: *from,
            message: packet.into_message(),


@@ 341,32 342,32 @@ pub fn packet_stream(
    }

    // and send the welcome message :)
    let packet = Packet::Message {
    let packet = Packet::Message(MessagePacket {
        message_type: MessageType::Server,
        actor: "SERVER".to_string(),
        content: format!(
            "starkingdoms-server v{} says hello",
            env!("CARGO_PKG_VERSION")
        ),
    };
    });
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into_message(),
    });
    let packet = Packet::Message {
    let packet = Packet::Message(MessagePacket {
        message_type: MessageType::Server,
        actor: "SERVER".to_string(),
        content: "Welcome to StarKingdoms.IO! Have fun!".to_string(),
    };
    });
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into_message(),
    });
    let packet = Packet::Message {
    let packet = Packet::Message(MessagePacket {
        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(),

M crates/server/src/player/mod.rs => crates/server/src/player/mod.rs +9 -9
@@ 6,7 6,7 @@ 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 starkingdoms_common::packet::{ClientLoginPacket, PlayerInputPacket, PlayerMouseInputPacket, RequestSavePacket, SendMessagePacket};
use crate::{
    config::StkConfig,
    crafting::components::IsMining,


@@ 79,11 79,11 @@ pub fn on_message(
            let packet: Packet = err_or_cont!(Packet::from_message(message));

            match packet {
                Packet::ClientLogin {
                Packet::ClientLogin(ClientLoginPacket{
                    username,
                    save,
                    jwt,
                } => {
                }) => {
                    // auth
                    if !client_login::join_auth(
                        jwt,


@@ 139,16 139,16 @@ pub fn on_message(
                        transform,
                    );
                }
                Packet::SendMessage { target, content } => {
                Packet::SendMessage(SendMessagePacket { target, content }) => {
                    // a player sent a message
                    send_message(&player_query, from, &mut event_queue, target, content);
                }
                Packet::PlayerInput {
                Packet::PlayerInput(PlayerInputPacket {
                    up,
                    down,
                    left,
                    right,
                } => {
                }) => {
                    for (_, mut q_player, _, _, _, _) in &mut player_query {
                        if q_player.addr == *from {
                            q_player.input.up = up;


@@ 158,12 158,12 @@ pub fn on_message(
                        }
                    }
                }
                Packet::PlayerMouseInput {
                Packet::PlayerMouseInput(PlayerMouseInputPacket {
                    x,
                    y,
                    released,
                    button,
                } => {
                }) => {
                    let x = x / CLIENT_SCALE;
                    let y = y / CLIENT_SCALE;
                    for (entity, mut q_player, transform, _velocity, _attach, _) in


@@ 206,7 206,7 @@ pub fn on_message(
                        }
                    }
                }
                Packet::RequestSave { old_save } => {
                Packet::RequestSave(RequestSavePacket { old_save }) => {
                    for (_, q_player, _, _, attach, _) in &mut player_query {
                        if q_player.addr == *from {
                            // HEY! GHOSTLY! PLEASE FILL THIS STRUCT WITH DATA!

M crates/server/src/player/packet.rs => crates/server/src/player/packet.rs +7 -7
@@ 3,7 3,7 @@ use starkingdoms_common::{
    packet::{Packet, Part, Planet},
    proto_part_flags, proto_transform,
};

use starkingdoms_common::packet::{EnergyUpdatePacket, PartPositionsPacket, PlanetPositionsPacket, PlayerLeavePacket};
use crate::{
    module::component::{Attach, PartFlags, PartType},
    planet::PlanetType,


@@ 15,10 15,10 @@ use super::component::Player;

pub fn send_player_energy(player_query: Query<&Player>, mut packet_send: EventWriter<WsEvent>) {
    for player in &player_query {
        let packet = Packet::EnergyUpdate {
        let packet = Packet::EnergyUpdate(EnergyUpdatePacket {
            amount: player.energy,
            max: player.energy_capacity,
        };
        });

        packet_send.send(WsEvent::Send {
            to: player.addr,


@@ 50,9 50,9 @@ pub fn on_position_change(
    }

    if !updated_parts.is_empty() {
        let packet = Packet::PartPositions {
        let packet = Packet::PartPositions(PartPositionsPacket {
            parts: updated_parts,
        };
        });

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


@@ 75,7 75,7 @@ pub fn on_position_change(
    }

    if !planets.is_empty() {
        let packet = Packet::PlanetPositions { planets };
        let packet = Packet::PlanetPositions(PlanetPositionsPacket { planets });

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


@@ 105,7 105,7 @@ pub fn on_close(
                    );
                    commands.entity(entity).despawn_recursive();

                    let packet = Packet::PlayerLeave { id: entity.index() };
                    let packet = Packet::PlayerLeave(PlayerLeavePacket { id: entity.index() });

                    for (in_entity, player, _) in &player_query {
                        if entity != in_entity {

M crates/server/src/player/player_mouse_input.rs => crates/server/src/player/player_mouse_input.rs +3 -3
@@ 11,7 11,7 @@ use starkingdoms_common::{
    packet::{ButtonType, Packet},
    PartType as c_PartType,
};

use starkingdoms_common::packet::OpenCraftingUiPacket;
use super::component::Player;

pub fn attach_or_detach(


@@ 126,7 126,7 @@ pub fn mouse_picking(
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    player: (Entity, &Transform),
    mining_query: &mut Query<&mut IsMining>,
    _mining_query: &mut Query<&mut IsMining>,
    q_player: &mut Player,
    x: f32,
    y: f32,


@@ 193,7 193,7 @@ pub fn mouse_picking(
            if *button == ButtonType::Right {
                send_events.push(WsEvent::Send {
                    to: q_player.addr,
                    message: Packet::OpenCraftingUi { id: entity.index() }.into_message(),
                    message: Packet::OpenCraftingUi(OpenCraftingUiPacket { id: entity.index() }).into_message(),
                });
                // toggle mining
                /*if let Ok(mut is_mining) = mining_query.get_mut(entity) {

M crates/server/src/player/request_save.rs => crates/server/src/player/request_save.rs +3 -3
@@ 3,7 3,7 @@ use std::net::SocketAddr;
use bevy::prelude::*;
use bevy_rapier2d::prelude::Velocity;
use starkingdoms_common::{pack_savefile, packet::Packet, unpack_savefile, SaveData};

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


@@ 47,9 47,9 @@ pub fn request_save(
        unused_modules,
    };
    let save_string = pack_savefile(&app_keys.app_key, save);
    let packet = Packet::SaveData {
    let packet = Packet::SaveData(SaveDataPacket {
        payload: save_string,
    };
    });

    event_queue.push(WsEvent::Send {
        to: *from,

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

use bevy::prelude::*;
use bevy_rapier2d::prelude::Velocity;
use starkingdoms_common::packet::Packet;
use starkingdoms_common::packet::{MessagePacket, Packet};

use crate::{
    module::component::{Attach, PartFlags},


@@ 45,11 45,11 @@ pub fn send_message(
            }
        }
        let target_player = target_player.unwrap();
        let packet = Packet::Message {
        let packet = Packet::Message(MessagePacket {
            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(),


@@ 60,11 60,11 @@ pub fn send_message(
        });
    } else {
        // send to general chat
        let packet = Packet::Message {
        let packet = Packet::Message(MessagePacket {
            message_type: starkingdoms_common::packet::MessageType::Chat,
            actor: player.username.clone(),
            content,
        };
        });

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

M crates/server/src/ws.rs => crates/server/src/ws.rs +2 -2
@@ 19,7 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 starkingdoms_common::packet::{MsgFromError, Packet, SpecialDisconnectPacket};
use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr, TcpListener};
use std::sync::{Arc, RwLock};


@@ 50,7 50,7 @@ impl PacketMessageConvert for Packet {
        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::Close(_) => Ok(Packet::_SpecialDisconnect(SpecialDisconnectPacket {})),
            Message::Frame(_) | Message::Pong(_) | Message::Ping(_) => {
                Err(MsgFromError::InvalidMessageType)
            }