5 Commits

Author SHA1 Message Date
390bc79dbc bro 2025-10-26 21:34:35 +01:00
07f3c9b852 add imgui-node editor dep 2025-10-26 18:28:04 +01:00
d1d2b63be8 remove return 2025-08-23 12:55:37 +02:00
62c5c762f9 detect memory leaks 2025-08-23 12:55:26 +02:00
1d436aa1c3 add bots 2025-08-23 12:54:48 +02:00
19 changed files with 9926 additions and 39 deletions

View File

@@ -3,27 +3,38 @@
#include <client/IClientSocket.h> #include <client/IClientSocket.h>
#include <client/PlayerManager.h> #include <client/PlayerManager.h>
#include <td/common/StateMachine.h> #include <td/common/StateMachine.h>
#include <optional>
namespace td { namespace td {
namespace client { namespace client {
class ClientState; class ClientState;
class LoggingState;
class Client : public StateMachine<Client, void, float> { class Client : public StateMachine<Client, void, float> {
private: private:
std::shared_ptr<IClientSocket> m_Socket; std::shared_ptr<IClientSocket> m_Socket;
PlayerManager m_Players; PlayerManager m_Players;
std::optional<PlayerID> m_Id;
public: public:
Client(const std::shared_ptr<IClientSocket>& a_Socket, const std::string& a_PlayerName); Client(const std::shared_ptr<IClientSocket>& a_Socket);
~Client();
void SendPacket(const protocol::PacketBase& a_Packet); void SendPacket(const protocol::PacketBase& a_Packet);
void Disconnect();
const PlayerManager& GetPlayers() const { const PlayerManager& GetPlayers() const {
return m_Players; return m_Players;
} }
const std::optional<PlayerID> GetId() const {
return m_Id;
}
friend class ClientState; friend class ClientState;
friend class LoggingState;
}; };
} // namespace client } // namespace client

View File

@@ -14,6 +14,7 @@ class IClientSocket {
utils::Signal<const protocol::PacketBase&> OnReceive; utils::Signal<const protocol::PacketBase&> OnReceive;
virtual void Send(const protocol::PacketBase& a_Packet) = 0; virtual void Send(const protocol::PacketBase& a_Packet) = 0;
virtual void Disconnect() = 0;
IClientSocket() {} IClientSocket() {}
virtual ~IClientSocket() {} virtual ~IClientSocket() {}

View File

@@ -27,6 +27,7 @@ class FakeSocket : public IClientSocket {
void ReceiveFromFakePeer(PeerID a_Peer, const protocol::PacketBase& a_Packet); void ReceiveFromFakePeer(PeerID a_Peer, const protocol::PacketBase& a_Packet);
virtual void Send(const protocol::PacketBase& a_Packet) override; virtual void Send(const protocol::PacketBase& a_Packet) override;
virtual void Disconnect() override;
}; };
} // namespace client } // namespace client

View File

@@ -16,8 +16,8 @@ class StateMachine {
virtual TReturn Update(TArgs... args) = 0; virtual TReturn Update(TArgs... args) = 0;
template <typename T, typename... Args> template <typename T, typename... Args>
T* ChangeState(Args... args) { void ChangeState(Args... args) {
return m_StateMachine.template ChangeState<T>(std::forward<Args>(args)...); m_StateMachine.template ChangeState<T>(std::forward<Args>(args)...);
} }
protected: protected:
@@ -36,7 +36,7 @@ class StateMachine {
} }
template <typename T, typename... Args> template <typename T, typename... Args>
T* ChangeState(Args... args) { void ChangeState(Args... args) {
auto* currentState = m_State.get(); auto* currentState = m_State.get();
auto newState = std::make_unique<T>(static_cast<TDerived&>(*this), std::forward<Args>(args)...); auto newState = std::make_unique<T>(static_cast<TDerived&>(*this), std::forward<Args>(args)...);
// This allows chaining // This allows chaining
@@ -44,7 +44,6 @@ class StateMachine {
m_State = std::move(newState); m_State = std::move(newState);
OnStateChange(*m_State); OnStateChange(*m_State);
} }
return static_cast<T*>(m_State.get());
} }
private: private:

View File

@@ -6,6 +6,7 @@
#include <td/render/Renderer.h> #include <td/render/Renderer.h>
#include <td/simulation/ClientSimulation.h> #include <td/simulation/ClientSimulation.h>
#include <client/state/GameState.h> #include <client/state/GameState.h>
#include <server/socket/FakeSocket.h>
namespace td { namespace td {
@@ -13,11 +14,13 @@ class DebugWorldState : public DisplayState {
private: private:
render::RenderPipeline m_Renderer; render::RenderPipeline m_Renderer;
render::Camera m_Camera; render::Camera m_Camera;
std::unique_ptr<client::Client> m_Client;
std::unique_ptr<client::Client> m_Client2;
std::unique_ptr<server::Server> m_Server; std::unique_ptr<server::Server> m_Server;
std::unique_ptr<client::Client> m_Client;
client::GameState* m_ClientState; client::GameState* m_ClientState;
std::vector<std::unique_ptr<client::Client>> m_FakeClients;
std::shared_ptr<server::FakeSocket> m_ServerSocket;
public: public:
DebugWorldState(Display& a_Display); DebugWorldState(Display& a_Display);
~DebugWorldState(); ~DebugWorldState();

View File

@@ -0,0 +1,127 @@
#pragma once
#include <td/display/DisplayState.h>
#include <imgui_node_editor.h>
namespace ed = ax::NodeEditor;
namespace td {
class NodeEditorState : public DisplayState {
public:
enum class PinType {
Flow,
Bool,
Int,
Float,
String,
Object,
Function,
Delegate,
};
enum class PinKind { Output, Input };
enum class NodeType { Blueprint, Simple, Tree, Comment, Houdini };
struct Pin;
struct Node {
ed::NodeId ID;
std::string Name;
std::vector<Pin> Inputs;
std::vector<Pin> Outputs;
ImColor Color;
NodeType Type;
ImVec2 Size;
std::string State;
std::string SavedState;
Node(int id, const char* name, ImColor color = ImColor(255, 255, 255)) :
ID(id), Name(name), Color(color), Type(NodeType::Blueprint), Size(0, 0) {}
};
struct Pin {
ed::PinId ID;
::td::NodeEditorState::Node* Node;
std::string Name;
PinType Type;
PinKind Kind;
Pin(int id, const char* name, PinType type) : ID(id), Node(nullptr), Name(name), Type(type), Kind(PinKind::Input) {}
};
struct Link {
ed::LinkId ID;
ed::PinId StartPinID;
ed::PinId EndPinID;
ImColor Color;
Link(ed::LinkId id, ed::PinId startPinId, ed::PinId endPinId) :
ID(id), StartPinID(startPinId), EndPinID(endPinId), Color(255, 255, 255) {}
};
struct NodeIdLess {
bool operator()(const ed::NodeId& lhs, const ed::NodeId& rhs) const {
return lhs.AsPointer() < rhs.AsPointer();
}
};
int m_NextId = 1;
const int m_PinIconSize = 24;
std::vector<Node> m_Nodes;
std::vector<Link> m_Links;
ImTextureID m_HeaderBackground = 0;
ImTextureID m_SaveIcon = 0;
ImTextureID m_RestoreIcon = 0;
const float m_TouchTime = 1.0f;
std::map<ed::NodeId, float, NodeIdLess> m_NodeTouchTime;
bool m_ShowOrdinals = false;
public:
NodeEditorState(Display& a_Display);
~NodeEditorState();
virtual void Update(float a_Delta) override;
private:
int GetNextId();
ed::LinkId GetNextLinkId();
void TouchNode(ed::NodeId id);
float GetTouchProgress(ed::NodeId id);
void UpdateTouch();
Node* FindNode(ed::NodeId id);
Link* FindLink(ed::LinkId id);
Pin* FindPin(ed::PinId id);
bool IsPinLinked(ed::PinId id);
bool CanCreateLink(Pin* a, Pin* b);
void BuildNode(Node* node);
Node* SpawnInputActionNode();
Node* SpawnBranchNode();
Node* SpawnDoNNode();
Node* SpawnOutputActionNode();
Node* SpawnPrintStringNode();
Node* SpawnMessageNode();
Node* SpawnSetTimerNode();
Node* SpawnLessNode();
Node* SpawnWeirdNode();
Node* SpawnTraceByChannelNode();
Node* SpawnTreeSequenceNode();
Node* SpawnTreeTaskNode();
Node* SpawnTreeTask2Node();
Node* SpawnComment();
Node* SpawnHoudiniTransformNode();
Node* SpawnHoudiniGroupNode();
void BuildNodes();
ImColor GetIconColor(PinType type);
void DrawPinIcon(const Pin& pin, bool connected, int alpha);
void ShowStyleEditor(bool* show = nullptr);
void ShowLeftPane(float paneWidth);
};
} // namespace td

View File

@@ -42,8 +42,11 @@ class RenderPipeline {
virtual ~RenderPipeline() {} virtual ~RenderPipeline() {}
template <typename T, typename... Args> template <typename T, typename... Args>
void AddRenderer(Args&&... args) { T& AddRenderer(Args&&... args) {
m_Renderers.push_back(std::make_unique<T>(args...)); auto ptr = std::make_unique<T>(args...);
auto rawPtr = ptr.get();
m_Renderers.push_back(std::move(ptr));
return *rawPtr;
} }
void Clear() { void Clear() {

View File

@@ -6,10 +6,17 @@
namespace td { namespace td {
namespace render { namespace render {
/**
* \brief This is a debug class
*/
class PlayerListRenderer : public BasicRenderer { class PlayerListRenderer : public BasicRenderer {
private: private:
const client::PlayerManager& m_Players; const client::PlayerManager& m_Players;
public: public:
utils::Signal<> OnPlayerCreate;
// utils::Signal<> OnRequestPOV;
utils::Signal<PlayerID> OnPlayerKick;
virtual void Render(float a_Lerp) override; virtual void Render(float a_Lerp) override;
PlayerListRenderer(const client::PlayerManager& a_Players); PlayerListRenderer(const client::PlayerManager& a_Players);

6
lib/imgui-node.lua Normal file
View File

@@ -0,0 +1,6 @@
target("imgui-node-editor")
set_kind("static")
add_files("imgui-node-editor/**.cpp")
add_includedirs("imgui-node-editor", {public = true})
add_packages("imgui")

View File

@@ -5,13 +5,21 @@
namespace td { namespace td {
namespace client { namespace client {
Client::Client(const std::shared_ptr<IClientSocket>& a_Socket, const std::string& a_PlayerName) : m_Socket(a_Socket), m_Players(a_Socket) { Client::Client(const std::shared_ptr<IClientSocket>& a_Socket) : m_Socket(a_Socket), m_Players(a_Socket) {
// ChangeState<LoggingState>(a_PlayerName); // ChangeState<LoggingState>(a_PlayerName);
} }
Client::~Client() {
Disconnect();
}
void Client::SendPacket(const protocol::PacketBase& a_Packet) { void Client::SendPacket(const protocol::PacketBase& a_Packet) {
m_Socket->Send(a_Packet); m_Socket->Send(a_Packet);
} }
void Client::Disconnect() {
m_Socket->Disconnect();
}
} // namespace client } // namespace client
} // namespace td } // namespace td

View File

@@ -15,5 +15,9 @@ std::shared_ptr<FakeSocket> FakeSocket::Connect(const std::shared_ptr<server::Fa
return socket; return socket;
} }
void FakeSocket::Disconnect() {
m_Server->DisconnectFakePeer(m_PeerId);
}
} // namespace client } // namespace client
} // namespace td } // namespace td

View File

@@ -19,6 +19,7 @@ void LoggingState::Handle(const protocol::packets::PlayerJoinPacket& a_Packet) {
} }
void LoggingState::Handle(const protocol::packets::LoggingSuccessPacket& a_Packet) { void LoggingState::Handle(const protocol::packets::LoggingSuccessPacket& a_Packet) {
m_StateMachine.m_Id = a_Packet->m_PlayerId;
ChangeState<LobbyState>(); ChangeState<LobbyState>();
} }

View File

@@ -1,12 +1,12 @@
#include <chrono> #include <chrono>
#include <td/display/state/MainMenuState.h> #include <td/display/state/NodeEditorState.h>
#include <td/misc/Time.h> #include <td/misc/Time.h>
int main(int argc, char** argv) { int main(int argc, char** argv) {
// init GL context // init GL context
td::Display display(1920, 1080, "Tower-Defense 2"); td::Display display(1920, 1080, "Tower-Defense 2");
display.ChangeState<td::MainMenuState>(); display.ChangeState<td::NodeEditorState>();
td::Timer timer; td::Timer timer;
while (!display.IsCloseRequested()) { while (!display.IsCloseRequested()) {

View File

@@ -36,14 +36,13 @@ void PlayerManager::ConnectionHandler::Handle(const protocol::packets::Disconnec
PlayerManager::PlayerManager(const std::shared_ptr<IServerSocket>& a_Socket) : m_Socket(a_Socket) { PlayerManager::PlayerManager(const std::shared_ptr<IServerSocket>& a_Socket) : m_Socket(a_Socket) {
a_Socket->RegisterHandler([this](PlayerID a_PlayerId) { return std::make_unique<ConnectionHandler>(*this, a_PlayerId); }); a_Socket->RegisterHandler([this](PlayerID a_PlayerId) { return std::make_unique<ConnectionHandler>(*this, a_PlayerId); });
a_Socket->OnPlayerDisconnect.Connect(std::bind(&PlayerManager::RemovePlayer, this, std::placeholders::_1)); a_Socket->OnPlayerDisconnect.Connect(std::bind(&PlayerManager::Disconnect, this, std::placeholders::_1));
} }
void PlayerManager::Disconnect(PlayerID a_Player) { void PlayerManager::Disconnect(PlayerID a_Player) {
if (!m_Players.contains(a_Player)) if (!m_Players.contains(a_Player))
return; return;
std::cout << "[Server] " << +a_Player << " wants to disconnect !\n"; std::cout << "[Server] " << +a_Player << " wants to disconnect !\n";
m_Socket->Disconnect(a_Player);
m_Socket->Broadcast(protocol::packets::PlayerLeavePacket(a_Player)); m_Socket->Broadcast(protocol::packets::PlayerLeavePacket(a_Player));
m_Players.erase(a_Player); m_Players.erase(a_Player);
} }
@@ -51,6 +50,7 @@ void PlayerManager::Disconnect(PlayerID a_Player) {
PlayerManager::~PlayerManager() {} PlayerManager::~PlayerManager() {}
void PlayerManager::RemovePlayer(PlayerID a_Player) { void PlayerManager::RemovePlayer(PlayerID a_Player) {
m_Socket->Disconnect(a_Player);
Disconnect(a_Player); Disconnect(a_Player);
} }

View File

@@ -26,12 +26,12 @@ namespace td {
DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) { DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) {
// server // server
auto serverFakeSocket = std::make_shared<server::FakeSocket>(); m_ServerSocket = std::make_shared<server::FakeSocket>();
m_Server = std::make_unique<server::Server>(serverFakeSocket); m_Server = std::make_unique<server::Server>(m_ServerSocket);
// client // client
auto clientFakeSocket = client::FakeSocket::Connect(serverFakeSocket); auto clientFakeSocket = client::FakeSocket::Connect(m_ServerSocket);
m_Client = std::make_unique<client::Client>(clientFakeSocket, "Player0"); m_Client = std::make_unique<client::Client>(clientFakeSocket);
// TODO: make it better // TODO: make it better
m_Client->OnStateChange.Connect([this](client::Client::State& a_State) { m_Client->OnStateChange.Connect([this](client::Client::State& a_State) {
@@ -41,7 +41,24 @@ DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) {
m_Renderer.AddRenderer<render::WorldRenderer>(m_Camera, clientWorld); m_Renderer.AddRenderer<render::WorldRenderer>(m_Camera, clientWorld);
m_Renderer.AddRenderer<render::EntityRenderer>(m_Camera, clientWorld); m_Renderer.AddRenderer<render::EntityRenderer>(m_Camera, clientWorld);
m_Renderer.AddRenderer<render::TowerRenderer>(m_Camera, clientWorld); m_Renderer.AddRenderer<render::TowerRenderer>(m_Camera, clientWorld);
m_Renderer.AddRenderer<render::PlayerListRenderer>(m_Client->GetPlayers());
auto& list = m_Renderer.AddRenderer<render::PlayerListRenderer>(m_Client->GetPlayers());
list.OnPlayerCreate.Connect([this]() {
auto newSocket = client::FakeSocket::Connect(m_ServerSocket);
auto newClient = std::make_unique<client::Client>(newSocket);
newClient->ChangeState<client::LoggingState>("Bot");
m_FakeClients.push_back(std::move(newClient));
});
list.OnPlayerKick.Connect([this](PlayerID a_Player) {
auto it = std::find_if(m_FakeClients.begin(), m_FakeClients.end(), [a_Player](auto& clientPtr){
if (!clientPtr->GetId().has_value())
return false;
return clientPtr->GetId().value() == a_Player;
});
m_FakeClients.erase(it);
});
// update state // update state
m_ClientState = gameState; m_ClientState = gameState;
@@ -50,11 +67,6 @@ DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) {
m_Client->ChangeState<client::LoggingState>("Player0"); m_Client->ChangeState<client::LoggingState>("Player0");
// client2
auto clientFakeSocket2 = client::FakeSocket::Connect(serverFakeSocket);
m_Client2 = std::make_unique<client::Client>(clientFakeSocket2, "Player1");
m_Client2->ChangeState<client::LoggingState>("Player1");
// camera // camera
m_Camera.SetCamPos({77, 7, 13}); m_Camera.SetCamPos({77, 7, 13});
m_Camera.UpdatePerspective(m_StateMachine.GetAspectRatio()); m_Camera.UpdatePerspective(m_StateMachine.GetAspectRatio());
@@ -73,10 +85,17 @@ void DebugWorldState::OnAspectRatioChange(float a_Ratio) {
void DebugWorldState::OnKeyDown(SDL_Keycode a_Key) { void DebugWorldState::OnKeyDown(SDL_Keycode a_Key) {
// temporary tests // temporary tests
if (a_Key == SDLK_A) { switch (a_Key) {
m_Client->SendPacket(td::protocol::packets::SpawnTroopPacket(td::EntityType::Zombie, 1)); case SDLK_A:
} else if (a_Key == SDLK_Z) { m_Client->SendPacket(td::protocol::packets::SpawnTroopPacket(td::EntityType::Zombie, 1));
m_Client->SendPacket(td::protocol::packets::PlaceTowerPacket(td::TowerType::Archer, td::TowerCoords(77, 13))); break;
case SDLK_Z:
m_Client->SendPacket(td::protocol::packets::PlaceTowerPacket(td::TowerType::Archer, td::TowerCoords(77, 13)));
break;
default:
break;
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,29 @@
#include <td/render/renderer/PlayerListRenderer.h> #include <td/render/renderer/PlayerListRenderer.h>
#include <imgui.h> #include <imgui.h>
#include <optional>
#include <iostream>
namespace td { namespace td {
namespace render { namespace render {
void PlayerListRenderer::Render(float a_Lerp) { void PlayerListRenderer::Render(float a_Lerp) {
ImGui::Begin("Players"); ImGui::Begin("Players");
if (ImGui::Button("Add player")) {
OnPlayerCreate();
}
std::optional<PlayerID> kick;
for (const auto& [id, player] : m_Players) { for (const auto& [id, player] : m_Players) {
ImGui::PushID(id);
ImGui::Text("[%i] %s", id, player.m_PlayerName.c_str()); ImGui::Text("[%i] %s", id, player.m_PlayerName.c_str());
ImGui::SameLine();
if (ImGui::Button("Kick")){
kick = id;
}
ImGui::PopID();
} }
ImGui::End(); ImGui::End();
if (kick.has_value())
OnPlayerKick(*kick);
} }
PlayerListRenderer::PlayerListRenderer(const client::PlayerManager& a_Players) : m_Players(a_Players) {} PlayerListRenderer::PlayerListRenderer(const client::PlayerManager& a_Players) : m_Players(a_Players) {}

View File

@@ -2,8 +2,7 @@ add_rules("mode.debug", "mode.release")
add_repositories("persson-repo https://git.ale-pri.com/Persson-dev/xmake-repo.git") add_repositories("persson-repo https://git.ale-pri.com/Persson-dev/xmake-repo.git")
add_requires("imgui 1.92.0", {configs = {sdl3 = true, opengl3 = true}}) add_requires("imgui[sdl3,opengl3] 1.92.1", "splib 2.3.2", "zlib", "glew", "fpm", "enet6")
add_requires("libsdl3 3.2.16", "splib 2.3.2", "zlib", "glew", "fpm", "enet6")
set_languages("c++20") set_languages("c++20")
@@ -11,9 +10,13 @@ set_warnings("all")
if is_mode("release") then if is_mode("release") then
set_warnings("all", "error") set_warnings("all", "error")
else
set_policy("build.sanitizer.address", true)
set_policy("build.sanitizer.leak", true)
set_policy("build.sanitizer.undefined", true)
end end
option("valgrind", {description = "Run binary with valgrind", default = false}) includes("lib/*.lua")
target("Tower-Defense2") target("Tower-Defense2")
add_includedirs("include", {public = true}) add_includedirs("include", {public = true})
@@ -22,12 +25,7 @@ target("Tower-Defense2")
add_packages("libsdl3", "imgui", "glew", "splib", "zlib", "fpm", "enet6", {public = true}) add_packages("libsdl3", "imgui", "glew", "splib", "zlib", "fpm", "enet6", {public = true})
set_rundir(".") set_rundir(".")
add_defines("TD_GL_LOADER_GLEW") add_defines("TD_GL_LOADER_GLEW")
add_deps("imgui-node-editor")
if has_config("valgrind") then
on_run(function (target)
os.execv("valgrind", {"-s", "--leak-check=full", target:targetfile()})
end)
end
-- Tests -- Tests