Implement generic online system #82

Merged
CoCo_Sol merged 14 commits from impl-online into main 2024-03-25 05:45:17 +00:00
9 changed files with 228 additions and 13 deletions

10
Cargo.lock generated
View file

@ -1314,10 +1314,12 @@ dependencies = [
name = "border-wars"
version = "0.1.0"
dependencies = [
"bevnet",
"bevy",
"bevy_egui",
"noise",
"paste",
"serde",
]
[[package]]
@ -3805,18 +3807,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.196"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.196"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",

View file

@ -15,3 +15,5 @@ bevy = "0.12.1"
bevy_egui = "0.24.0"
noise = "0.8.2"
paste = "1.0.14"
bevnet = { path = "../bevnet" }
serde = "1.0.197"

View file

@ -1,9 +1,13 @@
//! The file that contains utility functions, enums, structs for the game.
use bevnet::Uuid;
use bevy::prelude::*;
use networking::PlayerRank;
use serde::{Deserialize, Serialize};
pub mod camera;
pub mod map;
pub mod networking;
pub mod responsive_scale;
pub mod scenes;
@ -20,3 +24,16 @@ pub enum CurrentScene {
/// When we play this wonderful game.
Game,
}
/// A player in the game.
#[derive(Serialize, Deserialize, Clone, Debug, Component, Resource)]
pub struct Player {
/// The name of the player.
pub name: String,
/// The rank of the player.
pub rank: PlayerRank,
/// The uuid of the player.
pub uuid: Uuid,
}

View file

@ -5,6 +5,7 @@ use border_wars::camera::CameraPlugin;
use border_wars::map::generation::MapGenerationPlugin;
use border_wars::map::renderer::RendererPlugin;
use border_wars::map::selected_tile::SelectTilePlugin;
use border_wars::networking::NetworkingPlugin;
use border_wars::scenes::ScenesPlugin;
fn main() {
@ -14,6 +15,7 @@ fn main() {
.add_plugins(RendererPlugin)
.add_plugins(CameraPlugin)
.add_plugins(SelectTilePlugin)
.add_plugins(NetworkingPlugin)
.add_plugins(MapGenerationPlugin)
.run();
}

View file

@ -0,0 +1,95 @@
//! All the code related to the connection.
use bevnet::{Connection, NetworkAppExt, Receive, SendTo};
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use super::PlayerRank;
use crate::{CurrentScene, Player};
/// A plugin that manage connections (add, remove).
pub struct ConnectionPlugin;
impl Plugin for ConnectionPlugin {
fn build(&self, app: &mut App) {
app.add_network_event::<RequestJoin>()
.add_network_event::<AddPlayer>()
.add_network_event::<RemovePlayer>()
.add_systems(
Update,
(accept_connection, handle_new_player, handle_remove_player),
);
}
}
/// An event that is trigger when a new player request to join a game.
#[derive(Event, Serialize, Deserialize)]
pub struct RequestJoin(pub Player);
/// An event that is trigger when a new player is added.
#[derive(Event, Serialize, Deserialize)]
pub struct AddPlayer(Player);
/// An event that is trigger when a player is removed.
#[derive(Event, Serialize, Deserialize)]
pub struct RemovePlayer(pub Player);
/// A fonction that accept new connection.
/// It add the player to the list of all players.
pub fn accept_connection(
all_players_query: Query<&Player>,
mut requests_join_event: EventReader<Receive<RequestJoin>>,
mut add_players_event: EventWriter<SendTo<AddPlayer>>,
state: Res<State<CurrentScene>>,
CoCo_Sol marked this conversation as resolved Outdated

Pas compris pk il doit être admin ni qui est le joueur en question

Pas compris pk il doit être admin ni qui est le joueur en question
) {
for request_join in requests_join_event.read() {
let mut new_player = request_join.1.0.clone();
let current_state = *state.get();
if current_state == CurrentScene::Menu {
return;
} else if current_state == CurrentScene::Game {
new_player.rank = PlayerRank::Spectator;
}
add_players_event.send(SendTo(new_player.uuid, AddPlayer(new_player.clone())));
for old_player in all_players_query.iter() {
// Link all players
add_players_event.send(SendTo(old_player.uuid, AddPlayer(new_player.clone())));
add_players_event.send(SendTo(new_player.uuid, AddPlayer(old_player.clone())));
}
}
}
/// A fonction that handle new players when a events is received.
pub fn handle_new_player(mut add_players: EventReader<Receive<AddPlayer>>, mut commands: Commands) {
for add_player in add_players.read() {
commands.spawn(add_player.1.0.clone());
}
}
/// A fonction that handle remove players when a events is received.
pub fn handle_remove_player(
mut remove_players: EventReader<Receive<RemovePlayer>>,
mut commands: Commands,
all_players_query: Query<(Entity, &Player)>,
connection: Res<Connection>,
mut next_scene: ResMut<NextState<CurrentScene>>,
) {
for remove_player in remove_players.read() {
if Some(remove_player.1.0.uuid) == connection.identifier() {
next_scene.set(CurrentScene::Menu);
all_players_query.iter().for_each(|(entity, _)| {
commands.entity(entity).despawn();
});
return;
}
for (entity, player) in all_players_query.iter() {
if remove_player.1.0.uuid == player.uuid {
commands.entity(entity).despawn();
}
}
}
}

View file

@ -0,0 +1,32 @@
//! All the code related to the networking.
use bevnet::NetworkPlugin;
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use self::connection::ConnectionPlugin;
pub mod connection;
/// The plugin for the networking.
pub struct NetworkingPlugin;
impl Plugin for NetworkingPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(NetworkPlugin::new("relay.cocosol.fr".to_string()))
.add_plugins(ConnectionPlugin);
}
}
/// The rank of the player.
#[derive(PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Debug)]
pub enum PlayerRank {
/// A spectator. He does not play the game, just renderer the game.
Spectator,
/// An admin. He manages the game and play the game.
Admin,
CoCo_Sol marked this conversation as resolved Outdated

Es ce que l'admin peut aussi jouer ?

Es ce que l'admin peut aussi jouer ?
/// The player. He can join the game and play.
Player,
}

View file

@ -1,9 +1,12 @@
//! The lobby of the game.
use bevnet::{Connection, SendTo};
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts};
use crate::CurrentScene;
use crate::networking::connection::RemovePlayer;
use crate::networking::PlayerRank;
use crate::{CurrentScene, Player};
/// The plugin for the lobby.
pub struct LobbyPlugin;
@ -15,7 +18,21 @@ impl Plugin for LobbyPlugin {
}
/// Display the UI of the lobby.
fn lobby_ui(mut ctx: EguiContexts, mut next_scene: ResMut<NextState<CurrentScene>>) {
fn lobby_ui(
mut ctx: EguiContexts,
mut next_scene: ResMut<NextState<CurrentScene>>,
connection: Res<Connection>,
all_players_query: Query<&Player>,
mut kick_player: EventWriter<SendTo<RemovePlayer>>,
) {
// Get our player info.
let Some(self_player) = all_players_query
.iter()
.find(|player| connection.identifier() == Some(player.uuid))
else {
return;
};
egui::CentralPanel::default().show(ctx.ctx_mut(), |ui| {
ui.heading("Border Wars");
@ -23,14 +40,30 @@ fn lobby_ui(mut ctx: EguiContexts, mut next_scene: ResMut<NextState<CurrentScene
ui.label("Game created");
ui.horizontal(|ui| {
if self_player.rank != PlayerRank::Admin {
return;
}
ui.label("Game ID: ");
// TODO : get the game ID and display it.
ui.label("connection_string");
ui.text_edit_singleline(&mut connection.identifier().unwrap_or_default().to_string());
});
ui.separator();
if ui.button("Run the game").clicked() {
for player in all_players_query.iter() {
ui.label(player.name.to_string());
if self_player.rank == PlayerRank::Admin
&& player.rank != PlayerRank::Admin
&& ui.button("Remove").clicked()
{
for sender_id in all_players_query.iter() {
kick_player.send(SendTo(sender_id.uuid, RemovePlayer(player.clone())));
}
}
ui.separator();
}
if self_player.rank == PlayerRank::Admin && ui.button("Run the game").clicked() {
next_scene.set(CurrentScene::Game);
// TODO: run the game
}

View file

@ -1,9 +1,12 @@
//! The main menu of the game.
use bevnet::{Connection, SendTo, Uuid};
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts};
use crate::CurrentScene;
use crate::networking::connection::RequestJoin;
use crate::networking::PlayerRank;
use crate::{CurrentScene, Player};
/// The plugin for the menu.
pub struct MenuPlugin;
@ -13,25 +16,50 @@ impl Plugin for MenuPlugin {
app.add_systems(Update, menu_ui.run_if(in_state(CurrentScene::Menu)));
}
}
/// Display the UI of the menu to host a game or join one.
fn menu_ui(
mut ctx: EguiContexts,
mut connection_string: Local<String>,
mut next_scene: ResMut<NextState<CurrentScene>>,
mut request_join: EventWriter<SendTo<RequestJoin>>,
mut name: Local<String>,
connection: Res<Connection>,
mut commands: Commands,
) {
let Some(uuid) = connection.identifier() else {
return;
};
egui::CentralPanel::default().show(ctx.ctx_mut(), |ui| {
ui.heading("Border Wars");
ui.separator();
ui.label("Name");
ui.text_edit_singleline(&mut *name);
ui.separator();
ui.label("Connect to an existing game:");
ui.horizontal(|ui| {
ui.label("Game ID: ");
ui.text_edit_singleline(&mut *connection_string);
let Ok(game_id) = Uuid::parse_str(&connection_string) else {
return;
};
if ui.button("Join").clicked() {
next_scene.set(CurrentScene::Game);
// TODO: connect to the game
next_scene.set(CurrentScene::Lobby);
request_join.send(SendTo(
game_id,
RequestJoin(Player {
name: name.clone(),
rank: PlayerRank::Player,
uuid,
}),
));
}
});
@ -39,7 +67,11 @@ fn menu_ui(
if ui.button("Create new game").clicked() {
next_scene.set(CurrentScene::Lobby);
// TODO: create a new game
commands.spawn(Player {
name: name.clone(),
rank: PlayerRank::Admin,
uuid,
});
}
});
}

View file

@ -80,7 +80,7 @@ impl Connection {
path.push(".relay-data");
// Check if the file exists.
match path.exists() {
match false {
true => {
// Read the file and parse the identifier and secret key.
let contents = fs::read(&path)?;