From 64faac714c1a84321cd6630873ce8488b0e127ad Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:41:38 -0700 Subject: [PATCH] feat: Remove PERSISTENT ObjectID bit because it's not an ObjectID bit (#1881) * feat: Remove PERSISTENT ObjectID bit because it's not an ObjectID bit TODO: Need to add character save migration for the pet subkey in the inventory Tested that the migrations work on mysql and sqlite and that properties have all their contents as before. Need to test pets still * fix: ugc, pet ids. remove persistent bit --- dCommon/dEnums/eCharacterVersion.h | 3 +- dCommon/dEnums/eObjectBits.h | 7 +-- .../GameDatabase/ITables/IPropertyContents.h | 2 +- dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 38 ++++++------ dGame/Character.cpp | 5 +- dGame/UserManager.cpp | 2 - dGame/dComponents/InventoryComponent.h | 3 +- dGame/dComponents/PetComponent.cpp | 1 - .../PropertyManagementComponent.cpp | 1 - dGame/dGameMessages/GameMessages.cpp | 2 - dGame/dInventory/Item.cpp | 1 - dWorldServer/WorldServer.cpp | 60 +++++++++++++------ .../dlu/mysql/24_remove_persistent_bit.sql | 37 ++++++++++++ migrations/dlu/mysql/25_fix_pet_ids.sql | 1 + .../dlu/sqlite/7_remove_persistent_bit.sql | 37 ++++++++++++ migrations/dlu/sqlite/8_fix_pet_ids.sql | 2 + 16 files changed, 151 insertions(+), 51 deletions(-) create mode 100644 migrations/dlu/mysql/24_remove_persistent_bit.sql create mode 100644 migrations/dlu/mysql/25_fix_pet_ids.sql create mode 100644 migrations/dlu/sqlite/7_remove_persistent_bit.sql create mode 100644 migrations/dlu/sqlite/8_fix_pet_ids.sql diff --git a/dCommon/dEnums/eCharacterVersion.h b/dCommon/dEnums/eCharacterVersion.h index 51e6e5e5..477467b8 100644 --- a/dCommon/dEnums/eCharacterVersion.h +++ b/dCommon/dEnums/eCharacterVersion.h @@ -18,7 +18,8 @@ enum class eCharacterVersion : uint32_t { SPEED_BASE, // Fixes nexus force explorer missions NJ_JAYMISSIONS, - UP_TO_DATE, // will become NEXUS_FORCE_EXPLORER + NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories + UP_TO_DATE, // will become PET_IDS }; #endif //!__ECHARACTERVERSION__H__ diff --git a/dCommon/dEnums/eObjectBits.h b/dCommon/dEnums/eObjectBits.h index b978aad6..121c58b9 100644 --- a/dCommon/dEnums/eObjectBits.h +++ b/dCommon/dEnums/eObjectBits.h @@ -1,13 +1,12 @@ -#ifndef __EOBJECTBITS__H__ -#define __EOBJECTBITS__H__ +#ifndef EOBJECTBITS_H +#define EOBJECTBITS_H #include enum class eObjectBits : size_t { - PERSISTENT = 32, CLIENT = 46, SPAWNED = 58, CHARACTER = 60 }; -#endif //!__EOBJECTBITS__H__ +#endif //!EOBJECTBITS_H diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index 52f1a672..94636801 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -16,7 +16,7 @@ public: NiQuaternion rotation = QuatUtils::IDENTITY; LWOOBJID id{}; LOT lot{}; - uint32_t ugcId{}; + LWOOBJID ugcId{}; std::array behaviors{}; }; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 7a3a53b5..3546d668 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -168,91 +168,91 @@ private: template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string_view param) { - // LOG("%s", param.data()); + LOG_DEBUG("%s", param.data()); stmt->setString(index, param.data()); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const char* param) { - // LOG("%s", param); + LOG_DEBUG("%s", param); stmt->setString(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string param) { - // LOG("%s", param.c_str()); + LOG_DEBUG("%s", param.c_str()); stmt->setString(index, param.c_str()); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int8_t param) { - // LOG("%u", param); + LOG_DEBUG("%u", param); stmt->setByte(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint8_t param) { - // LOG("%d", param); + LOG_DEBUG("%d", param); stmt->setByte(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int16_t param) { - // LOG("%u", param); + LOG_DEBUG("%u", param); stmt->setShort(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint16_t param) { - // LOG("%d", param); + LOG_DEBUG("%d", param); stmt->setShort(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint32_t param) { - // LOG("%u", param); + LOG_DEBUG("%u", param); stmt->setUInt(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int32_t param) { - // LOG("%d", param); + LOG_DEBUG("%d", param); stmt->setInt(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int64_t param) { - // LOG("%llu", param); + LOG_DEBUG("%llu", param); stmt->setInt64(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint64_t param) { - // LOG("%llu", param); + LOG_DEBUG("%llu", param); stmt->setUInt64(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const float param) { - // LOG("%f", param); + LOG_DEBUG("%f", param); stmt->setFloat(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const double param) { - // LOG("%f", param); + LOG_DEBUG("%f", param); stmt->setDouble(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const bool param) { - // LOG("%d", param); + LOG_DEBUG("%s", param ? "true" : "false"); stmt->setBoolean(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istream* param) { - // LOG("Blob"); + LOG_DEBUG("Blob"); // This is the one time you will ever see me use const_cast. stmt->setBlob(index, const_cast(param)); } @@ -260,10 +260,10 @@ inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istr template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional param) { if (param) { - // LOG("%d", param.value()); + LOG_DEBUG("%d", param.value()); stmt->setInt(index, param.value()); } else { - // LOG("Null"); + LOG_DEBUG("Null"); stmt->setNull(index, sql::DataType::SQLNULL); } } @@ -271,10 +271,10 @@ inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::opti template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional param) { if (param) { - // LOG("%d", param.value()); + LOG_DEBUG("%d", param.value()); stmt->setInt64(index, param.value()); } else { - // LOG("Null"); + LOG_DEBUG("Null"); stmt->setNull(index, sql::DataType::SQLNULL); } } diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 250466a9..102db91a 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -336,8 +336,11 @@ void Character::WriteToDatabase() { tinyxml2::XMLPrinter printer(0, true, 0); m_Doc.Print(&printer); + // Update the xml on the character for future use if needed + m_XMLData = printer.CStr(); + //Finally, save to db: - Database::Get()->UpdateCharacterXml(m_ID, printer.CStr()); + Database::Get()->UpdateCharacterXml(m_ID, m_XMLData); } void Character::SetPlayerFlag(const uint32_t flagId, const bool value) { diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 9bd6d844..82afb063 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -360,9 +360,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) } while (lwoidforpants == lwoidforshirt); //Make sure we don't have the same ID for both shirt and pants GeneralUtils::SetBit(lwoidforshirt, eObjectBits::CHARACTER); - GeneralUtils::SetBit(lwoidforshirt, eObjectBits::PERSISTENT); GeneralUtils::SetBit(lwoidforpants, eObjectBits::CHARACTER); - GeneralUtils::SetBit(lwoidforpants, eObjectBits::PERSISTENT); xml << ""; xml << ""; diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 395dd5d6..d67cf3c1 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -402,7 +402,8 @@ public: bool SetSkill(BehaviorSlot slot, uint32_t skillId); void UpdateGroup(const GroupUpdate& groupUpdate); - void RemoveGroup(const std::string& groupId); + + std::unordered_map& GetPetsMut() { return m_Pets; }; void FixInvisibleItems(); diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index de805e10..5081db6c 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -482,7 +482,6 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { LWOOBJID petSubKey = ObjectIDManager::GenerateRandomObjectID(); GeneralUtils::SetBit(petSubKey, eObjectBits::CHARACTER); - GeneralUtils::SetBit(petSubKey, eObjectBits::PERSISTENT); m_DatabaseId = petSubKey; diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index d8d00787..4d3c1a3e 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -622,7 +622,6 @@ void PropertyManagementComponent::Load() { if (databaseModel.lot == 14) { LWOOBJID blueprintID = databaseModel.ugcId; GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); settings.push_back(new LDFData(u"blueprintid", blueprintID)); settings.push_back(new LDFData(u"componentWhitelist", 1)); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 08b1817e..c2ae363a 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2587,12 +2587,10 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return; LWOOBJID newIDL = newID; GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER); - GeneralUtils::SetBit(newIDL, eObjectBits::PERSISTENT); uint32_t blueprintIDSmall = ObjectIDManager::GenerateRandomObjectID(); LWOOBJID blueprintID = blueprintIDSmall; GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); //We need to get the propertyID: (stolen from Wincent's propertyManagementComp) const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index ca93cd9a..1a12c25f 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -101,7 +101,6 @@ Item::Item( LWOOBJID id = ObjectIDManager::GenerateRandomObjectID(); GeneralUtils::SetBit(id, eObjectBits::CHARACTER); - GeneralUtils::SetBit(id, eObjectBits::PERSISTENT); const auto type = static_cast(info->itemType); diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index f655bac7..3a22bbf0 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -82,6 +82,7 @@ #include "MissionComponent.h" #include "SlashCommandHandler.h" #include "InventoryComponent.h" +#include "Item.h" namespace Game { Logger* logger = nullptr; @@ -1038,21 +1039,6 @@ void HandlePacket(Packet* packet) { auto* characterComponent = player->GetComponent(); if (!characterComponent) return; - WorldPackets::SendCreateCharacter(packet->systemAddress, player->GetComponent()->GetReputation(), player->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel(), c->GetPropertyCloneID()); - WorldPackets::SendServerState(packet->systemAddress); - - const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID()); - - Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS); - - if (respawnPoint != NiPoint3Constant::ZERO) { - GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, QuatUtils::IDENTITY); - } - - Game::entityManager->ConstructAllEntities(packet->systemAddress); - - characterComponent->RocketUnEquip(player); - // Do charxml fixes here auto* levelComponent = player->GetComponent(); auto* const inventoryComponent = player->GetComponent(); @@ -1060,6 +1046,7 @@ void HandlePacket(Packet* packet) { if (!levelComponent || !missionComponent || !inventoryComponent) return; auto version = levelComponent->GetCharacterVersion(); + LOG("Updating character from version %s", StringifiedEnum::ToString(version).data()); switch (version) { case eCharacterVersion::RELEASE: // TODO: Implement, super low priority @@ -1108,6 +1095,29 @@ void HandlePacket(Packet* packet) { } 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::UP_TO_DATE); [[fallthrough]]; } @@ -1115,6 +1125,24 @@ void HandlePacket(Packet* packet) { break; } + // Update the characters xml to ensure the update above is not only saved, but so the client picks up on the changes. + c->SaveXMLToDatabase(); + + WorldPackets::SendCreateCharacter(packet->systemAddress, characterComponent->GetReputation(), player->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel(), c->GetPropertyCloneID()); + WorldPackets::SendServerState(packet->systemAddress); + + const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID()); + + Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS); + + if (respawnPoint != NiPoint3Constant::ZERO) { + GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, QuatUtils::IDENTITY); + } + + Game::entityManager->ConstructAllEntities(packet->systemAddress); + + characterComponent->RocketUnEquip(player); + player->GetCharacter()->SetTargetScene(""); // Fix the destroyable component @@ -1149,8 +1177,6 @@ void HandlePacket(Packet* packet) { //Send message: LWOOBJID blueprintID = bbbModel.id; - GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); // Workaround for not having a UGC server to get model LXFML onto the client so it // can generate the physics and nif for the object. diff --git a/migrations/dlu/mysql/24_remove_persistent_bit.sql b/migrations/dlu/mysql/24_remove_persistent_bit.sql new file mode 100644 index 00000000..2ed84947 --- /dev/null +++ b/migrations/dlu/mysql/24_remove_persistent_bit.sql @@ -0,0 +1,37 @@ +START TRANSACTION; +CREATE TABLE `ugc_2` ( + `id` BIGINT NOT NULL PRIMARY KEY, + `account_id` INT(11) NOT NULL REFERENCES `accounts` (`id`), + `character_id` BIGINT(20) NOT NULL REFERENCES `charinfo` (`id`), + `is_optimized` TINYINT(1) NOT NULL DEFAULT 0, + `lxfml` MEDIUMBLOB NOT NULL, + `bake_ao` TINYINT(1) NOT NULL DEFAULT 0, + `filename` TEXT NOT NULL DEFAULT '' +); +CREATE TABLE `properties_contents_2` ( + `id` BIGINT PRIMARY KEY NOT NULL, + `property_id` BIGINT NOT NULL REFERENCES `properties`(`id`), + `ugc_id` BIGINT DEFAULT NULL REFERENCES `ugc_2`(`id`), + `lot` INT NOT NULL, + `x` FLOAT NOT NULL, + `y` FLOAT NOT NULL, + `z` FLOAT NOT NULL, + `rx` FLOAT NOT NULL, + `ry` FLOAT NOT NULL, + `rz` FLOAT NOT NULL, + `rw` FLOAT NOT NULL, + `model_name` TEXT NOT NULL DEFAULT '', + `model_description` TEXT NOT NULL DEFAULT '', + `behavior_1` BIGINT DEFAULT 0, + `behavior_2` BIGINT DEFAULT 0, + `behavior_3` BIGINT DEFAULT 0, + `behavior_4` BIGINT DEFAULT 0, + `behavior_5` BIGINT DEFAULT 0 +); +INSERT INTO `ugc_2` SELECT `id`|0x1000000000000000,`account_id`,`character_id`,`is_optimized`,`lxfml`,`bake_ao`,`filename` FROM `ugc`; +INSERT INTO `properties_contents_2` SELECT `id`,`property_id`,`ugc_id`|0x1000000000000000,`lot`,`x`,`y`,`z`,`rx`,`ry`,`rz`,`rw`,`model_name`,`model_description`,`behavior_1`,`behavior_2`,`behavior_3`,`behavior_4`,`behavior_5` FROM `properties_contents`; +DROP TABLE `properties_contents`; +DROP TABLE `ugc`; +RENAME TABLE `properties_contents_2` TO `properties_contents`; +RENAME TABLE `ugc_2` TO `ugc`; +COMMIT; diff --git a/migrations/dlu/mysql/25_fix_pet_ids.sql b/migrations/dlu/mysql/25_fix_pet_ids.sql new file mode 100644 index 00000000..1ad79043 --- /dev/null +++ b/migrations/dlu/mysql/25_fix_pet_ids.sql @@ -0,0 +1 @@ +update pet_names set id = id % 0x100000000 | 0x1000000000000000; diff --git a/migrations/dlu/sqlite/7_remove_persistent_bit.sql b/migrations/dlu/sqlite/7_remove_persistent_bit.sql new file mode 100644 index 00000000..69feef3b --- /dev/null +++ b/migrations/dlu/sqlite/7_remove_persistent_bit.sql @@ -0,0 +1,37 @@ +BEGIN TRANSACTION; +CREATE TABLE `ugc_2` ( + `id` BIGINT NOT NULL PRIMARY KEY, + `account_id` INT NOT NULL REFERENCES `accounts` (`id`), + `character_id` BIGINT NOT NULL REFERENCES `charinfo` (`id`), + `is_optimized` INT NOT NULL DEFAULT 0, + `lxfml` BLOB NOT NULL, + `bake_ao` INT NOT NULL DEFAULT 0, + `filename` TEXT NOT NULL DEFAULT '' +); +CREATE TABLE `properties_contents_2` ( + `id` BIGINT PRIMARY KEY NOT NULL, + `property_id` BIGINT NOT NULL REFERENCES `properties`(`id`), + `ugc_id` BIGINT DEFAULT NULL REFERENCES `ugc_2`(`id`), + `lot` INT NOT NULL, + `x` DOUBLE NOT NULL, + `y` DOUBLE NOT NULL, + `z` DOUBLE NOT NULL, + `rx` DOUBLE NOT NULL, + `ry` DOUBLE NOT NULL, + `rz` DOUBLE NOT NULL, + `rw` DOUBLE NOT NULL, + `model_name` TEXT NOT NULL DEFAULT '', + `model_description` TEXT NOT NULL DEFAULT '', + `behavior_1` BIGINT DEFAULT 0, + `behavior_2` BIGINT DEFAULT 0, + `behavior_3` BIGINT DEFAULT 0, + `behavior_4` BIGINT DEFAULT 0, + `behavior_5` BIGINT DEFAULT 0 +); +INSERT INTO `ugc_2` SELECT `id`|0x1000000000000000,`account_id`,`character_id`,`is_optimized`,`lxfml`,`bake_ao`,`filename` FROM `ugc`; +INSERT INTO `properties_contents_2` SELECT `id`,`property_id`,`ugc_id`|0x1000000000000000,`lot`,`x`,`y`,`z`,`rx`,`ry`,`rz`,`rw`,`model_name`,`model_description`,`behavior_1`,`behavior_2`,`behavior_3`,`behavior_4`,`behavior_5` FROM `properties_contents`; +DROP TABLE `properties_contents`; +DROP TABLE `ugc`; +ALTER TABLE `properties_contents_2` RENAME TO `properties_contents`; +ALTER TABLE `ugc_2` RENAME TO `ugc`; +COMMIT; diff --git a/migrations/dlu/sqlite/8_fix_pet_ids.sql b/migrations/dlu/sqlite/8_fix_pet_ids.sql new file mode 100644 index 00000000..4b2b9283 --- /dev/null +++ b/migrations/dlu/sqlite/8_fix_pet_ids.sql @@ -0,0 +1,2 @@ +/* Unset the fake persistent bit alongside the Character bit and then re-set the Character bit */ +update pet_names set id = id % 0x100000000 | 0x1000000000000000;