use bevy::{ecs::event::ManualEventReader, math::{vec2, vec3}, prelude::*};
use bevy_rapier2d::prelude::*;
use component::Player;
use hmac::{Hmac, Mac};
use rand::Rng;
use sha2::Sha256;
use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData};
use jwt::VerifyWithKey;
use crate::{config::StkConfig, err_or_cont, mathutil::rot2d,
module::{component::{Attach, CanAttach, LooseAttach, PartBundle, PartFlags, PartType}, save::load_savefile, PART_HALF_SIZE},
part, planet::PlanetType,
proto_part_flags, proto_transform, ws::WsEvent, AppKeys, MessageType,
Packet, Part, Planet, ProtoPartFlags, UserToken, CLIENT_SCALE};
pub mod component;
pub mod packet;
pub 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,
&mut PartFlags,
),
Without<PlanetType>,
>,
mut packet_recv: Local<ManualEventReader<WsEvent>>,
mut packet_event_send: ResMut<Events<WsEvent>>,
app_keys: Res<AppKeys>,
server_config: Res<StkConfig>,
) {
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,
} => {
// auth
// plz no remove
if let Some(token) = jwt {
let key: Hmac<Sha256> = Hmac::new_from_slice(&app_keys.app_key).unwrap();
let claims: UserToken = match token.verify_with_key(&key) {
Ok(c) => c,
Err(e) => {
event_queue.push(WsEvent::Send {
to: *from,
message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Token is invalid or verification failed: {e}. Please log in again, or contact StarKingdoms staff if the problem persists.") }.into(),
});
event_queue.push(WsEvent::Close { addr: *from });
continue;
}
};
if claims.permission_level
< server_config.security.required_permission_level
{
event_queue.push(WsEvent::Send {
to: *from,
message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Permission level {} is too low, {} is required. If your permissions were just changed, you need to log out and log back in for the change to take effect. If you believe this is a mistake, contact StarKingdoms staff.", claims.permission_level, server_config.security.required_permission_level) }.into(),
});
event_queue.push(WsEvent::Close { addr: *from });
continue;
}
event_queue.push(WsEvent::Send {
to: *from,
message: Packet::Message { message_type: MessageType::Server, actor: "StarKingdoms Team".to_string(), content: "Thank you for participating in the StarKingdoms private alpha! Your feedback is essential to improving the game, so please give us any feedback you have in the Discord! <3".to_string() }.into(),
});
} else if server_config.security.required_permission_level != 0 {
event_queue.push(WsEvent::Send {
to: *from,
message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: "Authentication is required to join this server at the moment. Log in and try again, or try again later.".to_string() }.into(),
});
event_queue.push(WsEvent::Close { addr: *from });
continue;
}
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 player_comp = Player {
addr: *from,
username: username.to_string(),
input: component::Input::default(),
selected: None,
save_eligibility: false,
energy_capacity: part!(PartType::Hearty).energy_capacity,
energy: part!(PartType::Hearty).energy_capacity,
};
let mut entity_id = commands.spawn(PartBundle {
part_type: PartType::Hearty,
transform: TransformBundle::from(transform),
flags: PartFlags { attached: false },
});
entity_id
.insert(Collider::cuboid(0.5, 0.5))
.insert(AdditionalMassProperties::MassProperties(MassProperties {
local_center_of_mass: vec2(0.0, 0.0),
mass: part!(PartType::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,
&mut player_query,
&mut player_comp,
);
player_comp.energy = player_comp.energy_capacity;
attach.children = children;
} else {
let packet = Packet::Message {
message_type: crate::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(player_comp);
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 => {
planet!(PlanetType::Earth).size * CLIENT_SCALE
}
PlanetType::Moon => {
planet!(PlanetType::Moon).size * CLIENT_SCALE
}
PlanetType::Mars => {
planet!(PlanetType::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: crate::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: crate::packet::MessageType::Server,
actor: "SERVER".to_string(),
content: format!(
"starkingdoms-server v{} says hello",
env!("CARGO_PKG_VERSION")
),
};
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
let packet = Packet::Message {
message_type: crate::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(),
});
let packet = Packet::Message {
message_type: crate::packet::MessageType::Server,
actor: "SERVER".to_string(),
content: "Found a bug? Have a feature request? Please bring this and all other feedback to the game's official Discord server! Join here: https://discord.gg/3u7Yw8DWtQ".to_string(),
};
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
}
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: crate::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: crate::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, _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 attached_query.contains(select) {
let module = attached_query.get(select).unwrap();
let attach = module.3.clone();
let lost_energy_capacity = crate::module::detach_recursive(
&mut commands,
module.0,
&mut attached_query,
&mut player_query,
);
let mut module = attached_query.get_mut(select).unwrap();
module.2.translation = vec3(x, y, 0.);
if *module.1 == PartType::LandingThruster {
let sub_entity = attach.children[2].unwrap();
let mut suspension =
attached_query.get_mut(sub_entity).unwrap();
suspension.2.translation = vec3(x, y, 0.);
}
let mut player = player_query.get_mut(entity).unwrap().1;
player.energy_capacity -= lost_energy_capacity;
player.energy =
std::cmp::min(player.energy, player.energy_capacity);
break;
}
if crate::module::attach_on_module_tree(
x,
y,
&mut commands,
entity,
select,
entity,
&mut attached_query,
&mut part_query,
&mut player_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 (m_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]
&& m_attach.associated_player.unwrap() == entity
{
q_player.selected = Some(m_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 { old_save } => {
for (_, q_player, _, _, attach, _) in &mut player_query {
if q_player.addr == *from {
// HEY! GHOSTLY! PLEASE FILL THIS STRUCT WITH DATA!
// THANKS!
let unused_modules = if let Some(ref old_save) = old_save {
if let Ok(old_savedata) =
unpack_savefile(&app_keys.app_key, old_save.to_string())
{
old_savedata.unused_modules
} else {
Vec::new()
}
} else {
Vec::new()
};
let save = SaveData {
children: crate::module::save::construct_save_data(attach.clone(), &attached_query),
unused_modules,
};
let save_string = pack_savefile(&app_keys.app_key, save);
let packet = Packet::SaveData {
payload: save_string,
};
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into(),
});
}
}
}
_ => continue,
}
}
}
for event in event_queue {
packet_event_send.send(event);
}
}
pub 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 (_, mut 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 && player.energy >= part!(PartType::Hearty).thruster_energy {
player.energy -= part!(PartType::Hearty).thruster_energy;
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 * part!(PartType::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;
}
}
// change to support other thruster types later
if player.energy >= part!(PartType::LandingThruster).thruster_energy {
crate::module::thruster::search_thrusters(
player.input,
attach.clone(),
*transform,
&mut player.energy,
&mut attached_query,
);
}
}
}