mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
A placed model's client (1.10.64, UGCUSE3DSERVICES=7:0) always loads its blueprint's NIF and HKX (its BlueprintComponent sets renderUserGen and physicsUserGen itself) and its LXFML, asks the world for each file's manifest first and waits for the answer with no timeout. With ugc_manifest=1 and the new ugc_manifest_models=1 (default 0) the world: - leaves the models whose mesh the UGC server made out of the LXFML it sends when a property loads, - answers their NIF with the UGC server's checksum, their LXFML with the stored LXFML's (worked out once and kept) and their HKX as not known, - sends a model that isn't made its LXFML (once for the three requests), so the client builds it itself and no model is left waiting. When the UGC server writes a model's mesh with a new checksum it sends UGC_MODELS_MADE (a new master message, appended) to the master, which passes it to every world; a world with that model placed sends its players the new NIF checksum and NotifyClientUGCModelReady (game message 909, the blueprint id), so clients switch to the served mesh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
340 lines
14 KiB
C++
340 lines
14 KiB
C++
#ifndef BUILDINGMESSAGES_H
|
|
#define BUILDINGMESSAGES_H
|
|
|
|
#include "GameMessages.h"
|
|
|
|
#include "NiQuaternion.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// Game messages for building: build mode, arranging models on a property or in a modular build area, modular builds
|
|
// (rockets and cars) and brick by brick building.
|
|
// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order.
|
|
namespace GameMessages {
|
|
// Client -> server. The player picked something to build with.
|
|
struct StartBuildingWithItem : public NetGameMsg {
|
|
StartBuildingWithItem() : NetGameMsg(MessageType::Game::START_BUILDING_WITH_ITEM) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
bool firstTime{ true };
|
|
bool success{};
|
|
int32_t sourceBag{};
|
|
LWOOBJID sourceId{};
|
|
LOT sourceLot{};
|
|
int32_t sourceType{};
|
|
LWOOBJID targetId{};
|
|
LOT targetLot{};
|
|
NiPoint3 targetPos{};
|
|
int32_t targetType{};
|
|
};
|
|
|
|
// Server -> client. Sent to one client only.
|
|
struct StartArrangingWithItem : public NetGameMsg {
|
|
StartArrangingWithItem() : NetGameMsg(MessageType::Game::START_ARRANGING_WITH_ITEM) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
|
|
bool firstTime{ true };
|
|
LWOOBJID buildAreaID{ LWOOBJID_EMPTY }; // optional
|
|
NiPoint3 buildStartPos{ NiPoint3Constant::ZERO };
|
|
int32_t sourceBag{};
|
|
LWOOBJID sourceID{ LWOOBJID_EMPTY };
|
|
LOT sourceLot{};
|
|
int32_t sourceType{ 8 };
|
|
LWOOBJID targetID{};
|
|
LOT targetLot{};
|
|
NiPoint3 targetPos{ NiPoint3Constant::ZERO };
|
|
int32_t targetType{};
|
|
};
|
|
|
|
// Server -> client. Sent to one client only; ends arranging (and a modular build).
|
|
struct FinishArrangingWithItem : public NetGameMsg {
|
|
FinishArrangingWithItem() : NetGameMsg(MessageType::Game::FINISH_ARRANGING_WITH_ITEM) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
|
|
LWOOBJID buildAreaID{ LWOOBJID_EMPTY }; // optional
|
|
int32_t newSourceBag{};
|
|
LWOOBJID newSourceID{ LWOOBJID_EMPTY };
|
|
LOT newSourceLot{ LOT_NULL };
|
|
int32_t newSourceType{};
|
|
LWOOBJID newTargetID{ LWOOBJID_EMPTY };
|
|
LOT newTargetLot{ LOT_NULL };
|
|
int32_t newTargetType{};
|
|
NiPoint3 newTargetPos{};
|
|
int32_t oldItemBag{};
|
|
LWOOBJID oldItemID{ LWOOBJID_EMPTY };
|
|
LOT oldItemLot{ LOT_NULL };
|
|
int32_t oldItemType{};
|
|
};
|
|
|
|
// Client -> server.
|
|
struct DoneArrangingWithItem : public NetGameMsg {
|
|
DoneArrangingWithItem() : NetGameMsg(MessageType::Game::DONE_ARRANGING_WITH_ITEM) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
int32_t newSourceBag{};
|
|
LWOOBJID newSourceID{};
|
|
LOT newSourceLot{};
|
|
int32_t newSourceType{};
|
|
LWOOBJID newTargetID{};
|
|
LOT newTargetLot{};
|
|
int32_t newTargetType{};
|
|
NiPoint3 newTargetPosition{};
|
|
int32_t oldItemBag{};
|
|
LWOOBJID oldItemID{};
|
|
LOT oldItemLot{};
|
|
int32_t oldItemType{};
|
|
};
|
|
|
|
// Server -> client. No payload. Sent to one client only.
|
|
struct ModularBuildEnd : public NetGameMsg {
|
|
ModularBuildEnd() : NetGameMsg(MessageType::Game::MODULAR_BUILD_END) {}
|
|
};
|
|
|
|
// Client -> server. The finished modular build: 3 parts for a rocket, 7 for a car.
|
|
struct ModularBuildFinish : public NetGameMsg {
|
|
ModularBuildFinish() : NetGameMsg(MessageType::Game::MODULAR_BUILD_FINISH) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
std::vector<LOT> modules{}; // u8 count, then the part LOTs
|
|
};
|
|
|
|
// Client -> server.
|
|
struct ModularBuildMoveAndEquip : public NetGameMsg {
|
|
ModularBuildMoveAndEquip() : NetGameMsg(MessageType::Game::MODULAR_BUILD_MOVE_AND_EQUIP) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
LOT templateID{};
|
|
};
|
|
|
|
// Client -> server. Takes a modular build apart again.
|
|
struct ModularBuildConvertModel : public NetGameMsg {
|
|
ModularBuildConvertModel() : NetGameMsg(MessageType::Game::MODULAR_BUILD_CONVERT_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 SetBuildMode : public NetGameMsg {
|
|
SetBuildMode() : NetGameMsg(MessageType::Game::SET_BUILD_MODE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
bool start{};
|
|
int32_t distanceType{ -1 }; // optional
|
|
bool modePaused{};
|
|
int32_t modeValue{ 1 }; // optional
|
|
LWOOBJID playerId{};
|
|
NiPoint3 startPos{ NiPoint3Constant::ZERO }; // optional
|
|
};
|
|
|
|
// Server -> client. modeValue and startPos are optional in the client, but DLU always writes their flag set.
|
|
struct SetBuildModeConfirmed : public NetGameMsg {
|
|
SetBuildModeConfirmed() : NetGameMsg(MessageType::Game::SET_BUILD_MODE_CONFIRMED) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
|
|
bool start{};
|
|
bool warnVisitors{ true };
|
|
bool modePaused{};
|
|
int32_t modeValue{ 1 };
|
|
LWOOBJID playerId{};
|
|
NiPoint3 startPos{ NiPoint3Constant::ZERO };
|
|
};
|
|
|
|
// Client -> server.
|
|
struct BuildModeSet : public NetGameMsg {
|
|
BuildModeSet() : NetGameMsg(MessageType::Game::BUILD_MODE_SET) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
bool start{};
|
|
int32_t distanceType{ -1 }; // optional
|
|
bool modePaused{};
|
|
int32_t modeValue{ 1 }; // optional
|
|
LWOOBJID playerId{};
|
|
NiPoint3 startPos{ NiPoint3Constant::ZERO }; // optional
|
|
};
|
|
|
|
// Client -> server. The player put a model back from brick by brick building.
|
|
struct UnUseBBBModel : public NetGameMsg {
|
|
UnUseBBBModel() : NetGameMsg(MessageType::Game::UN_USE_BBB_MODEL) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
bool bHasWorldTransform{};
|
|
LWOOBJID modelID{};
|
|
NiPoint3 worldPos{ NiPoint3Constant::ZERO }; // optional
|
|
NiQuaternion worldRot{ QuatUtils::IDENTITY }; // optional
|
|
};
|
|
|
|
// Client -> server. The player opened a model in brick by brick building.
|
|
struct BBBLoadItemRequest : public NetGameMsg {
|
|
BBBLoadItemRequest() : NetGameMsg(MessageType::Game::BBB_LOAD_ITEM_REQUEST) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
LWOOBJID itemID{};
|
|
};
|
|
|
|
// Client -> server. A brick by brick model: its sd0 compressed LXFML.
|
|
struct BBBSaveRequest : public NetGameMsg {
|
|
BBBSaveRequest() : NetGameMsg(MessageType::Game::BBB_SAVE_REQUEST) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
LWOOBJID localID{};
|
|
std::string lxfmlDataCompressed{}; // u32 byte count, then the bytes
|
|
uint32_t timeTakenInMs{};
|
|
};
|
|
|
|
// Client -> server. The player went into (enterFlag) or came out of brick by brick building.
|
|
// Layout as GameMessage::ActivateBrickMode::Deserialize (0x00d8ecb0) reads it.
|
|
struct ActivateBrickMode : public NetGameMsg {
|
|
ActivateBrickMode() : NetGameMsg(MessageType::Game::ACTIVATE_BRICK_MODE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
LWOOBJID buildObjectID{ LWOOBJID_EMPTY }; // optional; the property's build area when entering
|
|
int32_t buildType{ 2 }; // optional; 0 nowhere, 1 in the world, 2 on a property
|
|
bool enterBuildFromWorld{ true };
|
|
bool enterFlag{ true };
|
|
};
|
|
|
|
// Client -> server. The client's quick save of the model being built: sd0 compressed LXFML, or just the sd0 header
|
|
// when there is nothing to keep. Sent every five minutes, when told the player is AFK (msgInformAFK) and before the
|
|
// client shuts down. Layout as GameMessage::SetBBBAutosave::Deserialize (0x00f2af60) reads it.
|
|
struct SetBBBAutosave : public NetGameMsg {
|
|
SetBBBAutosave() : NetGameMsg(MessageType::Game::SET_BBB_AUTOSAVE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
std::string lxfmlDataCompressed{}; // u32 byte count, then the bytes
|
|
};
|
|
|
|
// Server -> client. How many unfinished (autosaved) models the server rebuilt into models; the client shows
|
|
// BBB_AUTOSAVE_REBUILDING_SINGLE or _MULTIPLE (LWOBBBComponent_Client::msgRebuildBBBAutosaveMsg, 0x00cfabd0).
|
|
struct RebuildBBBAutosaveMsg : public NetGameMsg {
|
|
RebuildBBBAutosaveMsg() : NetGameMsg(MessageType::Game::REBUILD_BBB_AUTOSAVE_MSG) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
|
|
int32_t count{};
|
|
};
|
|
|
|
// Server -> client, to a placed model. Its blueprint's files were made (again) on the UGC server: the client's
|
|
// BlueprintComponent with that blueprint flushes its cached NIF, HKX and LXFML and requests the NIF and HKX again
|
|
// (LWOBlueprintComponent::OnNotifyClientUGCModelReady, 0x00ca6430; the payload is the blueprint id only,
|
|
// GameMessage::NotifyClientUGCIconReady::Serialize 0x00daae30 is shared with it). The client keeps using its
|
|
// cached manifest entry, so the new checksum is sent first (UgcManifestResponse). See docs/UgcServer.md.
|
|
struct NotifyClientUGCModelReady : public NetGameMsg {
|
|
NotifyClientUGCModelReady() : NetGameMsg(MessageType::Game::NOTIFY_CLIENT_UGC_MODEL_READY) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
|
|
LWOOBJID blueprintID{};
|
|
};
|
|
|
|
// A model's user generated data (UGObjectMetadata::Serialize, 0x00f5f590)
|
|
struct UGObjectMetadata {
|
|
LWOOBJID userModelID{};
|
|
LWOOBJID blueprintID{};
|
|
std::u16string userModelName;
|
|
std::u16string userModelDesc;
|
|
LWOOBJID owningPlayerID{};
|
|
LWOOBJID accountID{};
|
|
std::u16string owningPlayerName;
|
|
std::vector<LWOOBJID> userModelBehaviors; // a u8 count on the wire; live always sent 5
|
|
|
|
void Serialize(RakNet::BitStream& bitStream) const;
|
|
bool Deserialize(RakNet::BitStream& bitStream);
|
|
};
|
|
|
|
// A brick built model's blueprint data (BlueprintMetadata::Serialize, 0x00f5f760)
|
|
struct BlueprintMetadata {
|
|
LWOOBJID blueprintID{};
|
|
int64_t blueprintCreationTimestamp{}; // Unix seconds
|
|
int32_t userModelMod{ 1 };
|
|
NiPoint3 modelBoxMins{}; // the model's box, relative to its origin (the exhibit checks its size)
|
|
NiPoint3 modelBoxMaxs{};
|
|
bool userModelOpt{ true };
|
|
bool ugcIconReady{ true };
|
|
std::u16string brickListColonDelim; // every brick's LOT, each followed by ':'
|
|
bool neverFalseIfPresentInCaps{ true };
|
|
int32_t numberOfBricks{};
|
|
|
|
void Serialize(RakNet::BitStream& bitStream) const;
|
|
bool Deserialize(RakNet::BitStream& bitStream);
|
|
};
|
|
|
|
// Client -> server. The client wants the name, owner, behaviors and blueprint of a model (its UGID) it has no
|
|
// metadata for: a brick built model item's tooltip (LWOInventoryComponent_Client::FillinDetailsBlueprint, 0x00cddb40, via
|
|
// UGModelMetadataCache::GetModelMetadata, 0x00b5e680), a model on the property, an exhibit.
|
|
struct FetchModelMetadataRequest : public NetGameMsg {
|
|
FetchModelMetadataRequest() : NetGameMsg(MessageType::Game::FETCH_MODEL_METADATA_REQUEST) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
|
|
|
int32_t context{};
|
|
LWOOBJID objectID{};
|
|
LWOOBJID requestorID{};
|
|
LWOOBJID ugID{};
|
|
};
|
|
|
|
// Server -> client. The answer: the client caches it by UGID and blueprint id and writes it into the config of
|
|
// the inventory item objectID (LWOInventoryComponent_Client::msgFetchModelMetadataResponse, 0x00c64300).
|
|
struct FetchModelMetadataResponse : public NetGameMsg {
|
|
FetchModelMetadataResponse() : NetGameMsg(MessageType::Game::FETCH_MODEL_METADATA_RESPONSE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
|
|
LWOOBJID ugID{};
|
|
LWOOBJID objectID{};
|
|
LWOOBJID requestorID{};
|
|
int32_t context{};
|
|
bool bHasUGData{};
|
|
bool bHasBPData{};
|
|
UGObjectMetadata ugData; // when bHasUGData
|
|
BlueprintMetadata bpData; // when bHasBPData
|
|
};
|
|
|
|
struct SetModelToBuild : public NetGameMsg {
|
|
SetModelToBuild() : NetGameMsg(MessageType::Game::SET_MODEL_TO_BUILD) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
LOT modelLot{ -1 };
|
|
};
|
|
|
|
struct SpawnModelBricks : public NetGameMsg {
|
|
SpawnModelBricks() : NetGameMsg(MessageType::Game::SPAWN_MODEL_BRICKS) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
|
|
float amount{ 0.0f };
|
|
NiPoint3 position{ NiPoint3Constant::ZERO };
|
|
};
|
|
};
|
|
|
|
#endif // BUILDINGMESSAGES_H
|