Files
DarkflameServer/dGame/dComponents/PropertyManagementComponent.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

253 lines
7.2 KiB
C++

#pragma once
#include "ePropertyPrivacyOption.h"
#include <chrono>
#include "Entity.h"
#include "Component.h"
#include "eReplicaComponentType.h"
#include "LDFFormat.h"
class Item;
/**
* Main component that handles interactions with a property, generally the plaques you see on properties.
*/
class PropertyManagementComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY_MANAGEMENT;
PropertyManagementComponent(Entity* parent, const int32_t componentID);
static PropertyManagementComponent* Instance();
/**
* Event handler for when an entity requests information about this property, will send back whether it's owned, etc.
* @param originator the entity that triggered the event
* @param sysAddr the address to send game message responses to
* @param author optional explicit ID for the property, if not set defaults to the originator
*/
void OnQueryPropertyData(Entity* originator, const SystemAddress& sysAddr, LWOOBJID author = LWOOBJID_EMPTY);
/**
* Handles an OnUse event, telling the client who owns this property, etc.
* @param originator the entity that triggered the event
*/
void OnUse(Entity* originator) override;
/**
* Sets the owner of this property
* @param value the owner to set
*/
void SetOwnerId(LWOOBJID value);
/**
* Returns the ID of the owner of this property
* @return the ID of the owner of this property
*/
LWOOBJID GetOwnerId() const;
/**
* Returns the owner of this property
* @return the owner of this property
*/
Entity* GetOwner() const;
/**
* sets the owner of this property
* @param value the owner to set
*/
void SetOwner(Entity* value);
/**
* Returns the paths that this property has
* @return the paths that this property has
*/
std::vector<NiPoint3> GetPaths() const;
/**
* Returns the privacy options for this property
* @return the privacy options for this property
*/
PropertyPrivacyOption GetPrivacyOption() const;
/**
* Updates the privacy option for this property
* @param value the privacy option to set
*/
void SetPrivacyOption(PropertyPrivacyOption value);
/**
* Updates information of this property, saving it to the database
* @param name the name to set for the property
* @param description the description to set for the property
*/
void UpdatePropertyDetails(std::string name, std::string description);
/**
* Makes this property owned by the passed player ID, storing it in the database
* @param playerId the ID of the entity that claimed the property
*
* @return If the claim is successful return true.
*/
bool Claim(LWOOBJID playerId);
/**
* Event triggered when the owner of the property starts building, will kick other entities out
*/
void OnStartBuilding();
/**
* Event triggered when the owner of the property finished building, will re-apply this property for moderation
* request.
*/
void OnFinishBuilding();
/**
* Updates the position of a model on the property
* @param id the ID of the model to reposition
* @param position the position to place the model on
* @param rotation the rotation to place the model on
*/
void UpdateModelPosition(LWOOBJID id, NiPoint3 position, NiQuaternion rotation);
/**
* Deletes a model for a property
* @param id the ID of the model to delete
* @param deleteReason the reason of the deletion, e.g. picked up or destroyed (in case of UGC)
*/
void DeleteModel(LWOOBJID id, int deleteReason);
/**
* Spawns a model on the property and records it in the property's models (not yet saved)
* @param lot the model object's LOT (14 for a brick built model)
* @param modelId the model's id in properties_contents (its UGID)
* @param config extra config for the model object
*/
Entity* SpawnModel(LOT lot, LWOOBJID modelId, const NiPoint3& position, const NiQuaternion& rotation, const LwoNameValue& config);
/**
* Places a model item on the property, uses the item up and saves the property
* @return the placed model's id, or LWOOBJID_EMPTY if it could not be placed
*/
LWOOBJID PlaceModelFromItem(Item& item, const NiPoint3& position, const NiQuaternion& rotation);
// GetModelsOnProperty with every model on the property
void SendModelsOnProperty() const;
/**
* Updates whether or not this property is approved by a moderator
* @param value true if the property should be approved, false otherwise
*/
void UpdateApprovedStatus(bool value, const std::string& rejectionReason = "");
// A moderator decided on this property from the dashboard (already saved): keep this world's copy in step
void ApplyModeration(bool approved, const std::string& reason);
/**
* Loads all the models on this property from the database
*/
void Load();
/**
* Saves all the models from this property to the database
*/
void Save();
/**
* Adds a model to the cache of models
* @param modelId the ID of the model
* @param spawnerId the ID of the object that spawned the model
*/
void AddModel(LWOOBJID modelId, LWOOBJID spawnerId);
/**
* Returns all the models on this property, indexed by property ID, containing their spawn objects
* @return all the models on this proeprty
*/
const std::map<LWOOBJID, LWOOBJID>& GetModels() const;
LWOCLONEID GetCloneId() { return clone_Id; };
LWOOBJID GetId() const noexcept { return propertyId; }
// Reputation visitors just gave (PropertyReputation.h; the database is updated there), so property data shows it
void AddReputation(uint32_t points) { reputation += points; }
uint32_t GetReputation() const noexcept { return reputation; }
void OnChatMessageReceived(const std::string& sMessage) const;
private:
/**
* This
*/
static PropertyManagementComponent* instance;
/**
* The ID of the owner of this property
*/
LWOOBJID owner = LWOOBJID_EMPTY;
/**
* The LOT of this console
*/
uint32_t templateId = 0;
/**
* The unique ID for this property, if it's owned
*/
LWOOBJID propertyId = LWOOBJID_EMPTY;
/**
* The time since this property was claimed
*/
uint64_t claimedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
/**
* The models that are placed on this property
*/
std::map<LWOOBJID /* ObjectID */, LWOOBJID /* SpawnerID */> models = {};
/**
* The name of this property
*/
std::string propertyName = "";
/**
* The clone ID of this property
*/
LWOCLONEID clone_Id = 0;
/**
* Whether a moderator was requested
*/
bool moderatorRequested = false;
/**
* The rejection reason for the property
*/
std::string rejectionReason = "";
/**
* The description of this property
*/
std::string propertyDescription = "";
/**
* The reputation of this property
*/
uint32_t reputation = 0;
/**
* The last time this property was updated
*/
uint32_t LastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
/**
* Determines which players may visit this property
*/
PropertyPrivacyOption privacyOption = PropertyPrivacyOption::Private;
/**
* The privacy setting before it was changed, saved to set back after a player finishes building
*/
PropertyPrivacyOption originalPrivacyOption = PropertyPrivacyOption::Private;
};