mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
First step of moving hand written bitstream code to struct based messages (see docs/PacketArchitecture.md). No wire changes. - GameMsg is now only a server-internal event (delivered to handlers registered with RegisterMsg); NetGameMsg is a wire message with Send(sysAddr) (UNASSIGNED broadcasts), SendToClient(sysAddr) (one client, never broadcasts), WritePacket, Serialize, Deserialize and Handle. Mixing them up is now a compile error. NetGameMsgEvent<T> / DeliverLocally carry a wire message to local handlers; loot drops, pickup, the object debugger, GM invisibility and model RequestUse use them. The GM invisibility message keeps its target, so its bytes are unchanged. - Behaviour change: GameMessageHandler logs and drops messages whose Deserialize fails instead of handling them with default fields (the 4 messages already registered: RequestUse, RequestServerObjectInfo, ShootingGalleryFire, PickupItem). - LUBitStream (kept as on main) gets a virtual destructor (Mail deleted derived packets through the base) and WritePacket, also used by ChatPackets::SendRoutedMsg with identical bytes. - BitStreamUtils::WriteOptional/ReadOptional for default-flag fields and WriteLengthPrefixed/ReadLengthPrefixed for length prefixed strings. - Every message ID enumerator (MessageType::*, ServiceType, Mail's wire enums) is pinned with static_asserts, so renumbering or removing one fails to compile. - Tests: dServerMock copies each sent packet (it kept a pointer to the caller's destroyed BitStream); PacketTestUtils.h compares packets bit for bit; helper tests use hand computed golden bytes and equality with the hand written patterns they replace; compile-time checks keep the wire/internal split in place. - docs/PacketArchitecture.md: survey, target architecture, conventions, verification method and migration plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
3.4 KiB
C++
92 lines
3.4 KiB
C++
#include "GameMessages.h"
|
|
#include "GameDependencies.h"
|
|
#include "PacketTestUtils.h"
|
|
#include "Entity.h"
|
|
#include "EntityManager.h"
|
|
|
|
#include <type_traits>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
// Wire messages (NetGameMsg) and server-internal events (GameMsg) are separate types, so a message can only be
|
|
// sent where it belongs. These checks fail to compile if the split is undone.
|
|
namespace {
|
|
template<typename T>
|
|
concept CanSendLocally = requires(T msg) { msg.Send(); msg.Send(LWOOBJID{}); };
|
|
|
|
template<typename T>
|
|
concept CanSendToClient = requires(const T msg) { msg.Send(UNASSIGNED_SYSTEM_ADDRESS); };
|
|
|
|
template<typename T>
|
|
concept CanHandleLocally = requires(Entity & entity, T msg) { entity.HandleMsg(msg); };
|
|
|
|
static_assert(!std::is_base_of_v<GameMessages::GameMsg, GameMessages::NetGameMsg>);
|
|
static_assert(!std::is_base_of_v<GameMessages::NetGameMsg, GameMessages::GameMsg>);
|
|
|
|
// A wire message can go to a client, never to local handlers.
|
|
static_assert(CanSendToClient<GameMessages::DropClientLoot>);
|
|
static_assert(!CanSendLocally<GameMessages::DropClientLoot>);
|
|
static_assert(!CanHandleLocally<GameMessages::DropClientLoot>);
|
|
|
|
// A local event can go to local handlers, never to a client.
|
|
static_assert(CanSendLocally<GameMessages::GetPosition>);
|
|
static_assert(!CanSendToClient<GameMessages::GetPosition>);
|
|
static_assert(CanHandleLocally<GameMessages::GetPosition>);
|
|
|
|
// A wire message wrapped for local delivery is a local event.
|
|
static_assert(CanSendLocally<GameMessages::DropClientLootEvent>);
|
|
static_assert(!CanSendToClient<GameMessages::DropClientLootEvent>);
|
|
}
|
|
|
|
class GameMsgSplitTests : public GameDependenciesTest {
|
|
protected:
|
|
void SetUp() override { SetUpDependencies(); }
|
|
void TearDown() override { TearDownDependencies(); }
|
|
};
|
|
|
|
TEST_F(GameMsgSplitTests, EventCarriesACopyWithTheTarget) {
|
|
auto entity = std::make_unique<Entity>(15, info);
|
|
|
|
LWOOBJID seenTarget = LWOOBJID_EMPTY;
|
|
LOT seenItem = LOT_NULL;
|
|
entity->RegisterMsg(MessageType::Game::DROP_CLIENT_LOOT, [&](GameMessages::GameMsg& msg) {
|
|
auto& event = static_cast<GameMessages::DropClientLootEvent&>(msg);
|
|
seenTarget = event.target;
|
|
seenItem = event.msg.item;
|
|
event.msg.item = 1; // handlers only modify the copy
|
|
return true;
|
|
});
|
|
|
|
GameMessages::DropClientLoot loot;
|
|
loot.target = 15;
|
|
loot.item = 1234;
|
|
|
|
GameMessages::DropClientLootEvent event(loot);
|
|
EXPECT_EQ(event.msgId, MessageType::Game::DROP_CLIENT_LOOT);
|
|
const auto sent = PacketTestUtils::Capture([&] { EXPECT_TRUE(entity->HandleMsg(event)); });
|
|
EXPECT_TRUE(sent.empty()); // nothing went on the wire
|
|
EXPECT_EQ(seenTarget, 15);
|
|
EXPECT_EQ(seenItem, 1234);
|
|
EXPECT_EQ(loot.item, 1234);
|
|
}
|
|
|
|
TEST_F(GameMsgSplitTests, DeliverLocallyNeverSendsToAClient) {
|
|
GameMessages::DropClientLoot loot;
|
|
loot.target = 0x7777; // no such entity
|
|
const auto sent = PacketTestUtils::Capture([&] { EXPECT_FALSE(GameMessages::DeliverLocally(loot)); });
|
|
EXPECT_TRUE(sent.empty());
|
|
}
|
|
|
|
TEST_F(GameMsgSplitTests, SendToClientNeverBroadcasts) {
|
|
GameMessages::DropClientLoot loot;
|
|
for (const auto& address : { SystemAddress(), UNASSIGNED_SYSTEM_ADDRESS }) {
|
|
const auto sent = PacketTestUtils::Capture([&] { loot.SendToClient(address); });
|
|
ASSERT_EQ(sent.size(), 1);
|
|
EXPECT_FALSE(sent[0].broadcast);
|
|
EXPECT_EQ(sent[0].sysAddr, address);
|
|
}
|
|
const auto broadcast = PacketTestUtils::Capture([&] { loot.Send(UNASSIGNED_SYSTEM_ADDRESS); });
|
|
ASSERT_EQ(broadcast.size(), 1);
|
|
EXPECT_TRUE(broadcast[0].broadcast);
|
|
}
|