~starkingdoms/starkingdoms

4daa8be06437c36f0da4a00a9fc88839fe639b12 — ghostlyzsh 1 year, 4 months ago 9dd504a
basic organizational fixes
M server/src/component.rs => server/src/component.rs +1 -86
@@ 17,63 17,8 @@ use std::net::SocketAddr;

use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use starkingdoms_common::PartType as c_PartType;

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

#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
pub enum PartType {
    Placeholder,
    Hearty,
    Cargo,
    Hub,
    LandingThruster,
    LandingThrusterSuspension,
}
impl From<PartType> for c_PartType {
    fn from(value: PartType) -> Self {
        match value {
            PartType::Placeholder => c_PartType::Placeholder,
            PartType::Hearty => c_PartType::Hearty,
            PartType::Cargo => c_PartType::Cargo,
            PartType::Hub => c_PartType::Hub,
            PartType::LandingThruster => c_PartType::LandingThruster,
            PartType::LandingThrusterSuspension => c_PartType::LandingThrusterSuspension,
        }
    }
}
impl From<c_PartType> for PartType {
    fn from(value: c_PartType) -> Self {
        match value {
            c_PartType::Placeholder => PartType::Placeholder,
            c_PartType::Hearty => PartType::Hearty,
            c_PartType::Cargo => PartType::Cargo,
            c_PartType::Hub => PartType::Hub,
            c_PartType::LandingThruster => PartType::LandingThruster,
            c_PartType::LandingThrusterSuspension => PartType::LandingThrusterSuspension,
        }
    }
}

#[derive(Component, Clone, Debug)]
pub struct Attach {
    pub associated_player: Option<Entity>,
    pub parent: Option<Entity>,
    pub children: [Option<Entity>; 4],
}

#[derive(Component, Clone, Debug)]
pub struct LooseAttach {
    pub children: [Option<Entity>; 4],
}

#[derive(Component, Clone, Copy, PartialEq, Debug)]
pub struct CanAttach(pub u8); // each bit means a slot able to attach to
use crate::module::component::{Attach, PartBundle};

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


@@ 94,23 39,6 @@ pub struct Player {
    pub energy: u32,
}

#[derive(Bundle)]
pub struct PlanetBundle {
    pub planet_type: PlanetType,
    pub transform: TransformBundle,
}

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

#[derive(Bundle)]
pub struct PartBundle {
    pub transform: TransformBundle,
    pub part_type: PartType,
    pub flags: PartFlags,
}

#[derive(Bundle)]
pub struct PlayerBundle {


@@ 120,19 48,6 @@ pub struct PlayerBundle {
}

#[derive(Resource)]
pub struct ModuleTimer(pub Timer);
impl ModuleTimer {
    pub fn new() -> Self {
        Self(Timer::from_seconds(0.3, TimerMode::Repeating))
    }
}
impl Default for ModuleTimer {
    fn default() -> Self {
        Self::new()
    }
}

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

M server/src/config.rs => server/src/config.rs +35 -1
@@ 14,7 14,8 @@
//     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 crate::component::{PartType, PlanetType};
use crate::module::component::PartType;
use crate::planet::PlanetType;
use bevy::ecs::system::Resource;
use bevy_rapier2d::rapier::dynamics::IntegrationParameters;
use serde::{Deserialize, Serialize};


@@ 88,3 89,36 @@ pub struct PartConfig {
    pub thruster_force: f32,
    pub thruster_energy: u32,
}

#[macro_export]
macro_rules! mass {
    ($p:expr) => {
        match $crate::parts_config().parts.get(&$p) {
            Some(v) => v.mass,
            None => 1.0,
        }
    };
}

#[macro_export]
macro_rules! capacity {
    ($p:expr) => {
        match $crate::parts_config().parts.get(&$p) {
            Some(v) => v.energy_capacity,
            None => 0,
        }
    };
}

#[macro_export]
macro_rules! planet {
    ($t:expr) => {
        $crate::planets_config().planets.get(&$t).unwrap()
    };
}
#[macro_export]
macro_rules! part {
    ($t:expr) => {
        $crate::parts_config().parts.get(&$t).unwrap()
    };
}

M server/src/main.rs => server/src/main.rs +21 -1217
@@ 17,8 17,6 @@
#![allow(clippy::too_many_arguments)] // bevy :(
#![allow(clippy::only_used_in_recursion)] // todo: remove this

use std::collections::HashMap;

use crate::mathutil::rot2d;
use crate::ws::{StkTungsteniteServerConfig, StkTungsteniteServerPlugin, WsEvent};
use bevy::math::{vec2, vec3};


@@ 32,13 30,16 @@ use bevy_rapier2d::prelude::*;
use component::*;
use hmac::{Hmac, Mac};
use jwt::VerifyWithKey;
use module::component::{Attach, CanAttach, LooseAttach, ModuleTimer, PartBundle, PartFlags, PartType};
use module::save::{construct_save_data, load_savefile};
use module::thruster::search_thrusters;
use module::{attach_on_module_tree, despawn_module_tree, detach_recursive};
use packet::*;
use planet::PlanetType;
use rand::Rng;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use starkingdoms_common::SaveModule;
use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData};
use std::f32::consts::PI;
use std::fs;

use crate::config::{PartsConfig, PhysicsSolver, PlanetsConfig, StkConfig};


@@ 50,14 51,19 @@ mod config;
pub mod macros;
pub mod mathutil;
pub mod packet;
pub mod part;
pub mod ws;
pub mod planet;
pub mod module;
pub mod player;

struct StkPluginGroup;

// factor to multiply positions by to send to the client
pub static CLIENT_SCALE: f32 = 50.0;
// half size of hearty
pub static PART_HALF_SIZE: f32 = 25.0;

// good ol' classic almost useless but still necessary code
static _SERVER_CONFIG: OnceLock<StkConfig> = OnceLock::new();
#[inline]
pub fn server_config() -> StkConfig {


@@ 74,6 80,7 @@ pub fn planets_config() -> PlanetsConfig {
    _PLANETS_CONFIG.get().unwrap().clone()
}

// group main stk plugins together
#[cfg(debug_assertions)]
impl PluginGroup for StkPluginGroup {
    fn build(self) -> PluginGroupBuilder {


@@ 105,6 112,7 @@ impl PluginGroup for StkPluginGroup {
    }
}

// auth token
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserToken {
    pub id: i64,


@@ 119,6 127,7 @@ fn main() {
    let parts_config = fs::read_to_string("/etc/starkingdoms/parts.toml").unwrap();
    let planets_config = fs::read_to_string("/etc/starkingdoms/planets.toml").unwrap();

    // put config in variables
    let server_config: StkConfig = toml::from_str(&server_config).unwrap();
    _SERVER_CONFIG.set(server_config.clone()).unwrap();
    let parts_config: PartsConfig = toml::from_str(&parts_config).unwrap();


@@ 126,6 135,7 @@ fn main() {
    let planets_config: PlanetsConfig = toml::from_str(&planets_config).unwrap();
    _PLANETS_CONFIG.set(planets_config.clone()).unwrap();

    // make the game, start it in .run()
    App::new()
        .insert_resource(AppKeys {
            app_key: server_config.security.app_key.as_bytes().to_vec(),


@@ 148,23 158,24 @@ fn main() {
        ))
        .add_plugins(StkTungsteniteServerPlugin)
        .add_systems(Startup, setup_integration_parameters)
        .add_systems(Startup, spawn_planets)
        .add_systems(FixedUpdate, module_spawn)
        .add_systems(Startup, planet::spawn_planets)
        .add_systems(FixedUpdate, module::module_spawn)
        .add_systems(Update, on_message)
        .add_systems(Update, on_close)
        .add_systems(FixedUpdate, send_player_energy)
        .add_systems(FixedUpdate, on_position_change)
        .add_systems(
            FixedUpdate,
            (break_modules, gravity_update, player_input_update).chain(),
            (module::break_modules, gravity_update, player_input_update).chain(),
        )
        .add_systems(FixedUpdate, save_eligibility)
        .add_systems(FixedUpdate, convert_modules)
        .add_systems(FixedUpdate, module::save::save_eligibility)
        .add_systems(FixedUpdate, module::convert_modules)
        .insert_resource(Time::<Fixed>::from_seconds(
            server_config.server.world_fixed_timestep,
        ))
        .run();

    // game is done running
    info!("Goodbye!");
}



@@ 184,117 195,6 @@ fn setup_integration_parameters(mut context: ResMut<RapierContext>, server_confi
        }
    }
}
fn spawn_planets(mut commands: Commands) {
    info!("Spawning planets");
    let earth_pos = Transform::from_xyz(0.0, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Earth,
            transform: TransformBundle::from(earth_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Earth).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Earth).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Earth).size + 0.3))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })
        .insert(RigidBody::Fixed);
    let moon_pos = Transform::from_xyz(50.0, 20.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Moon,
            transform: TransformBundle::from(moon_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Moon).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Moon).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Moon).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })
        .insert(RigidBody::Fixed);
    let mars_pos = Transform::from_xyz(-50.0, 300.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Mars,
            transform: TransformBundle::from(mars_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Mars).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Mars).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Mars).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })
        .insert(RigidBody::Fixed);
}
fn module_spawn(
    mut commands: Commands,
    time: Res<Time>,
    mut module_timer: ResMut<ModuleTimer>,
    part_query: Query<&PartType, Without<Attach>>,
    mut packet_send: EventWriter<WsEvent>,
    server_config: Res<StkConfig>,
) {
    if module_timer.0.tick(time.delta()).just_finished()
        && part_query.iter().count() < server_config.server.max_free_parts
    {
        let angle: f32 = {
            let mut rng = rand::thread_rng();
            rng.gen::<f32>() * std::f32::consts::PI * 2.
        };
        let mut transform = Transform::from_xyz(angle.cos() * 30.0, angle.sin() * 30.0, 0.0);
        transform.rotate_z(angle);
        let flags = PartFlags { attached: false };
        let mut entity = commands.spawn(PartBundle {
            part_type: PartType::Cargo,
            transform: TransformBundle::from(transform),
            flags,
        });
        entity
            .insert(RigidBody::Dynamic)
            .with_children(|children| {
                children
                    .spawn(Collider::cuboid(0.375, 0.46875))
                    .insert(TransformBundle::from(Transform::from_xyz(0., 0.03125, 0.)));
            })
            .insert(AdditionalMassProperties::MassProperties(MassProperties {
                local_center_of_mass: vec2(0.0, 0.0),
                mass: part!(PartType::Cargo).mass,
                principal_inertia: 7.5,
            }))
            .insert(ExternalForce::default())
            .insert(ExternalImpulse::default())
            .insert(Velocity::default())
            .insert(ReadMassProperties::default());

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

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

fn on_message(
    mut commands: Commands,


@@ 823,911 723,6 @@ fn on_message(
    }
}

fn load_savefile(
    commands: &mut Commands,
    transform: Transform,
    player_id: Entity,
    parent: Entity,
    children: Vec<Option<SaveModule>>,
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    part_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Velocity,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
    player_comp: &mut Player,
) -> [Option<Entity>; 4] {
    let mut ret = [None, None, None, None];
    for (i, child) in children.iter().enumerate() {
        if let Some(child) = child {
            let part_type = PartType::from(child.part_type);
            //let attachable = can_attach != None;

            let p_pos = transform.translation;
            let angle = transform.rotation.to_euler(EulerRot::ZYX).0;
            let mut offset = Vec2::ZERO;
            let mut angle_offset = 0.;
            if i == 2 {
                //offset = Vec2::new(53., -53.);
                offset = Vec2::new(0., -1.06);
                angle_offset = 0.;
            } else if i == 0 {
                //offset = Vec2::new(-53., 53.);
                offset = Vec2::new(0., 1.06);
                angle_offset = PI;
            } else if i == 1 {
                //offset = Vec2::new(53., 53.);
                offset = Vec2::new(1.06, 0.);
                angle_offset = PI / 2.;
            } else if i == 3 {
                //offset = Vec2::new(-53., 53.);
                offset = Vec2::new(-1.06, 0.);
                angle_offset = -PI / 2.;
            }

            let transform = Transform::from_xyz(
                p_pos.x + offset.x * angle.cos() - offset.y * angle.sin(),
                p_pos.y + offset.x * angle.sin() + offset.y * angle.cos(),
                0.,
            )
            .with_rotation(Quat::from_euler(
                EulerRot::ZYX,
                angle + angle_offset,
                0.,
                0.,
            ));
            let module_id = {
                let module = commands.spawn(PartBundle {
                    transform: TransformBundle::from(transform),
                    part_type: child.part_type.into(),
                    flags: PartFlags { attached: true },
                });
                module.id()
            };

            let children = if part_type != PartType::LandingThruster {
                load_savefile(
                    commands,
                    transform,
                    player_id,
                    module_id,
                    child.children.clone(),
                    attached_query,
                    part_query,
                    player_query,
                    player_comp,
                )
            } else {
                [None, None, None, None]
            };

            let mut module = commands.entity(module_id);

            module.insert(Attach {
                associated_player: Some(player_id),
                parent: Some(parent),
                children,
            });
            //module.5.attached = true;
            ret[i] = Some(module.id());

            module
                .insert(RigidBody::Dynamic)
                .with_children(|children| {
                    children
                        .spawn(if part_type == PartType::Cargo {
                            Collider::cuboid(0.375, 0.46875)
                        } else if part_type == PartType::Hub {
                            Collider::cuboid(0.5, 0.5)
                        } else if part_type == PartType::LandingThruster {
                            Collider::cuboid(0.5, 0.375)
                        } else {
                            Collider::cuboid(0.5, 0.5)
                        })
                        .insert(TransformBundle::from(Transform::from_xyz(
                            0.,
                            if part_type == PartType::Cargo {
                                0.03125
                            } else if part_type == PartType::Hub {
                                0.
                            } else if part_type == PartType::LandingThruster {
                                0.125
                            } else {
                                0.
                            },
                            0.,
                        )));
                })
                .insert(AdditionalMassProperties::MassProperties(MassProperties {
                    local_center_of_mass: vec2(0.0, 0.0),
                    mass: mass!(part_type),
                    principal_inertia: 7.5,
                }))
                .insert(ExternalForce::default())
                .insert(ExternalImpulse::default())
                .insert(Velocity::default())
                .insert(ReadMassProperties::default());
            if part_type == PartType::Hub {
                module.insert(CanAttach(15));
            }

            let joint = FixedJointBuilder::new()
                .local_anchor1(vec2(offset.x, offset.y))
                .local_basis1(angle_offset);

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

            if part_type == PartType::LandingThruster {
                let joint = PrismaticJointBuilder::new(Vec2::new(0., 1.))
                    .set_motor(0., 0., 3000., 3000.)
                    .limits([0., 1.])
                    .build();
                let mut suspension = commands.spawn(PartBundle {
                    transform: TransformBundle::from(
                        Transform::from_xyz(
                            p_pos.x + offset.x * angle.cos() - offset.y * angle.sin(),
                            p_pos.y + offset.x * angle.sin() + offset.y * angle.cos(),
                            0.,
                        )
                        .with_rotation(Quat::from_euler(
                            EulerRot::ZYX,
                            angle + angle_offset,
                            0.,
                            0.,
                        )),
                    ),
                    part_type: PartType::LandingThrusterSuspension,
                    flags: PartFlags { attached: true },
                });
                suspension
                    .insert(RigidBody::Dynamic)
                    .with_children(|children| {
                        children
                            .spawn(Collider::cuboid(0.5, 0.02))
                            .insert(TransformBundle::from(Transform::from_xyz(0., -0.48, 0.)));
                    })
                    .insert(ImpulseJoint::new(module_id, joint))
                    .insert(ExternalForce::default())
                    .insert(ExternalImpulse::default())
                    .insert(Velocity::default())
                    .insert(ReadMassProperties::default())
                    .insert(AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: vec2(0.0, 0.0),
                        mass: 0.00000000000001,
                        principal_inertia: 0.0000000000001,
                    }))
                    .insert(Attach {
                        associated_player: Some(player_id),
                        parent: Some(module_id),
                        children: [None, None, None, None],
                    });
                let suspension_id = suspension.id();
                let mut module = commands.entity(module_id);
                module.remove::<Attach>();
                module.insert(Attach {
                    associated_player: Some(player_id),
                    parent: Some(parent),
                    children: [None, None, Some(suspension_id), None],
                });
            }
            player_comp.energy_capacity += capacity!(part_type);
        }
    }
    ret
}

fn construct_save_data(
    attach: Attach,
    attached_query: &Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
) -> Vec<Option<SaveModule>> {
    let mut modules = vec![None, None, None, None];
    for (i, child) in attach.children.iter().enumerate() {
        if let Some(child) = child {
            let (_, part_type, _, attach, _, _, _, _) = attached_query.get(*child).unwrap();
            if *part_type == PartType::LandingThrusterSuspension {
                continue;
            }
            let child_save_module = construct_save_data(attach.clone(), attached_query);
            modules[i] = Some(SaveModule {
                part_type: (*part_type).into(),
                children: child_save_module,
            });
        }
    }
    modules
}

fn detach_recursive(
    commands: &mut Commands,
    this: Entity,
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
) -> u32 {
    let mut energy = 0;
    let f_attach = if attached_query.contains(this) {
        attached_query.get(this).unwrap().3.clone()
    } else {
        player_query.get(this).unwrap().4.clone()
    };
    for child in f_attach.children.iter().flatten() {
        energy += detach_recursive(commands, *child, attached_query, player_query);
    }
    let (entity, part_type, attach, mut flags) = if attached_query.contains(this) {
        let (entity, part_type, _, attach, _, _, _, flags) = attached_query.get_mut(this).unwrap();
        (entity, part_type, attach, flags)
    } else {
        let (entity, _, _, _, attach, part_flags) = player_query.get_mut(this).unwrap();
        (entity, &PartType::Hearty, attach, part_flags)
    };
    energy += capacity!(*part_type);
    commands.entity(entity).remove::<Attach>();
    flags.attached = false;
    if *part_type == PartType::LandingThrusterSuspension {
        let parent = attach.parent.unwrap();
        let parent_attach = attached_query.get(parent).unwrap().3;
        commands.entity(parent).insert(LooseAttach {
            children: parent_attach.children,
        });
    } else {
        commands.entity(entity).remove::<ImpulseJoint>();
    }
    let parent = f_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;
                }
            }
        }
    } else if player_query.contains(parent) {
        let mut parent_attach = player_query.get_mut(parent).unwrap().4;
        for (i, child) in parent_attach.children.clone().iter().enumerate() {
            if let Some(child) = child {
                if *child == entity {
                    parent_attach.children[i] = None;
                }
            }
        }
    }
    energy
}

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

    let attach = if attached_query.contains(this) {
        attached_query.get(this).unwrap().3
    } else {
        player_query.get(this).unwrap().4
    };
    for this in attach.clone().children.iter().flatten() {
        let module = part_query.get(select).unwrap();
        let part_type = *module.1;
        ret |= if part_type != PartType::LandingThrusterSuspension {
            attach_on_module_tree(
                x,
                y,
                commands,
                *this,
                select,
                player_id,
                attached_query,
                part_query,
                player_query,
            )
        } else {
            false
        };
    }
    let is_player = player_query.contains(this);
    let (entity, transform, mut attach, velocity, can_attach) = if attached_query.contains(this) {
        let (entity, _, transform, attach, velocity, can_attach, _, _) =
            attached_query.get_mut(this).unwrap();
        (entity, *transform, attach, velocity, can_attach)
    } else {
        let (entity, _, transform, velocity, attach, _) = player_query.get_mut(this).unwrap();
        (entity, *transform, attach, velocity, Some(&CanAttach(15)))
    };

    let p_pos = transform.translation;
    let (rel_x, rel_y) = (p_pos.x - x, p_pos.y - y);
    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 = match can_attach {
        Some(s) => s.0,
        None => return false,
    };
    let mut attachment_slot = 5;
    let mut offset = Vec2::ZERO;
    let mut angle_offset = 0.;

    if attach.children[2].is_none() // top
        && attachable & 4 != 0
        && -(rel_x.abs()) + rel_y > 0.0
        && rel_x.abs() + rel_y < 1.0
    {
        attachment_slot = 2;
        offset = Vec2::new(0., -1.06);
        angle_offset = 0.;
    } else if attach.children[0].is_none() // bottom
        && rel_x.abs() + rel_y < 0.0
        && -(rel_x.abs()) + rel_y > -1.0
    {
        attachment_slot = 0;
        offset = Vec2::new(0., 1.06);
        angle_offset = PI;
    } else if attach.children[1].is_none() // left
        && rel_x + rel_y.abs() < 0.0
        && rel_x - rel_y.abs() > -1.0
    {
        attachment_slot = 1;
        offset = Vec2::new(1.06, 0.);
        angle_offset = PI / 2.;
    } else if attach.children[3].is_none() // right
        && rel_x - rel_y.abs() > 0.0
        && rel_x + rel_y.abs() < 1.0
    {
        attachment_slot = 3;
        offset = Vec2::new(-1.06, 0.);
        angle_offset = -PI / 2.;
    }
    let new_transform = Transform::from_xyz(
        p_pos.x + offset.x * angle.cos() - offset.y * angle.sin(),
        p_pos.y + offset.x * angle.sin() + offset.y * angle.cos(),
        0.,
    )
    .with_rotation(Quat::from_euler(
        EulerRot::ZYX,
        angle + angle_offset,
        0.,
        0.,
    ));
    if attachment_slot != 5 {
        let associated_player = if is_player {
            Some(entity)
        } else {
            attach.associated_player
        };
        *module.2 = new_transform;
        module.3.linvel = velocity.linvel;
        let joint = FixedJointBuilder::new()
            .local_anchor1(offset)
            .local_basis1(angle_offset);
        let mut children = [None, None, None, None];
        if let Some(loose_attach) = module.4 {
            if *module.1 == PartType::LandingThruster {
                commands
                    .entity(loose_attach.children[2].unwrap())
                    .insert(Attach {
                        associated_player,
                        parent: Some(module.0),
                        children: [None, None, None, None],
                    });
            }
            children = loose_attach.children;
            commands.entity(entity).remove::<LooseAttach>();
        }
        let mut module_entity = commands.entity(module.0);
        module_entity.insert(ImpulseJoint::new(entity, joint));
        module_entity.insert(Attach {
            associated_player,
            parent: Some(entity),
            children,
        });
        module_entity.insert(CanAttach(15));
        attach.children[attachment_slot] = Some(module.0);
        module.5.attached = true;
        player_query.get_mut(player_id).unwrap().1.energy_capacity += capacity!(*module.1);
        if *module.1 == PartType::LandingThruster {
            let loose_attach = module.4.unwrap().clone();
            let mut transform = part_query
                .get_mut(loose_attach.children[2].unwrap())
                .unwrap()
                .2;
            *transform = new_transform;
        }
        ret = true;
    }

    ret
}

fn convert_modules(
    mut commands: Commands,
    rapier_context: Res<RapierContext>,
    planet_query: Query<(Entity, &PlanetType, &Children)>,
    mut player_query: Query<(&Attach, &mut Player)>,
    mut attached_query: Query<
        (
            Entity,
            &mut PartType,
            &mut Attach,
            &mut AdditionalMassProperties,
            &Children,
            &Transform,
            &PartFlags,
        ),
        Without<Player>,
    >,
    mut collider_query: Query<
        (&mut Collider, &mut Transform, &Parent),
        (Without<Player>, Without<Attach>),
    >,
    mut packet_send: EventWriter<WsEvent>,
) {
    for (_planet_entity, planet_type, children) in &planet_query {
        for (entity1, entity2, intersecting) in
            rapier_context.intersection_pairs_with(*children.first().unwrap())
        {
            if intersecting {
                let other = if *children.first().unwrap() == entity1 {
                    entity2
                } else {
                    entity1
                };
                let player_entity = if player_query.contains(other) {
                    other
                } else if attached_query.contains(other) {
                    attached_query
                        .get(other)
                        .unwrap()
                        .2
                        .associated_player
                        .unwrap()
                } else if collider_query.contains(other) {
                    let parent = collider_query.get(other).unwrap().2.get();
                    if attached_query.contains(parent) {
                        attached_query
                            .get(parent)
                            .unwrap()
                            .2
                            .associated_player
                            .unwrap()
                    } else {
                        continue;
                    }
                } else {
                    continue;
                };
                let mut player = player_query.get_mut(player_entity).unwrap();
                let increase_capacity_by = convert_modules_recursive(
                    &mut commands,
                    *planet_type,
                    player.0.clone(),
                    &mut attached_query,
                    &mut collider_query,
                    &mut packet_send,
                );
                player.1.energy_capacity += increase_capacity_by;
                player.1.energy = player.1.energy_capacity;
            }
        }
    }
}

fn convert_modules_recursive(
    commands: &mut Commands,
    planet_type: PlanetType,
    attach: Attach,
    attached_query: &mut Query<
        (
            Entity,
            &mut PartType,
            &mut Attach,
            &mut AdditionalMassProperties,
            &Children,
            &Transform,
            &PartFlags,
        ),
        Without<Player>,
    >,
    collider_query: &mut Query<
        (&mut Collider, &mut Transform, &Parent),
        (Without<Player>, Without<Attach>),
    >,
    packet_send: &mut EventWriter<WsEvent>,
) -> u32 {
    let mut increase_capacity_by = 0;
    for child in attach.children.iter().flatten() {
        let (
            module_entity,
            mut part_type,
            mut attach,
            mut mass_prop,
            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;
                    *mass_prop = AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: Vec2::new(0.0, 0.0),
                        mass: part!(PartType::Hub).mass,
                        principal_inertia: 7.5,
                    });
                    let (mut collider, mut transform, _) =
                        collider_query.get_mut(*children.first().unwrap()).unwrap();
                    *collider = Collider::cuboid(0.5, 0.5);
                    *transform = Transform::from_xyz(0., 0., 0.);
                    commands
                        .get_entity(module_entity)
                        .unwrap()
                        .remove::<CanAttach>();
                    commands
                        .get_entity(module_entity)
                        .unwrap()
                        .insert(CanAttach(15));

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

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

                    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;
                    *mass_prop = AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: Vec2::new(0.0, 0.0),
                        mass: part!(PartType::LandingThruster).mass,
                        principal_inertia: 7.5,
                    });
                    let (mut collider, mut transform, _) =
                        collider_query.get_mut(*children.first().unwrap()).unwrap();
                    *collider = Collider::cuboid(0.5, 0.375);
                    *transform = Transform::from_xyz(0., 0.125, 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.))
                        .set_motor(0., 0., 3000., 3000.)
                        .limits([0., 1.])
                        .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(0.5, 0.02))
                                .insert(TransformBundle::from(Transform::from_xyz(0., -0.48, 0.)));
                        })
                        .insert(ImpulseJoint::new(module_entity, joint))
                        .insert(ExternalForce::default())
                        .insert(ExternalImpulse::default())
                        .insert(Velocity::default())
                        .insert(ReadMassProperties::default())
                        .insert(AdditionalMassProperties::MassProperties(MassProperties {
                            local_center_of_mass: vec2(0.0, 0.0),
                            mass: 0.00000000000001,
                            principal_inertia: 0.0000000000001,
                        }))
                        .insert(Attach {
                            associated_player: attach.associated_player,
                            parent: Some(module_entity),
                            children: [None, None, None, None],
                        });
                    attach.children[2] = Some(suspension.id());

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

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

                    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 {
            increase_capacity_by += convert_modules_recursive(
                commands,
                planet_type,
                attach.clone(),
                attached_query,
                collider_query,
                packet_send,
            );
        }
    }
    increase_capacity_by
}

fn break_modules(
    mut commands: Commands,
    rapier_context: Res<RapierContext>,
    mut attached_query: Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    mut player_query: Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
) {
    let joints = rapier_context.entity2impulse_joint();
    let mut detach_list = Vec::new();
    for (entity, part_type, _, attach, _, _, _, mut flags) in &mut attached_query {
        if *part_type == PartType::LandingThrusterSuspension {
            continue;
        }
        let handle = joints.get(&entity).unwrap();
        let joint = rapier_context.impulse_joints.get(*handle).unwrap();
        if joint.impulses.magnitude() > 0.2 {
            flags.attached = false;
            detach_list.push((entity, *part_type, attach.clone()));
        }
    }
    for (entity, _part_type, attach) in detach_list {
        let lost_energy_capacity = detach_recursive(
            &mut commands,
            entity,
            &mut attached_query,
            &mut player_query,
        );
        let id = attach.associated_player.unwrap();
        let mut player = player_query.get_mut(id).unwrap().1;
        player.energy_capacity -= lost_energy_capacity;
        player.energy = std::cmp::min(player.energy, player.energy_capacity);
    }
}

fn save_eligibility(
    rapier_context: Res<RapierContext>,
    planet_query: Query<(Entity, &PlanetType, &Children)>,
    attached_query: Query<&Attach, (Without<PlanetType>, Without<Player>)>,
    collider_query: Query<(&Collider, &Parent), (Without<Player>, Without<Attach>)>,
    mut player_query: Query<&mut Player>,
    mut packet_send: EventWriter<WsEvent>,
) {
    let mut player_eligibilities = HashMap::new();
    for (_planet_entity, _planet_type, children) in &planet_query {
        for (entity1, entity2, intersecting) in
            rapier_context.intersection_pairs_with(*children.first().unwrap())
        {
            if intersecting {
                let other = if *children.first().unwrap() == entity1 {
                    entity2
                } else {
                    entity1
                };
                let player_entity = if player_query.contains(other) {
                    other
                } else if attached_query.contains(other) {
                    attached_query
                        .get(other)
                        .unwrap()
                        .associated_player
                        .unwrap()
                } else if collider_query.contains(other) {
                    let parent = collider_query.get(other).unwrap().1.get();
                    if attached_query.contains(parent) {
                        attached_query
                            .get(parent)
                            .unwrap()
                            .associated_player
                            .unwrap()
                    } else {
                        continue;
                    }
                } else {
                    continue;
                };
                let player = player_query.get(player_entity).unwrap();
                if !player.save_eligibility {
                    if player_eligibilities.contains_key(&player_entity) {
                        player_eligibilities.remove(&player_entity);
                    }
                    player_eligibilities.insert(player_entity, true);
                }
            } else {
                let other = if *children.first().unwrap() == entity1 {
                    entity2
                } else {
                    entity1
                };
                if player_query.contains(other) {
                    let mut player = player_query.get_mut(other).unwrap();
                    if player.save_eligibility
                        && !(player_eligibilities.contains_key(&other)
                            && *player_eligibilities.get(&other).unwrap())
                    {
                        player.save_eligibility = false;
                        player_eligibilities.insert(other, false);
                    }
                }
            }
        }
    }
    for (other, eligible) in player_eligibilities.iter() {
        let mut player = player_query.get_mut(*other).unwrap();
        player.save_eligibility = *eligible;
        let packet = Packet::SaveEligibility {
            eligible: *eligible,
        };

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

fn on_close(
    player_query: Query<(Entity, &Player, &Attach)>,
    attached_query: Query<&Attach, With<PartType>>,


@@ 1769,35 764,6 @@ fn on_close(
    }
}

fn despawn_module_tree(
    commands: &mut Commands,
    attach: &Attach,
    attached_query: &Query<&Attach, With<PartType>>,
    part_query: &Query<&PartType>,
    packets: &mut Vec<WsEvent>,
) {
    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);
    }
}

fn send_player_energy(player_query: Query<&Player>, mut packet_send: EventWriter<WsEvent>) {
    for player in &player_query {


@@ 1964,168 930,6 @@ fn player_input_update(
        }
    }
}
fn search_thrusters(
    input: Input,
    attach: Attach,
    p_transform: Transform,
    energy: &mut u32,
    attached_query: &mut Query<
        (&Attach, &PartType, &mut ExternalForce, &Transform),
        Without<Player>,
    >,
) {
    let p_angle = p_transform.rotation.to_euler(EulerRot::ZYX).0;
    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.;
        let mut energy_lose_by = 0;
        if *part_type == PartType::LandingThruster {
            force_mult = part!(PartType::LandingThruster).thruster_force;
            energy_lose_by = part!(PartType::LandingThruster).thruster_energy;
        }
        if input.up && 3. * PI / 4. < relative_angle && relative_angle < 5. * PI / 4. {
            let thruster_force = ExternalForce::at_point(
                Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                transform.translation.xy(),
                transform.translation.xy(),
            );
            force.force += thruster_force.force;
            force.torque += thruster_force.torque;
            *energy -= energy_lose_by;
        }
        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 * angle.sin(), force_mult * angle.cos()),
                transform.translation.xy(),
                transform.translation.xy(),
            );
            force.force += thruster_force.force;
            force.torque += thruster_force.torque;
            *energy -= energy_lose_by;
        }
        if input.left {
            if 3. * PI / 4. < relative_angle
                && relative_angle < 5. * PI / 4.
                && relative_pos.x > 0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if ((0. < relative_angle && relative_angle < PI / 4.)
                || (7. * PI / 4. < relative_angle && relative_angle < 2. * PI))
                && relative_pos.x < -0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if PI / 4. < relative_angle && relative_angle < 3. * PI / 4. && relative_pos.y < -0.48 {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if 5. * PI / 4. < relative_angle
                && relative_angle < 7. * PI / 4.
                && relative_pos.y > 0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
        }
        if input.right {
            if ((0. < relative_angle && relative_angle < PI / 4.)
                || (7. * PI / 4. < relative_angle && relative_angle < 2. * PI))
                && relative_pos.x > 0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if 3. * PI / 4. < relative_angle
                && relative_angle < 5. * PI / 4.
                && relative_pos.x < -0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if PI / 4. < relative_angle && relative_angle < 3. * PI / 4. && relative_pos.y > 0.48 {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if 5. * PI / 4. < relative_angle
                && relative_angle < 7. * PI / 4.
                && relative_pos.y < -0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
        }

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

fn gravity_update(
    mut part_query: Query<

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

#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
pub enum PartType {
    Placeholder,
    Hearty,
    Cargo,
    Hub,
    LandingThruster,
    LandingThrusterSuspension,
}
impl From<PartType> for c_PartType {
    fn from(value: PartType) -> Self {
        match value {
            PartType::Placeholder => c_PartType::Placeholder,
            PartType::Hearty => c_PartType::Hearty,
            PartType::Cargo => c_PartType::Cargo,
            PartType::Hub => c_PartType::Hub,
            PartType::LandingThruster => c_PartType::LandingThruster,
            PartType::LandingThrusterSuspension => c_PartType::LandingThrusterSuspension,
        }
    }
}
impl From<c_PartType> for PartType {
    fn from(value: c_PartType) -> Self {
        match value {
            c_PartType::Placeholder => PartType::Placeholder,
            c_PartType::Hearty => PartType::Hearty,
            c_PartType::Cargo => PartType::Cargo,
            c_PartType::Hub => PartType::Hub,
            c_PartType::LandingThruster => PartType::LandingThruster,
            c_PartType::LandingThrusterSuspension => PartType::LandingThrusterSuspension,
        }
    }
}

#[derive(Component, Clone, Debug)]
pub struct Attach {
    pub associated_player: Option<Entity>,
    pub parent: Option<Entity>,
    pub children: [Option<Entity>; 4],
}

#[derive(Component, Clone, Debug)]
pub struct LooseAttach {
    pub children: [Option<Entity>; 4],
}

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

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

#[derive(Bundle)]
pub struct PartBundle {
    pub transform: TransformBundle,
    pub part_type: PartType,
    pub flags: PartFlags,
}

#[derive(Resource)]
pub struct ModuleTimer(pub Timer);
impl ModuleTimer {
    pub fn new() -> Self {
        Self(Timer::from_seconds(0.3, TimerMode::Repeating))
    }
}
impl Default for ModuleTimer {
    fn default() -> Self {
        Self::new()
    }
}


A server/src/module/mod.rs => server/src/module/mod.rs +666 -0
@@ 0,0 1,666 @@
use std::f32::consts::PI;

use bevy::{math::vec2, prelude::*};
use bevy_rapier2d::prelude::*;
use component::*;
use rand::Rng;

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

pub mod thruster;
pub mod component;
pub mod save;

pub fn module_spawn(
    mut commands: Commands,
    time: Res<Time>,
    mut module_timer: ResMut<ModuleTimer>,
    part_query: Query<&PartType, Without<Attach>>,
    mut packet_send: EventWriter<WsEvent>,
    server_config: Res<StkConfig>,
) {
    if module_timer.0.tick(time.delta()).just_finished()
        && part_query.iter().count() < server_config.server.max_free_parts
    {
        let angle: f32 = {
            let mut rng = rand::thread_rng();
            rng.gen::<f32>() * std::f32::consts::PI * 2.
        };
        let mut transform = Transform::from_xyz(angle.cos() * 30.0, angle.sin() * 30.0, 0.0);
        transform.rotate_z(angle);
        let flags = PartFlags { attached: false };
        let mut entity = commands.spawn(PartBundle {
            part_type: PartType::Cargo,
            transform: TransformBundle::from(transform),
            flags,
        });
        entity
            .insert(RigidBody::Dynamic)
            .with_children(|children| {
                children
                    .spawn(Collider::cuboid(0.375, 0.46875))
                    .insert(TransformBundle::from(Transform::from_xyz(0., 0.03125, 0.)));
            })
            .insert(AdditionalMassProperties::MassProperties(MassProperties {
                local_center_of_mass: vec2(0.0, 0.0),
                mass: part!(PartType::Cargo).mass,
                principal_inertia: 7.5,
            }))
            .insert(ExternalForce::default())
            .insert(ExternalImpulse::default())
            .insert(Velocity::default())
            .insert(ReadMassProperties::default());

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

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

pub fn detach_recursive(
    commands: &mut Commands,
    this: Entity,
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
) -> u32 {
    let mut energy = 0;
    let f_attach = if attached_query.contains(this) {
        attached_query.get(this).unwrap().3.clone()
    } else {
        player_query.get(this).unwrap().4.clone()
    };
    for child in f_attach.children.iter().flatten() {
        energy += detach_recursive(commands, *child, attached_query, player_query);
    }
    let (entity, part_type, attach, mut flags) = if attached_query.contains(this) {
        let (entity, part_type, _, attach, _, _, _, flags) = attached_query.get_mut(this).unwrap();
        (entity, part_type, attach, flags)
    } else {
        let (entity, _, _, _, attach, part_flags) = player_query.get_mut(this).unwrap();
        (entity, &PartType::Hearty, attach, part_flags)
    };
    energy += capacity!(*part_type);
    commands.entity(entity).remove::<Attach>();
    flags.attached = false;
    if *part_type == PartType::LandingThrusterSuspension {
        let parent = attach.parent.unwrap();
        let parent_attach = attached_query.get(parent).unwrap().3;
        commands.entity(parent).insert(LooseAttach {
            children: parent_attach.children,
        });
    } else {
        commands.entity(entity).remove::<ImpulseJoint>();
    }
    let parent = f_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;
                }
            }
        }
    } else if player_query.contains(parent) {
        let mut parent_attach = player_query.get_mut(parent).unwrap().4;
        for (i, child) in parent_attach.children.clone().iter().enumerate() {
            if let Some(child) = child {
                if *child == entity {
                    parent_attach.children[i] = None;
                }
            }
        }
    }
    energy
}

pub fn despawn_module_tree(
    commands: &mut Commands,
    attach: &Attach,
    attached_query: &Query<&Attach, With<PartType>>,
    part_query: &Query<&PartType>,
    packets: &mut Vec<WsEvent>,
) {
    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);
    }
}

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

    let attach = if attached_query.contains(this) {
        attached_query.get(this).unwrap().3
    } else {
        player_query.get(this).unwrap().4
    };
    for this in attach.clone().children.iter().flatten() {
        let module = part_query.get(select).unwrap();
        let part_type = *module.1;
        ret |= if part_type != PartType::LandingThrusterSuspension {
            attach_on_module_tree(
                x,
                y,
                commands,
                *this,
                select,
                player_id,
                attached_query,
                part_query,
                player_query,
            )
        } else {
            false
        };
    }
    let is_player = player_query.contains(this);
    let (entity, transform, mut attach, velocity, can_attach) = if attached_query.contains(this) {
        let (entity, _, transform, attach, velocity, can_attach, _, _) =
            attached_query.get_mut(this).unwrap();
        (entity, *transform, attach, velocity, can_attach)
    } else {
        let (entity, _, transform, velocity, attach, _) = player_query.get_mut(this).unwrap();
        (entity, *transform, attach, velocity, Some(&CanAttach(15)))
    };

    let p_pos = transform.translation;
    let (rel_x, rel_y) = (p_pos.x - x, p_pos.y - y);
    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 = match can_attach {
        Some(s) => s.0,
        None => return false,
    };
    let mut attachment_slot = 5;
    let mut offset = Vec2::ZERO;
    let mut angle_offset = 0.;

    if attach.children[2].is_none() // top
        && attachable & 4 != 0
        && -(rel_x.abs()) + rel_y > 0.0
        && rel_x.abs() + rel_y < 1.0
    {
        attachment_slot = 2;
        offset = Vec2::new(0., -1.06);
        angle_offset = 0.;
    } else if attach.children[0].is_none() // bottom
        && rel_x.abs() + rel_y < 0.0
        && -(rel_x.abs()) + rel_y > -1.0
    {
        attachment_slot = 0;
        offset = Vec2::new(0., 1.06);
        angle_offset = PI;
    } else if attach.children[1].is_none() // left
        && rel_x + rel_y.abs() < 0.0
        && rel_x - rel_y.abs() > -1.0
    {
        attachment_slot = 1;
        offset = Vec2::new(1.06, 0.);
        angle_offset = PI / 2.;
    } else if attach.children[3].is_none() // right
        && rel_x - rel_y.abs() > 0.0
        && rel_x + rel_y.abs() < 1.0
    {
        attachment_slot = 3;
        offset = Vec2::new(-1.06, 0.);
        angle_offset = -PI / 2.;
    }
    let new_transform = Transform::from_xyz(
        p_pos.x + offset.x * angle.cos() - offset.y * angle.sin(),
        p_pos.y + offset.x * angle.sin() + offset.y * angle.cos(),
        0.,
    )
    .with_rotation(Quat::from_euler(
        EulerRot::ZYX,
        angle + angle_offset,
        0.,
        0.,
    ));
    if attachment_slot != 5 {
        let associated_player = if is_player {
            Some(entity)
        } else {
            attach.associated_player
        };
        *module.2 = new_transform;
        module.3.linvel = velocity.linvel;
        let joint = FixedJointBuilder::new()
            .local_anchor1(offset)
            .local_basis1(angle_offset);
        let mut children = [None, None, None, None];
        if let Some(loose_attach) = module.4 {
            if *module.1 == PartType::LandingThruster {
                commands
                    .entity(loose_attach.children[2].unwrap())
                    .insert(Attach {
                        associated_player,
                        parent: Some(module.0),
                        children: [None, None, None, None],
                    });
            }
            children = loose_attach.children;
            commands.entity(entity).remove::<LooseAttach>();
        }
        let mut module_entity = commands.entity(module.0);
        module_entity.insert(ImpulseJoint::new(entity, joint));
        module_entity.insert(Attach {
            associated_player,
            parent: Some(entity),
            children,
        });
        module_entity.insert(CanAttach(15));
        attach.children[attachment_slot] = Some(module.0);
        module.5.attached = true;
        player_query.get_mut(player_id).unwrap().1.energy_capacity += capacity!(*module.1);
        if *module.1 == PartType::LandingThruster {
            let loose_attach = module.4.unwrap().clone();
            let mut transform = part_query
                .get_mut(loose_attach.children[2].unwrap())
                .unwrap()
                .2;
            *transform = new_transform;
        }
        ret = true;
    }

    ret
}

pub fn convert_modules(
    mut commands: Commands,
    rapier_context: Res<RapierContext>,
    planet_query: Query<(Entity, &PlanetType, &Children)>,
    mut player_query: Query<(&Attach, &mut Player)>,
    mut attached_query: Query<
        (
            Entity,
            &mut PartType,
            &mut Attach,
            &mut AdditionalMassProperties,
            &Children,
            &Transform,
            &PartFlags,
        ),
        Without<Player>,
    >,
    mut collider_query: Query<
        (&mut Collider, &mut Transform, &Parent),
        (Without<Player>, Without<Attach>),
    >,
    mut packet_send: EventWriter<WsEvent>,
) {
    for (_planet_entity, planet_type, children) in &planet_query {
        for (entity1, entity2, intersecting) in
            rapier_context.intersection_pairs_with(*children.first().unwrap())
        {
            if intersecting {
                let other = if *children.first().unwrap() == entity1 {
                    entity2
                } else {
                    entity1
                };
                let player_entity = if player_query.contains(other) {
                    other
                } else if attached_query.contains(other) {
                    attached_query
                        .get(other)
                        .unwrap()
                        .2
                        .associated_player
                        .unwrap()
                } else if collider_query.contains(other) {
                    let parent = collider_query.get(other).unwrap().2.get();
                    if attached_query.contains(parent) {
                        attached_query
                            .get(parent)
                            .unwrap()
                            .2
                            .associated_player
                            .unwrap()
                    } else {
                        continue;
                    }
                } else {
                    continue;
                };
                let mut player = player_query.get_mut(player_entity).unwrap();
                let increase_capacity_by = convert_modules_recursive(
                    &mut commands,
                    *planet_type,
                    player.0.clone(),
                    &mut attached_query,
                    &mut collider_query,
                    &mut packet_send,
                );
                player.1.energy_capacity += increase_capacity_by;
                player.1.energy = player.1.energy_capacity;
            }
        }
    }
}

fn convert_modules_recursive(
    commands: &mut Commands,
    planet_type: PlanetType,
    attach: Attach,
    attached_query: &mut Query<
        (
            Entity,
            &mut PartType,
            &mut Attach,
            &mut AdditionalMassProperties,
            &Children,
            &Transform,
            &PartFlags,
        ),
        Without<Player>,
    >,
    collider_query: &mut Query<
        (&mut Collider, &mut Transform, &Parent),
        (Without<Player>, Without<Attach>),
    >,
    packet_send: &mut EventWriter<WsEvent>,
) -> u32 {
    let mut increase_capacity_by = 0;
    for child in attach.children.iter().flatten() {
        let (
            module_entity,
            mut part_type,
            mut attach,
            mut mass_prop,
            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;
                    *mass_prop = AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: Vec2::new(0.0, 0.0),
                        mass: part!(PartType::Hub).mass,
                        principal_inertia: 7.5,
                    });
                    let (mut collider, mut transform, _) =
                        collider_query.get_mut(*children.first().unwrap()).unwrap();
                    *collider = Collider::cuboid(0.5, 0.5);
                    *transform = Transform::from_xyz(0., 0., 0.);
                    commands
                        .get_entity(module_entity)
                        .unwrap()
                        .remove::<CanAttach>();
                    commands
                        .get_entity(module_entity)
                        .unwrap()
                        .insert(CanAttach(15));

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

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

                    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;
                    *mass_prop = AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: Vec2::new(0.0, 0.0),
                        mass: part!(PartType::LandingThruster).mass,
                        principal_inertia: 7.5,
                    });
                    let (mut collider, mut transform, _) =
                        collider_query.get_mut(*children.first().unwrap()).unwrap();
                    *collider = Collider::cuboid(0.5, 0.375);
                    *transform = Transform::from_xyz(0., 0.125, 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.))
                        .set_motor(0., 0., 3000., 3000.)
                        .limits([0., 1.])
                        .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(0.5, 0.02))
                                .insert(TransformBundle::from(Transform::from_xyz(0., -0.48, 0.)));
                        })
                        .insert(ImpulseJoint::new(module_entity, joint))
                        .insert(ExternalForce::default())
                        .insert(ExternalImpulse::default())
                        .insert(Velocity::default())
                        .insert(ReadMassProperties::default())
                        .insert(AdditionalMassProperties::MassProperties(MassProperties {
                            local_center_of_mass: vec2(0.0, 0.0),
                            mass: 0.00000000000001,
                            principal_inertia: 0.0000000000001,
                        }))
                        .insert(Attach {
                            associated_player: attach.associated_player,
                            parent: Some(module_entity),
                            children: [None, None, None, None],
                        });
                    attach.children[2] = Some(suspension.id());

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

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

                    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 {
            increase_capacity_by += convert_modules_recursive(
                commands,
                planet_type,
                attach.clone(),
                attached_query,
                collider_query,
                packet_send,
            );
        }
    }
    increase_capacity_by
}

pub fn break_modules(
    mut commands: Commands,
    rapier_context: Res<RapierContext>,
    mut attached_query: Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    mut player_query: Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
) {
    let joints = rapier_context.entity2impulse_joint();
    let mut detach_list = Vec::new();
    for (entity, part_type, _, attach, _, _, _, mut flags) in &mut attached_query {
        if *part_type == PartType::LandingThrusterSuspension {
            continue;
        }
        let handle = joints.get(&entity).unwrap();
        let joint = rapier_context.impulse_joints.get(*handle).unwrap();
        if joint.impulses.magnitude() > 0.2 {
            flags.attached = false;
            detach_list.push((entity, *part_type, attach.clone()));
        }
    }
    for (entity, _part_type, attach) in detach_list {
        let lost_energy_capacity = detach_recursive(
            &mut commands,
            entity,
            &mut attached_query,
            &mut player_query,
        );
        let id = attach.associated_player.unwrap();
        let mut player = player_query.get_mut(id).unwrap().1;
        player.energy_capacity -= lost_energy_capacity;
        player.energy = std::cmp::min(player.energy, player.energy_capacity);
    }
}

A server/src/module/save.rs => server/src/module/save.rs +343 -0
@@ 0,0 1,343 @@
use std::{collections::HashMap, f32::consts::PI};

use bevy::{math::vec2, prelude::*};
use bevy_rapier2d::prelude::*;
use starkingdoms_common::SaveModule;

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

pub fn load_savefile(
    commands: &mut Commands,
    transform: Transform,
    player_id: Entity,
    parent: Entity,
    children: Vec<Option<SaveModule>>,
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    part_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Velocity,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
    player_comp: &mut Player,
) -> [Option<Entity>; 4] {
    let mut ret = [None, None, None, None];
    for (i, child) in children.iter().enumerate() {
        if let Some(child) = child {
            let part_type = PartType::from(child.part_type);
            //let attachable = can_attach != None;

            let p_pos = transform.translation;
            let angle = transform.rotation.to_euler(EulerRot::ZYX).0;
            let mut offset = Vec2::ZERO;
            let mut angle_offset = 0.;
            if i == 2 {
                //offset = Vec2::new(53., -53.);
                offset = Vec2::new(0., -1.06);
                angle_offset = 0.;
            } else if i == 0 {
                //offset = Vec2::new(-53., 53.);
                offset = Vec2::new(0., 1.06);
                angle_offset = PI;
            } else if i == 1 {
                //offset = Vec2::new(53., 53.);
                offset = Vec2::new(1.06, 0.);
                angle_offset = PI / 2.;
            } else if i == 3 {
                //offset = Vec2::new(-53., 53.);
                offset = Vec2::new(-1.06, 0.);
                angle_offset = -PI / 2.;
            }

            let transform = Transform::from_xyz(
                p_pos.x + offset.x * angle.cos() - offset.y * angle.sin(),
                p_pos.y + offset.x * angle.sin() + offset.y * angle.cos(),
                0.,
            )
            .with_rotation(Quat::from_euler(
                EulerRot::ZYX,
                angle + angle_offset,
                0.,
                0.,
            ));
            let module_id = {
                let module = commands.spawn(PartBundle {
                    transform: TransformBundle::from(transform),
                    part_type: child.part_type.into(),
                    flags: PartFlags { attached: true },
                });
                module.id()
            };

            let children = if part_type != PartType::LandingThruster {
                load_savefile(
                    commands,
                    transform,
                    player_id,
                    module_id,
                    child.children.clone(),
                    attached_query,
                    part_query,
                    player_query,
                    player_comp,
                )
            } else {
                [None, None, None, None]
            };

            let mut module = commands.entity(module_id);

            module.insert(Attach {
                associated_player: Some(player_id),
                parent: Some(parent),
                children,
            });
            //module.5.attached = true;
            ret[i] = Some(module.id());

            module
                .insert(RigidBody::Dynamic)
                .with_children(|children| {
                    children
                        .spawn(if part_type == PartType::Cargo {
                            Collider::cuboid(0.375, 0.46875)
                        } else if part_type == PartType::Hub {
                            Collider::cuboid(0.5, 0.5)
                        } else if part_type == PartType::LandingThruster {
                            Collider::cuboid(0.5, 0.375)
                        } else {
                            Collider::cuboid(0.5, 0.5)
                        })
                        .insert(TransformBundle::from(Transform::from_xyz(
                            0.,
                            if part_type == PartType::Cargo {
                                0.03125
                            } else if part_type == PartType::Hub {
                                0.
                            } else if part_type == PartType::LandingThruster {
                                0.125
                            } else {
                                0.
                            },
                            0.,
                        )));
                })
                .insert(AdditionalMassProperties::MassProperties(MassProperties {
                    local_center_of_mass: vec2(0.0, 0.0),
                    mass: mass!(part_type),
                    principal_inertia: 7.5,
                }))
                .insert(ExternalForce::default())
                .insert(ExternalImpulse::default())
                .insert(Velocity::default())
                .insert(ReadMassProperties::default());
            if part_type == PartType::Hub {
                module.insert(CanAttach(15));
            }

            let joint = FixedJointBuilder::new()
                .local_anchor1(vec2(offset.x, offset.y))
                .local_basis1(angle_offset);

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

            if part_type == PartType::LandingThruster {
                let joint = PrismaticJointBuilder::new(Vec2::new(0., 1.))
                    .set_motor(0., 0., 3000., 3000.)
                    .limits([0., 1.])
                    .build();
                let mut suspension = commands.spawn(PartBundle {
                    transform: TransformBundle::from(
                        Transform::from_xyz(
                            p_pos.x + offset.x * angle.cos() - offset.y * angle.sin(),
                            p_pos.y + offset.x * angle.sin() + offset.y * angle.cos(),
                            0.,
                        )
                        .with_rotation(Quat::from_euler(
                            EulerRot::ZYX,
                            angle + angle_offset,
                            0.,
                            0.,
                        )),
                    ),
                    part_type: PartType::LandingThrusterSuspension,
                    flags: PartFlags { attached: true },
                });
                suspension
                    .insert(RigidBody::Dynamic)
                    .with_children(|children| {
                        children
                            .spawn(Collider::cuboid(0.5, 0.02))
                            .insert(TransformBundle::from(Transform::from_xyz(0., -0.48, 0.)));
                    })
                    .insert(ImpulseJoint::new(module_id, joint))
                    .insert(ExternalForce::default())
                    .insert(ExternalImpulse::default())
                    .insert(Velocity::default())
                    .insert(ReadMassProperties::default())
                    .insert(AdditionalMassProperties::MassProperties(MassProperties {
                        local_center_of_mass: vec2(0.0, 0.0),
                        mass: 0.00000000000001,
                        principal_inertia: 0.0000000000001,
                    }))
                    .insert(Attach {
                        associated_player: Some(player_id),
                        parent: Some(module_id),
                        children: [None, None, None, None],
                    });
                let suspension_id = suspension.id();
                let mut module = commands.entity(module_id);
                module.remove::<Attach>();
                module.insert(Attach {
                    associated_player: Some(player_id),
                    parent: Some(parent),
                    children: [None, None, Some(suspension_id), None],
                });
            }
            player_comp.energy_capacity += capacity!(part_type);
        }
    }
    ret
}

pub fn construct_save_data(
    attach: Attach,
    attached_query: &Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
) -> Vec<Option<SaveModule>> {
    let mut modules = vec![None, None, None, None];
    for (i, child) in attach.children.iter().enumerate() {
        if let Some(child) = child {
            let (_, part_type, _, attach, _, _, _, _) = attached_query.get(*child).unwrap();
            if *part_type == PartType::LandingThrusterSuspension {
                continue;
            }
            let child_save_module = construct_save_data(attach.clone(), attached_query);
            modules[i] = Some(SaveModule {
                part_type: (*part_type).into(),
                children: child_save_module,
            });
        }
    }
    modules
}

pub fn save_eligibility(
    rapier_context: Res<RapierContext>,
    planet_query: Query<(Entity, &PlanetType, &Children)>,
    attached_query: Query<&Attach, (Without<PlanetType>, Without<Player>)>,
    collider_query: Query<(&Collider, &Parent), (Without<Player>, Without<Attach>)>,
    mut player_query: Query<&mut Player>,
    mut packet_send: EventWriter<WsEvent>,
) {
    let mut player_eligibilities = HashMap::new();
    for (_planet_entity, _planet_type, children) in &planet_query {
        for (entity1, entity2, intersecting) in
            rapier_context.intersection_pairs_with(*children.first().unwrap())
        {
            if intersecting {
                let other = if *children.first().unwrap() == entity1 {
                    entity2
                } else {
                    entity1
                };
                let player_entity = if player_query.contains(other) {
                    other
                } else if attached_query.contains(other) {
                    attached_query
                        .get(other)
                        .unwrap()
                        .associated_player
                        .unwrap()
                } else if collider_query.contains(other) {
                    let parent = collider_query.get(other).unwrap().1.get();
                    if attached_query.contains(parent) {
                        attached_query
                            .get(parent)
                            .unwrap()
                            .associated_player
                            .unwrap()
                    } else {
                        continue;
                    }
                } else {
                    continue;
                };
                let player = player_query.get(player_entity).unwrap();
                if !player.save_eligibility {
                    if player_eligibilities.contains_key(&player_entity) {
                        player_eligibilities.remove(&player_entity);
                    }
                    player_eligibilities.insert(player_entity, true);
                }
            } else {
                let other = if *children.first().unwrap() == entity1 {
                    entity2
                } else {
                    entity1
                };
                if player_query.contains(other) {
                    let mut player = player_query.get_mut(other).unwrap();
                    if player.save_eligibility
                        && !(player_eligibilities.contains_key(&other)
                            && *player_eligibilities.get(&other).unwrap())
                    {
                        player.save_eligibility = false;
                        player_eligibilities.insert(other, false);
                    }
                }
            }
        }
    }
    for (other, eligible) in player_eligibilities.iter() {
        let mut player = player_query.get_mut(*other).unwrap();
        player.save_eligibility = *eligible;
        let packet = Packet::SaveEligibility {
            eligible: *eligible,
        };

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

A server/src/module/thruster.rs => server/src/module/thruster.rs +168 -0
@@ 0,0 1,168 @@
use std::f32::consts::PI;

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

pub fn search_thrusters(
    input: Input,
    attach: Attach,
    p_transform: Transform,
    energy: &mut u32,
    attached_query: &mut Query<
        (&Attach, &PartType, &mut ExternalForce, &Transform),
        Without<Player>,
    >,
) {
    let p_angle = p_transform.rotation.to_euler(EulerRot::ZYX).0;
    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.;
        let mut energy_lose_by = 0;
        if *part_type == PartType::LandingThruster {
            force_mult = part!(PartType::LandingThruster).thruster_force;
            energy_lose_by = part!(PartType::LandingThruster).thruster_energy;
        }
        if input.up && 3. * PI / 4. < relative_angle && relative_angle < 5. * PI / 4. {
            let thruster_force = ExternalForce::at_point(
                Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                transform.translation.xy(),
                transform.translation.xy(),
            );
            force.force += thruster_force.force;
            force.torque += thruster_force.torque;
            *energy -= energy_lose_by;
        }
        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 * angle.sin(), force_mult * angle.cos()),
                transform.translation.xy(),
                transform.translation.xy(),
            );
            force.force += thruster_force.force;
            force.torque += thruster_force.torque;
            *energy -= energy_lose_by;
        }
        if input.left {
            if 3. * PI / 4. < relative_angle
                && relative_angle < 5. * PI / 4.
                && relative_pos.x > 0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if ((0. < relative_angle && relative_angle < PI / 4.)
                || (7. * PI / 4. < relative_angle && relative_angle < 2. * PI))
                && relative_pos.x < -0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if PI / 4. < relative_angle && relative_angle < 3. * PI / 4. && relative_pos.y < -0.48 {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if 5. * PI / 4. < relative_angle
                && relative_angle < 7. * PI / 4.
                && relative_pos.y > 0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
        }
        if input.right {
            if ((0. < relative_angle && relative_angle < PI / 4.)
                || (7. * PI / 4. < relative_angle && relative_angle < 2. * PI))
                && relative_pos.x > 0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if 3. * PI / 4. < relative_angle
                && relative_angle < 5. * PI / 4.
                && relative_pos.x < -0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if PI / 4. < relative_angle && relative_angle < 3. * PI / 4. && relative_pos.y > 0.48 {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
            if 5. * PI / 4. < relative_angle
                && relative_angle < 7. * PI / 4.
                && relative_pos.y < -0.48
            {
                let thruster_force = ExternalForce::at_point(
                    Vec2::new(-force_mult * angle.sin(), force_mult * angle.cos()),
                    transform.translation.xy(),
                    transform.translation.xy(),
                );
                force.force += thruster_force.force;
                force.torque += thruster_force.torque;
                *energy -= energy_lose_by;
            }
        }

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

M server/src/packet.rs => server/src/packet.rs +2 -1
@@ 14,10 14,11 @@ use std::fmt::{Display, Formatter};
//
//     You should have received a copy of the GNU Affero General Public License
//     along with this program.  If not, see <https://www.gnu.org/licenses/>.
use crate::component::{PartType, PlanetType};
use serde::{Deserialize, Serialize};
use tungstenite::Message;

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

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

D server/src/part.rs => server/src/part.rs +0 -32
@@ 1,32 0,0 @@
#[macro_export]
macro_rules! mass {
    ($p:expr) => {
        match $crate::parts_config().parts.get(&$p) {
            Some(v) => v.mass,
            None => 1.0,
        }
    };
}

#[macro_export]
macro_rules! capacity {
    ($p:expr) => {
        match $crate::parts_config().parts.get(&$p) {
            Some(v) => v.energy_capacity,
            None => 0,
        }
    };
}

#[macro_export]
macro_rules! planet {
    ($t:expr) => {
        $crate::planets_config().planets.get(&$t).unwrap()
    };
}
#[macro_export]
macro_rules! part {
    ($t:expr) => {
        $crate::parts_config().parts.get(&$t).unwrap()
    };
}

A server/src/planet.rs => server/src/planet.rs +76 -0
@@ 0,0 1,76 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use serde::{Deserialize, Serialize};

use crate::planet;

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

#[derive(Bundle)]
pub struct PlanetBundle {
    pub planet_type: PlanetType,
    pub transform: TransformBundle,
}

pub fn spawn_planets(mut commands: Commands) {
    info!("Spawning planets");
    let earth_pos = Transform::from_xyz(0.0, 0.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Earth,
            transform: TransformBundle::from(earth_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Earth).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Earth).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Earth).size + 0.3))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })
        .insert(RigidBody::Fixed);
    let moon_pos = Transform::from_xyz(50.0, 20.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Moon,
            transform: TransformBundle::from(moon_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Moon).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Moon).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Moon).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })
        .insert(RigidBody::Fixed);
    let mars_pos = Transform::from_xyz(-50.0, 300.0, 0.0);
    commands
        .spawn(PlanetBundle {
            planet_type: PlanetType::Mars,
            transform: TransformBundle::from(mars_pos),
        })
        .insert(Collider::ball(planet!(PlanetType::Mars).size))
        .insert(AdditionalMassProperties::Mass(
            planet!(PlanetType::Mars).mass,
        ))
        .insert(ReadMassProperties::default())
        .with_children(|children| {
            children
                .spawn(Collider::ball(planet!(PlanetType::Mars).size + 0.1))
                .insert(ActiveEvents::COLLISION_EVENTS)
                .insert(Sensor);
        })
        .insert(RigidBody::Fixed);
}

A server/src/player/mod.rs => server/src/player/mod.rs +0 -0