use std::net::Ipv4Addr; use crate::mathutil::{rot2d, v3_to_v2}; use bevy::math::{vec2, vec3}; 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::::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::::from_seconds(1.0/20.0)) .run(); info!("Goodbye!"); } fn setup_integration_parameters(mut context: ResMut) { 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>, mut packet_event_send: ResMut>, ) { 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::() * 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 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(), }, }) .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() .index(); // 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)>, mut commands: Commands, mut packet_recv: Local>, mut packet_send: ResMut>, ) { let mut packets = Vec::new(); for packet in packet_recv.read(&packet_send) { if let ServerEvent::Close(addr) = packet { for (entity, player) in &player_query { if player.addr == *addr { commands.entity(entity).despawn(); 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 on_position_change( mut commands: Commands, part_query: Query<(Entity, &PartType, &Transform), Changed>, planet_query: Query<(Entity, &PlanetType, &Transform), Changed>, mut packet_send: EventWriter, ) { 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>, planet_query: Query<(&Transform, &ReadMassProperties), With>, ) { 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(); } } }