chore: foundations for struct based packets and game messages

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>
This commit is contained in:
Aaron Kimbrell
2026-09-26 10:25:27 -05:00
parent 6f3a8d502f
commit 5f60900934
27 changed files with 2796 additions and 81 deletions

View File

@@ -2222,7 +2222,8 @@ void Entity::RegisterMsg(const MessageType::Game msgId, std::function<bool(GameM
m_MsgHandlers.emplace(msgId, handler);
}
bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& requestInfo) {
bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfoEvent& event) {
const auto& requestInfo = event.msg;
AMFArrayValue response;
response.Insert("visible", true);
response.Insert("objectID", std::to_string(m_ObjectID));
@@ -2264,7 +2265,8 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& r
return true;
}
bool Entity::MsgDropClientLoot(GameMessages::DropClientLoot& dropLootMsg) {
bool Entity::MsgDropClientLoot(GameMessages::DropClientLootEvent& event) {
const auto& dropLootMsg = event.msg;
if (dropLootMsg.item != LOT_NULL && dropLootMsg.item != 0) {
Loot::Info info{
.id = dropLootMsg.lootID,
@@ -2308,7 +2310,8 @@ bool Entity::MsgGetFactionTokenType(GameMessages::GetFactionTokenType& tokenMsg)
return tokenMsg.tokenType != LOT_NULL;
}
bool Entity::MsgPickupItem(GameMessages::PickupItem& pickupItemMsg) {
bool Entity::MsgPickupItem(GameMessages::PickupItemEvent& event) {
const auto& pickupItemMsg = event.msg;
if (GetObjectID() == pickupItemMsg.lootOwnerID) {
PickupItem(pickupItemMsg.lootID);
} else {

View File

@@ -16,6 +16,7 @@
namespace GameMessages {
struct GameMsg;
template<typename Msg> struct NetGameMsgEvent;
struct ActivityNotify;
struct ShootingGalleryFire;
struct ChildLoaded;
@@ -182,11 +183,11 @@ public:
void AddComponent(eReplicaComponentType componentId, Component* component);
bool MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& msg);
bool MsgDropClientLoot(GameMessages::DropClientLoot& msg);
bool MsgRequestServerObjectInfo(GameMessages::NetGameMsgEvent<GameMessages::RequestServerObjectInfo>& event);
bool MsgDropClientLoot(GameMessages::NetGameMsgEvent<GameMessages::DropClientLoot>& event);
bool MsgGetFlag(GameMessages::GetFlag& msg);
bool MsgGetFactionTokenType(GameMessages::GetFactionTokenType& msg);
bool MsgPickupItem(GameMessages::PickupItem& msg);
bool MsgPickupItem(GameMessages::NetGameMsgEvent<GameMessages::PickupItem>& event);
bool MsgChildRemoved(GameMessages::ChildRemoved& msg);
// This is expceted to never return nullptr, an assert checks this.

View File

@@ -783,7 +783,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
lootMsg.spawnPos = m_Parent->GetPosition();
lootMsg.sourceID = source;
lootMsg.item = LOT_NULL;
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
character->SetCoins(coinsTotal, eLootSourceType::DELETION);
}
}
@@ -1022,17 +1022,17 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
lootMsg.spawnPos = m_Parent->GetPosition();
lootMsg.sourceID = source;
lootMsg.item = LOT_NULL;
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
lootMsg.Send(m_Parent->GetSystemAddress());
while (coinsToDrop > MAX_TO_DROP_PER_GM) {
LOG("Dropping 100,000, %llu left", coinsToDrop);
lootMsg.currency = 100'000;
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
lootMsg.Send(m_Parent->GetSystemAddress());
coinsToDrop -= 100'000;
}
lootMsg.currency = coinsToDrop;
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
lootMsg.Send(m_Parent->GetSystemAddress());
}
return;

View File

@@ -68,7 +68,9 @@ void GhostComponent::GhostEntity(LWOOBJID id) {
m_ObservedEntities.erase(id);
}
bool GhostComponent::OnToggleGMInvis(GameMessages::ToggleGMInvis& gmInvisMsg) {
bool GhostComponent::OnToggleGMInvis(GameMessages::ToggleGMInvisEvent& event) {
auto& gmInvisMsg = event.msg;
gmInvisMsg.target = event.target; // the wire message goes out with the target the event was delivered to
gmInvisMsg.bStateOut = !m_IsGMInvisible;
m_IsGMInvisible = !m_IsGMInvisible;
LOG_DEBUG("GM Invisibility toggled to: %s", m_IsGMInvisible ? "true" : "false");

View File

@@ -43,7 +43,7 @@ public:
void GhostEntity(const LWOOBJID id);
bool OnToggleGMInvis(GameMessages::ToggleGMInvis& msg);
bool OnToggleGMInvis(GameMessages::ToggleGMInvisEvent& event);
bool OnGetGMInvis(GameMessages::GetGMInvis& msg);

View File

@@ -59,7 +59,8 @@ bool ModelComponent::OnResetModelToDefaults(GameMessages::ResetModelToDefaults&
return true;
}
bool ModelComponent::OnRequestUse(GameMessages::RequestUse& requestUse) {
bool ModelComponent::OnRequestUse(GameMessages::RequestUseEvent& event) {
auto& requestUse = event.msg;
bool toReturn = false;
if (!m_IsPaused) {
for (auto& behavior : m_Behaviors) behavior.HandleMsg(requestUse);

View File

@@ -32,7 +32,7 @@ public:
void LoadBehaviors();
void Update(float deltaTime) override;
bool OnRequestUse(GameMessages::RequestUse& requestUse);
bool OnRequestUse(GameMessages::RequestUseEvent& event);
bool OnResetModelToDefaults(GameMessages::ResetModelToDefaults& resetModelToDefaults);
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);

View File

@@ -43,7 +43,7 @@
namespace {
using enum MessageType::Game;
using namespace GameMessages;
using MessageCreator = std::function<std::unique_ptr<GameMessages::GameMsg>()>;
using MessageCreator = std::function<std::unique_ptr<GameMessages::NetGameMsg>()>;
std::map<MessageType::Game, MessageCreator> g_MessageHandlers = {
{ REQUEST_USE, []() { return std::make_unique<RequestUse>(); }},
{ REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique<RequestServerObjectInfo>(); } },
@@ -87,7 +87,10 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
}
}
msg->Deserialize(inStream);
if (!msg->Deserialize(inStream)) {
LOG("Dropping malformed GM %4i, %s from (%llu) targeting (%llu)", messageID, StringifiedEnum::ToString(messageID).data(), usr->GetLoggedInChar(), objectID);
return;
}
msg->Handle(*entity, sysAddr);
return;
}

View File

@@ -6285,14 +6285,24 @@ namespace GameMessages {
return Send();
}
void GameMsg::Send(const SystemAddress& sysAddr) const {
CBITSTREAM;
void NetGameMsg::WritePacket(RakNet::BitStream& bitStream) const {
CMSGHEADER;
bitStream.Write(target); // Who this message will be sent to on the (a) client
bitStream.Write(msgId); // the ID of this message
Serialize(bitStream); // write the message data
}
void NetGameMsg::SendToClient(const SystemAddress& sysAddr) const {
CBITSTREAM;
WritePacket(bitStream);
SEND_PACKET;
}
void NetGameMsg::Send(const SystemAddress& sysAddr) const {
CBITSTREAM;
WritePacket(bitStream);
// Send to everyone if someone sent unassigned system address, or to one specific client.
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
@@ -6378,8 +6388,10 @@ namespace GameMessages {
void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
if (handlingEntity) handlingEntity->HandleMsg(*this);
else LOG("Failed to find target %llu", targetForReport);
if (handlingEntity) {
RequestServerObjectInfoEvent event(*this);
handlingEntity->HandleMsg(event);
} else LOG("Failed to find target %llu", targetForReport);
}
bool RequestUse::Deserialize(RakNet::BitStream& stream) {
@@ -6418,7 +6430,8 @@ namespace GameMessages {
interactedObject->OnUse(&entity);
}
interactedObject->HandleMsg(*this);
RequestUseEvent event(*this);
interactedObject->HandleMsg(event);
//Perform use task if possible:
auto missionComponent = entity.GetComponent<MissionComponent>();
@@ -6480,7 +6493,8 @@ namespace GameMessages {
LOG("Has team %i picking up %llu:%llu", team != nullptr, lootID, lootOwnerID);
if (team) {
for (const auto memberId : team->members) {
this->Send(memberId);
PickupItemEvent event(*this);
event.Send(memberId);
TeamPickupItem teamPickupMsg{};
teamPickupMsg.target = lootID;
teamPickupMsg.lootID = lootID;

View File

@@ -3,6 +3,7 @@
#include "dCommonVars.h"
#include <map>
#include <type_traits>
#include <string>
#include <vector>
#include "eMovementPlatformState.h"
@@ -53,26 +54,78 @@ enum class eCameraTargetCyclingMode : int32_t {
};
namespace GameMessages {
/**
* A server-internal event, delivered only to the handlers entities/components/scripts registered with
* RegisterMsg. It never goes on the wire: it has no Serialize and no way to send it to a client.
*/
struct GameMsg {
GameMsg(MessageType::Game gmId, eGameMasterLevel lvl) : msgId{ gmId }, requiredGmLevel{ lvl } {}
GameMsg(MessageType::Game gmId) : GameMsg(gmId, eGameMasterLevel::CIVILIAN) {}
GameMsg(MessageType::Game gmId) : msgId{ gmId } {}
virtual ~GameMsg() = default;
// Sends a message to the entity manager to route to the target
// Delivers the message to the handlers registered on the target entity.
bool Send();
bool Send(const LWOOBJID _target);
MessageType::Game msgId;
LWOOBJID target{ LWOOBJID_EMPTY };
};
/**
* A game message that goes on the wire, to (Serialize) or from (Deserialize + Handle) a client.
* It is not a GameMsg, so it cannot be delivered to local handlers by mistake. When local handlers need to
* observe a network message, wrap it in a NetGameMsgEvent.
*/
struct NetGameMsg {
NetGameMsg(MessageType::Game gmId, eGameMasterLevel lvl) : msgId{ gmId }, requiredGmLevel{ lvl } {}
NetGameMsg(MessageType::Game gmId) : NetGameMsg(gmId, eGameMasterLevel::CIVILIAN) {}
virtual ~NetGameMsg() = default;
// Sends the message to the specified client or
// all clients if UNASSIGNED_SYSTEM_ADDRESS is specified
void Send(const SystemAddress& sysAddr) const;
// Sends the message to the specified client only and never broadcasts. Given UNASSIGNED_SYSTEM_ADDRESS
// nothing is delivered (RakNet rejects a non-broadcast send without an address).
void SendToClient(const SystemAddress& sysAddr) const;
// Writes the complete client packet (CLIENT/GAME_MSG header, target, msgId, then Serialize()) into bitStream.
// This is exactly what Send(sysAddr) puts on the wire; tests use it to compare bytes without a server.
void WritePacket(RakNet::BitStream& bitStream) const;
virtual void Serialize(RakNet::BitStream& bitStream) const {}
virtual bool Deserialize(RakNet::BitStream& bitStream) { return true; }
// Called for messages received from a client, after a successful Deserialize.
virtual void Handle(Entity& entity, const SystemAddress& sysAddr) {};
MessageType::Game msgId;
LWOOBJID target{ LWOOBJID_EMPTY };
// Minimum GM level the sending client needs for Handle to be called.
eGameMasterLevel requiredGmLevel;
};
/**
* A server-internal event carrying a network message, so entity/component handlers can react to a message
* received from (or about to be sent to) a client. Uses the network message's msgId for local dispatch.
*/
template<typename Msg>
struct NetGameMsgEvent : public GameMsg {
static_assert(std::is_base_of_v<NetGameMsg, Msg>, "NetGameMsgEvent carries a NetGameMsg");
NetGameMsgEvent() : GameMsg(Msg{}.msgId) {}
NetGameMsgEvent(const Msg& message) : GameMsg(message.msgId), msg{ message } { target = message.target; }
Msg msg;
};
// Delivers a copy of a network message to the local handlers registered on msg.target (never to a client).
// Returns whether a handler handled it. Handlers see (and may modify) only the copy.
template<typename Msg>
bool DeliverLocally(const Msg& msg) {
NetGameMsgEvent<Msg> event(msg);
return event.Send();
}
class PropertyDataMessage;
void SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender);
void SendTeleport(const LWOOBJID& objectID, const NiPoint3& pos, const NiQuaternion& rot, const SystemAddress& sysAddr, bool bSetRotation = false);
@@ -705,8 +758,8 @@ namespace GameMessages {
// This is a client gm however its default values are exactly what we need to get around the invisible inventory item issues.
void SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr);
struct DisplayTooltip : public GameMsg {
DisplayTooltip() : GameMsg(MessageType::Game::DISPLAY_TOOLTIP) {}
struct DisplayTooltip : public NetGameMsg {
DisplayTooltip() : NetGameMsg(MessageType::Game::DISPLAY_TOOLTIP) {}
bool doOrDie{};
bool noRepeat{};
bool noRevive{};
@@ -721,8 +774,8 @@ namespace GameMessages {
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct UseItemOnClient : public GameMsg {
UseItemOnClient() : GameMsg(MessageType::Game::USE_ITEM_ON_CLIENT) {}
struct UseItemOnClient : public NetGameMsg {
UseItemOnClient() : NetGameMsg(MessageType::Game::USE_ITEM_ON_CLIENT) {}
LWOOBJID playerId{};
LWOOBJID itemToUse{};
uint32_t itemType{};
@@ -741,14 +794,14 @@ namespace GameMessages {
LwoNameValue racingSettings{};
};
struct SetModelToBuild : public GameMsg {
SetModelToBuild() : GameMsg(MessageType::Game::SET_MODEL_TO_BUILD) {}
struct SetModelToBuild : public NetGameMsg {
SetModelToBuild() : NetGameMsg(MessageType::Game::SET_MODEL_TO_BUILD) {}
void Serialize(RakNet::BitStream& bitStream) const override;
LOT modelLot{ -1 };
};
struct SpawnModelBricks : public GameMsg {
SpawnModelBricks() : GameMsg(MessageType::Game::SPAWN_MODEL_BRICKS) {}
struct SpawnModelBricks : public NetGameMsg {
SpawnModelBricks() : NetGameMsg(MessageType::Game::SPAWN_MODEL_BRICKS) {}
void Serialize(RakNet::BitStream& bitStream) const override;
float amount{ 0.0f };
@@ -761,8 +814,8 @@ namespace GameMessages {
LwoNameValue notification{};
};
struct ShootingGalleryFire : public GameMsg {
ShootingGalleryFire() : GameMsg(MessageType::Game::SHOOTING_GALLERY_FIRE) {}
struct ShootingGalleryFire : public NetGameMsg {
ShootingGalleryFire() : NetGameMsg(MessageType::Game::SHOOTING_GALLERY_FIRE) {}
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
@@ -781,15 +834,16 @@ namespace GameMessages {
PlayerResurrectionFinished() : GameMsg(MessageType::Game::PLAYER_RESURRECTION_FINISHED) {}
};
struct RequestServerObjectInfo : public GameMsg {
struct RequestServerObjectInfo : public NetGameMsg {
bool bVerbose{};
LWOOBJID clientId{};
LWOOBJID targetForReport{};
RequestServerObjectInfo() : GameMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, eGameMasterLevel::DEVELOPER) {}
RequestServerObjectInfo() : NetGameMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, eGameMasterLevel::DEVELOPER) {}
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
using RequestServerObjectInfoEvent = NetGameMsgEvent<RequestServerObjectInfo>;
struct GetObjectReportInfo : public GameMsg {
AMFArrayValue* info{};
@@ -797,11 +851,11 @@ namespace GameMessages {
bool bVerbose{};
LWOOBJID clientID{};
GetObjectReportInfo() : GameMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, eGameMasterLevel::DEVELOPER) {}
GetObjectReportInfo() : GameMsg(MessageType::Game::GET_OBJECT_REPORT_INFO) {}
};
struct RequestUse : public GameMsg {
RequestUse() : GameMsg(MessageType::Game::REQUEST_USE) {}
struct RequestUse : public NetGameMsg {
RequestUse() : NetGameMsg(MessageType::Game::REQUEST_USE) {}
bool Deserialize(RakNet::BitStream& stream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
@@ -819,11 +873,12 @@ namespace GameMessages {
// Used only for multi-interaction, is of the enum type InteractionType
int multiInteractType{};
};
using RequestUseEvent = NetGameMsgEvent<RequestUse>;
struct Smash : public GameMsg {
Smash() : GameMsg(MessageType::Game::SMASH) {}
struct Smash : public NetGameMsg {
Smash() : NetGameMsg(MessageType::Game::SMASH) {}
void Serialize(RakNet::BitStream& stream) const;
void Serialize(RakNet::BitStream& stream) const override;
bool bIgnoreObjectVisibility{};
bool force{};
@@ -831,19 +886,19 @@ namespace GameMessages {
LWOOBJID killerID{};
};
struct UnSmash : public GameMsg {
UnSmash() : GameMsg(MessageType::Game::UN_SMASH) {}
struct UnSmash : public NetGameMsg {
UnSmash() : NetGameMsg(MessageType::Game::UN_SMASH) {}
void Serialize(RakNet::BitStream& stream) const;
void Serialize(RakNet::BitStream& stream) const override;
LWOOBJID builderID{ LWOOBJID_EMPTY };
float duration{ 3.0f };
};
struct PlayBehaviorSound : public GameMsg {
PlayBehaviorSound() : GameMsg(MessageType::Game::PLAY_BEHAVIOR_SOUND) {}
struct PlayBehaviorSound : public NetGameMsg {
PlayBehaviorSound() : NetGameMsg(MessageType::Game::PLAY_BEHAVIOR_SOUND) {}
void Serialize(RakNet::BitStream& stream) const;
void Serialize(RakNet::BitStream& stream) const override;
int32_t soundID{ -1 };
};
@@ -857,8 +912,8 @@ namespace GameMessages {
bool bResetBehaviors{ true };
};
struct EmotePlayed : public GameMsg {
EmotePlayed() : GameMsg(MessageType::Game::EMOTE_PLAYED), emoteID(0), targetID(0) {}
struct EmotePlayed : public NetGameMsg {
EmotePlayed() : NetGameMsg(MessageType::Game::EMOTE_PLAYED), emoteID(0), targetID(0) {}
void Serialize(RakNet::BitStream& stream) const override;
@@ -880,8 +935,8 @@ namespace GameMessages {
bool bIgnoreChecks{ false };
};
struct DropClientLoot : public GameMsg {
DropClientLoot() : GameMsg(MessageType::Game::DROP_CLIENT_LOOT) {}
struct DropClientLoot : public NetGameMsg {
DropClientLoot() : NetGameMsg(MessageType::Game::DROP_CLIENT_LOOT) {}
void Serialize(RakNet::BitStream& stream) const override;
LWOOBJID sourceID{ LWOOBJID_EMPTY };
@@ -894,6 +949,7 @@ namespace GameMessages {
LWOOBJID lootID{ LWOOBJID_EMPTY };
LWOOBJID ownerID{ LWOOBJID_EMPTY };
};
using DropClientLootEvent = NetGameMsgEvent<DropClientLoot>;
struct GetMissionState : public GameMsg {
GetMissionState() : GameMsg(MessageType::Game::GET_MISSION_STATE) {}
@@ -923,17 +979,18 @@ namespace GameMessages {
LOT item{};
};
struct PickupItem : public GameMsg {
PickupItem() : GameMsg(MessageType::Game::PICKUP_ITEM) {}
struct PickupItem : public NetGameMsg {
PickupItem() : NetGameMsg(MessageType::Game::PICKUP_ITEM) {}
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool Deserialize(RakNet::BitStream& stream) override;
LWOOBJID lootID{};
LWOOBJID lootOwnerID{};
};
using PickupItemEvent = NetGameMsgEvent<PickupItem>;
struct TeamPickupItem : public GameMsg {
TeamPickupItem() : GameMsg(MessageType::Game::TEAM_PICKUP_ITEM) {}
struct TeamPickupItem : public NetGameMsg {
TeamPickupItem() : NetGameMsg(MessageType::Game::TEAM_PICKUP_ITEM) {}
void Serialize(RakNet::BitStream& stream) const override;
LWOOBJID lootID{};
@@ -946,13 +1003,14 @@ namespace GameMessages {
bool bDead{};
};
struct ToggleGMInvis : public GameMsg {
ToggleGMInvis() : GameMsg(MessageType::Game::TOGGLE_GM_INVIS) {}
struct ToggleGMInvis : public NetGameMsg {
ToggleGMInvis() : NetGameMsg(MessageType::Game::TOGGLE_GM_INVIS) {}
void Serialize(RakNet::BitStream& stream) const override;
bool bStateOut{ false };
};
using ToggleGMInvisEvent = NetGameMsgEvent<ToggleGMInvis>;
struct GetGMInvis : public GameMsg {
GetGMInvis() : GameMsg(MessageType::Game::GET_GM_INVIS) {}
@@ -966,8 +1024,8 @@ namespace GameMessages {
LWOOBJID childID{};
};
struct UseSkillSet : public GameMsg {
UseSkillSet() : GameMsg(MessageType::Game::USE_SKILL_SET) {}
struct UseSkillSet : public NetGameMsg {
UseSkillSet() : NetGameMsg(MessageType::Game::USE_SKILL_SET) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool bRemove{};

View File

@@ -136,7 +136,7 @@ void DropFactionLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) {
lootMsg.lootID = ObjectIDManager::GenerateObjectID();
CalcFinalDropPos(lootMsg);
// Register the drop on the player
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
// Visually drop it for the player
lootMsg.Send(player.GetSystemAddress());
}
@@ -165,7 +165,7 @@ void DropFactionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg, co
lootMsg.lootID = ObjectIDManager::GenerateObjectID();
CalcFinalDropPos(lootMsg);
// Register the drop on this team member
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
// Show the rewards on all connected members of the team. Only the loot owner will be able to pick the tokens up.
DistrbuteMsgToTeam(lootMsg, team);
}
@@ -184,7 +184,7 @@ void DropPowerupLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) {
lootMsg.target = playerID;
// Register the drop on the player
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
// Visually drop it for the player
lootMsg.Send(player.GetSystemAddress());
}
@@ -210,7 +210,7 @@ void DropPowerupLoot(const Team& team, GameMessages::DropClientLoot& lootMsg, co
lootMsg.target = member;
// By sending this message with the same ID to all players on the team, all players on the team are allowed to pick it up.
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
// No need to send to all members in a loop since that will happen by using the outer loop above and also since there is no owner
// sending to all will do nothing.
const auto* const memberEntity = Game::entityManager->GetEntity(member);
@@ -232,7 +232,7 @@ void DropMissionLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) {
lootMsg.lootID = ObjectIDManager::GenerateObjectID();
CalcFinalDropPos(lootMsg);
// Register the drop with the player
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
// Visually drop the loot to be picked up
lootMsg.Send(player.GetSystemAddress());
}
@@ -261,7 +261,7 @@ void DropMissionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg, co
lootMsg.lootID = ObjectIDManager::GenerateObjectID();
CalcFinalDropPos(lootMsg);
// Register the drop with the player
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
DistrbuteMsgToTeam(lootMsg, team);
}
}
@@ -279,7 +279,7 @@ void DropRegularLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) {
lootMsg.target = playerID;
lootMsg.ownerID = playerID;
// Register the drop with the player
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
// Visually drop the loot to be picked up
lootMsg.Send(player.GetSystemAddress());
@@ -309,14 +309,14 @@ void DropRegularLoot(Team& team, GameMessages::DropClientLoot& lootMsg, const bo
if (team.lootOption == 0 /* Shared loot */) {
lootMsg.target = earningPlayer;
lootMsg.ownerID = earningPlayer;
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
} else /* Free for all loot */ {
lootMsg.ownerID = LWOOBJID_EMPTY;
// By sending the loot with NO owner and to ALL members of the team,
// its a first come, first serve with who picks the item up.
for (const auto ffaMember : team.members) {
lootMsg.target = ffaMember;
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
}
}
@@ -405,7 +405,7 @@ void DropLoot(Entity* player, const LWOOBJID source, const std::map<LOT, LootDro
lootMsg.sourceID = source;
lootMsg.item = LOT_NULL;
CalcFinalDropPos(lootMsg);
lootMsg.Send();
GameMessages::DeliverLocally(lootMsg);
const auto* const memberEntity = Game::entityManager->GetEntity(member);
if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress());
}

View File

@@ -91,7 +91,7 @@ namespace DEVGMCommands {
GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN);
entity->SetGMLevel(eGameMasterLevel::CIVILIAN);
GameMessages::ToggleGMInvis msg;
GameMessages::ToggleGMInvisEvent msg;
msg.Send(entity->GetObjectID());
GameMessages::SendSlashCommandFeedbackText(entity, u"Your game master level has been changed, you may not be able to use all commands.");
@@ -1593,7 +1593,8 @@ namespace DEVGMCommands {
objectInfo.target = closest->GetObjectID();
objectInfo.targetForReport = closest->GetObjectID();
objectInfo.clientId = entity->GetObjectID();
closest->HandleMsg(objectInfo);
GameMessages::RequestServerObjectInfoEvent objectInfoEvent(objectInfo);
closest->HandleMsg(objectInfoEvent);
Game::entityManager->SerializeEntity(closest);

View File

@@ -275,7 +275,7 @@ namespace GMGreaterThanZeroCommands {
}
void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
GameMessages::ToggleGMInvis msg;
GameMessages::ToggleGMInvisEvent msg;
msg.Send(entity->GetObjectID());
}