4 Commits

Author SHA1 Message Date
42cfbc80ee lua tets 2025-10-31 13:25:39 +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
16 changed files with 186 additions and 48 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

@@ -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);

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,18 +1,68 @@
#include <chrono>
#include <td/display/state/MainMenuState.h>
#include <td/misc/Time.h>
// #include <chrono>
// #include <td/display/state/MainMenuState.h>
// #include <td/misc/Time.h>
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
void some_function() {
std::cout << "some function!" << std::endl;
}
void some_other_function() {
std::cout << "some other function!" << std::endl;
}
struct some_class {
int variable = 30;
double member_function() {
return 24.5;
}
};
int main(int argc, char** argv) {
// 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::MainMenuState>();
td::Timer timer;
while (!display.IsCloseRequested()) {
display.PollEvents();
display.Update(timer.GetDelta());
}
// td::Timer timer;
// while (!display.IsCloseRequested()) {
// display.PollEvents();
// display.Update(timer.GetDelta());
// }
std::cout << "=== functions (all) ===" << std::endl;
sol::state lua;
lua.open_libraries(sol::lib::base);
// put an instance of "some_class" into lua
// (we'll go into more detail about this later
// just know here that it works and is
// put into lua as a userdata
lua.set("sc", some_class());
// binds a plain function
lua["f1"] = some_function;
lua.set_function("f2", &some_other_function);
// binds just the member function
lua["m1"] = &some_class::member_function;
// binds the class to the type
lua.set_function("m2", &some_class::member_function, some_class{});
// binds just the member variable as a function
lua["v1"] = &some_class::variable;
// binds class with member variable as function
lua.set_function("v2", &some_class::variable, some_class{});
lua.script_file("test/main.lua");
std::cout << std::endl;
return 0;
}

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;
}
}

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) {}

24
test/main.lua Normal file
View File

@@ -0,0 +1,24 @@
f1() -- some function!
f2() -- some other function!
-- need class instance if you don't bind it with the function
print(m1(sc)) -- 24.5
-- does not need class instance: was bound to lua with one
print(m2()) -- 24.5
-- need class instance if you
-- don't bind it with the function
print(v1(sc)) -- 30
-- does not need class instance:
-- it was bound with one
print(v2()) -- 30
-- can set, still
-- requires instance
v1(sc, 212)
-- can set, does not need
-- class instance: was bound with one
v2(254)
print(v1(sc)) -- 212
print(v2()) -- 254

View File

@@ -3,7 +3,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("libsdl3 3.2.16", "splib 2.3.2", "zlib", "glew", "fpm", "enet6", "sol2")
set_languages("c++20")
@@ -11,24 +11,20 @@ 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})
target("Tower-Defense2")
add_includedirs("include", {public = true})
set_kind("binary")
add_files("src/**.cpp")
add_packages("libsdl3", "imgui", "glew", "splib", "zlib", "fpm", "enet6", {public = true})
add_packages("libsdl3", "imgui", "glew", "splib", "zlib", "fpm", "enet6", "sol2", {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
-- Tests
for _, file in ipairs(os.files("test/**.cpp")) do