~starkingdoms/starkingdoms

28b96352b6cdc2a7bf0bd91ec8de51d6ccab11c4 — core 8 months ago 6b3599e
network refactor
M Cargo.lock => Cargo.lock +1 -0
@@ 6297,6 6297,7 @@ name = "starkingdoms-common"
version = "0.1.0"
dependencies = [
 "base64 0.21.7",
 "bevy_ecs 0.15.3",
 "hmac",
 "rmp-serde",
 "serde",

M crates/client/Cargo.toml => crates/client/Cargo.toml +1 -1
@@ 21,7 21,7 @@ egui-winit = { version = "0.31.1", default-features = false, features = ["links"
web-time = "1"
futures = "0.3"
nalgebra = "0.33"
starkingdoms-common = { version = "0.1", path = "../common" }
starkingdoms-common = { version = "0.1", path = "../common", features = ["bevy"] }
serde = "1"
serde_json = "1"
crossbeam = "0.8.4"

M crates/client/src/components.rs => crates/client/src/components.rs +34 -0
@@ 1,7 1,41 @@
use bevy_ecs::{bundle::Bundle, component::Component, event::Event, system::Resource};
use bevy_ecs::schedule::ScheduleLabel;
use nalgebra::{Matrix3, Matrix4, Rotation2, Scale2, Scale3, Translation2, Translation3};
use starkingdoms_common::packet::Packet;

#[derive(Bundle)]
pub struct PlayerBundle {
    pub transform: Transform,
    pub texture: Texture,
    pub player: Player,
    pub part: Part
}

#[derive(Bundle)]
pub struct PartBundle {
    pub transform: Transform,
    pub texture: Texture,
    pub server_id: ServerId,
    pub part: Part
}

#[derive(Bundle)]
pub struct OtherPlayerBundle {
    pub part: PartBundle,
    pub username: PlayerUsername
}

#[derive(ScheduleLabel, Hash, Clone, Debug, Eq, PartialEq)]
pub enum Schedule {
    Startup,
    Update
}

#[derive(Component, Debug)]
pub struct PlayerUsername {
    pub username: String
}

#[derive(Component, Debug)]
pub struct Texture {
    pub name: String,

M crates/client/src/lib.rs => crates/client/src/lib.rs +35 -13
@@ 1,6 1,9 @@
use crate::components::PlayerResources;
use crate::networking::websocket::Websocket;
use bevy_ecs::{event::Events, world::World};
use bevy_ecs::event::EventRegistry;
use bevy_ecs::prelude::Event;
use bevy_ecs::schedule::Schedule;
use components::{Camera, Chat, Part, Player, RecvPacket, SendPacket, Texture, Transform};
use nalgebra::{Rotation2, Scale3, Translation3};
use platform::assets::Assets;


@@ 9,6 12,10 @@ use rendering::assets::AssetLoader;
use rendering::App;
use tracing::info;
use winit::event_loop::{ControlFlow, EventLoop};
use starkingdoms_common::packet::{ClientLoginPacket, DespawnPartPacket, EnergyUpdatePacket, LoginResponsePacket, MessagePacket, OpenCraftingUiPacket, PartPositionsPacket, PlanetPositionsPacket, PlayerInputPacket, PlayerLeavePacket, PlayerListPacket, PlayerMouseInputPacket, RequestSavePacket, SaveDataPacket, SaveEligibilityPacket, SendMessagePacket, SpawnPartPacket, SpawnPlayerPacket, SpecialDisconnectPacket};
use crate::components::Schedule::{Startup, Update};
use crate::networking::handlers::{handle_crafting_ui, handle_despawn_part, handle_energy_update, handle_existing_players_list, handle_login_response, handle_message, handle_part_positions, handle_player_leave, handle_spawn_part, handle_spawn_player};
use crate::systems::create_hearty;

#[cfg(target_arch = "wasm32")]
#[path = "wasm/mod.rs"]


@@ 21,6 28,7 @@ pub mod components;
pub mod networking;
pub mod rendering;
pub mod ui;
pub mod systems;

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


@@ 59,18 67,32 @@ pub fn start() {
    let send_packet_events = Events::<SendPacket>::default();
    let recv_packet_events = Events::<RecvPacket>::default();

    world.spawn((
        Transform {
            translation: Translation3::new(0.0, 0.0, 0.0),
            rotation: Rotation2::new(0.0),
            scale: Scale3::new(25.0, 25.0, 1.0),
        },
        Texture {
            name: "hearty.svg".to_string(),
        },
        Player,
        Part(false),
    ));
    starkingdoms_common::packet::register_packet_events(&mut world);

    let mut startup_schedule = Schedule::new(Startup);

    startup_schedule.add_systems(create_hearty);

    let mut update_schedule = Schedule::new(Update);

    update_schedule.add_systems(handle_existing_players_list);
    update_schedule.add_systems(handle_spawn_player);
    update_schedule.add_systems(handle_spawn_part);
    update_schedule.add_systems(handle_part_positions);
    update_schedule.add_systems(handle_energy_update);
    update_schedule.add_systems(handle_crafting_ui);
    update_schedule.add_systems(handle_message);
    update_schedule.add_systems(handle_player_leave);
    update_schedule.add_systems(handle_despawn_part);
    update_schedule.add_systems(handle_login_response);


    // add systems here

    world.add_schedule(startup_schedule);
    world.add_schedule(update_schedule);

    world.run_schedule(Startup);

    let event_loop = EventLoop::new().unwrap();
    event_loop.set_control_flow(ControlFlow::Wait);


@@ 78,4 100,4 @@ pub fn start() {
    event_loop
        .run_app(&mut App::new(world, send_packet_events, recv_packet_events))
        .unwrap();
}
}
\ No newline at end of file

A crates/client/src/networking/handlers.rs => crates/client/src/networking/handlers.rs +153 -0
@@ 0,0 1,153 @@
use bevy_ecs::prelude::*;
use nalgebra::{Rotation2, Scale3, Translation3};
use starkingdoms_common::packet::{DespawnPartPacket, EnergyUpdatePacket, LoginResponsePacket, MessagePacket, OpenCraftingUiPacket, PartPositionsPacket, PlanetPositionsPacket, PlayerLeavePacket, PlayerListPacket, SpawnPartPacket, SpawnPlayerPacket};
use starkingdoms_common::PartType;
use crate::components::{Camera, Chat, Menu, OtherPlayerBundle, Part, PartBundle, Player, PlayerBundle, PlayerResources, PlayerUsername, ServerId, Texture, Transform};
use crate::networking::texture_name;

pub fn handle_login_response(mut reader: EventReader<LoginResponsePacket>, mut player: Query<Entity, With<Player>>, mut commands: Commands) {
    for packet in reader.read() {
        let player = player.single();
        commands.entity(player).insert(ServerId(packet.id));
    }
}

pub fn handle_message(mut reader: EventReader<MessagePacket>, mut chat: ResMut<Chat>) {
    for message in reader.read() {
        chat.messages.push(
            format!("{}: {}", message.actor, message.content)
        );
    }
}

pub fn handle_existing_players_list(mut reader: EventReader<PlayerListPacket>, mut commands: Commands) {
    for packet in reader.read() {
        for (server_id, username) in &packet.players {
            commands.spawn(OtherPlayerBundle {
                part: PartBundle {
                    transform: Transform {
                        translation: Translation3::new(0.0, 0.0, 0.0),
                        rotation: Rotation2::new(0.0),
                        scale: Scale3::new(25.0, 25.0, 1.0)
                    },
                    texture: Texture {
                        name: "hearty.svg".to_string()
                    },
                    server_id: ServerId(*server_id),
                    part: Part(false),
                },
                username: PlayerUsername {
                    username: username.clone()
                }
            });
        }
    }
}

pub fn handle_spawn_player(mut reader: EventReader<SpawnPlayerPacket>, mut commands: Commands) {
    for SpawnPlayerPacket { id: server_id, username } in reader.read() {
            commands.spawn(OtherPlayerBundle {
                part: PartBundle {
                    transform: Transform {
                        translation: Translation3::new(0.0, 0.0, 0.0),
                        rotation: Rotation2::new(0.0),
                        scale: Scale3::new(25.0, 25.0, 1.0)
                    },
                    texture: Texture {
                        name: "hearty.svg".to_string()
                    },
                    server_id: ServerId(*server_id),
                    part: Part(false),
                },
                username: PlayerUsername {
                    username: username.clone()
                }
            });
    }
}

pub fn handle_spawn_part(mut reader: EventReader<SpawnPartPacket>, mut commands: Commands) {
    for SpawnPartPacket { id: server_id, part } in reader.read() {
        commands.spawn(PartBundle {
                transform: Transform {
                    translation: Translation3::new(part.transform.x, part.transform.y, 0.0),
                    rotation: Rotation2::new(part.transform.rot),
                    scale: Scale3::new(25.0, 25.0, 1.0),
                },
                texture: Texture {
                    name: texture_name(part.part_type, part.flags.attached),
                },
                server_id: ServerId(*server_id),
                part: Part(part.flags.attached),
            });
    }
}

pub fn handle_part_positions(mut reader: EventReader<PartPositionsPacket>, mut parts_query: Query<(Entity, &ServerId, &mut Transform, &mut Texture, &mut Part)>, mut player: Query<&ServerId, With<Player>>, mut camera: ResMut<Camera>) {
    for PartPositionsPacket { parts } in reader.read() {
        for (this_id, part) in parts {
            for (_, server_id, mut transform, mut texture, mut bevy_part) in &mut parts_query {
                if *this_id == server_id.0 {
                    transform.translation.x = part.transform.x;
                    transform.translation.y = part.transform.y;
                    transform.rotation = Rotation2::new(part.transform.rot);

                    if part.flags.attached && !bevy_part.0 {
                        texture.name = texture_name(part.part_type, part.flags.attached);
                        bevy_part.0 = true;
                    } else if !part.flags.attached && bevy_part.0 {
                        texture.name = texture_name(part.part_type, part.flags.attached);
                        bevy_part.0 = false;
                    }
                }

                if part.part_type == PartType::Hearty {
                    let player_id = player.single();
                    if player_id.0 == server_id.0 {
                        camera.x = -part.transform.x;
                        camera.y = -part.transform.y;
                    }
                }
            }
        }
    }
}


pub fn handle_energy_update(mut reader: EventReader<EnergyUpdatePacket>, mut player_res: ResMut<PlayerResources>) {
    for EnergyUpdatePacket { amount, max } in reader.read() {
        player_res.fuel_amount = *amount;
        player_res.fuel_max = *max;
    }
}

pub fn handle_crafting_ui(mut reader: EventReader<OpenCraftingUiPacket>, entities: Query<(Entity, &ServerId)>, mut commands: Commands) {
    for OpenCraftingUiPacket { id } in reader.read() {
        for (entity, server_id) in &entities {
            if server_id.0 == *id {
                commands.entity(entity)
                    .insert(Menu);
            }
        }
    }
}

pub fn handle_player_leave(mut reader: EventReader<PlayerLeavePacket>, mut part_query: Query<(Entity, &ServerId), With<Part>>, mut commands: Commands) {
    for PlayerLeavePacket { id } in reader.read() {
        for (entity, server_id) in &mut part_query {
            if server_id.0 == *id {
                commands.entity(entity).despawn();
            }
        }
    }
}

pub fn handle_despawn_part(mut reader: EventReader<DespawnPartPacket>, mut part_query: Query<(Entity, &ServerId), With<Part>>, mut commands: Commands) {
    for DespawnPartPacket { id } in reader.read() {
        for (entity, server_id) in &mut part_query {
            if server_id.0 == *id {
                commands.entity(entity).despawn();
            }
        }
    }
}
\ No newline at end of file

M crates/client/src/networking/mod.rs => crates/client/src/networking/mod.rs +28 -202
@@ 10,19 10,17 @@ use bevy_ecs::{
    query::{QuerySingleError, With},
    world::World,
};
use bevy_ecs::prelude::Event;
use nalgebra::{Rotation2, Scale3, Translation3};
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::{DespawnPartPacket, EnergyUpdatePacket, LoginResponsePacket, MessagePacket, OpenCraftingUiPacket, Packet, PartPositionsPacket, PlanetPositionsPacket, PlayerLeavePacket, PlayerListPacket, SpawnPartPacket, SpawnPlayerPacket};
use starkingdoms_common::{PartType, PlanetType};

pub mod websocket;
pub mod handlers;

fn texture_name(part_type: PartType, attached: bool) -> String {
    use PartType::*;


@@ 49,203 47,31 @@ fn texture_name(part_type: PartType, attached: bool) -> String {
    }
}

pub fn process_packets(
    world: &mut World,
    _send_packet_events: &mut Events<SendPacket>,
    recv_packet_events: &mut Events<RecvPacket>,
    planet_types: &mut HashMap<PlanetType, (Entity, u32)>,
) {
    let mut recv_cursor = recv_packet_events.get_cursor();
    for recv in recv_cursor.read(recv_packet_events) {
        match &recv.0 {
            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(PlayerListPacket { players }) => {
                // existing players
                // username sync, eventually
                for player in players {
                    world.spawn((
                        Transform {
                            translation: Translation3::new(0.0, 0.0, 0.0),
                            rotation: Rotation2::new(0.0),
                            scale: Scale3::new(25.0, 25.0, 1.0),
                        },
                        Texture {
                            name: "hearty.svg".to_string(),
                        },
                        ServerId(player.0),
                        Part(false),
                    ));
                }
            }
            SpawnPlayer(SpawnPlayerPacket { id, .. }) => {
                // username sync, eventually
                world.spawn((
                    Transform {
                        translation: Translation3::new(0.0, 0.0, 0.0),
                        rotation: Rotation2::new(0.0),
                        scale: Scale3::new(25.0, 25.0, 1.0),
                    },
                    Texture {
                        name: "hearty.svg".to_string(),
                    },
                    ServerId(*id),
                    Part(false),
                ));
            }
            SpawnPart(SpawnPartPacket { id, part }) => {
                world.spawn((
                    Transform {
                        translation: Translation3::new(part.transform.x, part.transform.y, 0.0),
                        rotation: Rotation2::new(part.transform.rot),
                        scale: Scale3::new(25.0, 25.0, 1.0),
                    },
                    Texture {
                        name: texture_name(part.part_type, part.flags.attached),
                    },
                    ServerId(*id),
                    Part(part.flags.attached),
                ));
            }
            PartPositions(PartPositionsPacket { parts }) => {
                for (id, part) in parts {
                    if part.part_type == PartType::Hearty {
                        let mut player_query = world.query_filtered::<&ServerId, With<Player>>();
                        let server_id = match player_query.get_single(world) {
                            Ok(player) => player,
                            Err(e) => match e {
                                QuerySingleError::NoEntities(_) => {
                                    continue;
                                }
                                QuerySingleError::MultipleEntities(_) => {
                                    panic!("There should never multiple players marked as players");
                                }
                            },
                        };
                        if server_id.0 == *id {
                            let mut camera = world.resource_mut::<Camera>();
                            camera.x = -part.transform.x;
                            camera.y = -part.transform.y;
                        }
                    }
                    let mut part_query =
                        world
                            .query::<(Entity, &ServerId, &mut Transform, &mut Texture, &mut Part)>(
                            );
                    for (_, server_id, mut transform, mut texture, mut bevy_part) in
                        part_query.iter_mut(world)
                    {
                        if server_id.0 == *id {
                            transform.translation.x = part.transform.x;
                            transform.translation.y = part.transform.y;
                            transform.rotation = Rotation2::new(part.transform.rot);
pub fn send_event<T: Event>(e: T, world: &mut World) {
    let mut events = world.resource_mut::<Events<T>>();
    events.send(e);
}

                            if part.flags.attached && !bevy_part.0 {
                                texture.name = texture_name(part.part_type, part.flags.attached);
                                bevy_part.0 = true;
                            } else if !part.flags.attached && bevy_part.0 {
                                texture.name = texture_name(part.part_type, part.flags.attached);
                                bevy_part.0 = false;
                            }
                        }
                    }
                }
            }
            PlanetPositions(PlanetPositionsPacket { planets }) => {
                for (server_id, planet) in planets {
                    planet_types.entry(planet.planet_type).or_insert_with(|| {
                        let entity = world.spawn(SpriteBundle {
                            transform: Transform {
                                translation: Translation3::new(
                                    planet.transform.x,
                                    planet.transform.y,
                                    0.0,
                                ),
                                rotation: Rotation2::new(planet.transform.rot),
                                scale: Scale3::new(planet.radius, planet.radius, 1.0),
                            },
                            texture: Texture {
                                name: match planet.planet_type {
                                    /*PlanetType::Sun => "sun.svg",
                                    PlanetType::Mercury => "mercury.svg",
                                    PlanetType::Venus => "venus.svg",
                                    PlanetType::Earth => "earth.svg",
                                    PlanetType::Moon => "moon.svg",
                                    PlanetType::Mars => "mars.svg",
                                    PlanetType::Jupiter => "jupiter.svg",
                                    PlanetType::Saturn => "saturn.svg",
                                    PlanetType::Uranus => "uranus.svg",
                                    PlanetType::Neptune => "neptune.svg",
                                    PlanetType::Pluto => "pluto.svg",*/
                                    PlanetType::Sun => "sun.svg",
                                    PlanetType::Mercury => "moon.svg",
                                    PlanetType::Venus => "venus.svg",
                                    PlanetType::Earth => "earth.svg",
                                    PlanetType::Moon => "moon.svg",
                                    PlanetType::Mars => "mars.svg",
                                    PlanetType::Jupiter => "jupiter.svg",
                                    PlanetType::Saturn => "sun.svg",
                                    PlanetType::Uranus => "venus.svg",
                                    PlanetType::Neptune => "mars.svg",
                                    PlanetType::Pluto => "earth.svg",
                                }
                                .to_string(),
                            },
                        });
                        (entity.id(), *server_id)
                    });
                }
            }
            EnergyUpdate(EnergyUpdatePacket { amount, max }) => {
                let mut r = world.resource_mut::<PlayerResources>();
                r.fuel_amount = *amount;
                r.fuel_max = *max;
            }
            OpenCraftingUi(OpenCraftingUiPacket { id }) => {
                let mut query = world.query::<(Entity, &ServerId)>();
                let mut matching_id = None;
                for (entity, server_id) in query.iter(world) {
                    if server_id.0 == *id {
                        matching_id = Some(entity);
                    }
                }
                if let Some(id) = matching_id {
                    world.entity_mut(id).insert(Menu);
                }
            }
            Message(MessagePacket { actor, content, .. }) => {
                let mut chat = world.get_resource_mut::<Chat>().unwrap();
                chat.messages
                    .push(format!("{}: {}", actor.clone(), content.clone()));
            }
            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) {
                    if server_id.0 == *id {
                        entity_to_remove = Some(entity);
                    }
                }
                if let Some(entity) = entity_to_remove {
                    world.despawn(entity);
                }
            }
            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) {
                    if server_id.0 == *id {
                        entity_to_remove = Some(entity);
                    }
                }
                if let Some(entity) = entity_to_remove {
                    world.despawn(entity);
                }
            }
            _ => {}
        }
pub fn send_packet_event(packet: Packet, world: &mut World) {
    match packet {
        Packet::ClientLogin(e) => send_event(e, world),
        Packet::SendMessage(e) => send_event(e, world),
        Packet::PlayerInput(e) => send_event(e, world),
        Packet::PlayerMouseInput(e) => send_event(e, world),
        Packet::RequestSave(e) => send_event(e, world),
        Packet::_SpecialDisconnect(e) => send_event(e, world),
        LoginResponse(e) => send_event(e, world),
        SpawnPlayer(e) => send_event(e, world),
        PlayerList(e) => send_event(e, world),
        PlanetPositions(e) => send_event(e, world),
        PartPositions(e) => send_event(e, world),
        SpawnPart(e) => send_event(e, world),
        DespawnPart(e) => send_event(e, world),
        PlayerLeave(e) => send_event(e, world),
        Message(e) => send_event(e, world),
        Packet::SaveEligibility(e) => send_event(e, world),
        Packet::SaveData(e) => send_event(e, world),
        EnergyUpdate(e) => send_event(e, world),
        OpenCraftingUi(e) => send_event(e, world),
    }
}

M crates/client/src/rendering/mod.rs => crates/client/src/rendering/mod.rs +13 -10
@@ 34,12 34,13 @@ use winit::{
};

use crate::components::{Camera, Menu, RecvPacket, SendPacket};
use crate::networking::process_packets;
use crate::networking::send_packet_event;
use crate::networking::websocket::Websocket;
use crate::platform::websocket::Ws;
use crate::rendering::renderer::{RenderCreateContext, Renderer};
use crate::rendering::MaybeRenderer::{Initialized, Initializing};
use assets::AssetLoader;
use crate::components::Schedule::Update;

pub mod assets;
mod renderer;


@@ 402,28 403,30 @@ impl ApplicationHandler for App {
        };
        let Some(window) = &self.window else { return };

        let ws = self
        let mut ws = self
            .world
            .get_resource_mut::<Ws>()
            .expect("Failed to get Ws resource");

        let mut packets = vec![];

        #[cfg(target_arch = "wasm32")]
        while let Ok(Some(packet)) = ws.receiver.try_next() {
            renderer.recv_packet_events.send(RecvPacket(packet));
            packets.push(packet);
        }
        #[cfg(not(target_arch = "wasm32"))]
        while let Ok(packet) = ws.receiver.try_recv() {
            renderer.recv_packet_events.send(RecvPacket(packet));
            packets.push(packet);
        }

        for packet in packets {
            send_packet_event(packet, &mut self.world);
        }

        renderer.send_packet_events.update();
        renderer.recv_packet_events.update();

        process_packets(
            &mut self.world,
            &mut renderer.send_packet_events,
            &mut renderer.recv_packet_events,
            &mut renderer.planet_types,
        );
        self.world.run_schedule(Update);

        let gl = self.gl.as_ref().unwrap();
        unsafe {

A crates/client/src/systems.rs => crates/client/src/systems.rs +21 -0
@@ 0,0 1,21 @@
use bevy_ecs::bundle::Bundle;
use bevy_ecs::prelude::Commands;
use nalgebra::{Rotation2, Scale3, Translation3};
use crate::components::{Part, Player, PlayerBundle, Texture, Transform};



pub fn create_hearty(mut commands: Commands) {
    commands.spawn(PlayerBundle {
        transform: Transform {
            translation: Translation3::new(0.0, 0.0, 0.0),
            rotation: Rotation2::new(0.0),
            scale: Scale3::new(25.0, 25.0, 1.0),
        },
        texture: Texture {
            name: "hearty.svg".to_string(),
        },
        player: Player,
        part: Part(false)
    });
}
\ No newline at end of file

M crates/common/Cargo.toml => crates/common/Cargo.toml +5 -0
@@ 12,3 12,8 @@ rmp-serde = "1"
hmac = "0.12"
sha2 = "0.10"
base64 = "0.21"
bevy_ecs = { version = "0.15", optional = true }

[features]
default = []
bevy = ["dep:bevy_ecs"]
\ No newline at end of file

M crates/common/src/packet.rs => crates/common/src/packet.rs +43 -1
@@ 77,17 77,20 @@ pub enum ButtonType {
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct ClientLoginPacket {
    pub username: String,
    pub save: Option<String>,
    pub jwt: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct SendMessagePacket {
    pub target: Option<String>,
    pub content: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct PlayerInputPacket {
    pub up: bool,
    pub down: bool,


@@ 96,6 99,7 @@ pub struct PlayerInputPacket {
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct PlayerMouseInputPacket {
    pub x: f32,
    pub y: f32,


@@ 104,56 108,67 @@ pub struct PlayerMouseInputPacket {
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct RequestSavePacket {
    pub old_save: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct SpecialDisconnectPacket {}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct LoginResponsePacket {
    pub id: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct SpawnPlayerPacket {
    pub id: u32,
    pub username: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct PlayerListPacket {
    pub players: Vec<(u32, String)>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct PlanetPositionsPacket {
    pub planets: Vec<(u32, Planet)>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct PartPositionsPacket {
    pub parts: Vec<(u32, Part)>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct SpawnPartPacket {
    pub id: u32,
    pub part: Part,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct DespawnPartPacket {
    pub id: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct PlayerLeavePacket {
    pub id: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct MessagePacket {
    pub message_type: MessageType,
    pub actor: String,


@@ 161,22 176,26 @@ pub struct MessagePacket {
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct SaveEligibilityPacket {
    pub eligible: bool,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct SaveDataPacket {
    pub payload: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct EnergyUpdatePacket {
    pub amount: u32,
    pub max: u32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(::bevy_ecs::event::Event))]
pub struct OpenCraftingUiPacket {
    pub id: u32,
}


@@ 208,6 227,29 @@ pub enum Packet {
    OpenCraftingUi(OpenCraftingUiPacket),
}

#[cfg(feature = "bevy")]
pub fn register_packet_events(world: &mut bevy_ecs::prelude::World) {
    bevy_ecs::event::EventRegistry::register_event::<ClientLoginPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<SendMessagePacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<PlayerInputPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<PlayerMouseInputPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<RequestSavePacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<SpecialDisconnectPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<LoginResponsePacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<PlayerListPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<PlanetPositionsPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<PartPositionsPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<SpawnPartPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<DespawnPartPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<MessagePacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<SaveEligibilityPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<SaveDataPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<EnergyUpdatePacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<OpenCraftingUiPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<SpawnPlayerPacket>(world);
    bevy_ecs::event::EventRegistry::register_event::<PlayerLeavePacket>(world);
}

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


@@ 238,4 280,4 @@ impl TryFrom<&Vec<u8>> for Packet {
    fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
        serde_json::from_slice(value).map_err(MsgFromError::JSONError)
    }
}
}
\ No newline at end of file