Files
DarkflameServer/dGame/dUtilities/BrickByBrick.h
Aaron Kimbrell 6ce261c58c fix: brick by brick and model placement work the way the client expects
Mapped from the 1.10.64 client and live captures (docs/BuildWorkflow.md).

Model placement (PropertyManagementComponent):
- A brick built model placed from the inventory spawned at the world origin
  with no rotation and without PlaceModelResponse/PreCreate, and was saved
  to properties_contents with ugc_id 0; it is now placed where the client
  put it, keeps its UGID and blueprint and is saved with them.
- Picking up, putting away and taking apart a brick built model gave it to
  MODELS_IN_BBB and then deleted it; every way off the property now puts it
  in MODELS (carried when picked up), as live, with its blueprint config.
  Taking a premade model apart no longer deletes it either.
- Placing and removing a model saves the property at once, so a crash or a
  disconnect before PropertyEditorEnd does not lose it.
- DoneArrangingWithItem only answers when something new is picked (not when
  leaving), with the subject as build area; SetBuildModeConfirmed's
  warnVisitors matches live.

Brick by brick (BrickByBrick):
- BBBLoadItemRequest moves the model to MODELS_IN_BBB keeping its id and
  fails cleanly when the player has no such model.
- MoveInventoryBatch moves bricks between BRICKS and BRICKS_IN_BBB (it was
  not handled, so the client and server disagreed until a relog).
- BBBSaveRequest uses up the opened models, places the new ones through the
  property, returns the bricks (or uses them with bbb_consume_bricks=1),
  clears the autosave and sends RequeryPropertyModels. Every save makes new
  ugc rows (is_optimized 0, so the UGC server processes them).
- Quick save: SetBBBAutosave is stored per character (bbb_autosave).
- UnUseBBBModel puts a model back on the property where it was when it came
  from there, otherwise back in MODELS.
- Leaving brick mode without a save, a disconnect or a crash: the autosave
  is rebuilt into models (RebuildBBBAutosaveMsg) or the opened models go
  back to MODELS. MODELS_IN_BBB is saved with the character now and loads
  into MODELS, BRICKS_IN_BBB into BRICKS.

Fixes #1632
Fixes #159
Fixes #1565

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

107 lines
4.3 KiB
C++

#ifndef BRICKBYBRICK_H
#define BRICKBYBRICK_H
#include "dCommonVars.h"
#include "IBbbAutosave.h"
#include "LDFFormat.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
class Entity;
class InventoryComponent;
namespace GameMessages {
struct MoveInventoryBatch;
}
// Brick by brick building and the model items it works on, as the 1.10.64 client expects them (docs/BuildWorkflow.md).
//
// While a player builds, MODELS_IN_BBB holds the models they opened (their original items) and BRICKS_IN_BBB the bricks
// they took out of their backpack. A save turns the build into new models on the property and uses up the originals.
// Anything else that ends a build (leaving without saving, a disconnect or a crash) goes through RecoverUnfinishedBuild:
// the client's last autosave is rebuilt into models, or the originals go back to MODELS, so a model is never lost.
namespace BrickByBrick {
// A brick built model as an item (in MODELS, VAULT_MODELS or MODELS_IN_BBB)
constexpr LOT MODEL_ITEM_LOT = 6662;
// A brick built model placed in the world
constexpr LOT MODEL_OBJECT_LOT = 14;
// DeleteModelFromClient's reason
enum class eDeleteReason : int32_t {
PICKING_MODEL_UP = 0, // into MODELS, equipped to carry it
RETURNING_MODEL_TO_INVENTORY, // into MODELS
BREAKING_MODEL_APART, // into MODELS; the client then opens it with BBBLoadItemRequest
};
// PlaceModelResponse's response
constexpr int32_t PLACE_MODEL_PLACED = 14;
constexpr int32_t PLACE_MODEL_REMOVED = 16;
// ---- Rules with no game state (unit tested) ----
// Whether a SetBBBAutosave / BBBSaveRequest payload holds no model: nothing, or only the sd0 header the client sends
// to clear its autosave
bool IsEmptyModel(std::string_view sd0);
// What taking a model off the property does with it
struct ModelRemoval {
bool equip{}; // the player carries it
bool notifyPostDelete{}; // HandleUGCEquipPostDeleteBasedOnEditMode tells the client which item it became
};
ModelRemoval PlanModelRemoval(int32_t reason);
// What ending a build without a save does
struct Recovery {
bool rebuild{}; // the autosave is rebuilt into models
std::vector<LWOOBJID> consume; // originals the rebuilt models replace (only once a model was rebuilt)
std::vector<LWOOBJID> giveBack; // MODELS_IN_BBB items that go back to MODELS
};
Recovery PlanRecovery(const std::vector<LWOOBJID>& modelsInBbb, const std::optional<IBbbAutosave::Info>& autosave);
// The config of a brick built model item (6662) and of its placed object (14)
LwoNameValue ModelItemConfig(LWOOBJID blueprintId, LWOOBJID userModelId, const std::string& behaviors = "");
// An object id kept in a config (blueprintid, userModelID), or LWOOBJID_EMPTY
LWOOBJID ConfigObjectId(const LwoNameValue& config, const std::u16string& key);
// ---- Handlers ----
// BBBLoadItemRequest: moves the model item from MODELS to MODELS_IN_BBB keeping its object id (as live did).
// Returns the id in MODELS_IN_BBB, or LWOOBJID_EMPTY when the player has no such model.
LWOOBJID LoadModel(Entity& player, LWOOBJID itemId);
// BBBSaveRequest
void Save(Entity& player, LWOOBJID localId, const std::string& sd0);
// SetBBBAutosave: the client's quick save
void Autosave(Entity& player, const std::string& sd0);
// UnUseBBBModel: a model the player opened goes back where it came from (the import tool's undo, or a model that
// could not be loaded). With a world transform it goes back on the property there, otherwise to MODELS.
void ReturnModel(Entity& player, LWOOBJID itemId, bool hasWorldTransform, const NiPoint3& position, const NiQuaternion& rotation);
// MoveInventoryBatch between BRICKS and BRICKS_IN_BBB
void MoveBricks(Entity& player, const GameMessages::MoveInventoryBatch& move);
// ActivateBrickMode leaving brick mode
void EndSession(Entity& player);
// PlayerLoaded
void OnPlayerLoaded(Entity& player);
// Resolves a build that ended without a save; returns how many models were rebuilt from the autosave
uint32_t RecoverUnfinishedBuild(Entity& player);
// Whether an inventory bag saved as a BBB bag loads into the normal one (MODELS_IN_BBB -> MODELS,
// BRICKS_IN_BBB -> BRICKS): a build never survives a reload, its items do.
uint32_t InventoryToLoadInto(uint32_t savedType);
};
#endif //!BRICKBYBRICK_H