mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
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>
254 lines
17 KiB
Markdown
254 lines
17 KiB
Markdown
# 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`).
|
|
|
|
### 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's `blueprintid` config
|
|
(else its subkey) as a 3D services resource (`ResourceSpecifier(blueprint id, Dds)`: the UGC server's
|
|
`IMAGE128DDS/` file, docs/UgcServer.md) and shows it with `InventoryLoadCustomIcon`; until then the item shows
|
|
`RenderComponent.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 shows
|
|
`BBB_LOADING_BLUEPRINT` until the answer. With no name the tooltip says whose model it is (`UGG_MODEL_owner_model`
|
|
with `owningPlayerName`). 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 writes `blueprintid`,
|
|
`userModelMod`, `userModelOpt`, `userModelID`, `userModelName`, `userModelDesc`, `userModelHasBhvr` and
|
|
`userModelBehaviors` into 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
|
|
|
|
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` |
|
|
| `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` |
|