~starkingdoms/starkingdoms

33624fc62bad97549ace1582551d7faea0f358e3 — core 1 year, 11 months ago ef1f6ca
rewrite the entire game to use tungstenite
M Cargo.lock => Cargo.lock +0 -22
@@ 1067,16 1067,6 @@ dependencies = [
]

[[package]]
name = "bevy_twite"
version = "1.0.0"
source = "git+https://gitlab.com/ghostlyzsh/twite.git#d6f1b2a46b84048c239d8667f499977d9dc40e5b"
dependencies = [
 "bevy",
 "crossbeam-channel",
 "twite",
]

[[package]]
name = "bevy_utils"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3619,7 3609,6 @@ dependencies = [
 "bevy",
 "bevy_rapier2d",
 "bevy_tungstenite_stk",
 "bevy_twite",
 "hex",
 "hmac",
 "jwt",


@@ 4051,17 4040,6 @@ dependencies = [
]

[[package]]
name = "twite"
version = "0.1.0"
source = "git+https://gitlab.com/ghostlyzsh/twite.git#d6f1b2a46b84048c239d8667f499977d9dc40e5b"
dependencies = [
 "base64 0.21.5",
 "rand",
 "sha1",
 "url",
]

[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

M bevy_tungstenite_stk/src/lib.rs => bevy_tungstenite_stk/src/lib.rs +2 -2
@@ 7,7 7,7 @@ use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr, TcpListener};
use std::sync::{Arc, RwLock};
use std::thread;
use tungstenite::protocol::frame::coding::OpCode;

use tungstenite::protocol::Role;
use tungstenite::{Message, WebSocket};



@@ 108,7 108,7 @@ impl StkTungsteniteServerPlugin {
                                                        .send(WsEvent::Close { addr: this_addr })
                                                        .expect("failed to send on stream");
                                                }
                                                e => Err(e).unwrap(),
                                                e => panic!("{:?}", e),
                                            },
                                        }
                                    }

M server/Cargo.toml => server/Cargo.toml +0 -1
@@ 8,7 8,6 @@ license = "AGPL-3"
bevy = { version = "0.12", default-features = false }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
bevy_twite = { git = "https://gitlab.com/ghostlyzsh/twite.git" }
bevy_rapier2d = "0.23.0"
rand = "0.8.5"
tracing-subscriber = "0.3"

M server/src/component.rs => server/src/component.rs +1 -1
@@ 19,7 19,7 @@ use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use starkingdoms_common::PartType as c_PartType;

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

M server/src/main.rs => server/src/main.rs +536 -516
@@ 13,7 13,11 @@
//
//     You should have received a copy of the GNU Affero General Public License
//     along with this program.  If not, see <https://www.gnu.org/licenses/>.
use std::net::Ipv4Addr;
#![allow(clippy::type_complexity)] // bevy :(
#![allow(clippy::too_many_arguments)] // bevy :(
#![allow(clippy::only_used_in_recursion)] // todo: remove this

use std::net::IpAddr;

use crate::mathutil::rot2d;
use bevy::log::Level;


@@ 21,7 25,7 @@ use bevy::log::LogPlugin;
use bevy::math::{vec2, vec3};
use bevy::{ecs::event::ManualEventReader, prelude::*};
use bevy_rapier2d::prelude::*;
use bevy_twite::{twite::frame::MessageType, ServerEvent, TwiteServerConfig, TwiteServerPlugin};
use bevy_tungstenite_stk::{StkTungsteniteServerConfig, StkTungsteniteServerPlugin, WsEvent};
use component::Input;
use component::*;
use packet::*;


@@ 29,6 33,7 @@ use rand::Rng;
use starkingdoms_common::SaveModule;
use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData};
use std::f32::consts::PI;
use std::str::FromStr;

pub mod component;
pub mod macros;


@@ 62,8 67,8 @@ fn main() {
        .insert_resource(AppKeys {
            app_key: key.into_bytes(),
        })
        .insert_resource(TwiteServerConfig {
            addr: Ipv4Addr::new(0, 0, 0, 0),
        .insert_resource(StkTungsteniteServerConfig {
            addr: IpAddr::from_str("0.0.0.0").unwrap(),
            port: 3000,
        })
        .add_plugins(MinimalPlugins)


@@ 77,7 82,7 @@ fn main() {
        })
        .init_resource::<ModuleTimer>()
        .add_plugins(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(SCALE))
        .add_plugins(TwiteServerPlugin)
        .add_plugins(StkTungsteniteServerPlugin)
        .add_systems(Startup, setup_integration_parameters)
        .add_systems(Startup, spawn_planets)
        .add_systems(FixedUpdate, module_spawn)


@@ 158,7 163,7 @@ fn module_spawn(
    time: Res<Time>,
    mut module_timer: ResMut<ModuleTimer>,
    part_query: Query<&PartType, Without<Attach>>,
    mut packet_send: EventWriter<ServerEvent>,
    mut packet_send: EventWriter<WsEvent>,
) {
    if module_timer.0.tick(time.delta()).just_finished() {
        let angle: f32 = {


@@ 207,9 212,10 @@ fn module_spawn(
                    flags: proto_part_flags!(flags),
                },
            };
            let buf = serde_json::to_vec(&packet).unwrap();

            packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf));
            packet_send.send(WsEvent::Broadcast {
                message: packet.into(),
            });
        }
    }
}


@@ 245,21 251,20 @@ fn on_message(
        (Entity, &mut Player, &Transform, &Velocity, &mut Attach),
        Without<PlanetType>,
    >,
    mut packet_recv: Local<ManualEventReader<ServerEvent>>,
    mut packet_event_send: ResMut<Events<ServerEvent>>,
    mut packet_recv: Local<ManualEventReader<WsEvent>>,
    mut packet_event_send: ResMut<Events<WsEvent>>,
    app_keys: Res<AppKeys>,
) {
    let mut event_queue = Vec::new();
    for ev in packet_recv.read(&packet_event_send) {
        if let ServerEvent::Recv(addr, MessageType::Text, data) = ev {
            let data = String::from_utf8_lossy(&data);
            let packet: Packet = err_or_cont!(serde_json::from_str(&data));
        if let WsEvent::Recv { from, message } = ev {
            let packet: Packet = err_or_cont!(message.try_into());

            match packet {
                Packet::ClientLogin {
                    username,
                    save,
                    jwt,
                    jwt: _,
                } => {
                    let angle: f32 = {
                        let mut rng = rand::thread_rng();


@@ 278,7 283,7 @@ fn on_message(
                            flags: PartFlags { attached: false },
                        },
                        player: Player {
                            addr: *addr,
                            addr: *from,
                            username: username.to_string(),
                            input: component::Input::default(),
                            selected: None,


@@ 332,8 337,10 @@ fn on_message(
                                actor: "SERVER".to_string(),
                                content: "Savefile signature corrupted or inner data invalid. Save was not loaded. Contact StarKingdoms staff for assistance.".to_string(),
                            };
                            let buf = serde_json::to_vec(&packet).unwrap();
                            event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
                            event_queue.push(WsEvent::Send {
                                to: *from,
                                message: packet.into(),
                            });
                        }
                    } else {
                        // nothing to do


@@ 359,8 366,10 @@ fn on_message(
                        ));
                    }
                    let packet = Packet::PlanetPositions { planets };
                    let buf = serde_json::to_vec(&packet).unwrap();
                    event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
                    event_queue.push(WsEvent::Send {
                        to: *from,
                        message: packet.into(),
                    });

                    // tell the player already existing users
                    let mut players = Vec::new();


@@ 368,23 377,27 @@ fn on_message(
                        players.push((entity.index(), player.username.clone()));
                    }
                    let packet = Packet::PlayerList { players };
                    let buf = serde_json::to_vec(&packet).unwrap();
                    event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
                    event_queue.push(WsEvent::Send {
                        to: *from,
                        message: packet.into(),
                    });

                    // tell other players that a player has spawned in
                    let packet = Packet::SpawnPlayer {
                        id,
                        username: username.to_string(),
                    };
                    let buf = serde_json::to_vec(&packet).unwrap();
                    event_queue.push(ServerEvent::Broadcast(MessageType::Text, buf));
                    event_queue.push(WsEvent::Broadcast {
                        message: packet.into(),
                    });
                    let packet = Packet::Message {
                        message_type: packet::MessageType::Server,
                        actor: "SERVER".to_string(),
                        content: format!("{} has joined the server!", username),
                    };
                    let buf = serde_json::to_vec(&packet).unwrap();
                    event_queue.push(ServerEvent::Broadcast(MessageType::Text, buf));
                    event_queue.push(WsEvent::Broadcast {
                        message: packet.into(),
                    });

                    // tell the player where parts are
                    let mut parts = Vec::new();


@@ 424,8 437,10 @@ fn on_message(
                        },
                    ));
                    let packet = Packet::PartPositions { parts };
                    let buf = serde_json::to_vec(&packet).unwrap();
                    event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
                    event_queue.push(WsEvent::Send {
                        to: *from,
                        message: packet.into(),
                    });

                    // and send the welcome message :)
                    let packet = Packet::Message {


@@ 433,14 448,16 @@ fn on_message(
                        actor: "SERVER".to_string(),
                        content: "Welcome to StarKingdoms.IO! Have fun!".to_string(),
                    };
                    let buf = serde_json::to_vec(&packet).unwrap();
                    event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
                    event_queue.push(WsEvent::Send {
                        to: *from,
                        message: packet.into(),
                    });
                }
                Packet::SendMessage { target, content } => {
                    // find our player
                    let mut player = None;
                    for (_, q_player, _, _, _) in &player_query {
                        if q_player.addr == *addr {
                        if q_player.addr == *from {
                            player = Some(q_player);
                        }
                    }


@@ 458,13 475,14 @@ fn on_message(
                            actor: player.username.clone(),
                            content,
                        };
                        let buf = serde_json::to_vec(&packet).unwrap();
                        event_queue.push(ServerEvent::Send(
                            target_player.addr,
                            MessageType::Text,
                            buf.clone(),
                        ));
                        event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
                        event_queue.push(WsEvent::Send {
                            to: target_player.addr,
                            message: packet.clone().into(),
                        });
                        event_queue.push(WsEvent::Send {
                            to: *from,
                            message: packet.into(),
                        });
                    } else {
                        // send to general chat
                        let packet = Packet::Message {


@@ 472,8 490,10 @@ fn on_message(
                            actor: player.username.clone(),
                            content,
                        };
                        let buf = serde_json::to_vec(&packet).unwrap();
                        event_queue.push(ServerEvent::Broadcast(MessageType::Text, buf));

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


@@ 483,7 503,7 @@ fn on_message(
                    right,
                } => {
                    for (_, mut q_player, _, _, _) in &mut player_query {
                        if q_player.addr == *addr {
                        if q_player.addr == *from {
                            q_player.input.up = up;
                            q_player.input.down = down;
                            q_player.input.left = left;


@@ 499,7 519,7 @@ fn on_message(
                } => {
                    for (entity, mut q_player, transform, velocity, mut attach) in &mut player_query
                    {
                        if q_player.addr == *addr {
                        if q_player.addr == *from {
                            if released {
                                let select = if let Some(s) = q_player.selected {
                                    s


@@ 518,7 538,7 @@ fn on_message(
                                        rel_x * (-angle).sin() + rel_y * (-angle).cos(),
                                    );

                                    if attach.children[2] == None
                                    if attach.children[2].is_none()
                                        && 15. / SCALE < rel_y
                                        && rel_y < 30. / SCALE
                                        && -20. / SCALE < rel_x


@@ 572,7 592,7 @@ fn on_message(
                                                Quat::from_euler(EulerRot::ZYX, angle, 0., 0.);
                                        }
                                        break;
                                    } else if attach.children[0] == None
                                    } else if attach.children[0].is_none()
                                        && -30. / SCALE < rel_y
                                        && rel_y < -15. / SCALE
                                        && -20. / SCALE < rel_x


@@ 626,7 646,7 @@ fn on_message(
                                                Quat::from_euler(EulerRot::ZYX, angle + PI, 0., 0.);
                                        }
                                        break;
                                    } else if attach.children[1] == None
                                    } else if attach.children[1].is_none()
                                        && -30. / SCALE < rel_x
                                        && rel_x < -15. / SCALE
                                        && -20. / SCALE < rel_y


@@ 689,7 709,7 @@ fn on_message(
                                            );
                                        }
                                        break;
                                    } else if attach.children[3] == None
                                    } else if attach.children[3].is_none()
                                        && 15. / SCALE < rel_x
                                        && rel_x < 30. / SCALE
                                        && -20. / SCALE < rel_y


@@ 779,7 799,7 @@ fn on_message(
                                        {
                                            let mut parent_attach =
                                                attached_query.get_mut(parent).unwrap().3;
                                            let children = parent_attach.children.clone();
                                            let children = parent_attach.children;
                                            for (i, child) in children.iter().enumerate() {
                                                if let Some(child) = child {
                                                    if *child == select {


@@ 863,11 883,9 @@ fn on_message(
                                    ];
                                }

                                if bound[0] < x && x < bound[1] {
                                    if bound[2] < y && y < bound[3] {
                                        q_player.selected = Some(entity);
                                        break;
                                    }
                                if bound[0] < x && x < bound[1] && bound[2] < y && y < bound[3] {
                                    q_player.selected = Some(entity);
                                    break;
                                }
                            }
                            for (entity, part_type, transform, _, _, _) in &part_query {


@@ 891,11 909,9 @@ fn on_message(
                                    ];
                                }

                                if bound[0] < x && x < bound[1] {
                                    if bound[2] < y && y < bound[3] {
                                        q_player.selected = Some(entity);
                                        break;
                                    }
                                if bound[0] < x && x < bound[1] && bound[2] < y && y < bound[3] {
                                    q_player.selected = Some(entity);
                                    break;
                                }
                            }
                        }


@@ 903,7 919,7 @@ fn on_message(
                }
                Packet::RequestSave {} => {
                    for (_, q_player, _, _, attach) in &mut player_query {
                        if q_player.addr == *addr {
                        if q_player.addr == *from {
                            // HEY! GHOSTLY! PLEASE FILL THIS STRUCT WITH DATA!
                            // THANKS!
                            let save = SaveData {


@@ 913,8 929,10 @@ fn on_message(
                            let packet = Packet::SaveData {
                                payload: save_string,
                            };
                            let buf = serde_json::to_vec(&packet).unwrap();
                            event_queue.push(ServerEvent::Broadcast(MessageType::Text, buf));

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


@@ 1114,7 1132,7 @@ fn load_savefile(
            }
        }
    }
    return ret;
    ret
}

fn construct_save_data(


@@ 1167,47 1185,45 @@ fn detach_recursive(
        (Without<PlanetType>, Without<Player>),
    >,
) {
    for child in attach.children {
        if let Some(child) = child {
            {
                let (entity, part_type, _transform, attach, _velocity, _, _, mut flags) =
                    attached_query.get_mut(child).unwrap();
                commands.entity(entity).remove::<Attach>();
                if *part_type == PartType::LandingThruster {
                    commands.entity(entity).insert(LooseAttach {
                        children: attach.children,
                    });
                    commands
                        .entity(attach.children[2].unwrap())
                        .remove::<Attach>();
                    flags.attached = false;
                    continue;
                } else if *part_type == PartType::LandingThrusterSuspension {
                    flags.attached = false;
                    let parent = attach.parent.unwrap();
                    let parent_attach = attached_query.get(parent).unwrap().3;
                    println!("suspension {:?} {:?}", parent_attach.children, entity);
                    commands.entity(parent).insert(LooseAttach {
                        children: parent_attach.children,
                    });
                } else {
                    flags.attached = false;
                    commands.entity(entity).remove::<ImpulseJoint>();
                    detach_recursive(commands, attach.clone(), attached_query);
                }
    for child in attach.children.iter().flatten() {
        {
            let (entity, part_type, _transform, attach, _velocity, _, _, mut flags) =
                attached_query.get_mut(*child).unwrap();
            commands.entity(entity).remove::<Attach>();
            if *part_type == PartType::LandingThruster {
                commands.entity(entity).insert(LooseAttach {
                    children: attach.children,
                });
                commands
                    .entity(attach.children[2].unwrap())
                    .remove::<Attach>();
                flags.attached = false;
                continue;
            } else if *part_type == PartType::LandingThrusterSuspension {
                flags.attached = false;
                let parent = attach.parent.unwrap();
                let parent_attach = attached_query.get(parent).unwrap().3;
                println!("suspension {:?} {:?}", parent_attach.children, entity);
                commands.entity(parent).insert(LooseAttach {
                    children: parent_attach.children,
                });
            } else {
                flags.attached = false;
                commands.entity(entity).remove::<ImpulseJoint>();
                detach_recursive(commands, attach.clone(), attached_query);
            }
            let (entity, _part_type, _transform, attach, _velocity, _, _, mut flags) =
                attached_query.get_mut(child).unwrap();
            flags.attached = false;
            let parent = attach.parent.unwrap();
            if attached_query.contains(parent) {
                let mut parent_attach = attached_query.get_mut(parent).unwrap().3;
                let children = parent_attach.children.clone();
                for (i, child) in children.iter().enumerate() {
                    if let Some(child) = child {
                        if *child == entity {
                            parent_attach.children[i] = None;
                        }
        }
        let (entity, _part_type, _transform, attach, _velocity, _, _, mut flags) =
            attached_query.get_mut(*child).unwrap();
        flags.attached = false;
        let parent = attach.parent.unwrap();
        if attached_query.contains(parent) {
            let mut parent_attach = attached_query.get_mut(parent).unwrap().3;
            let children = parent_attach.children;
            for (i, child) in children.iter().enumerate() {
                if let Some(child) = child {
                    if *child == entity {
                        parent_attach.children[i] = None;
                    }
                }
            }


@@ 1247,161 1263,158 @@ fn attach_on_module_tree(
    >,
) -> bool {
    let mut ret = false;
    for child in attach.children {
        if let Some(child) = child {
            let (entity, _part_type, transform, mut attach, velocity, can_attach, loose_attach, _) =
                attached_query.get_mut(child).unwrap();

            let p_pos = transform.translation;
            let (rel_x, rel_y) = (p_pos.x - x / SCALE, p_pos.y - y / SCALE);
            let angle = transform.rotation.to_euler(EulerRot::ZYX).0;
            let (rel_x, rel_y) = (
                rel_x * (-angle).cos() - rel_y * (-angle).sin(),
                rel_x * (-angle).sin() + rel_y * (-angle).cos(),
    for child in attach.children.iter().flatten() {
        let (entity, _part_type, transform, mut attach, velocity, can_attach, loose_attach, _) =
            attached_query.get_mut(*child).unwrap();

        let p_pos = transform.translation;
        let (rel_x, rel_y) = (p_pos.x - x / SCALE, p_pos.y - y / SCALE);
        let angle = transform.rotation.to_euler(EulerRot::ZYX).0;
        let (rel_x, rel_y) = (
            rel_x * (-angle).cos() - rel_y * (-angle).sin(),
            rel_x * (-angle).sin() + rel_y * (-angle).cos(),
        );
        let mut module = part_query.get_mut(select).unwrap();
        let attachable = can_attach.is_some();
        if attach.children[2].is_none()
            && attachable
            && 15. / SCALE < rel_y
            && rel_y < 30. / SCALE
            && -20. / SCALE < rel_x
            && rel_x < 20. / SCALE
        {
            module.2.translation = vec3(
                p_pos.x + 53. / SCALE * angle.sin(),
                p_pos.y - 53. / SCALE * angle.cos(),
                0.,
            );
            let mut module = part_query.get_mut(select).unwrap();
            let attachable = can_attach != None;
            if attach.children[2] == None
                && attachable
                && 15. / SCALE < rel_y
                && rel_y < 30. / SCALE
                && -20. / SCALE < rel_x
                && rel_x < 20. / SCALE
            {
                module.2.translation = vec3(
                    p_pos.x + 53. / SCALE * angle.sin(),
                    p_pos.y - 53. / SCALE * angle.cos(),
                    0.,
                );
                module.2.rotation = Quat::from_euler(EulerRot::ZYX, angle, 0., 0.);
                module.3.linvel = velocity.linvel;
                let joint = FixedJointBuilder::new().local_anchor1(vec2(0. / SCALE, -53. / SCALE));
                let mut children = [None, None, None, None];
                if let Some(loose_attach) = loose_attach {
                    commands.entity(entity).remove::<LooseAttach>();
                    if *module.1 == PartType::LandingThruster {
                        commands
                            .entity(loose_attach.children[2].unwrap())
                            .insert(Attach {
                                associated_player: attach.associated_player,
                                parent: Some(entity),
                                children: [None, None, None, None],
                            });
                    }
                    children = loose_attach.children;
            module.2.rotation = Quat::from_euler(EulerRot::ZYX, angle, 0., 0.);
            module.3.linvel = velocity.linvel;
            let joint = FixedJointBuilder::new().local_anchor1(vec2(0. / SCALE, -53. / SCALE));
            let mut children = [None, None, None, None];
            if let Some(loose_attach) = loose_attach {
                commands.entity(entity).remove::<LooseAttach>();
                if *module.1 == PartType::LandingThruster {
                    commands
                        .entity(loose_attach.children[2].unwrap())
                        .insert(Attach {
                            associated_player: attach.associated_player,
                            parent: Some(entity),
                            children: [None, None, None, None],
                        });
                }
                let mut module_entity = commands.entity(module.0);
                module_entity.insert(ImpulseJoint::new(entity, joint));
                module_entity.insert(Attach {
                    associated_player: attach.associated_player,
                    parent: Some(entity),
                    children,
                });
                attach.children[2] = Some(module.0);
                module.5.attached = true;
                return true;
            } else if attach.children[1] == None
                && attachable
                && -30. / SCALE < rel_x
                && rel_x < -15. / SCALE
                && -20. / SCALE < rel_y
                && rel_y < 20. / SCALE
            {
                module.2.translation = vec3(
                    p_pos.x + 53. / SCALE * angle.cos(),
                    p_pos.y + 53. / SCALE * angle.sin(),
                    0.,
                );
                module.2.rotation =
                    Quat::from_euler(EulerRot::ZYX, angle + (std::f32::consts::PI / 2.), 0., 0.);
                module.3.linvel = velocity.linvel;
                let joint = FixedJointBuilder::new()
                    .local_anchor1(vec2(53. / SCALE, 0. / SCALE))
                    .local_basis2(std::f32::consts::PI / 2.);
                let mut children = [None, None, None, None];
                if let Some(loose_attach) = loose_attach {
                    commands.entity(entity).remove::<LooseAttach>();
                    if *module.1 == PartType::LandingThruster {
                        commands
                            .entity(loose_attach.children[2].unwrap())
                            .insert(Attach {
                                associated_player: attach.associated_player,
                                parent: Some(entity),
                                children: [None, None, None, None],
                            });
                    }
                    children = loose_attach.children;
                children = loose_attach.children;
            }
            let mut module_entity = commands.entity(module.0);
            module_entity.insert(ImpulseJoint::new(entity, joint));
            module_entity.insert(Attach {
                associated_player: attach.associated_player,
                parent: Some(entity),
                children,
            });
            attach.children[2] = Some(module.0);
            module.5.attached = true;
            return true;
        } else if attach.children[1].is_none()
            && attachable
            && -30. / SCALE < rel_x
            && rel_x < -15. / SCALE
            && -20. / SCALE < rel_y
            && rel_y < 20. / SCALE
        {
            module.2.translation = vec3(
                p_pos.x + 53. / SCALE * angle.cos(),
                p_pos.y + 53. / SCALE * angle.sin(),
                0.,
            );
            module.2.rotation =
                Quat::from_euler(EulerRot::ZYX, angle + (std::f32::consts::PI / 2.), 0., 0.);
            module.3.linvel = velocity.linvel;
            let joint = FixedJointBuilder::new()
                .local_anchor1(vec2(53. / SCALE, 0. / SCALE))
                .local_basis2(std::f32::consts::PI / 2.);
            let mut children = [None, None, None, None];
            if let Some(loose_attach) = loose_attach {
                commands.entity(entity).remove::<LooseAttach>();
                if *module.1 == PartType::LandingThruster {
                    commands
                        .entity(loose_attach.children[2].unwrap())
                        .insert(Attach {
                            associated_player: attach.associated_player,
                            parent: Some(entity),
                            children: [None, None, None, None],
                        });
                }
                let mut module_entity = commands.entity(module.0);
                module_entity.insert(ImpulseJoint::new(entity, joint));
                module_entity.insert(Attach {
                    associated_player: attach.associated_player,
                    parent: Some(entity),
                    children,
                });
                attach.children[1] = Some(module.0);
                module.5.attached = true;
                return true;
            } else if attach.children[3] == None
                && attachable
                && 15. / SCALE < rel_x
                && rel_x < 30. / SCALE
                && -20. / SCALE < rel_y
                && rel_y < 20. / SCALE
            {
                module.2.translation = vec3(
                    p_pos.x - 53. / SCALE * angle.cos(),
                    p_pos.y - 53. / SCALE * angle.sin(),
                    0.,
                );
                module.2.rotation =
                    Quat::from_euler(EulerRot::ZYX, angle - (std::f32::consts::PI / 2.), 0., 0.);
                module.3.linvel = velocity.linvel;
                let joint = FixedJointBuilder::new()
                    .local_anchor1(vec2(-53. / SCALE, 0. / SCALE))
                    .local_basis2(-std::f32::consts::PI / 2.);
                let mut children = [None, None, None, None];
                if let Some(loose_attach) = loose_attach {
                    commands.entity(entity).remove::<LooseAttach>();
                    if *module.1 == PartType::LandingThruster {
                        commands
                            .entity(loose_attach.children[2].unwrap())
                            .insert(Attach {
                                associated_player: attach.associated_player,
                                parent: Some(entity),
                                children: [None, None, None, None],
                            });
                    }
                    children = loose_attach.children;
                children = loose_attach.children;
            }
            let mut module_entity = commands.entity(module.0);
            module_entity.insert(ImpulseJoint::new(entity, joint));
            module_entity.insert(Attach {
                associated_player: attach.associated_player,
                parent: Some(entity),
                children,
            });
            attach.children[1] = Some(module.0);
            module.5.attached = true;
            return true;
        } else if attach.children[3].is_none()
            && attachable
            && 15. / SCALE < rel_x
            && rel_x < 30. / SCALE
            && -20. / SCALE < rel_y
            && rel_y < 20. / SCALE
        {
            module.2.translation = vec3(
                p_pos.x - 53. / SCALE * angle.cos(),
                p_pos.y - 53. / SCALE * angle.sin(),
                0.,
            );
            module.2.rotation =
                Quat::from_euler(EulerRot::ZYX, angle - (std::f32::consts::PI / 2.), 0., 0.);
            module.3.linvel = velocity.linvel;
            let joint = FixedJointBuilder::new()
                .local_anchor1(vec2(-53. / SCALE, 0. / SCALE))
                .local_basis2(-std::f32::consts::PI / 2.);
            let mut children = [None, None, None, None];
            if let Some(loose_attach) = loose_attach {
                commands.entity(entity).remove::<LooseAttach>();
                if *module.1 == PartType::LandingThruster {
                    commands
                        .entity(loose_attach.children[2].unwrap())
                        .insert(Attach {
                            associated_player: attach.associated_player,
                            parent: Some(entity),
                            children: [None, None, None, None],
                        });
                }
                let mut module_entity = commands.entity(module.0);
                module_entity.insert(ImpulseJoint::new(entity, joint));
                module_entity.insert(Attach {
                    associated_player: attach.associated_player,
                    parent: Some(entity),
                    children,
                });
                attach.children[3] = Some(module.0);
                module.5.attached = true;
                return true;
                children = loose_attach.children;
            }
            ret = ret
                | if *module.1 != PartType::LandingThruster {
                    attach_on_module_tree(
                        x,
                        y,
                        commands,
                        attach.clone(),
                        select,
                        attached_query,
                        part_query,
                    )
                } else {
                    false
                };
            let mut module_entity = commands.entity(module.0);
            module_entity.insert(ImpulseJoint::new(entity, joint));
            module_entity.insert(Attach {
                associated_player: attach.associated_player,
                parent: Some(entity),
                children,
            });
            attach.children[3] = Some(module.0);
            module.5.attached = true;
            return true;
        }
        ret |= if *module.1 != PartType::LandingThruster {
            attach_on_module_tree(
                x,
                y,
                commands,
                attach.clone(),
                select,
                attached_query,
                part_query,
            )
        } else {
            false
        };
    }
    return ret;
    ret
}

fn convert_modules(


@@ 1424,7 1437,7 @@ fn convert_modules(
        (&mut Collider, &mut Transform, &Parent),
        (Without<Player>, Without<Attach>),
    >,
    mut packet_send: EventWriter<ServerEvent>,
    mut packet_send: EventWriter<WsEvent>,
) {
    for (_planet_entity, planet_type, children) in &planet_query {
        for (entity1, entity2, intersecting) in


@@ 1493,132 1506,137 @@ fn convert_modules_recursive(
        (&mut Collider, &mut Transform, &Parent),
        (Without<Player>, Without<Attach>),
    >,
    packet_send: &mut EventWriter<ServerEvent>,
    packet_send: &mut EventWriter<WsEvent>,
) {
    for child in attach.children {
        if let Some(child) = child {
            let (module_entity, mut part_type, mut attach, children, module_transform, part_flags) =
                attached_query.get_mut(child).unwrap();
            if *part_type == PartType::Cargo {
                match planet_type {
                    PlanetType::Mars => {
                        *part_type = PartType::Hub;
                        let (mut collider, mut transform, _) =
                            collider_query.get_mut(*children.first().unwrap()).unwrap();
                        *collider =
                            Collider::cuboid(PART_HALF_SIZE / SCALE, PART_HALF_SIZE / SCALE);
                        *transform = Transform::from_xyz(0., 0., 0.);
                        commands
                            .get_entity(module_entity)
                            .unwrap()
                            .remove::<CanAttach>();
                        commands
                            .get_entity(module_entity)
                            .unwrap()
                            .insert(CanAttach(15));
                        let packet = Packet::DespawnPart { id: child.index() };
                        let buf = serde_json::to_vec(&packet).unwrap();
                        packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf.clone()));

                        let packet = Packet::SpawnPart {
                            id: child.index(),
                            part: Part {
                                part_type: PartType::Hub,
                                transform: proto_transform!(transform),
                                flags: proto_part_flags!(part_flags),
                            },
                        };
                        let buf = serde_json::to_vec(&packet).unwrap();
    for child in attach.children.iter().flatten() {
        let (module_entity, mut part_type, mut attach, children, module_transform, part_flags) =
            attached_query.get_mut(*child).unwrap();
        if *part_type == PartType::Cargo {
            match planet_type {
                PlanetType::Mars => {
                    *part_type = PartType::Hub;
                    let (mut collider, mut transform, _) =
                        collider_query.get_mut(*children.first().unwrap()).unwrap();
                    *collider = Collider::cuboid(PART_HALF_SIZE / SCALE, PART_HALF_SIZE / SCALE);
                    *transform = Transform::from_xyz(0., 0., 0.);
                    commands
                        .get_entity(module_entity)
                        .unwrap()
                        .remove::<CanAttach>();
                    commands
                        .get_entity(module_entity)
                        .unwrap()
                        .insert(CanAttach(15));
                    let packet = Packet::DespawnPart { id: child.index() };

                        packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf));
                    }
                    PlanetType::Moon => {
                        *part_type = PartType::LandingThruster;
                        let (mut collider, mut transform, _) =
                            collider_query.get_mut(*children.first().unwrap()).unwrap();
                        *collider = Collider::cuboid(PART_HALF_SIZE / SCALE, 18.75 / SCALE);
                        *transform = Transform::from_xyz(0., 6.25 / SCALE, 0.);
                        //commands.get_entity(module_entity).unwrap().remove::<CanAttach>();
                        let joint = PrismaticJointBuilder::new(Vec2::new(0., 1.))
                            .local_anchor1(Vec2::new(0., 0.))
                            .local_anchor2(Vec2::new(0., 0.))
                            .motor_position(0., 150., 10.)
                            .limits([0., 50. / SCALE])
                            .build();
                        let mut suspension = commands.spawn(PartBundle {
                            transform: TransformBundle::from(*module_transform),
                            part_type: PartType::LandingThrusterSuspension,
                            flags: PartFlags { attached: false },
                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                    });

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

                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                    });
                }
                PlanetType::Moon => {
                    *part_type = PartType::LandingThruster;
                    let (mut collider, mut transform, _) =
                        collider_query.get_mut(*children.first().unwrap()).unwrap();
                    *collider = Collider::cuboid(PART_HALF_SIZE / SCALE, 18.75 / SCALE);
                    *transform = Transform::from_xyz(0., 6.25 / SCALE, 0.);
                    //commands.get_entity(module_entity).unwrap().remove::<CanAttach>();
                    let joint = PrismaticJointBuilder::new(Vec2::new(0., 1.))
                        .local_anchor1(Vec2::new(0., 0.))
                        .local_anchor2(Vec2::new(0., 0.))
                        .motor_position(0., 150., 10.)
                        .limits([0., 50. / SCALE])
                        .build();
                    let mut suspension = commands.spawn(PartBundle {
                        transform: TransformBundle::from(*module_transform),
                        part_type: PartType::LandingThrusterSuspension,
                        flags: PartFlags { attached: false },
                    });
                    suspension
                        .insert(RigidBody::Dynamic)
                        .with_children(|children| {
                            children
                                .spawn(Collider::cuboid(PART_HALF_SIZE / SCALE, 1. / SCALE))
                                .insert(TransformBundle::from(Transform::from_xyz(
                                    0.,
                                    -24. / SCALE,
                                    0.,
                                )));
                        })
                        .insert(ImpulseJoint::new(module_entity, joint))
                        .insert(ExternalForce::default())
                        .insert(ExternalImpulse::default())
                        .insert(Velocity::default())
                        .insert(ReadMassProperties::default())
                        .insert(AdditionalMassProperties::MassProperties(MassProperties {
                            local_center_of_mass: vec2(0.0, 0.0),
                            mass: 0.00000000000001,
                            principal_inertia: 0.0000000000001,
                        }))
                        .insert(Attach {
                            associated_player: attach.associated_player,
                            parent: Some(module_entity),
                            children: [None, None, None, None],
                        });
                        suspension
                            .insert(RigidBody::Dynamic)
                            .with_children(|children| {
                                children
                                    .spawn(Collider::cuboid(PART_HALF_SIZE / SCALE, 1. / SCALE))
                                    .insert(TransformBundle::from(Transform::from_xyz(
                                        0.,
                                        -24. / SCALE,
                                        0.,
                                    )));
                            })
                            .insert(ImpulseJoint::new(module_entity, joint))
                            .insert(ExternalForce::default())
                            .insert(ExternalImpulse::default())
                            .insert(Velocity::default())
                            .insert(ReadMassProperties::default())
                            .insert(AdditionalMassProperties::MassProperties(MassProperties {
                                local_center_of_mass: vec2(0.0, 0.0),
                                mass: 0.00000000000001,
                                principal_inertia: 0.0000000000001,
                            }))
                            .insert(Attach {
                                associated_player: attach.associated_player,
                                parent: Some(module_entity),
                                children: [None, None, None, None],
                            });
                        attach.children[2] = Some(suspension.id());

                        let packet = Packet::DespawnPart { id: child.index() };
                        let buf = serde_json::to_vec(&packet).unwrap();
                        packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf.clone()));

                        let packet = Packet::SpawnPart {
                            id: child.index(),
                            part: Part {
                                part_type: PartType::LandingThruster,
                                transform: proto_transform!(transform),
                                flags: proto_part_flags!(part_flags),
                            },
                        };
                        let buf = serde_json::to_vec(&packet).unwrap();
                    attach.children[2] = Some(suspension.id());

                        packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf));
                    let packet = Packet::DespawnPart { id: child.index() };

                        let packet = Packet::SpawnPart {
                            id: suspension.id().index(),
                            part: Part {
                                part_type: PartType::LandingThrusterSuspension,
                                transform: proto_transform!(transform),
                                flags: proto_part_flags!(part_flags),
                            },
                        };
                        let buf = serde_json::to_vec(&packet).unwrap();
                        packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf));
                    }
                    _ => {}
                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                    });

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

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

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

                    packet_send.send(WsEvent::Broadcast {
                        message: packet.into(),
                    });
                }
            }
            if *part_type != PartType::LandingThruster {
                convert_modules_recursive(
                    commands,
                    planet_type,
                    attach.clone(),
                    attached_query,
                    collider_query,
                    packet_send,
                );
                _ => {}
            }
        }
        if *part_type != PartType::LandingThruster {
            convert_modules_recursive(
                commands,
                planet_type,
                attach.clone(),
                attached_query,
                collider_query,
                packet_send,
            );
        }
    }
}



@@ 1671,7 1689,7 @@ fn break_modules(
        let parent = attach.parent.unwrap();
        if attached_query.contains(parent) {
            let mut parent_attach = attached_query.get_mut(parent).unwrap().3;
            let children = parent_attach.children.clone();
            let children = parent_attach.children;
            for (i, child) in children.iter().enumerate() {
                if let Some(child) = child {
                    if *child == entity {


@@ 1696,7 1714,7 @@ fn save_eligibility(
    rapier_context: Res<RapierContext>,
    planet_query: Query<(Entity, &PlanetType, &Children)>,
    mut player_query: Query<&mut Player>,
    mut packet_send: EventWriter<ServerEvent>,
    mut packet_send: EventWriter<WsEvent>,
) {
    for (_planet_entity, _planet_type, children) in &planet_query {
        for (entity1, entity2, intersecting) in


@@ 1710,11 1728,13 @@ fn save_eligibility(
                };
                if player_query.contains(other) {
                    let mut player = player_query.get_mut(other).unwrap();
                    if player.save_eligibility != true {
                    if !player.save_eligibility {
                        let packet = Packet::SaveEligibility { eligible: true };
                        let buf = serde_json::to_vec(&packet).unwrap();

                        packet_send.send(ServerEvent::Send(player.addr, MessageType::Text, buf));
                        packet_send.send(WsEvent::Send {
                            to: player.addr,
                            message: packet.into(),
                        });
                    }
                    player.save_eligibility = true;
                }


@@ 1726,11 1746,13 @@ fn save_eligibility(
                };
                if player_query.contains(other) {
                    let mut player = player_query.get_mut(other).unwrap();
                    if player.save_eligibility != false {
                    if player.save_eligibility {
                        let packet = Packet::SaveEligibility { eligible: false };
                        let buf = serde_json::to_vec(&packet).unwrap();

                        packet_send.send(ServerEvent::Send(player.addr, MessageType::Text, buf));
                        packet_send.send(WsEvent::Send {
                            to: player.addr,
                            message: packet.into(),
                        });
                    }
                    player.save_eligibility = false;
                }


@@ 1744,12 1766,12 @@ fn on_close(
    attached_query: Query<&Attach, With<PartType>>,
    part_query: Query<&PartType>,
    mut commands: Commands,
    mut packet_recv: Local<ManualEventReader<ServerEvent>>,
    mut packet_send: ResMut<Events<ServerEvent>>,
    mut packet_recv: Local<ManualEventReader<WsEvent>>,
    mut packet_send: ResMut<Events<WsEvent>>,
) {
    let mut packets = Vec::new();
    for packet in packet_recv.read(&packet_send) {
        if let ServerEvent::Close(addr) = packet {
        if let WsEvent::Close { addr } = packet {
            for (entity, player, attach) in &player_query {
                if player.addr == *addr {
                    despawn_module_tree(


@@ 1762,14 1784,13 @@ fn on_close(
                    commands.entity(entity).despawn_recursive();

                    let packet = Packet::PlayerLeave { id: entity.index() };
                    let buf = serde_json::to_vec(&packet).unwrap();

                    for (in_entity, player, _) in &player_query {
                        if entity != in_entity {
                            packets.push(ServerEvent::Send(
                                player.addr,
                                MessageType::Text,
                                buf.clone(),
                            ));
                            packets.push(WsEvent::Send {
                                to: player.addr,
                                message: packet.clone().into(),
                            });
                        }
                    }
                }


@@ 1786,26 1807,28 @@ fn despawn_module_tree(
    attach: &Attach,
    attached_query: &Query<&Attach, With<PartType>>,
    part_query: &Query<&PartType>,
    packets: &mut Vec<ServerEvent>,
    packets: &mut Vec<WsEvent>,
) {
    for child in attach.children {
        if let Some(child) = child {
            commands.entity(child).despawn_recursive();
            let packet = Packet::DespawnPart { id: child.index() };
            let buf = serde_json::to_vec(&packet).unwrap();
            packets.push(ServerEvent::Broadcast(MessageType::Text, buf.clone()));

            let attach = match attached_query.get(child) {
                Ok(s) => s,
                Err(_) => match part_query.get(child) {
                    Ok(_) => {
                        continue;
                    }
                    Err(e) => panic!("{}", e),
                },
            };
            despawn_module_tree(commands, attach, attached_query, part_query, packets);
        }
    for child in attach.children.iter().flatten() {
        commands.entity(*child).despawn_recursive();
        let packet = Packet::DespawnPart {
            id: (*child).index(),
        };

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

        let attach = match attached_query.get(*child) {
            Ok(s) => s,
            Err(_) => match part_query.get(*child) {
                Ok(_) => {
                    continue;
                }
                Err(e) => panic!("{}", e),
            },
        };
        despawn_module_tree(commands, attach, attached_query, part_query, packets);
    }
}



@@ 1813,7 1836,7 @@ fn on_position_change(
    mut commands: Commands,
    part_query: Query<(Entity, &PartType, &Transform, &PartFlags), Changed<Transform>>,
    planet_query: Query<(Entity, &PlanetType, &Transform), Changed<Transform>>,
    mut packet_send: EventWriter<ServerEvent>,
    mut packet_send: EventWriter<WsEvent>,
) {
    let mut updated_parts = Vec::new();
    for (entity, part_type, transform, flags) in part_query.iter() {


@@ 1835,9 1858,10 @@ fn on_position_change(
        let packet = Packet::PartPositions {
            parts: updated_parts,
        };
        let buf = serde_json::to_vec(&packet).unwrap();

        packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf));
        packet_send.send(WsEvent::Broadcast {
            message: packet.into(),
        });
    }

    let mut planets = Vec::new();


@@ 1861,9 1885,10 @@ fn on_position_change(

    if !planets.is_empty() {
        let packet = Packet::PlanetPositions { planets };
        let buf = serde_json::to_vec(&packet).unwrap();

        packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf));
        packet_send.send(WsEvent::Broadcast {
            message: packet.into(),
        });
    }
}



@@ 1966,27 1991,84 @@ fn search_thrusters(
    >,
) {
    let p_angle = p_transform.rotation.to_euler(EulerRot::ZYX).0;
    for child in attach.children {
        if let Some(child) = child {
            let (attach, part_type, mut force, transform) = attached_query.get_mut(child).unwrap();
            let angle = transform.rotation.to_euler(EulerRot::ZYX).0;
            let relative_angle = (p_angle - angle).abs();
            let relative_pos = transform.translation - p_transform.translation;
            let relative_pos = Vec2::new(
                relative_pos
                    .x
                    .mul_add((-p_angle).cos(), -relative_pos.y * (-p_angle).sin()),
                relative_pos
                    .x
                    .mul_add((-p_angle).sin(), relative_pos.y * (-p_angle).cos()),
            );
    for child in attach.children.iter().flatten() {
        let (attach, part_type, mut force, transform) = attached_query.get_mut(*child).unwrap();
        let angle = transform.rotation.to_euler(EulerRot::ZYX).0;
        let relative_angle = (p_angle - angle).abs();
        let relative_pos = transform.translation - p_transform.translation;
        let relative_pos = Vec2::new(
            relative_pos
                .x
                .mul_add((-p_angle).cos(), -relative_pos.y * (-p_angle).sin()),
            relative_pos
                .x
                .mul_add((-p_angle).sin(), relative_pos.y * (-p_angle).cos()),
        );

            let mut force_mult = 0.;
            if *part_type == PartType::LandingThruster {
                force_mult = LANDING_THRUSTER_FORCE;
        let mut force_mult = 0.;
        if *part_type == PartType::LandingThruster {
            force_mult = LANDING_THRUSTER_FORCE;
        }
        if input.up && 3. * PI / 4. < relative_angle && relative_angle < 5. * PI / 4. {
            let thruster_force = ExternalForce::at_point(
                Vec2::new(
                    -force_mult / SCALE * angle.sin(),
                    force_mult / SCALE * angle.cos(),
                ),
                transform.translation.xy(),
                transform.translation.xy(),
            );
            force.force += thruster_force.force;
            force.torque += thruster_force.torque;
        }
        if input.down
            && ((0. < relative_angle && relative_angle < PI / 4.)
                || (7. * PI / 4. < relative_angle && relative_angle < 2. * PI))
        {
            let thruster_force = ExternalForce::at_point(
                Vec2::new(
                    -force_mult / SCALE * angle.sin(),
                    force_mult / SCALE * angle.cos(),
                ),
                transform.translation.xy(),
                transform.translation.xy(),
            );
            force.force += thruster_force.force;
            force.torque += thruster_force.torque;
        }
        if input.left {
            if 3. * PI / 4. < relative_angle
                && relative_angle < 5. * PI / 4.
                && relative_pos.x > 24. / SCALE
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(
                        -force_mult / SCALE * angle.sin(),
                        force_mult / SCALE * angle.cos(),
                    ),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
            }
            if ((0. < relative_angle && relative_angle < PI / 4.)
                || (7. * PI / 4. < relative_angle && relative_angle < 2. * PI))
                && relative_pos.x < -24. / SCALE
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(
                        -force_mult / SCALE * angle.sin(),
                        force_mult / SCALE * angle.cos(),
                    ),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
            }
            if input.up {
                if 3. * PI / 4. < relative_angle && relative_angle < 5. * PI / 4. {
            if PI / 4. < relative_angle && relative_angle < 3. * PI / 4. {
                if relative_pos.y < -24. / SCALE {
                    let thruster_force = ExternalForce::at_point(
                        Vec2::new(
                            -force_mult / SCALE * angle.sin(),


@@ 1998,11 2080,7 @@ fn search_thrusters(
                    force.force += thruster_force.force;
                    force.torque += thruster_force.torque;
                }
            }
            if input.down {
                if (0. < relative_angle && relative_angle < PI / 4.)
                    || (7. * PI / 4. < relative_angle && relative_angle < 2. * PI)
                {
                if -24. / SCALE < relative_pos.y && relative_pos.y < 24. / SCALE {
                    let thruster_force = ExternalForce::at_point(
                        Vec2::new(
                            -force_mult / SCALE * angle.sin(),


@@ 2015,94 2093,36 @@ fn search_thrusters(
                    force.torque += thruster_force.torque;
                }
            }
            if input.left {
                if 3. * PI / 4. < relative_angle && relative_angle < 5. * PI / 4. {
                    if relative_pos.x > 24. / SCALE {
                        let thruster_force = ExternalForce::at_point(
                            Vec2::new(
                                -force_mult / SCALE * angle.sin(),
                                force_mult / SCALE * angle.cos(),
                            ),
                            transform.translation.xy(),
                            transform.translation.xy(),
                        );
                        force.force += thruster_force.force;
                        force.torque += thruster_force.torque;
                    }
                }
                if (0. < relative_angle && relative_angle < PI / 4.)
                    || (7. * PI / 4. < relative_angle && relative_angle < 2. * PI)
                {
                    if relative_pos.x < -24. / SCALE {
                        let thruster_force = ExternalForce::at_point(
                            Vec2::new(
                                -force_mult / SCALE * angle.sin(),
                                force_mult / SCALE * angle.cos(),
                            ),
                            transform.translation.xy(),
                            transform.translation.xy(),
                        );
                        force.force += thruster_force.force;
                        force.torque += thruster_force.torque;
                    }
                }
                if PI / 4. < relative_angle && relative_angle < 3. * PI / 4. {
                    if relative_pos.y < -24. / SCALE {
                        let thruster_force = ExternalForce::at_point(
                            Vec2::new(
                                -force_mult / SCALE * angle.sin(),
                                force_mult / SCALE * angle.cos(),
                            ),
                            transform.translation.xy(),
                            transform.translation.xy(),
                        );
                        force.force += thruster_force.force;
                        force.torque += thruster_force.torque;
                    }
                    if -24. / SCALE < relative_pos.y && relative_pos.y < 24. / SCALE {
                        let thruster_force = ExternalForce::at_point(
                            Vec2::new(
                                -force_mult / SCALE * angle.sin(),
                                force_mult / SCALE * angle.cos(),
                            ),
                            transform.translation.xy(),
                            transform.translation.xy(),
                        );
                        force.force += thruster_force.force;
                        force.torque += thruster_force.torque;
                    }
            if 5. * PI / 4. < relative_angle && relative_angle < 7. * PI / 4. {
                if relative_pos.y > 24. / SCALE {
                    let thruster_force = ExternalForce::at_point(
                        Vec2::new(
                            -force_mult / SCALE * angle.sin(),
                            force_mult / SCALE * angle.cos(),
                        ),
                        transform.translation.xy(),
                        transform.translation.xy(),
                    );
                    force.force += thruster_force.force;
                    force.torque += thruster_force.torque;
                }
                if 5. * PI / 4. < relative_angle && relative_angle < 7. * PI / 4. {
                    if relative_pos.y > 24. / SCALE {
                        let thruster_force = ExternalForce::at_point(
                            Vec2::new(
                                -force_mult / SCALE * angle.sin(),
                                force_mult / SCALE * angle.cos(),
                            ),
                            transform.translation.xy(),
                            transform.translation.xy(),
                        );
                        force.force += thruster_force.force;
                        force.torque += thruster_force.torque;
                    }
                    if -24. / SCALE < relative_pos.y && relative_pos.y < 24. / SCALE {
                        let thruster_force = ExternalForce::at_point(
                            Vec2::new(
                                -force_mult / SCALE * angle.sin(),
                                force_mult / SCALE * angle.cos(),
                            ),
                            transform.translation.xy(),
                            transform.translation.xy(),
                        );
                        force.force += thruster_force.force;
                        force.torque += thruster_force.torque;
                    }
                if -24. / SCALE < relative_pos.y && relative_pos.y < 24. / SCALE {
                    let thruster_force = ExternalForce::at_point(
                        Vec2::new(
                            -force_mult / SCALE * angle.sin(),
                            force_mult / SCALE * angle.cos(),
                        ),
                        transform.translation.xy(),
                        transform.translation.xy(),
                    );
                    force.force += thruster_force.force;
                    force.torque += thruster_force.torque;
                }
            }
        }

            if *part_type != PartType::LandingThruster {
                search_thrusters(input, attach.clone(), p_transform, attached_query);
            }
        if *part_type != PartType::LandingThruster {
            search_thrusters(input, attach.clone(), p_transform, attached_query);
        }
    }
}

M server/src/packet.rs => server/src/packet.rs +14 -14
@@ 18,7 18,7 @@ use crate::component::{PartType, PlanetType};
use bevy_tungstenite_stk::tungstenite::Message;
use serde::{Deserialize, Serialize};

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


@@ 35,20 35,20 @@ macro_rules! proto_transform {
    };
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Planet {
    pub planet_type: PlanetType,
    pub transform: ProtoTransform,
    pub radius: f32,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Part {
    pub part_type: PartType,
    pub transform: ProtoTransform,
    pub flags: ProtoPartFlags,
}

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


@@ 62,21 62,21 @@ macro_rules! proto_part_flags {
    };
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum MessageType {
    Server,
    Error,
    Chat,
    Direct,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum ButtonType {
    Left,
    Middle,
    Right,
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "t", content = "c")]
pub enum Packet {
    // serverbound


@@ 140,9 140,9 @@ pub enum Packet {
    },
}

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



@@ 157,13 157,13 @@ impl Display for MsgFromError {
    }
}

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

    fn try_from(value: Message) -> Result<Self, Self::Error> {
    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::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)

M starkingdoms-client/src/hub.ts => starkingdoms-client/src/hub.ts +4 -6
@@ 47,6 47,10 @@ export async function hub_connect(
): Promise<ClientHub | null> {
  logger("connecting to client hub at " + url);

  if (x_pos === undefined || y_pos === undefined) {
    console.log("tsc shut up please");
  }

  let ws = new WebSocket(url);

  ws.onerror = (e) => {


@@ 292,12 296,6 @@ export async function hub_connect(
        }
      } else if (packet.t == PacketType.SaveEligibility) {
        let p = <SaveEligibilityPacket>packet.c;
        chatbox.addMessage(
          "server-message",
          p.eligible
            ? "Eligible to save. Run the .save command to save your progress!"
            : "No longer eligible to save.",
        );
        global.saveEligible = p.eligible;
      } else if (packet.t == PacketType.SaveData) {
        let p = <SaveDataPacket>packet.c;