Compare commits

...

29 Commits

Author SHA1 Message Date
65a84ecab0 wrong directory name
Some checks failed
Linux arm64 / Build (push) Failing after 1h55m3s
2024-08-08 13:06:43 +02:00
e193d05417 Update action file 2024-08-08 13:05:31 +02:00
82c7ff4895 Add action file 2024-08-08 13:04:38 +02:00
c9d94febbc add passlist for imgui 2024-08-07 19:00:40 +02:00
15625e5c30 add imgui dep 2024-08-07 19:00:02 +02:00
89a6219072 add doom font 2024-08-07 15:17:41 +02:00
2df849b63e add imgui support 2024-08-07 15:01:42 +02:00
e0d4dd053f correct launch.json 2024-08-07 14:57:16 +02:00
7b693c7d43 add RemovePlayersSystem in client 2024-08-07 11:58:54 +02:00
de6742f64d working main menu 2024-08-07 11:54:09 +02:00
adb6dce08a use jthread 2024-08-07 11:50:07 +02:00
af3ac8d37c add cache to gitignore 2024-08-07 11:49:01 +02:00
282ddac80c better removal of players 2024-08-03 15:06:33 +02:00
2065ea8334 uggly main menu 2024-08-03 15:03:38 +02:00
b6530c1220 fix Enet_test attempt 2024-08-02 14:25:19 +02:00
c14234551d use of ApplicationBase 2024-08-02 14:24:58 +02:00
5d7add69aa add enet test 2024-07-30 12:49:32 +02:00
632a23c084 refactor xmake.lua 2024-07-23 01:17:51 +02:00
5a16af2696 refactor client main 2024-07-23 01:17:42 +02:00
acbc00c6c6 add ServerMain 2024-07-23 01:14:42 +02:00
d5d459b658 add close server function 2024-07-23 01:12:12 +02:00
ff73cd348e log players on server 2024-07-23 01:11:23 +02:00
89d5ad5f54 true atomic EnttWorld 2024-07-23 01:10:20 +02:00
392eaeab83 implement server and client player join/leave notifications 2024-07-22 12:58:40 +02:00
cbb2f5005e add PacketFactory assertion 2024-07-21 20:59:27 +02:00
92a2e53036 fully implement KeepAlive behavior 2024-07-21 20:59:13 +02:00
36a2e67ac4 implement KeepAlive packet 2024-07-21 13:34:25 +02:00
a789cf9b1a add packet factory assert 2024-07-21 12:36:53 +02:00
f165aec824 make EnetConnection non copyable 2024-07-20 10:41:15 +02:00
74 changed files with 2578 additions and 396 deletions

View File

@@ -0,0 +1,52 @@
name: Linux arm64
run-name: Build And Test
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
Build:
runs-on: ubuntu-latest
env:
XMAKE_ROOT: y
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Prepare Xmake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: latest
actions-cache-folder: '.xmake-cache'
actions-cache-key: 'ubuntu-xmake'
- name: Packages List
run : |
xmake show -l packages | grep -E 'require|project' > ${{ github.workspace }}/packages.txt
cat ${{ github.workspace }}/packages.txt
- name: Calc deps hash
uses: seepine/hash-files@v1
id: get-hash
with:
patterns: 'packages.txt'
- name: Packages cache
uses: actions/cache@v4
with:
path: ~/.xmake/packages
key: ${{ runner.os }}-${{ steps.get-hash.outputs.hash }}
- name: XMake config
run: xmake f --policies=package.install_only -p linux -y
- name: Build
run: xmake
- name: Test
run: xmake test

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ NazaraLog.log
.vscode/settings.json
.vscode/compile_commands.json
.cache

2
.vscode/launch.json vendored
View File

@@ -8,7 +8,7 @@
"type": "xmake",
"request": "launch",
"name": "Debug",
"target": "Blitz2",
"target": "Blitz2Client",
"cwd": "${workspaceFolder}",
}
]

58
assets/example.passlist Normal file
View File

@@ -0,0 +1,58 @@
passlist "Forward Passlist"
{
attachment "DepthBuffer"
{
format "PreferredDepthStencil"
}
pass "DepthPrepass"
{
impl "Depth"
{
MatPass "DepthPass"
}
depthstenciloutput "DepthBuffer"
}
attachment "ForwardOutput"
{
format "RGBA16F"
}
pass "ForwardPass"
{
impl "Forward"
output "Output" "ForwardOutput"
depthstencilinput "DepthBuffer"
depthstenciloutput "DepthBuffer"
flag "LightShadowing"
}
attachment "Gamma"
{
format "RGBA8"
}
pass "Gamma correction"
{
impl "PostProcess"
{
Shader "PostProcess.GammaCorrection"
}
input "Input" "ForwardOutput"
output "Output" "Gamma"
}
attachmentproxy "ImguiOutput" "Gamma"
pass "Imgui"
{
impl "Imgui"
input "Input" "Gamma"
output "Output" "ImguiOutput"
}
output "ImguiOutput"
}

BIN
assets/fonts/doom.ttf Normal file

Binary file not shown.

View File

@@ -0,0 +1,31 @@
#pragma once
#include <Nazara/Core/EnttWorld.hpp>
namespace blitz {
struct AtomicEnttWorld {
Nz::EnttWorld& m_World;
std::lock_guard<std::mutex> m_LockGuard;
Nz::EnttWorld* operator->() {
return &m_World;
}
};
struct EnttWorld {
Nz::EnttWorld& m_World;
std::mutex m_Mutex;
/**
* \return an AtomicEnttWorld structure which will lock the associated mutex until destruction
* \warning Do not hold more than one instance or the current thread will self lock
*/
operator AtomicEnttWorld() {
return {m_World, std::lock_guard<std::mutex>(m_Mutex)};
}
};
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

@@ -0,0 +1,9 @@
#pragma once
#include <blitz/common/Types.h>
namespace blitz {
struct PlayerRemoveComponent {};
} // namespace blitz

View File

@@ -1,8 +1,7 @@
#pragma once
#include <Nazara/Network/ENetHost.hpp>
#include <blitz/common/NonCopyable.h>
#include <blitz/network/EnetConnexion.h>
#include <blitz/network/EnetConnection.h>
#include <thread>
namespace blitz {
@@ -19,15 +18,15 @@ class EnetClient : private NonCopyable {
NazaraSignal(OnDisconnect);
NazaraSignal(OnDisconnectTimeout);
const EnetConnexion& GetConnexion() const {
return m_Connexion;
EnetConnection& GetConnection() {
return m_Connection;
}
private:
EnetConnexion m_Connexion;
EnetConnection m_Connection;
Nz::ENetHost m_Host;
Nz::ENetPeer* m_Peer;
std::thread m_Thread;
std::jthread m_Thread;
bool m_Running;
void Update();

View File

@@ -1,12 +1,13 @@
#pragma once
/**
* \file EnetConnexion.h
* \brief File containing the blitz::network::EnetConnexion class
* \file EnetConnection.h
* \brief File containing the blitz::network::EnetConnection class
*/
#include <Nazara/Core/ByteArray.hpp>
#include <NazaraUtils/Signal.hpp>
#include <blitz/common/NonCopyable.h>
#include <blitz/protocol/PacketData.h>
#include <blitz/protocol/PacketDeclare.h>
@@ -34,12 +35,14 @@ class EnetServer;
class EnetConnexion {
class EnetConnection : private NonCopyable {
public:
EnetConnexion(Nz::ENetPeer* a_Peer = nullptr);
EnetConnection(Nz::ENetPeer* a_Peer = nullptr);
bool IsConnected() const;
std::uint16_t GetPeerId() const;
DeclareAllPacket();
private:

View File

@@ -2,8 +2,7 @@
#include <Nazara/Core/ThreadExt.hpp>
#include <Nazara/Network/ENetHost.hpp>
#include <blitz/common/NonCopyable.h>
#include <blitz/network/EnetConnexion.h>
#include <blitz/network/EnetConnection.h>
#include <cstdint>
#include <map>
#include <thread>
@@ -18,24 +17,26 @@ class EnetServer : private NonCopyable {
void Destroy();
// void BroadcastPacket(const protocol::Packet& a_Packet, Nz::ENetPacketFlags a_Flags);
bool IsClosed() const;
EnetConnexion* GetConnexion(std::uint16_t a_PeerId);
void CloseConnection(std::uint16_t a_PeerId);
NazaraSignal(OnClientConnect, EnetConnexion& /*a_Peer*/);
NazaraSignal(OnClientDisconnect, EnetConnexion& /*a_Peer*/);
NazaraSignal(OnClientDisconnectTimeout, EnetConnexion& /*a_Peer*/);
EnetConnection* GetConnection(std::uint16_t a_PeerId);
NazaraSignal(OnClientConnect, EnetConnection& /*a_Peer*/);
NazaraSignal(OnClientDisconnect, EnetConnection& /*a_Peer*/);
NazaraSignal(OnClientDisconnectTimeout, EnetConnection& /*a_Peer*/);
private:
void Update();
void WorkerThread();
void RemoveConnexion(std::uint16_t a_PeerId);
void RemoveConnection(std::uint16_t a_PeerId);
Nz::ENetHost m_Host;
bool m_Running;
std::thread m_Thread;
std::map<std::uint16_t, EnetConnexion> m_Connexion;
std::jthread m_Thread;
std::map<std::uint16_t, std::unique_ptr<EnetConnection>> m_Connections;
};
} // namespace network

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 {};
@@ -32,7 +41,9 @@ struct ServerTps {};
struct UpdateGameState {};
struct KeepAlive {};
struct KeepAlive {
std::uint64_t m_KeepAliveId;
};
struct Disconnect {};

View File

@@ -0,0 +1,20 @@
#pragma once
#include <Nazara/Core/EnttWorld.hpp>
#include <blitz/network/EnetConnection.h>
namespace blitz {
namespace protocol {
class PacketHandler : private NonCopyable {
public:
PacketHandler(network::EnetConnection& a_Connection, EnttWorld& a_World);
virtual ~PacketHandler();
protected:
network::EnetConnection& m_Connection;
EnttWorld& m_World;
};
} // namespace protocol
} // namespace blitz

View File

@@ -0,0 +1,18 @@
#pragma once
#include <Nazara/Core/Time.hpp>
#include <blitz/common/Types.h>
namespace blitz {
class RemovePlayersSystem {
public:
RemovePlayersSystem(entt::registry&, EnttWorld& a_World);
void Update(Nz::Time elapsedTime);
private:
EnttWorld& m_World;
};
} // namespace blitz

34
include/client/Client.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include <Nazara/Core/EnttWorld.hpp>
#include <blitz/network/EnetClient.h>
#include <blitz/protocol/PacketHandler.h>
namespace blitz {
namespace client {
class Client : private NonCopyable {
public:
Client(Nz::EnttWorld& a_World);
~Client();
void Connect(const Nz::IpAddress& a_Ip);
void Disconnect();
bool IsConnected();
NazaraSignal(OnClientReady);
NazaraSignal(OnClientDisconnect);
private:
EnttWorld m_World;
std::unique_ptr<network::EnetClient> m_NetworkClient;
std::vector<std::unique_ptr<protocol::PacketHandler>> m_Handlers;
void BindHandlers();
void UnbindHandlers();
void Login();
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,36 @@
#pragma once
#include <Nazara/Core/ApplicationComponent.hpp>
namespace Nz {
class Window;
class Canvas;
class StateMachine;
} // namespace Nz
namespace blitz {
namespace client {
class StateData;
class ClientApp : public Nz::ApplicationComponent {
public:
ClientApp(Nz::ApplicationBase& app);
~ClientApp();
void Update(Nz::Time elapsedTime) override;
Nz::Window* GetWindow() {
return m_Window;
}
private:
Nz::Window* m_Window;
std::unique_ptr<Nz::StateMachine> m_StateMachine;
std::shared_ptr<StateData> m_StateData;
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,33 @@
#pragma once
#include <Nazara/Core/ApplicationComponent.hpp>
namespace Nz {
class Window;
class Texture;
} // namespace Nz
namespace blitz {
namespace client {
class StateData;
class ImGuiAppComponent : public Nz::ApplicationComponent {
public:
ImGuiAppComponent(Nz::ApplicationBase& app, Nz::Window& a_Window);
~ImGuiAppComponent();
void Update(Nz::Time elapsedTime) override;
private:
Nz::Window& m_Window;
std::shared_ptr<Nz::Texture> m_FontTexture;
void SetStyle();
void UpdateFontTexture();
};
} // namespace client
} // namespace blitz

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

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

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,20 @@
#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);
NazaraSignal(OnLocalPlayerReady);
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

@@ -0,0 +1,75 @@
#pragma once
#include <Nazara/Core/Components/DisabledComponent.hpp>
#include <Nazara/Core/State.hpp>
#include <Nazara/Graphics/RenderTarget.hpp>
#include <Nazara/Widgets/BaseWidget.hpp>
#include <client/states/StateData.h>
#include <functional>
#include <memory>
#include <vector>
namespace blitz {
namespace client {
class AbstractState : public Nz::State, public std::enable_shared_from_this<AbstractState> {
public:
AbstractState(std::shared_ptr<StateData> stateData);
~AbstractState();
protected:
template <typename T, typename... Args>
void ConnectSignal(T& signal, Args&&... args) {
m_CleanupFunctions.emplace_back(
[connection = signal.Connect(std::forward<Args>(args)...)]() mutable { connection.Disconnect(); });
}
template <typename T, typename... Args>
T* CreateWidget(Args&&... args) {
T* widget = m_StateData->m_Canvas->Add<T>(std::forward<Args>(args)...);
auto& entry = m_Widgets.emplace_back();
entry.m_Widget = widget;
if (!m_IsVisible)
entry.m_Widget->Hide();
return widget;
}
void DestroyWidget(Nz::BaseWidget* widget);
StateData& GetStateData() {
return *m_StateData;
}
const StateData& GetStateData() const {
return *m_StateData;
}
const std::shared_ptr<StateData>& GetStateDataPtr() {
return m_StateData;
}
void Enter(Nz::StateMachine& fsm) override;
void Leave(Nz::StateMachine& fsm) override;
bool Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) override;
virtual void LayoutWidgets() {}
private:
NazaraSlot(Nz::RenderTarget, OnRenderTargetSizeChange, m_OnTargetChangeSizeSlot);
struct WidgetEntry {
Nz::BaseWidget* m_Widget;
bool m_WasVisible = true;
};
std::shared_ptr<StateData> m_StateData;
std::vector<std::function<void()>> m_CleanupFunctions;
std::vector<WidgetEntry> m_Widgets;
bool m_IsVisible;
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,62 @@
#pragma once
#include <Nazara/Network/IpAddress.hpp>
#include <Nazara/Widgets/ButtonWidget.hpp>
#include <Nazara/Widgets/LabelWidget.hpp>
#include <client/Client.h>
#include <client/states/AbstractState.h>
#include <queue>
namespace blitz {
namespace server {
class Server;
} // namespace server
namespace client {
class ConnectingState : public AbstractState {
public:
ConnectingState(std::shared_ptr<StateData> a_StateData, std::shared_ptr<AbstractState> a_PreviousState,
const std::string& a_Address, std::uint16_t a_Port, bool a_IntegratedServer = false);
~ConnectingState();
private:
struct ResolvingData {
bool m_HasResult = false;
Nz::Result<std::vector<Nz::IpAddress>, std::string /*error*/> m_Result = Nz::Err("");
std::string m_ServerName;
std::uint16_t m_Port;
};
ResolvingData m_ResolvingData;
bool m_IsConnected = false;
std::queue<Nz::IpAddress> m_Addresses;
Nz::MillisecondClock m_TimeoutClock;
Nz::ButtonWidget* m_BackButton;
Nz::LabelWidget* m_StatusText;
std::shared_ptr<AbstractState> m_NextState;
std::shared_ptr<AbstractState> m_PreviousState;
std::unique_ptr<Client> m_Client;
std::unique_ptr<server::Server> m_Server;
void LayoutWidgets() override;
bool Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) override;
void TryResolve();
void TryConnect(const Nz::IpAddress& a_ServerAddress);
void TryNextAddress();
void OnBackPressed();
void OnConnect();
void OnConnectFailed();
NazaraSlot(Client, OnClientReady, m_ClientReady);
NazaraSlot(Client, OnClientDisconnect, m_ClientDisconnect);
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,30 @@
#pragma once
#include <Nazara/Widgets/TextAreaWidget.hpp>
#include <Nazara/Widgets/ButtonWidget.hpp>
#include <client/states/AbstractState.h>
namespace blitz {
namespace client {
class CreateServerState : public AbstractState {
public:
CreateServerState(std::shared_ptr<StateData> a_StateData, std::shared_ptr<AbstractState> a_PreviousState);
~CreateServerState();
private:
Nz::ButtonWidget* m_CreateServerButton;
Nz::TextAreaWidget* m_InputPort;
Nz::ButtonWidget* m_BackButton;
std::shared_ptr<AbstractState> m_NextState;
std::shared_ptr<AbstractState> m_PreviousState;
void LayoutWidgets() override;
bool Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) override;
void OnCreateServerPressed();
void OnBackPressed();
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,27 @@
#pragma once
#include <client/states/AbstractState.h>
#include <memory>
namespace blitz {
namespace server {
class Server;
}
namespace client {
class Client;
class GameState : public AbstractState {
public:
GameState(std::shared_ptr<StateData> a_StateData, std::unique_ptr<Client>&& a_Client, std::unique_ptr<server::Server>&& a_Server);
~GameState();
private:
std::unique_ptr<Client> m_Client;
std::unique_ptr<server::Server> m_Server;
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,17 @@
#pragma once
#include <NazaraImgui/ImguiHandler.hpp>
namespace blitz {
namespace client {
class ImGuiDrawer : private Nz::ImguiHandler {
protected:
ImGuiDrawer();
~ImGuiDrawer();
virtual void OnRenderImgui() = 0;
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,30 @@
#pragma once
#include <Nazara/Widgets/ButtonWidget.hpp>
#include <Nazara/Widgets/TextAreaWidget.hpp>
#include <client/states/AbstractState.h>
namespace blitz {
namespace client {
class JoinServerState : public AbstractState {
public:
JoinServerState(std::shared_ptr<StateData> a_StateData, std::shared_ptr<AbstractState> a_PreviousState);
~JoinServerState();
private:
Nz::TextAreaWidget* m_InputAddress;
Nz::ButtonWidget* m_JoinServerButton;
Nz::ButtonWidget* m_BackButton;
std::shared_ptr<AbstractState> m_NextState;
std::shared_ptr<AbstractState> m_PreviousState;
void LayoutWidgets() override;
bool Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) override;
void OnJoinServerPressed();
void OnBackPressed();
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,34 @@
#pragma once
#include <Nazara/Widgets/ButtonWidget.hpp>
#include <client/states/AbstractState.h>
#include <client/states/ImGuiDrawer.h>
namespace blitz {
namespace client {
class MainMenuState : public AbstractState, private ImGuiDrawer {
public:
MainMenuState(std::shared_ptr<StateData>);
~MainMenuState();
private:
Nz::ButtonWidget* m_JoinServerButton;
Nz::ButtonWidget* m_CreateServerButton;
Nz::ButtonWidget* m_OptionButton;
Nz::ButtonWidget* m_QuitButton;
std::shared_ptr<AbstractState> m_NextState;
void LayoutWidgets() override;
bool Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) override;
void OnRenderImgui() override;
void OnJoinServerPressed();
void OnCreateServerPressed();
void OnOptionPressed();
void OnQuitPressed();
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,28 @@
#pragma once
#include <Nazara/Widgets/ButtonWidget.hpp>
#include <client/states/AbstractState.h>
namespace blitz {
namespace client {
class OptionState : public AbstractState {
public:
OptionState(std::shared_ptr<StateData> a_StateData, std::shared_ptr<AbstractState> a_PreviousState);
~OptionState();
private:
Nz::ButtonWidget* m_OptionButton;
Nz::ButtonWidget* m_BackButton;
std::shared_ptr<AbstractState> m_NextState;
std::shared_ptr<AbstractState> m_PreviousState;
void LayoutWidgets() override;
bool Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) override;
void OnOptionPressed();
void OnBackPressed();
};
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,34 @@
#pragma once
#include <Nazara/Core/EnttWorld.hpp>
#include <Nazara/Platform/Window.hpp>
#include <Nazara/Renderer/WindowSwapchain.hpp>
#include <Nazara/Widgets/Canvas.hpp>
#include <entt/fwd.hpp>
#include <memory>
#include <optional>
namespace Nz {
class ApplicationBase;
class RenderTarget;
} // namespace Nz
namespace blitz {
namespace client {
class ClientApp;
struct StateData {
std::optional<Nz::Canvas> m_Canvas;
std::shared_ptr<Nz::RenderTarget> m_RenderTarget;
ClientApp* m_AppComponent;
Nz::ApplicationBase* m_App;
Nz::EnttWorld* m_World;
Nz::Window* m_Window;
Nz::WindowSwapchain* m_Swapchain;
};
} // namespace client
} // namespace blitz

45
include/server/Server.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include <blitz/network/EnetServer.h>
#include <blitz/protocol/PacketHandler.h>
namespace blitz {
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, Nz::EnttWorld& a_World);
~Server();
network::EnetConnection* GetConnection(std::uint16_t a_PeerId);
bool IsClosed() const;
void CloseConnection(std::uint16_t a_PeerId);
void CloseServer();
private:
void HandleConnect(network::EnetConnection&);
void HandleDisconnect(network::EnetConnection&);
struct Session {
network::EnetConnection* m_Connection;
std::vector<std::unique_ptr<protocol::PacketHandler>> m_Handlers;
};
EnttWorld m_World;
std::map<std::uint16_t, Session> m_Sessions;
network::EnetServer m_NetworkServer;
void RegisterHandlers(Session& session);
void RegisterSystems();
void CreateEntity(network::EnetConnection&);
};
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,13 @@
#pragma once
namespace blitz {
namespace server {
enum Reason { Timeout };
struct DisconnectComponent {
Reason m_Reason;
};
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,13 @@
#pragma once
#include <blitz/network/EnetConnection.h>
namespace blitz {
namespace server {
struct EnetConnectionComponent {
network::EnetConnection* m_Connection;
};
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,17 @@
#pragma once
#include <Nazara/Core/Time.hpp>
#include <cstdint>
namespace blitz {
namespace server {
struct KeepAliveSessionComponent {
std::uint16_t m_PeerId;
std::uint64_t m_LastKeepAliveId;
Nz::Time m_LastTime;
bool m_RecievedResponse = false;
};
} // namespace server
} // namespace blitz

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 KeepAliveHandler : public protocol::PacketHandler {
public:
KeepAliveHandler(network::EnetConnection& a_Connection, EnttWorld& a_World);
~KeepAliveHandler();
NazaraSlot(network::EnetConnection, OnKeepAlive, m_Slot);
private:
void Handle(std::uint16_t, const protocol::data::KeepAlive&);
};
} // 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

@@ -0,0 +1,23 @@
#pragma once
#include <Nazara/Core/Time.hpp>
#include <blitz/common/Types.h>
namespace blitz {
namespace server {
class Server;
class DisconnectSystem {
public:
DisconnectSystem(entt::registry&, EnttWorld& a_World, Server& a_Server);
void Update(Nz::Time elapsedTime);
private:
EnttWorld& m_World;
Server& m_Server;
};
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,21 @@
#pragma once
#include <Nazara/Core/Time.hpp>
#include <entt/entity/registry.hpp>
#include <blitz/common/Types.h>
namespace blitz {
namespace server {
class KeepAliveSystem {
public:
KeepAliveSystem(entt::registry&, EnttWorld& a_World);
void Update(Nz::Time elapsedTime);
private:
EnttWorld& m_World;
};
} // namespace server
} // namespace blitz

20
src/ClientMain.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include <Nazara/Core/Application.hpp>
#include <Nazara/Core/EntitySystemAppComponent.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Physics3D/Physics3D.hpp>
#include <Nazara/Platform/WindowingAppComponent.hpp>
#include <Nazara/Widgets/Widgets.hpp>
#include <NazaraImgui/NazaraImgui.hpp>
#include <client/ClientApp.h>
#include <client/ImGuiAppComponent.h>
int main(int argc, char** argv) {
Nz::Application<Nz::Graphics, Nz::Physics3D, Nz::Widgets, Nz::TextRenderer, Nz::Imgui> app(argc, argv);
app.AddComponent<Nz::EntitySystemAppComponent>();
app.AddComponent<Nz::WindowingAppComponent>();
auto& client = app.AddComponent<blitz::client::ClientApp>();
app.AddComponent<blitz::client::ImGuiAppComponent>(*client.GetWindow());
return app.Run();
}

56
src/ServerMain.cpp Normal file
View File

@@ -0,0 +1,56 @@
#include <server/Server.h>
#include <Nazara/Core/Application.hpp>
#include <Nazara/Core/EntitySystemAppComponent.hpp>
#include <Nazara/Network/Network.hpp>
#include <iostream>
void ConsoleLoop(Nz::ApplicationBase& app, blitz::server::Server& server) {
std::string line;
while (true) {
getline(std::cin, line);
if (line == "stop") {
std::cout << "Exiting ...\n";
server.CloseServer();
app.Quit();
break;
}
}
}
static std::uint16_t DefaultPort = 25565;
int main(int argc, char* args[]) {
Nz::Application<Nz::Network> app(argc, args);
const auto& params = app.GetCommandLineParameters();
std::string_view portString;
std::uint16_t port = DefaultPort;
if (params.GetParameter("port", &portString)) {
std::cout << "Selected port : " << portString << "\n";
bool ok;
port = Nz::StringToNumber(portString, 10, &ok);
if (!ok) {
std::cerr << "Failed to parse port !\n";
port = DefaultPort;
}
}
auto& ecs = app.AddComponent<Nz::EntitySystemAppComponent>();
auto& serverWorld = ecs.AddWorld<Nz::EnttWorld>();
blitz::server::Server server(port, serverWorld);
std::cout << "Server running on port " << port << " ...\n";
std::jthread consoleThread([&app, &server]() { ConsoleLoop(app, server); });
// tick 20 times per seconds
app.AddUpdaterFunc([]() { std::this_thread::sleep_for(std::chrono::milliseconds(50)); });
return app.Run();
}

View File

@@ -1,31 +1,26 @@
#include <blitz/network/EnetClient.h>
#include <iostream>
namespace blitz {
namespace network {
EnetClient::EnetClient(const Nz::IpAddress& address) : m_Running(true) {
m_Host.Create(Nz::IpAddress::LoopbackIpV4, 1);
m_Host.Create(address.GetProtocol() == Nz::NetProtocol::IPv4 ? Nz::IpAddress::LoopbackIpV4 : Nz::IpAddress::LoopbackIpV6, 1);
m_Peer = m_Host.Connect(address);
m_Thread = std::thread(&EnetClient::WorkerThread, this);
m_Connexion.SetPeer(m_Peer);
m_Thread = std::jthread(&EnetClient::WorkerThread, this);
m_Connection.SetPeer(m_Peer);
}
EnetClient::~EnetClient() {
if (m_Peer->IsConnected())
Disconnect();
m_Host.Destroy();
m_Running = false;
m_Thread.join();
m_Thread.request_stop();
m_Host.Destroy();
}
void EnetClient::Disconnect() {
m_Peer->DisconnectNow(0);
m_Connexion.SetPeer(nullptr);
m_Connection.SetPeer(nullptr);
}
void EnetClient::WorkerThread() {
@@ -53,7 +48,7 @@ void EnetClient::Update() {
break;
case Nz::ENetEventType::Receive:
m_Connexion.Recieve(event.packet.m_packet->data);
m_Connection.Recieve(event.packet.m_packet->data);
break;
case Nz::ENetEventType::None:

View File

@@ -1,4 +1,4 @@
#include <blitz/network/EnetConnexion.h>
#include <blitz/network/EnetConnection.h>
#include <Nazara/Network/ENetPeer.hpp>
#include <blitz/protocol/PacketSerializer.h>
@@ -11,20 +11,17 @@ namespace packets = blitz::protocol::packets;
#define DeclarePacket(PacketName, ...) \
void Visit(const protocol::packets::PacketName& a_Packet) { \
m_Connexion.On##PacketName(a_Packet.m_Data); \
}
void Visit(const protocol::packets::PacketName& a_Packet) { m_Connection.On##PacketName(a_Packet.m_Data); }
class PacketDispatcher : public protocol::PacketVisitor {
public:
PacketDispatcher(EnetConnexion& a_Connexion) : m_Connexion(a_Connexion) {}
PacketDispatcher(EnetConnection& a_Connection) : m_Connection(a_Connection) {}
DeclareAllPacket()
private:
EnetConnexion& m_Connexion;
private : EnetConnection& m_Connection;
};
#undef DeclarePacket
@@ -33,19 +30,24 @@ class PacketDispatcher : public protocol::PacketVisitor {
EnetConnexion::EnetConnexion(Nz::ENetPeer* a_Peer) : m_Peer(a_Peer) {}
EnetConnection::EnetConnection(Nz::ENetPeer* a_Peer) : m_Peer(a_Peer) {}
void EnetConnexion::SetPeer(Nz::ENetPeer* a_Peer) {
void EnetConnection::SetPeer(Nz::ENetPeer* a_Peer) {
m_Peer = a_Peer;
}
bool EnetConnexion::IsConnected() const {
std::uint16_t EnetConnection::GetPeerId() const {
assert(m_Peer);
return m_Peer->GetPeerId();
}
bool EnetConnection::IsConnected() const {
if (!m_Peer)
return false;
return m_Peer->IsConnected();
}
void EnetConnexion::Recieve(Nz::ByteArray& a_Data) {
void EnetConnection::Recieve(Nz::ByteArray& a_Data) {
auto packet = protocol::PacketSerializer::Deserialize(a_Data);
if (!packet)
return;
@@ -54,7 +56,7 @@ void EnetConnexion::Recieve(Nz::ByteArray& a_Data) {
}
#define DeclarePacket(Name, NFlag, ...) \
void EnetConnexion::Send##Name(const blitz::protocol::data::Name& a_##Name) const { \
void EnetConnection::Send##Name(const blitz::protocol::data::Name& a_##Name) const { \
m_Peer->Send(0, Nz::ENetPacketFlag::NFlag, protocol::PacketSerializer::Serialize(protocol::packets::Name(a_##Name))); \
}

View File

@@ -5,10 +5,12 @@
namespace blitz {
namespace network {
EnetServer::EnetServer(std::uint16_t port) : m_Running(true) {
m_Host.Create(Nz::NetProtocol::Any, port, 80);
EnetServer::EnetServer(std::uint16_t a_Port) : m_Running(true) {
m_Running = m_Host.Create(Nz::NetProtocol::Any, a_Port, 80);
if (m_Running) {
m_Host.AllowsIncomingConnections(true);
m_Thread = std::thread(&EnetServer::WorkerThread, this);
m_Thread = std::jthread(&EnetServer::WorkerThread, this);
}
}
void EnetServer::WorkerThread() {
@@ -29,33 +31,36 @@ void EnetServer::Update() {
do {
switch (event.type) {
case Nz::ENetEventType::Disconnect: {
EnetConnexion* connexion = GetConnexion(event.peer->GetPeerId());
if (!connexion)
EnetConnection* connection = GetConnection(event.peer->GetPeerId());
if (!connection)
break;
OnClientDisconnect(*connexion);
OnClientDisconnect(*connection);
RemoveConnection(connection->GetPeerId());
break;
}
case Nz::ENetEventType::DisconnectTimeout: {
EnetConnexion* connexion = GetConnexion(event.peer->GetPeerId());
if (!connexion)
EnetConnection* connection = GetConnection(event.peer->GetPeerId());
if (!connection)
break;
OnClientDisconnectTimeout(*connexion);
OnClientDisconnectTimeout(*connection);
RemoveConnection(connection->GetPeerId());
break;
}
case Nz::ENetEventType::IncomingConnect: {
Nz::ENetPeer* peer = event.peer;
m_Connexion.insert({peer->GetPeerId(), EnetConnexion(peer)});
OnClientConnect(*GetConnexion(peer->GetPeerId()));
auto con = std::make_unique<EnetConnection>(peer);
m_Connections.insert({peer->GetPeerId(), std::move(con)});
OnClientConnect(*GetConnection(peer->GetPeerId()));
break;
}
case Nz::ENetEventType::Receive: {
EnetConnexion* connexion = GetConnexion(event.peer->GetPeerId());
if (!connexion)
EnetConnection* connection = GetConnection(event.peer->GetPeerId());
if (!connection)
break;
connexion->Recieve(event.packet.m_packet->data);
connection->Recieve(event.packet.m_packet->data);
break;
}
@@ -64,31 +69,44 @@ void EnetServer::Update() {
break;
}
} while (m_Host.CheckEvents(&event));
} else if (service < 0) {
m_Running = false;
}
}
EnetConnexion* EnetServer::GetConnexion(std::uint16_t a_PeerId) {
auto it = m_Connexion.find(a_PeerId);
EnetConnection* EnetServer::GetConnection(std::uint16_t a_PeerId) {
auto it = m_Connections.find(a_PeerId);
if (it == m_Connexion.end())
if (it == m_Connections.end())
return nullptr;
return &it->second;
return it->second.get();
}
/*void EnetServer::BroadcastPacket(const protocol::Packet& a_Packet, Nz::ENetPacketFlags a_Flags) {
m_Host.Broadcast(0, a_Flags, std::move(a_Data));
}*/
void EnetServer::CloseConnection(std::uint16_t a_PeerId) {
auto connection = GetConnection(a_PeerId);
void EnetServer::RemoveConnexion(std::uint16_t a_PeerId) {
m_Connexion.erase(a_PeerId);
if (!connection)
return;
connection->m_Peer->DisconnectNow(0);
OnClientDisconnect(*connection);
RemoveConnection(a_PeerId);
}
void EnetServer::RemoveConnection(std::uint16_t a_PeerId) {
m_Connections.erase(a_PeerId);
}
void EnetServer::Destroy() {
m_Running = false;
m_Thread.join();
m_Thread.request_stop();
m_Host.Destroy();
}
bool EnetServer::IsClosed() const {
return !m_Running;
}
} // namespace network
} // namespace blitz

View File

@@ -1,6 +1,7 @@
#include <blitz/protocol/PacketFactory.h>
#include <array>
#include <cassert>
#include <functional>
namespace blitz {
@@ -18,6 +19,7 @@ static const std::array<std::unique_ptr<Packet>, static_cast<std::size_t>(Packet
};
const std::unique_ptr<Packet>& CreateReadOnlyPacket(PacketType a_Type) {
assert(a_Type < PacketType::PACKET_COUNT);
return Packets[static_cast<std::size_t>(a_Type)];
}

View File

@@ -0,0 +1,12 @@
#include <blitz/protocol/PacketHandler.h>
namespace blitz {
namespace protocol {
PacketHandler::PacketHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
m_Connection(a_Connection), m_World(a_World) {}
PacketHandler::~PacketHandler() {}
} // namespace protocol
} // namespace blitz

View File

@@ -4,8 +4,6 @@
#include <blitz/protocol/PacketFactory.h>
#include <blitz/protocol/PacketVisitor.h>
#include <iostream>
namespace blitz {
namespace protocol {
@@ -154,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;
}
@@ -169,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);
}
}
@@ -218,9 +240,13 @@ void Deserializer::DeserializePacketData(data::UpdateGameState& a_Packet) {}
void Serializer::SerializePacketData(const data::KeepAlive& a_Packet) {}
void Serializer::SerializePacketData(const data::KeepAlive& a_Packet) {
m_Buffer << a_Packet.m_KeepAliveId;
}
void Deserializer::DeserializePacketData(data::KeepAlive& a_Packet) {}
void Deserializer::DeserializePacketData(data::KeepAlive& a_Packet) {
m_Buffer >> a_Packet.m_KeepAliveId;
}

View File

@@ -0,0 +1,19 @@
#include <blitz/systems/RemovePlayersSystem.h>
#include <blitz/components/PlayerRemove.h>
namespace blitz {
RemovePlayersSystem::RemovePlayersSystem(entt::registry&, EnttWorld& a_World) : m_World(a_World) {}
void RemovePlayersSystem::Update(Nz::Time /*elapsedTime*/) {
AtomicEnttWorld world = m_World;
entt::registry& registry = world->GetRegistry();
auto invalidPlayers = registry.view<PlayerRemoveComponent>();
registry.destroy(invalidPlayers.begin(), invalidPlayers.end());
}
} // namespace blitz

72
src/client/Client.cpp Normal file
View File

@@ -0,0 +1,72 @@
#include <client/Client.h>
#include <blitz/systems/RemovePlayersSystem.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>
namespace blitz {
namespace client {
Client::Client(Nz::EnttWorld& a_World) : m_World({a_World}) {
AtomicEnttWorld world = m_World;
world->AddSystem<RemovePlayersSystem>(m_World);
}
Client::~Client() {
Disconnect();
AtomicEnttWorld world = m_World;
world->RemoveSystem<RemovePlayersSystem>();
}
void Client::BindHandlers() {
m_NetworkClient->OnDisconnect.Connect([this]() { OnClientDisconnect(); });
m_NetworkClient->OnDisconnectTimeout.Connect([this]() { OnClientDisconnect(); });
m_Handlers.push_back(std::make_unique<KeepAliveHandler>(m_NetworkClient->GetConnection(), m_World));
m_Handlers.push_back(std::make_unique<LoggingSuccessHandler>(m_NetworkClient->GetConnection(), m_World));
auto playerJoin = std::make_unique<PlayerJoinHandler>(m_NetworkClient->GetConnection(), m_World);
playerJoin->OnLocalPlayerReady.Connect([this]() { OnClientReady(); });
m_Handlers.push_back(std::move(playerJoin));
m_Handlers.push_back(std::make_unique<PlayerLeaveHandler>(m_NetworkClient->GetConnection(), m_World));
m_Handlers.push_back(std::make_unique<PlayerListHandler>(m_NetworkClient->GetConnection(), m_World));
}
void Client::UnbindHandlers() {
m_Handlers.clear();
}
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() {
if (!m_NetworkClient)
return;
m_NetworkClient->GetConnection().SendDisconnect({});
m_NetworkClient->Disconnect();
UnbindHandlers();
m_NetworkClient.reset(nullptr);
}
bool Client::IsConnected() {
if (!m_NetworkClient)
return false;
return m_NetworkClient->GetConnection().IsConnected();
}
void Client::Login() {
srand(time(0));
if (!m_NetworkClient || !m_NetworkClient->GetConnection().IsConnected())
return;
m_NetworkClient->GetConnection().SendPlayerLogin({"Player_" + std::to_string(rand() % 100)});
}
} // namespace client
} // namespace blitz

80
src/client/ClientApp.cpp Normal file
View File

@@ -0,0 +1,80 @@
#include <client/ClientApp.h>
#include <Nazara/Core.hpp>
#include <Nazara/Core/StateMachine.hpp>
#include <Nazara/Graphics.hpp>
#include <Nazara/Physics3D.hpp>
#include <Nazara/Platform.hpp>
#include <Nazara/Renderer.hpp>
#include <Nazara/Widgets.hpp>
#include <NazaraImgui/NazaraImgui.hpp>
#include <client/states/MainMenuState.h>
#include <random>
namespace blitz {
namespace client {
constexpr Nz::UInt32 RenderMaskUI = 0x00010000;
constexpr Nz::UInt32 RenderMask3D = 0x0000FFFF;
ClientApp::ClientApp(Nz::ApplicationBase& app) : Nz::ApplicationComponent(app), m_StateMachine(std::make_unique<Nz::StateMachine>()) {
auto& windowing = app.GetComponent<Nz::WindowingAppComponent>();
std::string windowTitle = "Blitz 2";
m_Window = &windowing.CreateWindow(Nz::VideoMode(1920, 1080, 32), windowTitle);
auto& ecs = app.GetComponent<Nz::EntitySystemAppComponent>();
auto& world = ecs.AddWorld<Nz::EnttWorld>();
Nz::RenderSystem& renderSystem = world.AddSystem<Nz::RenderSystem>();
Nz::SwapchainParameters params;
params.presentMode.clear();
params.presentMode.push_back(Nz::PresentMode::VerticalSync);
Nz::WindowSwapchain& windowSwapchain = renderSystem.CreateSwapchain(*m_Window, params);
auto renderTarget = std::make_shared<Nz::RenderWindow>(windowSwapchain);
Nz::Imgui::Instance()->Init(*m_Window, false);
auto passList = Nz::PipelinePassList::LoadFromFile("assets/example.passlist");
entt::handle cameraEntity = world.CreateEntity();
{
cameraEntity.emplace<Nz::NodeComponent>();
auto& cameraComponent = cameraEntity.emplace<Nz::CameraComponent>(renderTarget, passList, Nz::ProjectionType::Orthographic);
cameraComponent.UpdateClearColor(Nz::Color(0.0f, 0.f, .0f, 0.0f));
cameraComponent.UpdateRenderMask(RenderMaskUI);
cameraComponent.UpdateRenderOrder(1);
}
m_StateData = std::make_shared<StateData>();
m_StateData->m_App = &app;
m_StateData->m_AppComponent = this;
m_StateData->m_RenderTarget = renderTarget;
m_StateData->m_Window = m_Window;
m_StateData->m_Swapchain = &windowSwapchain;
m_StateData->m_World = &world;
m_StateData->m_Canvas.emplace(
world.GetRegistry(), m_Window->GetEventHandler(), m_Window->GetCursorController().CreateHandle(), RenderMaskUI);
m_StateData->m_Canvas->Resize(Nz::Vector2f(m_Window->GetSize()));
m_StateMachine->PushState(std::make_shared<MainMenuState>(m_StateData));
}
ClientApp::~ClientApp() {
m_StateMachine->ResetState(nullptr);
m_StateMachine->Update(Nz::Time::Zero());
}
void ClientApp::Update(Nz::Time elapsedTime) {
if (!m_StateMachine->Update(elapsedTime))
GetApp().Quit();
}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,96 @@
#include <client/ImGuiAppComponent.h>
#include <Nazara/Core/ApplicationBase.hpp>
#include <Nazara/Platform/Window.hpp>
#include <NazaraImgui/NazaraImgui.hpp>
#include <blitz/common/Log.h>
namespace blitz {
namespace client {
ImGuiAppComponent::ImGuiAppComponent(Nz::ApplicationBase& a_App, Nz::Window& a_Window) :
Nz::ApplicationComponent(a_App), m_Window(a_Window) {
ImGui::EnsureContextOnThisThread();
SetStyle();
a_App.AddUpdaterFunc(Nz::ApplicationBase::Interval{Nz::Time::Milliseconds(16)}, [&](Nz::Time elapsed) {
if (!m_Window.IsOpen())
return;
m_Window.ProcessEvents();
Nz::Imgui::Instance()->Update(Nz::Time::Milliseconds(16).AsSeconds());
Nz::Imgui::Instance()->Render();
});
}
ImGuiAppComponent::~ImGuiAppComponent() {}
void ImGuiAppComponent::SetStyle() {
const static ImVec4 colorButton = {1.0f, 0.0f, 0.0f, 0.73f};
const static ImVec4 colorButtonHover = {0.56f, 0.02f, 0.02f, 1.0f};
const static ImVec4 colorButtonActive = {0.36f, 0.03f, 0.03f, 1.0f};
const static ImVec4 colorCheckMark = {1.0f, 1.0f, 1.0f, 1.0f};
const static ImVec4 colorTab = {0.38f, 0.02f, 0.02f, 1.0f};
const static ImVec4 colorTabHover = {0.74f, 0.0f, 0.0f, 0.73f};
const static ImVec4 colorTabActive = {1.0f, 0.0f, 0.0f, 0.73f};
ImGui::GetStyle().Colors[ImGuiCol_Button] = colorButton;
ImGui::GetStyle().Colors[ImGuiCol_ButtonActive] = colorButtonActive;
ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] = colorButtonHover;
ImGui::GetStyle().Colors[ImGuiCol_CheckMark] = colorCheckMark;
ImGui::GetStyle().Colors[ImGuiCol_FrameBg] = colorButton;
ImGui::GetStyle().Colors[ImGuiCol_FrameBgActive] = colorButtonActive;
ImGui::GetStyle().Colors[ImGuiCol_FrameBgHovered] = colorButtonHover;
ImGui::GetStyle().Colors[ImGuiCol_Tab] = colorTab;
ImGui::GetStyle().Colors[ImGuiCol_TabHovered] = colorTabHover;
ImGui::GetStyle().Colors[ImGuiCol_TabActive] = colorTabActive;
ImGui::GetStyle().Colors[ImGuiCol_TitleBgActive] = colorTabActive;
ImGui::GetStyle().Colors[ImGuiCol_SliderGrab] = colorButton;
ImGui::GetStyle().Colors[ImGuiCol_SliderGrabActive] = colorButton;
ImGui::GetStyle().Colors[ImGuiCol_HeaderActive] = colorTab;
ImGui::GetStyle().Colors[ImGuiCol_HeaderHovered] = colorTabActive;
ImGui::GetStyle().Colors[ImGuiCol_Header] = colorTabActive;
static const float DefaultFontSize = 25.0f;
ImFontConfig c;
c.SizePixels = DefaultFontSize;
c.OversampleH = 1; // prevent from blurring font
ImGui::GetIO().Fonts->AddFontDefault(&c);
auto* doomFont = ImGui::GetIO().Fonts->AddFontFromFileTTF("assets/fonts/doom.ttf", DefaultFontSize, &c);
ImGui::GetIO().FontDefault = doomFont;
UpdateFontTexture();
}
void ImGuiAppComponent::UpdateFontTexture() {
auto& io = ImGui::GetIO();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
auto renderDevice = Nz::Graphics::Instance()->GetRenderDevice();
Nz::TextureInfo texParams;
texParams.width = width;
texParams.height = height;
texParams.pixelFormat = Nz::PixelFormat::RGBA8;
texParams.type = Nz::ImageType::E2D;
m_FontTexture = renderDevice->InstantiateTexture(texParams, pixels, true);
m_FontTexture->UpdateDebugName("FontTexture");
ImTextureID textureID = m_FontTexture.get();
io.Fonts->TexID = textureID;
}
void ImGuiAppComponent::Update(Nz::Time elapsedTime) {}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,18 @@
#include <client/handlers/KeepAliveHandler.h>
namespace blitz {
namespace client {
KeepAliveHandler::KeepAliveHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
protocol::PacketHandler(a_Connection, a_World) {
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() {}
} // namespace client
} // namespace blitz

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) {
AtomicEnttWorld world = m_World;
auto player = world->CreateEntity();
player.emplace<LocalPlayerComponent>(a_LoggingSuccess.m_PlayerId);
}
LoggingSuccessHandler::~LoggingSuccessHandler() {}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,36 @@
#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) {
AtomicEnttWorld world = m_World;
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
OnLocalPlayerReady();
}
PlayerJoinHandler::~PlayerJoinHandler() {}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,27 @@
#include <client/handlers/PlayerLeaveHandler.h>
#include <blitz/components/PlayerRemove.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) {
AtomicEnttWorld world = m_World;
for (auto [player, playerInfo] : world->GetRegistry().view<PlayerInfoComponent>().each()) {
if (playerInfo.m_PlayerId == a_PlayerLeave.m_PlayerId) {
world->GetRegistry().emplace<PlayerRemoveComponent>(player);
break;
}
}
}
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) {
AtomicEnttWorld world = m_World;
for (auto playerInfo : a_PlayerList.m_Players) {
auto player = world->CreateEntity();
player.emplace<PlayerInfoComponent>(playerInfo);
}
}
PlayerListHandler::~PlayerListHandler() {}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,57 @@
#include <client/states/AbstractState.h>
namespace blitz {
namespace client {
AbstractState::AbstractState(std::shared_ptr<StateData> a_StateData) : m_StateData(std::move(a_StateData)), m_IsVisible(false) {
m_OnTargetChangeSizeSlot.Connect(
m_StateData->m_RenderTarget->OnRenderTargetSizeChange, [this](const Nz::RenderTarget*, const Nz::Vector2ui& /*newSize*/) {
if (m_IsVisible)
LayoutWidgets();
});
}
AbstractState::~AbstractState() {
for (const auto& cleanupFunc : m_CleanupFunctions)
cleanupFunc();
for (WidgetEntry& entry : m_Widgets)
entry.m_Widget->Destroy();
}
void AbstractState::DestroyWidget(Nz::BaseWidget* widget) {
auto it = std::find_if(
m_Widgets.begin(), m_Widgets.end(), [&](const WidgetEntry& widgetEntity) { return widgetEntity.m_Widget == widget; });
assert(it != m_Widgets.end());
m_Widgets.erase(it);
widget->Destroy();
}
void AbstractState::Enter(Nz::StateMachine& /*fsm*/) {
m_IsVisible = true;
for (WidgetEntry& entry : m_Widgets) {
if (entry.m_WasVisible)
entry.m_Widget->Show();
}
LayoutWidgets();
}
void AbstractState::Leave(Nz::StateMachine& /*fsm*/) {
m_IsVisible = false;
for (WidgetEntry& entry : m_Widgets) {
entry.m_WasVisible = entry.m_Widget->IsVisible();
entry.m_Widget->Hide();
}
}
bool AbstractState::Update(Nz::StateMachine& /*fsm*/, Nz::Time /*elapsedTime*/) {
return true;
}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,189 @@
#include "client/states/GameState.h"
#include <client/states/ConnectingState.h>
#include <Nazara/Core/ApplicationBase.hpp>
#include <Nazara/Core/Clock.hpp>
#include <Nazara/Core/EntitySystemAppComponent.hpp>
#include <Nazara/Core/EnttWorld.hpp>
#include <Nazara/Core/StateMachine.hpp>
#include <Nazara/Core/Time.hpp>
#include <Nazara/Network/Algorithm.hpp>
#include <Nazara/Network/IpAddress.hpp>
#include <Nazara/TextRenderer.hpp>
#include <Nazara/TextRenderer/SimpleTextDrawer.hpp>
#include <blitz/common/Format.h>
#include <blitz/common/Log.h>
#include <client/Client.h>
#include <memory>
#include <server/Server.h>
namespace blitz {
namespace client {
static const int ConnectTimeout = 5;
ConnectingState::ConnectingState(std::shared_ptr<StateData> a_StateData, std::shared_ptr<AbstractState> a_PreviousState,
const std::string& a_Address, std::uint16_t a_Port, bool a_Server) :
AbstractState(std::move(a_StateData)),
m_PreviousState(std::move(a_PreviousState)),
m_Client(std::make_unique<Client>(*GetStateData().m_World)) {
m_ResolvingData.m_Port = a_Port;
m_ResolvingData.m_ServerName = a_Address;
Nz::SimpleTextDrawer textDrawer;
textDrawer.SetTextColor({0.0, 0.0, 0.0, 1.0});
textDrawer.SetCharacterSize(75);
m_BackButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Back");
m_BackButton->UpdateText(textDrawer);
m_BackButton->Resize(m_BackButton->GetPreferredSize());
m_BackButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnBackPressed(); });
m_StatusText = CreateWidget<Nz::LabelWidget>();
m_StatusText->UpdateText(Nz::SimpleTextDrawer::Draw("Connecting ...", 80));
if (a_Server) {
auto& app = GetStateData().m_App;
auto& ecs = app->GetComponent<Nz::EntitySystemAppComponent>();
// create server
Nz::EnttWorld& world = ecs.AddWorld<Nz::EnttWorld>();
m_Server = std::make_unique<server::Server>(a_Port, world);
}
m_ClientReady.Connect(m_Client->OnClientReady, [this]() { OnConnect(); });
m_ClientDisconnect.Connect(m_Client->OnClientDisconnect, [this]() { TryNextAddress(); });
TryResolve();
}
ConnectingState::~ConnectingState() {}
bool ConnectingState::Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) {
if (m_NextState)
fsm.ChangeState(std::move(m_NextState));
if (m_Server && m_Server->IsClosed())
OnConnectFailed();
if (m_ResolvingData.m_HasResult) {
if (m_ResolvingData.m_Result) {
// Register resolved addresses as next addresses
const auto& addresses = m_ResolvingData.m_Result.GetValue();
for (auto resultIt = addresses.rbegin(); resultIt != addresses.rend(); ++resultIt)
m_Addresses.emplace(*resultIt);
TryConnect(m_Addresses.front());
} else {
OnConnectFailed();
}
m_ResolvingData.m_HasResult = false;
}
static Nz::MillisecondClock clock;
static int progress = -1;
if (m_Client && clock.RestartIfOver(Nz::Time::Milliseconds(500))) {
progress++;
progress %= 4;
std::string textStr = "Connecting ";
for (int i = 0; i < progress; i++) {
textStr += ".";
}
m_StatusText->UpdateText(Nz::SimpleTextDrawer::Draw(textStr, 80));
return true;
}
if (!m_Addresses.empty() && m_TimeoutClock.RestartIfOver(Nz::Time::Seconds(ConnectTimeout))) {
TryNextAddress();
}
if (m_IsConnected) {
fsm.ResetState(std::make_shared<GameState>(GetStateDataPtr(), std::move(m_Client), std::move(m_Server)));
}
return true;
}
void ConnectingState::TryConnect(const Nz::IpAddress& a_ServerAddress) {
if (!m_Client)
return;
LogD("Trying to connect to " + a_ServerAddress.ToString() + " ...");
m_TimeoutClock.Restart();
m_Client->Connect(a_ServerAddress);
}
void ConnectingState::TryNextAddress() {
m_Client->Disconnect();
m_Addresses.pop();
if (m_Addresses.empty())
OnConnectFailed();
else
TryConnect(m_Addresses.front());
}
void ConnectingState::TryResolve() {
Nz::ResolveError resolveError;
auto serverAddresses = Nz::IpAddress::ResolveHostname(
Nz::NetProtocol::Any, m_ResolvingData.m_ServerName, std::to_string(m_ResolvingData.m_Port), &resolveError);
if (serverAddresses.empty()) {
m_ResolvingData.m_Result = Nz::Err(Nz::ErrorToString(resolveError));
LogD("Failed to resolve " + m_ResolvingData.m_ServerName + " !");
} else {
std::vector<Nz::IpAddress> addresses;
addresses.reserve(serverAddresses.size());
for (auto hostnameInfo : serverAddresses) {
hostnameInfo.address.SetPort(m_ResolvingData.m_Port);
addresses.push_back(hostnameInfo.address);
}
m_ResolvingData.m_Result = std::move(addresses);
}
m_ResolvingData.m_HasResult = true;
}
void ConnectingState::LayoutWidgets() {
Nz::Vector2f canvasSize = GetStateData().m_Canvas->GetSize();
Nz::Vector2f center = canvasSize / 2.f;
constexpr float padding = 10.f;
std::array<Nz::BaseWidget*, 2> widgets = {m_StatusText, m_BackButton};
float maxWidth = 0.f;
float totalSize = padding * (widgets.size() - 1);
for (Nz::BaseWidget* widget : widgets) {
Nz::Vector2f size = widget->GetSize();
maxWidth = std::max(maxWidth, size.x);
totalSize += size.y;
}
Nz::Vector2f cursor = center;
cursor.y += totalSize / 2.f;
for (Nz::BaseWidget* widget : widgets) {
widget->Resize({maxWidth, widget->GetHeight()});
widget->SetPosition({0.f, cursor.y - widget->GetSize().y, 0.f});
widget->CenterHorizontal();
cursor.y -= widget->GetSize().y + padding;
}
}
void ConnectingState::OnBackPressed() {
m_NextState = std::move(m_PreviousState);
}
void ConnectingState::OnConnect() {
m_IsConnected = true;
}
void ConnectingState::OnConnectFailed() {
m_StatusText->UpdateText(Nz::SimpleTextDrawer::Draw("Connection failed !", 80));
m_StatusText->CenterHorizontal();
m_Server.reset(nullptr);
m_Client.reset(nullptr);
}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,108 @@
#include <Nazara/Core/Color.hpp>
#include <Nazara/Network/IpAddress.hpp>
#include <Nazara/Widgets/TextAreaWidget.hpp>
#include <client/states/CreateServerState.h>
#include <Nazara/Core/StateMachine.hpp>
#include <Nazara/TextRenderer.hpp>
#include <client/Client.h>
#include <client/states/ConnectingState.h>
#include <server/Server.h>
namespace blitz {
namespace client {
CreateServerState::CreateServerState(std::shared_ptr<StateData> a_StateData, std::shared_ptr<AbstractState> a_PreviousState) :
AbstractState(std::move(a_StateData)), m_PreviousState(std::move(a_PreviousState)) {
Nz::SimpleTextDrawer textDrawer;
textDrawer.SetTextColor({0.0, 0.0, 0.0, 1.0});
textDrawer.SetCharacterSize(75);
m_CreateServerButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Create Server");
m_CreateServerButton->UpdateText(textDrawer);
m_CreateServerButton->Resize(m_CreateServerButton->GetPreferredSize());
m_CreateServerButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnCreateServerPressed(); });
m_InputPort = CreateWidget<Nz::TextAreaWidget>();
m_InputPort->SetBackgroundColor(Nz::Color::White());
m_InputPort->EnableBackground(true);
m_InputPort->EnableMultiline(false);
m_InputPort->SetTextColor(Nz::Color::Black());
m_InputPort->SetCharacterFilter([](Nz::UInt32 character) {
if (character < U'0' || character > U'9')
return false;
return true;
});
m_BackButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Back");
m_BackButton->UpdateText(textDrawer);
m_BackButton->Resize(m_BackButton->GetPreferredSize());
m_BackButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnBackPressed(); });
}
CreateServerState::~CreateServerState() {}
bool CreateServerState::Update(Nz::StateMachine& fsm, Nz::Time /*elapsedTime*/) {
if (m_NextState)
fsm.ChangeState(std::move(m_NextState));
return true;
}
void CreateServerState::LayoutWidgets() {
Nz::Vector2f canvasSize = GetStateData().m_Canvas->GetSize();
Nz::Vector2f center = canvasSize / 2.f;
constexpr float padding = 10.f;
std::array<Nz::BaseWidget*, 3> widgets = {m_InputPort, m_CreateServerButton, m_BackButton};
float maxWidth = 0.f;
float totalSize = padding * (widgets.size() - 1);
for (Nz::BaseWidget* widget : widgets) {
Nz::Vector2f size = widget->GetSize();
maxWidth = std::max(maxWidth, size.x);
totalSize += size.y;
}
Nz::Vector2f cursor = center;
cursor.y += totalSize / 2.f;
for (Nz::BaseWidget* widget : widgets) {
widget->Resize({maxWidth, widget->GetHeight()});
widget->SetPosition({0.f, cursor.y - widget->GetSize().y, 0.f});
widget->CenterHorizontal();
cursor.y -= widget->GetSize().y + padding;
}
}
void CreateServerState::OnCreateServerPressed() {
std::string serverPort = m_InputPort->GetText();
if (serverPort.empty()) {
// UpdateStatus("Error: blank server port", Nz::Color::Red());
return;
}
long long rawPort = std::stoi(serverPort);
if (rawPort <= 0 || rawPort > 0xFFFF) {
// UpdateStatus("Error: " + serverPort + " is not a valid port", Nz::Color::Red());
return;
}
Nz::IpAddress address = Nz::IpAddress::LoopbackIpV4;
address.SetPort(rawPort);
m_NextState = std::make_shared<ConnectingState>(GetStateDataPtr(), shared_from_this(), "localhost", rawPort, true);
}
void CreateServerState::OnBackPressed() {
m_NextState = std::move(m_PreviousState);
}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,16 @@
#include <client/states/GameState.h>
#include <client/Client.h>
#include <server/Server.h>
namespace blitz {
namespace client {
GameState::GameState(
std::shared_ptr<StateData> a_StateData, std::unique_ptr<Client>&& a_Client, std::unique_ptr<server::Server>&& a_Server) :
AbstractState(a_StateData), m_Client(std::move(a_Client)), m_Server(std::move(a_Server)) {}
GameState::~GameState() {}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,17 @@
#include <client/states/ImGuiDrawer.h>
#include <NazaraImgui/NazaraImgui.hpp>
namespace blitz {
namespace client {
ImGuiDrawer::ImGuiDrawer() {
Nz::Imgui::Instance()->AddHandler(this);
}
ImGuiDrawer::~ImGuiDrawer() {
Nz::Imgui::Instance()->RemoveHandler(this);
}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,97 @@
#include <Nazara/Network/Enums.hpp>
#include <Nazara/Network/IpAddress.hpp>
#include <client/states/ConnectingState.h>
#include <client/states/JoinServerState.h>
#include <Nazara/Core/StateMachine.hpp>
#include <Nazara/TextRenderer.hpp>
namespace blitz {
namespace client {
JoinServerState::JoinServerState(std::shared_ptr<StateData> a_StateData, std::shared_ptr<AbstractState> a_PreviousState) :
AbstractState(std::move(a_StateData)), m_PreviousState(std::move(a_PreviousState)) {
Nz::SimpleTextDrawer textDrawer;
textDrawer.SetTextColor({0.0, 0.0, 0.0, 1.0});
textDrawer.SetCharacterSize(75);
m_InputAddress = CreateWidget<Nz::TextAreaWidget>();
m_InputAddress->SetBackgroundColor(Nz::Color::White());
m_InputAddress->EnableBackground(true);
m_InputAddress->EnableMultiline(false);
m_InputAddress->SetTextColor(Nz::Color::Black());
m_InputAddress->SetText("localhost");
m_JoinServerButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Join Server");
m_JoinServerButton->UpdateText(textDrawer);
m_JoinServerButton->Resize(m_JoinServerButton->GetPreferredSize());
m_JoinServerButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnJoinServerPressed(); });
m_BackButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Back");
m_BackButton->UpdateText(textDrawer);
m_BackButton->Resize(m_BackButton->GetPreferredSize());
m_BackButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnBackPressed(); });
}
JoinServerState::~JoinServerState() {}
bool JoinServerState::Update(Nz::StateMachine& fsm, Nz::Time /*elapsedTime*/) {
if (m_NextState)
fsm.ChangeState(std::move(m_NextState));
return true;
}
void JoinServerState::LayoutWidgets() {
Nz::Vector2f canvasSize = GetStateData().m_Canvas->GetSize();
Nz::Vector2f center = canvasSize / 2.f;
constexpr float padding = 10.f;
std::array<Nz::BaseWidget*, 3> widgets = {m_InputAddress, m_JoinServerButton, m_BackButton};
float maxWidth = 0.f;
float totalSize = padding * (widgets.size() - 1);
for (Nz::BaseWidget* widget : widgets) {
Nz::Vector2f size = widget->GetSize();
maxWidth = std::max(maxWidth, size.x);
totalSize += size.y;
}
Nz::Vector2f cursor = center;
cursor.y += totalSize / 2.f;
for (Nz::BaseWidget* widget : widgets) {
widget->Resize({maxWidth, widget->GetHeight()});
widget->SetPosition({0.f, cursor.y - widget->GetSize().y, 0.f});
widget->CenterHorizontal();
cursor.y -= widget->GetSize().y + padding;
}
}
void JoinServerState::OnJoinServerPressed() {
std::string address = m_InputAddress->GetText();
auto separator = address.find(':');
std::string name = address.substr(0, separator);
std::uint16_t port = 25565;
if (separator != std::string::npos) {
try {
std::string rawPort = address.substr(separator + 1, std::string::npos);
port = std::stoi(rawPort);
} catch (std::exception& e) {
return;
}
}
m_NextState = std::make_shared<ConnectingState>(GetStateDataPtr(), shared_from_this(), name, port, false);
}
void JoinServerState::OnBackPressed() {
m_NextState = std::move(m_PreviousState);
}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,108 @@
#include <client/states/MainMenuState.h>
#include <Nazara/Core/ApplicationBase.hpp>
#include <Nazara/Core/StateMachine.hpp>
#include <Nazara/TextRenderer.hpp>
#include <NazaraImgui/ImguiHandler.hpp>
#include <NazaraImgui/NazaraImgui.hpp>
#include <client/states/CreateServerState.h>
#include <client/states/JoinServerState.h>
#include <client/states/OptionState.h>
namespace blitz {
namespace client {
MainMenuState::MainMenuState(std::shared_ptr<StateData> a_StateData) : AbstractState(std::move(a_StateData)) {
Nz::SimpleTextDrawer textDrawer;
textDrawer.SetTextColor({0.0, 0.0, 0.0, 1.0});
textDrawer.SetCharacterSize(75);
m_JoinServerButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Join Server");
m_JoinServerButton->UpdateText(textDrawer);
m_JoinServerButton->Resize(m_JoinServerButton->GetPreferredSize());
m_JoinServerButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnJoinServerPressed(); });
m_CreateServerButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Create Server");
m_CreateServerButton->UpdateText(textDrawer);
m_CreateServerButton->Resize(m_CreateServerButton->GetPreferredSize());
m_CreateServerButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnCreateServerPressed(); });
m_OptionButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Settings");
m_OptionButton->UpdateText(textDrawer);
m_OptionButton->Resize(m_OptionButton->GetPreferredSize());
m_OptionButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnOptionPressed(); });
m_QuitButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Quit game");
m_QuitButton->UpdateText(textDrawer);
m_QuitButton->Resize(m_QuitButton->GetPreferredSize());
m_QuitButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnQuitPressed(); });
}
MainMenuState::~MainMenuState() {}
bool MainMenuState::Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) {
if (!AbstractState::Update(fsm, elapsedTime))
return false;
if (m_NextState)
fsm.ChangeState(std::move(m_NextState));
return true;
}
void MainMenuState::LayoutWidgets() {
Nz::Vector2f canvasSize = GetStateData().m_Canvas->GetSize();
Nz::Vector2f center = canvasSize / 2.f;
constexpr float padding = 10.f;
std::array<Nz::BaseWidget*, 4> widgets = {m_JoinServerButton, m_CreateServerButton, m_OptionButton, m_QuitButton};
float maxWidth = 0.f;
float totalSize = padding * (widgets.size() - 1);
for (Nz::BaseWidget* widget : widgets) {
Nz::Vector2f size = widget->GetSize();
maxWidth = std::max(maxWidth, size.x);
totalSize += size.y;
}
Nz::Vector2f cursor = center;
cursor.y += totalSize / 2.f;
for (Nz::BaseWidget* widget : widgets) {
widget->Resize({maxWidth, widget->GetHeight()});
widget->SetPosition({0.f, cursor.y - widget->GetSize().y, 0.f});
widget->CenterHorizontal();
cursor.y -= widget->GetSize().y + padding;
}
}
void MainMenuState::OnRenderImgui() {
#ifndef NDEBUG
ImGui::ShowDemoWindow(nullptr);
#endif
}
void MainMenuState::OnJoinServerPressed() {
m_NextState = std::make_shared<JoinServerState>(GetStateDataPtr(), shared_from_this());
}
void MainMenuState::OnCreateServerPressed() {
m_NextState = std::make_shared<CreateServerState>(GetStateDataPtr(), shared_from_this());
}
void MainMenuState::OnOptionPressed() {
m_NextState = std::make_shared<OptionState>(GetStateDataPtr(), shared_from_this());
}
void MainMenuState::OnQuitPressed() {
GetStateData().m_App->Quit();
}
} // namespace client
} // namespace blitz

View File

@@ -0,0 +1,73 @@
#include <client/states/OptionState.h>
#include <Nazara/Core/StateMachine.hpp>
#include <Nazara/TextRenderer.hpp>
namespace blitz {
namespace client {
OptionState::OptionState(std::shared_ptr<StateData> a_StateData, std::shared_ptr<AbstractState> a_PreviousState) :
AbstractState(std::move(a_StateData)), m_PreviousState(std::move(a_PreviousState)) {
Nz::SimpleTextDrawer textDrawer;
textDrawer.SetTextColor({0.0, 0.0, 0.0, 1.0});
textDrawer.SetCharacterSize(75);
m_OptionButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Settings");
m_OptionButton->UpdateText(textDrawer);
m_OptionButton->Resize(m_OptionButton->GetPreferredSize());
m_OptionButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnOptionPressed(); });
m_BackButton = CreateWidget<Nz::ButtonWidget>();
textDrawer.SetText("Back");
m_BackButton->UpdateText(textDrawer);
m_BackButton->Resize(m_BackButton->GetPreferredSize());
m_BackButton->OnButtonTrigger.Connect([this](const Nz::ButtonWidget*) { OnBackPressed(); });
}
OptionState::~OptionState() {}
void OptionState::LayoutWidgets() {
Nz::Vector2f canvasSize = GetStateData().m_Canvas->GetSize();
Nz::Vector2f center = canvasSize / 2.f;
constexpr float padding = 10.f;
std::array<Nz::BaseWidget*, 2> widgets = {m_OptionButton, m_BackButton};
float maxWidth = 0.f;
float totalSize = padding * (widgets.size() - 1);
for (Nz::BaseWidget* widget : widgets) {
Nz::Vector2f size = widget->GetSize();
maxWidth = std::max(maxWidth, size.x);
totalSize += size.y;
}
Nz::Vector2f cursor = center;
cursor.y += totalSize / 2.f;
for (Nz::BaseWidget* widget : widgets) {
widget->Resize({maxWidth, widget->GetHeight()});
widget->SetPosition({0.f, cursor.y - widget->GetSize().y, 0.f});
widget->CenterHorizontal();
cursor.y -= widget->GetSize().y + padding;
}
}
bool OptionState::Update(Nz::StateMachine& fsm, Nz::Time /*elapsedTime*/) {
if (m_NextState)
fsm.ChangeState(std::move(m_NextState));
return true;
}
void OptionState::OnOptionPressed() {}
void OptionState::OnBackPressed() {
m_NextState = std::move(m_PreviousState);
}
} // namespace client
} // namespace blitz

View File

@@ -1,290 +0,0 @@
#include <iostream>
#include "Network.h"
#include <Nazara/Core.hpp>
#include <Nazara/Graphics.hpp>
#include <Nazara/Renderer.hpp>
#include <Nazara/Platform.hpp>
#include <Nazara/Physics3D.hpp>
#include <random>
static Nz::Vector3f Get2DDirectionVectorFromRotation(float yaw)
{
return {
-std::sin(yaw),
0,
-std::cos(yaw),
};
}
static void CreateLight(Nz::EnttWorld &world)
{
entt::handle lightEntity = world.CreateEntity();
{
auto &lightNode = lightEntity.emplace<Nz::NodeComponent>();
lightNode.SetPosition({0, 5, 0});
auto &entityLight = lightEntity.emplace<Nz::LightComponent>();
auto &spotLight = entityLight.AddLight<Nz::PointLight>(1);
spotLight.EnableShadowCasting(true);
spotLight.UpdateShadowMapSize(1024);
spotLight.UpdateRadius(10.0f);
spotLight.UpdateDiffuseFactor(0.5f);
}
}
static void CreateBoxes(Nz::EnttWorld &world)
{
constexpr float BoxDims = 1.f;
std::mt19937 rd(42);
std::uniform_real_distribution<float> colorDis(0.f, 360.f);
std::uniform_real_distribution<float> radiusDis(0.1f, 0.5f);
std::uniform_real_distribution<float> lengthDis(0.2f, 1.5f);
std::shared_ptr<Nz::GraphicalMesh> boxMesh = Nz::GraphicalMesh::Build(Nz::Primitive::Box(Nz::Vector3f(1.f)));
constexpr std::size_t BoxCount = 100;
for (std::size_t i = 0; i < BoxCount; ++i)
{
float width = lengthDis(rd);
float height = lengthDis(rd);
float depth = lengthDis(rd);
std::uniform_real_distribution<float> xRandom(-BoxDims * 0.5f + width, BoxDims * 0.5f - width);
std::uniform_real_distribution<float> yRandom(-BoxDims * 0.5f + height, BoxDims * 0.5f - height);
std::uniform_real_distribution<float> zRandom(-BoxDims * 0.5f + depth, BoxDims * 0.5f - depth);
entt::handle boxEntity = world.CreateEntity();
std::shared_ptr<Nz::MaterialInstance> boxMaterial = Nz::MaterialInstance::Instantiate(Nz::MaterialType::Phong);
boxMaterial->SetValueProperty("BaseColor", Nz::Color::sRGBToLinear(Nz::Color::FromHSV(colorDis(rd), 1.f, 1.f)));
std::shared_ptr<Nz::Model> sphereModel = std::make_shared<Nz::Model>(boxMesh);
sphereModel->SetMaterial(0, std::move(boxMaterial));
boxEntity.emplace<Nz::GraphicsComponent>(std::move(sphereModel));
auto &ballNode = boxEntity.emplace<Nz::NodeComponent>();
ballNode.SetPosition({xRandom(rd), yRandom(rd) + 20.0f, zRandom(rd)});
ballNode.SetScale({width, height, depth});
std::shared_ptr<Nz::BoxCollider3D> boxCollider = std::make_shared<Nz::BoxCollider3D>(Nz::Vector3f(width, height, depth));
Nz::RigidBody3D::DynamicSettings settings;
settings.geom = boxCollider;
settings.mass = width * height * depth;
boxEntity.emplace<Nz::RigidBody3DComponent>(settings);
}
}
static void CreateModel(Nz::EnttWorld &world)
{
std::filesystem::path resourceDir = "assets/models";
Nz::MeshParams meshParams;
meshParams.center = true;
meshParams.vertexRotation = Nz::EulerAnglesf(0.f, 0.f, 0.f);
meshParams.vertexScale = Nz::Vector3f(1.0f);
meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV_Tangent);
std::shared_ptr<Nz::Mesh> deambuMesh = Nz::Mesh::LoadFromFile(resourceDir / "sol.obj", meshParams);
if (!deambuMesh)
{
NazaraError("failed to load model");
return;
}
std::shared_ptr<Nz::GraphicalMesh> gfxMesh = Nz::GraphicalMesh::BuildFromMesh(*deambuMesh);
std::shared_ptr<Nz::Model> deambModel = std::make_shared<Nz::Model>(std::move(gfxMesh));
entt::handle deambEntity = world.CreateEntity();
{
auto &entityGfx = deambEntity.emplace<Nz::GraphicsComponent>();
entityGfx.AttachRenderable(deambModel);
auto &entityNode = deambEntity.emplace<Nz::NodeComponent>();
entityNode.SetPosition(Nz::Vector3f(0.f, 0.f, 0.f));
}
std::shared_ptr<Nz::RenderDevice> device = Nz::Graphics::Instance()->GetRenderDevice();
std::shared_ptr<Nz::MaterialInstance> material = Nz::MaterialInstance::Instantiate(Nz::MaterialType::Phong);
for (std::string_view passName : {"DepthPass", "ForwardPass"})
{
material->UpdatePassStates(passName, [](Nz::RenderStates &states)
{
states.depthClamp = true;
return true; });
}
std::mt19937 rd(42);
std::uniform_real_distribution<float> colorDis(0.f, 360.f);
material->SetValueProperty("BaseColor", Nz::Color::sRGBToLinear(Nz::Color::FromHSV(colorDis(rd), 1.f, 1.f)));
for (std::size_t i = 0; i < deambModel->GetSubMeshCount(); ++i)
deambModel->SetMaterial(i, material);
Nz::VertexMapper vertexMapper(*deambuMesh->GetSubMesh(0));
Nz::SparsePtr<Nz::Vector3f> vertices = vertexMapper.GetComponentPtr<Nz::Vector3f>(Nz::VertexComponent::Position);
auto shipCollider = std::make_shared<Nz::ConvexHullCollider3D>(vertices, vertexMapper.GetVertexCount(), 0.1f);
Nz::RigidBody3D::StaticSettings settings;
settings.geom = shipCollider;
deambEntity.emplace<Nz::RigidBody3DComponent>(settings);
}
static Nz::EulerAnglesf camAngles(0.f, 0.f, 0.f);
static Nz::MillisecondClock updateClock;
static void CreateCamera(Nz::EnttWorld &world, Nz::Window &window, Nz::Application<Nz::Graphics, Nz::Physics3D> &app)
{
Nz::RenderSystem &renderSystem = world.AddSystem<Nz::RenderSystem>();
Nz::SwapchainParameters params;
params.presentMode.clear();
params.presentMode.push_back(Nz::PresentMode::VerticalSync);
Nz::WindowSwapchain &windowSwapchain = renderSystem.CreateSwapchain(window, params);
// Création de la caméra
entt::handle cameraEntity = world.CreateEntity();
auto &cameraNode = cameraEntity.emplace<Nz::NodeComponent>();
cameraNode.SetPosition({0, 2.5, 0});
auto &cameraComponent = cameraEntity.emplace<Nz::CameraComponent>(std::make_shared<Nz::RenderWindow>(windowSwapchain), Nz::ProjectionType::Perspective);
cameraComponent.UpdateClearColor(Nz::Color(0.3f, 0.8f, 1.0f));
cameraComponent.UpdateZNear(0.1f);
cameraComponent.UpdateZFar(100.0f);
entt::handle playerEntity = world.CreateEntity();
auto &playerNode = playerEntity.emplace<Nz::NodeComponent>();
playerNode.SetPosition({0, 5, 0});
cameraNode.SetParent(playerEntity);
window.GetEventHandler().OnMouseMoved.Connect([&](const Nz::WindowEventHandler * /*eventHandler*/, const Nz::WindowEvent::MouseMoveEvent &event)
{
constexpr float sensitivity = 0.3f;
camAngles.yaw -= event.deltaX * sensitivity;
camAngles.yaw.Normalize();
camAngles.pitch = Nz::Clamp(camAngles.pitch - event.deltaY * sensitivity, -89.f, 89.f);
camAngles.roll = 0.0f;
cameraNode.SetRotation(camAngles);
});
Nz::Vector3f playerSize{0.5, 2, 0.5};
{
std::shared_ptr<Nz::GraphicalMesh> boxMesh = Nz::GraphicalMesh::Build(Nz::Primitive::Box(playerSize));
std::shared_ptr<Nz::MaterialInstance> boxMaterial = Nz::MaterialInstance::Instantiate(Nz::MaterialType::Phong);
boxMaterial->SetValueProperty("BaseColor", Nz::Color::sRGBToLinear({1.0, 0.0, 0.0}));
std::shared_ptr<Nz::Model> sphereModel = std::make_shared<Nz::Model>(boxMesh);
sphereModel->SetMaterial(0, std::move(boxMaterial));
playerEntity.emplace<Nz::GraphicsComponent>(std::move(sphereModel));
}
std::shared_ptr<Nz::BoxCollider3D> boxCollider = std::make_shared<Nz::BoxCollider3D>(playerSize);
static const int playerMass = 100000;
Nz::RigidBody3D::DynamicSettings settings;
settings.geom = boxCollider;
settings.mass = playerMass;
playerEntity.emplace<Nz::RigidBody3DComponent>(settings);
app.AddUpdaterFunc([playerEntity]()
{
if (std::optional<Nz::Time> deltaTime = updateClock.RestartIfOver(Nz::Time::Milliseconds(30)))
{
Nz::Vector3f front = Get2DDirectionVectorFromRotation(camAngles.yaw.ToRadians());
Nz::Vector3f left = Get2DDirectionVectorFromRotation(camAngles.yaw.ToRadians() + 3.1415926535/2.0);
auto& playerBody = playerEntity.get<Nz::RigidBody3DComponent>();
if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Z)){
std::cout << front << "\n";
std::cout << "Pos : " << playerBody.GetPosition() << " \n";
playerBody.AddForce(front * 10.f * playerMass, Nz::CoordSys::Global);
}
if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::S))
playerBody.AddForce(-front * 10.f * playerMass, Nz::CoordSys::Local);
if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Q))
playerBody.AddForce(left * 10.f * playerMass, Nz::CoordSys::Local);
if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::D))
playerBody.AddForce(-left * 10.f * playerMass, Nz::CoordSys::Local);
if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Space)){
playerBody.AddForce(Nz::Vector3f::Up() * 15.f * playerMass, Nz::CoordSys::Global);
}
if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::R)) {
auto& playerNode = playerEntity.get<Nz::NodeComponent>();
playerNode.SetPosition({0, 10, 0});
playerBody.SetPosition({0, 10, 0});
}
//playerBody.SetRotation({});
} });
}
int Video(int argc, char **argv) {
Nz::Application<Nz::Graphics, Nz::Physics3D> app(argc, argv);
auto &windowing = app.AddComponent<Nz::WindowingAppComponent>();
std::string windowTitle = "Blitz 2";
Nz::Window &window = windowing.CreateWindow(Nz::VideoMode(1920, 1080, 32), windowTitle);
auto &ecs = app.AddComponent<Nz::EntitySystemAppComponent>();
auto &world = ecs.AddWorld<Nz::EnttWorld>();
auto &physSystem = world.AddSystem<Nz::Physics3DSystem>();
physSystem.GetPhysWorld().SetMaxStepCount(1);
physSystem.GetPhysWorld().SetStepSize(Nz::Time::Milliseconds(20));
physSystem.GetPhysWorld().SetGravity(Nz::Vector3f::Down() * 9.81f);
CreateCamera(world, window, app);
CreateBoxes(world);
CreateModel(world);
CreateLight(world);
Nz::Mouse::SetRelativeMouseMode(true);
Nz::MillisecondClock fpsClock;
unsigned int fps = 0;
app.AddUpdaterFunc([&]()
{
fps++;
if (fpsClock.RestartIfOver(Nz::Time::Second()))
{
window.SetTitle(windowTitle + " - " + Nz::NumberToString(fps) + " FPS" + " - " + Nz::NumberToString(world.GetAliveEntityCount()) + " entities");
fps = 0;
} });
return app.Run();
}
int main(int argc, char **argv)
{
TestNetwork();
}

94
src/server/Server.cpp Normal file
View File

@@ -0,0 +1,94 @@
#include <server/Server.h>
#include <server/components/EnetConnection.h>
#include <server/components/ServerIdCounter.h>
#include <server/handlers/KeepAliveHandler.h>
#include <server/handlers/PlayerLoginHandler.h>
#include <blitz/systems/RemovePlayersSystem.h>
#include <server/systems/DisconnectSystem.h>
#include <server/systems/KeepAliveSystem.h>
namespace blitz {
namespace server {
Server::Server(std::uint16_t a_Port, Nz::EnttWorld& a_World) : m_World({a_World}), m_NetworkServer(a_Port) {
RegisterSystems();
m_NetworkServer.OnClientConnect.Connect(this, &Server::HandleConnect);
m_NetworkServer.OnClientDisconnect.Connect(this, &Server::HandleDisconnect);
m_NetworkServer.OnClientDisconnectTimeout.Connect(this, &Server::HandleDisconnect);
}
Server::~Server() {
CloseServer();
AtomicEnttWorld world = m_World;
world->RemoveSystem<KeepAliveSystem>();
world->RemoveSystem<DisconnectSystem>();
world->RemoveSystem<RemovePlayersSystem>();
}
void Server::HandleConnect(network::EnetConnection& a_Connection) {
Session newSession;
newSession.m_Connection = &a_Connection;
RegisterHandlers(newSession);
m_Sessions.insert({a_Connection.GetPeerId(), std::move(newSession)});
CreateEntity(a_Connection);
}
void Server::HandleDisconnect(network::EnetConnection& a_Connection) {
m_Sessions.erase(a_Connection.GetPeerId());
}
void Server::CreateEntity(network::EnetConnection& a_Connection) {
AtomicEnttWorld world = m_World;
auto entity = world->CreateEntity();
entity.emplace<EnetConnectionComponent>(&a_Connection);
}
void Server::RegisterSystems() {
AtomicEnttWorld world = m_World;
world->AddSystem<KeepAliveSystem>(m_World);
world->AddSystem<DisconnectSystem>(m_World, *this);
world->AddSystem<RemovePlayersSystem>(m_World);
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));
session.m_Handlers.push_back(std::make_unique<PlayerLoginHandler>(*session.m_Connection, m_World));
}
network::EnetConnection* Server::GetConnection(std::uint16_t a_PeerId) {
auto it = m_Sessions.find(a_PeerId);
if (it == m_Sessions.end())
return nullptr;
return it->second.m_Connection;
}
void Server::CloseConnection(std::uint16_t a_PeerId) {
m_NetworkServer.CloseConnection(a_PeerId);
}
void Server::CloseServer() {
for (auto& [peerId, session] : m_Sessions) {
CloseConnection(peerId);
}
}
bool Server::IsClosed() const {
return m_NetworkServer.IsClosed();
}
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,29 @@
#include <server/handlers/KeepAliveHandler.h>
#include <server/components/KeepAliveSession.h>
#include <Nazara/Core/Time.hpp>
namespace blitz {
namespace server {
KeepAliveHandler::KeepAliveHandler(network::EnetConnection& a_Connection, EnttWorld& a_World) :
protocol::PacketHandler(a_Connection, a_World) {
m_Slot.Connect(a_Connection.OnKeepAlive,
[this](const protocol::data::KeepAlive& a_KeepAlive) { Handle(m_Connection.GetPeerId(), a_KeepAlive); });
}
KeepAliveHandler::~KeepAliveHandler() {}
void KeepAliveHandler::Handle(std::uint16_t a_PeerId, const protocol::data::KeepAlive& a_KeepAlive) {
AtomicEnttWorld world = m_World;
world->GetRegistry().view<KeepAliveSessionComponent>().each([a_PeerId, &a_KeepAlive](auto& keepAliveSession) {
if (keepAliveSession.m_PeerId == a_PeerId && keepAliveSession.m_LastKeepAliveId == a_KeepAlive.m_KeepAliveId) {
keepAliveSession.m_LastTime = Nz::GetElapsedMilliseconds();
keepAliveSession.m_RecievedResponse = true;
}
});
}
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,72 @@
#include <server/handlers/PlayerLoginHandler.h>
#include <Nazara/Core/Time.hpp>
#include <blitz/common/Format.h>
#include <blitz/common/Log.h>
#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) {
AtomicEnttWorld world = m_World;
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});
}
LogD(Format("[Server] %s joined !", a_PlayerLogin.m_PlayerName.c_str()));
}
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,48 @@
#include <server/systems/DisconnectSystem.h>
#include <server/components/Disconnect.h>
#include <server/components/EnetConnection.h>
#include <blitz/components/PlayerRemove.h>
#include <server/Server.h>
#include <blitz/common/Format.h>
#include <blitz/common/Log.h>
namespace blitz {
namespace server {
DisconnectSystem::DisconnectSystem(entt::registry&, EnttWorld& a_World, Server& a_Server) : m_World(a_World), m_Server(a_Server) {}
void DisconnectSystem::Update(Nz::Time elapsedTime) {
AtomicEnttWorld world = m_World;
entt::registry& registry = world->GetRegistry();
auto disconnects = registry.view<DisconnectComponent>();
// broadcast player leave
for (auto entity : disconnects) {
auto* player = registry.try_get<PlayerInfoComponent>(entity);
if (player) {
for (auto [entity, connection] : registry.view<EnetConnectionComponent>().each()) {
connection.m_Connection->SendPlayerLeave({player->m_PlayerId});
}
LogD(Format("[Server] %s left !", player->m_Pseudo.c_str()));
}
}
// close connections
registry.view<EnetConnectionComponent, DisconnectComponent>().each(
[this](auto entity, EnetConnectionComponent& connection, DisconnectComponent disconnect) {
m_Server.CloseConnection(connection.m_Connection->GetPeerId());
});
// remove the entities
for (auto entity : disconnects) {
registry.emplace<PlayerRemoveComponent>(entity);
}
}
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,42 @@
#include <server/systems/KeepAliveSystem.h>
#include <server/components/Disconnect.h>
#include <server/components/EnetConnection.h>
#include <server/components/KeepAliveSession.h>
#include <random>
namespace blitz {
namespace server {
KeepAliveSystem::KeepAliveSystem(entt::registry&, EnttWorld& a_World) : m_World(a_World) {}
void KeepAliveSystem::Update(Nz::Time elapsedTime) {
AtomicEnttWorld world = m_World;
entt::registry& registry = world->GetRegistry();
registry.view<KeepAliveSessionComponent, EnetConnectionComponent>().each(
[&registry](auto entity, auto& keepAliveSession, auto& connection) {
auto duration = Nz::GetElapsedMilliseconds() - keepAliveSession.m_LastTime;
if (duration > Nz::Time::Seconds(10)) {
if (keepAliveSession.m_RecievedResponse) {
keepAliveSession.m_RecievedResponse = false;
keepAliveSession.m_LastTime = Nz::GetElapsedMilliseconds();
std::random_device rd;
std::uniform_int_distribution<std::uint64_t> dis(0, std::numeric_limits<std::uint64_t>::max());
std::uint64_t keepAliveId = dis(rd);
keepAliveSession.m_LastKeepAliveId = keepAliveId;
connection.m_Connection->SendKeepAlive({keepAliveId});
} else {
// We kick the player because he's not responding anymore
registry.emplace<DisconnectComponent>(entity);
}
}
});
}
} // namespace server
} // namespace blitz

View File

@@ -0,0 +1,47 @@
#include <blitz/network/EnetClient.h>
#include <blitz/network/EnetServer.h>
#include <blitz/utils/Test.h>
using namespace std::chrono_literals;
int main() {
static constexpr std::uint16_t port = 25565;
int serverSendCount = 0;
int clientSendCount = 0;
blitz::network::EnetServer server(port);
blitz::network::EnetClient client(Nz::IpAddress{"127.0.0.1:" + std::to_string(port)});
blitz::network::EnetClient client1(Nz::IpAddress{"127.0.0.1:" + std::to_string(port)});
blitz::network::EnetClient client2(Nz::IpAddress{"127.0.0.1:" + std::to_string(port)});
blitz::network::EnetClient client3(Nz::IpAddress{"127.0.0.1:" + std::to_string(port)});
blitz::network::EnetClient client4(Nz::IpAddress{"127.0.0.1:" + std::to_string(port)});
std::this_thread::sleep_for(1s);
blitz_test_assert(client.GetConnection().IsConnected());
for (int i = 0; i < 5; i++) {
blitz_test_assert(server.GetConnection(i));
}
server.GetConnection(0)->OnKeepAlive.Connect([&server, &serverSendCount](const blitz::protocol::data::KeepAlive&) {
server.GetConnection(0)->SendKeepAlive({69});
serverSendCount++;
});
client.GetConnection().OnKeepAlive.Connect([&client, &clientSendCount](const blitz::protocol::data::KeepAlive&) {
client.GetConnection().SendKeepAlive({69});
clientSendCount++;
});
std::this_thread::sleep_for(1s);
client.GetConnection().SendKeepAlive({69});
std::this_thread::sleep_for(1s);
blitz_test_assert(serverSendCount > 10 && clientSendCount > 10);
return BLITZ_TEST_SUCCESSFUL;
}

View File

@@ -1,7 +1,10 @@
add_rules("mode.debug", "mode.release")
add_repositories("nazara-repo https://github.com/NazaraEngine/xmake-repo.git")
add_requires("nazaraengine", { debug = is_mode("debug") })
add_repositories("nazara-imgui-repo https://github.com/SweetId/NazaraImgui-xmake-repo")
add_requires("nazaraengine", { debug = false })
add_requires("nazaraimgui")
set_languages("c++20")
set_warnings("all")
@@ -10,20 +13,25 @@ if is_mode("release") then
set_warnings("all", "error")
end
target("BlitzLib")
set_kind("static")
add_files("src/blitz/**.cpp")
add_includedirs("include")
add_packages("nazaraengine")
add_includedirs("include")
target("Blitz2")
set_kind("binary")
add_files("src/*.cpp")
add_packages("nazaraengine")
add_includedirs("include")
add_deps("BlitzLib")
set_rundir(".")
set_kind("static")
add_files("src/blitz/**.cpp", "src/server/**.cpp")
add_packages("nazaraimgui", "nazaraengine", {public = true})
target("Blitz2Server")
set_kind("binary")
add_deps("Blitz2")
set_default(false)
add_files("src/ServerMain.cpp")
target("Blitz2Client")
set_kind("binary")
add_files("src/ClientMain.cpp", "src/client/**.cpp")
add_deps("Blitz2")
set_rundir(".")
-- Tests were introduced in that version
@@ -35,13 +43,11 @@ for _, file in ipairs(os.files("test/**.cpp")) do
target(name)
set_kind("binary")
add_includedirs("include", "test")
add_files(file)
set_default(false)
add_deps("BlitzLib")
add_packages("nazaraengine")
add_deps("Blitz2")
add_tests("compile_and_run")
end