use std::{collections::HashMap, net::SocketAddr};
use bevy::{math::vec2, prelude::*};
use bevy_rapier2d::prelude::*;
use hmac::{Hmac, Mac};
use jwt::VerifyWithKey;
use rand::Rng;
use sha2::Sha256;
use starkingdoms_common::{
packet::{MessageType, Packet, Part, Planet, ProtoPartFlags},
proto_part_flags, proto_transform, unpack_savefile, PartType as c_PartType,
};
use crate::{
config::StkConfig,
crafting::components::{IsMining, VarietyMaterialStorage},
module::{
component::{Attach, CanAttach, LooseAttach, PartBundle, PartFlags, PartType},
save::load_savefile,
},
planet::PlanetType,
ws::{PacketMessageConvert, WsEvent},
AppKeys, UserToken, CLIENT_SCALE,
};
use super::component::{Input, Player};
pub fn join_auth(
jwt: Option<String>,
app_keys: AppKeys,
from: &SocketAddr,
event_queue: &mut Vec<WsEvent>,
server_config: StkConfig,
) -> bool {
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_message(),
});
event_queue.push(WsEvent::Close { addr: *from });
return false;
}
};
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_message(),
});
event_queue.push(WsEvent::Close { addr: *from });
return false;
}
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_message(),
});
} 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_message(),
});
event_queue.push(WsEvent::Close { addr: *from });
return false;
}
true
}
pub fn spawn_player(
commands: &mut Commands,
from: &SocketAddr,
username: String,
) -> (Entity, Transform, Player) {
// generate random angle
let angle: f32 = {
let mut rng = rand::thread_rng();
rng.gen::<f32>() * std::f32::consts::PI * 2.
};
// convert to cartesian with 30.0 meter radius
let mut transform = Transform::from_xyz(angle.cos() * 30.0, angle.sin() * 30.0, 0.0);
transform.rotate_z(angle);
transform.translation += Vec3::new(6000.0, 0.0, 0.0);
let player_comp = Player {
addr: *from,
username,
input: Input::default(),
selected: None,
save_eligibility: false,
energy_capacity: part!(c_PartType::Hearty.into()).energy_capacity,
energy: part!(c_PartType::Hearty.into()).energy_capacity,
};
let mut entity_id = commands.spawn(PartBundle {
part_type: c_PartType::Hearty.into(),
transform: TransformBundle::from(transform),
flags: PartFlags { attached: false },
..default()
});
entity_id
.insert(VarietyMaterialStorage {
materials: HashMap::new(),
capacity: 200,
})
.insert(IsMining(false))
.insert(Collider::cuboid(0.5, 0.5))
.insert(AdditionalMassProperties::MassProperties(MassProperties {
local_center_of_mass: vec2(0.0, 0.0),
mass: part!(c_PartType::Hearty.into()).mass,
principal_inertia: 7.5,
}));
(entity_id.id(), transform, player_comp)
}
pub fn load_save(
commands: &mut Commands,
transform: Transform,
id: Entity,
save: Option<String>,
app_keys: AppKeys,
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,
attach: &mut Attach,
from: &SocketAddr,
event_queue: &mut Vec<WsEvent>,
) {
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, module_counts) = load_savefile(
commands,
transform,
id,
id,
savefile.children,
attached_query,
part_query,
player_query,
player_comp,
);
// update energy and children
player_comp.energy = player_comp.energy_capacity;
attach.children = children;
} else {
let packet = Packet::Message {
message_type: 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_message(),
});
}
} else {
// nothing to do
}
}
pub fn packet_stream(
planet_query: &Query<(Entity, &PlanetType, &Transform)>,
event_queue: &mut Vec<WsEvent>,
from: &SocketAddr,
player_query: &Query<
(
Entity,
&mut Player,
&Transform,
&Velocity,
&mut Attach,
&mut PartFlags,
),
Without<PlanetType>,
>,
index: u32,
username: String,
part_query: &Query<
(
Entity,
&PartType,
&mut Transform,
&mut Velocity,
Option<&LooseAttach>,
&mut PartFlags,
),
(Without<PlanetType>, Without<Player>, Without<Attach>),
>,
attached_query: &Query<
(
Entity,
&PartType,
&mut Transform,
&mut Attach,
&Velocity,
Option<&CanAttach>,
Option<&LooseAttach>,
&mut PartFlags,
),
(Without<PlanetType>, Without<Player>),
>,
transform: Transform,
) {
// response in the handshake
let packet = Packet::LoginResponse { id: index };
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into_message(),
});
// 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.0,
transform: proto_transform!(Transform::from_translation(
translation * CLIENT_SCALE
)),
radius: planet!(*planet_type).size * CLIENT_SCALE,
},
));
}
let packet = Packet::PlanetPositions { planets };
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into_message(),
});
// tell the player already existing users
let mut players = Vec::new();
for (entity, player, _, _, _, _) in player_query.iter() {
players.push((entity.index(), player.username.clone()));
}
let packet = Packet::PlayerList { players };
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into_message(),
});
// tell other players that a player has spawned in
let packet = Packet::SpawnPlayer {
id: index,
username: username.to_string(),
};
event_queue.push(WsEvent::Broadcast {
message: packet.into_message(),
});
let packet = Packet::Message {
message_type: MessageType::Server,
actor: "SERVER".to_string(),
content: format!("{} has joined the server!", username),
};
event_queue.push(WsEvent::Broadcast {
message: packet.into_message(),
});
// tell the player where parts are
let mut parts = Vec::new();
for (entity, part_type, transform, _, _, flags) in part_query.iter() {
parts.push((
entity.index(),
Part {
part_type: part_type.0,
transform: proto_transform!(Transform::from_translation(
transform.translation * CLIENT_SCALE
)),
flags: proto_part_flags!(flags),
},
));
}
for (entity, part_type, transform, _, _, _, _, flags) in attached_query.iter() {
parts.push((
entity.index(),
Part {
part_type: part_type.0,
transform: proto_transform!(Transform::from_translation(
transform.translation * CLIENT_SCALE
)),
flags: proto_part_flags!(flags),
},
));
}
parts.push((
index,
Part {
part_type: c_PartType::Hearty.into(),
transform: proto_transform!(Transform::from_translation(transform.translation)
.with_rotation(transform.rotation)),
flags: ProtoPartFlags { attached: false },
},
));
for part in parts {
let packet = Packet::SpawnPart {
id: part.0,
part: part.1,
};
event_queue.push(WsEvent::Send {
to: *from,
message: packet.into_message(),
});
}
// and send the welcome message :)
let packet = Packet::Message {
message_type: 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_message(),
});
let packet = Packet::Message {
message_type: 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_message(),
});
let packet = Packet::Message {
message_type: 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_message(),
});
}