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:
David Markowitz
2025-09-28 20:30:59 -07:00
parent b5a3cc9187
commit 151d05e853
43 changed files with 676 additions and 656 deletions

View File

@@ -3,9 +3,7 @@
namespace MessageType { namespace MessageType {
enum class Master : uint32_t { enum class Master : uint32_t {
REQUEST_PERSISTENT_ID = 1, REQUEST_ZONE_TRANSFER = 1,
REQUEST_PERSISTENT_ID_RESPONSE,
REQUEST_ZONE_TRANSFER,
REQUEST_ZONE_TRANSFER_RESPONSE, REQUEST_ZONE_TRANSFER_RESPONSE,
SERVER_INFO, SERVER_INFO,
REQUEST_SESSION_KEY, REQUEST_SESSION_KEY,

View File

@@ -19,7 +19,8 @@ enum class eCharacterVersion : uint32_t {
// Fixes nexus force explorer missions // Fixes nexus force explorer missions
NJ_JAYMISSIONS, NJ_JAYMISSIONS,
NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories 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__ #endif //!__ECHARACTERVERSION__H__

View File

@@ -6,14 +6,19 @@
class IObjectIdTracker { class IObjectIdTracker {
public: 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. // Get the current persistent id.
virtual std::optional<uint32_t> GetCurrentPersistentId() = 0; virtual std::optional<uint64_t> GetCurrentPersistentId() = 0;
// Insert the default persistent id. // Insert the default persistent id.
virtual void InsertDefaultPersistentId() = 0; virtual void InsertDefaultPersistentId() = 0;
// Update the persistent id. virtual Range GetPersistentIdRange() = 0;
virtual void UpdatePersistentId(const uint32_t newId) = 0;
}; };
#endif //!__IOBJECTIDTRACKER__H__ #endif //!__IOBJECTIDTRACKER__H__

View File

@@ -39,6 +39,9 @@ public:
std::vector<IProperty::Info> entries; std::vector<IProperty::Info> entries;
}; };
// Get the property info for the given property id.
virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) = 0;
// Get the property info for the given property id. // Get the property info for the given property id.
virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0; virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0;

View File

@@ -45,6 +45,6 @@ public:
virtual void RemoveModel(const LWOOBJID& modelId) = 0; virtual void RemoveModel(const LWOOBJID& modelId) = 0;
// Gets a model by ID // Gets a model by ID
virtual Model GetModel(const LWOOBJID modelID) = 0; virtual std::optional<Model> GetModel(const LWOOBJID modelID) = 0;
}; };
#endif //!__IPROPERTIESCONTENTS__H__ #endif //!__IPROPERTIESCONTENTS__H__

View File

@@ -29,5 +29,7 @@ public:
// Inserts a new UGC model into the database. // Inserts a new UGC model into the database.
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0; virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0;
virtual std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) = 0;
}; };
#endif //!__IUGC__H__ #endif //!__IUGC__H__

View File

@@ -98,9 +98,9 @@ public:
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; 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 InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterInfo(const IServers::MasterInfo& info) override; void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override; std::optional<uint64_t> GetCurrentPersistentId() override;
IObjectIdTracker::Range GetPersistentIdRange() override;
void InsertDefaultPersistentId() override; void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override; std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override; std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override; std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
@@ -127,7 +127,9 @@ public:
void DeleteUgcBuild(const LWOOBJID bigId) override; void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override; uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override; bool IsNameInUse(const std::string_view name) override;
IPropertyContents::Model GetModel(const LWOOBJID modelID) override; std::optional<IPropertyContents::Model> GetModel(const LWOOBJID modelID) override;
std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query); sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
private: private:

View File

@@ -1,17 +1,42 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
std::optional<uint32_t> MySQLDatabase::GetCurrentPersistentId() { std::optional<uint64_t> MySQLDatabase::GetCurrentPersistentId() {
auto result = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); auto result = ExecuteSelect("SELECT last_object_id FROM object_id_tracker");
if (!result->next()) { if (!result->next()) {
return std::nullopt; return std::nullopt;
} }
return result->getUInt("last_object_id"); return result->getUInt64("last_object_id");
} }
void MySQLDatabase::InsertDefaultPersistentId() { void MySQLDatabase::InsertDefaultPersistentId() {
ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);");
} }
void MySQLDatabase::UpdatePersistentId(const uint32_t newId) { IObjectIdTracker::Range MySQLDatabase::GetPersistentIdRange() {
ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId); 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;
} }

View File

@@ -1,6 +1,23 @@
#include "MySQLDatabase.h" #include "MySQLDatabase.h"
#include "ePropertySortType.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<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) { std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) {
std::optional<IProperty::PropertyEntranceResult> result; std::optional<IProperty::PropertyEntranceResult> result;
std::string query; std::string query;
@@ -117,19 +134,7 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
while (properties->next()) { while (properties->next()) {
if (!result) result = IProperty::PropertyEntranceResult(); if (!result) result = IProperty::PropertyEntranceResult();
auto& entry = result->entries.emplace_back(); result->entries.push_back(ReadPropertyInfo(properties));
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");
} }
return result; return result;
@@ -144,21 +149,7 @@ std::optional<IProperty::Info> MySQLDatabase::GetPropertyInfo(const LWOMAPID map
return std::nullopt; return std::nullopt;
} }
IProperty::Info toReturn; return ReadPropertyInfo(propertyEntry);
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;
} }
void MySQLDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { void MySQLDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) {
@@ -195,3 +186,15 @@ void MySQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_
zoneId.GetMapID() zoneId.GetMapID()
); );
} }
std::optional<IProperty::Info> 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);
}

View File

@@ -64,26 +64,27 @@ void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId);
} }
IPropertyContents::Model MySQLDatabase::GetModel(const LWOOBJID modelID) { std::optional<IPropertyContents::Model> MySQLDatabase::GetModel(const LWOOBJID modelID) {
auto result = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID); auto result = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID);
IPropertyContents::Model model{}; std::optional<IPropertyContents::Model> model = std::nullopt;
while (result->next()) { while (result->next()) {
model.id = result->getUInt64("id"); model = IPropertyContents::Model{};
model.lot = static_cast<LOT>(result->getUInt("lot")); model->id = result->getUInt64("id");
model.position.x = result->getFloat("x"); model->lot = static_cast<LOT>(result->getUInt("lot"));
model.position.y = result->getFloat("y"); model->position.x = result->getFloat("x");
model.position.z = result->getFloat("z"); model->position.y = result->getFloat("y");
model.rotation.w = result->getFloat("rw"); model->position.z = result->getFloat("z");
model.rotation.x = result->getFloat("rx"); model->rotation.w = result->getFloat("rw");
model.rotation.y = result->getFloat("ry"); model->rotation.x = result->getFloat("rx");
model.rotation.z = result->getFloat("rz"); model->rotation.y = result->getFloat("ry");
model.ugcId = result->getUInt64("ugc_id"); model->rotation.z = result->getFloat("rz");
model.behaviors[0] = result->getUInt64("behavior_1"); model->ugcId = result->getUInt64("ugc_id");
model.behaviors[1] = result->getUInt64("behavior_2"); model->behaviors[0] = result->getUInt64("behavior_1");
model.behaviors[2] = result->getUInt64("behavior_3"); model->behaviors[1] = result->getUInt64("behavior_2");
model.behaviors[3] = result->getUInt64("behavior_4"); model->behaviors[2] = result->getUInt64("behavior_3");
model.behaviors[4] = result->getUInt64("behavior_5"); model->behaviors[3] = result->getUInt64("behavior_4");
model->behaviors[4] = result->getUInt64("behavior_5");
} }
return model; return model;

View File

@@ -1,5 +1,17 @@
#include "MySQLDatabase.h" #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<std::istream> blob(result->getBlob("lxfml"));
model.lxfmlData << blob->rdbuf();
model.id = result->getUInt64("ugcID");
model.modelID = result->getUInt64("modelID");
return model;
}
std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) { std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) {
auto result = ExecuteSelect( 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;", "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<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId)
std::vector<IUgc::Model> toReturn; std::vector<IUgc::Model> toReturn;
while (result->next()) { while (result->next()) {
IUgc::Model model; toReturn.push_back(ReadModel(result));
// blob is owned by the query, so we need to do a deep copy :/
std::unique_ptr<std::istream> blob(result->getBlob("lxfml"));
model.lxfmlData << blob->rdbuf();
model.id = result->getUInt64("ugcID");
model.modelID = result->getUInt64("modelID");
toReturn.push_back(std::move(model));
} }
return toReturn; return toReturn;
@@ -27,14 +32,7 @@ std::vector<IUgc::Model> MySQLDatabase::GetAllUgcModels() {
std::vector<IUgc::Model> models; std::vector<IUgc::Model> models;
models.reserve(result->rowsCount()); models.reserve(result->rowsCount());
while (result->next()) { while (result->next()) {
IUgc::Model model; models.push_back(ReadModel(result));
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<std::istream> blob(result->getBlob("lxfml"));
model.lxfmlData << blob->rdbuf();
models.push_back(std::move(model));
} }
return models; return models;
@@ -45,7 +43,7 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() {
} }
void MySQLDatabase::InsertNewUgcModel( void MySQLDatabase::InsertNewUgcModel(
std:: stringstream& sd0Data, // cant be const sad std::stringstream& sd0Data, // cant be const sad
const uint64_t blueprintId, const uint64_t blueprintId,
const uint32_t accountId, const uint32_t accountId,
const LWOOBJID characterId) { const LWOOBJID characterId) {
@@ -71,3 +69,14 @@ void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstrea
const std::istream stream(lxfml.rdbuf()); const std::istream stream(lxfml.rdbuf());
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
} }
std::optional<IUgc::Model> 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<IUgc::Model> toReturn = std::nullopt;
if (result->next()) {
toReturn = ReadModel(result);
}
return toReturn;
}

View File

@@ -96,9 +96,9 @@ public:
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; 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 InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterInfo(const IServers::MasterInfo& info) override; void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override; std::optional<uint64_t> GetCurrentPersistentId() override;
IObjectIdTracker::Range GetPersistentIdRange() override;
void InsertDefaultPersistentId() override; void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override; std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override; std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override; std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
@@ -125,7 +125,9 @@ public:
void DeleteUgcBuild(const LWOOBJID bigId) override; void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override; uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override; bool IsNameInUse(const std::string_view name) override;
IPropertyContents::Model GetModel(const LWOOBJID modelID) override; std::optional<IPropertyContents::Model> GetModel(const LWOOBJID modelID) override;
std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) override;
private: private:
CppSQLite3Statement CreatePreppedStmt(const std::string& query); CppSQLite3Statement CreatePreppedStmt(const std::string& query);

View File

@@ -1,17 +1,40 @@
#include "SQLiteDatabase.h" #include "SQLiteDatabase.h"
std::optional<uint32_t> SQLiteDatabase::GetCurrentPersistentId() { std::optional<uint64_t> SQLiteDatabase::GetCurrentPersistentId() {
auto [_, result] = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); auto [_, result] = ExecuteSelect("SELECT last_object_id FROM object_id_tracker");
if (result.eof()) { if (result.eof()) {
return std::nullopt; return std::nullopt;
} }
return result.getIntField("last_object_id"); return result.getInt64Field("last_object_id");
} }
void SQLiteDatabase::InsertDefaultPersistentId() { void SQLiteDatabase::InsertDefaultPersistentId() {
ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);");
} }
void SQLiteDatabase::UpdatePersistentId(const uint32_t newId) { IObjectIdTracker::Range SQLiteDatabase::GetPersistentIdRange() {
ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId); 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;
} }

View File

@@ -1,6 +1,23 @@
#include "SQLiteDatabase.h" #include "SQLiteDatabase.h"
#include "ePropertySortType.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<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) { std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) {
std::optional<IProperty::PropertyEntranceResult> result; std::optional<IProperty::PropertyEntranceResult> result;
std::string query; std::string query;
@@ -118,19 +135,7 @@ std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(c
auto& [_, properties] = propertiesRes; auto& [_, properties] = propertiesRes;
if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult(); if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult();
while (!properties.eof()) { while (!properties.eof()) {
auto& entry = result->entries.emplace_back(); result->entries.push_back(ReadPropertyInfo(properties));
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");
properties.nextRow(); properties.nextRow();
} }
@@ -146,21 +151,7 @@ std::optional<IProperty::Info> SQLiteDatabase::GetPropertyInfo(const LWOMAPID ma
return std::nullopt; return std::nullopt;
} }
IProperty::Info toReturn; return ReadPropertyInfo(propertyEntry);
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;
} }
void SQLiteDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { void SQLiteDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) {
@@ -197,3 +188,15 @@ void SQLiteDatabase::InsertNewProperty(const IProperty::Info& info, const uint32
zoneId.GetMapID() zoneId.GetMapID()
); );
} }
std::optional<IProperty::Info> 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);
}

View File

@@ -64,27 +64,28 @@ void SQLiteDatabase::RemoveModel(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId);
} }
IPropertyContents::Model SQLiteDatabase::GetModel(const LWOOBJID modelID) { std::optional<IPropertyContents::Model> SQLiteDatabase::GetModel(const LWOOBJID modelID) {
auto [_, result] = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID); auto [_, result] = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID);
IPropertyContents::Model model{}; std::optional<IPropertyContents::Model> model = std::nullopt;
if (!result.eof()) { if (!result.eof()) {
do { do {
model.id = result.getInt64Field("id"); model = IPropertyContents::Model{};
model.lot = static_cast<LOT>(result.getIntField("lot")); model->id = result.getInt64Field("id");
model.position.x = result.getFloatField("x"); model->lot = static_cast<LOT>(result.getIntField("lot"));
model.position.y = result.getFloatField("y"); model->position.x = result.getFloatField("x");
model.position.z = result.getFloatField("z"); model->position.y = result.getFloatField("y");
model.rotation.w = result.getFloatField("rw"); model->position.z = result.getFloatField("z");
model.rotation.x = result.getFloatField("rx"); model->rotation.w = result.getFloatField("rw");
model.rotation.y = result.getFloatField("ry"); model->rotation.x = result.getFloatField("rx");
model.rotation.z = result.getFloatField("rz"); model->rotation.y = result.getFloatField("ry");
model.ugcId = result.getInt64Field("ugc_id"); model->rotation.z = result.getFloatField("rz");
model.behaviors[0] = result.getInt64Field("behavior_1"); model->ugcId = result.getInt64Field("ugc_id");
model.behaviors[1] = result.getInt64Field("behavior_2"); model->behaviors[0] = result.getInt64Field("behavior_1");
model.behaviors[2] = result.getInt64Field("behavior_3"); model->behaviors[1] = result.getInt64Field("behavior_2");
model.behaviors[3] = result.getInt64Field("behavior_4"); model->behaviors[2] = result.getInt64Field("behavior_3");
model.behaviors[4] = result.getInt64Field("behavior_5"); model->behaviors[3] = result.getInt64Field("behavior_4");
model->behaviors[4] = result.getInt64Field("behavior_5");
} while (result.nextRow()); } while (result.nextRow());
} }

View File

@@ -1,5 +1,17 @@
#include "SQLiteDatabase.h" #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<const char*>(blob), blobSize);
model.id = result.getInt64Field("ugcID");
model.modelID = result.getInt64Field("modelID");
return model;
}
std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId) { std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId) {
auto [_, result] = ExecuteSelect( 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;", "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<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId
std::vector<IUgc::Model> toReturn; std::vector<IUgc::Model> toReturn;
while (!result.eof()) { while (!result.eof()) {
IUgc::Model model; toReturn.push_back(ReadModel(result));
int blobSize{};
const auto* blob = result.getBlobField("lxfml", blobSize);
model.lxfmlData << std::string(reinterpret_cast<const char*>(blob), blobSize);
model.id = result.getInt64Field("ugcID");
model.modelID = result.getInt64Field("modelID");
toReturn.push_back(std::move(model));
result.nextRow(); result.nextRow();
} }
@@ -27,14 +32,7 @@ std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
std::vector<IUgc::Model> models; std::vector<IUgc::Model> models;
while (!result.eof()) { while (!result.eof()) {
IUgc::Model model; models.push_back(ReadModel(result));
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<const char*>(blob), blobSize);
models.push_back(std::move(model));
result.nextRow(); result.nextRow();
} }
@@ -72,3 +70,14 @@ void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstre
const std::istream stream(lxfml.rdbuf()); const std::istream stream(lxfml.rdbuf());
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
} }
std::optional<IUgc::Model> 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<IUgc::Model> toReturn = std::nullopt;
if (!result.eof()) {
toReturn = ReadModel(result);
}
return toReturn;
}

View File

@@ -244,7 +244,7 @@ void TestSQLDatabase::SetMasterInfo(const IServers::MasterInfo& info) {
} }
std::optional<uint32_t> TestSQLDatabase::GetCurrentPersistentId() { std::optional<uint64_t> TestSQLDatabase::GetCurrentPersistentId() {
return {}; return {};
} }
@@ -252,10 +252,6 @@ void TestSQLDatabase::InsertDefaultPersistentId() {
} }
void TestSQLDatabase::UpdatePersistentId(const uint32_t id) {
}
std::optional<uint32_t> TestSQLDatabase::GetDonationTotal(const uint32_t activityId) { std::optional<uint32_t> TestSQLDatabase::GetDonationTotal(const uint32_t activityId) {
return {}; return {};
} }
@@ -304,3 +300,6 @@ void TestSQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGame
} }
IObjectIdTracker::Range TestSQLDatabase::GetPersistentIdRange() {
return {};
}

View File

@@ -75,9 +75,9 @@ class TestSQLDatabase : public GameDatabase {
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; 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 InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterInfo(const IServers::MasterInfo& info) override; void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override; std::optional<uint64_t> GetCurrentPersistentId() override;
IObjectIdTracker::Range GetPersistentIdRange() override;
void InsertDefaultPersistentId() override; void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override; std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override; std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override; std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
@@ -105,7 +105,9 @@ class TestSQLDatabase : public GameDatabase {
uint32_t GetAccountCount() override { return 0; }; uint32_t GetAccountCount() override { return 0; };
bool IsNameInUse(const std::string_view name) override { return false; }; bool IsNameInUse(const std::string_view name) override { return false; };
IPropertyContents::Model GetModel(const LWOOBJID modelID) override { return {}; } std::optional<IPropertyContents::Model> GetModel(const LWOOBJID modelID) override { return {}; }
std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) override { return {}; }
std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) override { return {}; }
}; };
#endif //!TESTSQLDATABASE_H #endif //!TESTSQLDATABASE_H

View File

@@ -10,7 +10,7 @@ void ModelNormalizeMigration::Run() {
for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) {
const auto model = Database::Get()->GetModel(modelID); 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. // 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); Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed(); 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); LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z);
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size()); sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto asStream = sd0.GetAsStream(); 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()->UpdateUgcModelData(id, asStream);
} }
Database::Get()->SetAutoCommit(oldCommit); Database::Get()->SetAutoCommit(oldCommit);
@@ -35,15 +35,15 @@ void ModelNormalizeMigration::RunAfterFirstPart() {
for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) {
const auto model = Database::Get()->GetModel(modelID); const auto model = Database::Get()->GetModel(modelID);
// only BBB models (lot 14) need to have their position fixed from the above blunder // 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); Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed(); 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<const uint8_t*>(newLxfml.data()), newLxfml.size()); sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto asStream = sd0.GetAsStream(); 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()->UpdateUgcModelData(id, asStream);
} }
Database::Get()->SetAutoCommit(oldCommit); Database::Get()->SetAutoCommit(oldCommit);
@@ -55,16 +55,16 @@ void ModelNormalizeMigration::RunBrickBuildGrid() {
for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) {
const auto model = Database::Get()->GetModel(modelID); const auto model = Database::Get()->GetModel(modelID);
// only BBB models (lot 14) need to have their position fixed from the above blunder // 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); Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed(); 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<const uint8_t*>(newLxfml.data()), newLxfml.size()); sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z); LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z);
auto asStream = sd0.GetAsStream(); 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()->UpdateUgcModelData(id, asStream);
} }
Database::Get()->SetAutoCommit(oldCommit); Database::Get()->SetAutoCommit(oldCommit);

View File

@@ -30,6 +30,7 @@
#include "BitStreamUtils.h" #include "BitStreamUtils.h"
#include "CheatDetection.h" #include "CheatDetection.h"
#include "CharacterComponent.h" #include "CharacterComponent.h"
#include "eCharacterVersion.h"
UserManager* UserManager::m_Address = nullptr; UserManager* UserManager::m_Address = nullptr;
@@ -324,12 +325,18 @@ 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()); 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: //Now that the name is ok, we can get a persistent ObjectID:
ObjectIDManager::RequestPersistentID([=, this](uint32_t persistentID) { LWOOBJID objectID = ObjectIDManager::GetPersistentID();
LWOOBJID objectID = persistentID; const uint32_t maxRetries = 100;
GeneralUtils::SetBit(objectID, eObjectBits::CHARACTER); uint32_t tries = 0;
if (Database::Get()->GetCharacterInfo(objectID)) { while (Database::Get()->GetCharacterInfo(objectID) && tries < maxRetries) {
LOG("Character object id unavailable, check object_id_tracker!"); tries++;
LOG("Found a duplicate character %llu, getting a new objectID", objectID);
objectID = ObjectIDManager::GetPersistentID();
}
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); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
return; return;
} }
@@ -352,20 +359,13 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
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 << "<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\">";
LWOOBJID lwoidforshirt = ObjectIDManager::GenerateRandomObjectID(); LWOOBJID lwoidforshirt = ObjectIDManager::GetPersistentID();
LWOOBJID lwoidforpants; LWOOBJID lwoidforpants = ObjectIDManager::GetPersistentID();
do {
lwoidforpants = ObjectIDManager::GenerateRandomObjectID();
} while (lwoidforpants == lwoidforshirt); //Make sure we don't have the same ID for both shirt and pants
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=\"" << 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=\"" << pantsLOT << "\" id=\"" << lwoidforpants << "\" s=\"1\" c=\"1\" eq=\"1\" b=\"1\"/>";
xml << "</in></items></inv><lvl l=\"1\" cv=\"1\" sb=\"500\"/><flag></flag></obj>"; xml << "</in></items></inv><lvl l=\"1\" cv=\"" << GeneralUtils::ToUnderlying(eCharacterVersion::UP_TO_DATE) << "\" sb=\"500\"/><flag></flag></obj>";
//Check to see if our name was pre-approved: //Check to see if our name was pre-approved:
bool nameOk = IsNamePreapproved(name); bool nameOk = IsNamePreapproved(name);
@@ -396,7 +396,6 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS);
UserManager::RequestCharacterList(sysAddr); UserManager::RequestCharacterList(sysAddr);
});
} }
void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) { void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) {

View File

@@ -1786,3 +1786,9 @@ void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) {
groupElement = groupElement->NextSiblingElement("grp"); groupElement = groupElement->NextSiblingElement("grp");
} }
} }
void InventoryComponent::RegenerateItemIDs() {
for (auto* const inventory : m_Inventories | std::views::values) {
inventory->RegenerateItemIDs();
}
}

View File

@@ -407,6 +407,9 @@ public:
void FixInvisibleItems(); void FixInvisibleItems();
// Used to migrate a character version, no need to call outside of that context
void RegenerateItemIDs();
~InventoryComponent() override; ~InventoryComponent() override;
private: private:

View File

@@ -479,9 +479,19 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
return; 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; m_DatabaseId = petSubKey;

View File

@@ -135,7 +135,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
const auto owner = propertyEntry.ownerId; const auto owner = propertyEntry.ownerId;
const auto otherCharacter = Database::Get()->GetCharacterInfo(owner); const auto otherCharacter = Database::Get()->GetCharacterInfo(owner);
if (!otherCharacter.has_value()) { 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; continue;
} }
auto& entry = entries.emplace_back(); auto& entry = entries.emplace_back();

View File

@@ -203,14 +203,22 @@ bool PropertyManagementComponent::Claim(const LWOOBJID playerId) {
auto prop_path = zone->GetPath(m_Parent->GetVarAsString(u"propertyName")); 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; if (!prop_path->property.displayName.empty()) name = prop_path->property.displayName;
description = prop_path->property.displayDesc; description = prop_path->property.displayDesc;
} }
SetOwnerId(playerId); 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; IProperty::Info info;
info.id = propertyId; info.id = propertyId;
@@ -374,7 +382,6 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
node->position = position; node->position = position;
node->rotation = rotation; node->rotation = rotation;
ObjectIDManager::RequestPersistentID([this, node, modelLOT, entity, position, rotation, originalRotation](uint32_t persistentId) {
SpawnerInfo info{}; SpawnerInfo info{};
info.templateID = modelLOT; info.templateID = modelLOT;
@@ -387,7 +394,7 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
info.emulated = true; info.emulated = true;
info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID(); info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID();
info.spawnerID = persistentId; info.spawnerID = ObjectIDManager::GetPersistentID();
GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT); GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT);
const auto spawnerId = Game::zoneManager->MakeSpawner(info); const auto spawnerId = Game::zoneManager->MakeSpawner(info);
@@ -413,7 +420,7 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
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 // Progress place model missions
auto missionComponent = entity->GetComponent<MissionComponent>(); auto missionComponent = entity->GetComponent<MissionComponent>();
if (missionComponent != nullptr) missionComponent->Progress(eMissionTaskType::PLACE_MODEL, 0); if (missionComponent != nullptr) missionComponent->Progress(eMissionTaskType::PLACE_MODEL, 0);
@@ -693,7 +700,7 @@ void PropertyManagementComponent::Save() {
// save the behaviors of the model // save the behaviors of the model
for (const auto& [behaviorId, behaviorStr] : modelBehaviors) { for (const auto& [behaviorId, behaviorStr] : modelBehaviors) {
if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue; if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue;
IBehaviors::Info info { IBehaviors::Info info{
.behaviorId = behaviorId, .behaviorId = behaviorId,
.characterId = character->GetID(), .characterId = character->GetID(),
.behaviorInfo = behaviorStr .behaviorInfo = behaviorStr

View File

@@ -2555,7 +2555,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
uint32_t sd0Size; uint32_t sd0Size;
inStream.Read(sd0Size); inStream.Read(sd0Size);
std::shared_ptr<char[]> sd0Data(new char[sd0Size]); std::unique_ptr<char[]> sd0Data(new char[sd0Size]);
if (sd0Data == nullptr) return; if (sd0Data == nullptr) return;
@@ -2579,17 +2579,25 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
//Now, the cave of dragons: //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: //We need to get a new ID for our model first:
ObjectIDManager::RequestPersistentID([=](uint32_t newID) {
if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return; if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return;
LWOOBJID newIDL = newID; const uint32_t maxRetries = 100;
GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER); uint32_t retries = 0;
bool blueprintIDExists = true;
bool modelExists = true;
LWOOBJID blueprintID = ObjectIDManager::GenerateRandomObjectID(); // Legacy logic to check for old random IDs (regenerating these is not really feasible)
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); // 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) //We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
@@ -2629,8 +2637,8 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
(or you uncomment the lxfml decomp stuff above) (or you uncomment the lxfml decomp stuff above)
*/ */
////Send off to UGC for processing, if enabled: // //Send off to UGC for processing, if enabled:
//if (Game::config->GetValue("ugc_remote") == "1") { // if (Game::config->GetValue("ugc_remote") == "1") {
// std::string ugcIP = Game::config->GetValue("ugc_ip"); // std::string ugcIP = Game::config->GetValue("ugc_ip");
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port")); // int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
@@ -2642,12 +2650,12 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model & // //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. // //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): //Tell the client their model is saved: (this causes us to actually pop out of our current state):
const auto& newSd0 = sd0.GetAsVector(); const auto& newSd0 = sd0.GetAsVector();
uint32_t sd0Size{}; uint32_t newSd0Size{};
for (const auto& chunk : newSd0) sd0Size += chunk.size(); for (const auto& chunk : newSd0) newSd0Size += chunk.size();
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE); BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write(localId); bitStream.Write(localId);
@@ -2655,7 +2663,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
bitStream.Write<uint32_t>(1); bitStream.Write<uint32_t>(1);
bitStream.Write(blueprintID); 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());
@@ -2686,8 +2694,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
//there was an issue with builds not appearing since it was placed above ConstructEntity. //there was an issue with builds not appearing since it was placed above ConstructEntity.
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL); PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
} }
});
} }
void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
@@ -5526,7 +5532,6 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
} }
} }
ObjectIDManager::RequestPersistentID([=](uint32_t newId) {
LOG("Build finished"); LOG("Build finished");
GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it
@@ -5539,17 +5544,16 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
std::vector<LDFBaseData*> config; std::vector<LDFBaseData*> config;
config.push_back(moduleAssembly); config.push_back(moduleAssembly);
LWOOBJID newIdBig = newId; LWOOBJID newID = ObjectIDManager::GetPersistentID();
GeneralUtils::SetBit(newIdBig, eObjectBits::CHARACTER);
if (count == 3) { if (count == 3) {
inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig); inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID);
} else if (count == 7) { } else if (count == 7) {
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig); inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID);
} }
auto* pCharacter = character->GetCharacter(); auto* pCharacter = character->GetCharacter();
Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newIdBig, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt); Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newID, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
auto* missionComponent = character->GetComponent<MissionComponent>(); auto* missionComponent = character->GetComponent<MissionComponent>();
@@ -5574,7 +5578,6 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
for (auto* item : items) { for (auto* item : items) {
inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false); inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false);
} }
});
} }
} }

View File

@@ -5,8 +5,11 @@
#include "InventoryComponent.h" #include "InventoryComponent.h"
#include "eItemType.h" #include "eItemType.h"
#include "eReplicaComponentType.h" #include "eReplicaComponentType.h"
#include "ObjectIDManager.h"
#include "eObjectBits.h"
#include "CDComponentsRegistryTable.h" #include "CDComponentsRegistryTable.h"
#include <ranges>
std::vector<LOT> Inventory::m_GameMasterRestrictedItems = { std::vector<LOT> Inventory::m_GameMasterRestrictedItems = {
1727, // GM Only - JetPack 1727, // GM Only - JetPack
@@ -317,3 +320,16 @@ Inventory::~Inventory() {
items.clear(); 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;
}

View File

@@ -158,6 +158,8 @@ public:
*/ */
void DeleteAllItems(); void DeleteAllItems();
void RegenerateItemIDs();
~Inventory(); ~Inventory();
private: private:

View File

@@ -98,21 +98,12 @@ Item::Item(
this->preconditions = new PreconditionExpression(this->info->reqPrecondition); this->preconditions = new PreconditionExpression(this->info->reqPrecondition);
this->subKey = subKey; this->subKey = subKey;
LWOOBJID id = ObjectIDManager::GenerateRandomObjectID(); auto* const inventoryComponent = inventory->GetComponent();
GenerateID();
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;
inventory->AddManagedItem(this); 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); GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, static_cast<int>(this->count), subKey, lootSourceType);
if (isModMoveAndEquip) { if (isModMoveAndEquip) {
@@ -120,7 +111,7 @@ Item::Item(
LOG("Move and equipped (%i) from (%i)", this->lot, this->inventory->GetType()); 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)); 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;
}

View File

@@ -228,6 +228,8 @@ public:
void LoadConfigXml(const tinyxml2::XMLElement& i); void LoadConfigXml(const tinyxml2::XMLElement& i);
LWOOBJID GenerateID();
private: private:
/** /**
* The object ID of this item * The object ID of this item

View File

@@ -35,14 +35,11 @@
void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) { void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) {
BehaviorMessageBase msgBase{ context.arguments }; BehaviorMessageBase msgBase{ context.arguments };
const auto oldBehaviorID = msgBase.GetBehaviorId(); const auto oldBehaviorID = msgBase.GetBehaviorId();
ObjectIDManager::RequestPersistentID(
[context, oldBehaviorID](uint32_t persistentId) {
if (!context) { if (!context) {
LOG("Model to update behavior ID for is null. Cannot update ID."); LOG("Model to update behavior ID for is null. Cannot update ID.");
return; return;
} }
LWOOBJID persistentIdBig = persistentId; LWOOBJID persistentIdBig = ObjectIDManager::GetPersistentID();
GeneralUtils::SetBit(persistentIdBig, eObjectBits::CHARACTER);
// This updates the behavior ID of the behavior should this be a new behavior // This updates the behavior ID of the behavior should this be a new behavior
AMFArrayValue args; AMFArrayValue args;
@@ -53,7 +50,6 @@ void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) {
context.modelComponent->UpdatePendingBehaviorId(persistentIdBig, oldBehaviorID); context.modelComponent->UpdatePendingBehaviorId(persistentIdBig, oldBehaviorID);
ControlBehaviors::Instance().SendBehaviorListToClient(context); ControlBehaviors::Instance().SendBehaviorListToClient(context);
});
} }
void ControlBehaviors::SendBehaviorListToClient(const ControlBehaviorContext& context) { void ControlBehaviors::SendBehaviorListToClient(const ControlBehaviorContext& context) {

View File

@@ -5,47 +5,38 @@
#include "Database.h" #include "Database.h"
#include "Logger.h" #include "Logger.h"
#include "Game.h" #include "Game.h"
#include "eObjectBits.h"
//! The persistent ID request // proxy items and dropped loot get SPAWNED + CLIENT flag and need to have the ChangeObjectWorldState message sent to them
struct PersistentIDRequest { // should the spawners from vanity also have the CLIENT flag?
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;
};
namespace { namespace {
std::vector<PersistentIDRequest> Requests; //!< All outstanding persistent ID requests // Start the range in a way that it when first called it will fetch some new persistent IDs
uint64_t CurrentRequestID = 0; //!< The current request ID std::optional<IObjectIdTracker::Range> CurrentRange = std::nullopt;
uint32_t CurrentObjectID = uint32_t(1152921508165007067); //!< The current object ID 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)
std::uniform_int_distribution<int> Uni(10000000, INT32_MAX);
}; };
//! Requests a persistent ID uint64_t ObjectIDManager::GetPersistentID() {
void ObjectIDManager::RequestPersistentID(const std::function<void(uint32_t)> callback) { if (!CurrentRange.has_value() || CurrentRange->minID > CurrentRange->maxID) {
const auto& request = Requests.emplace_back(++CurrentRequestID, callback); 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 // Generates an object ID server-sided (used for regular entities like smashables)
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)
uint32_t ObjectIDManager::GenerateObjectID() { uint32_t ObjectIDManager::GenerateObjectID() {
return ++CurrentObjectID; return ++CurrentObjectID;
} }

View File

@@ -5,36 +5,25 @@
#include <vector> #include <vector>
#include <stdint.h> #include <stdint.h>
/*! /**
\file ObjectIDManager.h * There are 2 types of IDs:
\brief A manager for handling object ID generation * 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 { 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 * @brief Generates an ephemeral object ID for non-persistent objects.
/*! *
\param requestID The request ID * @return uint32_t
\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
*/ */
uint32_t GenerateObjectID(); uint32_t GenerateObjectID();
//! Generates a random object ID server-sided
/*!
\return A generated object ID
*/
uint32_t GenerateRandomObjectID();
}; };

View File

@@ -1,6 +1,5 @@
set(DMASTERSERVER_SOURCES set(DMASTERSERVER_SOURCES
"InstanceManager.cpp" "InstanceManager.cpp"
"PersistentIDManager.cpp"
"Start.cpp" "Start.cpp"
) )

View File

@@ -35,7 +35,6 @@
#include "Game.h" #include "Game.h"
#include "InstanceManager.h" #include "InstanceManager.h"
#include "MasterPackets.h" #include "MasterPackets.h"
#include "PersistentIDManager.h"
#include "FdbToSqlite.h" #include "FdbToSqlite.h"
#include "BitStreamUtils.h" #include "BitStreamUtils.h"
#include "Start.h" #include "Start.h"
@@ -360,7 +359,6 @@ int main(int argc, char** argv) {
Database::Get()->SetMasterInfo(info); Database::Get()->SetMasterInfo(info);
//Create additional objects here: //Create additional objects here:
PersistentIDManager::Initialize();
Game::im = new InstanceManager(Game::server->GetIP()); Game::im = new InstanceManager(Game::server->GetIP());
//Get CDClient initial information //Get CDClient initial information
@@ -534,17 +532,6 @@ void HandlePacket(Packet* packet) {
if (static_cast<ServiceType>(packet->data[1]) == ServiceType::MASTER) { if (static_cast<ServiceType>(packet->data[1]) == ServiceType::MASTER) {
switch (static_cast<MessageType::Master>(packet->data[3])) { switch (static_cast<MessageType::Master>(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: { case MessageType::Master::REQUEST_ZONE_TRANSFER: {
LOG("Received zone transfer req"); LOG("Received zone transfer req");
@@ -882,9 +869,6 @@ int ShutdownSequence(int32_t signal) {
LOG("Triggered master shutdown"); 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. // A server might not be finished spinning up yet, remove all of those here.
for (const auto& instance : Game::im->GetInstances()) { for (const auto& instance : Game::im->GetInstances()) {
if (!instance) continue; if (!instance) continue;

View File

@@ -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);
}

View File

@@ -1,23 +0,0 @@
#pragma once
// C++
#include <cstdint>
/*!
\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();
};

View File

@@ -8,23 +8,6 @@
#include <string> #include <string>
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) { void MasterPackets::SendZoneTransferRequest(dServer* server, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t cloneID) {
RakNet::BitStream bitStream; RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER); BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER);

View File

@@ -8,9 +8,6 @@
class dServer; class dServer;
namespace MasterPackets { 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 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); 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 SendZoneRequestPrivate(dServer* server, uint64_t requestID, bool mythranShift, const std::string& password);
void SendWorldReady(dServer* server, LWOMAPID zoneId, LWOINSTANCEID instanceId); void SendWorldReady(dServer* server, LWOMAPID zoneId, LWOINSTANCEID instanceId);
void HandleSetSessionKey(Packet* packet);
} }
#endif // MASTERPACKETS_H #endif // MASTERPACKETS_H

View File

@@ -8,7 +8,6 @@
#include "CharacterComponent.h" #include "CharacterComponent.h"
#include "SimplePhysicsComponent.h" #include "SimplePhysicsComponent.h"
#include "MovementAIComponent.h" #include "MovementAIComponent.h"
#include "ObjectIDManager.h"
#include "MissionComponent.h" #include "MissionComponent.h"
#include "Loot.h" #include "Loot.h"
#include "InventoryComponent.h" #include "InventoryComponent.h"

View File

@@ -35,7 +35,6 @@
#include "CDClientManager.h" #include "CDClientManager.h"
#include "CDClientDatabase.h" #include "CDClientDatabase.h"
#include "GeneralUtils.h" #include "GeneralUtils.h"
#include "ObjectIDManager.h"
#include "ZoneInstanceManager.h" #include "ZoneInstanceManager.h"
#include "dChatFilter.h" #include "dChatFilter.h"
#include "ClientPackets.h" #include "ClientPackets.h"
@@ -676,15 +675,6 @@ void HandleMasterPacket(Packet* packet) {
if (packet->length < 2) return; if (packet->length < 2) return;
if (static_cast<ServiceType>(packet->data[1]) != ServiceType::MASTER || packet->length < 4) return; if (static_cast<ServiceType>(packet->data[1]) != ServiceType::MASTER || packet->length < 4) return;
switch (static_cast<MessageType::Master>(packet->data[3])) { switch (static_cast<MessageType::Master>(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: { case MessageType::Master::SESSION_KEY_RESPONSE: {
//Read our session key and to which user it belongs: //Read our session key and to which user it belongs:
@@ -1047,6 +1037,7 @@ void HandlePacket(Packet* packet) {
auto version = levelComponent->GetCharacterVersion(); auto version = levelComponent->GetCharacterVersion();
LOG("Updating character from version %s", StringifiedEnum::ToString(version).data()); LOG("Updating character from version %s", StringifiedEnum::ToString(version).data());
if (version < eCharacterVersion::UP_TO_DATE) {
switch (version) { switch (version) {
case eCharacterVersion::RELEASE: case eCharacterVersion::RELEASE:
// TODO: Implement, super low priority // TODO: Implement, super low priority
@@ -1118,12 +1109,19 @@ void HandlePacket(Packet* packet) {
invPets[newId] = databasePet; 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); levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE);
[[fallthrough]]; [[fallthrough]];
} }
case eCharacterVersion::UP_TO_DATE: case eCharacterVersion::UP_TO_DATE:
break; break;
} }
}
// Update the characters xml to ensure the update above is not only saved, but so the client picks up on the changes. // Update the characters xml to ensure the update above is not only saved, but so the client picks up on the changes.
c->SaveXMLToDatabase(); c->SaveXMLToDatabase();

View File

@@ -0,0 +1 @@
UPDATE `properties_contents` SET `id` = `id` | 0x1000000000000000;

View File

@@ -0,0 +1 @@
UPDATE `properties_contents` SET `id` = `id` | 0x1000000000000000;