53 Commits

Author SHA1 Message Date
f879c5fe8f add basic README 2025-08-11 19:57:20 +02:00
b5ff44d793 add (very) basic main menu 2025-08-11 19:47:55 +02:00
24252896c7 begin MainMenuState 2025-08-11 18:53:10 +02:00
4c0078a5f2 refactor xmake.lua 2025-08-11 18:52:46 +02:00
4072e49b32 add some minor comments 2025-08-10 12:26:21 +02:00
e0080fa50c move ClientSimulation in Client 2025-08-10 12:19:50 +02:00
8bdcffcfa6 migrate main 2025-08-10 11:49:07 +02:00
6b987cf78d fix Signal 2025-08-10 11:46:43 +02:00
cba790f804 add StateMachine 2025-08-09 11:23:17 +02:00
ac3e949323 add client 2025-08-08 13:24:50 +02:00
b09c7f9efd rename signal 2025-08-08 12:18:26 +02:00
110e6a62d2 add fakesocket method 2025-08-08 11:37:30 +02:00
ba84864b6a fill client holes (lockstep) 2025-08-07 10:53:00 +02:00
c813c49707 begin client-server 2025-08-06 20:10:56 +02:00
89213e9a97 refactor IServerSocket 2025-08-06 17:16:50 +02:00
fb53ece340 private SlotGuard 2025-08-06 16:52:20 +02:00
5d196c4b61 add SlotGuard doc 2025-08-06 14:15:13 +02:00
0d9e5b647f use SlotGuard 2025-08-06 14:13:38 +02:00
079d653405 add SlotGuard 2025-08-06 14:07:23 +02:00
990c6f078d server state test 2025-08-06 13:55:22 +02:00
599dfa0cee begin server 2025-08-06 12:53:33 +02:00
9c5ad07747 serversimulation 2025-08-04 12:47:27 +02:00
df79a35eae add packets 2025-08-04 11:35:51 +02:00
d1d71ed086 Merge branch 'experimental' into dev 2025-08-04 10:22:42 +02:00
1ca88106ac update splib to 2.3.0 2025-08-04 10:15:52 +02:00
7ca374ea53 don't write packet id in map save 2025-08-04 10:00:14 +02:00
8641ddc525 fix map save 2025-08-04 09:57:38 +02:00
731c742346 begin serversimulation 2025-08-03 18:20:21 +02:00
c0a560a16e change default cam pos 2025-08-01 13:40:11 +02:00
50a6caf82e add multisampling 2025-08-01 13:39:32 +02:00
31bb0198fc add vsync 2025-08-01 13:38:00 +02:00
51dc910359 fastforward test in main 2025-08-01 13:28:53 +02:00
ced20ca991 less serialize code 2025-08-01 13:21:31 +02:00
fa663d0481 packed chunk data 2025-07-31 19:03:41 +02:00
1a455a3d6b refactor tile serialize 2025-07-31 18:02:14 +02:00
c8159bae6e fix predictpacket serialization 2025-07-31 15:07:34 +02:00
02d872c49b migrating save files 2025-07-31 13:48:26 +02:00
2b8447766a fix gcc build 2025-07-30 17:56:13 +02:00
2e556e0d45 feat: fast forward 2025-07-30 17:52:54 +02:00
56a43d7a60 update sp (illegal) 2025-07-29 15:17:56 +02:00
4c2ac7e3f0 temp tower rendering 2025-07-28 18:11:17 +02:00
da1586baed remove unused function 2025-07-28 14:32:01 +02:00
d0b30ba6f8 process tower creation 2025-07-28 14:28:37 +02:00
4f0a81d670 add PredictComma,d 2025-07-28 14:28:29 +02:00
4128b7fbb7 apply steps at the end 2025-07-28 14:28:14 +02:00
47b5a281fc refactor towers 2025-07-28 14:28:03 +02:00
05edb95150 nuke dispatch 2025-07-28 14:27:37 +02:00
e146f490cf CommandApply: add handle methods 2025-07-23 19:20:59 +02:00
9477099cfc fix warnings + cland-tidy 2025-07-23 19:18:25 +02:00
705671148b add comment 2025-07-22 14:04:56 +02:00
090ea962d3 and again 2025-07-18 18:56:49 +02:00
6d0e56eb46 too many things 2025-07-18 13:11:18 +02:00
cd03175b98 pupush 2024-10-25 17:12:13 +02:00
100 changed files with 2457 additions and 915 deletions

27
.clang-tidy Normal file
View File

@@ -0,0 +1,27 @@
Checks: >-
-*,
readability-identifier-naming,
readability-redundant-string-cstr,
readability-redundant-string-init,
readability-simplify-boolean-expr,
performance-unnecessary-value-param,
performance-unnecessary-copy-initialization,
performance-for-range-copy,
performance-implicit-conversion-in-loop,
CheckOptions:
- key: readability-identifier-naming.PrivateMemberPrefix
value: 'm_'
- key: readability-identifier-naming.ClassConstantCase
value: aNy_CasE
# an empty *Prefix needs a *Case to work
- key: readability-identifier-naming.ClassConstantPrefix
value: ''
#- key: readability-identifier-naming.PrivateMemberCase
# value: CamelCase
#- key: readability-identifier-naming.FunctionCase
# value: CamelCase
#- key: readability-identifier-naming.EnumCase
# value: camelBack
WarningsAsErrors: '*'
FormatStyle: 'file'

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@ build/
.vscode
imgui.ini
test/tdmap.tdmap3

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# (CubeCraft) Tower Defense
## Currently in contruction 🏗️👷⚙️

5
assets/stats.json Normal file
View File

@@ -0,0 +1,5 @@
{
"Zombie" : {
}
}

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

@@ -0,0 +1,24 @@
#pragma once
#include <client/IClientSocket.h>
#include <td/common/StateMachine.h>
namespace td {
namespace client {
class ClientState;
class Client : public StateMachine<Client, void, float> {
private:
std::shared_ptr<IClientSocket> m_Socket;
public:
Client(const std::shared_ptr<IClientSocket>& a_Socket) : m_Socket(a_Socket) {}
void SendPacket(const protocol::PacketBase& a_Packet);
friend class ClientState;
};
} // namespace client
} // namespace td

View File

@@ -0,0 +1,20 @@
#pragma once
#include <client/Client.h>
#include <td/misc/SlotGuard.h>
namespace td {
namespace client {
class ClientState : public Client::State, private utils::SlotGuard {
public:
ClientState(Client& a_Client);
virtual ~ClientState() {}
protected:
void SendPacket(const protocol::PacketBase& a_Packet);
virtual void HandlePacket(const protocol::PacketBase& a_Packet) {}
};
} // namespace server
} // namespace td

View File

@@ -0,0 +1,23 @@
#pragma once
#include <td/Types.h>
#include <td/misc/Signal.h>
#include <td/protocol/packet/Packets.h>
namespace td {
namespace client {
class IClientSocket {
public:
utils::Signal<> OnConnect;
utils::Signal<> OnDisconnect;
utils::Signal<const protocol::PacketBase&> OnReceive;
virtual void Send(const protocol::PacketBase& a_Packet) = 0;
IClientSocket() {}
virtual ~IClientSocket() {}
};
} // namespace client
} // namespace td

View File

@@ -0,0 +1,33 @@
#pragma once
#include <client/IClientSocket.h>
namespace td {
namespace server {
class FakeSocket;
} // namespace server
namespace client {
class FakeSocket : public IClientSocket {
private:
std::shared_ptr<server::FakeSocket> m_Server;
PeerID m_PeerId;
struct Private{ explicit Private() = default; };
public:
FakeSocket(Private) {}
~FakeSocket() {}
static std::shared_ptr<FakeSocket> Connect(const std::shared_ptr<server::FakeSocket>& a_Server);
void ReceiveFromFakePeer(PeerID a_Peer, const protocol::PacketBase& a_Packet);
virtual void Send(const protocol::PacketBase& a_Packet) override;
};
} // namespace client
} // namespace td

View File

@@ -0,0 +1,31 @@
#pragma once
#include <client/ClientState.h>
#include <td/game/World.h>
#include <td/simulation/ClientSimulation.h>
namespace td {
namespace client {
class GameState : public ClientState {
private:
std::shared_ptr<game::World> m_World;
sim::ClientSimulation m_Simulation;
float m_CurrentLerp;
public:
GameState(Client& a_Client, const std::shared_ptr<game::World>& a_World, std::uint64_t a_StepTime);
~GameState() {}
virtual void Update(float a_Delta) override;
float GetCurrentLerp() const {
return m_CurrentLerp;
}
protected:
virtual void HandlePacket(const protocol::PacketBase& a_Packet) override;
};
} // namespace client
} // namespace td

View File

@@ -0,0 +1,33 @@
#pragma once
#include <server/PlayerIds.h>
#include <td/misc/Signal.h>
#include <td/protocol/packet/Packets.h>
namespace td {
namespace server {
class IServerSocket {
public:
utils::Signal<PlayerID> OnConnect;
utils::Signal<PlayerID> OnDisconnect;
utils::Signal<PlayerID, const protocol::PacketBase&> OnReceive;
void Send(PlayerID a_PlayerId, const protocol::PacketBase& a_Packet);
void Broadcast(const protocol::PacketBase& a_Packet);
IServerSocket() {}
virtual ~IServerSocket() {}
private:
PlayerIds m_Ids;
protected:
void OnConnectPeer(PeerID a_PeerId);
void OnDisconnectPeer(PeerID a_PeerId);
void OnReceivePeer(PeerID a_PeerId, const protocol::PacketBase& a_Packet);
virtual void SendPeer(PeerID a_PeerId, const protocol::PacketBase& a_Packet) = 0;
};
} // namespace server
} // namespace td

View File

@@ -0,0 +1,49 @@
#pragma once
#include <map>
#include <td/Types.h>
namespace td {
namespace server {
class PlayerIds {
private:
std::map<PeerID, PlayerID> m_PeerToPlayer;
std::map<PlayerID, PeerID> m_PlayerToPeer;
PlayerID m_NextId;
public:
PlayerIds() : m_NextId(0) {}
~PlayerIds() {}
void AddConnection(PeerID a_PeerId) {
m_PeerToPlayer.emplace(a_PeerId, m_NextId);
m_PlayerToPeer.emplace(m_NextId, a_PeerId);
m_NextId++;
}
PlayerID GetPlayerId(PeerID a_PeerId) const {
return m_PeerToPlayer.at(a_PeerId);
}
PeerID GetPeerId(PlayerID a_PlayerId) const {
return m_PlayerToPeer.at(a_PlayerId);
}
void RemovePeer(PeerID a_PeerId) {
PlayerID playerId = GetPlayerId(a_PeerId);
m_PeerToPlayer.erase(a_PeerId);
m_PlayerToPeer.erase(playerId);
}
auto begin() {
return m_PeerToPlayer.begin();
}
auto end() {
return m_PeerToPlayer.end();
}
};
} // namespace server
} // namespace td

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

@@ -0,0 +1,22 @@
#pragma once
#include <server/IServerSocket.h>
#include <td/common/StateMachine.h>
namespace td {
namespace server {
class ServerState;
class Server : public StateMachine<Server, void, float> {
private:
std::shared_ptr<IServerSocket> m_Socket;
public:
Server(const std::shared_ptr<IServerSocket>& a_Socket) : m_Socket(a_Socket) {}
friend class ServerState;
};
} // namespace server
} // namespace td

View File

@@ -0,0 +1,23 @@
#pragma once
#include <server/Server.h>
#include <td/misc/SlotGuard.h>
namespace td {
namespace server {
class ServerState : public Server::State, private utils::SlotGuard {
public:
virtual void HandlePacket(PlayerID a_Id, const protocol::PacketBase& a_Packet) = 0;
virtual void Update(float a_Delta) = 0;
ServerState(Server& a_Server);
virtual ~ServerState();
protected:
void SendPacket(PlayerID a_Id, const protocol::PacketBase& a_Packet);
void BroadcastPacket(const protocol::PacketBase& a_Packet);
};
} // namespace server
} // namespace td

View File

@@ -0,0 +1,35 @@
#pragma once
#include <client/socket/FakeSocket.h>
#include <optional>
#include <server/IServerSocket.h>
namespace td {
namespace server {
class FakeSocket : public IServerSocket {
private:
std::vector<std::optional<std::weak_ptr<client::FakeSocket>>> m_Clients;
public:
FakeSocket() {}
~FakeSocket() {}
PeerID ConnectFakePeer(const std::shared_ptr<client::FakeSocket>& a_Socket);
void DisconnectFakePeer(PeerID a_Peer);
void ReceiveFromFakePeer(PeerID a_Peer, const protocol::PacketBase& a_Packet);
protected:
virtual void SendPeer(PeerID a_Peer, const protocol::PacketBase& a_Packet) override;
private:
/**
* \return -1 if all previous ids are not free
*/
int GetNextFreeId();
friend class client::FakeSocket;
};
} // namespace server
} // namespace td

View File

@@ -0,0 +1,17 @@
#pragma once
#include <server/ServerState.h>
namespace td {
namespace server {
class EndGameState : public ServerState {
private:
/* data */
public:
EndGameState(/* args */) {}
~EndGameState() {}
};
} // namespace server
} // namespace td

View File

@@ -0,0 +1,29 @@
#pragma once
#include <server/ServerState.h>
#include <td/game/World.h>
#include <td/simulation/ServerSimulation.h>
namespace td {
namespace server {
class GameStateHandler;
class GameState : public ServerState {
private:
std::shared_ptr<game::World> m_World;
sim::ServerSimulation m_Simulation;
float m_Time;
public:
GameState(Server& a_Server, const std::shared_ptr<game::World>& a_World);
~GameState() {}
virtual void HandlePacket(PlayerID a_Id, const protocol::PacketBase& a_Packet) override;
virtual void Update(float a_Delta) override;
friend class GameStateHandler;
};
} // namespace server
} // namespace td

View File

@@ -0,0 +1,26 @@
#pragma once
#include <td/protocol/packet/Packets.h>
namespace td {
namespace server {
class GameState;
class GameStateHandler : public protocol::PacketHandler {
private:
GameState& m_GameState;
PlayerID m_PlayerId;
using protocol::PacketHandler::Handle;
public:
GameStateHandler(GameState& a_GameState, PlayerID a_PlayerId);
virtual void Handle(const protocol::packets::SpawnTroopPacket& a_Packet) override;
virtual void Handle(const protocol::packets::PlaceTowerPacket& a_Packet) override;
virtual void Handle(const protocol::packets::LockStepRequestPacket& a_Packet) override;
};
} // namespace server
} // namespace td

View File

@@ -0,0 +1,21 @@
#pragma once
#include <server/ServerState.h>
namespace td {
namespace server {
// this class is temporary useless
class LobbyState : public ServerState {
private:
std::shared_ptr<game::World> m_World;
public:
LobbyState(Server& a_Server, const std::shared_ptr<game::World>& a_World);
~LobbyState() {}
virtual void HandlePacket(PlayerID a_Id, const protocol::PacketBase& a_Packet) override;
virtual void Update(float a_Delta) override;
};
} // namespace server
} // namespace td

10
include/td/Constants.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <cstdint>
namespace td {
constexpr int TPS = 20;
constexpr float STEP_PERIOD = 1.0f / static_cast<float>(TPS);
} // namespace td

View File

@@ -1,6 +1,6 @@
#pragma once
#include <cstdint>
#include <sp/common/DataBufferOperators.h>
namespace td {
@@ -18,7 +18,7 @@ struct Vec2 {
T g;
};
constexpr Vec2(T X = 0, T Y = 0) : x(X), y(Y) {}
constexpr Vec2(T X = T(0), T Y = T(0)) : x(X), y(Y) {}
};
template <typename T>
@@ -176,8 +176,42 @@ Mat4f Look(const Vec3f& eye, const Vec3f& center, const Vec3f& up);
Mat4f Inverse(const Mat4f& mat);
template<typename T>
T Lerp(T v0, T v1, T t) {
return (T(1) - t) * v0 + t * v1;
}
} // namespace maths
template <typename T>
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const Vec2<T>& a_Vec) {
return a_Buffer << a_Vec.x << a_Vec.y;
}
template <typename T>
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, Vec2<T>& a_Vec) {
return a_Buffer >> a_Vec.x >> a_Vec.y;
}
template <typename T>
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const Vec3<T>& a_Vec) {
return a_Buffer << a_Vec.x << a_Vec.y << a_Vec.z;
}
template <typename T>
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, Vec3<T>& a_Vec) {
return a_Buffer >> a_Vec.x >> a_Vec.y >> a_Vec.z;
}
template <typename T>
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const Vec4<T>& a_Vec) {
return a_Buffer << a_Vec.x << a_Vec.y << a_Vec.z << a_Vec.w;
}
template <typename T>
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, Vec4<T>& a_Vec) {
return a_Buffer >> a_Vec.x >> a_Vec.y >> a_Vec.z >> a_Vec.w;
}
} // namespace td

View File

@@ -2,11 +2,20 @@
#include <cstdint>
#include <fpm/fixed.hpp>
#include <td/Maths.h>
namespace sp {
class DataBuffer;
} // namespace sp
namespace td {
using FpFloat = fpm::fixed_16_16;
using StepTime = std::uint16_t;
constexpr int STEP_TIME = 50;
enum class TeamColor : std::int8_t {
None = -1,
Blue,
@@ -31,7 +40,7 @@ enum class EntityType : std::uint8_t {
Zombie = 0,
Spider,
Pigman,
Skeleton,
Skelon,
Creeper,
Silverfish,
Blaze,
@@ -56,15 +65,9 @@ using TowerID = std::uint16_t;
using PlayerID = std::uint8_t;
using EntityID = std::uint16_t;
struct TowerCoords {
std::int16_t x;
std::int16_t y;
};
using TowerCoords = Vec2<std::int16_t>;
struct EntityCoords {
FpFloat x;
FpFloat y;
};
using EntityCoords = Vec2<FpFloat>;
using PeerID = std::uint16_t;
@@ -75,4 +78,10 @@ enum class Direction : std::uint8_t {
NegativeY = 1 << 3,
};
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const EntityCoords& a_Coords);
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const FpFloat& a_Float);
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, EntityCoords& a_Coords);
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, FpFloat& a_Float);
} // namespace td

View File

@@ -48,6 +48,10 @@ class Array {
return m_Data[a_Index];
}
const T& operator[](std::size_t a_Index) const {
return m_Data[a_Index];
}
~Array() {
delete [] m_Data;
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include <cassert>
#include <memory>
namespace td {
template <typename TDerived, typename TReturn, typename... TArgs>
class StateMachine {
public:
class State {
public:
State(TDerived& a_StateMachine) : m_StateMachine(a_StateMachine) {}
virtual ~State() {}
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)...);
}
protected:
TDerived& m_StateMachine;
};
StateMachine() {}
StateMachine(StateMachine&&) = default;
virtual ~StateMachine() {}
virtual TReturn Update(TArgs... args) {
assert(m_State && "You must change state at least once before updating !");
return m_State->Update(args...);
}
template <typename T, typename... Args>
T* ChangeState(Args&&... args) {
m_State = std::make_unique<T>(static_cast<TDerived&>(*this), std::forward<Args>(args)...);
return static_cast<T*>(m_State.get());
}
private:
std::unique_ptr<State> m_State;
};
} // namespace td

View File

@@ -0,0 +1,69 @@
#pragma once
#include <cassert>
#include <vector>
#include <memory>
namespace td {
template <typename TDerived, typename TReturn, typename... TArgs>
class StateStack {
public:
class State {
public:
State(TDerived& a_StateStack) : m_StateStack(a_StateStack) {}
virtual ~State() {}
virtual TReturn Update(TArgs... args) = 0;
protected:
/**
* \brief Called when enabled (can be used to connect signals)
*/
virtual void OnEnable() {}
/**
* \brief Called when disabled (can be used to disconnect signals)
*/
virtual void OnDisable() {}
protected:
TDerived& m_StateStack;
friend class StateStack;
};
StateStack() {}
StateStack(StateStack&&) = default;
virtual ~StateStack() {}
virtual TReturn Update(TArgs... args) {
assert(!m_States.empty() && "You must push at least one state before updating !");
return m_States.back()->Update(args...);
}
void PopState() {
assert(!m_States.empty() && "You must push at least one state before poping !");
m_States.back()->OnDisable();
m_States.pop_back();
if (m_States.empty())
return;
m_States.back()->OnEnable();
}
template <typename T, typename... Args>
T* PushState(Args&&... args) {
if (!m_States.empty())
m_States.back()->OnDisable();
auto newState = std::make_unique<T>(static_cast<TDerived&>(*this), std::forward<Args>(args)...);
newState->OnEnable();
m_States.push_back(std::move(newState));
return static_cast<T*>(newState.get());
}
private:
std::vector<std::unique_ptr<State>> m_States;
};
} // namespace td

View File

@@ -2,17 +2,33 @@
#include <string>
#include <SDL3/SDL_keycode.h>
#include <SDL3/SDL_video.h>
#include <td/common/StateMachine.h>
#include <td/misc/Signal.h>
namespace td {
class Display {
class Display : public StateMachine<Display, void, float> {
private:
SDL_Window* m_Window;
SDL_GLContext m_GLContext;
int m_LastWidth, m_LastHeight;
float m_AspectRatio;
bool m_ShouldClose;
public:
utils::Signal<float> OnAspectRatioChange;
utils::Signal<SDL_Keycode> OnKeyDown;
Display(int a_Width, int a_Height, const std::string& a_Title);
~Display();
void PollEvents();
void Update();
void Update(float a_Delta) override;
void Close();
bool IsCloseRequested() {
return m_ShouldClose;
@@ -29,15 +45,6 @@ class Display {
int GetWindowHeight() {
return m_LastHeight;
}
private:
SDL_Window* m_Window;
SDL_GLContext m_GLContext;
int m_LastWidth, m_LastHeight;
float m_AspectRatio;
bool m_ShouldClose;
};
} // namespace td

View File

@@ -0,0 +1,16 @@
#pragma once
#include <td/display/Display.h>
#include <td/misc/SlotGuard.h>
namespace td {
class DisplayState : public Display::State, private utils::SlotGuard {
public:
DisplayState(Display& a_Display);
virtual ~DisplayState() {}
protected:
virtual void OnAspectRatioChange(float a_Ratio) {}
virtual void OnKeyDown(SDL_Keycode a_Key) {}
};
} // namespace td

View File

@@ -0,0 +1,15 @@
#pragma once
#include <td/display/state/MainMenuState.h>
namespace td {
class CreatePartyMenu : public MainMenuState::Menu {
public:
CreatePartyMenu(MainMenuState& a_MainMenu);
~CreatePartyMenu();
virtual void Update() override;
};
} // namespace td

View File

@@ -0,0 +1,15 @@
#pragma once
#include <td/display/state/MainMenuState.h>
namespace td {
class JoinPartyMenu : public MainMenuState::Menu {
public:
JoinPartyMenu(MainMenuState& a_MainMenu);
~JoinPartyMenu();
virtual void Update() override;
};
} // namespace td

View File

@@ -0,0 +1,15 @@
#pragma once
#include <td/display/state/MainMenuState.h>
namespace td {
class MainMenu : public MainMenuState::Menu {
public:
MainMenu(MainMenuState& a_MainMenu);
~MainMenu();
virtual void Update() override;
};
} // namespace td

View File

@@ -0,0 +1,15 @@
#pragma once
#include <td/display/state/MainMenuState.h>
namespace td {
class SettingsMenu : public MainMenuState::Menu {
public:
SettingsMenu(MainMenuState& a_MainMenu);
~SettingsMenu();
virtual void Update() override;
};
} // namespace td

View File

@@ -0,0 +1,31 @@
#pragma once
#include <client/Client.h>
#include <server/Server.h>
#include <td/display/DisplayState.h>
#include <td/render/Renderer.h>
#include <td/simulation/ClientSimulation.h>
#include <client/state/GameState.h>
namespace td {
class DebugWorldState : public DisplayState {
private:
render::RenderPipeline m_Renderer;
render::Camera m_Camera;
std::unique_ptr<client::Client> m_Client;
std::unique_ptr<server::Server> m_Server;
client::GameState* m_ClientState;
public:
DebugWorldState(Display& a_Display);
~DebugWorldState();
virtual void Update(float a_Delta) override;
protected:
virtual void OnAspectRatioChange(float a_Ratio) override;
virtual void OnKeyDown(SDL_Keycode a_Key) override;
};
} // namespace td

View File

@@ -0,0 +1,27 @@
#pragma once
#include <td/common/StateStack.h>
#include <td/display/DisplayState.h>
namespace td {
class MainMenuState;
using MainMenuStateStack = StateStack<MainMenuState, void>;
class MainMenuState : public DisplayState, public MainMenuStateStack {
public:
using Menu = MainMenuStateStack::State;
MainMenuState(Display& a_Display);
~MainMenuState();
virtual void Update(float a_Delta) override;
void RenderBackButton();
protected:
virtual void OnKeyDown(SDL_Keycode a_Key) override;
};
} // namespace td

View File

@@ -1,37 +0,0 @@
#pragma once
#include <optional>
#include <td/protocol/command/Commands.h>
#include <td/protocol/packet/Packets.h>
namespace td {
namespace game {
class GameHistory {
private:
using HistorySizeType = std::uint16_t;
std::vector<std::optional<protocol::LockStep>> m_History;
HistorySizeType m_Cursor;
public:
GameHistory();
void SetLockStep(HistorySizeType a_Index, protocol::LockStep&& a_LockStep);
const protocol::LockStep& GetLockStep(HistorySizeType a_Index) const;
bool HasLockStep(HistorySizeType a_Index) const;
const protocol::LockStep& GetNextStep();
bool HasNextStep() const;
void FromPacket(td::protocol::pdata::LockSteps&& a_Steps);
td::protocol::packets::LockStepsPacket ToPacket(HistorySizeType a_StartIndex);
};
} // namespace game
} // namespace td

View File

@@ -9,10 +9,14 @@
#include <memory>
#include <vector>
namespace td {
namespace game {
#include <sp/protocol/ConcreteMessage.h>
#include <sp/common/GenericHandler.h>
#include <sp/protocol/MessageFactory.h>
struct WalkableTile;
namespace td {
using Vec2fp = Vec2<FpFloat>;
namespace game {
enum class EffectType : std::uint8_t {
Slowness = 0,
@@ -62,151 +66,33 @@ const MobStats* GetMobStats(MobType type, std::uint8_t level);
const TowerImmunities& GetMobTowerImmunities(MobType type, std::uint8_t level);
const EffectImmunities& GetMobEffectImmunities(MobType type, std::uint8_t level);
class Mob : public utils::shape::Rectangle {
protected:
float m_Health;
class MobHandler;
private:
struct MobData {};
class Mob : public sp::MessageBase<MobType, MobHandler> {
public:
MobID m_ID;
PlayerID m_Sender;
MobLevel m_Level;
PlayerID m_Sender;
float m_Health;
Vec2fp m_Position;
Direction m_Direction;
std::vector<EffectDuration> m_Effects;
const Tower* m_LastDamage; // the last tower that damaged the mob
float m_HitCooldown;
// utils::Timer m_EffectFireTimer;
// utils::Timer m_EffectPoisonTimer;
// utils::Timer m_EffectHealTimer;
TeamCastle* m_CastleTarget;
// utils::CooldownTimer m_AttackTimer;
public:
Mob(MobID id, MobLevel level, PlayerID sender) : m_Sender(sender), m_Level(level), m_HitCooldown(0), m_CastleTarget(nullptr) {}
MobPtr m_Next;
virtual ~Mob() {}
Mob() {}
virtual MobType GetType() const = 0;
virtual void Tick(std::uint64_t delta, World* world) {}
virtual bool OnDeath(World* world) {
return true;
}
MobID GetMobID() const {
return m_ID;
}
const TowerImmunities& GetTowerImmunities() const {
return GetMobTowerImmunities(GetType(), m_Level);
}
const EffectImmunities& GetEffectImmunities() const {
return GetMobEffectImmunities(GetType(), m_Level);
}
PlayerID GetSender() const {
return m_Sender;
}
MobLevel GetLevel() const {
return m_Level;
}
const MobStats* GetStats() const {
return GetMobStats(GetType(), m_Level);
}
void SetHealth(float newHealth) {
m_Health = newHealth;
}
float GetHealth() const {
return m_Health;
}
bool IsDead() const {
return m_Health <= 0;
}
bool IsAlive() const {
return m_Health > 0;
}
const Tower* GetLastDamageTower() {
return m_LastDamage;
}
bool HasReachedEnemyCastle() {
return m_CastleTarget != nullptr;
}
void Damage(float dmg, const Tower* damager) {
m_Health = std::max(0.0f, m_Health - dmg);
m_LastDamage = damager;
m_HitCooldown = 0.1;
}
void Heal(float heal) {
m_Health = std::min(static_cast<float>(GetStats()->m_MaxLife), m_Health + heal);
}
void SetMobReachedCastle(TeamCastle* castle) {
m_CastleTarget = castle;
} // used when mob is in front of the castle
bool IsImmuneTo(TowerType type);
bool IsImmuneTo(EffectType type);
void AddEffect(EffectType type, float durationSec, Tower* tower);
bool HasEffect(EffectType type);
bool HasTakenDamage() {
return m_HitCooldown > 0;
}
// returns a float between 0 and 1 excluded
float GetTileX() {
return GetCenterX() - static_cast<float>(static_cast<std::int32_t>(GetCenterX()));
}
// returns a float between 0 and 1 excluded
float GetTileY() {
return GetCenterY() - static_cast<float>(static_cast<std::int32_t>(GetCenterY()));
}
Direction GetDirection() const {
return m_Direction;
}
void SetDirection(Direction dir) {
m_Direction = dir;
}
protected:
void InitMob() {
m_Health = static_cast<float>(GetStats()->m_MaxLife);
SetSize(GetStats()->m_Size.x, GetStats()->m_Size.y);
}
private:
void UpdateEffects(std::uint64_t delta, World* world);
void AttackCastle(std::uint64_t delta, World* world);
void Move(std::uint64_t delta, World* world);
void Walk(std::uint64_t delta, World* world);
void MoveBack(const TeamCastle& castle, World* world);
void ChangeDirection(const WalkableTile& tile, World* world);
bool IsTouchingCastle(const TeamCastle& castle) const;
EffectDuration& GetEffect(EffectType type);
Mob& operator=(const Mob& a_Other) = default;
};
typedef std::shared_ptr<Mob> MobPtr;
template <MobType MT>
class ConcreteMob : public Mob {
public:
ConcreteMob(MobID id, std::uint8_t level, PlayerID sender) : Mob(id, level, sender) {
InitMob();
}
virtual ~ConcreteMob() {}
virtual void Tick(std::uint64_t delta, World* world) {}
virtual constexpr MobType GetType() const {
return MT;
}
};
template <MobType ID>
using ConcreteMob = sp::ConcreteMessage<MobData, Mob, ID>;
using Zombie = ConcreteMob<MobType::Zombie>;
using Spider = ConcreteMob<MobType::Spider>;
@@ -219,16 +105,17 @@ using Witch = ConcreteMob<MobType::Witch>;
using Slime = ConcreteMob<MobType::Slime>;
using Giant = ConcreteMob<MobType::Giant>;
namespace MobFactory {
using AllMobs = std::tuple<Zombie, Spider, Skeleton, PigMan, Creeper, Silverfish, Blaze, Witch, Slime, Giant>;
MobPtr CreateMob(MobID id, MobType type, std::uint8_t level, PlayerID sender);
std::string GetMobName(MobType type);
class MobHandler : public sp::GenericHandler<AllMobs> {};
} // namespace MobFactory
using MobFactory = sp::MessageFactory<Mob, AllMobs>;
class MobListener {
public:
virtual void OnMobSpawn(Mob* mob) {}
virtual void OnMobSpawn(Mob* mob) {
MobHandler h;
}
virtual void OnMobDie(Mob* mob) {}
virtual void OnMobDamage(Mob* target, float damage, Tower* damager) {}
@@ -237,6 +124,8 @@ class MobListener {
virtual void OnMobCastleDamage(Mob* damager, TeamCastle* enemyCastle, float damage) {}
};
using MobList = std::vector<MobPtr>;
// typedef utils::ObjectNotifier<MobListener> MobNotifier;
} // namespace game

View File

@@ -17,7 +17,7 @@ class Spawn : public utils::shape::Rectangle {
private:
Direction m_Direction;
public:
Spawn() {
Spawn() : m_Direction(Direction::PositiveX) {
SetWidth(5);
SetHeight(5);
}
@@ -31,23 +31,17 @@ class Team;
class TeamCastle : public utils::shape::Rectangle {
private:
const Team* m_Team;
float m_Life;
public:
static constexpr int CastleMaxLife = 1000;
TeamCastle(const Team* team) : m_Team(team), m_Life(CastleMaxLife) {
TeamCastle() : m_Life(CastleMaxLife) {
SetWidth(5);
SetHeight(5);
}
TeamCastle() : TeamCastle(nullptr) {}
float GetLife() const { return m_Life; }
const Team* GetTeam() const { return m_Team; }
void SetTeam(const Team* team) { m_Team = team; }
void SetLife(float life) { m_Life = life; }
void Damage(float damage) { m_Life = std::max(0.0f, m_Life - damage); }
@@ -80,7 +74,29 @@ public:
std::uint8_t GetPlayerCount() const;
};
typedef std::array<Team, 2> TeamList;
struct TeamList {
std::array<Team, 2> m_Teams;
TeamList() : m_Teams{Team{TeamColor::Red}, Team{TeamColor::Blue}}{
}
Team& operator[](std::size_t a_Index) {
return m_Teams[a_Index];
}
Team& operator[](TeamColor a_Index) {
return m_Teams[static_cast<std::size_t>(a_Index)];
}
const Team& operator[](std::size_t a_Index) const {
return m_Teams[a_Index];
}
const Team& operator[](TeamColor a_Index) const {
return m_Teams[static_cast<std::size_t>(a_Index)];
}
};
} // namespace game
} // namespace td

View File

@@ -1,12 +1,16 @@
#pragma once
#include <string>
#include <memory>
#include <string>
#include <td/misc/Shapes.h>
#include <td/Types.h>
#include <sp/common/GenericHandler.h>
#include <sp/protocol/ConcreteMessage.h>
#include <sp/protocol/MessageFactory.h>
namespace td {
namespace game {
@@ -15,23 +19,6 @@ class Mob;
typedef std::shared_ptr<Mob> MobPtr;
enum class TowerType : std::uint8_t {
Archer = 0,
Ice,
Sorcerer,
Zeus,
Mage,
Artillery,
Quake,
Poison,
Leach,
Turret,
Necromancer,
TowerCount
};
enum class TowerSize : std::uint8_t {
Little = 3, // 3x3
Big = 5, // 5x5
@@ -48,14 +35,19 @@ private:
float m_Rate;
float m_Damage;
std::uint8_t m_Range;
public:
TowerStats(float rate, float damage, std::uint8_t range) : m_Rate(rate), m_Damage(damage),
m_Range(range) {
}
float GetDamageRate() const { return m_Rate; }
float GetDamage() const { return m_Damage; }
std::uint8_t GetRange() const { return m_Range; }
public:
TowerStats(float rate, float damage, std::uint8_t range) : m_Rate(rate), m_Damage(damage), m_Range(range) {}
float GetDamageRate() const {
return m_Rate;
}
float GetDamage() const {
return m_Damage;
}
std::uint8_t GetRange() const {
return m_Range;
}
};
class TowerLevel {
@@ -64,15 +56,24 @@ private:
std::uint8_t m_Level : 3;
// 0 : base path 1 : top path (if there is bottom path) 2 : bottom path (if there is top path)
TowerPath m_Path : 2;
public:
TowerLevel() : m_Level(1), m_Path(TowerPath::Base) {}
TowerLevel(std::uint8_t level, TowerPath path) : m_Level(level), m_Path(path) {}
std::uint8_t GetLevel() const { return m_Level; }
TowerPath GetPath() const { return m_Path; }
std::uint8_t GetLevel() const {
return m_Level;
}
TowerPath GetPath() const {
return m_Path;
}
void SetLevel(std::uint8_t level) { m_Level = level; }
void SetPath(TowerPath path) { m_Path = path; }
void SetLevel(std::uint8_t level) {
m_Level = level;
}
void SetPath(TowerPath path) {
m_Path = path;
}
// operator to sort maps
friend bool operator<(const TowerLevel& level, const TowerLevel& other) {
@@ -81,186 +82,66 @@ public:
}
};
const TowerStats* GetTowerStats(TowerType type, TowerLevel level);
using TowerID = std::uint16_t;
typedef std::uint16_t TowerID;
class TowerHandler;
class Tower : public utils::shape::Circle {
private:
class Tower : public utils::shape::Circle, public sp::MessageBase<TowerType, TowerHandler> {
public:
TowerID m_ID;
TowerType m_Type;
TowerLevel m_Level{};
PlayerID m_Builder;
protected:
// utils::CooldownTimer m_Timer;
public:
Tower(TowerID id, TowerType type, std::int32_t x, std::int32_t y, PlayerID builder) : utils::shape::Circle(x + 0.5f, y + 0.5f, 0), m_ID(id), m_Type(type), m_Builder(builder)
// m_Timer(GetStats()->GetDamageRate() * 1000)
{ // converting seconds to millis
SetRadius(GetStats()->GetRange());
}
Tower() : m_ID(0), m_Level({}), m_Builder(0) {}
virtual TowerType GetType() const = 0;
virtual TowerSize GetSize() const = 0;
virtual void Tick(std::uint64_t delta, World* world) = 0;
};
void Upgrade(std::uint8_t level, TowerPath path) {
m_Level.SetLevel(level);
m_Level.SetPath(path);
// m_Timer.SetCooldown(GetStats()->GetDamageRate() * 1000); // converting seconds to millis
// m_Timer.Reset();
SetRadius(GetStats()->GetRange());
struct TowerData {};
template <TowerType Type, TowerSize Size>
class ConcreteTower : public sp::ConcreteMessage<TowerData, Tower, Type, false> {
public:
using HandlerType = typename sp::ConcreteMessage<TowerData, Tower, Type, false>::HandlerType;
virtual TowerSize GetSize() const override {
return Size;
}
std::uint16_t GetID() const { return m_ID; }
const TowerLevel& GetLevel() const { return m_Level; }
const TowerStats* GetStats() const { return GetTowerStats(m_Type, m_Level); }
PlayerID GetBuilder() const { return m_Builder; }
bool IsMobInRange(MobPtr mob);
};
typedef std::shared_ptr<Tower> TowerPtr;
namespace TowerFactory {
TowerPtr CreateTower(TowerType type, TowerID id, std::int32_t x, std::int32_t y, PlayerID builder);
std::string GetTowerName(TowerType type);
} // namespace TowerFactory
class TowerInfo {
private:
std::string m_Name, m_Description;
bool m_IsBigTower;
public:
TowerInfo(std::string&& name, std::string&& description, bool big) : m_Name(std::move(name)),
m_Description(std::move(description)), m_IsBigTower(big) {
virtual TowerType GetType() const override {
return Type;
}
const std::string& GetName() const { return m_Name; }
const std::string& GetDescription() const { return m_Description; }
virtual void Tick(std::uint64_t delta, World* world) override {}
bool IsBigTower() const { return m_IsBigTower; }
virtual void Dispatch(HandlerType& handler) const override {
handler.Handle(*this);
}
};
const TowerInfo& GetTowerInfo(TowerType type);
using TowerPtr = std::shared_ptr<Tower>;
// ---------- Little Towers ----------
using ArcherTower = ConcreteTower<TowerType::Archer, TowerSize::Little>;
using ArtilleryTower = ConcreteTower<TowerType::Artillery, TowerSize::Little>;
using IceTower = ConcreteTower<TowerType::Ice, TowerSize::Little>;
using LeachTower = ConcreteTower<TowerType::Leach, TowerSize::Big>;
using MageTower = ConcreteTower<TowerType::Mage, TowerSize::Little>;
using NecromancerTower = ConcreteTower<TowerType::Necromancer, TowerSize::Big>;
using PoisonTower = ConcreteTower<TowerType::Poison, TowerSize::Little>;
using QuakeTower = ConcreteTower<TowerType::Quake, TowerSize::Little>;
using SorcererTower = ConcreteTower<TowerType::Sorcerer, TowerSize::Little>;
using TurretTower = ConcreteTower<TowerType::Turret, TowerSize::Big>;
using ZeusTower = ConcreteTower<TowerType::Zeus, TowerSize::Little>;
class LittleTower : public Tower {
public:
LittleTower(TowerID id, TowerType type, std::uint16_t x, std::uint16_t y, PlayerID builder) : Tower(id, type, x, y, builder) {}
using AllTowers = std::tuple<ArcherTower, ArtilleryTower, IceTower, LeachTower, MageTower, NecromancerTower, PoisonTower, QuakeTower,
SorcererTower, TurretTower, ZeusTower>;
virtual TowerSize GetSize() const { return TowerSize::Little; }
using TowerFactory = sp::MessageFactory<Tower, AllTowers>;
virtual TowerType GetType() const = 0;
virtual void Tick(std::uint64_t delta, World* world) = 0;
};
class ArcherTower : public LittleTower {
public:
ArcherTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}
constexpr static float ExplosionRadius = 1.5f;
constexpr static float FireDurationSec = 10.0f;
virtual TowerType GetType() const { return TowerType::Archer; }
virtual void Tick(std::uint64_t delta, World* world);
};
class IceTower : public LittleTower {
public:
IceTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Ice; }
virtual void Tick(std::uint64_t delta, World* world);
};
class MageTower : public LittleTower {
public:
MageTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Mage; }
virtual void Tick(std::uint64_t delta, World* world);
};
class PoisonTower : public LittleTower {
public:
PoisonTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Poison; }
virtual void Tick(std::uint64_t delta, World* world);
};
class QuakeTower : public LittleTower {
public:
QuakeTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Quake; }
virtual void Tick(std::uint64_t delta, World* world);
};
class ArtilleryTower : public LittleTower {
public:
ArtilleryTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Artillery; }
virtual void Tick(std::uint64_t delta, World* world);
};
class SorcererTower : public LittleTower {
public:
SorcererTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Sorcerer; }
virtual void Tick(std::uint64_t delta, World* world);
};
class ZeusTower : public LittleTower {
public:
ZeusTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Zeus; }
virtual void Tick(std::uint64_t delta, World* world);
};
// ---------- Big Towers ----------
class BigTower : public Tower {
public:
BigTower(TowerID id, TowerType type, std::uint16_t x, std::uint16_t y, PlayerID builder) : Tower(id, type, x, y, builder) {}
virtual TowerSize GetSize() const { return TowerSize::Big; }
virtual TowerType GetType() const = 0;
virtual void Tick(std::uint64_t delta, World* world) = 0;
};
class TurretTower : public BigTower {
public:
TurretTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : BigTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Turret; }
virtual void Tick(std::uint64_t delta, World* world);
};
class NecromancerTower : public BigTower {
public:
NecromancerTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : BigTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Necromancer; }
virtual void Tick(std::uint64_t delta, World* world);
};
class LeachTower : public BigTower {
public:
LeachTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : BigTower(id, GetType(), x, y, builder) {}
virtual TowerType GetType() const { return TowerType::Leach; }
virtual void Tick(std::uint64_t delta, World* world);
};
class TowerHandler : public sp::GenericHandler<AllTowers> {};
} // namespace game
} // namespace td

View File

@@ -2,31 +2,33 @@
#include <td/game/WorldTypes.h>
#include <td/protocol/packet/Packets.h>
#include <td/simulation/WorldTicker.h>
namespace td {
namespace game {
class World {
protected:
// header
TowerTileColorPalette m_TowerPlacePalette;
Color m_WalkablePalette;
std::vector<Color> m_DecorationPalette;
Color m_Background;
std::unordered_map<ChunkCoord, ChunkPtr> m_Chunks;
SpawnColorPalette m_SpawnColorPalette;
TilePalette m_TilePalette;
MobList m_Mobs;
//data
ChunkList m_Chunks;
TowerList m_Towers;
std::shared_ptr<sim::WorldSnapshot> m_CurrentState;
std::shared_ptr<sim::WorldSnapshot> m_NextState;
TeamList m_Teams;
private:
sim::WorldTicker m_Ticker;
public:
World();
World(World&&) = default;
bool LoadMap(const protocol::pdata::WorldHeader& worldHeader);
bool LoadMap(const protocol::pdata::WorldData& worldData);
@@ -34,8 +36,6 @@ class World {
bool LoadMapFromFile(const std::string& fileName);
bool SaveMap(const std::string& fileName) const;
void Tick(std::uint64_t delta);
void SpawnMobAt(MobID id, MobType type, std::uint8_t level, PlayerID sender, float x, float y, Direction dir);
TowerPtr PlaceTowerAt(TowerID id, TowerType type, std::int32_t x, std::int32_t y, PlayerID builder);
@@ -62,7 +62,7 @@ class World {
TilePtr GetTilePtr(TileIndex index) const {
if (index == 0)
return nullptr;
return TilePtr(nullptr);
return m_TilePalette.at(index - 1);
}
@@ -71,7 +71,7 @@ class World {
TowerPtr GetTower(const Vec2f& position) const; // returns null if no tower is here
const std::unordered_map<ChunkCoord, ChunkPtr>& GetChunks() const {
const ChunkList& GetChunks() const {
return m_Chunks;
}
@@ -83,55 +83,57 @@ class World {
}
const MobList& GetMobList() const {
return m_Mobs;
return m_CurrentState->m_Mobs;
}
MobList& GetMobList() {
return m_Mobs;
return m_CurrentState->m_Mobs;
}
const Color* GetTileColor(TilePtr tile) const;
const Color* GetTileColor(const TilePtr& tile) const;
Team& GetRedTeam() {
return m_Teams[static_cast<std::uint8_t>(TeamColor::Red)];
return m_CurrentState->m_Teams[TeamColor::Red];
}
const Team& GetRedTeam() const {
return m_Teams[static_cast<std::uint8_t>(TeamColor::Red)];
return m_CurrentState->m_Teams[TeamColor::Red];
}
Team& GetBlueTeam() {
return m_Teams[static_cast<std::uint8_t>(TeamColor::Blue)];
return m_CurrentState->m_Teams[TeamColor::Blue];
}
const Team& GetBlueTeam() const {
return m_Teams[static_cast<std::uint8_t>(TeamColor::Red)];
return m_CurrentState->m_Teams[TeamColor::Red];
}
Team& GetTeam(TeamColor team) {
return m_Teams[static_cast<std::uint8_t>(team)];
return m_CurrentState->m_Teams[team];
}
const Team& GetTeam(TeamColor team) const {
return m_Teams[static_cast<std::uint8_t>(team)];
return m_CurrentState->m_Teams[team];
}
const TeamList& GetTeams() const {
return m_Teams;
return m_CurrentState->m_Teams;
}
const TowerList& GetTowers() const {
return m_Towers;
return m_CurrentState->m_Towers;
}
TowerPtr GetTowerById(TowerID tower);
const Player* GetPlayerById(PlayerID id) const;
const std::shared_ptr<sim::WorldSnapshot>& Tick(const protocol::LockStep& a_LockStep, FpFloat a_Delta);
void ResetSnapshots(std::shared_ptr<sim::WorldSnapshot>& a_Current, std::shared_ptr<sim::WorldSnapshot>& a_Next);
private:
void TickMobs(std::uint64_t delta);
void CleanDeadMobs();
};
using WorldPtr = std::shared_ptr<World>;
} // namespace game
} // namespace td

View File

@@ -1,20 +1,15 @@
#pragma once
#include <td/Maths.h>
#include <td/game/Mobs.h>
#include <td/game/Team.h>
#include <td/game/Towers.h>
#include <sp/io/SerializableMessage.h>
namespace td {
namespace game {
struct ChunkCoord {
std::int16_t x, y;
friend bool operator==(const td::game::ChunkCoord& first, const td::game::ChunkCoord& other) {
return first.x == other.x && first.y == other.y;
}
};
using ChunkCoord = Vec2<std::int16_t>;
class Game;
@@ -37,36 +32,44 @@ static constexpr Color RED{255, 0, 0};
static constexpr Color GREEN{0, 255, 0};
static constexpr Color BLUE{0, 0, 255};
struct Tile {
virtual TileType GetType() const = 0;
class TileHandler;
using Tile = sp::MessageBase<TileType, TileHandler>;
template <TileType ID, typename TileData>
using ConcreteTile = sp::ConcreteMessage<TileData, Tile, ID>;
namespace data {
struct EmptyData {};
struct TowerTileData {
std::uint8_t m_ColorPaletteRef;
TeamColor m_TeamOwner;
};
struct TowerTile : Tile {
std::uint8_t color_palette_ref;
TeamColor team_owner;
virtual TileType GetType() const {
return TileType::Tower;
}
struct WalkableTileData {
Direction m_Direction;
};
struct WalkableTile : Tile {
Direction direction;
virtual TileType GetType() const {
return TileType::Walk;
}
struct DecorationTileData {
std::uint16_t m_ColorPaletteRef;
};
} // namespace data
struct DecorationTile : Tile {
std::uint16_t color_palette_ref;
using EmptyTile = ConcreteTile<TileType::None, data::EmptyData>;
using TowerTile = ConcreteTile<TileType::Tower, data::TowerTileData>;
using WalkableTile = ConcreteTile<TileType::Walk, data::WalkableTileData>;
using DecorationTile = ConcreteTile<TileType::Decoration, data::DecorationTileData>;
virtual TileType GetType() const {
return TileType::Decoration;
}
};
using AllTiles = std::tuple<EmptyTile, TowerTile, WalkableTile, DecorationTile>;
typedef std::shared_ptr<Tile> TilePtr;
using TileFactory = sp::MessageFactory<Tile, AllTiles>;
class TileHandler : public sp::GenericHandler<AllTiles> {};
using TilePtr = sp::SerializableMessage<TileFactory>;
// typedef std::shared_ptr<Tile> TilePtr;
typedef std::vector<std::uint16_t> ChunkPalette;
typedef std::shared_ptr<WalkableTile> WalkableTilePtr;
@@ -76,18 +79,14 @@ typedef std::uint32_t TileIndex;
// 32 x 32 area
struct Chunk {
enum { ChunkWidth = 32, ChunkHeight = 32, ChunkSize = ChunkWidth * ChunkHeight };
typedef std::array<std::uint16_t, ChunkSize> ChunkData;
using ChunkData = std::array<std::uint16_t, ChunkSize>;
using ChunkPackedData = std::vector<uint64_t>;
// stores index of tile palette
ChunkData tiles{0};
ChunkPalette palette;
ChunkPalette m_Palette;
ChunkPackedData m_Data;
TileIndex GetTileIndex(std::uint16_t tileNumber) const {
TileIndex chunkPaletteIndex = tiles.at(tileNumber);
if (chunkPaletteIndex == 0) // index 0 means empty tile index 1 = first tile
return 0;
return palette.at(chunkPaletteIndex);
}
TileIndex GetTileIndex(std::uint16_t tileNumber) const;
};
typedef std::shared_ptr<Chunk> ChunkPtr;
@@ -96,12 +95,11 @@ typedef std::array<Color, 2> TowerTileColorPalette;
typedef std::vector<TilePtr> TilePalette;
typedef std::vector<MobPtr> MobList;
typedef std::array<Color, 2> SpawnColorPalette;
typedef std::vector<TowerPtr> TowerList;
using ChunkList = std::unordered_map<ChunkCoord, ChunkPtr>;
} // namespace game

View File

@@ -30,7 +30,7 @@ private:
Point m_Center;
float m_Width, m_Height;
public:
Rectangle() {}
Rectangle() : m_Center(), m_Width(0), m_Height(0) {}
const Point& GetCenter() const { return m_Center; }
float GetCenterX() const { return m_Center.GetX(); }

View File

@@ -8,16 +8,27 @@
namespace td {
namespace utils {
/**
* \brief Signal class
*/
template <typename... Args>
class Signal : private NonCopyable {
private:
using CallBack = std::function<void(Args...)>;
class SignalRaw : private NonCopyable {
public:
using FnType = void(Args...);
using CallBack = std::function<FnType>;
private:
std::vector<CallBack> m_Callbacks;
public:
void Connect(CallBack&& a_Callback) {
m_Callbacks.push_back(std::move(a_Callback));
void Connect(const CallBack& a_Callback) {
m_Callbacks.push_back(a_Callback);
}
void Disconnect(const CallBack& a_Callback) {
auto it = std::find_if(m_Callbacks.begin(), m_Callbacks.end(),
[&a_Callback](CallBack& callback) { return a_Callback.template target<FnType>() == callback.template target<FnType>(); });
m_Callbacks.erase(it);
}
void operator()(Args... args) const {
@@ -27,5 +38,63 @@ class Signal : private NonCopyable {
}
};
/**
* \brief Memory managed Signal class
*/
template <typename... Args>
class Signal {
public:
using SignalBase = SignalRaw<Args...>;
using CallBack = typename SignalBase::CallBack;
using SignalPtr = std::shared_ptr<SignalBase>;
class ConnectionGuard;
private:
SignalPtr m_Signal;
public:
Signal() : m_Signal(std::make_shared<SignalBase>()) {}
Signal(const Signal&) = default;
void Connect(const CallBack& a_Callback) {
m_Signal->Connect(a_Callback);
}
[[nodiscard]] std::unique_ptr<ConnectionGuard> ConnectSafe(const CallBack& a_Callback) {
Connect(a_Callback);
return std::make_unique<ConnectionGuard>(*this, a_Callback);
}
void Disconnect(const CallBack& a_Callback) {
m_Signal->Disconnect(a_Callback);
}
void operator()(Args... args) const {
m_Signal->operator()(args...);
}
};
class Connection {
public:
virtual ~Connection() {}
};
template <typename... Args>
class Signal<Args...>::ConnectionGuard : public Connection {
private:
using CallBack = typename Signal<Args...>::CallBack;
std::weak_ptr<SignalRaw<Args...>> m_Signal;
CallBack m_Callback;
public:
ConnectionGuard(const Signal<Args...>& a_Signal, const CallBack& a_Callback) : m_Signal(a_Signal.m_Signal), m_Callback(a_Callback) {}
~ConnectionGuard() {
if (!m_Signal.expired())
m_Signal.lock()->Disconnect(m_Callback);
}
};
} // namespace utils
} // namespace td

View File

@@ -0,0 +1,32 @@
#pragma once
#include <td/misc/Signal.h>
namespace td {
namespace utils {
/**
* \brief Wrapper class to automatically disconnect from a Signal on object destruction
* \note You should inherit this class privately
* \sa Signal
*/
class SlotGuard {
private:
std::vector<std::unique_ptr<Connection>> m_Connections;
public:
/**
* \brief Connect a signal to a function (with the same signature)
*/
template <typename... Args>
void Connect(Signal<Args...> a_Signal, const typename Signal<Args...>::CallBack& a_Callback) {
m_Connections.push_back(a_Signal.ConnectSafe(a_Callback));
}
void Disconnect() {
m_Connections.clear();
}
};
} // namespace utils
} // namespace td

View File

@@ -0,0 +1 @@
#pragma once

View File

@@ -0,0 +1 @@
#pragma once

View File

@@ -0,0 +1 @@
#pragma once

View File

@@ -1,61 +0,0 @@
#pragma once
/**
* \file PacketDispatcher.h
* \brief File containing the td::protocol::PacketDispatcher class
*/
#include <td/common/NonCopyable.h>
#include <td/protocol/packet/Packets.h>
#include <map>
namespace td {
namespace protocol {
/**
* \class Dispatcher
* \brief Class used to dispatch things
*/
template <typename T_Enum, typename T_Handler, typename T>
class Dispatcher : private NonCopyable {
private:
std::map<T_Enum, std::vector<T_Handler*>> m_Handlers;
public:
/**
* \brief Constructor
*/
Dispatcher() {}
/**
* \brief Dispatch a packet
* \param packet The packet to dispatch
*/
void Dispatch(const T& packet);
/**
* \brief Register a packet handler
* \param type The packet type
* \param handler The packet handler
*/
void RegisterHandler(T_Enum type, T_Handler& handler);
/**
* \brief Unregister a packet handler
* \param type The packet type
* \param handler The packet handler
*/
void UnregisterHandler(T_Enum type, T_Handler& handler);
/**
* \brief Unregister a packet handler
* \param handler The packet handler
*/
void UnregisterHandler(T_Handler& handler);
};
} // namespace protocol
} // namespace td
#include "Dispatcher.inl"

View File

@@ -1,38 +0,0 @@
#pragma once
namespace td {
namespace protocol {
template <typename T_Enum, typename T_Handler, typename T>
void Dispatcher<T_Enum, T_Handler, T>::Dispatch(const T& packet) {
T_Enum type = packet.GetType();
for (auto* handler : m_Handlers[type])
handler->Check(packet);
}
template <typename T_Enum, typename T_Handler, typename T>
void Dispatcher<T_Enum, T_Handler, T>::RegisterHandler(T_Enum type, T_Handler& handler) {
auto found = std::find(m_Handlers[type].begin(), m_Handlers[type].end(), &handler);
if (found == m_Handlers[type].end())
m_Handlers[type].push_back(&handler);
}
template <typename T_Enum, typename T_Handler, typename T>
void Dispatcher<T_Enum, T_Handler, T>::UnregisterHandler(T_Enum type, T_Handler& handler) {
m_Handlers[type].erase(std::remove(m_Handlers[type].begin(), m_Handlers[type].end(), &handler), m_Handlers[type].end());
}
template <typename T_Enum, typename T_Handler, typename T>
void Dispatcher<T_Enum, T_Handler, T>::UnregisterHandler(T_Handler& handler) {
for (auto& pair : m_Handlers) {
if (pair.second.empty())
continue;
PacketType type = pair.first;
m_Handlers[type].erase(std::remove(m_Handlers[type].begin(), m_Handlers[type].end(), &handler), m_Handlers[type].end());
}
}
} // namespace protocol
} // namespace td

View File

@@ -6,14 +6,16 @@
*/
#include <memory>
#include <sp/common/GenericHandler.h>
#include <sp/protocol/ConcreteMessage.h>
#include <sp/protocol/MessageDispatcher.h>
#include <sp/protocol/MessageFactory.h>
#include <sp/protocol/MessageHandler.h>
#include <td/Types.h>
#include <td/common/NonCopyable.h>
#include <td/protocol/command/CommandData.h>
#include <sp/io/SerializableMessage.h>
namespace td {
namespace protocol {
@@ -33,7 +35,7 @@ class CommandHandler;
using CommandBase = sp::MessageBase<CommandID, CommandHandler>;
template <typename TData, CommandID ID>
using CommandMessage = sp::ConcreteMessage<TData, CommandID, ID, CommandHandler>;
using CommandMessage = sp::ConcreteMessage<TData, CommandBase, ID>;
namespace commands {
@@ -51,13 +53,15 @@ using UseItemCommand = CommandMessage<cdata::UseItem, CommandID::UseItem>;
using AllCommands = std::tuple<commands::EndCommand, commands::PlaceTowerCommand, commands::PlayerJoinCommand,
commands::SpawnTroopCommand, commands::TeamChangeCommand, commands::UpgradeTowerCommand, commands::UseItemCommand>;
class CommandHandler : public sp::MessageHandler<AllCommands> {};
class CommandHandler : public sp::GenericHandler<AllCommands> {};
using CommandDispatcher = sp::MessageDispatcher<CommandBase>;
using CommandFactory = sp::MessageFactory<CommandBase, AllCommands>;
using LockStep = std::vector<std::shared_ptr<CommandBase>>;
using CommandPtr = sp::SerializableMessage<CommandFactory>;
using LockStep = std::vector<CommandPtr>;
} // namespace protocol
} // namespace td

View File

@@ -7,7 +7,11 @@
#include <td/game/WorldTypes.h>
// Make it dynamic ?
#ifdef NDEBUG
#define LOCKSTEP_BUFFER_SIZE 10
#else
#define LOCKSTEP_BUFFER_SIZE 1
#endif
namespace td {
namespace protocol {
@@ -43,6 +47,11 @@ struct PlayerLeave {
PlayerID m_PlayerId;
};
struct PredictCommand {
CommandPtr m_Command;
StepTime m_FrameNumber;
};
/** Keep alive */
struct KeepAlive {
std::uint64_t m_KeepAliveId;
@@ -68,8 +77,8 @@ struct BeginGame {
};
struct LockSteps {
std::uint16_t m_FirstFrameNumber;
Array<LockStep, LOCKSTEP_BUFFER_SIZE> m_LockSteps;
StepTime m_FirstFrameNumber;
std::array<LockStep, LOCKSTEP_BUFFER_SIZE> m_LockSteps;
};
struct WorldHeader {
@@ -87,7 +96,26 @@ struct WorldHeader {
};
struct WorldData {
std::unordered_map<game::ChunkCoord, game::ChunkPtr> m_Chunks;
game::ChunkList m_Chunks;
};
// TODO: spawn multiple troops at the same time
struct SpawnTroop {
sp::BitField<EntityType, 5> m_Type;
sp::BitField<std::uint8_t, 3> m_Level;
};
struct PlaceTower {
TowerType m_Type;
TowerCoords m_Position;
};
struct LockStepRequest {
std::vector<StepTime> m_Missing;
};
struct LockStepResponse {
std::map<StepTime, LockStep> m_Steps;
};
} // namespace pdata

View File

@@ -0,0 +1,18 @@
#pragma once
#include <td/protocol/packet/PacketData.h>
namespace td {
namespace game {
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const TeamCastle& a_Castle);
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, TeamCastle& a_Castle);
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const Spawn& a_Spawn);
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, Spawn& a_Spawn);
} // namespace game
} // namespace td

View File

@@ -8,11 +8,11 @@
#include <td/common/NonCopyable.h>
#include <td/protocol/packet/PacketData.h>
#include <sp/common/GenericHandler.h>
#include <sp/protocol/ConcreteMessage.h>
#include <sp/protocol/MessageBase.h>
#include <sp/protocol/MessageDispatcher.h>
#include <sp/protocol/MessageFactory.h>
#include <sp/protocol/MessageHandler.h>
namespace td {
namespace protocol {
@@ -22,11 +22,16 @@ enum class PacketID : std::uint8_t {
BeginGame,
Disconnect,
KeepAlive,
LockStepRequest,
LockStepResponse,
LockSteps,
LoggingSuccess,
PlaceTower,
PlayerJoin,
PlayerLeave,
PlayerLogin,
PredictCommand,
SpawnTroop,
WorldHeader,
WorldData,
};
@@ -37,7 +42,7 @@ using PacketBase = sp::MessageBase<PacketID, PacketHandler>;
template <typename TData, PacketID ID>
using PacketMessage = sp::ConcreteMessage<TData, PacketID, ID, PacketHandler>;
using PacketMessage = sp::ConcreteMessage<TData, PacketBase, ID>;
namespace packets {
@@ -46,22 +51,27 @@ using BeginGamePacket = PacketMessage<pdata::BeginGame, PacketID::BeginGame>;
using ChatMessagePacket = PacketMessage<pdata::ChatMessage, PacketID::ChatMessage>;
using DisconnectPacket = PacketMessage<pdata::Disconnect, PacketID::Disconnect>;
using KeepAlivePacket = PacketMessage<pdata::KeepAlive, PacketID::KeepAlive>;
using LockStepRequestPacket = PacketMessage<pdata::LockStepRequest, PacketID::LockStepRequest>;
using LockStepResponsePacket = PacketMessage<pdata::LockStepResponse, PacketID::LockStepResponse>;
using LockStepsPacket = PacketMessage<pdata::LockSteps, PacketID::LockSteps>;
using LoggingSuccessPacket = PacketMessage<pdata::LoggingSuccess, PacketID::LoggingSuccess>;
using PlaceTowerPacket = PacketMessage<pdata::PlaceTower, PacketID::PlaceTower>;
using PlayerJoinPacket = PacketMessage<pdata::PlayerJoin, PacketID::PlayerJoin>;
using PlayerLeavePacket = PacketMessage<pdata::PlayerLeave, PacketID::PlayerLeave>;
using PlayerLoginPacket = PacketMessage<pdata::PlayerLogin, PacketID::PlayerLogin>;
using PredictCommandPacket = PacketMessage<pdata::PredictCommand, PacketID::PredictCommand>;
using SpawnTroopPacket = PacketMessage<pdata::SpawnTroop, PacketID::SpawnTroop>;
using WorldHeaderPacket = PacketMessage<pdata::WorldHeader, PacketID::WorldHeader>;
using WorldDataPacket = PacketMessage<pdata::WorldData, PacketID::WorldData>;
} // namespace packets
using AllPackets = std::tuple<packets::BeginGamePacket, packets::ChatMessagePacket, packets::DisconnectPacket,
packets::KeepAlivePacket, packets::LockStepsPacket, packets::LoggingSuccessPacket, packets::PlayerJoinPacket,
packets::PlayerLeavePacket, packets::PlayerLoginPacket, packets::WorldHeaderPacket, packets::WorldDataPacket>;
packets::KeepAlivePacket, packets::LockStepRequestPacket, packets::LockStepResponsePacket, packets::LockStepsPacket, packets::LoggingSuccessPacket, packets::PlaceTowerPacket,
packets::PlayerJoinPacket, packets::PlayerLeavePacket, packets::PlayerLoginPacket, packets::PredictCommandPacket,
packets::SpawnTroopPacket, packets::WorldHeaderPacket, packets::WorldDataPacket>;
class PacketHandler : public sp::MessageHandler<AllPackets> {};
class PacketHandler : public sp::GenericHandler<AllPackets> {};
using PacketDispatcher = sp::MessageDispatcher<PacketBase>;

View File

@@ -4,19 +4,21 @@
#include <td/render/Camera.h>
#include <td/render/loader/GLLoader.h>
#include <td/render/shader/CameraShaderProgram.h>
#include <td/misc/SlotGuard.h>
namespace td {
namespace render {
class BasicRenderer {
public:
virtual void Render() = 0;
virtual void Render(float a_Lerp) = 0;
virtual ~BasicRenderer() {}
void Render(const GL::VertexArray& a_Vao);
};
template <typename TShader>
class Renderer : public BasicRenderer {
class Renderer : public BasicRenderer, private utils::SlotGuard {
protected:
std::unique_ptr<TShader> m_Shader;
Camera& m_Camera;
@@ -24,6 +26,11 @@ class Renderer : public BasicRenderer {
public:
Renderer(Camera& a_Camera);
virtual ~Renderer() {}
template <typename T>
float Lerp(const T& a_Mob, float a_LerpFactor, const std::function<float(const T&)>& a_MemberGetter) {
return static_cast<float>(maths::Lerp(a_MemberGetter(a_Mob), a_MemberGetter(*a_Mob.m_Next), a_LerpFactor));
}
};
class RenderPipeline {
@@ -32,7 +39,7 @@ class RenderPipeline {
public:
RenderPipeline();
~RenderPipeline() = default;
virtual ~RenderPipeline() {}
template <typename T, typename... Args>
void AddRenderer(Args&&... args) {
@@ -43,9 +50,9 @@ class RenderPipeline {
m_Renderers.clear();
}
void Render() {
void Render(float a_Lerp) {
for (auto& renderer : m_Renderers) {
renderer->Render();
renderer->Render(a_Lerp);
}
}
};
@@ -56,12 +63,12 @@ class RenderPipeline {
template <typename TShader>
Renderer<TShader>::Renderer(Camera& a_Camera) : m_Shader(std::make_unique<TShader>()), m_Camera(a_Camera) {
a_Camera.OnPerspectiveChange.Connect([this]() {
Connect(a_Camera.OnPerspectiveChange, [this](){
m_Shader->Start();
m_Shader->SetProjectionMatrix(m_Camera.GetProjectionMatrix());
});
a_Camera.OnViewChange.Connect([this]() {
Connect(a_Camera.OnViewChange, [this]() {
m_Shader->Start();
m_Shader->SetViewMatrix(m_Camera.GetViewMatrix());
});

View File

@@ -16,7 +16,7 @@ struct RenderData {
GL::VertexArray LoadMobModel();
GL::VertexArray LoadWorldModel(const td::game::World* world);
GL::VertexArray LoadTileSelectModel();
RenderData LoadTowerModel(game::TowerPtr tower);
RenderData LoadTowerModel(const game::TowerPtr& tower);
} // namespace WorldLoader

View File

@@ -16,7 +16,7 @@ class EntityRenderer : public Renderer<shader::EntityShader> {
EntityRenderer(Camera& a_Camera, const game::World& a_World);
virtual ~EntityRenderer();
virtual void Render() override;
virtual void Render(float a_Lerp) override;
};
} // namespace render

View File

@@ -0,0 +1,23 @@
#pragma once
#include <td/render/Renderer.h>
#include <td/render/shader/EntityShader.h>
#include <td/game/World.h>
namespace td {
namespace render {
class TowerRenderer : public Renderer<shader::EntityShader> {
private:
const game::World& m_World;
std::unique_ptr<GL::VertexArray> m_EntityVao;
public:
TowerRenderer(Camera& a_Camera, const game::World& a_World);
virtual ~TowerRenderer();
virtual void Render(float a_Lerp) override;
};
} // namespace render
} // namespace td

View File

@@ -10,14 +10,13 @@ namespace render {
class WorldRenderer : public Renderer<shader::WorldShader> {
private:
const game::World& m_World;
std::unique_ptr<GL::VertexArray> m_WorldVao;
public:
WorldRenderer(Camera& a_Camera, const game::World& a_World);
virtual ~WorldRenderer();
virtual void Render() override;
virtual void Render(float a_Lerp) override;
};
} // namespace render

View File

@@ -0,0 +1,70 @@
#pragma once
#include <td/game/World.h>
#include <optional>
#include <td/misc/Signal.h>
namespace td {
namespace sim {
using GameHistory = std::vector<td::protocol::LockStep>;
using GameBuffer = std::vector<std::optional<td::protocol::LockStep>>;
// TODO: OnEnd signal
class ClientSimulation : public protocol::PacketHandler {
private:
std::uint64_t m_StepTime;
std::shared_ptr<game::World> m_World;
GameBuffer m_History;
float m_CurrentTime;
StepTime m_CurrentStep;
std::shared_ptr<WorldSnapshot> m_LastSnapshot;
StepTime m_LastValidStep;
static const protocol::LockStep EMPTY_LOCKSTEP;
using protocol::PacketHandler::Handle;
public:
utils::Signal<const std::vector<StepTime>&> OnMissingLockSteps;
/**
* \brief Replay constructor
* \param a_StepTime in ms
*/
ClientSimulation(std::shared_ptr<game::World> a_World, GameHistory&& a_History, std::uint64_t a_StepTime);
/**
* \brief Live update constructor (continuous game updates)
* \param a_StepTime in ms
*/
ClientSimulation(std::shared_ptr<game::World> a_World, std::uint64_t a_StepTime);
/**
* \return the progress [0-1] between two steps
*/
float Update(float a_Delta);
virtual void Handle(const protocol::packets::LockStepsPacket& a_LockSteps) override;
virtual void Handle(const protocol::packets::LockStepResponsePacket& a_LockSteps) override;
private:
/**
* \returns false if the empty lockstep was used
*/
bool Step();
/**
* \brief Ticks a_Count times
*/
std::size_t FastForward(std::size_t a_Count);
/**
* \brief Tries to recompute simulation if needed (for example in late command receival)
*/
void FastReplay();
};
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,27 @@
#pragma once
#include <td/game/World.h>
#include <td/protocol/command/Commands.h>
namespace td {
namespace sim {
class CommandApply : public protocol::CommandHandler {
private:
const game::World& m_World;
WorldSnapshot& m_Snapshot;
public:
CommandApply(const game::World& a_World, WorldSnapshot& a_Snapshot);
virtual void Handle(const protocol::commands::EndCommand& a_End) override;
virtual void Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) override;
virtual void Handle(const protocol::commands::PlayerJoinCommand& a_PlayerJoin) override;
virtual void Handle(const protocol::commands::SpawnTroopCommand& a_SpawnTroop) override;
virtual void Handle(const protocol::commands::TeamChangeCommand& a_TeamChange) override;
virtual void Handle(const protocol::commands::UpgradeTowerCommand& a_UpgradeTower) override;
virtual void Handle(const protocol::commands::UseItemCommand& a_UseItem) override;
};
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,39 @@
#pragma once
#include <mutex>
#include <td/game/World.h>
namespace td {
namespace sim {
// TODO: OnEnd signal
/**
* \note This class is thread safe
*/
class ServerSimulation : public protocol::CommandHandler {
private:
std::mutex m_Mutex;
game::World& m_World;
std::uint64_t m_StepTime;
std::uint64_t m_CurrentTime;
std::vector<td::protocol::LockStep> m_History;
using protocol::CommandHandler::Handle;
public:
ServerSimulation(game::World& a_World, std::uint64_t a_StepTime);
protocol::packets::LockStepsPacket Update();
protocol::packets::LockStepsPacket MakePacket();
// no checks are done !
virtual void Handle(const protocol::commands::SpawnTroopCommand& a_SpawnTroop) override;
virtual void Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) override;
protocol::packets::LockStepResponsePacket GetResponse(const protocol::packets::LockStepRequestPacket& a_Missing) const;
};
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,18 @@
#pragma once
#include <td/game/WorldTypes.h>
#include <td/game/Mobs.h>
namespace td {
namespace sim {
struct WorldSnapshot {
game::MobList m_Mobs;
game::TowerList m_Towers;
game::TeamList m_Teams;
};
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,41 @@
#pragma once
#include <td/protocol/command/Commands.h>
#include <td/simulation/WorldSnapshot.h>
namespace td {
namespace game {
class World;
}
namespace sim {
class IWorldSystem {
public:
virtual void Tick(const game::World& a_World, WorldSnapshot& a_State, FpFloat a_Delta) = 0;
virtual ~IWorldSystem() = default;
};
class WorldTicker {
private:
std::vector<std::unique_ptr<IWorldSystem>> m_Systems;
public:
WorldTicker();
WorldSnapshot NextStep(
const game::World& a_World, WorldSnapshot& a_PreviousState, const protocol::LockStep& a_LockStep, FpFloat a_Delta);
private:
void ApplySteps(const game::World& a_World, WorldSnapshot& a_State, const protocol::LockStep& a_LockStep);
void Tick(const game::World& a_World, WorldSnapshot& a_State, FpFloat a_Delta);
WorldSnapshot CreateNext(WorldSnapshot& a_PreviousState);
template <typename T>
void AddSystem() {
m_Systems.push_back(std::make_unique<T>());
}
};
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,14 @@
#pragma once
#include <td/simulation/WorldTicker.h>
namespace td {
namespace sim {
class EntityMove : public IWorldSystem {
public:
virtual void Tick(const game::World& a_World, WorldSnapshot& a_State, FpFloat a_Delta) override;
};
} // namespace sim
} // namespace td

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

@@ -0,0 +1,11 @@
#include <client/Client.h>
namespace td {
namespace client {
void Client::SendPacket(const protocol::PacketBase& a_Packet) {
m_Socket->Send(a_Packet);
}
} // namespace client
} // namespace td

View File

@@ -0,0 +1,16 @@
#include <client/ClientState.h>
#include <client/Client.h>
namespace td {
namespace client {
ClientState::ClientState(Client& a_Client) : Client::State(a_Client) {
Connect(m_StateMachine.m_Socket->OnReceive, std::bind(&ClientState::HandlePacket, this, std::placeholders::_1));
}
void ClientState::SendPacket(const protocol::PacketBase& a_Packet) {
m_StateMachine.m_Socket->Send(a_Packet);
}
} // namespace server
} // namespace td

View File

@@ -0,0 +1,19 @@
#include <client/socket/FakeSocket.h>
#include <server/socket/FakeSocket.h>
namespace td {
namespace client {
void FakeSocket::Send(const protocol::PacketBase& a_Packet) {
m_Server->OnReceivePeer(m_PeerId, a_Packet);
}
std::shared_ptr<FakeSocket> FakeSocket::Connect(const std::shared_ptr<server::FakeSocket>& a_Server) {
auto socket = std::make_shared<FakeSocket>(Private());
socket->m_Server = a_Server;
socket->m_PeerId = a_Server->ConnectFakePeer(socket);
return socket;
}
} // namespace client
} // namespace td

View File

@@ -0,0 +1,41 @@
#include <client/state/GameState.h>
namespace td {
namespace client {
class ClientHandler : public protocol::PacketHandler {
private:
sim::ClientSimulation& m_Simulation;
using protocol::PacketHandler::Handle;
public:
ClientHandler(sim::ClientSimulation& a_Simulation) : m_Simulation(a_Simulation) {}
void Handle(const protocol::packets::LockStepsPacket& a_LockStep) {
m_Simulation.Handle(a_LockStep);
}
void Handle(const protocol::packets::LockStepResponsePacket& a_LockStep) {
m_Simulation.Handle(a_LockStep);
}
};
GameState::GameState(Client& a_Client, const std::shared_ptr<game::World>& a_World, std::uint64_t a_StepTime) :
ClientState(a_Client), m_World(a_World), m_Simulation(a_World, a_StepTime) {
m_Simulation.OnMissingLockSteps.Connect([this](const std::vector<td::StepTime>& a_MissingSteps) {
SendPacket(protocol::packets::LockStepRequestPacket(a_MissingSteps));
});
}
void GameState::HandlePacket(const protocol::PacketBase& a_Packet) {
ClientHandler handler(m_Simulation);
a_Packet.Dispatch(handler);
}
void GameState::Update(float a_Delta) {
m_CurrentLerp = m_Simulation.Update(a_Delta);
}
} // namespace client
} // namespace td

View File

@@ -1,224 +1,24 @@
#include <iostream>
#include <chrono>
#include <td/display/state/MainMenuState.h>
#include <sp/common/DataBuffer.h>
#include <sp/extensions/Compress.h>
#include <td/input/Display.h>
#include <td/game/World.h>
#include <td/protocol/packet/Packets.h>
#include <td/render/renderer/WorldRenderer.h>
#include <td/render/renderer/EntityRenderer.h>
namespace td {
namespace game {
sp::DataBuffer& operator>>(sp::DataBuffer& buffer, game::TilePtr& tile) {
game::TileType tileType;
buffer >> tileType;
switch (tileType) {
case game::TileType::Tower: {
auto tilePtr = std::make_shared<game::TowerTile>();
buffer >> tilePtr->color_palette_ref >> tilePtr->team_owner;
tile = tilePtr;
break;
}
case game::TileType::Walk: {
auto tilePtr = std::make_shared<game::WalkableTile>();
buffer >> tilePtr->direction;
tile = tilePtr;
break;
}
case game::TileType::Decoration: {
auto tilePtr = std::make_shared<game::DecorationTile>();
buffer >> tilePtr->color_palette_ref;
tile = tilePtr;
break;
}
default:
break;
}
return buffer;
}
} // namespace game
} // namespace td
namespace sp {
namespace details {
template <>
void ReadMessage(DataBuffer& a_Buffer, td::protocol::pdata::WorldHeader& a_Header) {
a_Buffer >> a_Header.m_TowerPlacePalette >> a_Header.m_WalkablePalette;
std::uint16_t decoPaletteSize;
a_Buffer >> decoPaletteSize;
std::size_t decoPalletteSizeByte = decoPaletteSize * sizeof(td::Color);
a_Header.m_DecorationPalette.resize(decoPaletteSize);
memcpy(reinterpret_cast<std::uint8_t*>(a_Header.m_DecorationPalette.data()), a_Buffer.data() + a_Buffer.GetReadOffset(),
decoPalletteSizeByte);
a_Buffer.SetReadOffset(a_Buffer.GetReadOffset() + decoPalletteSizeByte);
a_Buffer >> a_Header.m_Background;
td::utils::shape::Rectangle redCastle, blueCastle;
a_Buffer >> a_Header.m_RedSpawn >> redCastle;
a_Buffer >> a_Header.m_BlueSpawn >> blueCastle;
a_Header.m_RedCastle.SetShape(redCastle);
a_Header.m_BlueCastle.SetShape(blueCastle);
std::uint64_t tilePaletteSize;
a_Buffer >> tilePaletteSize;
a_Header.m_TilePalette.reserve(tilePaletteSize);
for (std::uint64_t tileNumber = 0; tileNumber < tilePaletteSize; tileNumber++) {
td::game::TilePtr tile;
a_Buffer >> tile;
a_Header.m_TilePalette.push_back(tile);
}
a_Buffer >> a_Header.m_SpawnColorPalette;
}
typedef std::vector<uint64_t> ChunkPackedData;
const int BITS_IN_BYTE = 8;
const int BITS_IN_LONG = BITS_IN_BYTE * sizeof(std::uint64_t);
static unsigned int countBits(unsigned int number) {
// log function in base 2
// take only integer part
return static_cast<unsigned int>(std::log2(number) + 1);
}
template <>
void ReadMessage(DataBuffer& a_Buffer, td::protocol::pdata::WorldData& a_WorldData) {
std::uint64_t chunkCount;
a_Buffer >> chunkCount;
for (std::uint64_t chunkNumber = 0; chunkNumber < chunkCount; chunkNumber++) {
td::game::ChunkPtr chunk = std::make_shared<td::game::Chunk>();
td::game::ChunkCoord chunkCoords;
a_Buffer >> chunkCoords.x >> chunkCoords.y;
std::uint64_t chunkPaletteSize;
// std::reverse(reinterpret_cast<std::uint8_t*>(&chunkPaletteSize), reinterpret_cast<std::uint8_t*>(&chunkPaletteSize) + 4);
a_Buffer >> chunkPaletteSize;
td::game::ChunkPalette chunkPalette(chunkPaletteSize);
memcpy(reinterpret_cast<void*>(chunkPalette.data()), a_Buffer.data() + a_Buffer.GetReadOffset(),
chunkPaletteSize * sizeof(td::game::ChunkPalette::value_type));
a_Buffer.SetReadOffset(a_Buffer.GetReadOffset() + chunkPaletteSize * sizeof(td::game::ChunkPalette::value_type));
chunk->palette = chunkPalette;
std::uint8_t bitsPerTile = countBits(chunkPaletteSize);
// A bitmask that contains bitsPerTile set bits
td::game::Chunk::ChunkData::value_type individualValueMask = ((1 << bitsPerTile) - 1);
ChunkPackedData chunkData(td::game::Chunk::ChunkSize / (BITS_IN_BYTE * sizeof(ChunkPackedData::value_type) / bitsPerTile), 0);
memcpy(reinterpret_cast<void*>(chunkData.data()), a_Buffer.data() + a_Buffer.GetReadOffset(),
chunkData.size() * sizeof(ChunkPackedData::value_type));
a_Buffer.SetReadOffset(a_Buffer.GetReadOffset() + chunkData.size() * sizeof(ChunkPackedData::value_type));
for (unsigned int tileNumber = 0; tileNumber < td::game::Chunk::ChunkSize; tileNumber++) {
std::size_t startLong = (tileNumber * bitsPerTile) / BITS_IN_LONG;
std::size_t startOffset = (tileNumber * bitsPerTile) % BITS_IN_LONG;
std::size_t endLong = ((tileNumber + 1) * bitsPerTile - 1) / BITS_IN_LONG;
td::game::Chunk::ChunkData::value_type value;
if (startLong == endLong) {
value = (chunkData[startLong] >> startOffset);
} else {
int endOffset = BITS_IN_LONG - startOffset;
value = (chunkData[startLong] >> startOffset | chunkData[endLong] << endOffset);
}
value &= individualValueMask;
chunk->tiles[tileNumber] = value;
}
a_WorldData.m_Chunks.insert({chunkCoords, chunk});
}
}
} // namespace details
} // namespace sp
class WorldApply : public td::protocol::PacketHandler {
private:
td::game::World& m_World;
public:
WorldApply(td::game::World& a_World) : m_World(a_World) {}
void Handle(const td::protocol::pdata::WorldHeader& a_Header) override {
m_World.LoadMap(a_Header);
}
void Handle(const td::protocol::pdata::WorldData& a_Data) override {
m_World.LoadMap(a_Data);
}
};
td::game::World GetWorld() {
sp::DataBuffer buffer;
buffer.ReadFile("test/tdmap.tdmap2");
sp::DataBuffer buffer1 = sp::zlib::Decompress(buffer, 84);
buffer.SetReadOffset(buffer.GetReadOffset() + 83);
sp::DataBuffer buffer2 = sp::zlib::Decompress(buffer, 511);
td::protocol::packets::WorldHeaderPacket header;
header.Read(buffer1);
td::protocol::packets::WorldDataPacket data;
data.Read(buffer2);
td::game::World w;
WorldApply wa(w);
td::protocol::PacketDispatcher d;
d.RegisterHandler(td::protocol::PacketID::WorldData, &wa);
d.RegisterHandler(td::protocol::PacketID::WorldHeader, &wa);
d.Dispatch(header);
d.Dispatch(data);
return w;
float GetDelta() {
static std::chrono::time_point<std::chrono::system_clock> m_LastTime = std::chrono::system_clock::now();
auto timeElapsed = std::chrono::system_clock::now() - m_LastTime;
float timeSeconds = std::chrono::duration<float, std::chrono::seconds::period>(timeElapsed).count();
m_LastTime = std::chrono::system_clock::now();
return timeSeconds;
}
int main(int argc, char** argv) {
td::game::World w = GetWorld();
// init GL context
td::Display display(1920, 1080, "Tower-Defense 2");
td::render::Camera cam;
auto mob = std::make_shared<td::game::Zombie>(0, 0, 0);
mob->SetCenter({77, 13});
w.GetMobList().push_back(mob);
td::render::RenderPipeline renderer;
renderer.AddRenderer<td::render::WorldRenderer>(cam, w);
renderer.AddRenderer<td::render::EntityRenderer>(cam, w);
cam.SetCamPos({77, 25, 13});
cam.UpdatePerspective(display.GetAspectRatio());
display.ChangeState<td::MainMenuState>();
while (!display.IsCloseRequested()) {
display.PollEvents();
renderer.Render();
display.Update();
float delta = GetDelta();
display.Update(delta);
}
return 0;

View File

@@ -0,0 +1,31 @@
#include <server/IServerSocket.h>
namespace td {
namespace server {
void IServerSocket::OnConnectPeer(PeerID a_PeerId) {
m_Ids.AddConnection(a_PeerId);
OnConnect(m_Ids.GetPlayerId(a_PeerId));
}
void IServerSocket::OnDisconnectPeer(PeerID a_PeerId) {
OnDisconnect(m_Ids.GetPlayerId(a_PeerId));
m_Ids.RemovePeer(a_PeerId);
}
void IServerSocket::OnReceivePeer(PeerID a_PeerId, const protocol::PacketBase& a_Packet) {
OnReceive(m_Ids.GetPlayerId(a_PeerId), a_Packet);
}
void IServerSocket::Send(PlayerID a_PlayerId, const protocol::PacketBase& a_Packet) {
SendPeer(m_Ids.GetPeerId(a_PlayerId), a_Packet);
}
void IServerSocket::Broadcast(const protocol::PacketBase& a_Packet) {
for (auto [peerId, playerId] : m_Ids) {
SendPeer(peerId, a_Packet);
}
}
} // namespace server
} // namespace td

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

@@ -0,0 +1,9 @@
#include <server/Server.h>
namespace td {
namespace server {
} // namespace server
} // namespace td

View File

@@ -0,0 +1,24 @@
#include <server/ServerState.h>
#include <server/Server.h>
#include <td/common/StateMachine.h>
namespace td {
namespace server {
ServerState::ServerState(Server& a_Server) : Server::State(a_Server) {
Connect(m_StateMachine.m_Socket->OnReceive, std::bind(&ServerState::HandlePacket, this, std::placeholders::_1, std::placeholders::_2));
}
ServerState::~ServerState() {}
void ServerState::SendPacket(PlayerID a_Id, const protocol::PacketBase& a_Packet) {
m_StateMachine.m_Socket->Send(a_Id, a_Packet);
}
void ServerState::BroadcastPacket(const protocol::PacketBase& a_Packet) {
m_StateMachine.m_Socket->Broadcast(a_Packet);
}
} // namespace server
} // namespace

View File

@@ -0,0 +1,48 @@
#include <server/socket/FakeSocket.h>
namespace td {
namespace server {
void FakeSocket::SendPeer(PeerID a_Peer, const protocol::PacketBase& a_Packet) {
auto socket = m_Clients.at(a_Peer);
assert(socket.has_value());
assert(!socket.value().expired());
socket.value().lock()->OnReceive(a_Packet);
}
void FakeSocket::ReceiveFromFakePeer(PeerID a_Peer, const protocol::PacketBase& a_Packet) {
OnReceivePeer(a_Peer, a_Packet);
}
PeerID FakeSocket::ConnectFakePeer(const std::shared_ptr<client::FakeSocket>& a_Socket) {
int peerId = GetNextFreeId();
if (peerId == -1) {
peerId = m_Clients.size();
m_Clients.push_back(a_Socket);
} else {
m_Clients.emplace(m_Clients.begin() + peerId, a_Socket);
}
a_Socket->OnConnect();
OnConnectPeer(peerId);
return peerId;
}
void FakeSocket::DisconnectFakePeer(PeerID a_Peer) {
auto socket = m_Clients.at(a_Peer);
assert(socket.has_value());
assert(!socket.value().expired());
socket.value().lock()->OnDisconnect();
OnDisconnectPeer(a_Peer);
}
int FakeSocket::GetNextFreeId() {
auto it = std::find_if(m_Clients.begin(), m_Clients.end(), [](const auto& a_Value) { return !a_Value.has_value(); });
if (it == m_Clients.end())
return -1;
return std::distance(m_Clients.begin(), it);
}
} // namespace server
} // namespace td

View File

@@ -0,0 +1,31 @@
#include <server/state/GameState.h>
#include <server/state/GameStateHandler.h>
#include <iostream>
namespace td {
namespace server {
GameState::GameState(Server& a_Server, const std::shared_ptr<game::World>& a_World) : ServerState(a_Server), m_World(a_World), m_Simulation(*m_World, STEP_TIME), m_Time(0) {
std::cout << "Switched to Game state !\n";
BroadcastPacket(m_Simulation.MakePacket());
}
void GameState::HandlePacket(PlayerID a_Id, const protocol::PacketBase& a_Packet) {
GameStateHandler handler(*this, a_Id);
a_Packet.Dispatch(handler);
}
void GameState::Update(float a_Delta) {
// TODO: don't make STEP_TIME constant
static const float stepTimeSecond = static_cast<float>(STEP_TIME) / 1000.0f;
m_Time += a_Delta;
if (m_Time > stepTimeSecond) {
m_Time = std::fmod(m_Time, stepTimeSecond);
auto lockStepPacket = m_Simulation.Update();
BroadcastPacket(lockStepPacket);
}
}
} // namespace server
} // namespace td

View File

@@ -0,0 +1,28 @@
#include <server/state/GameState.h>
#include <server/state/GameStateHandler.h>
namespace td {
namespace server {
GameStateHandler::GameStateHandler(GameState& a_GameState, PlayerID a_PlayerId) : m_GameState(a_GameState), m_PlayerId(a_PlayerId) {}
// TODO: redo this
void GameStateHandler::Handle(const protocol::packets::SpawnTroopPacket& a_Packet) {
static const EntityCoords DEFAULT_POS(td::FpFloat(77), td::FpFloat(13));
td::protocol::commands::SpawnTroopCommand spawn(*a_Packet->m_Type, *a_Packet->m_Level, DEFAULT_POS, m_PlayerId);
m_GameState.m_Simulation.Handle(spawn);
}
// TODO: and this
void GameStateHandler::Handle(const protocol::packets::PlaceTowerPacket& a_Packet) {
td::protocol::commands::PlaceTowerCommand place(a_Packet->m_Type, m_PlayerId, a_Packet->m_Position);
m_GameState.m_Simulation.Handle(place);
}
void GameStateHandler::Handle(const protocol::packets::LockStepRequestPacket& a_Packet) {
m_GameState.SendPacket(m_PlayerId, m_GameState.m_Simulation.GetResponse(a_Packet));
}
} // namespace server
} // namespace td

View File

@@ -0,0 +1,18 @@
#include <server/state/LobbyState.h>
#include <server/state/GameState.h>
#include <iostream>
namespace td {
namespace server {
void LobbyState::HandlePacket(PlayerID a_Id, const protocol::PacketBase& a_Packet) {
}
void LobbyState::Update(float a_Delta) {
m_StateMachine.ChangeState<GameState>(m_World);
}
} // namespace server
} // namespace td

29
src/td/Types.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include <td/Types.h>
#include <sp/common/DataBuffer.h>
#include <sp/common/ByteSwapping.h>
#include <sp/common/DataBufferOperators.h>
namespace td {
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const EntityCoords& a_Coords) {
return a_Buffer << a_Coords.x << a_Coords.y;
}
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const FpFloat& a_Float) {
auto raw = a_Float.raw_value();
return a_Buffer << raw;
}
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, EntityCoords& a_Coords) {
return a_Buffer >> a_Coords.x >> a_Coords.y;
}
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, FpFloat& a_Float) {
auto raw = a_Float.raw_value();
a_Buffer >> raw;
a_Float = FpFloat::from_raw_value(raw);
return a_Buffer;
}
} // namespace td

View File

@@ -1,4 +1,4 @@
#include <td/input/Display.h>
#include <td/display/Display.h>
#include <GL/glew.h>
#include <SDL3/SDL.h>
@@ -38,6 +38,9 @@ Display::Display(int a_Width, int a_Height, const std::string& a_Title) :
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8);
m_GLContext = SDL_GL_CreateContext(m_Window);
if (!m_GLContext) {
@@ -46,6 +49,8 @@ Display::Display(int a_Width, int a_Height, const std::string& a_Title) :
int major, minor, mask;
int r, g, b, a, depth;
int mBuffers, mSamples;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &mask);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor);
@@ -57,6 +62,9 @@ Display::Display(int a_Width, int a_Height, const std::string& a_Title) :
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depth);
SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &mBuffers);
SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &mSamples);
const char* mask_desc;
if (mask & SDL_GL_CONTEXT_PROFILE_CORE) {
@@ -72,6 +80,8 @@ Display::Display(int a_Width, int a_Height, const std::string& a_Title) :
utils::LOG(utils::Format(
"GL Context : %i.%i %s, Color : R:%i G:%i B:%i A:%i, Depth bits : %i", major, minor, mask_desc, r, g, b, a, depth));
utils::LOG(utils::Format("MultiSamples : Buffers : %i, Samples : %i", mBuffers, mSamples));
SDL_GL_MakeCurrent(m_Window, m_GLContext);
GLenum error = glewInit();
@@ -81,6 +91,9 @@ Display::Display(int a_Width, int a_Height, const std::string& a_Title) :
// WindowResizeEvent(WindowWidth, WindowHeight);
// vsync
SDL_GL_SetSwapInterval(1);
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
@@ -111,6 +124,9 @@ Display::Display(int a_Width, int a_Height, const std::string& a_Title) :
ImGui_ImplOpenGL3_Init("#version 330");
}
void Display::Close() {
m_ShouldClose = true;
}
void Display::PollEvents() {
SDL_Event event;
@@ -119,12 +135,21 @@ void Display::PollEvents() {
case SDL_EVENT_QUIT:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED: {
m_ShouldClose = true;
break;
}
case SDL_EVENT_WINDOW_RESIZED: {
m_LastWidth = event.window.data1;
m_LastHeight = event.window.data2;
m_AspectRatio = (float)m_LastWidth / m_LastHeight;
OnAspectRatioChange(m_AspectRatio);
break;
}
case SDL_EVENT_KEY_DOWN: {
if (!event.key.repeat)
OnKeyDown(event.key.key);
break;
}
default:
@@ -138,7 +163,11 @@ void Display::PollEvents() {
ImGui::NewFrame();
}
void Display::Update() {
void Display::Update(float a_Delta) {
StateMachine::Update(a_Delta);
#ifndef NDEBUG
ImGui::ShowDemoWindow();
#endif
ImGui::Render();
ImGuiIO& io = ImGui::GetIO();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);

View File

@@ -0,0 +1,10 @@
#include <td/display/DisplayState.h>
namespace td {
DisplayState::DisplayState(Display& a_Display) : Display::State(a_Display) {
Connect(m_StateMachine.OnKeyDown, std::bind(&DisplayState::OnKeyDown, this, std::placeholders::_1));
Connect(m_StateMachine.OnAspectRatioChange, std::bind(&DisplayState::OnAspectRatioChange, this, std::placeholders::_1));
}
} // namespace td

View File

@@ -0,0 +1,16 @@
#include <td/display/menu/CreatePartyMenu.h>
#include <imgui.h>
namespace td {
CreatePartyMenu::CreatePartyMenu(MainMenuState& a_MainMenu) : MainMenuState::Menu(a_MainMenu) {}
CreatePartyMenu::~CreatePartyMenu() {}
void CreatePartyMenu::Update() {
ImGui::Text("CreatePartyMenu");
m_StateStack.RenderBackButton();
}
} // namespace td

View File

@@ -0,0 +1,16 @@
#include <td/display/menu/JoinPartyMenu.h>
#include <imgui.h>
namespace td {
JoinPartyMenu::JoinPartyMenu(MainMenuState& a_MainMenu) : MainMenuState::Menu(a_MainMenu) {}
JoinPartyMenu::~JoinPartyMenu() {}
void JoinPartyMenu::Update() {
ImGui::Text("JoinPartyMenu");
m_StateStack.RenderBackButton();
}
} // namespace td

View File

@@ -0,0 +1,28 @@
#include <td/display/menu/MainMenu.h>
#include <imgui.h>
#include <td/display/menu/CreatePartyMenu.h>
#include <td/display/menu/JoinPartyMenu.h>
#include <td/display/menu/SettingsMenu.h>
#include <td/display/state/DebugWorldState.h>
namespace td {
MainMenu::MainMenu(MainMenuState& a_MainMenu) : MainMenuState::Menu(a_MainMenu) {}
MainMenu::~MainMenu() {}
void MainMenu::Update() {
if (ImGui::Button("Create Party"))
m_StateStack.PushState<CreatePartyMenu>();
if (ImGui::Button("Join Party"))
m_StateStack.PushState<JoinPartyMenu>();
if (ImGui::Button("Settings"))
m_StateStack.PushState<SettingsMenu>();
#ifndef NDEBUG
if (ImGui::Button("Debug world"))
m_StateStack.ChangeState<DebugWorldState>();
#endif
}
} // namespace td

View File

@@ -0,0 +1,16 @@
#include <td/display/menu/SettingsMenu.h>
#include <imgui.h>
namespace td {
SettingsMenu::SettingsMenu(MainMenuState& a_MainMenu) : MainMenuState::Menu(a_MainMenu) {}
SettingsMenu::~SettingsMenu() {}
void SettingsMenu::Update() {
ImGui::Text("SettingsMenu");
m_StateStack.RenderBackButton();
}
} // namespace td

View File

@@ -0,0 +1,134 @@
#include <td/display/state/DebugWorldState.h>
#include <chrono>
#include <fstream>
#include <iostream>
#include <td/game/World.h>
#include <td/protocol/packet/PacketSerialize.h>
#include <td/protocol/packet/Packets.h>
#include <td/render/renderer/EntityRenderer.h>
#include <td/render/renderer/TowerRenderer.h>
#include <td/render/renderer/WorldRenderer.h>
#include <sp/common/DataBuffer.h>
#include <sp/extensions/Compress.h>
#include <sp/io/MessageStream.h>
#include <sp/io/StdIo.h>
#include <server/Server.h>
#include <server/socket/FakeSocket.h>
#include <server/state/GameState.h>
#include <client/Client.h>
#include <client/socket/FakeSocket.h>
#include <client/state/GameState.h>
#include <td/display/Display.h>
#include <td/display/state/DebugWorldState.h>
namespace td {
// TODO: get rid of this class
class WorldApply : public protocol::PacketHandler {
private:
game::World& m_World;
using protocol::PacketHandler::Handle;
public:
WorldApply(game::World& a_World) : m_World(a_World) {}
void Handle(const protocol::packets::WorldHeaderPacket& a_Header) override {
m_World.LoadMap(*a_Header);
}
void Handle(const protocol::packets::WorldDataPacket& a_Data) override {
m_World.LoadMap(*a_Data);
}
};
void Save(const protocol::PacketBase& header, const protocol::PacketBase& data) {
auto comp = std::make_shared<sp::ZlibCompress>();
std::ofstream fStream("test/tdmap.tdmap3");
auto out = std::make_shared<sp::StdOuput>(fStream);
sp::MessageStream<protocol::PacketFactory> stream(std::move(out), std::move(comp));
stream.WriteMessage(header, false);
stream.WriteMessage(data, false);
}
game::WorldPtr GetWorld() {
auto comp = std::make_shared<sp::ZlibCompress>();
std::ifstream fStream("test/tdmap.tdmap2");
auto out = std::make_shared<sp::StdInput>(fStream);
sp::MessageStream<protocol::PacketFactory> stream(std::move(out), std::move(comp));
auto header = stream.ReadMessage(protocol::PacketID::WorldHeader);
auto data = stream.ReadMessage(protocol::PacketID::WorldData);
auto w = std::make_shared<game::World>();
auto wa = std::make_shared<WorldApply>(*w);
protocol::PacketDispatcher d;
d.RegisterHandler(wa);
d.Dispatch(*header);
d.Dispatch(*data);
Save(*header, *data);
return w;
}
DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) {
// server
game::WorldPtr serverWorld = GetWorld();
auto serverFakeSocket = std::make_shared<server::FakeSocket>();
m_Server = std::make_unique<server::Server>(serverFakeSocket);
// client
game::WorldPtr clientWorld = GetWorld();
auto clientFakeSocket = client::FakeSocket::Connect(serverFakeSocket);
m_Client = std::make_unique<client::Client>(clientFakeSocket);
// render
m_Renderer.AddRenderer<render::WorldRenderer>(m_Camera, *clientWorld);
m_Renderer.AddRenderer<render::EntityRenderer>(m_Camera, *clientWorld);
m_Renderer.AddRenderer<render::TowerRenderer>(m_Camera, *clientWorld);
// camera
m_Camera.SetCamPos({77, 7, 13});
m_Camera.UpdatePerspective(m_StateMachine.GetAspectRatio());
// states
m_ClientState = m_Client->ChangeState<client::GameState>(clientWorld, STEP_TIME);
m_Server->ChangeState<server::GameState>(serverWorld);
}
void DebugWorldState::Update(float a_Delta) {
m_Server->Update(a_Delta);
m_Client->Update(a_Delta);
// TODO: m_ClientState might be invalid !
m_Renderer.Render(m_ClientState->GetCurrentLerp());
}
void DebugWorldState::OnAspectRatioChange(float a_Ratio) {
m_Camera.UpdatePerspective(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)));
}
}
DebugWorldState::~DebugWorldState() {}
} // namespace td

View File

@@ -0,0 +1,31 @@
#include <td/display/state/MainMenuState.h>
#include <imgui.h>
#include <td/display/menu/MainMenu.h>
#include <td/display/state/DebugWorldState.h>
namespace td {
MainMenuState::MainMenuState(Display& a_Display) : DisplayState(a_Display) {
PushState<MainMenu>();
}
MainMenuState::~MainMenuState() {}
void MainMenuState::Update(float a_Delta) {
ImGui::Begin("MainWindow");
MainMenuStateStack::Update();
ImGui::End();
}
void MainMenuState::RenderBackButton() {
if (ImGui::Button("Back"))
PopState();
}
void MainMenuState::OnKeyDown(SDL_Keycode a_Key) {
if (a_Key == SDLK_ESCAPE)
PopState();
}
} // namespace td

View File

@@ -1,44 +0,0 @@
#include <td/game/GameHistory.h>
namespace td {
namespace game {
GameHistory::GameHistory() : m_History(std::numeric_limits<HistorySizeType>::max()), m_Cursor(0) {}
void GameHistory::SetLockStep(HistorySizeType a_Index, protocol::LockStep&& a_LockStep) {
m_History[a_Index] = std::move(a_LockStep);
}
const protocol::LockStep& GameHistory::GetLockStep(HistorySizeType a_Index) const {
return *m_History[a_Index];
}
bool GameHistory::HasLockStep(HistorySizeType a_Index) const {
return m_History[a_Index].has_value();
}
const protocol::LockStep& GameHistory::GetNextStep() {
return GetLockStep(m_Cursor++);
}
bool GameHistory::HasNextStep() const {
return HasLockStep(m_Cursor);
}
void GameHistory::FromPacket(td::protocol::pdata::LockSteps&& a_Steps) {
for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
protocol::LockStep& step = a_Steps.m_LockSteps[i];
SetLockStep(i + a_Steps.m_FirstFrameNumber, std::move(step));
}
}
td::protocol::packets::LockStepsPacket GameHistory::ToPacket(HistorySizeType a_StartIndex) {
Array<protocol::LockStep, LOCKSTEP_BUFFER_SIZE> steps;
for (int i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
steps[i] = GetLockStep(a_StartIndex + i);
}
return {a_StartIndex, std::move(steps)};
}
} // namespace game
} // namespace td

View File

@@ -1,31 +1,43 @@
#include <td/game/World.h>
#include <td/simulation/WorldTicker.h>
namespace td {
namespace game {
World::World() : m_Teams{Team{TeamColor::Red}, Team{TeamColor::Blue}} {
class ColorTileVisitor : public TileHandler {
private:
const World& m_World;
const Color* m_Result;
public:
ColorTileVisitor(const World& a_World) : m_World(a_World), m_Result(nullptr) {}
virtual void Handle(const EmptyTile& a_Tile) override {}
virtual void Handle(const TowerTile& a_Tile) override {
m_Result = &m_World.GetTowerTileColorPalette()[a_Tile->m_ColorPaletteRef];
}
const Color* World::GetTileColor(TilePtr tile) const {
switch (tile->GetType()) {
case TileType::Tower: {
TowerTile* towerTile = dynamic_cast<TowerTile*>(tile.get());
return &m_TowerPlacePalette[towerTile->color_palette_ref];
virtual void Handle(const WalkableTile& a_Tile) override {
m_Result = &m_World.GetWalkableTileColor();
}
case TileType::Walk: {
return &m_WalkablePalette;
virtual void Handle(const DecorationTile& a_Tile) override {
m_Result = &m_World.GetDecorationPalette()[a_Tile->m_ColorPaletteRef];
}
case TileType::Decoration: {
DecorationTile* towerTile = dynamic_cast<DecorationTile*>(tile.get());
return &m_DecorationPalette[towerTile->color_palette_ref];
break;
const Color* GetResult() {
return m_Result;
}
default: {
return nullptr;
}
}
return nullptr;
};
World::World() : m_CurrentState(std::make_shared<sim::WorldSnapshot>()), m_NextState(m_CurrentState) {}
const Color* World::GetTileColor(const TilePtr& tile) const {
ColorTileVisitor visitor(*this);
tile->Dispatch(visitor);
return visitor.GetResult();
}
bool World::LoadMap(const protocol::pdata::WorldHeader& a_WorldHeader) {
@@ -40,10 +52,10 @@ bool World::LoadMap(const protocol::pdata::WorldHeader& a_WorldHeader) {
m_SpawnColorPalette = a_WorldHeader.m_SpawnColorPalette;
GetRedTeam().GetCastle() = a_WorldHeader.m_RedCastle;
GetRedTeam().GetCastle().SetTeam(&GetRedTeam());
// GetRedTeam().GetCastle().SetTeam(&GetRedTeam());
GetBlueTeam().GetCastle() = a_WorldHeader.m_BlueCastle;
GetBlueTeam().GetCastle().SetTeam(&GetBlueTeam());
// GetBlueTeam().GetCastle().SetTeam(&GetBlueTeam());
m_TilePalette = a_WorldHeader.m_TilePalette;
@@ -55,5 +67,16 @@ bool World::LoadMap(const protocol::pdata::WorldData& a_WorldData) {
return true;
}
const std::shared_ptr<sim::WorldSnapshot>& World::Tick(const protocol::LockStep& a_LockStep, FpFloat a_Delta) {
m_CurrentState = m_NextState;
m_NextState = std::make_shared<sim::WorldSnapshot>(m_Ticker.NextStep(*this, *m_NextState, a_LockStep, a_Delta));
return m_CurrentState;
}
void World::ResetSnapshots(std::shared_ptr<sim::WorldSnapshot>& a_Current, std::shared_ptr<sim::WorldSnapshot>& a_Next) {
m_CurrentState = a_Current;
m_NextState = a_Next;
}
} // namespace game
} // namespace td

View File

@@ -0,0 +1,37 @@
#include <td/game/WorldTypes.h>
namespace td {
namespace game {
const int BITS_IN_BYTE = 8;
const int BITS_IN_LONG = BITS_IN_BYTE * sizeof(std::uint64_t);
static unsigned int countBits(unsigned int number) {
// log function in base 2
// take only integer part
return static_cast<unsigned int>(std::log2(number) + 1);
}
TileIndex Chunk::GetTileIndex(std::uint16_t tileNumber) const {
const std::uint8_t bitsPerTile = countBits(m_Palette.size());
const std::size_t startLong = (tileNumber * bitsPerTile) / BITS_IN_LONG;
const std::size_t startOffset = (tileNumber * bitsPerTile) % BITS_IN_LONG;
const std::size_t endLong = ((tileNumber + 1) * bitsPerTile - 1) / BITS_IN_LONG;
const Chunk::ChunkData::value_type individualValueMask = ((1 << bitsPerTile) - 1);
td::game::Chunk::ChunkData::value_type value;
if (startLong == endLong) {
value = (m_Data[startLong] >> startOffset);
} else {
int endOffset = BITS_IN_LONG - startOffset;
value = (m_Data[startLong] >> startOffset | m_Data[endLong] << endOffset);
}
value &= individualValueMask;
return m_Palette.at(value);
}
} // namespace game
} // namespace td

View File

@@ -0,0 +1,9 @@
#include <td/protocol/packet/PacketSerialize.h>
namespace td {
namespace game {
} // namespace game
} // namespace td

View File

@@ -0,0 +1,33 @@
#include <td/protocol/packet/PacketData.h>
#include <td/protocol/packet/PacketSerialize.h>
#include <sp/common/DataBufferOperators.h>
namespace td {
namespace game {
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const TeamCastle& a_Castle) {
return a_Buffer << a_Castle.GetCenterX() << a_Castle.GetCenterY();
}
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, TeamCastle& a_Castle) {
float x, y;
a_Buffer >> x >> y;
a_Castle.SetCenter({x, y});
return a_Buffer;
}
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const Spawn& a_Spawn) {
return a_Buffer << a_Spawn.GetCenterX() << a_Spawn.GetCenterY();
}
sp::DataBuffer& operator>>(sp::DataBuffer& a_Buffer, Spawn& a_Spawn) {
float x, y;
a_Buffer >> x >> y;
a_Spawn.SetCenter({x, y});
return a_Buffer;
}
} // namespace game
} // namespace td

View File

@@ -11,16 +11,13 @@ namespace render {
namespace WorldLoader {
const static int POSITION_VERTEX_SIZE = 3;
const static int TEXTURE_VERTEX_SIZE = 2;
// const static int TEXTURE_VERTEX_SIZE = 2;
GL::VertexArray LoadWorldModel(const td::game::World* world) {
std::vector<float> positions;
std::vector<float> colors;
for (const auto& chunkInfo : world->GetChunks()) {
const td::game::ChunkCoord& coords = chunkInfo.first;
td::game::ChunkPtr chunk = chunkInfo.second;
for (const auto& [coords, chunk] : world->GetChunks()) {
std::int32_t chunkX = coords.x * td::game::Chunk::ChunkWidth;
std::int32_t chunkY = coords.y * td::game::Chunk::ChunkHeight;
@@ -30,7 +27,7 @@ GL::VertexArray LoadWorldModel(const td::game::World* world) {
td::game::TileIndex tileIndex = chunk->GetTileIndex(tileNumber);
td::game::TilePtr tile = world->GetTilePtr(tileIndex);
if (tile == nullptr)
if (!tile)
continue;
positions.insert(
@@ -173,7 +170,7 @@ GL::VertexArray LoadTileSelectModel() {
return tileSelectVao;
}
RenderData LoadTowerModel(game::TowerPtr tower) {
RenderData LoadTowerModel(const game::TowerPtr& tower) {
RenderData renderData;
float towerX, towerDX;

View File

@@ -6,18 +6,23 @@ namespace td {
namespace render {
EntityRenderer::EntityRenderer(Camera& a_Camera, const game::World& a_World) : Renderer(a_Camera), m_World(a_World) {
m_EntityVao = std::make_unique<GL::VertexArray>(std::move(WorldLoader::LoadMobModel()));
m_EntityVao = std::make_unique<GL::VertexArray>(WorldLoader::LoadMobModel());
m_Shader->Start();
m_Shader->SetColorEffect({1, 0, 1});
}
EntityRenderer::~EntityRenderer() {}
void EntityRenderer::Render() {
void EntityRenderer::Render(float a_Lerp) {
m_Shader->Start();
for (const auto& mob : m_World.GetMobList()) {
const auto mobCoords = mob->GetCenter();
m_Shader->SetModelPos({mobCoords.GetX(), 1, mobCoords.GetY()});
float x = Lerp<game::Mob>(*mob, a_Lerp, [](const game::Mob& a_Mob) { return static_cast<float>(a_Mob.m_Position.x); });
float z = Lerp<game::Mob>(*mob, a_Lerp, [](const game::Mob& a_Mob) { return static_cast<float>(a_Mob.m_Position.y); });
m_Shader->SetModelPos({x, 1, z});
Renderer::Render(*m_EntityVao);
}
}

View File

@@ -0,0 +1,27 @@
#include <td/render/renderer/TowerRenderer.h>
#include <td/render/loader/WorldLoader.h>
namespace td {
namespace render {
TowerRenderer::TowerRenderer(Camera& a_Camera, const game::World& a_World) : Renderer(a_Camera), m_World(a_World) {
m_EntityVao = std::make_unique<GL::VertexArray>(WorldLoader::LoadMobModel());
m_Shader->Start();
m_Shader->SetColorEffect({0, 0, 1});
}
TowerRenderer::~TowerRenderer() {}
void TowerRenderer::Render(float a_Lerp) {
m_Shader->Start();
for (const auto& tower : m_World.GetTowers()) {
m_Shader->SetModelPos({tower->GetCenterX(), 1, tower->GetCenterY()});
Renderer::Render(*m_EntityVao);
}
}
} // namespace render
} // namespace td

View File

@@ -7,16 +7,15 @@
namespace td {
namespace render {
WorldRenderer::WorldRenderer(Camera& a_Camera, const game::World& a_World) : Renderer(a_Camera), m_World(a_World) {
m_WorldVao = std::make_unique<GL::VertexArray>(std::move(WorldLoader::LoadWorldModel(&a_World)));
WorldRenderer::WorldRenderer(Camera& a_Camera, const game::World& a_World) : Renderer(a_Camera) {
m_WorldVao = std::make_unique<GL::VertexArray>(WorldLoader::LoadWorldModel(&a_World));
}
WorldRenderer::~WorldRenderer() {}
void WorldRenderer::Render() {
void WorldRenderer::Render(float a_Lerp) {
m_Shader->Start();
Renderer::Render(*m_WorldVao);
ImGui::ShowDemoWindow();
}
} // namespace render

View File

@@ -109,7 +109,7 @@ unsigned int ShaderProgram::LoadShader(const std::string& source, GLenum type) {
glCompileShader(shaderID);
GLint compilesuccessful;
glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compilesuccessful);
if (compilesuccessful == false) {
if (!compilesuccessful) {
GLsizei size;
glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &size);
std::vector<char> shaderError(static_cast<std::size_t>(size));

View File

@@ -0,0 +1,126 @@
#include <td/simulation/ClientSimulation.h>
#include <chrono>
#include <iostream>
// TODO: mutex this bad boy
namespace td {
namespace sim {
const protocol::LockStep ClientSimulation::EMPTY_LOCKSTEP;
std::uint64_t GetTime() {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock().now().time_since_epoch()).count());
}
ClientSimulation::ClientSimulation(std::shared_ptr<game::World> a_World, GameHistory&& a_History, std::uint64_t a_StepTime) :
m_StepTime(a_StepTime),
m_World(a_World),
m_CurrentTime(0),
m_CurrentStep(0),
m_LastSnapshot(std::make_shared<WorldSnapshot>()),
m_LastValidStep(0) {
m_History.reserve(a_History.size());
for (auto&& lockstep : a_History) {
m_History.emplace_back(std::move(lockstep));
}
Step();
}
ClientSimulation::ClientSimulation(std::shared_ptr<game::World> a_World, std::uint64_t a_StepTime) :
m_StepTime(a_StepTime),
m_World(a_World),
m_History(std::numeric_limits<StepTime>::max()),
m_CurrentTime(0),
m_CurrentStep(0),
m_LastSnapshot(std::make_shared<WorldSnapshot>()),
m_LastValidStep(0) {}
float ClientSimulation::Update(float a_Delta) {
// TODO: handle freezes (m_CurrentTime > 2 * m_StepTime)
static const float stepTimeSecond = static_cast<float>(m_StepTime) / 1000.0f;
m_CurrentTime += a_Delta;
if (m_CurrentTime > stepTimeSecond) {
m_CurrentTime = std::fmod(m_CurrentTime, stepTimeSecond);
Step();
}
return (float)m_CurrentTime / stepTimeSecond;
}
bool ClientSimulation::Step() {
const auto& step = m_History[m_CurrentStep];
if (step.has_value()) {
auto snapshot = m_World->Tick(step.value(), FpFloat(m_StepTime) / FpFloat(1000));
if (m_LastValidStep + 1 == m_CurrentStep) {
m_LastValidStep = m_CurrentStep;
m_LastSnapshot = snapshot;
}
} else {
m_World->Tick(EMPTY_LOCKSTEP, FpFloat(m_StepTime) / FpFloat(1000));
std::cout << "Empty tick (" << m_CurrentStep << ") !\n";
}
m_CurrentStep++;
return step.has_value();
}
void ClientSimulation::Handle(const protocol::packets::LockStepsPacket& a_LockSteps) {
const auto& steps = a_LockSteps->m_LockSteps;
for (std::size_t i = 0; i < LOCKSTEP_BUFFER_SIZE; i++) {
m_History[a_LockSteps->m_FirstFrameNumber + i] = steps[i];
}
// to be in the state [0-1]
if (a_LockSteps->m_FirstFrameNumber == 0)
Step();
FastReplay();
}
void ClientSimulation::Handle(const protocol::packets::LockStepResponsePacket& a_LockSteps) {
for (const auto& [stepTime, lockStep] : a_LockSteps->m_Steps) {
m_History[stepTime] = lockStep;
}
FastReplay();
}
std::size_t ClientSimulation::FastForward(std::size_t a_Count) {
std::size_t stepCount;
bool hasMissingStep = false;
for (std::size_t i = 0; i < a_Count; i++) {
if (!Step() && !hasMissingStep) {
hasMissingStep = true;
stepCount = i + 1;
}
}
std::cout << "Was behind " << (hasMissingStep ? stepCount : a_Count) << " ticks !\n";
return hasMissingStep ? stepCount : a_Count;
}
void ClientSimulation::FastReplay() {
if (m_LastValidStep + 1 >= m_CurrentStep)
return;
m_World->ResetSnapshots(m_LastSnapshot, m_LastSnapshot);
const std::size_t stepCount = m_CurrentStep - m_LastValidStep;
m_CurrentStep = m_LastValidStep;
if (FastForward(stepCount) == stepCount)
return;
std::vector<StepTime> missingSteps;
missingSteps.reserve(stepCount);
for (StepTime step = m_LastValidStep + 1; step < m_CurrentStep; step++) {
if (!m_History[step].has_value())
missingSteps.push_back(step);
}
OnMissingLockSteps(missingSteps);
}
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,35 @@
#include <td/simulation/CommandApply.h>
namespace td {
namespace sim {
CommandApply::CommandApply(const game::World& a_World, WorldSnapshot& a_Snapshot) : m_World(a_World), m_Snapshot(a_Snapshot) {}
void CommandApply::Handle(const protocol::commands::EndCommand& a_End) {
(void) m_World;
}
void CommandApply::Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) {
static game::TowerFactory factory;
auto tower = std::shared_ptr<game::Tower>(factory.CreateMessage(*a_PlaceTower->m_Type).release());
tower->m_Builder = *a_PlaceTower->m_Placer;
tower->SetCenter(utils::shape::Point(a_PlaceTower->m_Position.x, a_PlaceTower->m_Position.y));
m_Snapshot.m_Towers.push_back(tower);
}
void CommandApply::Handle(const protocol::commands::PlayerJoinCommand& a_PlayerJoin) {}
void CommandApply::Handle(const protocol::commands::SpawnTroopCommand& a_SpawnTroop) {
auto zombie = std::make_shared<game::Zombie>();
zombie->m_Position = a_SpawnTroop->m_Position;
m_Snapshot.m_Mobs.push_back(zombie);
}
void CommandApply::Handle(const protocol::commands::TeamChangeCommand& a_TeamChange) {}
void CommandApply::Handle(const protocol::commands::UpgradeTowerCommand& a_UpgradeTower) {}
void CommandApply::Handle(const protocol::commands::UseItemCommand& a_UseItem) {}
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,52 @@
#include <td/simulation/ServerSimulation.h>
namespace td {
namespace sim {
ServerSimulation::ServerSimulation(game::World& a_World, std::uint64_t a_StepTime) :
m_World(a_World), m_StepTime(a_StepTime), m_CurrentTime(0), m_History(std::numeric_limits<StepTime>::max()) {}
protocol::packets::LockStepsPacket ServerSimulation::Update() {
std::lock_guard<std::mutex> lock(m_Mutex);
m_World.Tick(m_History[m_CurrentTime], FpFloat(m_StepTime) / FpFloat(1000));
m_CurrentTime++;
return MakePacket();
}
protocol::packets::LockStepsPacket ServerSimulation::MakePacket() {
std::array<protocol::LockStep, LOCKSTEP_BUFFER_SIZE> nextSteps;
std::copy(m_History.begin() + m_CurrentTime, m_History.begin() + m_CurrentTime + nextSteps.size(), nextSteps.begin());
return {m_CurrentTime, std::move(nextSteps)};
}
template <typename T>
void AddToCommandHistory(protocol::LockStep& a_LockStep, const T& a_Cmd) {
a_LockStep.push_back({std::make_shared<T>(a_Cmd)});
}
void ServerSimulation::Handle(const protocol::commands::SpawnTroopCommand& a_SpawnTroop) {
std::lock_guard<std::mutex> lock(m_Mutex);
AddToCommandHistory(m_History[m_CurrentTime + LOCKSTEP_BUFFER_SIZE], a_SpawnTroop);
}
void ServerSimulation::Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) {
std::lock_guard<std::mutex> lock(m_Mutex);
AddToCommandHistory(m_History[m_CurrentTime + LOCKSTEP_BUFFER_SIZE], a_PlaceTower);
}
protocol::packets::LockStepResponsePacket ServerSimulation::GetResponse(
const protocol::packets::LockStepRequestPacket& a_Missing) const {
std::map<StepTime, protocol::LockStep> result;
for (StepTime step : a_Missing->m_Missing) {
result.emplace(step, m_History[step]);
}
return {result};
}
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,53 @@
#include <td/simulation/WorldTicker.h>
#include <td/simulation/system/EntityMove.h>
#include <td/simulation/CommandApply.h>
namespace td {
namespace sim {
WorldTicker::WorldTicker() {
AddSystem<EntityMove>();
}
WorldSnapshot WorldTicker::NextStep(
const game::World& a_World, WorldSnapshot& a_PreviousState, const protocol::LockStep& a_LockStep, FpFloat a_Delta) {
WorldSnapshot next = CreateNext(a_PreviousState);
Tick(a_World, next, a_Delta);
ApplySteps(a_World, next, a_LockStep);
return next;
}
void WorldTicker::ApplySteps(const game::World& a_World, WorldSnapshot& a_State, const protocol::LockStep& a_LockStep) {
CommandApply cmdHandler(a_World, a_State);
for (const auto& cmd : a_LockStep) {
cmd->Dispatch(cmdHandler);
}
}
void WorldTicker::Tick(const game::World& a_World, WorldSnapshot& a_State, FpFloat a_Delta) {
for (const auto& system : m_Systems) {
system->Tick(a_World, a_State, a_Delta);
}
}
WorldSnapshot WorldTicker::CreateNext(WorldSnapshot& a_PreviousState) {
static game::MobFactory mobFactory;
WorldSnapshot next {
.m_Towers = a_PreviousState.m_Towers,
.m_Teams = a_PreviousState.m_Teams
};
next.m_Mobs.reserve(a_PreviousState.m_Mobs.size());
// creating a "linked list" of mobs between steps
for (auto& mob : a_PreviousState.m_Mobs) {
game::MobPtr newMob = std::shared_ptr<game::Mob>(mobFactory.CreateMessage(mob->GetId()).release());
*newMob = *mob;
mob->m_Next = newMob;
next.m_Mobs.push_back(newMob);
}
return next;
}
} // namespace sim
} // namespace td

View File

@@ -0,0 +1,13 @@
#include <td/simulation/system/EntityMove.h>
namespace td {
namespace sim {
void EntityMove::Tick(const game::World& a_World, WorldSnapshot& a_State, FpFloat a_Delta) {
for (auto& mob : a_State.m_Mobs) {
mob->m_Position.x += a_Delta;
}
}
} // namespace sim
} // namespace td

BIN
test/tdmap.tdmap Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -3,13 +3,16 @@ 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("splib 2.0.0", "zlib")
add_requires("libsdl3 3.2.16", "glew", "fpm", "enet6")
add_requires("libsdl3 3.2.16", "splib 2.3.0", "zlib", "glew", "fpm", "enet6")
set_languages("c++17")
set_warnings("all")
if is_mode("release") then
set_warnings("all", "error")
end
target("Tower-Defense2")
add_includedirs("include", {public = true})
set_kind("binary")