The client asks for a model's metadata (name, owner, behaviors and its blueprint's bricks and box) when it shows a brick built model item's tooltip, a model on a property or an exhibit, and shows BBB_LOADING_BLUEPRINT until it gets it. The world server never answered. It now does as live did: UG data for the model (found among the player's items by subkey, else among placed models) and, for a brick built model, the blueprint data from its ugc row and LXFML. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
17 KiB
Building on a property: model placement and brick by brick
How the 1.10.64 client goes in and out of property editing, places and removes models and builds brick by brick (BBB)
models, what it expects the server to answer, and what DLU does. Sources: the client in Ghidra (addresses below,
bookmark category BuildWorkflow) and 2014 live captures (only the order of messages and how ids relate are used here;
no capture data is copied). Arrows: C->S client to server, S->C server to client. Message names are the client's.
Code: dGame/dComponents/PropertyManagementComponent (placing and removing models), dGame/dUtilities/BrickByBrick
(BBB sessions, autosave, recovery), messages in dGame/dGameMessages/BuildingMessages, PropertyMessages,
InventoryMessages, and ClientPackets::BlueprintSaveResponse / BlueprintLoadItemResponse.
1. Inventories and ids
| Inventory | Holds | Notes |
|---|---|---|
MODELS (5) |
Model items: premade models (their own LOT) and brick built models (LOT 6662) | Where every model goes when it leaves the property |
MODELS_IN_BBB (3) |
The models opened in BBB (the originals) | Used up by a save; given back otherwise |
BRICKS (2) |
Bricks | |
BRICKS_IN_BBB (9) |
Bricks taken into the BBB model being built | Moved with MoveInventoryBatch |
TEMP_MODELS (6) |
Modular build parts (rocket/car) | Modular builds only |
Ids of one model through the workflow (live):
- Model item id: a new object id every time the model becomes an item (placing then picking up gives a new item id:
one more than the placed model's id in the capture). Opening a model in BBB keeps the item id
(
BlueprintLoadItemResponse.destItemId == itemId). - UGID / property model id (
userModelID, theproperties_contentsid, the spawner id): premade models get a new one each time they are placed. A brick built model keeps its UGID and blueprint across pick up / place: it is the item'ssubkeyanduserModelIDconfig. - Blueprint id (
blueprintid, theugcrow): made by a BBB save, one per model the save splits into. Every save makes new blueprint ids (the client caches blueprints by id), so an edited model is a new blueprint; the oldugcrow is kept. - Model object id: the runtime id of the spawned object on the property; new on every spawn.
GetModelsOnPropertypairs it with the UGID. - BBB local id:
BBBSaveRequest.localID, a client-made id the save response must echo (client keeps it atBBBManager+0x18).
A brick built model item's config (as live sent it): blueprintid, userModelID, userModelName, userModelDesc,
userModelHasBhvr, userModelBehaviors ("id,id,id,id,id"), userModelBehaviorSourceIDs, userModelOpt,
userModelMod, userModelPhysicsType (BrickByBrick::ModelItemConfig).
Brick built models in the inventory
How the client shows a brick built model item (LOT 6662) in the backpack, and what it asks the server:
- Icon:
LWOInventoryComponent_Client::LoadBlueprintIcon(0x00c64980) loads the item'sblueprintidconfig (else its subkey) as a 3D services resource (ResourceSpecifier(blueprint id, Dds): the UGC server'sIMAGE128DDS/file, docs/UgcServer.md) and shows it withInventoryLoadCustomIcon; until then the item showsRenderComponent.icon_asset. Nothing is sent to the world server for the icon. - Tooltip (
LWOInventoryComponent_Client::FillinDetailsBlueprint,0x00cddb40): for LOT 6662 with a persistent subkey (the UGID) it reads the model's metadata from the client's cache (UGModelMetadataCache::GetModelMetadata,0x00b5e680). Not cached:C->S FetchModelMetadataRequest(context, objectID, requestorID, ugID)and the name showsBBB_LOADING_BLUEPRINTuntil the answer. With no name the tooltip says whose model it is (UGG_MODEL_owner_modelwithowningPlayerName). Exhibits need both parts too: they compare the model's box to the exhibit's size. - Answer:
S->C FetchModelMetadataResponse(ugID, objectID, requestorID, context, bHasUGData, bHasBPData, UG data, blueprint data)(0x00e3ccb0,0x00f5f590,0x00f5f760). The client caches it and writesblueprintid,userModelMod,userModelOpt,userModelID,userModelName,userModelDesc,userModelHasBhvranduserModelBehaviorsinto the item's config (msgFetchModelMetadataResponse,0x00c64300), then refreshes the item.
What live sent (2014 captures, one request per model the client shows, the item's own ids 0): UG data with the UGID,
blueprint id, name and description (mostly empty), owner character, account and name, and always 5 behavior ids; for
a brick built model also the blueprint data: blueprint id, creation time, userModelMod 1, the model's box relative
to its origin, userModelOpt 1, ugcIconReady 1, the brick list (every brick's LOT followed by :), 1, and the
number of bricks. Premade models got UG data only (behaviors, no owner); an unknown UGID got neither.
DLU (BrickByBrick::FillModelMetadata) finds the UGID among the player's items (their subkey), else among the placed
models (properties_contents), and fills the rest from the ugc row (owner) and its LXFML (bricks and box). Two
differences: there is no creation time stored (0 is sent), and the box holds the bricks' origins (brick shapes are
not known to the world server), so it is smaller than the model.
2. Entering and leaving property editing
C->S StartBuildingWithItem(subject: property plaque/build area; source = the thinking hat,sourceType1).S->C StartArrangingWithItem(firstTime,buildAreaID= subject, player position,sourceType1 answered as 4).C->S SetBuildMode(start)to the build area.S->C SetBuildModeConfirmed(broadcast;warnVisitorsfalse going in, true coming out).C->S PropertyEditorBegin. Server: property goes private, visitors are sent away, equipped items are pushed, models pause and reset (OnStartBuilding). Live also sentNotifyPropertyOfEditModeandPropertyBuildModeUpdate.C->S PropertyContentsFromClient.S->C GetModelsOnProperty(pairs: model object, UGID).C->S BuildModeSet(start)(player). The client's own notification; DLU records build mode on the character.
Leaving: C->S SetBuildMode(false), PopEquippedItemsState, DoneArrangingWithItem (new source empty: no answer),
S->C SetBuildModeConfirmed(false), C->S PropertyEditorEnd (property saved, models resume, privacy restored),
C->S BuildModeSet(false).
3. Placing and removing models
Place (the model item is carried in the hand):
| Step | Messages |
|---|---|
| Equip | C->S EquipInventory(item), C->S ZonePropertyModelEquipped (zone control) |
| Rotate | C->S ZonePropertyModelRotated (zone control), client side only |
| Place | C->S PlacePropertyModel(0) then C->S UpdateModelFromClient(item id, position, rotation) |
| Answer | S->C RemoveItemFromInventory(item), S->C HandleUGCEquipPreCreateBasedOnEditMode(0, UGID), S->C PlaceModelResponse(position, plaque, 14, rotation), S->C GetModelsOnProperty |
PlaceModelResponse echoes the rotation the client sent: every field is optional and the rotation is a w, x, y, z
quaternion (PlaceModelResponse::Deserialize, 0x00dc0170). DLU used to write the 4-byte response there instead
(wire fix, commit "fix(wire): PlaceModelResponse writes the model's rotation").
Remove (C->S DeleteModelFromClient(model object, reason)):
| Reason | Where the model goes | Answer |
|---|---|---|
| 0 picking up | MODELS, equipped (carried) |
AddItemToInventoryClientSync (new item id), EquipInventory, HandleUGCEquipPostDeleteBasedOnEditMode(item, count), GetModelsOnProperty, PlaceModelResponse(16) |
| 1 returning to inventory | MODELS |
AddItemToInventoryClientSync, GetModelsOnProperty, PlaceModelResponse(16) |
| 2 breaking apart (open in BBB) | MODELS; the client then sends BBBLoadItemRequest |
as picking up, not equipped |
Putting a carried model away: C->S UnEquipInventory and ZonePropertyModelRemovedWhileEquipped; nothing to answer.
DLU saves the property after each place and remove (PropertyManagementComponent::Save), so a crash or disconnect
before PropertyEditorEnd loses nothing: the item is gone only once the model is in properties_contents.
4. Brick by brick
4.1 Entering
- New model:
C->S DoneArrangingWithItem(newSource = a brick in BRICKS, sourceType 2),S->C StartArrangingWithItem(firstTime false, same source). - Editing a placed model:
C->S DoneArrangingWithItem(newTarget = the model object, LOT 14, targetType 4),S->C StartArrangingWithItem, thenDeleteModelFromClient(reason 2)(3) andBBBLoadItemRequest. C->S ActivateBrickMode(buildObjectID = build area, buildType 2, enterBuildFromWorld false, enterFlag true),C->S BuildModeSet(start).
C->S BBBLoadItemRequest(item): the server moves the model item from MODELS to MODELS_IN_BBB keeping its id
(RemoveItemFromInventory, AddItemToInventoryClientSync) and answers S->C BlueprintLoadItemResponse(success, item, destItem). With success 0 the client shows BBB_ERROR_LOADING_BLUEPRINT and drops the load (0x00b75bb0). Live also
put the model's bricks in BRICKS_IN_BBB; DLU does not track the bricks inside models (see 6).
Bricks: each brick taken from the backpack is C->S MoveInventoryBatch(BRICKS -> BRICKS_IN_BBB, LOT, count) and each
brick put back the reverse. The client has already taken them out of the source bag (0x00ce1310); the server moves
them without telling the client about the source and answers S->C AddItemToInventoryClientSync for the destination.
4.2 Saving
C->S BBBSaveRequest(localID, sd0 LXFML, timeTakenInMs). The client sends it only from B3Close (the exit button,
BBBManager::OnB3Close, 0x00b73ad0) when the model has bricks (with BBB_TOO_MANY_MODELS_WARNING first when it
splits into more than 10 models); it shows a saving bar and waits (BBB_SAVE_WAIT_FOR_PREVIOUS meanwhile). The server:
- splits the LXFML into models (
Lxfml::Split), stores each as a newugcrow (is_optimized0: the UGC server makes its mesh and icon); - settles the bricks in
BRICKS_IN_BBB(live used them up:RemoveItemFromInventory; DLU gives them back unlessbbb_consume_bricks=1); S->C BlueprintSaveResponse(localID, reason, [blueprint id, sd0 of each model]);- uses up the models in
MODELS_IN_BBB(RemoveItemFromInventory); - places the new models on the property at their centers, saves the property, clears the autosave;
S->C RequeryPropertyModels(the client then asksPropertyContentsFromClient).
On the save response (BBBManager::OnBlueprintSaveResponse, 0x00b73a50) the client ignores any localId other than
the one it is waiting for; reason 0 while in BBB clears the build and leaves brick mode; any reason ends the saving
state. Reasons with a message: 2, 3, 5, 6, 7, 8, 9, 10, 13. DLU answers 10 (ModelGenerationFailed) when nothing
could be made from the LXFML and 11 (PlacementFailed, no message, the build stays) off the player's own property.
4.3 Quick save (autosave)
C->S SetBBBAutosave(sd0 LXFML) is the client's quick save of the model being built
(BBBManager::SaveModel, 0x00b5be30, sent only when the model changed since the last one):
- every 5 minutes while not placing a brick (
BBBManager::AutosaveTick,0x00b5bfd0); - when the server tells the client the player is AFK (
msgInformAFK,0x00be1b88) and before the client shuts down (PreShutdown,0x00be1c79); - when leaving brick mode (
BBBManager::Deactivate,0x00b71f90).
With nothing to keep (after a save, or an emptied model) it sends the bare sd0 header (5 bytes) to clear it. (The
client also has a BBB_SAVE key action, 0x55; its handler was not traced.) DLU keeps the latest one per character in bbb_autosave, with the
ids of the models in MODELS_IN_BBB at the time.
S->C RebuildBBBAutosaveMsg(count) tells the client the server rebuilt count unfinished models from an autosave; it
shows BBB_AUTOSAVE_REBUILDING_SINGLE / _MULTIPLE ("... tried to rebuild it for you. Any leftover bricks have been
returned to your Backpack", 0x00cfabd0).
4.4 Leaving without a save, disconnects and crashes
Leaving: C->S BBBResetMetadataSourceItem (when the model is empty), C->S SetBBBAutosave (maybe), C->S ActivateBrickMode(enterFlag false), C->S BuildModeSet(false), then the property editor's leave (2).
DLU resolves every build that ends without a save the same way (BrickByBrick::RecoverUnfinishedBuild), when the
player leaves brick mode (ActivateBrickMode with enterFlag false) and when a character loads into a world:
- an autosave with a model: it is rebuilt into brick built model items in
MODELS(newugcrows), the models it was made from are used up,RebuildBBBAutosaveMsg(count)is sent; - otherwise the models in
MODELS_IN_BBBgo back toMODELSunchanged (leaving an edit without saving never loses the original); - bricks in
BRICKS_IN_BBBgo back toBRICKS; the autosave is cleared.
MODELS_IN_BBB is saved with the character (it used to be left out, so a disconnect in BBB deleted the model, #1632),
and on load the BBB bags load into MODELS and BRICKS. A world or client crash therefore keeps the opened models;
the next load rebuilds the autosave, if there was one, in their place.
4.5 Undo
The client's undo and redo (PropertyEditUndo / PropertyEditRedo, registered only by the BBB UI in
BBBManager::RegisterUIListeners, 0x00b73c70) work on the BBB session; model placement has no undo. What reaches the
server:
- Bricks taken or put back:
MoveInventoryBatchin either direction (4.1). - Importing a placed model into the build (import tool,
BBBImportModelTool::ImportModel,0x00b5fd20): it is taken off the property (DeleteModelFromClientreason 2) and opened (BBBLoadItemRequest). Undoing it, or a model whose blueprint could not be loaded (BBBManager::OnBlueprintLoaded,0x00b73040), sendsC->S UnUseBBBModel(model item, bHasWorldTransform, position, rotation)(BBBManager::SendUnUseBBBModel,0x00b6b210). With a world transform the model came from the property: DLU places it back there; otherwise it goes back toMODELS. Either way the id is dropped from the autosave's list so a later rebuild cannot use it up. With a world transform the client waits in its saving state; DLU ends it withBlueprintSaveResponse(localId 0, PlacementFailed), which keeps the build. - Leaving without saving (4.4): the originals come back.
5. Modular builds (rockets and cars)
C->S StartBuildingWithItem / S->C StartArrangingWithItem, the parts are arranged client side, C->S ModularBuildFinish(part LOTs), C->S ModularBuildMoveAndEquip(LOT) (the built item from TEMP_MODELS to MODELS,
equipped), S->C FinishArrangingWithItem / ModularBuildEnd, C->S DoneArrangingWithItem. Parts sit in
TEMP_MODELS while building; DoneArrangingWithItem moves what is left there back to MODELS.
ModularBuildConvertModel takes a build apart into TEMP_MODELS. Unchanged by this work.
6. DLU choices that differ from live
- Bricks are free: a save gives the bricks in
BRICKS_IN_BBBback to the backpack (live used them up), unlessbbb_consume_bricks=1. Loading a model does not put its bricks inBRICKS_IN_BBB, so bricks taken off a loaded model cannot be put in the backpack (the client shows them there until the next load). - A model opened and then emptied is given back when leaving, rather than removed as live did (its bricks were never given to the player).
- An edited model gets new blueprint ids; the old
ugcrow stays.
7. Client functions (1.10.64)
| Address | Name |
|---|---|
0x00b5be30 |
BBBManager::SaveModel (autosave) |
0x00b5b580 |
BBBManager::SendAutosaveIfChanged |
0x00b5bfd0 |
BBBManager::AutosaveTick |
0x00b6cb40 |
BBBManager::SendBBBSaveRequest |
0x00b6cf00 |
BBBManager::SendSaveRequestTimed |
0x00b6d7b0 |
BBBManager::RequestSave |
0x00b73ad0 |
BBBManager::OnB3Close |
0x00b756b0 |
BBBManager::RequestExit |
0x00b71f90 |
BBBManager::Deactivate |
0x00b73a50 |
BBBManager::OnBlueprintSaveResponse |
0x00b66850 |
BBBManager::ProcessBlueprintSaveResponse |
0x00b75bb0 |
OnBlueprintLoadItemResponse |
0x00b73040 |
BBBManager::OnBlueprintLoaded |
0x00b6b210 |
BBBManager::SendUnUseBBBModel |
0x00b5fd20 / 0x00b6b400 |
BBBImportModelTool::ImportModel / ReturnModel |
0x00b73c70 |
BBBManager::RegisterUIListeners |
0x00cfabd0 |
LWOBBBComponent_Client::msgRebuildBBBAutosaveMsg |
0x00d8ecb0 |
GameMessage::ActivateBrickMode::Deserialize |
0x00b5e680 |
UGModelMetadataCache::GetModelMetadata (sends FetchModelMetadataRequest) |
0x00c64300 |
LWOInventoryComponent_Client::msgFetchModelMetadataResponse |
0x00e3ccb0 |
GameMessage::FetchModelMetadataResponse::Serialize |
0x00f5f590 / 0x00f5f760 |
UGObjectMetadata::Serialize / BlueprintMetadata::Serialize |
0x00cddb40 |
LWOInventoryComponent_Client::FillinDetailsBlueprint (model item tooltip) |
0x00c64980 |
LWOInventoryComponent_Client::LoadBlueprintIcon |
0x00f2af60 |
GameMessage::SetBBBAutosave::Deserialize |
0x00dc0170 |
GameMessage::PlaceModelResponse::Deserialize |
0x00ce1310 |
LWOInventoryComponent_Common::msgMoveInventoryBatch |