Merge branch 'main' into bbb-lxfml-splitting

This commit is contained in:
Aaron Kimbrell
2025-09-29 09:10:31 -05:00
44 changed files with 686 additions and 668 deletions

View File

@@ -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,

View File

@@ -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__

View File

@@ -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<uint32_t> GetCurrentPersistentId() = 0;
virtual std::optional<uint64_t> 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__

View File

@@ -39,6 +39,9 @@ public:
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.
virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0;

View File

@@ -23,7 +23,7 @@ public:
// Inserts a new UGC model into the database.
virtual void InsertNewUgcModel(
std::stringstream& sd0Data,
const uint32_t blueprintId,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) = 0;
@@ -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<Model> GetModel(const LWOOBJID modelID) = 0;
};
#endif //!__IPROPERTIESCONTENTS__H__

View File

@@ -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<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) = 0;
};
#endif //!__IUGC__H__

View File

@@ -83,7 +83,7 @@ public:
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
const uint32_t blueprintId,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) override;
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
@@ -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<uint32_t> GetCurrentPersistentId() override;
std::optional<uint64_t> GetCurrentPersistentId() override;
IObjectIdTracker::Range GetPersistentIdRange() override;
void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> 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<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);
private:

View File

@@ -1,17 +1,42 @@
#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");
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;
}

View File

@@ -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<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) {
std::optional<IProperty::PropertyEntranceResult> result;
std::string query;
@@ -117,19 +134,7 @@ std::optional<IProperty::PropertyEntranceResult> 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<IProperty::Info> 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<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);
}
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);
IPropertyContents::Model model{};
std::optional<IPropertyContents::Model> model = std::nullopt;
while (result->next()) {
model.id = result->getUInt64("id");
model.lot = static_cast<LOT>(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<LOT>(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;

View File

@@ -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<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) {
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<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId)
std::vector<IUgc::Model> toReturn;
while (result->next()) {
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");
toReturn.push_back(std::move(model));
toReturn.push_back(ReadModel(result));
}
return toReturn;
@@ -27,14 +32,7 @@ std::vector<IUgc::Model> MySQLDatabase::GetAllUgcModels() {
std::vector<IUgc::Model> 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<std::istream> blob(result->getBlob("lxfml"));
model.lxfmlData << blob->rdbuf();
models.push_back(std::move(model));
models.push_back(ReadModel(result));
}
return models;
@@ -45,8 +43,8 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() {
}
void MySQLDatabase::InsertNewUgcModel(
std:: stringstream& sd0Data, // cant be const sad
const uint32_t blueprintId,
std::stringstream& sd0Data, // cant be const sad
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) {
const std::istream stream(sd0Data.rdbuf());
@@ -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<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

@@ -61,9 +61,9 @@ bool SQLiteDatabase::GetAutoCommit() {
void SQLiteDatabase::SetAutoCommit(bool value) {
if (value) {
if (GetAutoCommit()) con->compileStatement("BEGIN;").execDML();
} else {
if (!GetAutoCommit()) con->compileStatement("COMMIT;").execDML();
} else {
if (GetAutoCommit()) con->compileStatement("BEGIN;").execDML();
}
}

View File

@@ -81,7 +81,7 @@ public:
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
const uint32_t blueprintId,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) override;
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
@@ -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<uint32_t> GetCurrentPersistentId() override;
std::optional<uint64_t> GetCurrentPersistentId() override;
IObjectIdTracker::Range GetPersistentIdRange() override;
void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> 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<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:
CppSQLite3Statement CreatePreppedStmt(const std::string& query);

View File

@@ -1,17 +1,40 @@
#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");
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 begins the transaction for us if one is not already in progress
// 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.
// 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;
// We must commit here manually, this will unlock the database for all other servers
ExecuteCustomQuery("COMMIT;");
SetAutoCommit(prevCommit);
return range;
}

View File

@@ -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<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) {
std::optional<IProperty::PropertyEntranceResult> result;
std::string query;
@@ -118,19 +135,7 @@ std::optional<IProperty::PropertyEntranceResult> 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<IProperty::Info> 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<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);
}
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);
IPropertyContents::Model model{};
std::optional<IPropertyContents::Model> model = std::nullopt;
if (!result.eof()) {
do {
model.id = result.getInt64Field("id");
model.lot = static_cast<LOT>(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<LOT>(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());
}

View File

@@ -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<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) {
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<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId
std::vector<IUgc::Model> toReturn;
while (!result.eof()) {
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");
toReturn.push_back(std::move(model));
toReturn.push_back(ReadModel(result));
result.nextRow();
}
@@ -27,14 +32,7 @@ std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
std::vector<IUgc::Model> 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<const char*>(blob), blobSize);
models.push_back(std::move(model));
models.push_back(ReadModel(result));
result.nextRow();
}
@@ -47,7 +45,7 @@ void SQLiteDatabase::RemoveUnreferencedUgcModels() {
void SQLiteDatabase::InsertNewUgcModel(
std::stringstream& sd0Data, // cant be const sad
const uint32_t blueprintId,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) {
const std::istream stream(sd0Data.rdbuf());
@@ -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<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

@@ -192,7 +192,7 @@ void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
}
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) {
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) {
}
@@ -244,7 +244,7 @@ void TestSQLDatabase::SetMasterInfo(const IServers::MasterInfo& info) {
}
std::optional<uint32_t> TestSQLDatabase::GetCurrentPersistentId() {
std::optional<uint64_t> TestSQLDatabase::GetCurrentPersistentId() {
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) {
return {};
}
@@ -304,3 +300,6 @@ void TestSQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGame
}
IObjectIdTracker::Range TestSQLDatabase::GetPersistentIdRange() {
return {};
}

View File

@@ -60,7 +60,7 @@ class TestSQLDatabase : public GameDatabase {
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
const uint32_t blueprintId,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) override;
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
@@ -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<uint32_t> GetCurrentPersistentId() override;
std::optional<uint64_t> GetCurrentPersistentId() override;
IObjectIdTracker::Range GetPersistentIdRange() override;
void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> 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<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

View File

@@ -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<const uint8_t*>(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<const uint8_t*>(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<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);
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);

View File

@@ -30,6 +30,7 @@
#include "BitStreamUtils.h"
#include "CheatDetection.h"
#include "CharacterComponent.h"
#include "eCharacterVersion.h"
UserManager* UserManager::m_Address = nullptr;
@@ -324,79 +325,77 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
LOG("AccountID: %i is creating a character with name: %s (temporary: %s)", u->GetAccountID(), name.c_str(), predefinedName.c_str());
}
//Now that the name is ok, we can get an objectID from Master:
ObjectIDManager::RequestPersistentID([=, this](uint32_t persistentID) {
LWOOBJID objectID = persistentID;
GeneralUtils::SetBit(objectID, eObjectBits::CHARACTER);
if (Database::Get()->GetCharacterInfo(objectID)) {
LOG("Character object id unavailable, check object_id_tracker!");
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
return;
}
//Now that the name is ok, we can get a persistent ObjectID:
LWOOBJID objectID = ObjectIDManager::GetPersistentID();
const uint32_t maxRetries = 100;
uint32_t tries = 0;
while (Database::Get()->GetCharacterInfo(objectID) && tries < maxRetries) {
tries++;
LOG("Found a duplicate character %llu, getting a new objectID", objectID);
objectID = ObjectIDManager::GetPersistentID();
}
std::stringstream xml;
xml << "<obj v=\"1\">";
if (tries >= maxRetries) {
LOG("Failed to get a unique objectID for new character after %i tries, aborting char creation for account %i", maxRetries, u->GetAccountID());
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
return;
}
xml << "<mf hc=\"" << hairColor << "\" hs=\"" << hairStyle << "\" hd=\"0\" t=\"" << shirtColor << "\" l=\"" << pantsColor;
xml << "\" hdc=\"0\" cd=\"" << shirtStyle << "\" lh=\"" << lh << "\" rh=\"" << rh << "\" es=\"" << eyebrows << "\" ";
xml << "ess=\"" << eyes << "\" ms=\"" << mouth << "\"/>";
std::stringstream xml;
xml << "<obj v=\"1\">";
xml << "<char acct=\"" << u->GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" ";
xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" ";
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">";
xml << "<vl><l id=\"1000\" cid=\"0\"/></vl>";
xml << "<mf hc=\"" << hairColor << "\" hs=\"" << hairStyle << "\" hd=\"0\" t=\"" << shirtColor << "\" l=\"" << pantsColor;
xml << "\" hdc=\"0\" cd=\"" << shirtStyle << "\" lh=\"" << lh << "\" rh=\"" << rh << "\" es=\"" << eyebrows << "\" ";
xml << "ess=\"" << eyes << "\" ms=\"" << mouth << "\"/>";
xml << "</char>";
xml << "<char acct=\"" << u->GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" ";
xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" ";
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">";
xml << "<vl><l id=\"1000\" cid=\"0\"/></vl>";
xml << "<dest hm=\"4\" hc=\"4\" im=\"0\" ic=\"0\" am=\"0\" ac=\"0\" d=\"0\"/>";
xml << "</char>";
xml << "<inv><bag><b t=\"0\" m=\"20\"/><b t=\"1\" m=\"40\"/><b t=\"2\" m=\"240\"/><b t=\"3\" m=\"240\"/><b t=\"14\" m=\"40\"/></bag><items><in t=\"0\">";
xml << "<dest hm=\"4\" hc=\"4\" im=\"0\" ic=\"0\" am=\"0\" ac=\"0\" d=\"0\"/>";
LWOOBJID lwoidforshirt = ObjectIDManager::GenerateRandomObjectID();
LWOOBJID lwoidforpants;
xml << "<inv><bag><b t=\"0\" m=\"20\"/><b t=\"1\" m=\"40\"/><b t=\"2\" m=\"240\"/><b t=\"3\" m=\"240\"/><b t=\"14\" m=\"40\"/></bag><items><in t=\"0\">";
do {
lwoidforpants = ObjectIDManager::GenerateRandomObjectID();
} while (lwoidforpants == lwoidforshirt); //Make sure we don't have the same ID for both shirt and pants
LWOOBJID lwoidforshirt = ObjectIDManager::GetPersistentID();
LWOOBJID lwoidforpants = ObjectIDManager::GetPersistentID();
GeneralUtils::SetBit(lwoidforshirt, eObjectBits::CHARACTER);
GeneralUtils::SetBit(lwoidforpants, eObjectBits::CHARACTER);
xml << "<i l=\"" << shirtLOT << "\" id=\"" << lwoidforshirt << "\" s=\"0\" c=\"1\" eq=\"1\" b=\"1\"/>";
xml << "<i l=\"" << pantsLOT << "\" id=\"" << lwoidforpants << "\" s=\"1\" c=\"1\" eq=\"1\" b=\"1\"/>";
xml << "<i l=\"" << shirtLOT << "\" id=\"" << lwoidforshirt << "\" s=\"0\" c=\"1\" eq=\"1\" b=\"1\"/>";
xml << "<i l=\"" << pantsLOT << "\" id=\"" << lwoidforpants << "\" s=\"1\" c=\"1\" eq=\"1\" b=\"1\"/>";
xml << "</in></items></inv><lvl l=\"1\" cv=\"" << GeneralUtils::ToUnderlying(eCharacterVersion::UP_TO_DATE) << "\" sb=\"500\"/><flag></flag></obj>";
xml << "</in></items></inv><lvl l=\"1\" cv=\"1\" sb=\"500\"/><flag></flag></obj>";
//Check to see if our name was pre-approved:
bool nameOk = IsNamePreapproved(name);
if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true;
//Check to see if our name was pre-approved:
bool nameOk = IsNamePreapproved(name);
if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true;
// If predefined name is invalid, change it to be their object id
// that way more than one player can create characters if the predefined name files are not provided
auto assignedPredefinedName = predefinedName;
if (assignedPredefinedName == "INVALID") {
std::stringstream nameObjID;
nameObjID << "minifig" << objectID;
assignedPredefinedName = nameObjID.str();
}
// If predefined name is invalid, change it to be their object id
// that way more than one player can create characters if the predefined name files are not provided
auto assignedPredefinedName = predefinedName;
if (assignedPredefinedName == "INVALID") {
std::stringstream nameObjID;
nameObjID << "minifig" << objectID;
assignedPredefinedName = nameObjID.str();
}
std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName;
std::string pendingName = !name.empty() && !nameOk ? name : "";
std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName;
std::string pendingName = !name.empty() && !nameOk ? name : "";
ICharInfo::Info info;
info.name = nameToAssign;
info.pendingName = pendingName;
info.id = objectID;
info.accountId = u->GetAccountID();
ICharInfo::Info info;
info.name = nameToAssign;
info.pendingName = pendingName;
info.id = objectID;
info.accountId = u->GetAccountID();
Database::Get()->InsertNewCharacter(info);
Database::Get()->InsertNewCharacter(info);
//Now finally insert our character xml:
Database::Get()->InsertCharacterXml(objectID, xml.str());
//Now finally insert our character xml:
Database::Get()->InsertCharacterXml(objectID, xml.str());
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS);
UserManager::RequestCharacterList(sysAddr);
});
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS);
UserManager::RequestCharacterList(sysAddr);
}
void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) {

View File

@@ -626,7 +626,8 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
for (const auto& pair : this->m_Inventories) {
auto* inventory = pair.second;
if (inventory->GetType() == VENDOR_BUYBACK || inventory->GetType() == eInventoryType::MODELS_IN_BBB) {
static const auto EXCLUDED_INVENTORIES = {VENDOR_BUYBACK, MODELS_IN_BBB, ITEM_SETS};
if (std::ranges::find(EXCLUDED_INVENTORIES, inventory->GetType()) != EXCLUDED_INVENTORIES.end()) {
continue;
}
@@ -1786,3 +1787,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();
}
}

View File

@@ -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:

View File

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

View File

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

View File

@@ -170,7 +170,7 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s
info.name = propertyName;
info.description = propertyDescription;
info.lastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
Database::Get()->UpdateLastSave(info);
Database::Get()->UpdatePropertyDetails(info);
@@ -203,14 +203,22 @@ bool PropertyManagementComponent::Claim(const LWOOBJID playerId) {
auto prop_path = zone->GetPath(m_Parent->GetVarAsString(u"propertyName"));
if (prop_path){
if (prop_path) {
if (!prop_path->property.displayName.empty()) name = prop_path->property.displayName;
description = prop_path->property.displayDesc;
}
SetOwnerId(playerId);
propertyId = ObjectIDManager::GenerateRandomObjectID();
// Due to legacy IDs being random
propertyId = ObjectIDManager::GetPersistentID();
const uint32_t maxTries = 100;
uint32_t tries = 0;
while (Database::Get()->GetPropertyInfo(propertyId) && tries < maxTries) {
tries++;
LOG("Found a duplicate property %llu, getting a new propertyId", propertyId);
propertyId = ObjectIDManager::GetPersistentID();
}
IProperty::Info info;
info.id = propertyId;
@@ -374,46 +382,45 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
node->position = position;
node->rotation = rotation;
ObjectIDManager::RequestPersistentID([this, node, modelLOT, entity, position, rotation, originalRotation](uint32_t persistentId) {
SpawnerInfo info{};
SpawnerInfo info{};
info.templateID = modelLOT;
info.nodes = { node };
info.templateScale = 1.0f;
info.activeOnLoad = true;
info.amountMaintained = 1;
info.respawnTime = 10;
info.templateID = modelLOT;
info.nodes = { node };
info.templateScale = 1.0f;
info.activeOnLoad = true;
info.amountMaintained = 1;
info.respawnTime = 10;
info.emulated = true;
info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID();
info.emulated = true;
info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID();
info.spawnerID = persistentId;
GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT);
info.spawnerID = ObjectIDManager::GetPersistentID();
GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT);
const auto spawnerId = Game::zoneManager->MakeSpawner(info);
const auto spawnerId = Game::zoneManager->MakeSpawner(info);
auto* spawner = Game::zoneManager->GetSpawner(spawnerId);
auto* spawner = Game::zoneManager->GetSpawner(spawnerId);
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"modelBehaviors", 0));
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"userModelID", info.spawnerID));
info.nodes[0]->config.push_back(new LDFData<int>(u"modelType", 2));
info.nodes[0]->config.push_back(new LDFData<bool>(u"propertyObjectID", true));
info.nodes[0]->config.push_back(new LDFData<int>(u"componentWhitelist", 1));
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"modelBehaviors", 0));
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"userModelID", info.spawnerID));
info.nodes[0]->config.push_back(new LDFData<int>(u"modelType", 2));
info.nodes[0]->config.push_back(new LDFData<bool>(u"propertyObjectID", true));
info.nodes[0]->config.push_back(new LDFData<int>(u"componentWhitelist", 1));
auto* model = spawner->Spawn();
auto* modelComponent = model->GetComponent<ModelComponent>();
if (modelComponent) modelComponent->Pause();
auto* model = spawner->Spawn();
auto* modelComponent = model->GetComponent<ModelComponent>();
if (modelComponent) modelComponent->Pause();
models.insert_or_assign(model->GetObjectID(), spawnerId);
models.insert_or_assign(model->GetObjectID(), spawnerId);
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), position, m_Parent->GetObjectID(), 14, originalRotation);
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), position, m_Parent->GetObjectID(), 14, originalRotation);
GameMessages::SendUGCEquipPreCreateBasedOnEditMode(entity->GetObjectID(), entity->GetSystemAddress(), 0, spawnerId);
GameMessages::SendUGCEquipPreCreateBasedOnEditMode(entity->GetObjectID(), entity->GetSystemAddress(), 0, spawnerId);
GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity);
Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity);
});
// Progress place model missions
auto missionComponent = entity->GetComponent<MissionComponent>();
if (missionComponent != nullptr) missionComponent->Progress(eMissionTaskType::PLACE_MODEL, 0);
@@ -621,7 +628,6 @@ void PropertyManagementComponent::Load() {
//BBB property models need to have extra stuff set for them:
if (databaseModel.lot == 14) {
LWOOBJID blueprintID = databaseModel.ugcId;
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
@@ -694,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
@@ -822,7 +828,7 @@ void PropertyManagementComponent::OnChatMessageReceived(const std::string& sMess
if (!model) continue;
auto* const modelComponent = model->GetComponent<ModelComponent>();
if (!modelComponent) continue;
modelComponent->OnChatMessageReceived(sMessage);
}
}

View File

@@ -2555,7 +2555,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
uint32_t sd0Size;
inStream.Read(sd0Size);
std::shared_ptr<char[]> sd0Data(new char[sd0Size]);
std::unique_ptr<char[]> sd0Data(new char[sd0Size]);
if (sd0Data == nullptr) return;
@@ -2579,116 +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;
uint32_t blueprintIDSmall = ObjectIDManager::GenerateRandomObjectID();
LWOOBJID blueprintID = blueprintIDSmall;
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
// Legacy logic to check for old random IDs (regenerating these is not really feasible)
// Probably good to have this anyway in case someone messes with the last_object_id or it gets reset somehow
LWOOBJID newIDL = LWOOBJID_EMPTY;
LWOOBJID blueprintID = LWOOBJID_EMPTY;
do {
if (newIDL != LWOOBJID_EMPTY) LOG("Generating blueprintID for UGC model, collision with existing model ID: %llu", blueprintID);
newIDL = ObjectIDManager::GetPersistentID();
blueprintID = ObjectIDManager::GetPersistentID();
++retries;
blueprintIDExists = Database::Get()->GetUgcModel(blueprintID).has_value();
modelExists = Database::Get()->GetModel(newIDL).has_value();
} while ((blueprintIDExists || modelExists) && retries < maxRetries);
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
const auto zoneId = worldId.GetMapID();
const auto cloneId = worldId.GetCloneID();
const auto zoneId = worldId.GetMapID();
const auto cloneId = worldId.GetCloneID();
auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
LWOOBJID propertyId = LWOOBJID_EMPTY;
if (propertyInfo) propertyId = propertyInfo->id;
auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
LWOOBJID propertyId = LWOOBJID_EMPTY;
if (propertyInfo) propertyId = propertyInfo->id;
// Save the binary data to the Sd0 buffer
std::string str(sd0Data.get(), sd0Size);
std::istringstream sd0DataStream(str);
Sd0 sd0(sd0DataStream);
// Save the binary data to the Sd0 buffer
std::string str(sd0Data.get(), sd0Size);
std::istringstream sd0DataStream(str);
Sd0 sd0(sd0DataStream);
// Uncompress the data and normalize the position
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
// Uncompress the data and normalize the position
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
// Recompress the data and save to the database
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto sd0AsStream = sd0.GetAsStream();
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintIDSmall, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
// Recompress the data and save to the database
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto sd0AsStream = sd0.GetAsStream();
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
//Insert into the db as a BBB model:
IPropertyContents::Model model;
model.id = newIDL;
model.ugcId = blueprintIDSmall;
model.position = newCenter;
model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
model.lot = 14;
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
//Insert into the db as a BBB model:
IPropertyContents::Model model;
model.id = newIDL;
model.ugcId = blueprintID;
model.position = newCenter;
model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
model.lot = 14;
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
/*
Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream.
(or you uncomment the lxfml decomp stuff above)
*/
/*
Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream.
(or you uncomment the lxfml decomp stuff above)
*/
////Send off to UGC for processing, if enabled:
//if (Game::config->GetValue("ugc_remote") == "1") {
// std::string ugcIP = Game::config->GetValue("ugc_ip");
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
// //Send off to UGC for processing, if enabled:
// if (Game::config->GetValue("ugc_remote") == "1") {
// std::string ugcIP = Game::config->GetValue("ugc_ip");
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
// httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^
// httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^
// //Send out a request:
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
// cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml");
// //Send out a request:
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
// cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml");
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model &
// //the nif, hkx and checksum files are ready to be downloaded from cache.
//}
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model &
// //the nif, hkx and checksum files are ready to be downloaded from cache.
// }
//Tell the client their model is saved: (this causes us to actually pop out of our current state):
const auto& newSd0 = sd0.GetAsVector();
uint32_t sd0Size{};
for (const auto& chunk : newSd0) sd0Size += chunk.size();
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write(localId);
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
bitStream.Write<uint32_t>(1);
bitStream.Write(blueprintID);
//Tell the client their model is saved: (this causes us to actually pop out of our current state):
const auto& newSd0 = sd0.GetAsVector();
uint32_t newSd0Size{};
for (const auto& chunk : newSd0) newSd0Size += chunk.size();
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write(localId);
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
bitStream.Write<uint32_t>(1);
bitStream.Write(blueprintID);
bitStream.Write(sd0Size);
bitStream.Write(newSd0Size);
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
SEND_PACKET;
SEND_PACKET;
//Now we have to construct this object:
//Now we have to construct this object:
EntityInfo info;
info.lot = 14;
info.pos = newCenter;
info.rot = {};
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
EntityInfo info;
info.lot = 14;
info.pos = newCenter;
info.rot = {};
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
info.settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
info.settings.push_back(new LDFData<int>(u"modelType", 2));
info.settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
info.settings.push_back(new LDFData<LWOOBJID>(u"userModelID", newIDL));
info.settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
info.settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
info.settings.push_back(new LDFData<int>(u"modelType", 2));
info.settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
info.settings.push_back(new LDFData<LWOOBJID>(u"userModelID", newIDL));
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
if (newEntity) {
Game::entityManager->ConstructEntity(newEntity);
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
if (newEntity) {
Game::entityManager->ConstructEntity(newEntity);
//Make sure the propMgmt doesn't delete our model after the server dies
//Trying to do this after the entity is constructed. Shouldn't really change anything but
//there was an issue with builds not appearing since it was placed above ConstructEntity.
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
}
});
//Make sure the propMgmt doesn't delete our model after the server dies
//Trying to do this after the entity is constructed. Shouldn't really change anything but
//there was an issue with builds not appearing since it was placed above ConstructEntity.
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
}
}
void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
@@ -5527,55 +5532,52 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
}
}
ObjectIDManager::RequestPersistentID([=](uint32_t newId) {
LOG("Build finished");
GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it
LOG("Build finished");
GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it
//inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap
//Game::entityManager->SerializeEntity(entity);
//inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap
//Game::entityManager->SerializeEntity(entity);
const auto moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", modules);
const auto moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", modules);
std::vector<LDFBaseData*> config;
config.push_back(moduleAssembly);
std::vector<LDFBaseData*> config;
config.push_back(moduleAssembly);
LWOOBJID newIdBig = newId;
GeneralUtils::SetBit(newIdBig, eObjectBits::CHARACTER);
LWOOBJID newID = ObjectIDManager::GetPersistentID();
if (count == 3) {
inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
} else if (count == 7) {
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
if (count == 3) {
inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID);
} else if (count == 7) {
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID);
}
auto* pCharacter = character->GetCharacter();
Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newID, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
auto* missionComponent = character->GetComponent<MissionComponent>();
if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) {
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::SCRIPT, entity->GetLOT(), entity->GetObjectID());
if (count >= 7 && everyPieceSwapped) missionComponent->Progress(eMissionTaskType::RACING, LWOOBJID_EMPTY, static_cast<LWOOBJID>(eRacingTaskParam::MODULAR_BUILDING));
}
}
auto* pCharacter = character->GetCharacter();
Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newIdBig, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt);
ScriptComponent* script = static_cast<ScriptComponent*>(entity->GetComponent(eReplicaComponentType::SCRIPT));
auto* missionComponent = character->GetComponent<MissionComponent>();
entity->GetScript()->OnModularBuildExit(entity, character, count >= 3, modList);
if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) {
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::SCRIPT, entity->GetLOT(), entity->GetObjectID());
if (count >= 7 && everyPieceSwapped) missionComponent->Progress(eMissionTaskType::RACING, LWOOBJID_EMPTY, static_cast<LWOOBJID>(eRacingTaskParam::MODULAR_BUILDING));
}
}
// Move remaining temp models back to models
std::vector<Item*> items;
ScriptComponent* script = static_cast<ScriptComponent*>(entity->GetComponent(eReplicaComponentType::SCRIPT));
for (const auto& pair : temp->GetItems()) {
items.push_back(pair.second);
}
entity->GetScript()->OnModularBuildExit(entity, character, count >= 3, modList);
// Move remaining temp models back to models
std::vector<Item*> items;
for (const auto& pair : temp->GetItems()) {
items.push_back(pair.second);
}
for (auto* item : items) {
inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false);
}
});
for (auto* item : items) {
inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false);
}
}
}

View File

@@ -5,8 +5,11 @@
#include "InventoryComponent.h"
#include "eItemType.h"
#include "eReplicaComponentType.h"
#include "ObjectIDManager.h"
#include "eObjectBits.h"
#include "CDComponentsRegistryTable.h"
#include <ranges>
std::vector<LOT> Inventory::m_GameMasterRestrictedItems = {
1727, // GM Only - JetPack
@@ -317,3 +320,16 @@ Inventory::~Inventory() {
items.clear();
}
void Inventory::RegenerateItemIDs() {
std::map<LWOOBJID, Item*> newItems{};
for (auto* const item : items | std::views::values) {
const auto oldID = item->GetId();
const auto newID = item->GenerateID();
LOG("Updating item ID from %llu to %llu", oldID, newID);
newItems.insert_or_assign(newID, item);
}
// We don't want to delete the item pointers, we're just moving from map to map
items = newItems;
}

View File

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

View File

@@ -98,21 +98,12 @@ Item::Item(
this->preconditions = new PreconditionExpression(this->info->reqPrecondition);
this->subKey = subKey;
LWOOBJID id = ObjectIDManager::GenerateRandomObjectID();
GeneralUtils::SetBit(id, eObjectBits::CHARACTER);
const auto type = static_cast<eItemType>(info->itemType);
if (type == eItemType::MOUNT) {
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
}
this->id = id;
auto* const inventoryComponent = inventory->GetComponent();
GenerateID();
inventory->AddManagedItem(this);
auto* entity = inventory->GetComponent()->GetParent();
auto* entity = inventoryComponent->GetParent();
GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, static_cast<int>(this->count), subKey, lootSourceType);
if (isModMoveAndEquip) {
@@ -120,7 +111,7 @@ Item::Item(
LOG("Move and equipped (%i) from (%i)", this->lot, this->inventory->GetType());
Game::entityManager->SerializeEntity(inventory->GetComponent()->GetParent());
Game::entityManager->SerializeEntity(inventoryComponent->GetParent());
}
}
@@ -573,3 +564,28 @@ void Item::LoadConfigXml(const tinyxml2::XMLElement& i) {
config.push_back(LDFBaseData::DataFromString(value));
}
}
LWOOBJID Item::GenerateID() {
auto* const inventoryComponent = inventory->GetComponent();
const bool isPlayer = inventoryComponent->GetParent()->IsPlayer();
LWOOBJID id{};
// Only players and non-proxy items get persistent IDs (since they are the only ones that will persist between worlds)
if (isPlayer && parent == LWOOBJID_EMPTY) {
id = ObjectIDManager::GetPersistentID();
} else {
id = ObjectIDManager::GenerateObjectID();
GeneralUtils::SetBit(id, eObjectBits::SPAWNED);
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
}
LOG("Parent %llu 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);
LWOOBJID GenerateID();
private:
/**
* The object ID of this item

View File

@@ -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) {

View File

@@ -5,47 +5,37 @@
#include "Database.h"
#include "Logger.h"
#include "Game.h"
#include "eObjectBits.h"
//! The persistent ID request
struct PersistentIDRequest {
PersistentIDRequest(const uint64_t& requestID, const std::function<void(uint32_t)>& callback) : requestID(requestID), callback(callback) {}
uint64_t requestID;
std::function<void(uint32_t)> callback;
};
// should the spawners from vanity also have the CLIENT flag?
namespace {
std::vector<PersistentIDRequest> Requests; //!< All outstanding persistent ID requests
uint64_t CurrentRequestID = 0; //!< The current request ID
uint32_t CurrentObjectID = uint32_t(1152921508165007067); //!< The current object ID
std::uniform_int_distribution<int> Uni(10000000, INT32_MAX);
// Start the range in a way that it when first called it will fetch some new persistent IDs
std::optional<IObjectIdTracker::Range> CurrentRange = std::nullopt;
uint32_t CurrentObjectID = uint32_t(1152921508165007067); // The current object ID (this should really start at the highest current ID in the world, then increment from there)
};
//! Requests a persistent ID
void ObjectIDManager::RequestPersistentID(const std::function<void(uint32_t)> callback) {
const auto& request = Requests.emplace_back(++CurrentRequestID, callback);
uint64_t ObjectIDManager::GetPersistentID() {
if (!CurrentRange.has_value() || CurrentRange->minID > CurrentRange->maxID) {
CurrentRange = Database::Get()->GetPersistentIdRange();
// We're getting close to being out of IDs in this range, log a warning
const auto WARNING_RANGE = 70368744100000ULL;
if (CurrentRange->minID >= 70368744100000ULL) {
LOG("WARNING: Your server is running low on persistent IDs, please consider an ID squash in the near future.");
}
MasterPackets::SendPersistentIDRequest(Game::server, request.requestID);
LOG("Reserved object ID range: %llu - %llu", CurrentRange->minID, CurrentRange->maxID);
}
const auto usedID = CurrentRange->minID++;
auto toReturn = usedID;
// Any IDs gotten from persistent IDs use the CHARACTER bit
GeneralUtils::SetBit(toReturn, eObjectBits::CHARACTER);
LOG("Using ID: %llu:%llu", toReturn, usedID);
return toReturn;
}
//! Handles a persistent ID response
void ObjectIDManager::HandleRequestPersistentIDResponse(const uint64_t requestID, const uint32_t persistentID) {
auto it = std::find_if(Requests.begin(), Requests.end(), [requestID](const PersistentIDRequest& request) {
return request.requestID == requestID;
});
if (it == Requests.end()) return;
it->callback(persistentID);
Requests.erase(it);
}
//! Handles cases where we have to get a unique object ID synchronously
uint32_t ObjectIDManager::GenerateRandomObjectID() {
return Uni(Game::randomEngine);
}
//! Generates an object ID server-sided (used for regular entities like smashables)
// Generates an object ID server-sided (used for regular entities like smashables)
uint32_t ObjectIDManager::GenerateObjectID() {
return ++CurrentObjectID;
}

View File

@@ -5,36 +5,25 @@
#include <vector>
#include <stdint.h>
/*!
\file ObjectIDManager.h
\brief A manager for handling object ID generation
/**
* There are 2 types of IDs:
* Persistent IDs - These are used for anything that needs to be persist between worlds.
* Ephemeral IDs - These are used for any objects that only need to be unique for this world session.
*/
//! The Object ID Manager
namespace ObjectIDManager {
//! Requests a persistent ID
/*!
\param callback The callback function
/**
* @brief Returns a Persistent ID with the CHARACTER bit set.
*
* @return uint64_t A unique persistent ID with the CHARACTER bit set.
*/
void RequestPersistentID(const std::function<void(uint32_t)> callback);
uint64_t GetPersistentID();
//! Handles a persistent ID response
/*!
\param requestID The request ID
\param persistentID The persistent ID
*/
void HandleRequestPersistentIDResponse(const uint64_t requestID, const uint32_t persistentID);
//! Generates an object ID server-sided
/*!
\return A generated object ID
/**
* @brief Generates an ephemeral object ID for non-persistent objects.
*
* @return uint32_t
*/
uint32_t GenerateObjectID();
//! Generates a random object ID server-sided
/*!
\return A generated object ID
*/
uint32_t GenerateRandomObjectID();
};

View File

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

View File

@@ -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<ServiceType>(packet->data[1]) == ServiceType::MASTER) {
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: {
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;

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

View File

@@ -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

View File

@@ -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"

View File

@@ -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<ServiceType>(packet->data[1]) != ServiceType::MASTER || packet->length < 4) return;
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: {
//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.

View File

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

View File

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