Files
DarkflameServer/docs/BuildWorkflow.md
Aaron Kimbrell 68a810ea7b docs: the property building and brick by brick workflow
Messages in order with directions, what the server does at each step, the
inventories and how model ids change, quick save and recovery, undo, and
the client functions they were mapped from (1.10.64).

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

14 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, the properties_contents id, 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's subkey and userModelID config.
  • Blueprint id (blueprintid, the ugc row): 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 old ugc row is kept.
  • Model object id: the runtime id of the spawned object on the property; new on every spawn. GetModelsOnProperty pairs it with the UGID.
  • BBB local id: BBBSaveRequest.localID, a client-made id the save response must echo (client keeps it at BBBManager+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).

2. Entering and leaving property editing

  1. C->S StartBuildingWithItem (subject: property plaque/build area; source = the thinking hat, sourceType 1). S->C StartArrangingWithItem (firstTime, buildAreaID = subject, player position, sourceType 1 answered as 4).
  2. C->S SetBuildMode(start) to the build area. S->C SetBuildModeConfirmed (broadcast; warnVisitors false going in, true coming out).
  3. C->S PropertyEditorBegin. Server: property goes private, visitors are sent away, equipped items are pushed, models pause and reset (OnStartBuilding). Live also sent NotifyPropertyOfEditMode and PropertyBuildModeUpdate.
  4. C->S PropertyContentsFromClient. S->C GetModelsOnProperty (pairs: model object, UGID).
  5. 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, then DeleteModelFromClient(reason 2) (3) and BBBLoadItemRequest.
  • 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:

  1. splits the LXFML into models (Lxfml::Split), stores each as a new ugc row (is_optimized 0: the UGC server makes its mesh and icon);
  2. settles the bricks in BRICKS_IN_BBB (live used them up: RemoveItemFromInventory; DLU gives them back unless bbb_consume_bricks=1);
  3. S->C BlueprintSaveResponse(localID, reason, [blueprint id, sd0 of each model]);
  4. uses up the models in MODELS_IN_BBB (RemoveItemFromInventory);
  5. places the new models on the property at their centers, saves the property, clears the autosave;
  6. S->C RequeryPropertyModels (the client then asks PropertyContentsFromClient).

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 (new ugc rows), the models it was made from are used up, RebuildBBBAutosaveMsg(count) is sent;
  • otherwise the models in MODELS_IN_BBB go back to MODELS unchanged (leaving an edit without saving never loses the original);
  • bricks in BRICKS_IN_BBB go back to BRICKS; 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: MoveInventoryBatch in either direction (4.1).
  • Importing a placed model into the build (import tool, BBBImportModelTool::ImportModel, 0x00b5fd20): it is taken off the property (DeleteModelFromClient reason 2) and opened (BBBLoadItemRequest). Undoing it, or a model whose blueprint could not be loaded (BBBManager::OnBlueprintLoaded, 0x00b73040), sends C->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 to MODELS. 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 with BlueprintSaveResponse(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_BBB back to the backpack (live used them up), unless bbb_consume_bricks=1. Loading a model does not put its bricks in BRICKS_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 ugc row 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
0x00f2af60 GameMessage::SetBBBAutosave::Deserialize
0x00dc0170 GameMessage::PlaceModelResponse::Deserialize
0x00ce1310 LWOInventoryComponent_Common::msgMoveInventoryBatch