use std::net::Ipv4Addr; 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 packet; const SCALE: f32 = 10.0; const EARTH_SIZE: f32 = 1000.0; const GRAVITY: f32 = 0.02; 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., y: 0. }, ..Default::default() }) .add_plugins(RapierPhysicsPlugin::::pixels_per_meter(SCALE)) .add_plugins(TwiteServerPlugin) .add_systems(Startup, spawn_planets) .add_systems(Update, on_message) .add_systems(Update, on_close) .add_systems(FixedUpdate, on_position_change) .add_systems(FixedUpdate, do_gravity) .run(); info!("Goodbye!"); } 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 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. / SCALE, angle.sin() * 1100. / 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(25.0 / SCALE, 25.0 / SCALE)) .insert(AdditionalMassProperties::Mass(10.0)) .insert(ExternalImpulse { impulse: Vec2::ZERO, torque_impulse: 0., }) .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 do_gravity( 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(); } } }