use std::net::Ipv4Addr;
use crate::mathutil::rot2d;
use bevy::math::vec2;
use bevy::utils::tracing;
use bevy::{ecs::event::ManualEventReader, prelude::*};
use bevy_rapier2d::prelude::*;
use bevy_twite::{twite::frame::MessageType, ServerEvent, TwiteServerConfig, TwiteServerPlugin};
use component::*;
use packet::*;
use rand::Rng;
pub mod component;
pub mod macros;
pub mod mathutil;
pub mod packet;
const SCALE: f32 = 10.0;
const EARTH_SIZE: f32 = 1000.0;
const GRAVITY: f32 = 0.02;
const PART_HALF_SIZE: f32 = 25.0;
const THRUSTER_FORCE: f32 = 0.08;
fn main() {
let subscriber = tracing_subscriber::FmtSubscriber::new();
tracing::subscriber::set_global_default(subscriber).unwrap();
info!(
"StarKingdoms server v{} starting up",
env!("CARGO_PKG_VERSION")
);
App::new()
.insert_resource(TwiteServerConfig {
addr: Ipv4Addr::new(0, 0, 0, 0),
port: 3000,
})
.add_plugins(MinimalPlugins)
.insert_resource(RapierConfiguration {
gravity: Vect { x: 0.0, y: 0.0 },
..Default::default()
})
.add_plugins(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(SCALE))
.add_plugins(TwiteServerPlugin)
.add_systems(Startup, setup_integration_parameters)
.add_systems(Startup, spawn_planets)
.add_systems(Startup, remove_later_module_spawn)
.add_systems(Update, on_message)
.add_systems(Update, on_close)
.add_systems(FixedUpdate, on_position_change)
.add_systems(FixedUpdate, gravity_update)
.add_systems(FixedUpdate, player_input_update)
//.insert_resource(Time::<Fixed>::from_seconds(1.0/20.0))
.run();
info!("Goodbye!");
}
fn setup_integration_parameters(mut context: ResMut<RapierContext>) {
context.integration_parameters.dt = 1.0 / 20.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 / SCALE))
.insert(AdditionalMassProperties::Mass(10000.0))
.insert(ReadMassProperties::default())
.insert(RigidBody::Fixed);
}
fn remove_later_module_spawn(mut commands: Commands) {
commands
.spawn(PartBundle {
part_type: PartType::Cargo,
transform: TransformBundle::from(Transform::from_xyz(1100. / SCALE, 0., 0.)),
})
//.insert(Collider::cuboid(18.75 / SCALE, 23.4375 / SCALE))
.insert(RigidBody::Dynamic)
.with_children(|children| {
children
.spawn(Collider::cuboid(18.75 / SCALE, 23.4375 / SCALE))
.insert(TransformBundle::from(Transform::from_xyz(
0.,
1.5625 / SCALE,
0.,
)));
})
.insert(ExternalForce::default())
.insert(ExternalImpulse::default())
.insert(ReadMassProperties::default());
}
fn on_message(
mut commands: Commands,
planet_query: Query<(Entity, &PlanetType, &Transform)>,
mut player_query: Query<(Entity, &mut Player)>,
part_query: Query<(Entity, &PartType, &Transform)>,
mut packet_recv: Local<ManualEventReader<ServerEvent>>,
mut packet_event_send: ResMut<Events<ServerEvent>>,
) {
let mut event_queue = Vec::new();
for ev in packet_recv.read(&packet_event_send) {
if let ServerEvent::Recv(addr, MessageType::Text, data) = ev {
let data = String::from_utf8_lossy(data);
let packet: Packet = err_or_cont!(serde_json::from_str(&data));
match packet {
Packet::ClientLogin { username, .. } => {
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() * 1100.0 / SCALE,
angle.sin() * 1100.0 / SCALE,
0.0,
);
transform.rotate_z(angle);
let entity_id = commands
.spawn(PlayerBundle {
part: PartBundle {
part_type: PartType::Hearty,
transform: TransformBundle::from(transform),
},
player: Player {
addr: *addr,
username: username.to_string(),
input: component::Input::default(),
},
attach: Attach {
associated_player: None,
children: [None, None, None, None],
},
})
.insert(Collider::cuboid(
PART_HALF_SIZE / SCALE,
PART_HALF_SIZE / SCALE,
))
.insert(AdditionalMassProperties::MassProperties(MassProperties {
local_center_of_mass: vec2(0.0, 0.0),
mass: 0.0001,
principal_inertia: 0.005,
}))
.insert(ExternalImpulse {
impulse: Vec2::ZERO,
torque_impulse: 0.0,
})
.insert(ExternalForce::default())
.insert(ReadMassProperties::default())
.insert(RigidBody::Dynamic).id();
let id = entity_id.index();
// spawn module
let module_id = commands
.spawn(PartBundle {
part_type: PartType::Cargo,
transform: TransformBundle::from(Transform::from_xyz(1100. / SCALE, 0., 0.)),
})
//.insert(Collider::cuboid(18.75 / SCALE, 23.4375 / SCALE))
.insert(RigidBody::Dynamic)
.with_children(|children| {
children
.spawn(Collider::cuboid(18.75 / SCALE, 23.4375 / SCALE))
.insert(TransformBundle::from(Transform::from_xyz(
0.,
1.5625 / SCALE,
0.,
)));
})
.insert(ExternalForce::default())
.insert(ExternalImpulse::default())
.insert(ReadMassProperties::default()).id();
// attachment logic
let joint = FixedJointBuilder::new().local_anchor1(vec2(0. / SCALE, -53. / SCALE));
let mut module = commands.entity(module_id);
module.insert(ImpulseJoint::new(entity_id, joint));
let mut entity = commands.entity(entity_id);
// temporary remove attach
entity.remove::<Attach>();
entity.insert(Attach {
associated_player: None,
children: [None, None, Some(module_id), None]
});
// 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 * SCALE
)),
radius: match *planet_type {
PlanetType::Earth => EARTH_SIZE,
},
},
));
}
let packet = Packet::PlanetPositions { planets };
let buf = serde_json::to_vec(&packet).unwrap();
event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
// 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 };
let buf = serde_json::to_vec(&packet).unwrap();
event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
// tell other players that a player has spawned in
let packet = Packet::SpawnPlayer {
id,
username: username.to_string(),
};
let buf = serde_json::to_vec(&packet).unwrap();
event_queue.push(ServerEvent::Broadcast(MessageType::Text, buf));
let packet = Packet::Message {
message_type: packet::MessageType::Server,
actor: "SERVER".to_string(),
content: format!("{} has joined the server!", username),
};
let buf = serde_json::to_vec(&packet).unwrap();
event_queue.push(ServerEvent::Broadcast(MessageType::Text, buf));
// tell the player where parts are
let mut parts = Vec::new();
for (entity, part_type, transform) in &part_query {
parts.push((
entity.index(),
Part {
part_type: *part_type,
transform: proto_transform!(Transform::from_translation(
transform.translation * SCALE
)),
},
));
}
parts.push((
id,
Part {
part_type: PartType::Hearty,
transform: proto_transform!(Transform::from_translation(
transform.translation * SCALE
)
.with_rotation(transform.rotation)),
},
));
let packet = Packet::PartPositions { parts };
let buf = serde_json::to_vec(&packet).unwrap();
event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
// 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(),
};
let buf = serde_json::to_vec(&packet).unwrap();
event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
}
Packet::SendMessage { target, content } => {
// find our player
let mut player = None;
for (_, q_player) in &player_query {
if q_player.addr == *addr {
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,
};
let buf = serde_json::to_vec(&packet).unwrap();
event_queue.push(ServerEvent::Send(
target_player.addr,
MessageType::Text,
buf.clone(),
));
event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf));
} else {
// send to general chat
let packet = Packet::Message {
message_type: packet::MessageType::Chat,
actor: player.username.clone(),
content,
};
let buf = serde_json::to_vec(&packet).unwrap();
event_queue.push(ServerEvent::Broadcast(MessageType::Text, buf));
}
}
Packet::PlayerInput {
up,
down,
left,
right,
} => {
for (_, mut q_player) in &mut player_query {
if q_player.addr == *addr {
q_player.input.up = up;
q_player.input.down = down;
q_player.input.left = left;
q_player.input.right = right;
}
}
}
_ => continue,
}
}
}
for event in event_queue {
packet_event_send.send(event);
}
}
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<ServerEvent>>,
mut packet_send: ResMut<Events<ServerEvent>>,
) {
let mut packets = Vec::new();
for packet in packet_recv.read(&packet_send) {
if let ServerEvent::Close(addr) = packet {
for (entity, player, attach) in &player_query {
if player.addr == *addr {
despawn_module_tree(&mut commands, attach, &attached_query, &part_query);
commands.entity(entity).despawn_recursive();
let packet = Packet::PlayerLeave { id: entity.index() };
let buf = serde_json::to_vec(&packet).unwrap();
for (in_entity, player, _) in &player_query {
if entity != in_entity {
packets.push(ServerEvent::Send(
player.addr,
MessageType::Text,
buf.clone(),
));
}
}
}
}
}
}
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>,
) {
for child in attach.children {
if let Some(child) = child {
commands.entity(child).despawn_recursive();
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);
}
}
}
fn on_position_change(
mut commands: Commands,
part_query: Query<(Entity, &PartType, &Transform), Changed<Transform>>,
planet_query: Query<(Entity, &PlanetType, &Transform), Changed<Transform>>,
mut packet_send: EventWriter<ServerEvent>,
) {
let mut updated_parts = Vec::new();
for (entity, part_type, transform) 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 * SCALE
)
.with_rotation(transform.rotation)),
},
));
}
if !updated_parts.is_empty() {
let packet = Packet::PartPositions {
parts: updated_parts,
};
let buf = serde_json::to_vec(&packet).unwrap();
packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf));
}
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 * SCALE
)),
radius: match *planet_type {
PlanetType::Earth => EARTH_SIZE,
},
},
));
}
if !planets.is_empty() {
let packet = Packet::PlanetPositions { planets };
let buf = serde_json::to_vec(&packet).unwrap();
packet_send.send(ServerEvent::Broadcast(MessageType::Text, buf));
}
}
fn player_input_update(
mut player_and_body_query: Query<(Entity, &mut Player, &mut ExternalForce, &Transform)>,
) {
for (_, player, 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 -= 60.0;
fmul_bottom_right_thruster -= 60.0;
}
if player.input.down {
fmul_top_left_thruster += 60.0;
fmul_top_right_thruster += 60.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 / SCALE, y_offset / SCALE);
let thruster_pos_cast =
rot2d(thruster_pos_uncast, rot) + transform.translation.xy();
let thruster_force = force_multiplier * THRUSTER_FORCE;
let thruster_vec = vec2(
-thruster_force / SCALE * rot.sin(),
thruster_force / SCALE * 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;
}
}
}
}
fn gravity_update(
mut part_query: Query<(&Transform, &ReadMassProperties, &mut ExternalImpulse), With<PartType>>,
planet_query: Query<(&Transform, &ReadMassProperties), With<PlanetType>>,
) {
for (part_transform, part_mp, mut impulses) in &mut part_query {
impulses.impulse = Vec2::ZERO;
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();
}
}
}