mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-10-14 03:19:56 +00:00
feat: re-write persistent object ID tracker
Features: - Remove random objectIDs entirely - Replace random objectIDs with persistentIDs - Remove the need to contact the MASTER server for a persistent ID - Add persistent ID logic to WorldServers that use transactions to guarantee unique IDs no matter when they are generated - Default character xml version to be the most recent one Fixes: - Return optional from GetModel (and check for nullopt where it may exist) - Regenerate inventory item ids on first login to be unique item IDs (fixes all those random IDs Pet IDs and subkeys are left alone and are assumed to be reserved (checks are there to prevent this) There is also duplicate check logic in place for properties and UGC/Models
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
#include "BitStreamUtils.h"
|
||||
#include "CheatDetection.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "eCharacterVersion.h"
|
||||
|
||||
UserManager* UserManager::m_Address = nullptr;
|
||||
|
||||
@@ -324,79 +325,77 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
|
||||
LOG("AccountID: %i is creating a character with name: %s (temporary: %s)", u->GetAccountID(), name.c_str(), predefinedName.c_str());
|
||||
}
|
||||
|
||||
//Now that the name is ok, we can get an objectID from Master:
|
||||
ObjectIDManager::RequestPersistentID([=, this](uint32_t persistentID) {
|
||||
LWOOBJID objectID = persistentID;
|
||||
GeneralUtils::SetBit(objectID, eObjectBits::CHARACTER);
|
||||
if (Database::Get()->GetCharacterInfo(objectID)) {
|
||||
LOG("Character object id unavailable, check object_id_tracker!");
|
||||
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
|
||||
return;
|
||||
}
|
||||
//Now that the name is ok, we can get a persistent ObjectID:
|
||||
LWOOBJID objectID = ObjectIDManager::GetPersistentID();
|
||||
const uint32_t maxRetries = 100;
|
||||
uint32_t tries = 0;
|
||||
while (Database::Get()->GetCharacterInfo(objectID) && tries < maxRetries) {
|
||||
tries++;
|
||||
LOG("Found a duplicate character %llu, getting a new objectID", objectID);
|
||||
objectID = ObjectIDManager::GetPersistentID();
|
||||
}
|
||||
|
||||
std::stringstream xml;
|
||||
xml << "<obj v=\"1\">";
|
||||
if (tries >= maxRetries) {
|
||||
LOG("Failed to get a unique objectID for new character after %i tries, aborting char creation for account %i", maxRetries, u->GetAccountID());
|
||||
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
xml << "<mf hc=\"" << hairColor << "\" hs=\"" << hairStyle << "\" hd=\"0\" t=\"" << shirtColor << "\" l=\"" << pantsColor;
|
||||
xml << "\" hdc=\"0\" cd=\"" << shirtStyle << "\" lh=\"" << lh << "\" rh=\"" << rh << "\" es=\"" << eyebrows << "\" ";
|
||||
xml << "ess=\"" << eyes << "\" ms=\"" << mouth << "\"/>";
|
||||
std::stringstream xml;
|
||||
xml << "<obj v=\"1\">";
|
||||
|
||||
xml << "<char acct=\"" << u->GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" ";
|
||||
xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" ";
|
||||
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">";
|
||||
xml << "<vl><l id=\"1000\" cid=\"0\"/></vl>";
|
||||
xml << "<mf hc=\"" << hairColor << "\" hs=\"" << hairStyle << "\" hd=\"0\" t=\"" << shirtColor << "\" l=\"" << pantsColor;
|
||||
xml << "\" hdc=\"0\" cd=\"" << shirtStyle << "\" lh=\"" << lh << "\" rh=\"" << rh << "\" es=\"" << eyebrows << "\" ";
|
||||
xml << "ess=\"" << eyes << "\" ms=\"" << mouth << "\"/>";
|
||||
|
||||
xml << "</char>";
|
||||
xml << "<char acct=\"" << u->GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" ";
|
||||
xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" ";
|
||||
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">";
|
||||
xml << "<vl><l id=\"1000\" cid=\"0\"/></vl>";
|
||||
|
||||
xml << "<dest hm=\"4\" hc=\"4\" im=\"0\" ic=\"0\" am=\"0\" ac=\"0\" d=\"0\"/>";
|
||||
xml << "</char>";
|
||||
|
||||
xml << "<inv><bag><b t=\"0\" m=\"20\"/><b t=\"1\" m=\"40\"/><b t=\"2\" m=\"240\"/><b t=\"3\" m=\"240\"/><b t=\"14\" m=\"40\"/></bag><items><in t=\"0\">";
|
||||
xml << "<dest hm=\"4\" hc=\"4\" im=\"0\" ic=\"0\" am=\"0\" ac=\"0\" d=\"0\"/>";
|
||||
|
||||
LWOOBJID lwoidforshirt = ObjectIDManager::GenerateRandomObjectID();
|
||||
LWOOBJID lwoidforpants;
|
||||
xml << "<inv><bag><b t=\"0\" m=\"20\"/><b t=\"1\" m=\"40\"/><b t=\"2\" m=\"240\"/><b t=\"3\" m=\"240\"/><b t=\"14\" m=\"40\"/></bag><items><in t=\"0\">";
|
||||
|
||||
do {
|
||||
lwoidforpants = ObjectIDManager::GenerateRandomObjectID();
|
||||
} while (lwoidforpants == lwoidforshirt); //Make sure we don't have the same ID for both shirt and pants
|
||||
LWOOBJID lwoidforshirt = ObjectIDManager::GetPersistentID();
|
||||
LWOOBJID lwoidforpants = ObjectIDManager::GetPersistentID();
|
||||
|
||||
GeneralUtils::SetBit(lwoidforshirt, eObjectBits::CHARACTER);
|
||||
GeneralUtils::SetBit(lwoidforpants, eObjectBits::CHARACTER);
|
||||
xml << "<i l=\"" << shirtLOT << "\" id=\"" << lwoidforshirt << "\" s=\"0\" c=\"1\" eq=\"1\" b=\"1\"/>";
|
||||
xml << "<i l=\"" << pantsLOT << "\" id=\"" << lwoidforpants << "\" s=\"1\" c=\"1\" eq=\"1\" b=\"1\"/>";
|
||||
|
||||
xml << "<i l=\"" << shirtLOT << "\" id=\"" << lwoidforshirt << "\" s=\"0\" c=\"1\" eq=\"1\" b=\"1\"/>";
|
||||
xml << "<i l=\"" << pantsLOT << "\" id=\"" << lwoidforpants << "\" s=\"1\" c=\"1\" eq=\"1\" b=\"1\"/>";
|
||||
xml << "</in></items></inv><lvl l=\"1\" cv=\"" << GeneralUtils::ToUnderlying(eCharacterVersion::UP_TO_DATE) << "\" sb=\"500\"/><flag></flag></obj>";
|
||||
|
||||
xml << "</in></items></inv><lvl l=\"1\" cv=\"1\" sb=\"500\"/><flag></flag></obj>";
|
||||
//Check to see if our name was pre-approved:
|
||||
bool nameOk = IsNamePreapproved(name);
|
||||
if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true;
|
||||
|
||||
//Check to see if our name was pre-approved:
|
||||
bool nameOk = IsNamePreapproved(name);
|
||||
if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true;
|
||||
// If predefined name is invalid, change it to be their object id
|
||||
// that way more than one player can create characters if the predefined name files are not provided
|
||||
auto assignedPredefinedName = predefinedName;
|
||||
if (assignedPredefinedName == "INVALID") {
|
||||
std::stringstream nameObjID;
|
||||
nameObjID << "minifig" << objectID;
|
||||
assignedPredefinedName = nameObjID.str();
|
||||
}
|
||||
|
||||
// If predefined name is invalid, change it to be their object id
|
||||
// that way more than one player can create characters if the predefined name files are not provided
|
||||
auto assignedPredefinedName = predefinedName;
|
||||
if (assignedPredefinedName == "INVALID") {
|
||||
std::stringstream nameObjID;
|
||||
nameObjID << "minifig" << objectID;
|
||||
assignedPredefinedName = nameObjID.str();
|
||||
}
|
||||
std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName;
|
||||
std::string pendingName = !name.empty() && !nameOk ? name : "";
|
||||
|
||||
std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName;
|
||||
std::string pendingName = !name.empty() && !nameOk ? name : "";
|
||||
ICharInfo::Info info;
|
||||
info.name = nameToAssign;
|
||||
info.pendingName = pendingName;
|
||||
info.id = objectID;
|
||||
info.accountId = u->GetAccountID();
|
||||
|
||||
ICharInfo::Info info;
|
||||
info.name = nameToAssign;
|
||||
info.pendingName = pendingName;
|
||||
info.id = objectID;
|
||||
info.accountId = u->GetAccountID();
|
||||
Database::Get()->InsertNewCharacter(info);
|
||||
|
||||
Database::Get()->InsertNewCharacter(info);
|
||||
//Now finally insert our character xml:
|
||||
Database::Get()->InsertCharacterXml(objectID, xml.str());
|
||||
|
||||
//Now finally insert our character xml:
|
||||
Database::Get()->InsertCharacterXml(objectID, xml.str());
|
||||
|
||||
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS);
|
||||
UserManager::RequestCharacterList(sysAddr);
|
||||
});
|
||||
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS);
|
||||
UserManager::RequestCharacterList(sysAddr);
|
||||
}
|
||||
|
||||
void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) {
|
||||
|
@@ -1786,3 +1786,9 @@ void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) {
|
||||
groupElement = groupElement->NextSiblingElement("grp");
|
||||
}
|
||||
}
|
||||
|
||||
void InventoryComponent::RegenerateItemIDs() {
|
||||
for (auto* const inventory : m_Inventories | std::views::values) {
|
||||
inventory->RegenerateItemIDs();
|
||||
}
|
||||
}
|
||||
|
@@ -407,6 +407,9 @@ public:
|
||||
|
||||
void FixInvisibleItems();
|
||||
|
||||
// Used to migrate a character version, no need to call outside of that context
|
||||
void RegenerateItemIDs();
|
||||
|
||||
~InventoryComponent() override;
|
||||
|
||||
private:
|
||||
|
@@ -479,9 +479,19 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
|
||||
return;
|
||||
}
|
||||
|
||||
LWOOBJID petSubKey = ObjectIDManager::GenerateRandomObjectID();
|
||||
LWOOBJID petSubKey = ObjectIDManager::GetPersistentID();
|
||||
const uint32_t maxTries = 100;
|
||||
uint32_t tries = 0;
|
||||
while (Database::Get()->GetPetNameInfo(petSubKey) && tries < maxTries) {
|
||||
tries++;
|
||||
LOG("Found a duplicate pet %llu, getting a new subKey", petSubKey);
|
||||
petSubKey = ObjectIDManager::GetPersistentID();
|
||||
}
|
||||
|
||||
GeneralUtils::SetBit(petSubKey, eObjectBits::CHARACTER);
|
||||
if (tries >= maxTries) {
|
||||
LOG("Failed to get a unique pet subKey after %i tries, aborting pet creation for player %i", maxTries, tamer->GetCharacter() ? tamer->GetCharacter()->GetID() : -1);
|
||||
return;
|
||||
}
|
||||
|
||||
m_DatabaseId = petSubKey;
|
||||
|
||||
|
@@ -135,7 +135,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
|
||||
const auto owner = propertyEntry.ownerId;
|
||||
const auto otherCharacter = Database::Get()->GetCharacterInfo(owner);
|
||||
if (!otherCharacter.has_value()) {
|
||||
LOG("Failed to find property owner name for %u!", owner);
|
||||
LOG("Failed to find property owner name for %llu!", owner);
|
||||
continue;
|
||||
}
|
||||
auto& entry = entries.emplace_back();
|
||||
|
@@ -170,7 +170,7 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s
|
||||
info.name = propertyName;
|
||||
info.description = propertyDescription;
|
||||
info.lastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
|
||||
Database::Get()->UpdateLastSave(info);
|
||||
Database::Get()->UpdatePropertyDetails(info);
|
||||
|
||||
@@ -203,14 +203,22 @@ bool PropertyManagementComponent::Claim(const LWOOBJID playerId) {
|
||||
|
||||
auto prop_path = zone->GetPath(m_Parent->GetVarAsString(u"propertyName"));
|
||||
|
||||
if (prop_path){
|
||||
if (prop_path) {
|
||||
if (!prop_path->property.displayName.empty()) name = prop_path->property.displayName;
|
||||
description = prop_path->property.displayDesc;
|
||||
}
|
||||
|
||||
SetOwnerId(playerId);
|
||||
|
||||
propertyId = ObjectIDManager::GenerateRandomObjectID();
|
||||
// Due to legacy IDs being random
|
||||
propertyId = ObjectIDManager::GetPersistentID();
|
||||
const uint32_t maxTries = 100;
|
||||
uint32_t tries = 0;
|
||||
while (Database::Get()->GetPropertyInfo(propertyId) && tries < maxTries) {
|
||||
tries++;
|
||||
LOG("Found a duplicate property %llu, getting a new propertyId", propertyId);
|
||||
propertyId = ObjectIDManager::GetPersistentID();
|
||||
}
|
||||
|
||||
IProperty::Info info;
|
||||
info.id = propertyId;
|
||||
@@ -374,46 +382,45 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
|
||||
node->position = position;
|
||||
node->rotation = rotation;
|
||||
|
||||
ObjectIDManager::RequestPersistentID([this, node, modelLOT, entity, position, rotation, originalRotation](uint32_t persistentId) {
|
||||
SpawnerInfo info{};
|
||||
SpawnerInfo info{};
|
||||
|
||||
info.templateID = modelLOT;
|
||||
info.nodes = { node };
|
||||
info.templateScale = 1.0f;
|
||||
info.activeOnLoad = true;
|
||||
info.amountMaintained = 1;
|
||||
info.respawnTime = 10;
|
||||
info.templateID = modelLOT;
|
||||
info.nodes = { node };
|
||||
info.templateScale = 1.0f;
|
||||
info.activeOnLoad = true;
|
||||
info.amountMaintained = 1;
|
||||
info.respawnTime = 10;
|
||||
|
||||
info.emulated = true;
|
||||
info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID();
|
||||
info.emulated = true;
|
||||
info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID();
|
||||
|
||||
info.spawnerID = persistentId;
|
||||
GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT);
|
||||
info.spawnerID = ObjectIDManager::GetPersistentID();
|
||||
GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT);
|
||||
|
||||
const auto spawnerId = Game::zoneManager->MakeSpawner(info);
|
||||
const auto spawnerId = Game::zoneManager->MakeSpawner(info);
|
||||
|
||||
auto* spawner = Game::zoneManager->GetSpawner(spawnerId);
|
||||
auto* spawner = Game::zoneManager->GetSpawner(spawnerId);
|
||||
|
||||
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"modelBehaviors", 0));
|
||||
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"userModelID", info.spawnerID));
|
||||
info.nodes[0]->config.push_back(new LDFData<int>(u"modelType", 2));
|
||||
info.nodes[0]->config.push_back(new LDFData<bool>(u"propertyObjectID", true));
|
||||
info.nodes[0]->config.push_back(new LDFData<int>(u"componentWhitelist", 1));
|
||||
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"modelBehaviors", 0));
|
||||
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"userModelID", info.spawnerID));
|
||||
info.nodes[0]->config.push_back(new LDFData<int>(u"modelType", 2));
|
||||
info.nodes[0]->config.push_back(new LDFData<bool>(u"propertyObjectID", true));
|
||||
info.nodes[0]->config.push_back(new LDFData<int>(u"componentWhitelist", 1));
|
||||
|
||||
auto* model = spawner->Spawn();
|
||||
auto* modelComponent = model->GetComponent<ModelComponent>();
|
||||
if (modelComponent) modelComponent->Pause();
|
||||
auto* model = spawner->Spawn();
|
||||
auto* modelComponent = model->GetComponent<ModelComponent>();
|
||||
if (modelComponent) modelComponent->Pause();
|
||||
|
||||
models.insert_or_assign(model->GetObjectID(), spawnerId);
|
||||
models.insert_or_assign(model->GetObjectID(), spawnerId);
|
||||
|
||||
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), position, m_Parent->GetObjectID(), 14, originalRotation);
|
||||
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), position, m_Parent->GetObjectID(), 14, originalRotation);
|
||||
|
||||
GameMessages::SendUGCEquipPreCreateBasedOnEditMode(entity->GetObjectID(), entity->GetSystemAddress(), 0, spawnerId);
|
||||
GameMessages::SendUGCEquipPreCreateBasedOnEditMode(entity->GetObjectID(), entity->GetSystemAddress(), 0, spawnerId);
|
||||
|
||||
GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity);
|
||||
|
||||
Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity);
|
||||
});
|
||||
// Progress place model missions
|
||||
auto missionComponent = entity->GetComponent<MissionComponent>();
|
||||
if (missionComponent != nullptr) missionComponent->Progress(eMissionTaskType::PLACE_MODEL, 0);
|
||||
@@ -693,7 +700,7 @@ void PropertyManagementComponent::Save() {
|
||||
// save the behaviors of the model
|
||||
for (const auto& [behaviorId, behaviorStr] : modelBehaviors) {
|
||||
if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue;
|
||||
IBehaviors::Info info {
|
||||
IBehaviors::Info info{
|
||||
.behaviorId = behaviorId,
|
||||
.characterId = character->GetID(),
|
||||
.behaviorInfo = behaviorStr
|
||||
@@ -821,7 +828,7 @@ void PropertyManagementComponent::OnChatMessageReceived(const std::string& sMess
|
||||
if (!model) continue;
|
||||
auto* const modelComponent = model->GetComponent<ModelComponent>();
|
||||
if (!modelComponent) continue;
|
||||
|
||||
|
||||
modelComponent->OnChatMessageReceived(sMessage);
|
||||
}
|
||||
}
|
||||
|
@@ -2555,7 +2555,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
||||
|
||||
uint32_t sd0Size;
|
||||
inStream.Read(sd0Size);
|
||||
std::shared_ptr<char[]> sd0Data(new char[sd0Size]);
|
||||
std::unique_ptr<char[]> sd0Data(new char[sd0Size]);
|
||||
|
||||
if (sd0Data == nullptr) return;
|
||||
|
||||
@@ -2579,115 +2579,121 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
||||
|
||||
//Now, the cave of dragons:
|
||||
|
||||
//We runs this in async because the http library here is blocking, meaning it'll halt the thread.
|
||||
//But we don't want the server to go unresponsive, because then the client would disconnect.
|
||||
|
||||
//We need to get a new ID for our model first:
|
||||
ObjectIDManager::RequestPersistentID([=](uint32_t newID) {
|
||||
if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return;
|
||||
LWOOBJID newIDL = newID;
|
||||
GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER);
|
||||
if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return;
|
||||
const uint32_t maxRetries = 100;
|
||||
uint32_t retries = 0;
|
||||
bool blueprintIDExists = true;
|
||||
bool modelExists = true;
|
||||
|
||||
LWOOBJID blueprintID = ObjectIDManager::GenerateRandomObjectID();
|
||||
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
|
||||
// Legacy logic to check for old random IDs (regenerating these is not really feasible)
|
||||
// Probably good to have this anyway in case someone messes with the last_object_id or it gets reset somehow
|
||||
LWOOBJID newIDL = LWOOBJID_EMPTY;
|
||||
LWOOBJID blueprintID = LWOOBJID_EMPTY;
|
||||
do {
|
||||
if (newIDL != LWOOBJID_EMPTY) LOG("Generating blueprintID for UGC model, collision with existing model ID: %llu", blueprintID);
|
||||
newIDL = ObjectIDManager::GetPersistentID();
|
||||
blueprintID = ObjectIDManager::GetPersistentID();
|
||||
++retries;
|
||||
blueprintIDExists = Database::Get()->GetUgcModel(blueprintID).has_value();
|
||||
modelExists = Database::Get()->GetModel(newIDL).has_value();
|
||||
} while ((blueprintIDExists || modelExists) && retries < maxRetries);
|
||||
|
||||
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
|
||||
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
|
||||
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
|
||||
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
|
||||
|
||||
const auto zoneId = worldId.GetMapID();
|
||||
const auto cloneId = worldId.GetCloneID();
|
||||
const auto zoneId = worldId.GetMapID();
|
||||
const auto cloneId = worldId.GetCloneID();
|
||||
|
||||
auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
|
||||
LWOOBJID propertyId = LWOOBJID_EMPTY;
|
||||
if (propertyInfo) propertyId = propertyInfo->id;
|
||||
auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
|
||||
LWOOBJID propertyId = LWOOBJID_EMPTY;
|
||||
if (propertyInfo) propertyId = propertyInfo->id;
|
||||
|
||||
// Save the binary data to the Sd0 buffer
|
||||
std::string str(sd0Data.get(), sd0Size);
|
||||
std::istringstream sd0DataStream(str);
|
||||
Sd0 sd0(sd0DataStream);
|
||||
// Save the binary data to the Sd0 buffer
|
||||
std::string str(sd0Data.get(), sd0Size);
|
||||
std::istringstream sd0DataStream(str);
|
||||
Sd0 sd0(sd0DataStream);
|
||||
|
||||
// Uncompress the data and normalize the position
|
||||
const auto asStr = sd0.GetAsStringUncompressed();
|
||||
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
|
||||
// Uncompress the data and normalize the position
|
||||
const auto asStr = sd0.GetAsStringUncompressed();
|
||||
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
|
||||
|
||||
// Recompress the data and save to the database
|
||||
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
|
||||
auto sd0AsStream = sd0.GetAsStream();
|
||||
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
|
||||
// Recompress the data and save to the database
|
||||
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
|
||||
auto sd0AsStream = sd0.GetAsStream();
|
||||
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
|
||||
|
||||
//Insert into the db as a BBB model:
|
||||
IPropertyContents::Model model;
|
||||
model.id = newIDL;
|
||||
model.ugcId = blueprintID;
|
||||
model.position = newCenter;
|
||||
model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
model.lot = 14;
|
||||
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
|
||||
//Insert into the db as a BBB model:
|
||||
IPropertyContents::Model model;
|
||||
model.id = newIDL;
|
||||
model.ugcId = blueprintID;
|
||||
model.position = newCenter;
|
||||
model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
model.lot = 14;
|
||||
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
|
||||
|
||||
/*
|
||||
Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream.
|
||||
(or you uncomment the lxfml decomp stuff above)
|
||||
*/
|
||||
/*
|
||||
Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream.
|
||||
(or you uncomment the lxfml decomp stuff above)
|
||||
*/
|
||||
|
||||
////Send off to UGC for processing, if enabled:
|
||||
//if (Game::config->GetValue("ugc_remote") == "1") {
|
||||
// std::string ugcIP = Game::config->GetValue("ugc_ip");
|
||||
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
|
||||
// //Send off to UGC for processing, if enabled:
|
||||
// if (Game::config->GetValue("ugc_remote") == "1") {
|
||||
// std::string ugcIP = Game::config->GetValue("ugc_ip");
|
||||
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
|
||||
|
||||
// httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^
|
||||
// httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^
|
||||
|
||||
// //Send out a request:
|
||||
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
|
||||
// cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml");
|
||||
// //Send out a request:
|
||||
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
|
||||
// cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml");
|
||||
|
||||
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model &
|
||||
// //the nif, hkx and checksum files are ready to be downloaded from cache.
|
||||
//}
|
||||
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model &
|
||||
// //the nif, hkx and checksum files are ready to be downloaded from cache.
|
||||
// }
|
||||
|
||||
//Tell the client their model is saved: (this causes us to actually pop out of our current state):
|
||||
const auto& newSd0 = sd0.GetAsVector();
|
||||
uint32_t sd0Size{};
|
||||
for (const auto& chunk : newSd0) sd0Size += chunk.size();
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
||||
bitStream.Write(localId);
|
||||
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
|
||||
bitStream.Write<uint32_t>(1);
|
||||
bitStream.Write(blueprintID);
|
||||
//Tell the client their model is saved: (this causes us to actually pop out of our current state):
|
||||
const auto& newSd0 = sd0.GetAsVector();
|
||||
uint32_t newSd0Size{};
|
||||
for (const auto& chunk : newSd0) newSd0Size += chunk.size();
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
||||
bitStream.Write(localId);
|
||||
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
|
||||
bitStream.Write<uint32_t>(1);
|
||||
bitStream.Write(blueprintID);
|
||||
|
||||
bitStream.Write(sd0Size);
|
||||
bitStream.Write(newSd0Size);
|
||||
|
||||
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
|
||||
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
|
||||
|
||||
SEND_PACKET;
|
||||
SEND_PACKET;
|
||||
|
||||
//Now we have to construct this object:
|
||||
//Now we have to construct this object:
|
||||
|
||||
EntityInfo info;
|
||||
info.lot = 14;
|
||||
info.pos = newCenter;
|
||||
info.rot = {};
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
EntityInfo info;
|
||||
info.lot = 14;
|
||||
info.pos = newCenter;
|
||||
info.rot = {};
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
|
||||
info.settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
|
||||
info.settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
|
||||
info.settings.push_back(new LDFData<int>(u"modelType", 2));
|
||||
info.settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
|
||||
info.settings.push_back(new LDFData<LWOOBJID>(u"userModelID", newIDL));
|
||||
info.settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
|
||||
info.settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
|
||||
info.settings.push_back(new LDFData<int>(u"modelType", 2));
|
||||
info.settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
|
||||
info.settings.push_back(new LDFData<LWOOBJID>(u"userModelID", newIDL));
|
||||
|
||||
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||
if (newEntity) {
|
||||
Game::entityManager->ConstructEntity(newEntity);
|
||||
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||
if (newEntity) {
|
||||
Game::entityManager->ConstructEntity(newEntity);
|
||||
|
||||
//Make sure the propMgmt doesn't delete our model after the server dies
|
||||
//Trying to do this after the entity is constructed. Shouldn't really change anything but
|
||||
//there was an issue with builds not appearing since it was placed above ConstructEntity.
|
||||
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
|
||||
}
|
||||
|
||||
});
|
||||
//Make sure the propMgmt doesn't delete our model after the server dies
|
||||
//Trying to do this after the entity is constructed. Shouldn't really change anything but
|
||||
//there was an issue with builds not appearing since it was placed above ConstructEntity.
|
||||
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
|
||||
}
|
||||
}
|
||||
|
||||
void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
@@ -5526,55 +5532,52 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
|
||||
}
|
||||
}
|
||||
|
||||
ObjectIDManager::RequestPersistentID([=](uint32_t newId) {
|
||||
LOG("Build finished");
|
||||
GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
|
||||
GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it
|
||||
LOG("Build finished");
|
||||
GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
|
||||
GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it
|
||||
|
||||
//inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap
|
||||
//Game::entityManager->SerializeEntity(entity);
|
||||
//inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap
|
||||
//Game::entityManager->SerializeEntity(entity);
|
||||
|
||||
const auto moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", modules);
|
||||
const auto moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", modules);
|
||||
|
||||
std::vector<LDFBaseData*> config;
|
||||
config.push_back(moduleAssembly);
|
||||
std::vector<LDFBaseData*> config;
|
||||
config.push_back(moduleAssembly);
|
||||
|
||||
LWOOBJID newIdBig = newId;
|
||||
GeneralUtils::SetBit(newIdBig, eObjectBits::CHARACTER);
|
||||
LWOOBJID newID = ObjectIDManager::GetPersistentID();
|
||||
|
||||
if (count == 3) {
|
||||
inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
|
||||
} else if (count == 7) {
|
||||
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
|
||||
if (count == 3) {
|
||||
inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID);
|
||||
} else if (count == 7) {
|
||||
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID);
|
||||
}
|
||||
|
||||
auto* pCharacter = character->GetCharacter();
|
||||
Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newID, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
|
||||
|
||||
auto* missionComponent = character->GetComponent<MissionComponent>();
|
||||
|
||||
if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) {
|
||||
if (missionComponent != nullptr) {
|
||||
missionComponent->Progress(eMissionTaskType::SCRIPT, entity->GetLOT(), entity->GetObjectID());
|
||||
if (count >= 7 && everyPieceSwapped) missionComponent->Progress(eMissionTaskType::RACING, LWOOBJID_EMPTY, static_cast<LWOOBJID>(eRacingTaskParam::MODULAR_BUILDING));
|
||||
}
|
||||
}
|
||||
|
||||
auto* pCharacter = character->GetCharacter();
|
||||
Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newIdBig, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
|
||||
ScriptComponent* script = static_cast<ScriptComponent*>(entity->GetComponent(eReplicaComponentType::SCRIPT));
|
||||
|
||||
auto* missionComponent = character->GetComponent<MissionComponent>();
|
||||
entity->GetScript()->OnModularBuildExit(entity, character, count >= 3, modList);
|
||||
|
||||
if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) {
|
||||
if (missionComponent != nullptr) {
|
||||
missionComponent->Progress(eMissionTaskType::SCRIPT, entity->GetLOT(), entity->GetObjectID());
|
||||
if (count >= 7 && everyPieceSwapped) missionComponent->Progress(eMissionTaskType::RACING, LWOOBJID_EMPTY, static_cast<LWOOBJID>(eRacingTaskParam::MODULAR_BUILDING));
|
||||
}
|
||||
}
|
||||
// Move remaining temp models back to models
|
||||
std::vector<Item*> items;
|
||||
|
||||
ScriptComponent* script = static_cast<ScriptComponent*>(entity->GetComponent(eReplicaComponentType::SCRIPT));
|
||||
for (const auto& pair : temp->GetItems()) {
|
||||
items.push_back(pair.second);
|
||||
}
|
||||
|
||||
entity->GetScript()->OnModularBuildExit(entity, character, count >= 3, modList);
|
||||
|
||||
// Move remaining temp models back to models
|
||||
std::vector<Item*> items;
|
||||
|
||||
for (const auto& pair : temp->GetItems()) {
|
||||
items.push_back(pair.second);
|
||||
}
|
||||
|
||||
for (auto* item : items) {
|
||||
inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false);
|
||||
}
|
||||
});
|
||||
for (auto* item : items) {
|
||||
inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,8 +5,11 @@
|
||||
#include "InventoryComponent.h"
|
||||
#include "eItemType.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "ObjectIDManager.h"
|
||||
#include "eObjectBits.h"
|
||||
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include <ranges>
|
||||
|
||||
std::vector<LOT> Inventory::m_GameMasterRestrictedItems = {
|
||||
1727, // GM Only - JetPack
|
||||
@@ -317,3 +320,16 @@ Inventory::~Inventory() {
|
||||
|
||||
items.clear();
|
||||
}
|
||||
|
||||
void Inventory::RegenerateItemIDs() {
|
||||
std::map<LWOOBJID, Item*> newItems{};
|
||||
for (auto* const item : items | std::views::values) {
|
||||
const auto oldID = item->GetId();
|
||||
const auto newID = item->GenerateID();
|
||||
LOG("Updating item ID from %llu to %llu", oldID, newID);
|
||||
newItems.insert_or_assign(newID, item);
|
||||
}
|
||||
|
||||
// We don't want to delete the item pointers, we're just moving from map to map
|
||||
items = newItems;
|
||||
}
|
||||
|
@@ -158,6 +158,8 @@ public:
|
||||
*/
|
||||
void DeleteAllItems();
|
||||
|
||||
void RegenerateItemIDs();
|
||||
|
||||
~Inventory();
|
||||
|
||||
private:
|
||||
|
@@ -98,21 +98,12 @@ Item::Item(
|
||||
this->preconditions = new PreconditionExpression(this->info->reqPrecondition);
|
||||
this->subKey = subKey;
|
||||
|
||||
LWOOBJID id = ObjectIDManager::GenerateRandomObjectID();
|
||||
|
||||
GeneralUtils::SetBit(id, eObjectBits::CHARACTER);
|
||||
|
||||
const auto type = static_cast<eItemType>(info->itemType);
|
||||
|
||||
if (type == eItemType::MOUNT) {
|
||||
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
|
||||
}
|
||||
|
||||
this->id = id;
|
||||
auto* const inventoryComponent = inventory->GetComponent();
|
||||
GenerateID();
|
||||
|
||||
inventory->AddManagedItem(this);
|
||||
|
||||
auto* entity = inventory->GetComponent()->GetParent();
|
||||
auto* entity = inventoryComponent->GetParent();
|
||||
GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, static_cast<int>(this->count), subKey, lootSourceType);
|
||||
|
||||
if (isModMoveAndEquip) {
|
||||
@@ -120,7 +111,7 @@ Item::Item(
|
||||
|
||||
LOG("Move and equipped (%i) from (%i)", this->lot, this->inventory->GetType());
|
||||
|
||||
Game::entityManager->SerializeEntity(inventory->GetComponent()->GetParent());
|
||||
Game::entityManager->SerializeEntity(inventoryComponent->GetParent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,3 +564,28 @@ void Item::LoadConfigXml(const tinyxml2::XMLElement& i) {
|
||||
config.push_back(LDFBaseData::DataFromString(value));
|
||||
}
|
||||
}
|
||||
|
||||
LWOOBJID Item::GenerateID() {
|
||||
auto* const inventoryComponent = inventory->GetComponent();
|
||||
const bool isPlayer = inventoryComponent->GetParent()->IsPlayer();
|
||||
LWOOBJID id{};
|
||||
|
||||
// Only players and non-proxy items get persistent IDs (since they are the only ones that will persist between worlds)
|
||||
if (isPlayer && parent == LWOOBJID_EMPTY) {
|
||||
id = ObjectIDManager::GetPersistentID();
|
||||
} else {
|
||||
id = ObjectIDManager::GenerateObjectID();
|
||||
GeneralUtils::SetBit(id, eObjectBits::SPAWNED);
|
||||
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
|
||||
}
|
||||
|
||||
LOG("Parent %i lot %u Generated id %u:%llu", parent, GetLot(), static_cast<uint32_t>(id), id);
|
||||
const auto type = static_cast<eItemType>(info->itemType);
|
||||
|
||||
if (type == eItemType::MOUNT) {
|
||||
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
|
||||
}
|
||||
|
||||
this->id = id;
|
||||
return id;
|
||||
}
|
||||
|
@@ -228,6 +228,8 @@ public:
|
||||
|
||||
void LoadConfigXml(const tinyxml2::XMLElement& i);
|
||||
|
||||
LWOOBJID GenerateID();
|
||||
|
||||
private:
|
||||
/**
|
||||
* The object ID of this item
|
||||
|
@@ -35,25 +35,21 @@
|
||||
void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) {
|
||||
BehaviorMessageBase msgBase{ context.arguments };
|
||||
const auto oldBehaviorID = msgBase.GetBehaviorId();
|
||||
ObjectIDManager::RequestPersistentID(
|
||||
[context, oldBehaviorID](uint32_t persistentId) {
|
||||
if (!context) {
|
||||
LOG("Model to update behavior ID for is null. Cannot update ID.");
|
||||
return;
|
||||
}
|
||||
LWOOBJID persistentIdBig = persistentId;
|
||||
GeneralUtils::SetBit(persistentIdBig, eObjectBits::CHARACTER);
|
||||
// This updates the behavior ID of the behavior should this be a new behavior
|
||||
AMFArrayValue args;
|
||||
if (!context) {
|
||||
LOG("Model to update behavior ID for is null. Cannot update ID.");
|
||||
return;
|
||||
}
|
||||
LWOOBJID persistentIdBig = ObjectIDManager::GetPersistentID();
|
||||
// This updates the behavior ID of the behavior should this be a new behavior
|
||||
AMFArrayValue args;
|
||||
|
||||
args.Insert("behaviorID", std::to_string(persistentIdBig));
|
||||
args.Insert("objectID", std::to_string(context.modelComponent->GetParent()->GetObjectID()));
|
||||
args.Insert("behaviorID", std::to_string(persistentIdBig));
|
||||
args.Insert("objectID", std::to_string(context.modelComponent->GetParent()->GetObjectID()));
|
||||
|
||||
GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorID", args);
|
||||
context.modelComponent->UpdatePendingBehaviorId(persistentIdBig, oldBehaviorID);
|
||||
GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorID", args);
|
||||
context.modelComponent->UpdatePendingBehaviorId(persistentIdBig, oldBehaviorID);
|
||||
|
||||
ControlBehaviors::Instance().SendBehaviorListToClient(context);
|
||||
});
|
||||
ControlBehaviors::Instance().SendBehaviorListToClient(context);
|
||||
}
|
||||
|
||||
void ControlBehaviors::SendBehaviorListToClient(const ControlBehaviorContext& context) {
|
||||
|
@@ -5,47 +5,38 @@
|
||||
#include "Database.h"
|
||||
#include "Logger.h"
|
||||
#include "Game.h"
|
||||
#include "eObjectBits.h"
|
||||
|
||||
//! The persistent ID request
|
||||
struct PersistentIDRequest {
|
||||
PersistentIDRequest(const uint64_t& requestID, const std::function<void(uint32_t)>& callback) : requestID(requestID), callback(callback) {}
|
||||
uint64_t requestID;
|
||||
|
||||
std::function<void(uint32_t)> callback;
|
||||
};
|
||||
// proxy items and dropped loot get SPAWNED + CLIENT flag and need to have the ChangeObjectWorldState message sent to them
|
||||
// should the spawners from vanity also have the CLIENT flag?
|
||||
|
||||
namespace {
|
||||
std::vector<PersistentIDRequest> Requests; //!< All outstanding persistent ID requests
|
||||
uint64_t CurrentRequestID = 0; //!< The current request ID
|
||||
uint32_t CurrentObjectID = uint32_t(1152921508165007067); //!< The current object ID
|
||||
std::uniform_int_distribution<int> Uni(10000000, INT32_MAX);
|
||||
// Start the range in a way that it when first called it will fetch some new persistent IDs
|
||||
std::optional<IObjectIdTracker::Range> CurrentRange = std::nullopt;
|
||||
uint32_t CurrentObjectID = uint32_t(1152921508165007067); // The current object ID (this should really start at the highest current ID in the world, then increment from there)
|
||||
};
|
||||
|
||||
//! Requests a persistent ID
|
||||
void ObjectIDManager::RequestPersistentID(const std::function<void(uint32_t)> callback) {
|
||||
const auto& request = Requests.emplace_back(++CurrentRequestID, callback);
|
||||
uint64_t ObjectIDManager::GetPersistentID() {
|
||||
if (!CurrentRange.has_value() || CurrentRange->minID > CurrentRange->maxID) {
|
||||
CurrentRange = Database::Get()->GetPersistentIdRange();
|
||||
// We're getting close to being out of IDs in this range, log a warning
|
||||
const auto WARNING_RANGE = 70368744100000ULL;
|
||||
if (CurrentRange->minID >= 70368744100000ULL) {
|
||||
LOG("WARNING: Your server is running low on persistent IDs, please consider an ID squash in the near future.");
|
||||
}
|
||||
|
||||
MasterPackets::SendPersistentIDRequest(Game::server, request.requestID);
|
||||
LOG("Reserved object ID range: %llu - %llu", CurrentRange->minID, CurrentRange->maxID);
|
||||
}
|
||||
|
||||
const auto usedID = CurrentRange->minID++;
|
||||
auto toReturn = usedID;
|
||||
// Any IDs gotten from persistent IDs use the CHARACTER bit
|
||||
GeneralUtils::SetBit(toReturn, eObjectBits::CHARACTER);
|
||||
LOG("Using ID: %llu:%llu", toReturn, usedID);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
//! Handles a persistent ID response
|
||||
void ObjectIDManager::HandleRequestPersistentIDResponse(const uint64_t requestID, const uint32_t persistentID) {
|
||||
auto it = std::find_if(Requests.begin(), Requests.end(), [requestID](const PersistentIDRequest& request) {
|
||||
return request.requestID == requestID;
|
||||
});
|
||||
|
||||
if (it == Requests.end()) return;
|
||||
|
||||
it->callback(persistentID);
|
||||
Requests.erase(it);
|
||||
}
|
||||
|
||||
//! Handles cases where we have to get a unique object ID synchronously
|
||||
uint32_t ObjectIDManager::GenerateRandomObjectID() {
|
||||
return Uni(Game::randomEngine);
|
||||
}
|
||||
|
||||
//! Generates an object ID server-sided (used for regular entities like smashables)
|
||||
// Generates an object ID server-sided (used for regular entities like smashables)
|
||||
uint32_t ObjectIDManager::GenerateObjectID() {
|
||||
return ++CurrentObjectID;
|
||||
}
|
||||
|
@@ -5,36 +5,25 @@
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
/*!
|
||||
\file ObjectIDManager.h
|
||||
\brief A manager for handling object ID generation
|
||||
/**
|
||||
* There are 2 types of IDs:
|
||||
* Persistent IDs - These are used for anything that needs to be persist between worlds.
|
||||
* Ephemeral IDs - These are used for any objects that only need to be unique for this world session.
|
||||
*/
|
||||
|
||||
//! The Object ID Manager
|
||||
namespace ObjectIDManager {
|
||||
//! Requests a persistent ID
|
||||
/*!
|
||||
\param callback The callback function
|
||||
|
||||
/**
|
||||
* @brief Returns a Persistent ID with the CHARACTER bit set.
|
||||
*
|
||||
* @return uint64_t A unique persistent ID with the CHARACTER bit set.
|
||||
*/
|
||||
void RequestPersistentID(const std::function<void(uint32_t)> callback);
|
||||
uint64_t GetPersistentID();
|
||||
|
||||
|
||||
//! Handles a persistent ID response
|
||||
/*!
|
||||
\param requestID The request ID
|
||||
\param persistentID The persistent ID
|
||||
*/
|
||||
void HandleRequestPersistentIDResponse(const uint64_t requestID, const uint32_t persistentID);
|
||||
|
||||
//! Generates an object ID server-sided
|
||||
/*!
|
||||
\return A generated object ID
|
||||
/**
|
||||
* @brief Generates an ephemeral object ID for non-persistent objects.
|
||||
*
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t GenerateObjectID();
|
||||
|
||||
//! Generates a random object ID server-sided
|
||||
/*!
|
||||
\return A generated object ID
|
||||
*/
|
||||
uint32_t GenerateRandomObjectID();
|
||||
};
|
||||
|
Reference in New Issue
Block a user