// StarKingdoms.IO, a browser game about drifting through space
// Copyright (C) 2023 ghostly_zsh, TerraMaster85, core
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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/>.
#![allow(clippy::type_complexity)] // bevy :(
#![allow(clippy::too_many_arguments)] // bevy :(
#![allow(clippy::only_used_in_recursion)] // todo: remove this
use std::net::IpAddr;
use crate::mathutil::rot2d;
use bevy::log::Level;
use bevy::log::LogPlugin;
use bevy::math::{vec2, vec3};
use bevy::{
app::ScheduleRunnerPlugin, ecs::event::ManualEventReader, prelude::*, time::TimePlugin,
};
use bevy_rapier2d::prelude::*;
use bevy_tungstenite_stk::{StkTungsteniteServerConfig, StkTungsteniteServerPlugin, WsEvent};
use component::Input;
use component::*;
use packet::*;
use rand::Rng;
use starkingdoms_common::SaveModule;
use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData};
use std::f32::consts::PI;
use std::str::FromStr;
use std::time::Duration;
pub mod component;
pub mod macros;
pub mod mathutil;
pub mod packet;
const CLIENT_SCALE: f32 = 50.;
const EARTH_SIZE: f32 = 20.0;
const MOON_SIZE: f32 = EARTH_SIZE / 4.;
const MARS_SIZE: f32 = EARTH_SIZE / 2.;
const EARTH_MASS: f32 = 10000.0;
const MOON_MASS: f32 = EARTH_MASS / 30.;
const MARS_MASS: f32 = EARTH_MASS / 8.;
const GRAVITY: f32 = 0.0002;
const PART_HALF_SIZE: f32 = 25.0;
const HEARTY_THRUSTER_FORCE: f32 = 0.3;
const LANDING_THRUSTER_FORCE: f32 = 5.;
const HEARTY_MASS: f32 = 1.;
const CARGO_MASS: f32 = 0.5;
const HUB_MASS: f32 = 1.;
const LANDING_THRUSTER_MASS: f32 = 0.9;
// maybe make this only cargo modules later
const FREE_MODULE_CAP: usize = 30;
fn main() {
// read the key in
let key = std::fs::read_to_string("/etc/starkingdoms/app_key").unwrap();
App::new()
.insert_resource(AppKeys {
app_key: key.into_bytes(),
})
.insert_resource(StkTungsteniteServerConfig {
addr: IpAddr::from_str("0.0.0.0").unwrap(),
port: 3000,
})
.add_plugins(TaskPoolPlugin::default())
.add_plugins(TypeRegistrationPlugin)
.add_plugins(FrameCountPlugin)
.add_plugins(TimePlugin)
.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_millis(1)))
.add_plugins(LogPlugin {
level: Level::DEBUG,
filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
})
.insert_resource(RapierConfiguration {
gravity: Vect { x: 0.0, y: 0.0 },
..Default::default()
})
.init_resource::<ModuleTimer>()
.add_plugins(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(1.0))
.add_plugins(StkTungsteniteServerPlugin)
.add_systems(Startup, setup_integration_parameters)
.add_systems(Startup, spawn_planets)
.add_systems(FixedUpdate, module_spawn)
.add_systems(Update, on_message)
.add_systems(Update, on_close)
.add_systems(FixedUpdate, on_position_change)
.add_systems(
FixedUpdate,
(break_modules, gravity_update, player_input_update).chain(),
)
.add_systems(FixedUpdate, save_eligibility)
.add_systems(FixedUpdate, convert_modules)
//.insert_resource(Time::<Fixed>::from_seconds(1.0/1.0))
.run();
info!("Goodbye!");
}
fn setup_integration_parameters(mut context: ResMut<RapierContext>) {
context.integration_parameters.dt = 1.0 / 60.0;
context.integration_parameters.joint_erp = 0.2;
context.integration_parameters.erp = 0.5;
context.integration_parameters.max_stabilization_iterations = 16;
}
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(EARTH_SIZE))
.insert(AdditionalMassProperties::Mass(EARTH_MASS))
.insert(ReadMassProperties::default())
.with_children(|children| {
children
.spawn(Collider::ball(EARTH_SIZE + 0.3))
.insert(ActiveEvents::COLLISION_EVENTS)
.insert(Sensor);
})
.insert(RigidBody::Fixed);
let moon_pos = Transform::from_xyz(50.0, 0.0, 0.0);
commands
.spawn(PlanetBundle {
planet_type: PlanetType::Moon,
transform: TransformBundle::from(moon_pos),
})
.insert(Collider::ball(MOON_SIZE))
.insert(AdditionalMassProperties::Mass(MOON_MASS))
.insert(ReadMassProperties::default())
.with_children(|children| {
children
.spawn(Collider::ball(MOON_SIZE + 0.1))
.insert(ActiveEvents::COLLISION_EVENTS)
.insert(Sensor);
})
.insert(RigidBody::Fixed);
let mars_pos = Transform::from_xyz(50.0, 20.0, 0.0);
commands
.spawn(PlanetBundle {
planet_type: PlanetType::Mars,
transform: TransformBundle::from(mars_pos),
})
.insert(Collider::ball(MARS_SIZE))
.insert(AdditionalMassProperties::Mass(MARS_MASS))
.insert(ReadMassProperties::default())
.with_children(|children| {
children
.spawn(Collider::ball(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>,
) {
if module_timer.0.tick(time.delta()).just_finished() {
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);
if part_query.iter().count() < FREE_MODULE_CAP {
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: 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,
planet_query: Query<(Entity, &PlanetType, &Transform)>,
mut part_query: Query<
(
Entity,
&PartType,
&mut Transform,
&mut Velocity,
Option<&LooseAttach>,
&mut PartFlags,
),
(Without<PlanetType>, Without<Player>, Without<Attach>),
>,
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),
Without<PlanetType>,
>,
mut packet_recv: Local<ManualEventReader<WsEvent>>,
mut packet_event_send: ResMut<Events<WsEvent>>,
app_keys: Res<AppKeys>,
) {
let mut event_queue = Vec::new();
for ev in packet_recv.read(&packet_event_send) {
if let WsEvent::Recv { from, message } = ev {
let packet: Packet = err_or_cont!(message.try_into());
match packet {
Packet::ClientLogin {
username,
save,
jwt: _,
} => {
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 mut entity_id = commands.spawn((
PartBundle {
part_type: PartType::Hearty,
transform: TransformBundle::from(transform),
flags: PartFlags { attached: false },
},
Player {
addr: *from,
username: username.to_string(),
input: component::Input::default(),
selected: None,
save_eligibility: false,
},
));
entity_id
.insert(Collider::cuboid(
0.5,
0.5,
))
.insert(AdditionalMassProperties::MassProperties(MassProperties {
local_center_of_mass: vec2(0.0, 0.0),
mass: HEARTY_MASS,
principal_inertia: 7.5,
}))
.insert(ExternalImpulse {
impulse: Vec2::ZERO,
torque_impulse: 0.0,
})
.insert(ExternalForce::default())
.insert(ReadMassProperties::default())
.insert(Velocity::default())
.insert(RigidBody::Dynamic);
let id = entity_id.id().index();
let entity = entity_id.id();
let mut attach = Attach {
associated_player: None,
parent: None,
children: [None, None, None, None],
};
if let Some(save) = save {
// attempt to decode the savefile
if let Ok(savefile) = unpack_savefile(&app_keys.app_key, save) {
// HEY! GHOSTLY! THIS SAVE FILE IS VALID! PLEASE LOAD IT!
// THANKS!
let children = load_savefile(
&mut commands,
transform,
entity,
entity,
savefile.children,
&mut attached_query,
&mut part_query,
);
attach.children = children;
} else {
let packet = Packet::Message {
message_type: packet::MessageType::Error,
actor: "SERVER".to_string(),
content: "Savefile signature corrupted or inner data invalid. Save was not loaded. Contact StarKingdoms staff for assistance.".to_string(),
};
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
}
} else {
// nothing to do
}
let mut entity_id = commands.entity(entity);
entity_id.insert(attach);
// tell this player the planets
let mut planets = Vec::new();
for (entity, planet_type, transform) in planet_query.iter() {
let translation = transform.translation;
planets.push((
entity.index(),
Planet {
planet_type: *planet_type,
transform: proto_transform!(Transform::from_translation(
translation * CLIENT_SCALE
)),
radius: match *planet_type {
PlanetType::Earth => EARTH_SIZE * CLIENT_SCALE,
PlanetType::Moon => MOON_SIZE * CLIENT_SCALE,
PlanetType::Mars => MARS_SIZE * CLIENT_SCALE,
},
},
));
}
let packet = Packet::PlanetPositions { planets };
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
// tell the player already existing users
let mut players = Vec::new();
for (entity, player, _, _, _) in &player_query {
players.push((entity.index(), player.username.clone()));
}
let packet = Packet::PlayerList { players };
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
// tell other players that a player has spawned in
let packet = Packet::SpawnPlayer {
id,
username: username.to_string(),
};
event_queue.push(WsEvent::Broadcast {
message: packet.into(),
});
let packet = Packet::Message {
message_type: packet::MessageType::Server,
actor: "SERVER".to_string(),
content: format!("{} has joined the server!", username),
};
event_queue.push(WsEvent::Broadcast {
message: packet.into(),
});
// tell the player where parts are
let mut parts = Vec::new();
for (entity, part_type, transform, _, _, flags) in &part_query {
parts.push((
entity.index(),
Part {
part_type: *part_type,
transform: proto_transform!(Transform::from_translation(
transform.translation * CLIENT_SCALE
)),
flags: proto_part_flags!(flags),
},
));
}
for (entity, part_type, transform, _, _, _, _, flags) in &attached_query {
parts.push((
entity.index(),
Part {
part_type: *part_type,
transform: proto_transform!(Transform::from_translation(
transform.translation * CLIENT_SCALE
)),
flags: proto_part_flags!(flags),
},
));
}
parts.push((
id,
Part {
part_type: PartType::Hearty,
transform: proto_transform!(Transform::from_translation(
transform.translation
)
.with_rotation(transform.rotation)),
flags: ProtoPartFlags { attached: false },
},
));
let packet = Packet::PartPositions { parts };
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
// and send the welcome message :)
let packet = Packet::Message {
message_type: packet::MessageType::Server,
actor: "SERVER".to_string(),
content: "Welcome to StarKingdoms.IO! Have fun!".to_string(),
};
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
}
Packet::SendMessage { target, content } => {
// find our player
let mut player = None;
for (_, q_player, _, _, _) in &player_query {
if q_player.addr == *from {
player = Some(q_player);
}
}
let player = player.unwrap();
if let Some(target_username) = target {
let mut target_player = None;
for (_, q_player, _, _, _) in &player_query {
if q_player.username == target_username {
target_player = Some(q_player);
}
}
let target_player = target_player.unwrap();
let packet = Packet::Message {
message_type: packet::MessageType::Direct,
actor: player.username.clone(),
content,
};
event_queue.push(WsEvent::Send {
to: target_player.addr,
message: packet.clone().into(),
});
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
} else {
// send to general chat
let packet = Packet::Message {
message_type: packet::MessageType::Chat,
actor: player.username.clone(),
content,
};
event_queue.push(WsEvent::Broadcast {
message: packet.into(),
});
}
}
Packet::PlayerInput {
up,
down,
left,
right,
} => {
for (_, mut q_player, _, _, _) in &mut player_query {
if q_player.addr == *from {
q_player.input.up = up;
q_player.input.down = down;
q_player.input.left = left;
q_player.input.right = right;
}
}
}
Packet::PlayerMouseInput {
x,
y,
released,
button: _,
} => {
let x = x / CLIENT_SCALE;
let y = y / CLIENT_SCALE;
for (entity, mut q_player, transform, velocity, mut attach) in &mut player_query
{
if q_player.addr == *from {
if released {
let select = if let Some(s) = q_player.selected {
s
} else {
break;
};
q_player.selected = None;
if part_query.contains(select) {
let mut module = part_query.get_mut(select).unwrap();
// attach module
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(),
);
if attach.children[2].is_none()
&& 0.3 < rel_y
&& rel_y < 0.6
&& -0.4 < rel_x
&& rel_x < 0.4
{
module.2.translation = vec3(
p_pos.x + 1.06 * angle.sin(),
p_pos.y - 1.06 * angle.cos(),
0.,
);
module.2.rotation =
Quat::from_euler(EulerRot::ZYX, angle, 0., 0.);
module.3.linvel = velocity.linvel;
module.5.attached = true;
let joint = FixedJointBuilder::new()
.local_anchor1(vec2(0., -1.06));
let mut children = [None, None, None, None];
if let Some(loose_attach) = module.4 {
commands.entity(entity).remove::<LooseAttach>();
if *module.1 == PartType::LandingThruster {
commands
.entity(loose_attach.children[2].unwrap())
.insert(Attach {
associated_player: Some(entity),
parent: Some(entity),
children: [None, None, None, None],
});
}
children = loose_attach.children;
}
let mut module_entity = commands.entity(module.0);
module_entity.insert(ImpulseJoint::new(entity, joint));
module_entity.insert(Attach {
associated_player: Some(entity),
parent: Some(entity),
children,
});
attach.children[2] = Some(module.0);
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.translation = vec3(
p_pos.x + 1.06 * angle.sin(),
p_pos.y - 1.06 * angle.cos(),
0.,
);
transform.rotation =
Quat::from_euler(EulerRot::ZYX, angle, 0., 0.);
}
break;
} else if attach.children[0].is_none()
&& -0.6 < rel_y
&& rel_y < -0.3
&& -0.4 < rel_x
&& rel_x < 0.4
{
module.2.translation = vec3(
p_pos.x - 1.06 * angle.sin(),
p_pos.y + 1.06 * angle.cos(),
0.,
);
module.2.rotation =
Quat::from_euler(EulerRot::ZYX, angle + PI, 0., 0.);
module.3.linvel = velocity.linvel;
module.5.attached = true;
let joint = FixedJointBuilder::new()
.local_anchor1(vec2(0., 1.06));
let mut children = [None, None, None, None];
if let Some(loose_attach) = module.4 {
commands.entity(entity).remove::<LooseAttach>();
if *module.1 == PartType::LandingThruster {
commands
.entity(loose_attach.children[2].unwrap())
.insert(Attach {
associated_player: Some(entity),
parent: Some(entity),
children: [None, None, None, None],
});
}
children = loose_attach.children;
}
let mut module_entity = commands.entity(module.0);
module_entity.insert(ImpulseJoint::new(entity, joint));
module_entity.insert(Attach {
associated_player: Some(entity),
parent: Some(entity),
children,
});
attach.children[0] = Some(module.0);
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.translation = vec3(
p_pos.x - 1.06 * angle.sin(),
p_pos.y + 1.06 * angle.cos(),
0.,
);
transform.rotation =
Quat::from_euler(EulerRot::ZYX, angle + PI, 0., 0.);
}
break;
} else if attach.children[1].is_none()
&& -0.6 < rel_x
&& rel_x < -0.3
&& -0.4 < rel_y
&& rel_y < 0.4
{
module.2.translation = vec3(
p_pos.x + 1.06 * angle.cos(),
p_pos.y + 1.06 * angle.sin(),
0.,
);
module.2.rotation = Quat::from_euler(
EulerRot::ZYX,
angle + (PI / 2.),
0.,
0.,
);
module.3.linvel = velocity.linvel;
module.5.attached = true;
let joint = FixedJointBuilder::new()
.local_anchor1(vec2(1.06, 0.))
.local_basis2(std::f32::consts::PI / 2.);
let mut children = [None, None, None, None];
if let Some(loose_attach) = module.4 {
commands.entity(entity).remove::<LooseAttach>();
if *module.1 == PartType::LandingThruster {
commands
.entity(loose_attach.children[2].unwrap())
.insert(Attach {
associated_player: Some(entity),
parent: Some(entity),
children: [None, None, None, None],
});
}
children = loose_attach.children;
}
let mut module_entity = commands.entity(module.0);
module_entity.insert(ImpulseJoint::new(entity, joint));
module_entity.insert(Attach {
associated_player: Some(entity),
parent: Some(entity),
children,
});
attach.children[1] = Some(module.0);
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.translation = vec3(
p_pos.x + 1.06 * angle.cos(),
p_pos.y + 1.06 * angle.sin(),
0.,
);
transform.rotation = Quat::from_euler(
EulerRot::ZYX,
angle + (PI / 2.),
0.,
0.,
);
}
break;
} else if attach.children[3].is_none()
&& 0.3 < rel_x
&& rel_x < 0.6
&& -0.4 < rel_y
&& rel_y < 0.4
{
module.2.translation = vec3(
p_pos.x - 1.06 * angle.cos(),
p_pos.y - 1.06 * angle.sin(),
0.,
);
module.2.rotation = Quat::from_euler(
EulerRot::ZYX,
angle - (PI / 2.),
0.,
0.,
);
module.3.linvel = velocity.linvel;
module.5.attached = true;
let joint = FixedJointBuilder::new()
.local_anchor1(vec2(-1.06, 0.))
.local_basis2(-std::f32::consts::PI / 2.);
let mut children = [None, None, None, None];
if let Some(loose_attach) = module.4 {
commands.entity(entity).remove::<LooseAttach>();
if *module.1 == PartType::LandingThruster {
commands
.entity(loose_attach.children[2].unwrap())
.insert(Attach {
associated_player: Some(entity),
parent: Some(entity),
children: [None, None, None, None],
});
}
children = loose_attach.children;
}
let mut module_entity = commands.entity(module.0);
module_entity.insert(ImpulseJoint::new(entity, joint));
module_entity.insert(Attach {
associated_player: Some(entity),
parent: Some(entity),
children,
});
attach.children[3] = Some(module.0);
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.translation = vec3(
p_pos.x - 1.06 * angle.cos(),
p_pos.y - 1.06 * angle.sin(),
0.,
);
transform.rotation = Quat::from_euler(
EulerRot::ZYX,
angle - (PI / 2.),
0.,
0.,
);
}
break;
}
} else if attached_query.contains(select) {
let mut module = attached_query.get_mut(select).unwrap();
let parent = module.3.parent.unwrap();
commands.entity(select).remove::<ImpulseJoint>();
commands.entity(select).remove::<Attach>();
let children_attach = module.3.clone();
if *module.1 == PartType::LandingThruster {
module.7.attached = false;
commands.entity(select).insert(LooseAttach {
children: module.3.children,
});
commands
.entity(module.3.children[2].unwrap())
.remove::<Attach>();
} else {
module.7.attached = false;
detach_recursive(
&mut commands,
module.3.clone(),
&mut attached_query,
);
}
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 == select {
parent_attach.children[i] = None;
}
}
}
}
let mut module = attached_query.get_mut(select).unwrap();
module.2.translation = vec3(x, y, 0.);
} else {
for (i, child) in attach.children.clone().iter().enumerate()
{
if let Some(child) = child {
if *child == select {
attach.children[i] = None;
let mut module =
attached_query.get_mut(select).unwrap();
module.2.translation =
vec3(x, y, 0.);
if *module.1 == PartType::LandingThruster {
let sub_entity =
children_attach.children[2].unwrap();
let mut suspension = attached_query
.get_mut(sub_entity)
.unwrap();
suspension.2.translation =
vec3(x, y, 0.);
}
}
}
}
}
break;
}
if attach_on_module_tree(
x,
y,
&mut commands,
attach.clone(),
select,
&mut attached_query,
&mut part_query,
) {
let mut part = part_query.get_mut(select).unwrap();
part.5.attached = true; // all of this code is cursed. what the hell is it actually doing
break;
}
// move module to cursor since no attach
let mut part = part_query.get_mut(select).unwrap();
part.2.translation = vec3(x, y, 0.);
if *part.1 == PartType::LandingThruster {
if let Some(loose_attach) = part.4 {
let sub_entity = loose_attach.children[2].unwrap();
let mut part = part_query.get_mut(sub_entity).unwrap();
part.2.translation = vec3(x, y, 0.);
}
}
break;
}
for (entity, part_type, transform, _m_attach, _velocity, _, _, _) in
&attached_query
{
if *part_type == PartType::LandingThrusterSuspension {
continue;
}
let pos = transform.translation;
let rel_x = pos.x - x;
let rel_y = pos.y - y;
let angle = -transform.rotation.z;
let x = rel_x * angle.cos() - rel_y * angle.sin();
let y = rel_x * angle.sin() + rel_y * angle.cos();
let mut bound =
[-0.5, 0.5, -0.5, 0.5]; // left, right, top, bottom
if let PartType::Cargo = part_type {
bound = [
-0.375,
0.375,
-0.5,
0.4375,
];
}
if bound[0] < x && x < bound[1] && bound[2] < y && y < bound[3] {
q_player.selected = Some(entity);
break;
}
}
for (entity, part_type, transform, _, _, _) in &part_query {
if *part_type == PartType::LandingThrusterSuspension {
continue;
}
let pos = transform.translation;
let rel_x = pos.x - x;
let rel_y = pos.y - y;
let angle = -transform.rotation.z;
let x = rel_x * angle.cos() - rel_y * angle.sin();
let y = rel_x * angle.sin() + rel_y * angle.cos();
let mut bound =
[-0.5, 0.5, -0.5, 0.5]; // left, right, top, bottom
if let PartType::Cargo = part_type {
bound = [
-0.375,
0.375,
-0.5,
0.4375
];
}
if bound[0] < x && x < bound[1] && bound[2] < y && y < bound[3] {
q_player.selected = Some(entity);
break;
}
}
}
}
}
Packet::RequestSave {} => {
for (_, q_player, _, _, attach) in &mut player_query {
if q_player.addr == *from {
// HEY! GHOSTLY! PLEASE FILL THIS STRUCT WITH DATA!
// THANKS!
let save = SaveData {
children: construct_save_data(attach.clone(), &attached_query),
};
let save_string = pack_savefile(&app_keys.app_key, save);
let packet = Packet::SaveData {
payload: save_string,
};
event_queue.push(WsEvent::Broadcast {
message: packet.into(),
});
}
}
}
_ => continue,
}
}
}
for event in event_queue {
packet_event_send.send(event);
}
}
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>),
>,
) -> [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,
)
} 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: if part_type == PartType::Cargo {
CARGO_MASS
} else if part_type == PartType::Hub {
HUB_MASS
} else if part_type == PartType::LandingThruster {
LANDING_THRUSTER_MASS
} else {
1.
},
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_basis2(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],
});
}
}
}
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,
attach: Attach,
attached_query: &mut Query<
(
Entity,
&PartType,
&mut Transform,
&mut Attach,
&Velocity,
Option<&CanAttach>,
Option<&LooseAttach>,
&mut PartFlags,
),
(Without<PlanetType>, Without<Player>),
>,
) {
for child in attach.children.iter().flatten() {
{
let (entity, part_type, _transform, attach, _velocity, _, _, mut flags) =
attached_query.get_mut(*child).unwrap();
commands.entity(entity).remove::<Attach>();
if *part_type == PartType::LandingThruster {
commands.entity(entity).insert(LooseAttach {
children: attach.children,
});
commands
.entity(attach.children[2].unwrap())
.remove::<Attach>();
flags.attached = false;
continue;
} else if *part_type == PartType::LandingThrusterSuspension {
flags.attached = false;
let parent = attach.parent.unwrap();
let parent_attach = attached_query.get(parent).unwrap().3;
println!("suspension {:?} {:?}", parent_attach.children, entity);
commands.entity(parent).insert(LooseAttach {
children: parent_attach.children,
});
} else {
flags.attached = false;
commands.entity(entity).remove::<ImpulseJoint>();
detach_recursive(commands, attach.clone(), attached_query);
}
}
let (entity, _part_type, _transform, attach, _velocity, _, _, mut flags) =
attached_query.get_mut(*child).unwrap();
flags.attached = false;
let parent = attach.parent.unwrap();
if attached_query.contains(parent) {
let mut parent_attach = attached_query.get_mut(parent).unwrap().3;
let children = parent_attach.children;
for (i, child) in children.iter().enumerate() {
if let Some(child) = child {
if *child == entity {
parent_attach.children[i] = None;
}
}
}
}
}
}
fn attach_on_module_tree(
x: f32,
y: f32,
commands: &mut Commands,
attach: Attach,
select: 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>),
>,
) -> bool {
let mut ret = false;
for child in attach.children.iter().flatten() {
let (entity, _part_type, transform, mut attach, velocity, can_attach, loose_attach, _) =
attached_query.get_mut(*child).unwrap();
let p_pos = transform.translation;
let (rel_x, rel_y) = (p_pos.x - x, 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 = can_attach.is_some();
if attach.children[2].is_none()
&& attachable
&& 0.3 < rel_y
&& rel_y < 0.6
&& -0.4 < rel_x
&& rel_x < 0.4
{
module.2.translation = vec3(
p_pos.x + 1.06 * angle.sin(),
p_pos.y - 1.06 * angle.cos(),
0.,
);
module.2.rotation = Quat::from_euler(EulerRot::ZYX, angle, 0., 0.);
module.3.linvel = velocity.linvel;
let joint = FixedJointBuilder::new().local_anchor1(vec2(0., -1.06));
let mut children = [None, None, None, None];
if let Some(loose_attach) = loose_attach {
commands.entity(entity).remove::<LooseAttach>();
if *module.1 == PartType::LandingThruster {
commands
.entity(loose_attach.children[2].unwrap())
.insert(Attach {
associated_player: attach.associated_player,
parent: Some(entity),
children: [None, None, None, None],
});
}
children = loose_attach.children;
}
let mut module_entity = commands.entity(module.0);
module_entity.insert(ImpulseJoint::new(entity, joint));
module_entity.insert(Attach {
associated_player: attach.associated_player,
parent: Some(entity),
children,
});
attach.children[2] = Some(module.0);
module.5.attached = true;
return true;
} else if attach.children[1].is_none()
&& attachable
&& -0.6 < rel_x
&& rel_x < -0.3
&& -0.4 < rel_y
&& rel_y < 0.4
{
module.2.translation = vec3(
p_pos.x + 1.06 * angle.cos(),
p_pos.y + 1.06 * angle.sin(),
0.,
);
module.2.rotation =
Quat::from_euler(EulerRot::ZYX, angle + (std::f32::consts::PI / 2.), 0., 0.);
module.3.linvel = velocity.linvel;
let joint = FixedJointBuilder::new()
.local_anchor1(vec2(1.06, 0.))
.local_basis2(std::f32::consts::PI / 2.);
let mut children = [None, None, None, None];
if let Some(loose_attach) = loose_attach {
commands.entity(entity).remove::<LooseAttach>();
if *module.1 == PartType::LandingThruster {
commands
.entity(loose_attach.children[2].unwrap())
.insert(Attach {
associated_player: attach.associated_player,
parent: Some(entity),
children: [None, None, None, None],
});
}
children = loose_attach.children;
}
let mut module_entity = commands.entity(module.0);
module_entity.insert(ImpulseJoint::new(entity, joint));
module_entity.insert(Attach {
associated_player: attach.associated_player,
parent: Some(entity),
children,
});
attach.children[1] = Some(module.0);
module.5.attached = true;
return true;
} else if attach.children[3].is_none()
&& attachable
&& 0.3 < rel_x
&& rel_x < 0.6
&& -0.4 < rel_y
&& rel_y < 0.4
{
module.2.translation = vec3(
p_pos.x - 1.06 * angle.cos(),
p_pos.y - 1.06 * angle.sin(),
0.,
);
module.2.rotation =
Quat::from_euler(EulerRot::ZYX, angle - (std::f32::consts::PI / 2.), 0., 0.);
module.3.linvel = velocity.linvel;
let joint = FixedJointBuilder::new()
.local_anchor1(vec2(-1.06, 0.))
.local_basis2(-std::f32::consts::PI / 2.);
let mut children = [None, None, None, None];
if let Some(loose_attach) = loose_attach {
commands.entity(entity).remove::<LooseAttach>();
if *module.1 == PartType::LandingThruster {
commands
.entity(loose_attach.children[2].unwrap())
.insert(Attach {
associated_player: attach.associated_player,
parent: Some(entity),
children: [None, None, None, None],
});
}
children = loose_attach.children;
}
let mut module_entity = commands.entity(module.0);
module_entity.insert(ImpulseJoint::new(entity, joint));
module_entity.insert(Attach {
associated_player: attach.associated_player,
parent: Some(entity),
children,
});
attach.children[3] = Some(module.0);
module.5.attached = true;
return true;
}
ret |= if *module.1 != PartType::LandingThruster {
attach_on_module_tree(
x,
y,
commands,
attach.clone(),
select,
attached_query,
part_query,
)
} else {
false
};
}
ret
}
fn convert_modules(
mut commands: Commands,
rapier_context: Res<RapierContext>,
planet_query: Query<(Entity, &PlanetType, &Children)>,
player_query: Query<&Attach, With<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.intersections_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 player_attach = player_query.get(player_entity).unwrap();
convert_modules_recursive(
&mut commands,
*planet_type,
player_attach.clone(),
&mut attached_query,
&mut collider_query,
&mut packet_send,
);
}
}
}
}
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>,
) {
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: 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));
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: LANDING_THRUSTER_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());
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 {
convert_modules_recursive(
commands,
planet_type,
attach.clone(),
attached_query,
collider_query,
packet_send,
);
}
}
}
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<&mut Attach, With<Player>>,
) {
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 {
commands.entity(entity).remove::<ImpulseJoint>();
commands.entity(entity).remove::<Attach>();
if part_type == PartType::LandingThruster {
commands.entity(entity).insert(LooseAttach {
children: attach.children,
});
if attach.children[2].is_some() {
commands
.entity(attach.children[2].unwrap())
.remove::<Attach>();
}
} else {
detach_recursive(&mut commands, attach.clone(), &mut attached_query);
}
let parent = attach.parent.unwrap();
if attached_query.contains(parent) {
let mut parent_attach = attached_query.get_mut(parent).unwrap().3;
let children = parent_attach.children;
for (i, child) in children.iter().enumerate() {
if let Some(child) = child {
if *child == entity {
parent_attach.children[i] = None;
}
}
}
} else {
let mut attach = player_query.get_mut(parent).unwrap();
for (i, child) in attach.children.clone().iter().enumerate() {
if let Some(child) = child {
if *child == entity {
attach.children[i] = None;
}
}
}
}
}
}
fn save_eligibility(
rapier_context: Res<RapierContext>,
planet_query: Query<(Entity, &PlanetType, &Children)>,
mut player_query: Query<&mut Player>,
mut packet_send: EventWriter<WsEvent>,
) {
for (_planet_entity, _planet_type, children) in &planet_query {
for (entity1, entity2, intersecting) in
rapier_context.intersections_with(*children.first().unwrap())
{
if intersecting {
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 {
let packet = Packet::SaveEligibility { eligible: true };
packet_send.send(WsEvent::Send {
to: player.addr,
message: packet.into(),
});
}
player.save_eligibility = 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 {
let packet = Packet::SaveEligibility { eligible: false };
packet_send.send(WsEvent::Send {
to: player.addr,
message: packet.into(),
});
}
player.save_eligibility = false;
}
}
}
}
}
fn on_close(
player_query: Query<(Entity, &Player, &Attach)>,
attached_query: Query<&Attach, With<PartType>>,
part_query: Query<&PartType>,
mut commands: Commands,
mut packet_recv: Local<ManualEventReader<WsEvent>>,
mut packet_send: ResMut<Events<WsEvent>>,
) {
let mut packets = Vec::new();
for packet in packet_recv.read(&packet_send) {
if let WsEvent::Close { addr } = packet {
for (entity, player, attach) in &player_query {
if player.addr == *addr {
despawn_module_tree(
&mut commands,
attach,
&attached_query,
&part_query,
&mut packets,
);
commands.entity(entity).despawn_recursive();
let packet = Packet::PlayerLeave { id: entity.index() };
for (in_entity, player, _) in &player_query {
if entity != in_entity {
packets.push(WsEvent::Send {
to: player.addr,
message: packet.clone().into(),
});
}
}
}
}
}
}
for packet in packets {
packet_send.send(packet);
}
}
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 on_position_change(
mut commands: Commands,
part_query: Query<(Entity, &PartType, &Transform, &PartFlags), Changed<Transform>>,
planet_query: Query<(Entity, &PlanetType, &Transform), Changed<Transform>>,
mut packet_send: EventWriter<WsEvent>,
) {
let mut updated_parts = Vec::new();
for (entity, part_type, transform, flags) in part_query.iter() {
let id = commands.entity(entity).id().index();
updated_parts.push((
id,
Part {
part_type: *part_type,
transform: proto_transform!(Transform::from_translation(
transform.translation * CLIENT_SCALE,
)
.with_rotation(transform.rotation)),
flags: proto_part_flags!(flags),
},
));
}
if !updated_parts.is_empty() {
let packet = Packet::PartPositions {
parts: updated_parts,
};
packet_send.send(WsEvent::Broadcast {
message: packet.into(),
});
}
let mut planets = Vec::new();
for (entity, planet_type, transform) in planet_query.iter() {
let id = commands.entity(entity).id().index();
planets.push((
id,
Planet {
planet_type: *planet_type,
transform: proto_transform!(Transform::from_translation(
transform.translation * CLIENT_SCALE
)),
radius: match *planet_type {
PlanetType::Earth => EARTH_SIZE * CLIENT_SCALE,
PlanetType::Moon => MOON_SIZE * CLIENT_SCALE,
PlanetType::Mars => MARS_SIZE * CLIENT_SCALE,
},
},
));
}
if !planets.is_empty() {
let packet = Packet::PlanetPositions { planets };
packet_send.send(WsEvent::Broadcast {
message: packet.into(),
});
}
}
fn player_input_update(
mut player_and_body_query: Query<(
Entity,
&mut Player,
&Attach,
&mut ExternalForce,
&Transform,
)>,
mut attached_query: Query<
(&Attach, &PartType, &mut ExternalForce, &Transform),
Without<Player>,
>,
) {
for (_, player, attach, mut forces, transform) in &mut player_and_body_query {
//forces.torque = 0.0;
//forces.force = Vec2::ZERO;
if !(player.input.up || player.input.down || player.input.right || player.input.left) {
continue;
}
let mut fmul_bottom_left_thruster: f32 = 0.0;
let mut fmul_bottom_right_thruster: f32 = 0.0;
let mut fmul_top_left_thruster: f32 = 0.0;
let mut fmul_top_right_thruster: f32 = 0.0;
if player.input.up {
fmul_bottom_left_thruster -= 1.0;
fmul_bottom_right_thruster -= 1.0;
}
if player.input.down {
fmul_top_left_thruster += 1.0;
fmul_top_right_thruster += 1.0;
}
if player.input.left {
fmul_top_left_thruster += 1.0;
fmul_bottom_right_thruster -= 1.0;
}
if player.input.right {
fmul_top_right_thruster += 1.0;
fmul_bottom_left_thruster -= 1.0;
}
fmul_top_left_thruster = fmul_top_left_thruster.clamp(-1.0, 1.0);
fmul_top_right_thruster = fmul_top_right_thruster.clamp(-1.0, 1.0);
fmul_bottom_left_thruster = fmul_bottom_left_thruster.clamp(-1.0, 1.0);
fmul_bottom_right_thruster = fmul_bottom_right_thruster.clamp(-1.0, 1.0);
if player.input.up {
fmul_bottom_left_thruster -= 2.0;
fmul_bottom_right_thruster -= 2.0;
}
if player.input.down {
fmul_top_left_thruster += 2.0;
fmul_top_right_thruster += 2.0;
}
let rot = transform.rotation.to_euler(EulerRot::ZYX).0;
let thrusters = [
(fmul_bottom_left_thruster, -PART_HALF_SIZE, -PART_HALF_SIZE),
(fmul_bottom_right_thruster, PART_HALF_SIZE, -PART_HALF_SIZE),
(fmul_top_left_thruster, -PART_HALF_SIZE, PART_HALF_SIZE),
(fmul_top_right_thruster, PART_HALF_SIZE, PART_HALF_SIZE),
];
for (force_multiplier, x_offset, y_offset) in thrusters {
if force_multiplier != 0.0 {
let thruster_pos_uncast = vec2(x_offset, y_offset);
let thruster_pos_cast =
rot2d(thruster_pos_uncast, rot) + transform.translation.xy();
let thruster_force = force_multiplier * HEARTY_THRUSTER_FORCE;
let thruster_vec = vec2(
-thruster_force * rot.sin(),
thruster_force * rot.cos(),
);
let thruster_force = ExternalForce::at_point(
thruster_vec,
thruster_pos_cast,
transform.translation.xy(),
);
forces.force += thruster_force.force;
forces.torque += thruster_force.torque;
}
}
search_thrusters(
player.input,
attach.clone(),
*transform,
&mut attached_query,
);
}
}
fn search_thrusters(
input: Input,
attach: Attach,
p_transform: Transform,
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.;
if *part_type == PartType::LandingThruster {
force_mult = LANDING_THRUSTER_FORCE;
}
if input.up && 3. * PI / 4. < relative_angle && relative_angle < 5. * PI / 4. {
let thruster_force = ExternalForce::at_point(
Vec2::new(
-force_mult * angle.sin(),
force_mult * angle.cos(),
),
transform.translation.xy(),
transform.translation.xy(),
);
force.force += thruster_force.force;
force.torque += thruster_force.torque;
}
if input.down
&& ((0. < relative_angle && relative_angle < PI / 4.)
|| (7. * PI / 4. < relative_angle && relative_angle < 2. * PI))
{
let thruster_force = ExternalForce::at_point(
Vec2::new(
-force_mult * angle.sin(),
force_mult * angle.cos(),
),
transform.translation.xy(),
transform.translation.xy(),
);
force.force += thruster_force.force;
force.torque += thruster_force.torque;
}
if input.left {
if 3. * PI / 4. < relative_angle
&& relative_angle < 5. * PI / 4.
&& relative_pos.x > 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;
}
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;
}
if PI / 4. < relative_angle && relative_angle < 3. * PI / 4. {
if 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;
}
if -0.48 < relative_pos.y && 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;
}
}
if 5. * PI / 4. < relative_angle && relative_angle < 7. * PI / 4. {
if 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;
}
if -0.48 < relative_pos.y && 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;
}
}
}
if *part_type != PartType::LandingThruster {
search_thrusters(input, attach.clone(), p_transform, attached_query);
}
}
}
fn gravity_update(
mut part_query: Query<
(
&Transform,
&ReadMassProperties,
&mut ExternalForce,
&mut ExternalImpulse,
),
With<PartType>,
>,
planet_query: Query<(&Transform, &ReadMassProperties), With<PlanetType>>,
) {
for (part_transform, part_mp, mut forces, mut impulses) in &mut part_query {
impulses.impulse = Vec2::ZERO;
forces.force = Vec2::ZERO;
forces.torque = 0.;
let part_mp = part_mp.get();
let part_mass = part_mp.mass;
let part_translate = part_transform.translation;
for (planet_transform, planet_mp) in &planet_query {
let planet_mp = planet_mp.get();
let planet_mass = planet_mp.mass;
let planet_translate = planet_transform.translation;
let distance = planet_translate.distance(part_translate);
let force = GRAVITY * ((part_mass * planet_mass) / (distance * distance));
let direction = (planet_translate - part_translate).normalize() * force;
impulses.impulse += direction.xy();
}
}
}