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/PlayerManager.h>
#include <td/common/StateMachine.h>
#include <optional>
namespace td {
namespace client {
class ClientState;
class LoggingState;
class Client : public StateMachine<Client, void, float> {
private:
std::shared_ptr<IClientSocket> m_Socket;
PlayerManager m_Players;
std::optional<PlayerID> m_Id;
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 Disconnect();
const PlayerManager& GetPlayers() const {
return m_Players;
}
const std::optional<PlayerID> GetId() const {
return m_Id;
}
friend class ClientState;
friend class LoggingState;
};
} // namespace client

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@
#include <td/render/Renderer.h>
#include <td/simulation/ClientSimulation.h>
#include <client/state/GameState.h>
#include <server/socket/FakeSocket.h>
namespace td {
@@ -13,11 +14,13 @@ class DebugWorldState : public DisplayState {
private:
render::RenderPipeline m_Renderer;
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<client::Client> m_Client;
client::GameState* m_ClientState;
std::vector<std::unique_ptr<client::Client>> m_FakeClients;
std::shared_ptr<server::FakeSocket> m_ServerSocket;
public:
DebugWorldState(Display& a_Display);
~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() {}
template <typename T, typename... Args>
void AddRenderer(Args&&... args) {
m_Renderers.push_back(std::make_unique<T>(args...));
T& AddRenderer(Args&&... args) {
auto ptr = std::make_unique<T>(args...);
auto rawPtr = ptr.get();
m_Renderers.push_back(std::move(ptr));
return *rawPtr;
}
void Clear() {

View File

@@ -6,10 +6,17 @@
namespace td {
namespace render {
/**
* \brief This is a debug class
*/
class PlayerListRenderer : public BasicRenderer {
private:
const client::PlayerManager& m_Players;
public:
utils::Signal<> OnPlayerCreate;
// utils::Signal<> OnRequestPOV;
utils::Signal<PlayerID> OnPlayerKick;
virtual void Render(float a_Lerp) override;
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 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);
}
Client::~Client() {
Disconnect();
}
void Client::SendPacket(const protocol::PacketBase& a_Packet) {
m_Socket->Send(a_Packet);
}
void Client::Disconnect() {
m_Socket->Disconnect();
}
} // namespace client
} // namespace td

View File

@@ -15,5 +15,9 @@ std::shared_ptr<FakeSocket> FakeSocket::Connect(const std::shared_ptr<server::Fa
return socket;
}
void FakeSocket::Disconnect() {
m_Server->DisconnectFakePeer(m_PeerId);
}
} // namespace client
} // 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) {
m_StateMachine.m_Id = a_Packet->m_PlayerId;
ChangeState<LobbyState>();
}

View File

@@ -1,12 +1,12 @@
#include <chrono>
#include <td/display/state/MainMenuState.h>
#include <td/display/state/NodeEditorState.h>
#include <td/misc/Time.h>
int main(int argc, char** argv) {
// init GL context
td::Display display(1920, 1080, "Tower-Defense 2");
display.ChangeState<td::MainMenuState>();
display.ChangeState<td::NodeEditorState>();
td::Timer timer;
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) {
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) {
if (!m_Players.contains(a_Player))
return;
std::cout << "[Server] " << +a_Player << " wants to disconnect !\n";
m_Socket->Disconnect(a_Player);
m_Socket->Broadcast(protocol::packets::PlayerLeavePacket(a_Player));
m_Players.erase(a_Player);
}
@@ -51,6 +50,7 @@ void PlayerManager::Disconnect(PlayerID a_Player) {
PlayerManager::~PlayerManager() {}
void PlayerManager::RemovePlayer(PlayerID a_Player) {
m_Socket->Disconnect(a_Player);
Disconnect(a_Player);
}

View File

@@ -26,12 +26,12 @@ namespace td {
DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) {
// server
auto serverFakeSocket = std::make_shared<server::FakeSocket>();
m_Server = std::make_unique<server::Server>(serverFakeSocket);
m_ServerSocket = std::make_shared<server::FakeSocket>();
m_Server = std::make_unique<server::Server>(m_ServerSocket);
// client
auto clientFakeSocket = client::FakeSocket::Connect(serverFakeSocket);
m_Client = std::make_unique<client::Client>(clientFakeSocket, "Player0");
auto clientFakeSocket = client::FakeSocket::Connect(m_ServerSocket);
m_Client = std::make_unique<client::Client>(clientFakeSocket);
// TODO: make it better
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::EntityRenderer>(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
m_ClientState = gameState;
@@ -50,11 +67,6 @@ DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) {
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
m_Camera.SetCamPos({77, 7, 13});
m_Camera.UpdatePerspective(m_StateMachine.GetAspectRatio());
@@ -73,10 +85,17 @@ void DebugWorldState::OnAspectRatioChange(float a_Ratio) {
void DebugWorldState::OnKeyDown(SDL_Keycode a_Key) {
// temporary tests
if (a_Key == SDLK_A) {
m_Client->SendPacket(td::protocol::packets::SpawnTroopPacket(td::EntityType::Zombie, 1));
} else if (a_Key == SDLK_Z) {
m_Client->SendPacket(td::protocol::packets::PlaceTowerPacket(td::TowerType::Archer, td::TowerCoords(77, 13)));
switch (a_Key) {
case SDLK_A:
m_Client->SendPacket(td::protocol::packets::SpawnTroopPacket(td::EntityType::Zombie, 1));
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 <imgui.h>
#include <iostream>
#include <optional>
namespace td {
namespace render {
void PlayerListRenderer::Render(float a_Lerp) {
ImGui::Begin("Players");
if (ImGui::Button("Add player")) {
OnPlayerCreate();
}
std::optional<PlayerID> kick;
for (const auto& [id, player] : m_Players) {
ImGui::PushID(id);
ImGui::Text("[%i] %s", id, player.m_PlayerName.c_str());
ImGui::SameLine();
if (ImGui::Button("Kick")){
kick = id;
}
ImGui::PopID();
}
ImGui::End();
if (kick.has_value())
OnPlayerKick(*kick);
}
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_requires("imgui 1.92.0", {configs = {sdl3 = true, opengl3 = true}})
add_requires("libsdl3 3.2.16", "splib 2.3.2", "zlib", "glew", "fpm", "enet6")
add_requires("imgui[sdl3,opengl3] 1.92.1", "splib 2.3.2", "zlib", "glew", "fpm", "enet6")
set_languages("c++20")
@@ -11,9 +10,13 @@ set_warnings("all")
if is_mode("release") then
set_warnings("all", "error")
else
set_policy("build.sanitizer.address", true)
set_policy("build.sanitizer.leak", true)
set_policy("build.sanitizer.undefined", true)
end
option("valgrind", {description = "Run binary with valgrind", default = false})
includes("lib/*.lua")
target("Tower-Defense2")
add_includedirs("include", {public = true})
@@ -22,12 +25,7 @@ target("Tower-Defense2")
add_packages("libsdl3", "imgui", "glew", "splib", "zlib", "fpm", "enet6", {public = true})
set_rundir(".")
add_defines("TD_GL_LOADER_GLEW")
if has_config("valgrind") then
on_run(function (target)
os.execv("valgrind", {"-s", "--leak-check=full", target:targetfile()})
end)
end
add_deps("imgui-node-editor")
-- Tests