292 lines
10 KiB
Rust
292 lines
10 KiB
Rust
use std::{collections::HashMap, net::UdpSocket, time::SystemTime};
|
|
|
|
use bevy::{
|
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
|
prelude::*,
|
|
};
|
|
|
|
use bevy_egui::{EguiContext, EguiPlugin};
|
|
use bevy_rapier2d::prelude::*;
|
|
use bevy_renet::{
|
|
renet::{RenetServer, ServerAuthentication, ServerConfig, ServerEvent},
|
|
RenetServerPlugin,
|
|
};
|
|
|
|
use daggmask_shared::{
|
|
server_connection_config, setup_level, spawn_projectile, ClientChannel, NetworkFrame, Player,
|
|
PlayerCommand, PlayerInput, Projectile, ServerChannel, ServerMessages, PROTOCOL_ID,
|
|
};
|
|
|
|
use renet_visualizer::RenetServerVisualizer;
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct ServerLobby {
|
|
pub players: HashMap<u64, Entity>,
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct NetworkTick(u32);
|
|
|
|
// Clients last received ticks
|
|
#[derive(Debug, Default)]
|
|
struct ClientTicks(HashMap<u64, Option<u32>>);
|
|
|
|
const PLAYER_MOVE_SPEED: f32 = 5.0;
|
|
|
|
fn new_renet_server() -> RenetServer {
|
|
let server_addr = "127.0.0.1:5000".parse().unwrap();
|
|
let socket = UdpSocket::bind(server_addr).unwrap();
|
|
let connection_config = server_connection_config();
|
|
let server_config =
|
|
ServerConfig::new(64, PROTOCOL_ID, server_addr, ServerAuthentication::Unsecure);
|
|
let current_time = SystemTime::now()
|
|
.duration_since(SystemTime::UNIX_EPOCH)
|
|
.unwrap();
|
|
RenetServer::new(current_time, server_config, connection_config, socket).unwrap()
|
|
}
|
|
|
|
fn main() {
|
|
let mut app = App::new();
|
|
app.add_plugins(DefaultPlugins);
|
|
|
|
app.add_plugin(RenetServerPlugin);
|
|
app.add_plugin(RapierPhysicsPlugin::<NoUserData>::default());
|
|
app.add_plugin(RapierDebugRenderPlugin::default());
|
|
app.add_plugin(FrameTimeDiagnosticsPlugin::default());
|
|
app.add_plugin(LogDiagnosticsPlugin::default());
|
|
app.add_plugin(EguiPlugin);
|
|
|
|
app.insert_resource(ServerLobby::default());
|
|
app.insert_resource(NetworkTick(0));
|
|
app.insert_resource(ClientTicks::default());
|
|
app.insert_resource(new_renet_server());
|
|
app.insert_resource(RenetServerVisualizer::<200>::default());
|
|
|
|
app.add_system(server_update_system);
|
|
app.add_system(server_network_sync);
|
|
app.add_system(move_players_system);
|
|
app.add_system(update_projectiles_system);
|
|
app.add_system(update_visulizer_system);
|
|
app.add_system(despawn_projectile_system);
|
|
app.add_system_to_stage(CoreStage::PostUpdate, projectile_on_removal_system);
|
|
|
|
app.add_startup_system(setup_level);
|
|
app.add_startup_system(setup_simple_camera);
|
|
|
|
app.run();
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn server_update_system(
|
|
mut server_events: EventReader<ServerEvent>,
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
mut lobby: ResMut<ServerLobby>,
|
|
mut server: ResMut<RenetServer>,
|
|
mut visualizer: ResMut<RenetServerVisualizer<200>>,
|
|
mut client_ticks: ResMut<ClientTicks>,
|
|
players: Query<(Entity, &Player, &Transform)>,
|
|
) {
|
|
for event in server_events.iter() {
|
|
match event {
|
|
ServerEvent::ClientConnected(id, _) => {
|
|
println!("Player {} connected.", id);
|
|
visualizer.add_client(*id);
|
|
|
|
// Initialize other players for this new client
|
|
for (entity, player, transform) in players.iter() {
|
|
let translation: [f32; 3] = transform.translation.into();
|
|
let message = bincode::serialize(&ServerMessages::PlayerCreate {
|
|
id: player.id,
|
|
entity,
|
|
translation,
|
|
})
|
|
.unwrap();
|
|
server.send_message(*id, ServerChannel::ServerMessages.id(), message);
|
|
}
|
|
|
|
// Spawn new player
|
|
let transform = Transform::from_xyz(0., 0.51, 0.);
|
|
let player_entity = commands
|
|
.spawn_bundle(PbrBundle {
|
|
mesh: meshes.add(Mesh::from(shape::Capsule::default())),
|
|
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
|
transform,
|
|
..Default::default()
|
|
})
|
|
.insert(RigidBody::Dynamic)
|
|
.insert(LockedAxes::ROTATION_LOCKED | LockedAxes::TRANSLATION_LOCKED_Y)
|
|
.insert(Collider::capsule_y(0.5, 0.5))
|
|
.insert(PlayerInput::default())
|
|
.insert(Velocity::default())
|
|
.insert(Player {
|
|
id: *id,
|
|
location: Vec2::new(10., 10.),
|
|
})
|
|
.id();
|
|
|
|
lobby.players.insert(*id, player_entity);
|
|
|
|
let translation: [f32; 3] = transform.translation.into();
|
|
let message = bincode::serialize(&ServerMessages::PlayerCreate {
|
|
id: *id,
|
|
entity: player_entity,
|
|
translation,
|
|
})
|
|
.unwrap();
|
|
|
|
server.broadcast_message(ServerChannel::ServerMessages.id(), message);
|
|
}
|
|
ServerEvent::ClientDisconnected(id) => {
|
|
println!("Player {} disconnected.", id);
|
|
visualizer.remove_client(*id);
|
|
client_ticks.0.remove(id);
|
|
if let Some(player_entity) = lobby.players.remove(id) {
|
|
commands.entity(player_entity).despawn();
|
|
}
|
|
|
|
let message =
|
|
bincode::serialize(&ServerMessages::PlayerRemove { id: *id }).unwrap();
|
|
server.broadcast_message(ServerChannel::ServerMessages.id(), message);
|
|
}
|
|
}
|
|
}
|
|
|
|
for client_id in server.clients_id().into_iter() {
|
|
while let Some(message) = server.receive_message(client_id, ClientChannel::Command.id()) {
|
|
let command: PlayerCommand = bincode::deserialize(&message).unwrap();
|
|
match command {
|
|
PlayerCommand::BasicAttack { mut cast_at } => {
|
|
println!(
|
|
"Received basic attack from client {}: {:?}",
|
|
client_id, cast_at
|
|
);
|
|
|
|
if let Some(player_entity) = lobby.players.get(&client_id) {
|
|
if let Ok((_, _, player_transform)) = players.get(*player_entity) {
|
|
cast_at[1] = player_transform.translation[1];
|
|
|
|
let projectile_entity =
|
|
spawn_projectile(&mut commands, cast_at, cast_at);
|
|
|
|
let message = ServerMessages::SpawnProjectile {
|
|
entity: projectile_entity,
|
|
location: cast_at,
|
|
direction: cast_at,
|
|
};
|
|
|
|
let message = bincode::serialize(&message).unwrap();
|
|
server.broadcast_message(ServerChannel::ServerMessages.id(), message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
while let Some(message) = server.receive_message(client_id, ClientChannel::Input.id()) {
|
|
let input: PlayerInput = bincode::deserialize(&message).unwrap();
|
|
client_ticks.0.insert(client_id, input.most_recent_tick);
|
|
|
|
if let Some(player_entity) = lobby.players.get(&client_id) {
|
|
commands.entity(*player_entity).insert(input);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_projectiles_system(
|
|
mut commands: Commands,
|
|
mut projectiles: Query<(Entity, &mut Projectile)>,
|
|
time: Res<Time>,
|
|
) {
|
|
for (entity, mut projectile) in projectiles.iter_mut() {
|
|
projectile.duration.tick(time.delta());
|
|
|
|
if projectile.duration.finished() {
|
|
commands.entity(entity).despawn();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_visulizer_system(
|
|
mut egui_context: ResMut<EguiContext>,
|
|
mut visualizer: ResMut<RenetServerVisualizer<200>>,
|
|
server: Res<RenetServer>,
|
|
) {
|
|
visualizer.update(&server);
|
|
visualizer.show_window(egui_context.ctx_mut());
|
|
}
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
fn server_network_sync(
|
|
mut tick: ResMut<NetworkTick>,
|
|
mut server: ResMut<RenetServer>,
|
|
networked_entities: Query<(Entity, &Transform), Or<(With<Player>, With<Projectile>)>>,
|
|
) {
|
|
let mut frame = NetworkFrame::default();
|
|
|
|
for (entity, transform) in networked_entities.iter() {
|
|
frame.entities.entities.push(entity);
|
|
frame
|
|
.entities
|
|
.translations
|
|
.push(transform.translation.into());
|
|
}
|
|
|
|
frame.tick = tick.0;
|
|
tick.0 += 1;
|
|
|
|
let sync_message = bincode::serialize(&frame).unwrap();
|
|
|
|
server.broadcast_message(ServerChannel::NetworkFrame.id(), sync_message);
|
|
}
|
|
|
|
fn move_players_system(mut query: Query<(&mut Velocity, &PlayerInput)>) {
|
|
for (mut velocity, input) in query.iter_mut() {
|
|
let x = (input.right as i8 - input.left as i8) as f32;
|
|
let y = (input.down as i8 - input.up as i8) as f32;
|
|
let direction = Vec2::new(x, y).normalize_or_zero();
|
|
velocity.linvel.x = direction.x * PLAYER_MOVE_SPEED;
|
|
velocity.linvel.y = direction.y * PLAYER_MOVE_SPEED;
|
|
}
|
|
}
|
|
|
|
pub fn setup_simple_camera(mut commands: Commands) {
|
|
// camera
|
|
commands.spawn_bundle(Camera3dBundle {
|
|
transform: Transform::from_xyz(-5.5, 5.0, 5.5).looking_at(Vec3::ZERO, Vec3::Y),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
fn despawn_projectile_system(
|
|
mut commands: Commands,
|
|
mut collision_events: EventReader<CollisionEvent>,
|
|
projectile_query: Query<Option<&Projectile>>,
|
|
) {
|
|
for collision_event in collision_events.iter() {
|
|
if let CollisionEvent::Started(entity1, entity2, _) = collision_event {
|
|
if let Ok(Some(_)) = projectile_query.get(*entity1) {
|
|
commands.entity(*entity1).despawn();
|
|
}
|
|
|
|
if let Ok(Some(_)) = projectile_query.get(*entity2) {
|
|
commands.entity(*entity2).despawn();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn projectile_on_removal_system(
|
|
mut server: ResMut<RenetServer>,
|
|
removed_projectiles: RemovedComponents<Projectile>,
|
|
) {
|
|
for entity in removed_projectiles.iter() {
|
|
let message = ServerMessages::DespawnProjectile { entity };
|
|
let message = bincode::serialize(&message).unwrap();
|
|
|
|
server.broadcast_message(ServerChannel::ServerMessages.id(), message);
|
|
}
|
|
}
|