Files
DarkflameServer/dGame/dGameMessages/PropertyMessages.h
Aaron Kimbrell b2a1e9e0b8 refactor: remaining game messages as structs, switch removed
Every game message still written or read by hand is now a NetGameMsg with
Serialize and Deserialize, in per-domain files:
- MovementMessages: teleport, platforms (resync and its request), orient to
  angle, node rotation lock, gravity scale, jetpack mode, control scheme,
  respawn checkpoint, rails (set/start, ready, cancel, arrived), mount
  inventory ID, dismount complete, possession ack, ghost reference override
  and position, camera cycling (eCameraTargetCyclingMode moves here).
- ZoneMessages: player loaded (the old PLAYER_LOADED case), ready for
  updates, player ready, restore to post load stats, server done loading,
  invalid zone transfer list, zone summary display/dismissed, level
  processing complete, object world state, and the localized announcement
  WorldMigration wrote by hand.
- PlayerMessages: chat mode, GM level, LEGO score, currency, reputation,
  GM invis, pickup currency, zone and player statistics, chat commands, bug
  reports, verify ack.
- ObjectMessages: fire event client/server side, notify client
  (zone) object, notify object, script network vars, failed preconditions,
  terminate interaction, set name, request use, request server object info.
- QuickBuildMessages: notify state, enable, cancel.
- ActivityMessages gains match response/update/request, leaderboard request
  and data, shooting gallery score/rotation/fire, activity state change.
- MissionMessages gains MissionDialogueCancelled (a no-op, as before).
The wire structs left in GameMessages.h move to their domains (tooltip and
emote to Effects, loot and item use to Inventory, model build to Building,
behavior sound to Property, skill sets to Skill) and gain the missing
direction. The dismount logic moves to PossessorComponent::OnDismountComplete.

Every inbound message is registered in the GameMessageHandler map; the
switch is gone, and GameMessages.cpp only holds the GameMsg/NetGameMsg base
code. Call sites build the structs (NotifyClientObject, TerminateInteraction,
Teleport, PlatformResync, FireEventClientSide, NotifyObject and
NotifyClientZoneObject get convenience constructors like PlayFXEffect).
Dead senders are dropped: SendSetShootingGalleryParams (no callers, field
order was a guess), SendTeamPickupItem (the struct already existed),
SendRequestActivitySummaryLeaderboardData (the struct covers it).

Verified with RemainingMessagesTests: every old Send* function is frozen
verbatim in Legacy/RemainingMessagesLegacy.h and compared byte for byte
(same bits, destination and broadcast flag) over grids of inputs; every old
Handle* read sequence is frozen as a Read* oracle and compared with the
struct's Deserialize; round trips, truncation and a golden packet.
PlayerLoaded (0x00dc36f0), SetGMLevel (0x00dd6230), MissionDialogueCancelled
(0x00d9cc10) and LocalizedAnnouncementServerToSingleClient (0x00f23c50)
were checked against the client. Behaviour notes: an inbound message that
fails to deserialize is dropped, so ParseChatMessage over MAX_MESSAGE_LENGTH
is dropped instead of truncated, and PLAYER_LOADED / READY_FOR_UPDATES /
MISSION_DIALOGUE_CANCELLED now read their (unused) client fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:56 -05:00

408 lines
15 KiB
C++

#ifndef PROPERTYMESSAGES_H
#define PROPERTYMESSAGES_H
#include "GameMessages.h"
#include "NiQuaternion.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
class AMFArrayValue;
// Game messages for properties: renting, the property data, browsing and entering properties, placing and editing
// models, property behaviors, reports and the news screen's top properties.
// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order.
// Messages received from a client hand their work to the property components (PropertyManagementComponent,
// PropertyVendorComponent, PropertyEntranceComponent, MultiZoneEntranceComponent) as the old handlers did.
namespace GameMessages {
// Server -> client. No payload.
struct OpenPropertyVendor : public NetGameMsg {
OpenPropertyVendor() : NetGameMsg(MessageType::Game::OPEN_PROPERTY_VENDOR) {}
};
// Server -> client. No payload. Targets the property management object.
struct OpenPropertyManagement : public NetGameMsg {
OpenPropertyManagement() : NetGameMsg(MessageType::Game::OPEN_PROPERTY_MANAGEMENT) {}
};
// Server -> client. The client reads it with PropertyData::Deserialize (0x00c084f0).
struct DownloadPropertyData : public NetGameMsg {
DownloadPropertyData() : NetGameMsg(MessageType::Game::DOWNLOAD_PROPERTY_DATA) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
// PropertyData::moderationStatus
enum RejectionStatus : uint32_t {
REJECTION_STATUS_APPROVED = 0,
REJECTION_STATUS_PENDING = 1,
REJECTION_STATUS_REJECTED = 2
};
LWOOBJID propertyId{};
int32_t templateId{};
uint16_t mapId{};
uint16_t vendorMapId{};
uint32_t cloneId{};
std::u16string name{};
std::u16string description{};
std::u16string ownerName{};
LWOOBJID ownerId{};
uint32_t propertyType{};
uint32_t zoneCode{};
uint32_t rent{};
uint32_t rentalPeriod{};
uint64_t expirationDate{};
uint32_t rentAmount{};
uint64_t reputation{};
std::u16string spawnName{};
std::u16string templateName{};
std::u16string templateDescription{};
uint32_t rentDuration{};
uint32_t votes{};
uint32_t durationType{};
uint8_t renew{};
LWOOBJID ownerAccountID{};
uint32_t moderationStatus{};
std::u16string rejectionReason{};
uint64_t lastLogoutTime{};
uint32_t dayOfMonthPlaqueWasBought{};
uint32_t repAchievementReq{};
NiPoint3 zonePosition{};
float maxBuildHeight{};
uint64_t rentalDate{};
uint8_t accessType{};
std::vector<NiPoint3> pathPositions{}; // u32 count, then the points
};
// Server -> client.
struct PropertyRentalResponse : public NetGameMsg {
PropertyRentalResponse() : NetGameMsg(MessageType::Game::PROPERTY_RENTAL_RESPONSE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
LWOCLONEID cloneid{};
uint32_t code{};
LWOOBJID propertyID{};
int64_t rentdue{};
};
// Server -> client. No payload. Opens the property browser of a property launch pad.
struct PropertyEntranceBegin : public NetGameMsg {
PropertyEntranceBegin() : NetGameMsg(MessageType::Game::PROPERTY_ENTRANCE_BEGIN) {}
};
// Server -> client. One page of the property browser.
struct PropertySelectQuery : public NetGameMsg {
PropertySelectQuery() : NetGameMsg(MessageType::Game::PROPERTY_SELECT_QUERY) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
// One property, as the client's PropertyInfo.
struct PropertyInfo {
LWOCLONEID cloneId{ LWOCLONEID_INVALID };
std::u16string ownerName{};
std::u16string name{};
std::u16string description{};
float reputation{};
bool isBff{};
bool isFriend{};
bool isModApproved{};
bool isAlt{};
bool isOwned{};
uint32_t accessType{};
uint64_t dateLastPublished{};
float performanceCost{};
};
int32_t navOffset{};
bool thereAreMore{};
int32_t cloneId{};
bool hasFeaturedProperty{};
bool wasFriends{};
std::vector<PropertyInfo> properties{}; // u32 count, then the entries
};
// Server -> client. Every model on the property: pairs of (model object, model item).
struct GetModelsOnProperty : public NetGameMsg {
GetModelsOnProperty() : NetGameMsg(MessageType::Game::GET_MODELS_ON_PROPERTY) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
std::vector<std::pair<LWOOBJID, LWOOBJID>> models{}; // u32 count, then both IDs of each pair
};
// Server -> client.
// WIRE BUG (kept): DLU writes rotation's flag, but then writes response (4 bytes) instead of the quaternion
// (16 bytes) that the client's PlaceModelResponse::Deserialize (0x00dc0170) reads. Deserialize reads the
// client's layout, so a message with a non-identity rotation does not round trip.
struct PlaceModelResponse : public NetGameMsg {
PlaceModelResponse() : NetGameMsg(MessageType::Game::PLACE_MODEL_RESPONSE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
NiPoint3 position{ NiPoint3Constant::ZERO }; // optional
LWOOBJID propertyPlaqueID{ LWOOBJID_EMPTY }; // optional
int32_t response{}; // optional
NiQuaternion rotation{ QuatUtils::IDENTITY }; // optional
};
// Server -> client. Sent with id HANDLE_UGC_POST_CREATE_BASED_ON_EDIT_MODE (1301), which the client names
// HandleUGCEquipPreCreateBasedOnEditMode.
struct HandleUGCEquipPreCreateBasedOnEditMode : public NetGameMsg {
HandleUGCEquipPreCreateBasedOnEditMode() : NetGameMsg(MessageType::Game::HANDLE_UGC_POST_CREATE_BASED_ON_EDIT_MODE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
int32_t modelCount{};
LWOOBJID modelID{};
};
// Server -> client.
struct HandleUGCEquipPostDeleteBasedOnEditMode : public NetGameMsg {
HandleUGCEquipPostDeleteBasedOnEditMode() : NetGameMsg(MessageType::Game::HANDLE_UGC_POST_DELETE_BASED_ON_EDIT_MODE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
LWOOBJID invItem{};
int32_t itemsTotal{}; // optional
};
// Client -> server.
struct SetPropertyAccess : public NetGameMsg {
SetPropertyAccess() : NetGameMsg(MessageType::Game::SET_PROPERTY_ACCESS) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
uint8_t accessType{}; // optional
int32_t renew{}; // optional
};
// Client -> server. A new name and description for the property.
struct UpdatePropertyOrModelForFilterCheck : public NetGameMsg {
UpdatePropertyOrModelForFilterCheck() : NetGameMsg(MessageType::Game::UPDATE_PROPERTY_OR_MODEL_FOR_FILTER_CHECK) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool isProperty{};
LWOOBJID ugcId{};
LWOOBJID playerId{};
LWOOBJID worldId{};
std::u16string newDescription{};
std::u16string newName{};
};
// Client -> server. No payload.
struct QueryPropertyData : public NetGameMsg {
QueryPropertyData() : NetGameMsg(MessageType::Game::QUERY_PROPERTY_DATA) {}
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
// Client -> server.
struct PropertyEditorBegin : public NetGameMsg {
PropertyEditorBegin() : NetGameMsg(MessageType::Game::PROPERTY_EDITOR_BEGIN) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
int32_t distanceType{}; // optional
LWOOBJID propertyObjectID{ LWOOBJID_EMPTY }; // optional
int32_t startMode{ 1 }; // optional
bool startPaused{};
};
// Client -> server. No payload.
struct PropertyEditorEnd : public NetGameMsg {
PropertyEditorEnd() : NetGameMsg(MessageType::Game::PROPERTY_EDITOR_END) {}
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
// Client -> server.
struct PropertyContentsFromClient : public NetGameMsg {
PropertyContentsFromClient() : NetGameMsg(MessageType::Game::PROPERTY_CONTENTS_FROM_CLIENT) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool queryDB{};
};
// Client -> server (DLU never sends it).
struct ZonePropertyModelEquipped : public NetGameMsg {
ZonePropertyModelEquipped() : NetGameMsg(MessageType::Game::ZONE_PROPERTY_MODEL_EQUIPPED) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
LWOOBJID playerID{ LWOOBJID_EMPTY }; // optional
LWOOBJID propertyID{ LWOOBJID_EMPTY }; // optional
};
// Client -> server.
struct ZonePropertyModelRotated : public NetGameMsg {
ZonePropertyModelRotated() : NetGameMsg(MessageType::Game::ZONE_PROPERTY_MODEL_ROTATED) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
LWOOBJID playerID{ LWOOBJID_EMPTY }; // optional
LWOOBJID propertyID{ LWOOBJID_EMPTY }; // optional
};
// Client -> server.
struct PlacePropertyModel : public NetGameMsg {
PlacePropertyModel() : NetGameMsg(MessageType::Game::PLACE_PROPERTY_MODEL) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
LWOOBJID modelID{};
};
// Client -> server.
struct UpdateModelFromClient : public NetGameMsg {
UpdateModelFromClient() : NetGameMsg(MessageType::Game::UPDATE_MODEL_FROM_CLIENT) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
LWOOBJID modelID{};
NiPoint3 position{};
NiQuaternion rotation{ QuatUtils::IDENTITY }; // optional
};
// Client -> server.
struct DeleteModelFromClient : public NetGameMsg {
DeleteModelFromClient() : NetGameMsg(MessageType::Game::DELETE_MODEL_FROM_CLIENT) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
LWOOBJID modelID{ LWOOBJID_EMPTY }; // optional
int32_t reason{}; // optional
};
// Client -> server. Asks a property launch pad for a page of properties.
struct PropertyEntranceSync : public NetGameMsg {
PropertyEntranceSync() : NetGameMsg(MessageType::Game::PROPERTY_ENTRANCE_SYNC) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
bool includeNullAddress{};
bool includeNullDescription{};
bool playersOwn{};
bool updateUI{};
int32_t numResults{};
int32_t reputationTime{};
int32_t sortMethod{};
int32_t startIndex{};
std::string filterText{};
};
// Client -> server. The player picked a property (or a world, on a multi zone entrance).
struct EnterProperty1 : public NetGameMsg {
EnterProperty1() : NetGameMsg(MessageType::Game::ENTER_PROPERTY1) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
int32_t index{};
bool returnToZone{ true };
};
// Client -> server.
struct UpdatePropertyPerformanceCost : public NetGameMsg {
UpdatePropertyPerformanceCost() : NetGameMsg(MessageType::Game::UPDATE_PROPERTY_PERFORMANCE_COST) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
float performanceCost{}; // optional
};
// Client -> server.
struct ReportOffensiveModel : public NetGameMsg {
ReportOffensiveModel() : NetGameMsg(MessageType::Game::REPORT_OFFENSIVE_MODEL) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
std::u16string description{};
LWOOBJID offensiveObjectID{};
};
// Client -> server.
struct ReportOffensiveProperty : public NetGameMsg {
ReportOffensiveProperty() : NetGameMsg(MessageType::Game::REPORT_OFFENSIVE_PROPERTY) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
std::u16string description{};
LWOOBJID propertyPlaqueObjectID{};
};
/**
* Client -> server. Every property behavior (model behavior editor) command.
* args is an AMF3 array; for AMF3 see https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf
*/
struct ControlBehaviors : public NetGameMsg {
ControlBehaviors();
~ControlBehaviors() override;
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
std::unique_ptr<AMFArrayValue> args{};
std::string command{};
};
// Client -> server. No payload. The news screen asks for today's top properties.
struct GetHotPropertyData : public NetGameMsg {
GetHotPropertyData() : NetGameMsg(MessageType::Game::GET_HOT_PROPERTY_DATA) {}
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
/**
* Server -> client. The news screen's "Today's Top Properties" (GM 1510), the answer to GetHotPropertyData (GM 1511).
* Client: GameMessage::NewsSendHotPropertiesInfoToClient, entries read by NewsHotPropertyInfo::Deserialize
* (0x00c0cc20 in 1.10.64) and shown by LWOCharacterComponent::HotPropertyData (0x00cf83f0). See HotPropertySlots.h.
*/
struct NewsSendHotPropertiesInfoToClient : public NetGameMsg {
NewsSendHotPropertiesInfoToClient() : NetGameMsg(MessageType::Game::SEND_HOT_PROPERTY_DATA) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
struct HotPropertyInfo {
LWOOBJID propertyId{};
LWOOBJID ownerId{};
std::u16string ownerName;
uint64_t reputation{};
int32_t templateId{}; // PropertyTemplate id: picks the news screen slot
std::u16string name;
std::u16string description;
float performanceCost{};
uint64_t lastPublished{}; // unix time
uint32_t cloneId{};
};
std::vector<HotPropertyInfo> properties;
};
struct PlayBehaviorSound : public NetGameMsg {
PlayBehaviorSound() : NetGameMsg(MessageType::Game::PLAY_BEHAVIOR_SOUND) {}
void Serialize(RakNet::BitStream& stream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
int32_t soundID{ -1 };
};
};
#endif // PROPERTYMESSAGES_H