Implement generic online system #82

Merged
CoCo_Sol merged 14 commits from impl-online into main 2024-03-25 05:45:17 +00:00
6 changed files with 77 additions and 87 deletions
Showing only changes of commit 89861980fa - Show all commits

View file

@ -2,8 +2,7 @@
use bevnet::Uuid; use bevnet::Uuid;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::utils::HashMap; use networking::PlayerRank;
use networking::GameRank;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod camera; pub mod camera;
@ -27,15 +26,14 @@ pub enum CurrentScene {
} }
/// A player in the game. /// A player in the game.
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug, Component, Resource)]
pub struct Player { pub struct Player {
/// The name of the player. /// The name of the player.
pub name: String, pub name: String,
/// The rank of the player. /// The rank of the player.
pub rank: GameRank, pub rank: PlayerRank,
}
/// All the players in the game. /// The uuid of the player.
#[derive(Resource, Default)] pub uuid: Uuid,
pub struct AllPlayers(pub HashMap<Uuid, Player>); }

View file

@ -1,11 +1,11 @@
//! All the code related to the connection. //! All the code related to the connection.
use bevnet::{Connection, NetworkAppExt, Receive, SendTo, Uuid}; use bevnet::{Connection, NetworkAppExt, Receive, SendTo};
use bevy::prelude::*; use bevy::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::GameRank; use super::PlayerRank;
use crate::{AllPlayers, CurrentScene, Player}; use crate::{CurrentScene, Player};
/// A plugin that manage connections (add, remove). /// A plugin that manage connections (add, remove).
pub struct ConnectionPlugin; pub struct ConnectionPlugin;
@ -15,7 +15,10 @@ impl Plugin for ConnectionPlugin {
app.add_network_event::<RequestJoin>() app.add_network_event::<RequestJoin>()
.add_network_event::<AddPlayer>() .add_network_event::<AddPlayer>()
.add_network_event::<RemovePlayer>() .add_network_event::<RemovePlayer>()
.add_systems(Update, (accept_connection, handle_change_player)); .add_systems(
Update,
(accept_connection, handle_new_player, handle_remove_player),
);
} }
} }
@ -25,32 +28,21 @@ pub struct RequestJoin(pub Player);
/// An event that is trigger when a new player is added. /// An event that is trigger when a new player is added.
#[derive(Event, Serialize, Deserialize)] #[derive(Event, Serialize, Deserialize)]
pub struct AddPlayer(Uuid, Player); pub struct AddPlayer(Player);
/// An event that is trigger when a player is removed. /// An event that is trigger when a player is removed.
#[derive(Event, Serialize, Deserialize)] #[derive(Event, Serialize, Deserialize)]
pub struct RemovePlayer(pub Uuid); pub struct RemovePlayer(pub Player);
/// A fonction that accept new connection. /// A fonction that accept new connection.
/// It add the player to the list of all players. /// It add the player to the list of all players.
pub fn accept_connection( pub fn accept_connection(
all_players: Res<AllPlayers>, all_players_query: Query<&Player>,
mut requests_join_event: EventReader<Receive<RequestJoin>>, mut requests_join_event: EventReader<Receive<RequestJoin>>,
mut add_players_event: EventWriter<SendTo<AddPlayer>>, mut add_players_event: EventWriter<SendTo<AddPlayer>>,
connection: Res<Connection>,
state: Res<State<CurrentScene>>, state: Res<State<CurrentScene>>,
) { ) {
// Check if the player is an admin
if all_players
.get_by_connection(&connection)
.map(|player| player.rank == GameRank::Admin)
!= Some(true)
{
return;
}
for request_join in requests_join_event.read() { for request_join in requests_join_event.read() {
let new_uuid = request_join.0;
let mut new_player = request_join.1.0.clone(); let mut new_player = request_join.1.0.clone();
let current_state = *state.get(); let current_state = *state.get();
@ -58,42 +50,46 @@ pub fn accept_connection(
if current_state == CurrentScene::Menu { if current_state == CurrentScene::Menu {
return; return;
} else if current_state == CurrentScene::Game { } else if current_state == CurrentScene::Game {
new_player.rank = GameRank::Spectator; new_player.rank = PlayerRank::Spectator;
} }
add_players_event.send(SendTo(new_uuid, AddPlayer(new_uuid, new_player.clone()))); add_players_event.send(SendTo(new_player.uuid, AddPlayer(new_player.clone())));
for (uuid, player) in all_players.0.iter() { for old_player in all_players_query.iter() {
// Link all players // Link all players
add_players_event.send(SendTo(*uuid, AddPlayer(new_uuid, new_player.clone()))); add_players_event.send(SendTo(old_player.uuid, AddPlayer(new_player.clone())));
add_players_event.send(SendTo(new_uuid, AddPlayer(*uuid, player.clone()))); add_players_event.send(SendTo(new_player.uuid, AddPlayer(old_player.clone())));
} }
} }
} }
/// A fonction that handle new / remove players when a events. /// A fonction that handle new players when a events is received.
pub fn handle_change_player( pub fn handle_new_player(mut add_players: EventReader<Receive<AddPlayer>>, mut commands: Commands) {
mut add_players: EventReader<Receive<AddPlayer>>, 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 remove_players: EventReader<Receive<RemovePlayer>>,
mut all_players: ResMut<AllPlayers>, mut commands: Commands,
all_players_query: Query<(Entity, &Player)>,
connection: Res<Connection>, connection: Res<Connection>,
mut next_scene: ResMut<NextState<CurrentScene>>, mut next_scene: ResMut<NextState<CurrentScene>>,
) { ) {
let Some(connection) = connection.identifier() else {
return;
};
for add_player in add_players.read() {
println!("{:?}", add_player.1.1);
all_players.0.insert(add_player.1.0, add_player.1.1.clone());
}
for remove_player in remove_players.read() { for remove_player in remove_players.read() {
if remove_player.1.0 == connection { if Some(remove_player.1.0.uuid) == connection.identifier() {
all_players.0.clear();
next_scene.set(CurrentScene::Menu); next_scene.set(CurrentScene::Menu);
all_players_query.iter().for_each(|(entity, _)| {
commands.entity(entity).despawn();
});
return; return;
} }
all_players.0.remove(&remove_player.1.0); for (entity, player) in all_players_query.iter() {
if remove_player.1.0.uuid == player.uuid {
commands.entity(entity).despawn();
}
}
} }
} }

View file

@ -1,11 +1,10 @@
//! All the code related to the networking. //! All the code related to the networking.
use bevnet::{Connection, NetworkPlugin}; use bevnet::NetworkPlugin;
use bevy::prelude::*; use bevy::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use self::connection::ConnectionPlugin; use self::connection::ConnectionPlugin;
use crate::{AllPlayers, Player};
pub mod connection; pub mod connection;
@ -15,28 +14,19 @@ pub struct NetworkingPlugin;
impl Plugin for NetworkingPlugin { impl Plugin for NetworkingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(NetworkPlugin::new("relay.cocosol.fr".to_string())) app.add_plugins(NetworkPlugin::new("relay.cocosol.fr".to_string()))
.add_plugins(ConnectionPlugin) .add_plugins(ConnectionPlugin);
.init_resource::<AllPlayers>();
} }
} }
/// The rank of the player. /// The rank of the player.
#[derive(PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Debug)] #[derive(PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Debug)]
pub enum GameRank { pub enum PlayerRank {
/// The player is a spectator. He does not play the game. /// A spectator. He does not play the game, just renderer the game.
Spectator, Spectator,
/// The player is an admin. He can remove players. /// An admin. He manages the game and play the game.
Admin, Admin,
/// The player is a player. He can join the game and play. /// The player. He can join the game and play.
Player, Player,
} }
impl AllPlayers {
/// Get the player by a connection.
pub fn get_by_connection(&self, connection: &Connection) -> Option<&Player> {
let uuid = connection.identifier()?;
self.0.get(&uuid)
}
}

View file

@ -5,8 +5,8 @@ use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts}; use bevy_egui::{egui, EguiContexts};
use crate::networking::connection::RemovePlayer; use crate::networking::connection::RemovePlayer;
use crate::networking::GameRank; use crate::networking::PlayerRank;
use crate::{AllPlayers, CurrentScene}; use crate::{CurrentScene, Player};
/// The plugin for the lobby. /// The plugin for the lobby.
pub struct LobbyPlugin; pub struct LobbyPlugin;
@ -22,9 +22,17 @@ fn lobby_ui(
mut ctx: EguiContexts, mut ctx: EguiContexts,
mut next_scene: ResMut<NextState<CurrentScene>>, mut next_scene: ResMut<NextState<CurrentScene>>,
connection: Res<Connection>, connection: Res<Connection>,
all_players: Res<AllPlayers>, all_players_query: Query<&Player>,
mut kick_player: EventWriter<SendTo<RemovePlayer>>, 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| { egui::CentralPanel::default().show(ctx.ctx_mut(), |ui| {
ui.heading("Border Wars"); ui.heading("Border Wars");
@ -32,7 +40,7 @@ fn lobby_ui(
ui.label("Game created"); ui.label("Game created");
ui.horizontal(|ui| { ui.horizontal(|ui| {
if all_players.get_by_connection(&connection).map(|p| p.rank) != Some(GameRank::Admin) { if self_player.rank != PlayerRank::Admin {
return; return;
} }
ui.label("Game ID: "); ui.label("Game ID: ");
@ -42,22 +50,20 @@ fn lobby_ui(
ui.separator(); ui.separator();
for (connected_player_id, connected_player) in all_players.0.iter() { for player in all_players_query.iter() {
ui.label(connected_player.name.to_string()); ui.label(player.name.to_string());
if all_players.get_by_connection(&connection).map(|p| p.rank) == Some(GameRank::Admin) if self_player.rank == PlayerRank::Admin
&& connected_player.rank != GameRank::Admin && player.rank != PlayerRank::Admin
&& ui.button("Remove").clicked() && ui.button("Remove").clicked()
{ {
for sender_id in all_players.0.keys() { for sender_id in all_players_query.iter() {
kick_player.send(SendTo(*sender_id, RemovePlayer(*connected_player_id))); kick_player.send(SendTo(sender_id.uuid, RemovePlayer(player.clone())));
} }
} }
ui.separator(); ui.separator();
} }
if all_players.get_by_connection(&connection).map(|p| p.rank) == Some(GameRank::Admin) if self_player.rank == PlayerRank::Admin && ui.button("Run the game").clicked() {
&& ui.button("Run the game").clicked()
{
next_scene.set(CurrentScene::Game); next_scene.set(CurrentScene::Game);
// TODO: run the game // TODO: run the game
} }

View file

@ -5,8 +5,8 @@ use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts}; use bevy_egui::{egui, EguiContexts};
use crate::networking::connection::RequestJoin; use crate::networking::connection::RequestJoin;
use crate::networking::GameRank; use crate::networking::PlayerRank;
use crate::{AllPlayers, CurrentScene, Player}; use crate::{CurrentScene, Player};
/// The plugin for the menu. /// The plugin for the menu.
pub struct MenuPlugin; pub struct MenuPlugin;
@ -16,6 +16,7 @@ impl Plugin for MenuPlugin {
app.add_systems(Update, menu_ui.run_if(in_state(CurrentScene::Menu))); 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. /// Display the UI of the menu to host a game or join one.
fn menu_ui( fn menu_ui(
mut ctx: EguiContexts, mut ctx: EguiContexts,
@ -23,8 +24,8 @@ fn menu_ui(
mut next_scene: ResMut<NextState<CurrentScene>>, mut next_scene: ResMut<NextState<CurrentScene>>,
mut request_join: EventWriter<SendTo<RequestJoin>>, mut request_join: EventWriter<SendTo<RequestJoin>>,
mut name: Local<String>, mut name: Local<String>,
mut all_player: ResMut<AllPlayers>,
connection: Res<Connection>, connection: Res<Connection>,
mut commands: Commands,
) { ) {
let Some(uuid) = connection.identifier() else { let Some(uuid) = connection.identifier() else {
return; return;
@ -55,7 +56,8 @@ fn menu_ui(
game_id, game_id,
RequestJoin(Player { RequestJoin(Player {
name: name.clone(), name: name.clone(),
rank: GameRank::Player, rank: PlayerRank::Player,
uuid,
}), }),
)); ));
} }
@ -65,13 +67,11 @@ fn menu_ui(
if ui.button("Create new game").clicked() { if ui.button("Create new game").clicked() {
next_scene.set(CurrentScene::Lobby); next_scene.set(CurrentScene::Lobby);
all_player.0.insert( commands.spawn(Player {
name: name.clone(),
rank: PlayerRank::Admin,
uuid, uuid,
Player { });
name: name.clone(),
rank: GameRank::Admin,
},
);
} }
}); });
} }

View file

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