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;