This commit is contained in:
2024-10-06 19:15:14 +02:00
parent 5d32eda090
commit 701b815dc1
14 changed files with 734 additions and 0 deletions

37
.clang-format Normal file
View File

@@ -0,0 +1,37 @@
Language: Cpp
BasedOnStyle: LLVM
AlignAfterOpenBracket: DontAlign
BreakConstructorInitializers: AfterColon
ConstructorInitializerAllOnOneLineOrOnePerLine: true
PointerAlignment: Left
SortIncludes: true
SpacesBeforeTrailingComments: 2
UseTab: Always
MaxEmptyLinesToKeep: 5
TabWidth: 4
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
IndentWidth: 4
IndentCaseLabels: true
ColumnLimit: 135
AlwaysBreakTemplateDeclarations: Yes
AllowShortFunctionsOnASingleLine: Empty
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true

66
include/td/Types.h Normal file
View File

@@ -0,0 +1,66 @@
#pragma once
#include <cstdint>
#include <fpm/fpm.hpp>
namespace td {
enum class Team : std::uint8_t {
Blue = 0,
Red,
};
enum class CastleType : std::uint8_t {
Archer = 0,
Leach,
Artillery,
Mage,
Ice,
Poison,
Quake,
Sorcerer,
Zeus,
Necromancer,
Turret,
};
enum class EntityType : std::uint8_t {
Zombie = 0,
Spider,
Pigman,
Skeleton,
Creeper,
Silverfish,
Blaze,
Witch,
Slime,
Giant,
Wither,
// And passive animals
};
enum class ShopItem : std::uint8_t {
Goldmine = 0,
Inferno,
Meteor,
Zeus,
Freeze,
Speed,
Heal,
};
using CastleID = std::uint16_t;
using PlayerID = std::uint8_t;
using EntityID = std::uint16_t;
struct CastleCoords {
std::int16_t x;
std::int16_t y;
};
struct EntityCoords {
fpm::fixed_16_16 x;
fpm::fixed_16_16 y;
};
} // namespace td

View File

@@ -0,0 +1,94 @@
#pragma once
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/string.hpp>
#include <stdexcept>
#include <vector>
namespace td {
namespace protocol {
class PlayerInfo;
class ByteBuffer {
private:
godot::PackedByteArray m_Buffer;
std::size_t m_ReadOffset;
public:
class ReadError : public std::runtime_error {
public:
ReadError(const std::string& msg) : std::runtime_error(msg) {}
};
ByteBuffer(godot::PackedByteArray&& a_Buffer) : m_Buffer(std::move(a_Buffer)), m_ReadOffset(0) {}
ByteBuffer() : m_ReadOffset(0) {
m_Buffer.resize(0);
}
const godot::PackedByteArray& GetByteArray() const {
return m_Buffer;
}
godot::PackedByteArray& GetByteArray() {
return m_Buffer;
}
// Integers
ByteBuffer& operator<<(int8_t a_Data);
ByteBuffer& operator>>(int8_t& a_Data);
ByteBuffer& operator<<(uint8_t a_Data);
ByteBuffer& operator>>(uint8_t& a_Data);
ByteBuffer& operator<<(int16_t a_Data);
ByteBuffer& operator>>(int16_t& a_Data);
ByteBuffer& operator<<(uint16_t a_Data);
ByteBuffer& operator>>(uint16_t& a_Data);
ByteBuffer& operator<<(int32_t a_Data);
ByteBuffer& operator>>(int32_t& a_Data);
ByteBuffer& operator<<(uint32_t a_Data);
ByteBuffer& operator>>(uint32_t& a_Data);
ByteBuffer& operator<<(int64_t a_Data);
ByteBuffer& operator>>(int64_t& a_Data);
ByteBuffer& operator<<(uint64_t a_Data);
ByteBuffer& operator>>(uint64_t& a_Data);
ByteBuffer& operator<<(float a_Data);
ByteBuffer& operator>>(float& a_Data);
ByteBuffer& operator<<(double a_Data);
ByteBuffer& operator>>(double& a_Data);
ByteBuffer& operator<<(const godot::String& a_Data);
ByteBuffer& operator>>(godot::String& a_Data);
ByteBuffer& operator<<(const godot::Vector3& a_Data);
ByteBuffer& operator>>(godot::Vector3& a_Data);
template <typename T>
ByteBuffer& operator<<(const std::vector<T>& a_Data) {
*this << static_cast<std::uint32_t>(a_Data.size());
for (const T& data : a_Data) {
*this << data;
}
return *this;
}
template <typename T>
ByteBuffer& operator>>(std::vector<T>& a_Data) {
std::uint32_t arraySize;
*this >> arraySize;
a_Data.resize(arraySize);
for (std::uint32_t i = 0; i < arraySize; i++) {
*this >> a_Data[i];
}
return *this;
}
ByteBuffer& operator<<(const PlayerInfo& a_Data);
ByteBuffer& operator>>(PlayerInfo& a_Data);
};
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,78 @@
#pragma once
#include <td/common/Types.h>
#include <godot_cpp/variant/string.hpp>
#include <godot_cpp/variant/vector3.hpp>
#include <vector>
namespace td {
namespace protocol {
struct PlayerInfo {
PlayerID m_PlayerId;
godot::String m_PlayerName;
};
namespace data {
struct PlayerLogin {
godot::String m_PlayerName;
};
struct UpdateHealth {
float m_NewHealth;
};
struct LoggingSuccess {
PlayerID m_PlayerId;
};
struct PlayerDeath {};
struct PlayerJoin {
PlayerInfo m_Player;
};
struct PlayerLeave {
PlayerID m_PlayerId;
};
struct PlayerStats {};
struct PlayerList {
std::vector<PlayerInfo> m_Players;
};
struct ServerConfig {};
struct ServerTps {};
struct UpdateGameState {};
struct KeepAlive {
std::uint64_t m_KeepAliveId;
};
struct Disconnect {};
struct ChatMessage {
godot::String m_Text;
};
struct PlayerPositionAndRotation {
PlayerID m_Player;
godot::Vector3 m_Position;
godot::Vector3 m_Rotation;
godot::Vector3 m_Velocity;
};
struct PlayerShoot {
PlayerID m_Sender;
godot::Vector3 m_Position;
godot::Vector3 m_Rotation;
godot::Vector3 m_Velocity;
};
} // namespace data
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,55 @@
#pragma once
/**
* \file PacketDeclare.h
* \brief Holds the definitions of the packets (but not their content)
*/
namespace td {
namespace protocol {
/**
* \enum PacketSender
* \brief Indicate who should send a packet
*/
enum class PacketSenderType {
/** Sent by clients and server */
Both = 1,
/** Sent by clients to the server */
Client,
/** Sent by server to the clients */
Server,
};
enum class PacketSendType {
Reliable = 1,
Unreliable,
UnreliableOrdered,
};
/**
* \def DeclareAllPacket
* \brief Avoids repetitive operations on packets
*/
#define DeclareAllPacket() \
DeclarePacket(ChatMessage, Reliable, Both) \
DeclarePacket(Disconnect, Reliable, Both) \
DeclarePacket(KeepAlive, Reliable, Both) \
DeclarePacket(LoggingSuccess, Reliable, Server) \
DeclarePacket(PlayerDeath, Reliable, Server) \
DeclarePacket(PlayerJoin, Reliable, Server) \
DeclarePacket(PlayerLeave, Reliable, Server) \
DeclarePacket(PlayerList, Reliable, Server) \
DeclarePacket(PlayerLogin, Reliable, Client) \
DeclarePacket(PlayerPositionAndRotation, Unreliable, Both) \
DeclarePacket(PlayerShoot, Reliable, Both) \
DeclarePacket(PlayerStats, Reliable, Server) \
DeclarePacket(ServerConfig, Reliable, Server) \
DeclarePacket(ServerTps, Reliable, Server) \
DeclarePacket(UpdateGameState, Reliable, Server) \
DeclarePacket(UpdateHealth, Reliable, Client)
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,58 @@
#pragma once
/**
* \file PacketDispatcher.h
* \brief File containing the blitz::protocol::PacketDispatcher class
*/
#include <td/common/NonCopyable.h>
#include <td/protocol/Packets.h>
#include <map>
namespace td {
namespace protocol {
class PacketHandler;
/**
* \class PacketDispatcher
* \brief Class used to dispatch packets
*/
class PacketDispatcher : private NonCopyable {
private:
std::map<PacketType, std::vector<PacketHandler*>> m_Handlers;
public:
/**
* \brief Constructor
*/
PacketDispatcher() {}
/**
* \brief Dispatch a packet
* \param packet The packet to dispatch
*/
void Dispatch(const Packet& packet);
/**
* \brief Register a packet handler
* \param type The packet type
* \param handler The packet handler
*/
void RegisterHandler(PacketType type, PacketHandler& handler);
/**
* \brief Unregister a packet handler
* \param type The packet type
* \param handler The packet handler
*/
void UnregisterHandler(PacketType type, PacketHandler& handler);
/**
* \brief Unregister a packet handler
* \param handler The packet handler
*/
void UnregisterHandler(PacketHandler& handler);
};
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,19 @@
#pragma once
#include <td/protocol/Packets.h>
#include <memory>
namespace td {
namespace protocol {
namespace PacketFactory {
template<typename PacketDerived, typename = typename std::enable_if<std::is_base_of<Packet, PacketDerived>::value>::type>
std::unique_ptr<PacketDerived> CreatePacket() {
return std::make_unique<PacketDerived>();
}
const std::unique_ptr<Packet>& CreateReadOnlyPacket(PacketType a_Type);
} // namespace PacketFactory
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,34 @@
#pragma once
/**
* \file PacketHandler.h
* \brief File containing the blitz::protocol::PacketHandler class
*/
#include <td/protocol/Packets.h>
#include <td/protocol/PacketVisitor.h>
namespace td {
namespace protocol {
class PacketDispatcher;
#define DeclarePacket(PacketName, ...) virtual void Visit(const packets::PacketName&); virtual void HandlePacket(const packets::PacketName&) {}
/**
* \class PacketHandler
* \brief Class used to handle packets
*/
class PacketHandler : public PacketVisitor {
public:
PacketHandler() {}
~PacketHandler() {}
DeclareAllPacket()
};
#undef DeclarePacket
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,60 @@
#pragma once
#include <td/protocol/PacketVisitor.h>
namespace td {
class NetworkInterface;
namespace protocol {
#define DeclarePacket(PacketName, Reliability, ...) void Visit(const protocol::packets::PacketName& a_Packet) override;
///////////////////////
/* PacketBroadcaster */
///////////////////////
class PacketBroadcaster : public protocol::PacketVisitor {
private:
NetworkInterface& m_NetworkInterface;
public:
PacketBroadcaster(NetworkInterface& a_NetworkInterface) : m_NetworkInterface(a_NetworkInterface) {}
void BroadcastPacket(const protocol::Packet& a_Packet) {
Check(a_Packet);
}
DeclareAllPacket()
};
//////////////////
/* PacketSender */
//////////////////
class PacketSender : public protocol::PacketVisitor {
private:
NetworkInterface& m_NetworkInterface;
PeerID m_PeerId;
public:
PacketSender(PeerID a_PeerId, NetworkInterface& a_NetworkInterface) : m_PeerId(a_PeerId), m_NetworkInterface(a_NetworkInterface) {}
void SendPacket(const protocol::Packet& a_Packet) {
Check(a_Packet);
}
DeclareAllPacket()
};
#undef DeclarePacket
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,20 @@
#pragma once
#include <td/protocol/Packets.h>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <memory>
namespace td {
namespace protocol {
using PacketPtr = std::unique_ptr<Packet>;
namespace PacketSerializer {
godot::PackedByteArray Serialize(const Packet& a_Packet);
std::unique_ptr<Packet> Deserialize(godot::PackedByteArray& a_Data);
}
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,39 @@
#pragma once
/**
* \file PacketVisitor.h
* \brief File containing the blitz::protocol::PacketVisitor class
*/
#include <td/protocol/Packets.h>
namespace td {
namespace protocol {
#define DeclarePacket(PacketName, ...) \
/** This function is called when the packet processed by PacketVisitor::Check is a PacketName */ \
virtual void Visit(const packets::PacketName&) {}
/**
* \class PacketVisitor
* \brief This class uses double-dispatch in order to find the real type of a packet
*/
class PacketVisitor : private NonCopyable {
protected:
PacketVisitor() {}
virtual ~PacketVisitor() {}
public:
/**
* \brief Calls the right PacketVisitor::Visit method corresponding to the real type of the packet
* \param packet the Packet to visit
*/
void Check(const Packet& packet);
DeclareAllPacket()
};
#undef DeclarePacket
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,112 @@
#pragma once
/**
* \file Packets.h
* \brief File containing the definitions of the packets
*/
#include <td/common/NonCopyable.h>
#include <td/protocol/PacketData.h>
#include <td/protocol/PacketDeclare.h>
namespace td {
namespace protocol {
class PacketVisitor;
/** A Packet id is 8 bits wide */
using PacketID = std::uint8_t;
#define DeclarePacket(PacketName, ...) /** PacketName */ PacketName,
/**
* \enum PacketType
* \brief Map a Packet to an id
*/
enum class PacketType : PacketID {
DeclareAllPacket()
/** The number of packets */
PACKET_COUNT
};
#undef DeclarePacket
class Packet : private NonCopyable {
public:
/**
* \return The real type of the packet
*/
virtual PacketType GetType() const = 0;
/**
* \brief The network peer who sent the packet
*/
PeerID m_Sender;
private:
/** Use a PacketVisitor to make double-dispatch possible */
virtual void Accept(PacketVisitor& a_Visitor) const = 0;
friend class PacketVisitor;
};
namespace packets {
/**
* \class ConcretePacket
* \brief A Packet associated with an id and holding data
* \tparam PT The packet type
* \tparam Data The structure holding the data of the packet (in blitz::protocol::data namespace)
*/
template <PacketType PT, typename Data>
class ConcretePacket : public Packet {
public:
/** The type of the struct holding the data */
using PacketDataType = Data;
/** The structure holding the actual data */
PacketDataType m_Data;
/** Construct the packet with data of type PacketDataType */
ConcretePacket(const PacketDataType& a_Data = {});
constexpr PacketType GetType() const override {
return PT;
};
private:
void Accept(PacketVisitor& a_Visitor) const override;
};
// define BLITZ_INSTANCIATE_PACKETS
// before including this file
// if you want to instantiate templates
#ifdef BLITZ_INSTANCIATE_PACKETS
#define DeclarePacket(PacketName, ...) \
using PacketName = ConcretePacket<PacketType::PacketName, data::PacketName>; \
template class ConcretePacket<PacketType::PacketName, data::PacketName>;
#else
#define DeclarePacket(PacketName, ...) /** Defines the PacketName packet */ \
using PacketName = ConcretePacket<PacketType::PacketName, data::PacketName>;
#endif
DeclareAllPacket()
#undef DeclarePacket
} // namespace packets
} // namespace protocol
} // namespace td

View File

@@ -0,0 +1,58 @@
#pragma once
#include <td/Types.h>
namespace td {
namespace protocol {
struct LockStep {
std::uint8_t m_CommandNumber;
std::vector<LockStepCommand> m_Commands;
};
struct LockSteps {
std::uint16_t m_FirstFrameNumber;
std::array<LockStep, 10> m_LockSteps;
};
namespace data {
struct PlaceTower {
CastleType m_Type : 4;
PlayerID m_Placer : 4;
CastleCoords m_Position;
};
struct UpgradeTower {
CastleID m_Tower : 12;
std::uint8_t m_Upgrade : 4;
};
struct SpawnTroop {
EntityType m_Type : 5;
std::uint8_t m_Level : 3;
EntityCoords m_Position;
PlayerID m_Sender;
};
struct UseItem {
ShopItem m_Item : 4;
PlayerID m_User : 4;
EntityCoords m_Position;
};
struct TeamChange {
PlayerID m_Player : 7;
Team m_NewTeam : 1;
};
} // namespace data
} // namespace protocol
} // namespace td

View File

@@ -1,8 +1,12 @@
add_rules("mode.debug", "mode.release")
add_requires("fpm")
target("Tower-Defense2")
add_includedirs("include")
set_kind("binary")
add_files("src/*.cpp")
add_packages("fpm")
--
-- If you want to known more usage about xmake, please see https://xmake.io