From 151d05e8535b4d08153b09c7028d56a725d49d75 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Sun, 28 Sep 2025 20:30:59 -0700 Subject: [PATCH] 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 --- dCommon/dEnums/MessageType/Master.h | 4 +- dCommon/dEnums/eCharacterVersion.h | 3 +- .../GameDatabase/ITables/IObjectIdTracker.h | 11 +- dDatabase/GameDatabase/ITables/IProperty.h | 3 + .../GameDatabase/ITables/IPropertyContents.h | 2 +- dDatabase/GameDatabase/ITables/IUgc.h | 2 + dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 8 +- .../MySQL/Tables/ObjectIdTracker.cpp | 33 ++- .../GameDatabase/MySQL/Tables/Property.cpp | 59 ++-- .../MySQL/Tables/PropertyContents.cpp | 35 +-- dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp | 43 +-- .../GameDatabase/SQLite/SQLiteDatabase.h | 8 +- .../SQLite/Tables/ObjectIdTracker.cpp | 31 ++- .../GameDatabase/SQLite/Tables/Property.cpp | 59 ++-- .../SQLite/Tables/PropertyContents.cpp | 35 +-- dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp | 41 +-- .../GameDatabase/TestSQL/TestSQLDatabase.cpp | 9 +- .../GameDatabase/TestSQL/TestSQLDatabase.h | 8 +- dDatabase/ModelNormalizeMigration.cpp | 16 +- dGame/UserManager.cpp | 111 ++++---- dGame/dComponents/InventoryComponent.cpp | 6 + dGame/dComponents/InventoryComponent.h | 3 + dGame/dComponents/PetComponent.cpp | 14 +- .../dComponents/PropertyEntranceComponent.cpp | 2 +- .../PropertyManagementComponent.cpp | 73 ++--- dGame/dGameMessages/GameMessages.cpp | 253 +++++++++--------- dGame/dInventory/Inventory.cpp | 16 ++ dGame/dInventory/Inventory.h | 2 + dGame/dInventory/Item.cpp | 42 ++- dGame/dInventory/Item.h | 2 + dGame/dPropertyBehaviors/ControlBehaviors.cpp | 28 +- dGame/dUtilities/ObjectIDManager.cpp | 57 ++-- dGame/dUtilities/ObjectIDManager.h | 39 +-- dMasterServer/CMakeLists.txt | 1 - dMasterServer/MasterServer.cpp | 16 -- dMasterServer/PersistentIDManager.cpp | 45 ---- dMasterServer/PersistentIDManager.h | 23 -- dNet/MasterPackets.cpp | 17 -- dNet/MasterPackets.h | 5 - .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 1 - dWorldServer/WorldServer.cpp | 164 ++++++------ .../mysql/26_update_property_contents_ids.sql | 1 + .../sqlite/9_update_property_contents_ids.sql | 1 + 43 files changed, 676 insertions(+), 656 deletions(-) delete mode 100644 dMasterServer/PersistentIDManager.cpp delete mode 100644 dMasterServer/PersistentIDManager.h create mode 100644 migrations/dlu/mysql/26_update_property_contents_ids.sql create mode 100644 migrations/dlu/sqlite/9_update_property_contents_ids.sql diff --git a/dCommon/dEnums/MessageType/Master.h b/dCommon/dEnums/MessageType/Master.h index b6054d7f..1529ca51 100644 --- a/dCommon/dEnums/MessageType/Master.h +++ b/dCommon/dEnums/MessageType/Master.h @@ -3,9 +3,7 @@ namespace MessageType { enum class Master : uint32_t { - REQUEST_PERSISTENT_ID = 1, - REQUEST_PERSISTENT_ID_RESPONSE, - REQUEST_ZONE_TRANSFER, + REQUEST_ZONE_TRANSFER = 1, REQUEST_ZONE_TRANSFER_RESPONSE, SERVER_INFO, REQUEST_SESSION_KEY, diff --git a/dCommon/dEnums/eCharacterVersion.h b/dCommon/dEnums/eCharacterVersion.h index 477467b8..15610c3a 100644 --- a/dCommon/dEnums/eCharacterVersion.h +++ b/dCommon/dEnums/eCharacterVersion.h @@ -19,7 +19,8 @@ enum class eCharacterVersion : uint32_t { // Fixes nexus force explorer missions NJ_JAYMISSIONS, NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories - UP_TO_DATE, // will become PET_IDS + PET_IDS, // Fixes pet ids in player inventories + UP_TO_DATE, // will become INVENTORY_PERSISTENT_IDS }; #endif //!__ECHARACTERVERSION__H__ diff --git a/dDatabase/GameDatabase/ITables/IObjectIdTracker.h b/dDatabase/GameDatabase/ITables/IObjectIdTracker.h index cbe34b6d..6344f2e7 100644 --- a/dDatabase/GameDatabase/ITables/IObjectIdTracker.h +++ b/dDatabase/GameDatabase/ITables/IObjectIdTracker.h @@ -6,14 +6,19 @@ class IObjectIdTracker { public: + // Only the first 48 bits of the ids are the id, the last 16 bits are reserved for flags. + struct Range { + uint64_t minID{}; // Only the first 48 bits are the id, the last 16 bits are reserved for flags. + uint64_t maxID{}; // Only the first 48 bits are the id, the last 16 bits are reserved for flags. + }; + // Get the current persistent id. - virtual std::optional GetCurrentPersistentId() = 0; + virtual std::optional GetCurrentPersistentId() = 0; // Insert the default persistent id. virtual void InsertDefaultPersistentId() = 0; - // Update the persistent id. - virtual void UpdatePersistentId(const uint32_t newId) = 0; + virtual Range GetPersistentIdRange() = 0; }; #endif //!__IOBJECTIDTRACKER__H__ diff --git a/dDatabase/GameDatabase/ITables/IProperty.h b/dDatabase/GameDatabase/ITables/IProperty.h index 78f58284..c4957e5b 100644 --- a/dDatabase/GameDatabase/ITables/IProperty.h +++ b/dDatabase/GameDatabase/ITables/IProperty.h @@ -39,6 +39,9 @@ public: std::vector entries; }; + // Get the property info for the given property id. + virtual std::optional GetPropertyInfo(const LWOOBJID id) = 0; + // Get the property info for the given property id. virtual std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0; diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index b2bb1a97..9a16d5d7 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -45,6 +45,6 @@ public: virtual void RemoveModel(const LWOOBJID& modelId) = 0; // Gets a model by ID - virtual Model GetModel(const LWOOBJID modelID) = 0; + virtual std::optional GetModel(const LWOOBJID modelID) = 0; }; #endif //!__IPROPERTIESCONTENTS__H__ diff --git a/dDatabase/GameDatabase/ITables/IUgc.h b/dDatabase/GameDatabase/ITables/IUgc.h index cbc770b8..9f6d2ef1 100644 --- a/dDatabase/GameDatabase/ITables/IUgc.h +++ b/dDatabase/GameDatabase/ITables/IUgc.h @@ -29,5 +29,7 @@ public: // Inserts a new UGC model into the database. virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0; + + virtual std::optional GetUgcModel(const LWOOBJID ugcId) = 0; }; #endif //!__IUGC__H__ diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index e355ce62..456ab5fa 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -98,9 +98,9 @@ public: void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void SetMasterInfo(const IServers::MasterInfo& info) override; - std::optional GetCurrentPersistentId() override; + std::optional GetCurrentPersistentId() override; + IObjectIdTracker::Range GetPersistentIdRange() override; void InsertDefaultPersistentId() override; - void UpdatePersistentId(const uint32_t id) override; std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; @@ -127,7 +127,9 @@ public: void DeleteUgcBuild(const LWOOBJID bigId) override; uint32_t GetAccountCount() override; bool IsNameInUse(const std::string_view name) override; - IPropertyContents::Model GetModel(const LWOOBJID modelID) override; + std::optional GetModel(const LWOOBJID modelID) override; + std::optional GetUgcModel(const LWOOBJID ugcId) override; + std::optional GetPropertyInfo(const LWOOBJID id) override; sql::PreparedStatement* CreatePreppedStmt(const std::string& query); private: diff --git a/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp b/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp index f22cd855..462a0edf 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp @@ -1,17 +1,42 @@ #include "MySQLDatabase.h" -std::optional MySQLDatabase::GetCurrentPersistentId() { +std::optional MySQLDatabase::GetCurrentPersistentId() { auto result = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); if (!result->next()) { return std::nullopt; } - return result->getUInt("last_object_id"); + return result->getUInt64("last_object_id"); } void MySQLDatabase::InsertDefaultPersistentId() { ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); } -void MySQLDatabase::UpdatePersistentId(const uint32_t newId) { - ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId); +IObjectIdTracker::Range MySQLDatabase::GetPersistentIdRange() { + IObjectIdTracker::Range range; + auto prevCommit = GetAutoCommit(); + SetAutoCommit(false); + + // THIS MUST ABSOLUTELY NOT FAIL. These IDs are expected to be unique. As such a transactional select is used to safely get a range + // of IDs that will never be used again. A separate feature could track unused IDs and recycle them, but that is not implemented. + ExecuteCustomQuery("START TRANSACTION;"); + // 200 seems like a good range to reserve at once. Only way this would be noticable is if a player + // added hundreds of items at once. + // Doing the update first ensures that all other connections are blocked from accessing this table until we commit. + auto result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;"); + // If no rows were updated, it means the table is empty, so we need to insert the default row first. + if (result == 0) { + InsertDefaultPersistentId(); + result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;"); + } + + // At this point all connections are waiting on us to finish the transaction, so we can safely select the ID we just set. + auto selectRes = ExecuteSelect("SELECT last_object_id FROM object_id_tracker;"); + selectRes->next(); + range.maxID = selectRes->getUInt64("last_object_id"); + range.minID = range.maxID - 199; + + ExecuteCustomQuery("COMMIT;"); + SetAutoCommit(prevCommit); + return range; } diff --git a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp index 662070df..18916e24 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp @@ -1,6 +1,23 @@ #include "MySQLDatabase.h" #include "ePropertySortType.h" +IProperty::Info ReadPropertyInfo(UniqueResultSet& result) { + IProperty::Info info; + info.id = result->getUInt64("id"); + info.ownerId = result->getInt64("owner_id"); + info.cloneId = result->getUInt64("clone_id"); + info.name = result->getString("name").c_str(); + info.description = result->getString("description").c_str(); + info.privacyOption = result->getInt("privacy_option"); + info.rejectionReason = result->getString("rejection_reason").c_str(); + info.lastUpdatedTime = result->getUInt("last_updated"); + info.claimedTime = result->getUInt("time_claimed"); + info.reputation = result->getUInt("reputation"); + info.modApproved = result->getUInt("mod_approved"); + info.performanceCost = result->getFloat("performance_cost"); + return info; +} + std::optional MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) { std::optional result; std::string query; @@ -117,19 +134,7 @@ std::optional MySQLDatabase::GetProperties(co while (properties->next()) { if (!result) result = IProperty::PropertyEntranceResult(); - auto& entry = result->entries.emplace_back(); - entry.id = properties->getUInt64("id"); - entry.ownerId = properties->getInt64("owner_id"); - entry.cloneId = properties->getUInt64("clone_id"); - entry.name = properties->getString("name").c_str(); - entry.description = properties->getString("description").c_str(); - entry.privacyOption = properties->getInt("privacy_option"); - entry.rejectionReason = properties->getString("rejection_reason").c_str(); - entry.lastUpdatedTime = properties->getUInt("last_updated"); - entry.claimedTime = properties->getUInt("time_claimed"); - entry.reputation = properties->getUInt("reputation"); - entry.modApproved = properties->getUInt("mod_approved"); - entry.performanceCost = properties->getFloat("performance_cost"); + result->entries.push_back(ReadPropertyInfo(properties)); } return result; @@ -144,21 +149,7 @@ std::optional MySQLDatabase::GetPropertyInfo(const LWOMAPID map return std::nullopt; } - IProperty::Info toReturn; - toReturn.id = propertyEntry->getUInt64("id"); - toReturn.ownerId = propertyEntry->getInt64("owner_id"); - toReturn.cloneId = propertyEntry->getUInt64("clone_id"); - toReturn.name = propertyEntry->getString("name").c_str(); - toReturn.description = propertyEntry->getString("description").c_str(); - toReturn.privacyOption = propertyEntry->getInt("privacy_option"); - toReturn.rejectionReason = propertyEntry->getString("rejection_reason").c_str(); - toReturn.lastUpdatedTime = propertyEntry->getUInt("last_updated"); - toReturn.claimedTime = propertyEntry->getUInt("time_claimed"); - toReturn.reputation = propertyEntry->getUInt("reputation"); - toReturn.modApproved = propertyEntry->getUInt("mod_approved"); - toReturn.performanceCost = propertyEntry->getFloat("performance_cost"); - - return toReturn; + return ReadPropertyInfo(propertyEntry); } void MySQLDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { @@ -195,3 +186,15 @@ void MySQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_ zoneId.GetMapID() ); } + +std::optional MySQLDatabase::GetPropertyInfo(const LWOOBJID id) { + auto propertyEntry = ExecuteSelect( + "SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost " + "FROM properties WHERE id = ?;", id); + + if (!propertyEntry->next()) { + return std::nullopt; + } + + return ReadPropertyInfo(propertyEntry); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp index 44394518..cecea68b 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -64,26 +64,27 @@ void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) { ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); } -IPropertyContents::Model MySQLDatabase::GetModel(const LWOOBJID modelID) { +std::optional MySQLDatabase::GetModel(const LWOOBJID modelID) { auto result = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID); - IPropertyContents::Model model{}; + std::optional model = std::nullopt; while (result->next()) { - model.id = result->getUInt64("id"); - model.lot = static_cast(result->getUInt("lot")); - model.position.x = result->getFloat("x"); - model.position.y = result->getFloat("y"); - model.position.z = result->getFloat("z"); - model.rotation.w = result->getFloat("rw"); - model.rotation.x = result->getFloat("rx"); - model.rotation.y = result->getFloat("ry"); - model.rotation.z = result->getFloat("rz"); - model.ugcId = result->getUInt64("ugc_id"); - model.behaviors[0] = result->getUInt64("behavior_1"); - model.behaviors[1] = result->getUInt64("behavior_2"); - model.behaviors[2] = result->getUInt64("behavior_3"); - model.behaviors[3] = result->getUInt64("behavior_4"); - model.behaviors[4] = result->getUInt64("behavior_5"); + model = IPropertyContents::Model{}; + model->id = result->getUInt64("id"); + model->lot = static_cast(result->getUInt("lot")); + model->position.x = result->getFloat("x"); + model->position.y = result->getFloat("y"); + model->position.z = result->getFloat("z"); + model->rotation.w = result->getFloat("rw"); + model->rotation.x = result->getFloat("rx"); + model->rotation.y = result->getFloat("ry"); + model->rotation.z = result->getFloat("rz"); + model->ugcId = result->getUInt64("ugc_id"); + model->behaviors[0] = result->getUInt64("behavior_1"); + model->behaviors[1] = result->getUInt64("behavior_2"); + model->behaviors[2] = result->getUInt64("behavior_3"); + model->behaviors[3] = result->getUInt64("behavior_4"); + model->behaviors[4] = result->getUInt64("behavior_5"); } return model; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp index 031ae6c7..06865c5e 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp @@ -1,5 +1,17 @@ #include "MySQLDatabase.h" +IUgc::Model ReadModel(UniqueResultSet& result) { + IUgc::Model model; + + // blob is owned by the query, so we need to do a deep copy :/ + std::unique_ptr blob(result->getBlob("lxfml")); + model.lxfmlData << blob->rdbuf(); + model.id = result->getUInt64("ugcID"); + model.modelID = result->getUInt64("modelID"); + + return model; +} + std::vector MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) { auto result = ExecuteSelect( "SELECT lxfml, u.id as ugcID, pc.id as modelID FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;", @@ -8,14 +20,7 @@ std::vector MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) std::vector toReturn; while (result->next()) { - IUgc::Model model; - - // blob is owned by the query, so we need to do a deep copy :/ - std::unique_ptr blob(result->getBlob("lxfml")); - model.lxfmlData << blob->rdbuf(); - model.id = result->getUInt64("ugcID"); - model.modelID = result->getUInt64("modelID"); - toReturn.push_back(std::move(model)); + toReturn.push_back(ReadModel(result)); } return toReturn; @@ -27,14 +32,7 @@ std::vector MySQLDatabase::GetAllUgcModels() { std::vector models; models.reserve(result->rowsCount()); while (result->next()) { - IUgc::Model model; - model.id = result->getInt64("ugcID"); - model.modelID = result->getUInt64("modelID"); - - // blob is owned by the query, so we need to do a deep copy :/ - std::unique_ptr blob(result->getBlob("lxfml")); - model.lxfmlData << blob->rdbuf(); - models.push_back(std::move(model)); + models.push_back(ReadModel(result)); } return models; @@ -45,7 +43,7 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() { } void MySQLDatabase::InsertNewUgcModel( - std:: stringstream& sd0Data, // cant be const sad + std::stringstream& sd0Data, // cant be const sad const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) { @@ -71,3 +69,14 @@ void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstrea const std::istream stream(lxfml.rdbuf()); ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); } + +std::optional MySQLDatabase::GetUgcModel(const LWOOBJID ugcId) { + auto result = ExecuteSelect("SELECT u.id AS ugcID, lxfml, pc.id AS modelID FROM ugc AS u JOIN properties_contents AS pc ON pc.ugc_id = u.id WHERE u.id = ?", ugcId); + + std::optional toReturn = std::nullopt; + if (result->next()) { + toReturn = ReadModel(result); + } + + return toReturn; +} diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h index 54dc39d3..3b6dc643 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -96,9 +96,9 @@ public: void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void SetMasterInfo(const IServers::MasterInfo& info) override; - std::optional GetCurrentPersistentId() override; + std::optional GetCurrentPersistentId() override; + IObjectIdTracker::Range GetPersistentIdRange() override; void InsertDefaultPersistentId() override; - void UpdatePersistentId(const uint32_t id) override; std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; @@ -125,7 +125,9 @@ public: void DeleteUgcBuild(const LWOOBJID bigId) override; uint32_t GetAccountCount() override; bool IsNameInUse(const std::string_view name) override; - IPropertyContents::Model GetModel(const LWOOBJID modelID) override; + std::optional GetModel(const LWOOBJID modelID) override; + std::optional GetUgcModel(const LWOOBJID ugcId) override; + std::optional GetPropertyInfo(const LWOOBJID id) override; private: CppSQLite3Statement CreatePreppedStmt(const std::string& query); diff --git a/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp b/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp index af8014dd..aef1d267 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp @@ -1,17 +1,40 @@ #include "SQLiteDatabase.h" -std::optional SQLiteDatabase::GetCurrentPersistentId() { +std::optional SQLiteDatabase::GetCurrentPersistentId() { auto [_, result] = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); if (result.eof()) { return std::nullopt; } - return result.getIntField("last_object_id"); + return result.getInt64Field("last_object_id"); } void SQLiteDatabase::InsertDefaultPersistentId() { ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); } -void SQLiteDatabase::UpdatePersistentId(const uint32_t newId) { - ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId); +IObjectIdTracker::Range SQLiteDatabase::GetPersistentIdRange() { + IObjectIdTracker::Range range; + auto prevCommit = GetAutoCommit(); + SetAutoCommit(false); + + // THIS MUST ABSOLUTELY NOT FAIL. These IDs are expected to be unique. As such a transactional select is used to safely get a range + // of IDs that will never be used again. A separate feature could track unused IDs and recycle them, but that is not implemented. + ExecuteCustomQuery("BEGIN TRANSACTION;"); + // 200 seems like a good range to reserve at once. Only way this would be noticable is if a player + // added hundreds of items at once. + // Doing the update first ensures that all other connections are blocked from accessing this table until we commit. + auto result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;"); + if (result == 0) { + InsertDefaultPersistentId(); + result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;"); + } + + // At this point all connections are waiting on us to finish the transaction, so we can safely select the ID we just set. + auto [_, selectResult] = ExecuteSelect("SELECT last_object_id FROM object_id_tracker;"); + range.maxID = selectResult.getInt64Field("last_object_id"); + range.minID = range.maxID - 199; + + ExecuteCustomQuery("COMMIT;"); + SetAutoCommit(prevCommit); + return range; } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Property.cpp b/dDatabase/GameDatabase/SQLite/Tables/Property.cpp index 195fee2a..67fc57b3 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Property.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Property.cpp @@ -1,6 +1,23 @@ #include "SQLiteDatabase.h" #include "ePropertySortType.h" +IProperty::Info ReadPropertyInfo(CppSQLite3Query& propertyEntry) { + IProperty::Info toReturn; + toReturn.id = propertyEntry.getInt64Field("id"); + toReturn.ownerId = propertyEntry.getInt64Field("owner_id"); + toReturn.cloneId = propertyEntry.getInt64Field("clone_id"); + toReturn.name = propertyEntry.getStringField("name"); + toReturn.description = propertyEntry.getStringField("description"); + toReturn.privacyOption = propertyEntry.getIntField("privacy_option"); + toReturn.rejectionReason = propertyEntry.getStringField("rejection_reason"); + toReturn.lastUpdatedTime = propertyEntry.getIntField("last_updated"); + toReturn.claimedTime = propertyEntry.getIntField("time_claimed"); + toReturn.reputation = propertyEntry.getIntField("reputation"); + toReturn.modApproved = propertyEntry.getIntField("mod_approved"); + toReturn.performanceCost = propertyEntry.getFloatField("performance_cost"); + return toReturn; +} + std::optional SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) { std::optional result; std::string query; @@ -118,19 +135,7 @@ std::optional SQLiteDatabase::GetProperties(c auto& [_, properties] = propertiesRes; if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult(); while (!properties.eof()) { - auto& entry = result->entries.emplace_back(); - entry.id = properties.getInt64Field("id"); - entry.ownerId = properties.getInt64Field("owner_id"); - entry.cloneId = properties.getInt64Field("clone_id"); - entry.name = properties.getStringField("name"); - entry.description = properties.getStringField("description"); - entry.privacyOption = properties.getIntField("privacy_option"); - entry.rejectionReason = properties.getStringField("rejection_reason"); - entry.lastUpdatedTime = properties.getIntField("last_updated"); - entry.claimedTime = properties.getIntField("time_claimed"); - entry.reputation = properties.getIntField("reputation"); - entry.modApproved = properties.getIntField("mod_approved"); - entry.performanceCost = properties.getFloatField("performance_cost"); + result->entries.push_back(ReadPropertyInfo(properties)); properties.nextRow(); } @@ -146,21 +151,7 @@ std::optional SQLiteDatabase::GetPropertyInfo(const LWOMAPID ma return std::nullopt; } - IProperty::Info toReturn; - toReturn.id = propertyEntry.getInt64Field("id"); - toReturn.ownerId = propertyEntry.getInt64Field("owner_id"); - toReturn.cloneId = propertyEntry.getInt64Field("clone_id"); - toReturn.name = propertyEntry.getStringField("name"); - toReturn.description = propertyEntry.getStringField("description"); - toReturn.privacyOption = propertyEntry.getIntField("privacy_option"); - toReturn.rejectionReason = propertyEntry.getStringField("rejection_reason"); - toReturn.lastUpdatedTime = propertyEntry.getIntField("last_updated"); - toReturn.claimedTime = propertyEntry.getIntField("time_claimed"); - toReturn.reputation = propertyEntry.getIntField("reputation"); - toReturn.modApproved = propertyEntry.getIntField("mod_approved"); - toReturn.performanceCost = propertyEntry.getFloatField("performance_cost"); - - return toReturn; + return ReadPropertyInfo(propertyEntry); } void SQLiteDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { @@ -197,3 +188,15 @@ void SQLiteDatabase::InsertNewProperty(const IProperty::Info& info, const uint32 zoneId.GetMapID() ); } + +std::optional SQLiteDatabase::GetPropertyInfo(const LWOOBJID id) { + auto [_, propertyEntry] = ExecuteSelect( + "SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost " + "FROM properties WHERE id = ?;", id); + + if (propertyEntry.eof()) { + return std::nullopt; + } + + return ReadPropertyInfo(propertyEntry); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp index a04f3536..5251c534 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp @@ -64,27 +64,28 @@ void SQLiteDatabase::RemoveModel(const LWOOBJID& modelId) { ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); } -IPropertyContents::Model SQLiteDatabase::GetModel(const LWOOBJID modelID) { +std::optional SQLiteDatabase::GetModel(const LWOOBJID modelID) { auto [_, result] = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID); - IPropertyContents::Model model{}; + std::optional model = std::nullopt; if (!result.eof()) { do { - model.id = result.getInt64Field("id"); - model.lot = static_cast(result.getIntField("lot")); - model.position.x = result.getFloatField("x"); - model.position.y = result.getFloatField("y"); - model.position.z = result.getFloatField("z"); - model.rotation.w = result.getFloatField("rw"); - model.rotation.x = result.getFloatField("rx"); - model.rotation.y = result.getFloatField("ry"); - model.rotation.z = result.getFloatField("rz"); - model.ugcId = result.getInt64Field("ugc_id"); - model.behaviors[0] = result.getInt64Field("behavior_1"); - model.behaviors[1] = result.getInt64Field("behavior_2"); - model.behaviors[2] = result.getInt64Field("behavior_3"); - model.behaviors[3] = result.getInt64Field("behavior_4"); - model.behaviors[4] = result.getInt64Field("behavior_5"); + model = IPropertyContents::Model{}; + model->id = result.getInt64Field("id"); + model->lot = static_cast(result.getIntField("lot")); + model->position.x = result.getFloatField("x"); + model->position.y = result.getFloatField("y"); + model->position.z = result.getFloatField("z"); + model->rotation.w = result.getFloatField("rw"); + model->rotation.x = result.getFloatField("rx"); + model->rotation.y = result.getFloatField("ry"); + model->rotation.z = result.getFloatField("rz"); + model->ugcId = result.getInt64Field("ugc_id"); + model->behaviors[0] = result.getInt64Field("behavior_1"); + model->behaviors[1] = result.getInt64Field("behavior_2"); + model->behaviors[2] = result.getInt64Field("behavior_3"); + model->behaviors[3] = result.getInt64Field("behavior_4"); + model->behaviors[4] = result.getInt64Field("behavior_5"); } while (result.nextRow()); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp index 31a6a2e7..0649bb9f 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp @@ -1,5 +1,17 @@ #include "SQLiteDatabase.h" +IUgc::Model ReadModel(CppSQLite3Query& result) { + IUgc::Model model; + + int blobSize{}; + const auto* blob = result.getBlobField("lxfml", blobSize); + model.lxfmlData << std::string(reinterpret_cast(blob), blobSize); + model.id = result.getInt64Field("ugcID"); + model.modelID = result.getInt64Field("modelID"); + + return model; +} + std::vector SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId) { auto [_, result] = ExecuteSelect( "SELECT lxfml, u.id AS ugcID, pc.id AS modelID FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;", @@ -8,14 +20,7 @@ std::vector SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId std::vector toReturn; while (!result.eof()) { - IUgc::Model model; - - int blobSize{}; - const auto* blob = result.getBlobField("lxfml", blobSize); - model.lxfmlData << std::string(reinterpret_cast(blob), blobSize); - model.id = result.getInt64Field("ugcID"); - model.modelID = result.getInt64Field("modelID"); - toReturn.push_back(std::move(model)); + toReturn.push_back(ReadModel(result)); result.nextRow(); } @@ -27,14 +32,7 @@ std::vector SQLiteDatabase::GetAllUgcModels() { std::vector models; while (!result.eof()) { - IUgc::Model model; - model.id = result.getInt64Field("ugcID"); - model.modelID = result.getInt64Field("modelID"); - - int blobSize{}; - const auto* blob = result.getBlobField("lxfml", blobSize); - model.lxfmlData << std::string(reinterpret_cast(blob), blobSize); - models.push_back(std::move(model)); + models.push_back(ReadModel(result)); result.nextRow(); } @@ -72,3 +70,14 @@ void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstre const std::istream stream(lxfml.rdbuf()); ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); } + +std::optional SQLiteDatabase::GetUgcModel(const LWOOBJID ugcId) { + auto [_, result] = ExecuteSelect("SELECT u.id AS ugcID, pc.id AS modelID, lxfml FROM ugc AS u JOIN properties_contents AS pc ON pc.id = u.id WHERE u.id = ?;", ugcId); + + std::optional toReturn = std::nullopt; + if (!result.eof()) { + toReturn = ReadModel(result); + } + + return toReturn; +} diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp index f9c9fb09..1cd48ad9 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp @@ -244,7 +244,7 @@ void TestSQLDatabase::SetMasterInfo(const IServers::MasterInfo& info) { } -std::optional TestSQLDatabase::GetCurrentPersistentId() { +std::optional TestSQLDatabase::GetCurrentPersistentId() { return {}; } @@ -252,10 +252,6 @@ void TestSQLDatabase::InsertDefaultPersistentId() { } -void TestSQLDatabase::UpdatePersistentId(const uint32_t id) { - -} - std::optional TestSQLDatabase::GetDonationTotal(const uint32_t activityId) { return {}; } @@ -304,3 +300,6 @@ void TestSQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGame } +IObjectIdTracker::Range TestSQLDatabase::GetPersistentIdRange() { + return {}; +} diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 87e08e91..2c7890dd 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -75,9 +75,9 @@ class TestSQLDatabase : public GameDatabase { void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void SetMasterInfo(const IServers::MasterInfo& info) override; - std::optional GetCurrentPersistentId() override; + std::optional GetCurrentPersistentId() override; + IObjectIdTracker::Range GetPersistentIdRange() override; void InsertDefaultPersistentId() override; - void UpdatePersistentId(const uint32_t id) override; std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; @@ -105,7 +105,9 @@ class TestSQLDatabase : public GameDatabase { uint32_t GetAccountCount() override { return 0; }; bool IsNameInUse(const std::string_view name) override { return false; }; - IPropertyContents::Model GetModel(const LWOOBJID modelID) override { return {}; } + std::optional GetModel(const LWOOBJID modelID) override { return {}; } + std::optional GetPropertyInfo(const LWOOBJID id) override { return {}; } + std::optional GetUgcModel(const LWOOBJID ugcId) override { return {}; } }; #endif //!TESTSQLDATABASE_H diff --git a/dDatabase/ModelNormalizeMigration.cpp b/dDatabase/ModelNormalizeMigration.cpp index b415ef57..45fcdb82 100644 --- a/dDatabase/ModelNormalizeMigration.cpp +++ b/dDatabase/ModelNormalizeMigration.cpp @@ -10,7 +10,7 @@ void ModelNormalizeMigration::Run() { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { const auto model = Database::Get()->GetModel(modelID); // only BBB models (lot 14) and models with a position of NiPoint3::ZERO need to have their position fixed. - if (model.position != NiPoint3Constant::ZERO || model.lot != 14) continue; + if (!model || model->position != NiPoint3Constant::ZERO || model->lot != 14) continue; Sd0 sd0(lxfmlData); const auto asStr = sd0.GetAsStringUncompressed(); @@ -23,7 +23,7 @@ void ModelNormalizeMigration::Run() { LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z); sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); auto asStream = sd0.GetAsStream(); - Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors); + Database::Get()->UpdateModel(model->id, newCenter, model->rotation, model->behaviors); Database::Get()->UpdateUgcModelData(id, asStream); } Database::Get()->SetAutoCommit(oldCommit); @@ -35,15 +35,15 @@ void ModelNormalizeMigration::RunAfterFirstPart() { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { const auto model = Database::Get()->GetModel(modelID); // only BBB models (lot 14) need to have their position fixed from the above blunder - if (model.lot != 14) continue; + if (!model || model->lot != 14) continue; Sd0 sd0(lxfmlData); const auto asStr = sd0.GetAsStringUncompressed(); - const auto [newLxfml, newCenter] = Lxfml::NormalizePositionAfterFirstPart(asStr, model.position); + const auto [newLxfml, newCenter] = Lxfml::NormalizePositionAfterFirstPart(asStr, model->position); sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); auto asStream = sd0.GetAsStream(); - Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors); + Database::Get()->UpdateModel(model->id, newCenter, model->rotation, model->behaviors); Database::Get()->UpdateUgcModelData(id, asStream); } Database::Get()->SetAutoCommit(oldCommit); @@ -55,16 +55,16 @@ void ModelNormalizeMigration::RunBrickBuildGrid() { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { const auto model = Database::Get()->GetModel(modelID); // only BBB models (lot 14) need to have their position fixed from the above blunder - if (model.lot != 14) continue; + if (!model || model->lot != 14) continue; Sd0 sd0(lxfmlData); const auto asStr = sd0.GetAsStringUncompressed(); - const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr, model.position); + const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr, model->position); sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z); auto asStream = sd0.GetAsStream(); - Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors); + Database::Get()->UpdateModel(model->id, newCenter, model->rotation, model->behaviors); Database::Get()->UpdateUgcModelData(id, asStream); } Database::Get()->SetAutoCommit(oldCommit); diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 82afb063..343ce28d 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -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 << ""; + 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 << ""; + std::stringstream xml; + xml << ""; - xml << "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 << ""; + xml << ""; - xml << ""; + xml << "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 << ""; - xml << ""; + xml << ""; - xml << ""; + xml << ""; - LWOOBJID lwoidforshirt = ObjectIDManager::GenerateRandomObjectID(); - LWOOBJID lwoidforpants; + xml << ""; - 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 << ""; + xml << ""; - xml << ""; - xml << ""; + xml << ""; - xml << ""; + //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) { diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 4b50ece2..e22becad 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -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(); + } +} diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index d67cf3c1..a1441655 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -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: diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 5081db6c..df57aef7 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -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; diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index 783de9a9..d2e595cf 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -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(); diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 06792897..c6edbf6d 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -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::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(u"modelBehaviors", 0)); - info.nodes[0]->config.push_back(new LDFData(u"userModelID", info.spawnerID)); - info.nodes[0]->config.push_back(new LDFData(u"modelType", 2)); - info.nodes[0]->config.push_back(new LDFData(u"propertyObjectID", true)); - info.nodes[0]->config.push_back(new LDFData(u"componentWhitelist", 1)); + info.nodes[0]->config.push_back(new LDFData(u"modelBehaviors", 0)); + info.nodes[0]->config.push_back(new LDFData(u"userModelID", info.spawnerID)); + info.nodes[0]->config.push_back(new LDFData(u"modelType", 2)); + info.nodes[0]->config.push_back(new LDFData(u"propertyObjectID", true)); + info.nodes[0]->config.push_back(new LDFData(u"componentWhitelist", 1)); - auto* model = spawner->Spawn(); - auto* modelComponent = model->GetComponent(); - if (modelComponent) modelComponent->Pause(); + auto* model = spawner->Spawn(); + auto* modelComponent = model->GetComponent(); + 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(); 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(); if (!modelComponent) continue; - + modelComponent->OnChatMessageReceived(sMessage); } } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 168469c9..c7108e50 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2555,7 +2555,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent uint32_t sd0Size; inStream.Read(sd0Size); - std::shared_ptr sd0Data(new char[sd0Size]); + std::unique_ptr 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(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(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(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(1); + bitStream.Write(blueprintID); - bitStream.Write(sd0Size); + bitStream.Write(newSd0Size); - for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast(chunk.data()), chunk.size()); + for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast(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(u"blueprintid", blueprintID)); - info.settings.push_back(new LDFData(u"componentWhitelist", 1)); - info.settings.push_back(new LDFData(u"modelType", 2)); - info.settings.push_back(new LDFData(u"propertyObjectID", true)); - info.settings.push_back(new LDFData(u"userModelID", newIDL)); + info.settings.push_back(new LDFData(u"blueprintid", blueprintID)); + info.settings.push_back(new LDFData(u"componentWhitelist", 1)); + info.settings.push_back(new LDFData(u"modelType", 2)); + info.settings.push_back(new LDFData(u"propertyObjectID", true)); + info.settings.push_back(new LDFData(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(u"assemblyPartLOTs", modules); + const auto moduleAssembly = new LDFData(u"assemblyPartLOTs", modules); - std::vector config; - config.push_back(moduleAssembly); + std::vector 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(); + + 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(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(entity->GetComponent(eReplicaComponentType::SCRIPT)); - auto* missionComponent = character->GetComponent(); + 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(eRacingTaskParam::MODULAR_BUILDING)); - } - } + // Move remaining temp models back to models + std::vector items; - ScriptComponent* script = static_cast(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 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); + } } } diff --git a/dGame/dInventory/Inventory.cpp b/dGame/dInventory/Inventory.cpp index 35222bea..98e34f8d 100644 --- a/dGame/dInventory/Inventory.cpp +++ b/dGame/dInventory/Inventory.cpp @@ -5,8 +5,11 @@ #include "InventoryComponent.h" #include "eItemType.h" #include "eReplicaComponentType.h" +#include "ObjectIDManager.h" +#include "eObjectBits.h" #include "CDComponentsRegistryTable.h" +#include std::vector Inventory::m_GameMasterRestrictedItems = { 1727, // GM Only - JetPack @@ -317,3 +320,16 @@ Inventory::~Inventory() { items.clear(); } + +void Inventory::RegenerateItemIDs() { + std::map 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; +} diff --git a/dGame/dInventory/Inventory.h b/dGame/dInventory/Inventory.h index 5e0ac8d6..f07ec68e 100644 --- a/dGame/dInventory/Inventory.h +++ b/dGame/dInventory/Inventory.h @@ -158,6 +158,8 @@ public: */ void DeleteAllItems(); + void RegenerateItemIDs(); + ~Inventory(); private: diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index 1a12c25f..89f8fcc1 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -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(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(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(id), id); + const auto type = static_cast(info->itemType); + + if (type == eItemType::MOUNT) { + GeneralUtils::SetBit(id, eObjectBits::CLIENT); + } + + this->id = id; + return id; +} diff --git a/dGame/dInventory/Item.h b/dGame/dInventory/Item.h index 72ff264c..846a7aa7 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -228,6 +228,8 @@ public: void LoadConfigXml(const tinyxml2::XMLElement& i); + LWOOBJID GenerateID(); + private: /** * The object ID of this item diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.cpp b/dGame/dPropertyBehaviors/ControlBehaviors.cpp index 1a142273..70a32981 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviors.cpp @@ -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) { diff --git a/dGame/dUtilities/ObjectIDManager.cpp b/dGame/dUtilities/ObjectIDManager.cpp index a30ede05..2eda10e2 100644 --- a/dGame/dUtilities/ObjectIDManager.cpp +++ b/dGame/dUtilities/ObjectIDManager.cpp @@ -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& callback) : requestID(requestID), callback(callback) {} - uint64_t requestID; - - std::function 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 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 Uni(10000000, INT32_MAX); + // Start the range in a way that it when first called it will fetch some new persistent IDs + std::optional 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 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; } diff --git a/dGame/dUtilities/ObjectIDManager.h b/dGame/dUtilities/ObjectIDManager.h index 7650e4cc..4e2f245f 100644 --- a/dGame/dUtilities/ObjectIDManager.h +++ b/dGame/dUtilities/ObjectIDManager.h @@ -5,36 +5,25 @@ #include #include -/*! - \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 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(); }; diff --git a/dMasterServer/CMakeLists.txt b/dMasterServer/CMakeLists.txt index 2e2b4dd9..ec313ab0 100644 --- a/dMasterServer/CMakeLists.txt +++ b/dMasterServer/CMakeLists.txt @@ -1,6 +1,5 @@ set(DMASTERSERVER_SOURCES "InstanceManager.cpp" - "PersistentIDManager.cpp" "Start.cpp" ) diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 34d44ea8..32a2cb56 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -35,7 +35,6 @@ #include "Game.h" #include "InstanceManager.h" #include "MasterPackets.h" -#include "PersistentIDManager.h" #include "FdbToSqlite.h" #include "BitStreamUtils.h" #include "Start.h" @@ -360,7 +359,6 @@ int main(int argc, char** argv) { Database::Get()->SetMasterInfo(info); //Create additional objects here: - PersistentIDManager::Initialize(); Game::im = new InstanceManager(Game::server->GetIP()); //Get CDClient initial information @@ -534,17 +532,6 @@ void HandlePacket(Packet* packet) { if (static_cast(packet->data[1]) == ServiceType::MASTER) { switch (static_cast(packet->data[3])) { - case MessageType::Master::REQUEST_PERSISTENT_ID: { - LOG("A persistent ID req"); - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); - uint64_t requestID = 0; - inStream.Read(requestID); - - uint32_t objID = PersistentIDManager::GeneratePersistentID(); - MasterPackets::SendPersistentIDResponse(Game::server, packet->systemAddress, requestID, objID); - break; - } case MessageType::Master::REQUEST_ZONE_TRANSFER: { LOG("Received zone transfer req"); @@ -882,9 +869,6 @@ int ShutdownSequence(int32_t signal) { LOG("Triggered master shutdown"); } - PersistentIDManager::SaveToDatabase(); - LOG("Saved ObjectIDTracker to DB"); - // A server might not be finished spinning up yet, remove all of those here. for (const auto& instance : Game::im->GetInstances()) { if (!instance) continue; diff --git a/dMasterServer/PersistentIDManager.cpp b/dMasterServer/PersistentIDManager.cpp deleted file mode 100644 index 1b3a1c0b..00000000 --- a/dMasterServer/PersistentIDManager.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "PersistentIDManager.h" - -// Custom Classes -#include "Database.h" -#include "Logger.h" -#include "Game.h" - -namespace { - uint32_t CurrentPersistentID = 1; //!< The highest current persistent ID in use -}; - -//! Initializes the manager -void PersistentIDManager::Initialize() { - try { - auto lastObjectId = Database::Get()->GetCurrentPersistentId(); - - if (!lastObjectId) { - Database::Get()->InsertDefaultPersistentId(); - } else { - CurrentPersistentID = lastObjectId.value(); - } - - if (CurrentPersistentID <= 0) { - LOG("Invalid persistent object ID in database. Aborting to prevent bad id generation."); - throw std::runtime_error("Invalid persistent object ID in database. Aborting to prevent bad id generation."); - } - } catch (std::exception& e) { - LOG("Unable to fetch max persistent object ID in use. This will cause issues. Aborting to prevent collisions."); - LOG("Error: %s", e.what()); - throw e; - } -} - -//! Generates a new persistent ID -uint32_t PersistentIDManager::GeneratePersistentID() { - uint32_t toReturn = ++CurrentPersistentID; - - SaveToDatabase(); - - return toReturn; -} - -void PersistentIDManager::SaveToDatabase() { - Database::Get()->UpdatePersistentId(CurrentPersistentID); -} diff --git a/dMasterServer/PersistentIDManager.h b/dMasterServer/PersistentIDManager.h deleted file mode 100644 index 916ee33a..00000000 --- a/dMasterServer/PersistentIDManager.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -// C++ -#include - -/*! - \file PersistentIDManager.h - \brief A manager that handles requests for object IDs - */ - - //! The Object ID Manager -namespace PersistentIDManager { - //! Initializes the manager - void Initialize(); - - //! Generates a new persistent ID - /*! - \return The new persistent ID - */ - uint32_t GeneratePersistentID(); - - void SaveToDatabase(); -}; diff --git a/dNet/MasterPackets.cpp b/dNet/MasterPackets.cpp index 36a23f4f..aac49929 100644 --- a/dNet/MasterPackets.cpp +++ b/dNet/MasterPackets.cpp @@ -8,23 +8,6 @@ #include -void MasterPackets::SendPersistentIDRequest(dServer* server, uint64_t requestID) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_PERSISTENT_ID); - bitStream.Write(requestID); - server->SendToMaster(bitStream); -} - -void MasterPackets::SendPersistentIDResponse(dServer* server, const SystemAddress& sysAddr, uint64_t requestID, uint32_t objID) { - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_PERSISTENT_ID_RESPONSE); - - bitStream.Write(requestID); - bitStream.Write(objID); - - server->Send(bitStream, sysAddr, false); -} - void MasterPackets::SendZoneTransferRequest(dServer* server, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t cloneID) { RakNet::BitStream bitStream; BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER); diff --git a/dNet/MasterPackets.h b/dNet/MasterPackets.h index 93fd158e..68f184eb 100644 --- a/dNet/MasterPackets.h +++ b/dNet/MasterPackets.h @@ -8,9 +8,6 @@ class dServer; namespace MasterPackets { - void SendPersistentIDRequest(dServer* server, uint64_t requestID); //Called from the World server - void SendPersistentIDResponse(dServer* server, const SystemAddress& sysAddr, uint64_t requestID, uint32_t objID); - void SendZoneTransferRequest(dServer* server, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t cloneID); void SendZoneTransferResponse(dServer* server, const SystemAddress& sysAddr, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, const std::string& serverIP, uint32_t serverPort); @@ -22,8 +19,6 @@ namespace MasterPackets { void SendZoneRequestPrivate(dServer* server, uint64_t requestID, bool mythranShift, const std::string& password); void SendWorldReady(dServer* server, LWOMAPID zoneId, LWOINSTANCEID instanceId); - - void HandleSetSessionKey(Packet* packet); } #endif // MASTERPACKETS_H diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index d291be8b..b57acd58 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -8,7 +8,6 @@ #include "CharacterComponent.h" #include "SimplePhysicsComponent.h" #include "MovementAIComponent.h" -#include "ObjectIDManager.h" #include "MissionComponent.h" #include "Loot.h" #include "InventoryComponent.h" diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 3a22bbf0..964a4bb9 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -35,7 +35,6 @@ #include "CDClientManager.h" #include "CDClientDatabase.h" #include "GeneralUtils.h" -#include "ObjectIDManager.h" #include "ZoneInstanceManager.h" #include "dChatFilter.h" #include "ClientPackets.h" @@ -676,15 +675,6 @@ void HandleMasterPacket(Packet* packet) { if (packet->length < 2) return; if (static_cast(packet->data[1]) != ServiceType::MASTER || packet->length < 4) return; switch (static_cast(packet->data[3])) { - case MessageType::Master::REQUEST_PERSISTENT_ID_RESPONSE: { - CINSTREAM_SKIP_HEADER; - uint64_t requestID; - inStream.Read(requestID); - uint32_t objectID; - inStream.Read(objectID); - ObjectIDManager::HandleRequestPersistentIDResponse(requestID, objectID); - break; - } case MessageType::Master::SESSION_KEY_RESPONSE: { //Read our session key and to which user it belongs: @@ -1047,82 +1037,90 @@ void HandlePacket(Packet* packet) { auto version = levelComponent->GetCharacterVersion(); LOG("Updating character from version %s", StringifiedEnum::ToString(version).data()); - switch (version) { - case eCharacterVersion::RELEASE: - // TODO: Implement, super low priority - [[fallthrough]]; - case eCharacterVersion::LIVE: - LOG("Updating Character Flags"); - c->SetRetroactiveFlags(); - levelComponent->SetCharacterVersion(eCharacterVersion::PLAYER_FACTION_FLAGS); - [[fallthrough]]; - case eCharacterVersion::PLAYER_FACTION_FLAGS: - LOG("Updating Vault Size"); - player->RetroactiveVaultSize(); - levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE); - [[fallthrough]]; - case eCharacterVersion::VAULT_SIZE: - LOG("Updaing Speedbase"); - levelComponent->SetRetroactiveBaseSpeed(); - levelComponent->SetCharacterVersion(eCharacterVersion::SPEED_BASE); - [[fallthrough]]; - case eCharacterVersion::SPEED_BASE: { - LOG("Removing lots from NJ Jay missions bugged at foss"); - // https://explorer.lu/missions/1789 - const auto* mission = missionComponent->GetMission(1789); - if (mission && mission->IsComplete()) { - inventoryComponent->RemoveItem(14474, 1, eInventoryType::ITEMS); - inventoryComponent->RemoveItem(14474, 1, eInventoryType::VAULT_ITEMS); - } - // https://explorer.lu/missions/1927 - mission = missionComponent->GetMission(1927); - if (mission && mission->IsComplete()) { - inventoryComponent->RemoveItem(14493, 1, eInventoryType::ITEMS); - inventoryComponent->RemoveItem(14493, 1, eInventoryType::VAULT_ITEMS); - } - levelComponent->SetCharacterVersion(eCharacterVersion::NJ_JAYMISSIONS); - [[fallthrough]]; - } - case eCharacterVersion::NJ_JAYMISSIONS: { - LOG("Fixing Nexus Force Explorer missions"); - auto missions = { 502 /* Pet Cove */, 593/* Nimbus Station */, 938/* Avant Gardens */, 284/* Gnarled Forest */, 754/* Forbidden Valley */ }; - bool complete = true; - for (auto missionID : missions) { - auto* mission = missionComponent->GetMission(missionID); - if (!mission || !mission->IsComplete()) { - complete = false; + if (version < eCharacterVersion::UP_TO_DATE) { + switch (version) { + case eCharacterVersion::RELEASE: + // TODO: Implement, super low priority + [[fallthrough]]; + case eCharacterVersion::LIVE: + LOG("Updating Character Flags"); + c->SetRetroactiveFlags(); + levelComponent->SetCharacterVersion(eCharacterVersion::PLAYER_FACTION_FLAGS); + [[fallthrough]]; + case eCharacterVersion::PLAYER_FACTION_FLAGS: + LOG("Updating Vault Size"); + player->RetroactiveVaultSize(); + levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE); + [[fallthrough]]; + case eCharacterVersion::VAULT_SIZE: + LOG("Updaing Speedbase"); + levelComponent->SetRetroactiveBaseSpeed(); + levelComponent->SetCharacterVersion(eCharacterVersion::SPEED_BASE); + [[fallthrough]]; + case eCharacterVersion::SPEED_BASE: { + LOG("Removing lots from NJ Jay missions bugged at foss"); + // https://explorer.lu/missions/1789 + const auto* mission = missionComponent->GetMission(1789); + if (mission && mission->IsComplete()) { + inventoryComponent->RemoveItem(14474, 1, eInventoryType::ITEMS); + inventoryComponent->RemoveItem(14474, 1, eInventoryType::VAULT_ITEMS); } - } - - if (complete) missionComponent->CompleteMission(937 /* Nexus Force explorer */); - levelComponent->SetCharacterVersion(eCharacterVersion::NEXUS_FORCE_EXPLORER); - [[fallthrough]]; - } - case eCharacterVersion::NEXUS_FORCE_EXPLORER: { - LOG("Fixing pet IDs"); - - // First copy the original ids - const auto pets = inventoryComponent->GetPetsMut(); - - // Then clear the pets so we can re-add them with the updated IDs - auto& invPets = inventoryComponent->GetPetsMut(); - invPets.clear(); - for (auto& [id, databasePet] : pets) { - const auto originalID = id; - const auto newId = GeneralUtils::ClearBit(id, 32); // Persistent bit that didn't exist - LOG("New ID %llu", newId); - auto* item = inventoryComponent->FindItemBySubKey(originalID); - if (item) { - LOG("item subkey %llu", item->GetSubKey()); - item->SetSubKey(newId); - invPets[newId] = databasePet; + // https://explorer.lu/missions/1927 + mission = missionComponent->GetMission(1927); + if (mission && mission->IsComplete()) { + inventoryComponent->RemoveItem(14493, 1, eInventoryType::ITEMS); + inventoryComponent->RemoveItem(14493, 1, eInventoryType::VAULT_ITEMS); } + levelComponent->SetCharacterVersion(eCharacterVersion::NJ_JAYMISSIONS); + [[fallthrough]]; + } + case eCharacterVersion::NJ_JAYMISSIONS: { + LOG("Fixing Nexus Force Explorer missions"); + auto missions = { 502 /* Pet Cove */, 593/* Nimbus Station */, 938/* Avant Gardens */, 284/* Gnarled Forest */, 754/* Forbidden Valley */ }; + bool complete = true; + for (auto missionID : missions) { + auto* mission = missionComponent->GetMission(missionID); + if (!mission || !mission->IsComplete()) { + complete = false; + } + } + + if (complete) missionComponent->CompleteMission(937 /* Nexus Force explorer */); + levelComponent->SetCharacterVersion(eCharacterVersion::NEXUS_FORCE_EXPLORER); + [[fallthrough]]; + } + case eCharacterVersion::NEXUS_FORCE_EXPLORER: { + LOG("Fixing pet IDs"); + + // First copy the original ids + const auto pets = inventoryComponent->GetPetsMut(); + + // Then clear the pets so we can re-add them with the updated IDs + auto& invPets = inventoryComponent->GetPetsMut(); + invPets.clear(); + for (auto& [id, databasePet] : pets) { + const auto originalID = id; + const auto newId = GeneralUtils::ClearBit(id, 32); // Persistent bit that didn't exist + LOG("New ID %llu", newId); + auto* item = inventoryComponent->FindItemBySubKey(originalID); + if (item) { + LOG("item subkey %llu", item->GetSubKey()); + item->SetSubKey(newId); + invPets[newId] = databasePet; + } + } + levelComponent->SetCharacterVersion(eCharacterVersion::PET_IDS); + [[fallthrough]]; + } + case eCharacterVersion::PET_IDS: { + LOG("Regenerating item ids"); + inventoryComponent->RegenerateItemIDs(); + levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); + [[fallthrough]]; + } + case eCharacterVersion::UP_TO_DATE: + break; } - levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); - [[fallthrough]]; - } - case eCharacterVersion::UP_TO_DATE: - break; } // Update the characters xml to ensure the update above is not only saved, but so the client picks up on the changes. diff --git a/migrations/dlu/mysql/26_update_property_contents_ids.sql b/migrations/dlu/mysql/26_update_property_contents_ids.sql new file mode 100644 index 00000000..7e36924e --- /dev/null +++ b/migrations/dlu/mysql/26_update_property_contents_ids.sql @@ -0,0 +1 @@ +UPDATE `properties_contents` SET `id` = `id` | 0x1000000000000000; diff --git a/migrations/dlu/sqlite/9_update_property_contents_ids.sql b/migrations/dlu/sqlite/9_update_property_contents_ids.sql new file mode 100644 index 00000000..7e36924e --- /dev/null +++ b/migrations/dlu/sqlite/9_update_property_contents_ids.sql @@ -0,0 +1 @@ +UPDATE `properties_contents` SET `id` = `id` | 0x1000000000000000;