network code and stuff
This commit is contained in:
parent
e5a83229f1
commit
b0eadd5420
805
Cargo.lock
generated
805
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,3 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["client", "server"]
|
members = ["client", "server", "shared"]
|
||||||
|
@ -5,6 +5,11 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = "0.8"
|
bevy = "0.8"
|
||||||
|
bevy_renet = "0.0.5"
|
||||||
|
bevy_egui = "0.15.0"
|
||||||
|
renet_visualizer = "0.0.2"
|
||||||
|
bincode = "1.3.1"
|
||||||
|
daggmask-shared = { path = "../shared" }
|
||||||
|
|
||||||
# Enable a small amount of optimization in debug mode
|
# Enable a small amount of optimization in debug mode
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
@ -1,59 +1,246 @@
|
|||||||
use bevy::prelude::*;
|
use std::{collections::HashMap, net::UdpSocket, time::SystemTime};
|
||||||
|
|
||||||
fn main() {
|
use bevy::{
|
||||||
App::new()
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
.add_plugins(DefaultPlugins)
|
prelude::*,
|
||||||
.add_startup_system(setup)
|
};
|
||||||
.add_startup_system(spawn_player)
|
|
||||||
.add_system(move_player)
|
use bevy_egui::{EguiContext, EguiPlugin};
|
||||||
.run();
|
use bevy_renet::{
|
||||||
|
renet::{ClientAuthentication, RenetClient, RenetError},
|
||||||
|
run_if_client_connected, RenetClientPlugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
use daggmask_shared::{
|
||||||
|
client_connection_config, setup_level, ClientChannel, NetworkFrame, PlayerCommand, PlayerInput,
|
||||||
|
ServerChannel, ServerMessages, PROTOCOL_ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
use renet_visualizer::{RenetClientVisualizer, RenetVisualizerStyle};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct ControlledPlayer;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct NetworkMapping(HashMap<Entity, Entity>);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PlayerInfo {
|
||||||
|
client_entity: Entity,
|
||||||
|
server_entity: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
#[derive(Debug, Default)]
|
||||||
info!("hehe");
|
struct ClientLobby {
|
||||||
let mut camera_bundle = Camera2dBundle::default();
|
players: HashMap<u64, PlayerInfo>,
|
||||||
camera_bundle.projection.scale = 1. / 50.;
|
}
|
||||||
commands.spawn_bundle(camera_bundle);
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MostRecentTick(Option<u32>);
|
||||||
|
|
||||||
|
fn new_renet_client() -> RenetClient {
|
||||||
|
let server_addr = "127.0.0.1:5000".parse().unwrap();
|
||||||
|
let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||||
|
let connection_config = client_connection_config();
|
||||||
|
let current_time = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap();
|
||||||
|
let client_id = current_time.as_millis() as u64;
|
||||||
|
let authentication = ClientAuthentication::Unsecure {
|
||||||
|
client_id,
|
||||||
|
protocol_id: PROTOCOL_ID,
|
||||||
|
server_addr,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
RenetClient::new(current_time, socket, 1, connection_config, authentication).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(DefaultPlugins);
|
||||||
|
app.add_plugin(RenetClientPlugin);
|
||||||
|
app.add_plugin(TransformPlugin);
|
||||||
|
app.add_plugin(FrameTimeDiagnosticsPlugin::default());
|
||||||
|
app.add_plugin(LogDiagnosticsPlugin::default());
|
||||||
|
app.add_plugin(EguiPlugin);
|
||||||
|
|
||||||
|
app.add_event::<PlayerCommand>();
|
||||||
|
|
||||||
|
app.insert_resource(ClientLobby::default());
|
||||||
|
app.insert_resource(PlayerInput::default());
|
||||||
|
app.insert_resource(MostRecentTick(None));
|
||||||
|
app.insert_resource(new_renet_client());
|
||||||
|
app.insert_resource(RenetClientVisualizer::<200>::new(
|
||||||
|
RenetVisualizerStyle::default(),
|
||||||
|
));
|
||||||
|
app.insert_resource(NetworkMapping::default());
|
||||||
|
|
||||||
|
app.add_system(player_input);
|
||||||
|
app.add_system(client_send_input.with_run_criteria(run_if_client_connected));
|
||||||
|
app.add_system(client_send_player_commands.with_run_criteria(run_if_client_connected));
|
||||||
|
app.add_system(client_sync_players.with_run_criteria(run_if_client_connected));
|
||||||
|
app.add_system(update_visulizer_system);
|
||||||
|
|
||||||
|
app.add_startup_system(setup_level);
|
||||||
|
app.add_system(panic_on_error_system);
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any error is found we just panic
|
||||||
|
fn panic_on_error_system(mut renet_error: EventReader<RenetError>) {
|
||||||
|
for e in renet_error.iter() {
|
||||||
|
panic!("{}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_visulizer_system(
|
||||||
|
mut egui_context: ResMut<EguiContext>,
|
||||||
|
mut visualizer: ResMut<RenetClientVisualizer<200>>,
|
||||||
|
client: Res<RenetClient>,
|
||||||
|
mut show_visualizer: Local<bool>,
|
||||||
|
keyboard_input: Res<Input<KeyCode>>,
|
||||||
|
) {
|
||||||
|
visualizer.add_network_info(client.network_info());
|
||||||
|
if keyboard_input.just_pressed(KeyCode::F1) {
|
||||||
|
*show_visualizer = !*show_visualizer;
|
||||||
|
}
|
||||||
|
if *show_visualizer {
|
||||||
|
visualizer.show_window(egui_context.ctx_mut());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn player_input(
|
||||||
|
keyboard_input: Res<Input<KeyCode>>,
|
||||||
|
mut player_input: ResMut<PlayerInput>,
|
||||||
|
mouse_button_input: Res<Input<MouseButton>>,
|
||||||
|
target_query: Query<&Transform, With<Target>>,
|
||||||
|
mut player_commands: EventWriter<PlayerCommand>,
|
||||||
|
most_recent_tick: Res<MostRecentTick>,
|
||||||
|
) {
|
||||||
|
player_input.left = keyboard_input.pressed(KeyCode::A) || keyboard_input.pressed(KeyCode::Left);
|
||||||
|
player_input.right =
|
||||||
|
keyboard_input.pressed(KeyCode::D) || keyboard_input.pressed(KeyCode::Right);
|
||||||
|
player_input.up = keyboard_input.pressed(KeyCode::W) || keyboard_input.pressed(KeyCode::Up);
|
||||||
|
player_input.down = keyboard_input.pressed(KeyCode::S) || keyboard_input.pressed(KeyCode::Down);
|
||||||
|
player_input.most_recent_tick = most_recent_tick.0;
|
||||||
|
|
||||||
|
if mouse_button_input.just_pressed(MouseButton::Left) {
|
||||||
|
player_commands.send(PlayerCommand::BasicAttack {
|
||||||
|
cast_at: Vec2::default(), // TODO: spawn projectiles correctly
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_send_input(player_input: Res<PlayerInput>, mut client: ResMut<RenetClient>) {
|
||||||
|
let input_message = bincode::serialize(&*player_input).unwrap();
|
||||||
|
|
||||||
|
client.send_message(ClientChannel::Input.id(), input_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_send_player_commands(
|
||||||
|
mut player_commands: EventReader<PlayerCommand>,
|
||||||
|
mut client: ResMut<RenetClient>,
|
||||||
|
) {
|
||||||
|
for command in player_commands.iter() {
|
||||||
|
let command_message = bincode::serialize(command).unwrap();
|
||||||
|
client.send_message(ClientChannel::Command.id(), command_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_sync_players(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
mut client: ResMut<RenetClient>,
|
||||||
|
mut lobby: ResMut<ClientLobby>,
|
||||||
|
mut network_mapping: ResMut<NetworkMapping>,
|
||||||
|
mut most_recent_tick: ResMut<MostRecentTick>,
|
||||||
|
) {
|
||||||
|
let client_id = client.client_id();
|
||||||
|
while let Some(message) = client.receive_message(ServerChannel::ServerMessages.id()) {
|
||||||
|
let server_message = bincode::deserialize(&message).unwrap();
|
||||||
|
match server_message {
|
||||||
|
ServerMessages::PlayerCreate {
|
||||||
|
id,
|
||||||
|
translation,
|
||||||
|
entity,
|
||||||
|
} => {
|
||||||
|
println!("Player {} connected.", id);
|
||||||
|
let mut client_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: Transform::from_xyz(translation[0], translation[1], translation[2]),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
if client_id == id {
|
||||||
|
client_entity.insert(ControlledPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let player_info = PlayerInfo {
|
||||||
|
server_entity: entity,
|
||||||
|
client_entity: client_entity.id(),
|
||||||
|
};
|
||||||
|
lobby.players.insert(id, player_info);
|
||||||
|
network_mapping.0.insert(entity, client_entity.id());
|
||||||
|
}
|
||||||
|
ServerMessages::PlayerRemove { id } => {
|
||||||
|
println!("Player {} disconnected.", id);
|
||||||
|
if let Some(PlayerInfo {
|
||||||
|
server_entity,
|
||||||
|
client_entity,
|
||||||
|
}) = lobby.players.remove(&id)
|
||||||
|
{
|
||||||
|
commands.entity(client_entity).despawn();
|
||||||
|
network_mapping.0.remove(&server_entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServerMessages::SpawnProjectile {
|
||||||
|
entity,
|
||||||
|
location,
|
||||||
|
direction,
|
||||||
|
} => {
|
||||||
|
let projectile_entity = commands.spawn_bundle(SpriteBundle {
|
||||||
|
sprite: Sprite {
|
||||||
|
color: Color::rgb(0.25, 0.25, 0.75),
|
||||||
|
custom_size: Some(Vec2::new(50.0, 100.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
network_mapping.0.insert(entity, projectile_entity.id());
|
||||||
|
}
|
||||||
|
ServerMessages::DespawnProjectile { entity } => {
|
||||||
|
if let Some(entity) = network_mapping.0.remove(&entity) {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(message) = client.receive_message(ServerChannel::NetworkFrame.id()) {
|
||||||
|
let frame: NetworkFrame = bincode::deserialize(&message).unwrap();
|
||||||
|
match most_recent_tick.0 {
|
||||||
|
None => most_recent_tick.0 = Some(frame.tick),
|
||||||
|
Some(tick) if tick < frame.tick => most_recent_tick.0 = Some(frame.tick),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..frame.entities.entities.len() {
|
||||||
|
if let Some(entity) = network_mapping.0.get(&frame.entities.entities[i]) {
|
||||||
|
let translation = frame.entities.translations[i].into();
|
||||||
|
let transform = Transform {
|
||||||
|
translation,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
commands.entity(*entity).insert(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct Player;
|
struct Target;
|
||||||
|
|
||||||
fn spawn_player(mut commands: Commands) {
|
|
||||||
commands
|
|
||||||
.spawn_bundle(SpriteBundle {
|
|
||||||
sprite: Sprite {
|
|
||||||
color: Color::rgb(0., 0.47, 1.),
|
|
||||||
custom_size: Some(Vec2::new(1., 1.)),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.insert(Player);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_player(keys: Res<Input<KeyCode>>, mut player_query: Query<&mut Transform, With<Player>>) {
|
|
||||||
let mut direction = Vec2::ZERO;
|
|
||||||
if keys.any_pressed([KeyCode::Up, KeyCode::W]) {
|
|
||||||
direction.y += 1.;
|
|
||||||
}
|
|
||||||
if keys.any_pressed([KeyCode::Down, KeyCode::S]) {
|
|
||||||
direction.y -= 1.;
|
|
||||||
}
|
|
||||||
if keys.any_pressed([KeyCode::Right, KeyCode::D]) {
|
|
||||||
direction.x += 1.;
|
|
||||||
}
|
|
||||||
if keys.any_pressed([KeyCode::Left, KeyCode::A]) {
|
|
||||||
direction.x -= 1.;
|
|
||||||
}
|
|
||||||
if direction == Vec2::ZERO {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let move_speed = 0.13;
|
|
||||||
let move_delta = (direction * move_speed).extend(0.);
|
|
||||||
|
|
||||||
for mut transform in player_query.iter_mut() {
|
|
||||||
transform.translation += move_delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -3,5 +3,12 @@ name = "daggmask-server"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[dependencies]
|
||||||
|
bevy = "0.8.0"
|
||||||
|
bevy_renet = "0.0.5"
|
||||||
|
bevy_egui = "0.15.0"
|
||||||
|
bevy_rapier2d = "0.16.0"
|
||||||
|
renet_visualizer = "0.0.2"
|
||||||
|
bincode = "1.3.1"
|
||||||
|
|
||||||
|
daggmask-shared = { path = "../shared" }
|
||||||
|
@ -1,45 +1,291 @@
|
|||||||
use std::net::UdpSocket;
|
use std::{collections::HashMap, net::UdpSocket, time::SystemTime};
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
use bevy::{
|
||||||
// replace xxxx with your desired port
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
// replace S.S.S.S with your server address
|
prelude::*,
|
||||||
let socket = UdpSocket::bind("S.S.S.S:xxxx")?;
|
};
|
||||||
let mut peers: Vec<String> = vec![];
|
|
||||||
|
|
||||||
loop {
|
use bevy_egui::{EguiContext, EguiPlugin};
|
||||||
let mut buf = [0; 1024];
|
use bevy_rapier2d::prelude::*;
|
||||||
let (_, src) = socket.recv_from(&mut buf)?;
|
use bevy_renet::{
|
||||||
let stringified_buff = String::from_utf8(buf.to_vec()).unwrap();
|
renet::{RenetServer, ServerAuthentication, ServerConfig, ServerEvent},
|
||||||
let stringified_buff = stringified_buff.trim_matches(char::from(0));
|
RenetServerPlugin,
|
||||||
|
};
|
||||||
|
|
||||||
println!("[NEW MESSAGE]{:?} => {:?}", src, stringified_buff);
|
use daggmask_shared::{
|
||||||
|
server_connection_config, setup_level, spawn_projectile, ClientChannel, NetworkFrame, Player,
|
||||||
|
PlayerCommand, PlayerInput, Projectile, ServerChannel, ServerMessages, PROTOCOL_ID,
|
||||||
|
};
|
||||||
|
|
||||||
if stringified_buff != "register" {
|
use renet_visualizer::RenetServerVisualizer;
|
||||||
continue;
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ServerLobby {
|
||||||
|
pub players: HashMap<u64, Entity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !peers.contains(&format!("{}", src)) {
|
#[derive(Debug, Default)]
|
||||||
peers.push(format!("{}", src));
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
for p in &peers {
|
fn main() {
|
||||||
let filtered_peers = filter_peers(&peers, p);
|
let mut app = App::new();
|
||||||
|
app.add_plugins(DefaultPlugins);
|
||||||
|
|
||||||
if !filtered_peers.is_empty() {
|
app.add_plugin(RenetServerPlugin);
|
||||||
socket.send_to(filtered_peers.join(",").as_bytes(), p)?;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter_peers(peers: &Vec<String>, filter: &String) -> Vec<String> {
|
while let Some(message) = server.receive_message(client_id, ClientChannel::Input.id()) {
|
||||||
let mut new_peers: Vec<String> = vec![];
|
let input: PlayerInput = bincode::deserialize(&message).unwrap();
|
||||||
|
client_ticks.0.insert(client_id, input.most_recent_tick);
|
||||||
|
|
||||||
for p in peers {
|
if let Some(player_entity) = lobby.players.get(&client_id) {
|
||||||
if p != filter {
|
commands.entity(*player_entity).insert(input);
|
||||||
new_peers.push(String::from(p));
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new_peers
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
11
shared/Cargo.toml
Normal file
11
shared/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "daggmask-shared"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy = "0.8.0"
|
||||||
|
bevy_renet = "0.0.5"
|
||||||
|
bevy_egui = "0.15.0"
|
||||||
|
bevy_rapier2d = "0.16.0"
|
||||||
|
serde = { version = "1.0", features = [ "derive" ] }
|
170
shared/src/lib.rs
Normal file
170
shared/src/lib.rs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_renet::renet::{
|
||||||
|
ChannelConfig, ReliableChannelConfig, RenetConnectionConfig, UnreliableChannelConfig,
|
||||||
|
NETCODE_KEY_BYTES,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy_rapier2d::geometry::Collider;
|
||||||
|
use bevy_rapier2d::prelude::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub const PRIVATE_KEY: &[u8; NETCODE_KEY_BYTES] = b"en grisars katt hund hemlis key."; // 32-bytes
|
||||||
|
pub const PROTOCOL_ID: u64 = 7;
|
||||||
|
|
||||||
|
#[derive(Debug, Component)]
|
||||||
|
pub struct Player {
|
||||||
|
pub id: u64,
|
||||||
|
pub location: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Component)]
|
||||||
|
pub struct PlayerInput {
|
||||||
|
pub most_recent_tick: Option<u32>,
|
||||||
|
pub up: bool,
|
||||||
|
pub down: bool,
|
||||||
|
pub left: bool,
|
||||||
|
pub right: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Component)]
|
||||||
|
pub enum PlayerCommand {
|
||||||
|
BasicAttack { cast_at: Vec2 },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ClientChannel {
|
||||||
|
Input,
|
||||||
|
Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ServerChannel {
|
||||||
|
ServerMessages,
|
||||||
|
NetworkFrame,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Component)]
|
||||||
|
pub enum ServerMessages {
|
||||||
|
PlayerCreate {
|
||||||
|
entity: Entity,
|
||||||
|
id: u64,
|
||||||
|
translation: [f32; 3],
|
||||||
|
},
|
||||||
|
PlayerRemove {
|
||||||
|
id: u64,
|
||||||
|
},
|
||||||
|
SpawnProjectile {
|
||||||
|
entity: Entity,
|
||||||
|
location: Vec2,
|
||||||
|
direction: Vec2,
|
||||||
|
},
|
||||||
|
DespawnProjectile {
|
||||||
|
entity: Entity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct NetworkedEntities {
|
||||||
|
pub entities: Vec<Entity>,
|
||||||
|
pub translations: Vec<[f32; 3]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct NetworkFrame {
|
||||||
|
pub tick: u32,
|
||||||
|
pub entities: NetworkedEntities,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientChannel {
|
||||||
|
pub fn id(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Input => 0,
|
||||||
|
Self::Command => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channels_config() -> Vec<ChannelConfig> {
|
||||||
|
vec![
|
||||||
|
ReliableChannelConfig {
|
||||||
|
channel_id: Self::Input.id(),
|
||||||
|
message_resend_time: Duration::ZERO,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
ReliableChannelConfig {
|
||||||
|
channel_id: Self::Command.id(),
|
||||||
|
message_resend_time: Duration::ZERO,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerChannel {
|
||||||
|
pub fn id(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::NetworkFrame => 0,
|
||||||
|
Self::ServerMessages => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channels_config() -> Vec<ChannelConfig> {
|
||||||
|
vec![
|
||||||
|
UnreliableChannelConfig {
|
||||||
|
channel_id: Self::NetworkFrame.id(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
ReliableChannelConfig {
|
||||||
|
channel_id: Self::ServerMessages.id(),
|
||||||
|
message_resend_time: Duration::from_millis(200),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_connection_config() -> RenetConnectionConfig {
|
||||||
|
RenetConnectionConfig {
|
||||||
|
send_channels_config: ClientChannel::channels_config(),
|
||||||
|
receive_channels_config: ServerChannel::channels_config(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn server_connection_config() -> RenetConnectionConfig {
|
||||||
|
RenetConnectionConfig {
|
||||||
|
send_channels_config: ServerChannel::channels_config(),
|
||||||
|
receive_channels_config: ClientChannel::channels_config(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up the level
|
||||||
|
pub fn setup_level(mut _commands: Commands) {
|
||||||
|
info!("bygger level...");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_projectile(commands: &mut Commands, location: Vec2, direction: Vec2) -> Entity {
|
||||||
|
commands
|
||||||
|
.spawn()
|
||||||
|
.insert(Collider::ball(0.1))
|
||||||
|
.insert(Velocity::linear(direction * 10.))
|
||||||
|
.insert(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
.insert(Projectile {
|
||||||
|
duration: Timer::from_seconds(1.5, false),
|
||||||
|
location,
|
||||||
|
direction,
|
||||||
|
})
|
||||||
|
.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Component)]
|
||||||
|
pub struct Projectile {
|
||||||
|
pub duration: Timer,
|
||||||
|
pub location: Vec2,
|
||||||
|
pub direction: Vec2,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user