implement server and client player join/leave notifications

This commit is contained in:
2024-07-22 12:58:40 +02:00
parent cbb2f5005e
commit 392eaeab83
23 changed files with 422 additions and 25 deletions

View File

@@ -0,0 +1,9 @@
#pragma once
#include <cstdint>
namespace blitz {
using EntityID = std::uint32_t;
} // namespace blitz

View File

@@ -0,0 +1,13 @@
#pragma once
#include <blitz/common/Types.h>
#include <string>
namespace blitz {
struct PlayerInfoComponent {
EntityID m_PlayerId;
std::string m_Pseudo;
};
} // namespace blitz

View File

@@ -1,6 +1,7 @@
#pragma once
#include <string>
#include <blitz/components/PlayerInfo.h>
#include <vector>
namespace blitz {
namespace protocol {
@@ -14,17 +15,25 @@ struct UpdateHealth {
float m_NewHealth;
};
struct LoggingSuccess {};
struct LoggingSuccess {
EntityID m_PlayerId;
};
struct PlayerDeath {};
struct PlayerJoin {};
struct PlayerJoin {
PlayerInfoComponent m_Player;
};
struct PlayerLeave {};
struct PlayerLeave {
EntityID m_PlayerId;
};
struct PlayerStats {};
struct PlayerList {};
struct PlayerList {
std::vector<PlayerInfoComponent> m_Players;
};
struct ServerConfig {};

View File

@@ -10,7 +10,7 @@ namespace client {
class Client : private NonCopyable {
public:
Client();
Client(std::shared_ptr<Nz::EnttWorld> a_World);
~Client();
void Connect(const Nz::IpAddress& a_Ip);
@@ -25,6 +25,7 @@ class Client : private NonCopyable {
void BindHandlers();
void UnbindHandlers();
void Login();
};
} // namespace client

View File

@@ -0,0 +1,13 @@
#pragma once
#include <blitz/common/Types.h>
namespace blitz {
namespace client {
struct LocalPlayerComponent {
EntityID m_LocalPlayerId;
};
} // namespace client
} // namespace blitz

View File

@@ -9,6 +9,9 @@ class KeepAliveHandler : public protocol::PacketHandler {
~KeepAliveHandler();
NazaraSlot(network::EnetConnection, OnKeepAlive, m_Slot);
private:
void Handle(const protocol::data::KeepAlive&);
};
} // namespace client

View File

@@ -0,0 +1,18 @@
#include <blitz/protocol/PacketHandler.h>
namespace blitz {
namespace client {
class LoggingSuccessHandler : public protocol::PacketHandler {
public:
LoggingSuccessHandler(network::EnetConnection& a_Connection, EnttWorld& a_World);
~LoggingSuccessHandler();
NazaraSlot(network::EnetConnection, OnLoggingSuccess, m_Slot);
private:
void Handle(const protocol::data::LoggingSuccess&);
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,18 @@
#include <blitz/protocol/PacketHandler.h>
namespace blitz {
namespace client {
class PlayerJoinHandler : public protocol::PacketHandler {
public:
PlayerJoinHandler(network::EnetConnection& a_Connection, EnttWorld& a_World);
~PlayerJoinHandler();
NazaraSlot(network::EnetConnection, OnPlayerJoin, m_Slot);
private:
void Handle(const protocol::data::PlayerJoin&);
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,18 @@
#include <blitz/protocol/PacketHandler.h>
namespace blitz {
namespace client {
class PlayerLeaveHandler : public protocol::PacketHandler {
public:
PlayerLeaveHandler(network::EnetConnection& a_Connection, EnttWorld& a_World);
~PlayerLeaveHandler();
NazaraSlot(network::EnetConnection, OnPlayerLeave, m_Slot);
private:
void Handle(const protocol::data::PlayerLeave&);
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,18 @@
#include <blitz/protocol/PacketHandler.h>
namespace blitz {
namespace client {
class PlayerListHandler : public protocol::PacketHandler {
public:
PlayerListHandler(network::EnetConnection& a_Connection, EnttWorld& a_World);
~PlayerListHandler();
NazaraSlot(network::EnetConnection, OnPlayerList, m_Slot);
private:
void Handle(const protocol::data::PlayerList&);
};
} // namespace client
} // namespace blitz

View File

@@ -8,6 +8,10 @@ namespace server {
class Server {
public:
/**
* \brief Construct a server
* \pre Two instances of Server should not share the same world
*/
Server(std::uint16_t a_Port, std::shared_ptr<Nz::EnttWorld> a_World);
~Server();

View File

@@ -0,0 +1,13 @@
#pragma once
#include <blitz/common/Types.h>
namespace blitz {
namespace server {
struct ServerIdCounterComponent {
EntityID m_NextEntityId;
};
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,20 @@
#pragma once
#include <blitz/protocol/PacketHandler.h>
namespace blitz {
namespace server {
class PlayerLoginHandler : public protocol::PacketHandler {
public:
PlayerLoginHandler(network::EnetConnection& a_Connection, EnttWorld& a_World);
~PlayerLoginHandler();
NazaraSlot(network::EnetConnection, OnPlayerLogin, m_Slot);
private:
void Handle(std::uint16_t, const protocol::data::PlayerLogin&);
};
} // namespace server
} // namespace blitz

View File

@@ -152,10 +152,13 @@ void Deserializer::DeserializePacketData(data::UpdateHealth& a_Packet) {
void Serializer::SerializePacketData(const data::LoggingSuccess& a_Packet) {
m_Buffer << a_Packet.m_PlayerId;
}
void Serializer::SerializePacketData(const data::LoggingSuccess& a_Packet) {}
void Deserializer::DeserializePacketData(data::LoggingSuccess& a_Packet) {}
void Deserializer::DeserializePacketData(data::LoggingSuccess& a_Packet) {
m_Buffer >> a_Packet.m_PlayerId;
}
@@ -167,23 +170,44 @@ void Deserializer::DeserializePacketData(data::PlayerDeath& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerJoin& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerJoin& a_Packet) {
m_Buffer << a_Packet.m_Player.m_PlayerId << a_Packet.m_Player.m_Pseudo;
}
void Deserializer::DeserializePacketData(data::PlayerJoin& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerJoin& a_Packet) {
m_Buffer >> a_Packet.m_Player.m_PlayerId >> a_Packet.m_Player.m_Pseudo;
}
void Serializer::SerializePacketData(const data::PlayerLeave& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerLeave& a_Packet) {
m_Buffer << a_Packet.m_PlayerId;
}
void Deserializer::DeserializePacketData(data::PlayerLeave& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerLeave& a_Packet) {
m_Buffer >> a_Packet.m_PlayerId;
}
void Serializer::SerializePacketData(const data::PlayerList& a_Packet) {}
void Serializer::SerializePacketData(const data::PlayerList& a_Packet) {
m_Buffer << static_cast<std::uint8_t>(a_Packet.m_Players.size());
for (auto player : a_Packet.m_Players) {
m_Buffer << player.m_PlayerId << player.m_Pseudo;
}
}
void Deserializer::DeserializePacketData(data::PlayerList& a_Packet) {}
void Deserializer::DeserializePacketData(data::PlayerList& a_Packet) {
std::uint8_t playerCount;
m_Buffer >> playerCount;
for (std::uint8_t i = 0; i < playerCount; i++) {
PlayerInfoComponent player;
m_Buffer >> player.m_PlayerId >> player.m_Pseudo;
a_Packet.m_Players.push_back(player);
}
}

View File

@@ -1,12 +1,18 @@
#include <client/Client.h>
#include <client/handlers/KeepAliveHandler.h>
#include <client/handlers/LoggingSuccessHandler.h>
#include <client/handlers/PlayerJoinHandler.h>
#include <client/handlers/PlayerLeaveHandler.h>
#include <client/handlers/PlayerListHandler.h>
#define RegisterHandler(Handler) m_Handlers.push_back(std::make_unique<Handler>(m_NetworkClient->GetConnection(), m_World))
namespace blitz {
namespace client {
Client::Client() : m_World() {
m_World.store(std::make_shared<Nz::EnttWorld>());
Client::Client(std::shared_ptr<Nz::EnttWorld> a_World) {
m_World.store(a_World);
}
Client::~Client() {
@@ -14,7 +20,11 @@ Client::~Client() {
}
void Client::BindHandlers() {
m_Handlers.push_back(std::make_unique<KeepAliveHandler>(m_NetworkClient->GetConnection(), m_World));
RegisterHandler(KeepAliveHandler);
RegisterHandler(LoggingSuccessHandler);
RegisterHandler(PlayerJoinHandler);
RegisterHandler(PlayerLeaveHandler);
RegisterHandler(PlayerListHandler);
}
void Client::UnbindHandlers() {
@@ -24,6 +34,7 @@ void Client::UnbindHandlers() {
void Client::Connect(const Nz::IpAddress& a_Ip) {
m_NetworkClient = std::make_unique<network::EnetClient>(a_Ip);
BindHandlers();
m_NetworkClient->OnConnect.Connect([this]() { Login(); });
}
void Client::Disconnect() {
@@ -41,5 +52,11 @@ bool Client::IsConnected() {
return m_NetworkClient->GetConnection().IsConnected();
}
void Client::Login() {
if (!m_NetworkClient || !m_NetworkClient->GetConnection().IsConnected())
return;
m_NetworkClient->GetConnection().SendPlayerLogin({"Player_" + std::to_string(rand() % 100)});
}
} // namespace client
} // namespace blitz

View File

@@ -5,8 +5,11 @@ namespace client {
KeepAliveHandler::KeepAliveHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
protocol::PacketHandler(a_Connection, a_World) {
m_Slot.Connect(m_Connection.OnKeepAlive,
[this](const blitz::protocol::data::KeepAlive& a_KeepAlive) { m_Connection.SendKeepAlive(a_KeepAlive); });
m_Slot.Connect(m_Connection.OnKeepAlive, this, &KeepAliveHandler::Handle);
}
void KeepAliveHandler::Handle(const blitz::protocol::data::KeepAlive& a_KeepAlive) {
m_Connection.SendKeepAlive(a_KeepAlive);
}
KeepAliveHandler::~KeepAliveHandler() {}

View File

@@ -0,0 +1,22 @@
#include <client/handlers/LoggingSuccessHandler.h>
#include <client/components/LocalPlayer.h>
namespace blitz {
namespace client {
LoggingSuccessHandler::LoggingSuccessHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
protocol::PacketHandler(a_Connection, a_World) {
m_Slot.Connect(m_Connection.OnLoggingSuccess, this, &LoggingSuccessHandler::Handle);
}
void LoggingSuccessHandler::Handle(const blitz::protocol::data::LoggingSuccess& a_LoggingSuccess) {
auto world = m_World.load();
auto player = world->CreateEntity();
player.emplace<LocalPlayerComponent>(a_LoggingSuccess.m_PlayerId);
}
LoggingSuccessHandler::~LoggingSuccessHandler() {}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,35 @@
#include <client/handlers/PlayerJoinHandler.h>
#include <client/components/LocalPlayer.h>
namespace blitz {
namespace client {
PlayerJoinHandler::PlayerJoinHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
protocol::PacketHandler(a_Connection, a_World) {
m_Slot.Connect(m_Connection.OnPlayerJoin, this, &PlayerJoinHandler::Handle);
}
void PlayerJoinHandler::Handle(const protocol::data::PlayerJoin& a_PlayerJoin) {
auto world = m_World.load();
assert(world->GetRegistry().view<LocalPlayerComponent>().size() == 1 && "There should be only one local player !");
auto localPlayer = world->GetRegistry().view<LocalPlayerComponent>().front();
auto localPlayerId = world->GetRegistry().get<LocalPlayerComponent>(localPlayer).m_LocalPlayerId;
if (a_PlayerJoin.m_Player.m_PlayerId != localPlayerId) {
auto newPlayer = world->CreateEntity();
newPlayer.emplace<PlayerInfoComponent>(a_PlayerJoin.m_Player);
return;
}
world->GetRegistry().emplace<PlayerInfoComponent>(localPlayer, a_PlayerJoin.m_Player);
// we are now into the game so we can begin to load the world for example
}
PlayerJoinHandler::~PlayerJoinHandler() {}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,29 @@
#include <client/handlers/PlayerLeaveHandler.h>
namespace blitz {
namespace client {
PlayerLeaveHandler::PlayerLeaveHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
protocol::PacketHandler(a_Connection, a_World) {
m_Slot.Connect(m_Connection.OnPlayerLeave, this, &PlayerLeaveHandler::Handle);
}
void PlayerLeaveHandler::Handle(const protocol::data::PlayerLeave& a_PlayerLeave) {
auto world = m_World.load();
entt::entity playerLeft;
for (auto [player, playerInfo] : world->GetRegistry().view<PlayerInfoComponent>().each()) {
if (playerInfo.m_PlayerId == a_PlayerLeave.m_PlayerId) {
playerLeft = player;
break;
}
}
world->GetRegistry().destroy(playerLeft);
}
PlayerLeaveHandler::~PlayerLeaveHandler() {}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,22 @@
#include <client/handlers/PlayerListHandler.h>
namespace blitz {
namespace client {
PlayerListHandler::PlayerListHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
protocol::PacketHandler(a_Connection, a_World) {
m_Slot.Connect(m_Connection.OnPlayerList, this, &PlayerListHandler::Handle);
}
void PlayerListHandler::Handle(const protocol::data::PlayerList& a_PlayerList) {
auto world = m_World.load();
for (auto playerInfo : a_PlayerList.m_Players) {
auto player = world->CreateEntity();
player.emplace<PlayerInfoComponent>(playerInfo);
}
}
PlayerListHandler::~PlayerListHandler() {}
} // namespace client
} // namespace blitz

View File

@@ -1,14 +1,19 @@
#include <server/Server.h>
#include <server/components/EnetConnection.h>
#include <server/components/KeepAliveSession.h>
#include <server/components/ServerIdCounter.h>
#include <server/handlers/KeepAliveHandler.h>
#include <server/handlers/PlayerLoginHandler.h>
#include <server/systems/DisconnectSystem.h>
#include <server/systems/KeepAliveSystem.h>
namespace blitz {
namespace server {
#define RegisterHandler(Handler) session.m_Handlers.push_back(std::make_unique<Handler>(*session.m_Connection, m_World))
Server::Server(std::uint16_t a_Port, std::shared_ptr<Nz::EnttWorld> a_World) : m_NetworkServer(a_Port) {
m_World.store(a_World);
RegisterSystems();
@@ -39,7 +44,6 @@ void Server::CreateEntity(network::EnetConnection& a_Connection) {
auto entity = world->CreateEntity();
entity.emplace<KeepAliveSessionComponent>(a_Connection.GetPeerId(), 69, Nz::GetElapsedMilliseconds(), true);
entity.emplace<EnetConnectionComponent>(&a_Connection);
}
@@ -47,10 +51,14 @@ void Server::RegisterSystems() {
auto world = m_World.load();
world->AddSystem<KeepAliveSystem>();
world->AddSystem<DisconectSystem>(*this);
auto counter = world->CreateEntity();
counter.emplace<ServerIdCounterComponent>(0);
}
void Server::RegisterHandlers(Session& session) {
session.m_Handlers.push_back(std::make_unique<KeepAliveHandler>(*session.m_Connection, m_World));
RegisterHandler(KeepAliveHandler);
RegisterHandler(PlayerLoginHandler);
}
network::EnetConnection* Server::GetConnection(std::uint16_t a_PeerId) {

View File

@@ -0,0 +1,68 @@
#include <server/handlers/PlayerLoginHandler.h>
#include <Nazara/Core/Time.hpp>
#include <blitz/components/PlayerInfo.h>
#include <server/components/EnetConnection.h>
#include <server/components/KeepAliveSession.h>
#include <server/components/ServerIdCounter.h>
namespace blitz {
namespace server {
PlayerLoginHandler::PlayerLoginHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
protocol::PacketHandler(a_Connection, a_World) {
m_Slot.Connect(a_Connection.OnPlayerLogin,
[this](const protocol::data::PlayerLogin& a_PlayerLogin) { Handle(m_Connection.GetPeerId(), a_PlayerLogin); });
}
PlayerLoginHandler::~PlayerLoginHandler() {}
void PlayerLoginHandler::Handle(std::uint16_t a_PeerId, const protocol::data::PlayerLogin& a_PlayerLogin) {
auto world = m_World.load();
std::vector<PlayerInfoComponent> players;
for (auto [entity, playerInfo] : world->GetRegistry().view<PlayerInfoComponent>().each()) {
players.push_back(playerInfo);
}
assert(world->GetRegistry().view<ServerIdCounterComponent>().size() == 1 && "There should be only one counter !");
auto& serverCounter =
world->GetRegistry().get<ServerIdCounterComponent>(world->GetRegistry().view<ServerIdCounterComponent>().front());
EntityID newEntityId = serverCounter.m_NextEntityId++;
PlayerInfoComponent newPlayer;
for (auto [entity, connection] : world->GetRegistry().view<EnetConnectionComponent>().each()) {
if (connection.m_Connection->GetPeerId() == m_Connection.GetPeerId()) {
// players should not try to log in twice
auto previousPlayerInfo = world->GetRegistry().try_get<PlayerInfoComponent>(entity);
if (previousPlayerInfo)
return;
newPlayer = world->GetRegistry().emplace<PlayerInfoComponent>(entity, newEntityId, a_PlayerLogin.m_PlayerName);
world->GetRegistry().emplace<KeepAliveSessionComponent>(
entity, m_Connection.GetPeerId(), 69, Nz::GetElapsedMilliseconds(), true);
break;
}
}
// send logging success
m_Connection.SendLoggingSuccess({newEntityId});
// send player list
if (!players.empty())
m_Connection.SendPlayerList({players});
// broadcast player join
for (auto [entity, connection] : world->GetRegistry().view<EnetConnectionComponent>().each()) {
connection.m_Connection->SendPlayerJoin({newPlayer});
}
}
} // namespace server
} // namespace blitz

View File

@@ -11,14 +11,26 @@ namespace server {
DisconectSystem::DisconectSystem(entt::registry& a_Registry, Server& a_Server) : m_Registry(a_Registry), m_Server(a_Server) {}
void DisconectSystem::Update(Nz::Time elapsedTime) {
auto disconnects = m_Registry.view<DisconnectComponent>();
// broadcast player leave
for (auto entity : disconnects) {
auto* player = m_Registry.try_get<PlayerInfoComponent>(entity);
if (player) {
for (auto [entity, connection] : m_Registry.view<EnetConnectionComponent>().each()) {
connection.m_Connection->SendPlayerLeave({player->m_PlayerId});
}
}
}
// close connections
m_Registry.view<EnetConnectionComponent, DisconnectComponent>().each(
[this](auto entity, EnetConnectionComponent& connection, DisconnectComponent disconnect) {
m_Server.CloseConnection(connection.m_Connection->GetPeerId());
});
// remove the entities
auto it = m_Registry.view<DisconnectComponent>();
m_Registry.destroy(it.begin(), it.end());
m_Registry.destroy(disconnects.begin(), disconnects.end());
}
} // namespace server