diff --git a/data/sql/updates/pending_db_characters/rev_decouple_item_guids.sql b/data/sql/updates/pending_db_characters/rev_decouple_item_guids.sql new file mode 100644 index 0000000000..4ddf946f42 --- /dev/null +++ b/data/sql/updates/pending_db_characters/rev_decouple_item_guids.sql @@ -0,0 +1,40 @@ +-- Decouple item GUIDs: widen to BIGINT UNSIGNED for persistent DB IDs +-- Item instance primary key to BIGINT +ALTER TABLE `item_instance` + MODIFY COLUMN `guid` BIGINT UNSIGNED NOT NULL, + DROP PRIMARY KEY, + ADD PRIMARY KEY (`guid`); + +-- Character inventory references +ALTER TABLE `character_inventory` + MODIFY COLUMN `item` BIGINT UNSIGNED NOT NULL; + +-- Mail items references +ALTER TABLE `mail_items` + MODIFY COLUMN `item_guid` BIGINT UNSIGNED NOT NULL; + +-- Auctionhouse references +ALTER TABLE `auctionhouse` + MODIFY COLUMN `itemguid` BIGINT UNSIGNED NOT NULL, + DROP INDEX `item_guid`, + ADD UNIQUE KEY `item_guid` (`itemguid`); + +-- Guild bank references +ALTER TABLE `guild_bank_item` + MODIFY COLUMN `item_guid` BIGINT UNSIGNED NOT NULL, + DROP INDEX `Idx_item_guid`, + ADD KEY `Idx_item_guid` (`item_guid`); + +-- Refund/trade/gift tables referencing items by guid +ALTER TABLE `item_refund_instance` + MODIFY COLUMN `item_guid` BIGINT UNSIGNED NOT NULL; + +ALTER TABLE `item_soulbound_trade_data` + MODIFY COLUMN `itemGuid` BIGINT UNSIGNED NOT NULL, + DROP PRIMARY KEY, + ADD PRIMARY KEY (`itemGuid`); + +ALTER TABLE `character_gifts` + MODIFY COLUMN `item_guid` BIGINT UNSIGNED NOT NULL, + DROP PRIMARY KEY, + ADD PRIMARY KEY (`item_guid`); diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp index dcc1b76621..c6534f81ee 100644 --- a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp +++ b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp @@ -29,6 +29,7 @@ #include "UpdateTime.h" #include "World.h" #include "WorldPacket.h" +#include "Entities/Item/ItemGuidMap.h" #include constexpr auto AH_MINIMUM_DEPOSIT = 100; @@ -580,7 +581,11 @@ void AuctionEntry::SaveToDB(CharacterDatabaseTransaction trans) const CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_AUCTION); stmt->SetData(0, Id); stmt->SetData(1, houseId); - stmt->SetData(2, item_guid.GetCounter()); + { + Item* it = sAuctionMgr->GetAItem(item_guid); + uint64 dbGuid = it ? it->GetDbGuid() : static_cast(item_guid.GetCounter()); + stmt->SetData(2, dbGuid); + } stmt->SetData(3, owner.GetCounter()); stmt->SetData (4, buyout); stmt->SetData(5, uint32(expire_time)); @@ -595,7 +600,11 @@ bool AuctionEntry::LoadFromDB(Field* fields) { Id = fields[0].Get(); houseId = AuctionHouseId(fields[1].Get()); - item_guid = ObjectGuid::Create(fields[2].Get()); + { + uint64 dbGuid = fields[2].Get(); + uint32 low = sItemGuidMap->Acquire(dbGuid); + item_guid = ObjectGuid::Create(low); + } item_template = fields[3].Get(); itemCount = fields[4].Get(); owner = ObjectGuid::Create(fields[5].Get()); diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 36c45e5449..961002c520 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -28,6 +28,7 @@ #include "StringConvert.h" #include "Tokenize.h" #include "WorldPacket.h" +#include "ItemGuidMap.h" void AddItemsSetItem(Player* player, Item* item) { @@ -374,7 +375,12 @@ void Item::SaveToDB(CharacterDatabaseTransaction trans) stmt->SetData(++index, GetUInt32Value(ITEM_FIELD_DURABILITY)); stmt->SetData(++index, GetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME)); stmt->SetData(++index, m_text); - stmt->SetData(++index, guid); + if (m_dbGuid == 0) + { + m_dbGuid = sItemGuidMap->AcquireDbGuid(); + sItemGuidMap->Bind(m_dbGuid, guid); + } + stmt->SetData(++index, static_cast(m_dbGuid)); trans->Append(stmt); @@ -382,7 +388,7 @@ void Item::SaveToDB(CharacterDatabaseTransaction trans) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_GIFT_OWNER); stmt->SetData(0, GetOwnerGUID().GetCounter()); - stmt->SetData(1, guid); + stmt->SetData(1, static_cast(m_dbGuid)); trans->Append(stmt); } break; @@ -390,19 +396,21 @@ void Item::SaveToDB(CharacterDatabaseTransaction trans) case ITEM_REMOVED: { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); - stmt->SetData(0, guid); + stmt->SetData(0, static_cast(m_dbGuid)); trans->Append(stmt); if (IsWrapped()) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GIFT); - stmt->SetData(0, guid); + stmt->SetData(0, static_cast(m_dbGuid)); trans->Append(stmt); } if (!isInTransaction) CharacterDatabase.CommitTransaction(trans); + if (m_dbGuid) + sItemGuidMap->Release(m_dbGuid); delete this; return; } @@ -514,12 +522,105 @@ bool Item::LoadFromDB(ObjectGuid::LowType guid, ObjectGuid owner_guid, Field* fi return true; } +bool Item::LoadFromDB64(uint64 dbGuid, ObjectGuid owner_guid, Field* fields, uint32 entry) +{ + uint32 low = sItemGuidMap->Acquire(dbGuid); + Object::_Create(low, 0, HighGuid::Item); + m_dbGuid = dbGuid; + + SetEntry(entry); + SetObjectScale(1.0f); + + ItemTemplate const* proto = GetTemplate(); + if (!proto) + { + LOG_ERROR("entities.item", "Invalid entry {} for item {}. Refusing to load.", GetEntry(), GetGUID().ToString()); + return false; + } + + if (owner_guid) + SetOwnerGUID(owner_guid); + + bool need_save = false; + SetGuidValue(ITEM_FIELD_CREATOR, ObjectGuid::Create(fields[0].Get())); + SetGuidValue(ITEM_FIELD_GIFTCREATOR, ObjectGuid::Create(fields[1].Get())); + SetCount( fields[2].Get()); + + // Duration + uint32 duration = fields[3].Get(); + SetUInt32Value(ITEM_FIELD_DURATION, duration); + if ((proto->Duration == 0) != (duration == 0)) + { + SetUInt32Value(ITEM_FIELD_DURATION, proto->Duration); + need_save = true; + } + + // Charges + { + std::vector tokens = Acore::Tokenize(fields[4].Get(), ' ', false); + if (tokens.size() == MAX_ITEM_PROTO_SPELLS) + { + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + if (Optional charges = Acore::StringTo(tokens[i])) + SetSpellCharges(i, *charges); + else + LOG_ERROR("entities.item", "Invalid charge info '{}' for item {}, charge data not loaded.", tokens.at(i), GetGUID().ToString()); + } + } + } + + // Flags + SetUInt32Value(ITEM_FIELD_FLAGS, fields[5].Get()); + if (IsSoulBound() && proto->Bonding == NO_BIND && sScriptMgr->CanApplySoulboundFlag(this, proto)) + { + ApplyModFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_SOULBOUND, false); + need_save = true; + } + + // Enchantments + if (!_LoadIntoDataField(fields[6].Get(), ITEM_FIELD_ENCHANTMENT_1_1, MAX_ENCHANTMENT_SLOT * MAX_ENCHANTMENT_OFFSET)) + { + LOG_WARN("entities.item", "Invalid enchantment data '{}' for item {}. Forcing partial load.", fields[6].Get(), GetGUID().ToString()); + } + + // Random properties + SetInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID, fields[7].Get()); + if (GetItemRandomPropertyId() < 0) + UpdateItemSuffixFactor(); + + // Durability + uint32 durability = fields[8].Get(); + SetUInt32Value(ITEM_FIELD_DURABILITY, durability); + SetUInt32Value(ITEM_FIELD_MAXDURABILITY, proto->MaxDurability); + if (durability > proto->MaxDurability && !IsWrapped()) + { + SetUInt32Value(ITEM_FIELD_DURABILITY, proto->MaxDurability); + need_save = true; + } + + SetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME, fields[9].Get()); + SetText(fields[10].Get()); + + if (need_save) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_INSTANCE_ON_LOAD); + stmt->SetData(0, GetUInt32Value(ITEM_FIELD_DURATION)); + stmt->SetData(1, GetUInt32Value(ITEM_FIELD_FLAGS)); + stmt->SetData(2, GetUInt32Value(ITEM_FIELD_DURABILITY)); + stmt->SetData(3, static_cast(m_dbGuid)); + CharacterDatabase.Execute(stmt); + } + + return true; +} + /*static*/ void Item::DeleteFromDB(CharacterDatabaseTransaction trans, ObjectGuid::LowType itemGuid) { sScriptMgr->OnGlobalItemDelFromDB(trans, itemGuid); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); - stmt->SetData(0, itemGuid); + stmt->SetData(0, static_cast(itemGuid)); trans->Append(stmt); } @@ -532,7 +633,7 @@ void Item::DeleteFromDB(CharacterDatabaseTransaction trans) void Item::DeleteFromInventoryDB(CharacterDatabaseTransaction trans, ObjectGuid::LowType itemGuid) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); - stmt->SetData(0, itemGuid); + stmt->SetData(0, static_cast(itemGuid)); trans->Append(stmt); } @@ -1098,8 +1199,9 @@ Item* Item::CreateItem(uint32 item, uint32 count, Player const* player, bool clo ASSERT_NODEBUGINFO(count != 0 && "pProto->Stackable == 0 but checked at loading already"); Item* pItem = NewItemOrBag(pProto); - if (pItem->Create(sObjectMgr->GetGenerator().Generate(), item, player)) - { + uint32 newLow = sItemGuidMap->AcquireForNew(); + if (pItem->Create(newLow, item, player)) + { pItem->SetCount(count); if (!clone) pItem->SetItemRandomProperties(randomPropertyId ? randomPropertyId : Item::GenerateItemRandomPropertyId(item)); @@ -1174,11 +1276,11 @@ void Item::SaveRefundDataToDB() CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_REFUND_INSTANCE); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, static_cast(GetDbGuid())); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_REFUND_INSTANCE); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, static_cast(GetDbGuid())); stmt->SetData(1, GetRefundRecipient()); stmt->SetData(2, GetPaidMoney()); stmt->SetData(3, uint16(GetPaidExtendedCost())); @@ -1192,7 +1294,7 @@ void Item::DeleteRefundDataFromDB(CharacterDatabaseTransaction* trans) if (trans) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_REFUND_INSTANCE); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, static_cast(GetDbGuid())); (*trans)->Append(stmt); } } @@ -1270,7 +1372,7 @@ void Item::ClearSoulboundTradeable(Player* currentOwner) allowedGUIDs.clear(); SetState(ITEM_CHANGED, currentOwner); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_BOP_TRADE); - stmt->SetData(0, GetGUID().GetCounter()); + stmt->SetData(0, static_cast(GetDbGuid())); CharacterDatabase.Execute(stmt); } diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index a4ce99fdd0..a4ffdefe08 100644 --- a/src/server/game/Entities/Item/Item.h +++ b/src/server/game/Entities/Item/Item.h @@ -239,11 +239,18 @@ public: [[nodiscard]] bool IsBoundByEnchant() const; [[nodiscard]] bool IsBoundByTempEnchant() const; virtual void SaveToDB(CharacterDatabaseTransaction trans); + // Legacy: Load by low-guid (pre-decoupling) virtual bool LoadFromDB(ObjectGuid::LowType guid, ObjectGuid owner_guid, Field* fields, uint32 entry); + // Decoupled: Load by persistent db-guid (64-bit), will map to a transient low-guid + virtual bool LoadFromDB64(uint64 dbGuid, ObjectGuid owner_guid, Field* fields, uint32 entry); static void DeleteFromDB(CharacterDatabaseTransaction trans, ObjectGuid::LowType itemGuid); virtual void DeleteFromDB(CharacterDatabaseTransaction trans); static void DeleteFromInventoryDB(CharacterDatabaseTransaction trans, ObjectGuid::LowType itemGuid); void DeleteFromInventoryDB(CharacterDatabaseTransaction trans); + + // Decoupled: DB GUID accessors + uint64 GetDbGuid() const { return m_dbGuid; } + void SetDbGuid(uint64 v) { m_dbGuid = v; } void SaveRefundDataToDB(); void DeleteRefundDataFromDB(CharacterDatabaseTransaction* trans); @@ -376,5 +383,6 @@ private: uint32 m_paidMoney; uint32 m_paidExtendedCost; AllowedLooterSet allowedGUIDs; + uint64 m_dbGuid = 0; // Persistent DB identifier (64-bit) }; #endif diff --git a/src/server/game/Entities/Item/ItemGuidMap.cpp b/src/server/game/Entities/Item/ItemGuidMap.cpp new file mode 100644 index 0000000000..2230e50198 --- /dev/null +++ b/src/server/game/Entities/Item/ItemGuidMap.cpp @@ -0,0 +1,59 @@ +/* + * Lightweight runtime mapper for Items: DB GUID (uint64) <-> transient Low GUID (uint32) + */ + +#include "ItemGuidMap.h" + +ItemGuidMap* ItemGuidMap::instance() +{ + // Function-local static to respect the private constructor + static ItemGuidMap s_Instance; + return &s_Instance; +} + +uint32_t ItemGuidMap::NextLow() +{ + // Simple monotonically increasing counter with free-list reuse + if (!_free.empty()) + { + uint32_t id = _free.front(); + _free.pop(); + return id; + } + // wrap-around protection is not handled here; upper layers should ensure recyclable usage + return _next++; +} + +uint32_t ItemGuidMap::Acquire(uint64_t dbGuid) +{ + std::lock_guard g(_mtx); + auto it = _dbToLow.find(dbGuid); + if (it != _dbToLow.end()) + return it->second; + uint32_t low = NextLow(); + _dbToLow.emplace(dbGuid, low); + return low; +} + +uint32_t ItemGuidMap::AcquireForNew() +{ + std::lock_guard g(_mtx); + return NextLow(); +} + +void ItemGuidMap::Bind(uint64_t dbGuid, uint32_t lowGuid) +{ + std::lock_guard g(_mtx); + _dbToLow[dbGuid] = lowGuid; +} + +void ItemGuidMap::Release(uint64_t dbGuid) +{ + std::lock_guard g(_mtx); + auto it = _dbToLow.find(dbGuid); + if (it != _dbToLow.end()) + { + _free.push(it->second); + _dbToLow.erase(it); + } +} diff --git a/src/server/game/Entities/Item/ItemGuidMap.h b/src/server/game/Entities/Item/ItemGuidMap.h new file mode 100644 index 0000000000..1929c74519 --- /dev/null +++ b/src/server/game/Entities/Item/ItemGuidMap.h @@ -0,0 +1,56 @@ +/* + * Lightweight runtime mapper for Items: DB GUID (uint64) <-> transient Low GUID (uint32) + */ + +#pragma once + +#include +#include +#include +#include +#include + +class ItemGuidMap +{ +public: + static ItemGuidMap* instance(); + + // Acquire or create a low-guid for a persistent db-guid + uint32_t Acquire(uint64_t dbGuid); + + // Reserve a new low-guid for a brand new, unsaved item (dbGuid not known yet) + uint32_t AcquireForNew(); + + // Bind a db-guid to an already reserved low-guid (used after first save assigns dbGuid) + void Bind(uint64_t dbGuid, uint32_t lowGuid); + + // Release mapping and recycle low-guid + void Release(uint64_t dbGuid); + + // Initialize the starting value for persistent DB GUID generator + void InitDbGuidSeed(uint64_t start) + { + std::lock_guard g(_mtx); + if (_nextDbGuid.load() == 0 || start > _nextDbGuid.load()) + _nextDbGuid.store(start); + } + + // Get next persistent DB GUID + uint64_t AcquireDbGuid() + { + return _nextDbGuid.fetch_add(1, std::memory_order_relaxed); + } + +private: + ItemGuidMap() = default; + + uint32_t NextLow(); + + std::unordered_map _dbToLow; + std::queue _free; + uint32_t _next{1}; + std::mutex _mtx; + std::atomic _nextDbGuid{0}; +}; + +#define sItemGuidMap ItemGuidMap::instance() diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 80f6a068e4..d12a238708 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -64,6 +64,7 @@ #include "Util.h" #include "World.h" #include "WorldPacket.h" +#include "Entities/Item/ItemGuidMap.h" /// @todo: this import is not necessary for compilation and marked as unused by the IDE // however, for some reasons removing it would cause a damn linking issue @@ -2567,7 +2568,7 @@ Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update ss << ' ' << (*itr).GetCounter(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_BOP_TRADE); - stmt->SetData(0, pItem->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(pItem->GetDbGuid())); stmt->SetData(1, ss.str()); CharacterDatabase.Execute(stmt); } @@ -3036,7 +3037,7 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update) if (pItem->IsWrapped()) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GIFT); - stmt->SetData(0, pItem->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(pItem->GetDbGuid())); CharacterDatabase.Execute(stmt); } @@ -5973,20 +5974,21 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timeDiff) Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint32 timeDiff, Field* fields) { Item* item = nullptr; - ObjectGuid::LowType itemGuid = fields[13].Get(); + uint64 itemGuidDb = fields[13].Get(); uint32 itemEntry = fields[14].Get(); if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry)) { bool remove = false; item = NewItemOrBag(proto); - if (item->LoadFromDB(itemGuid, GetGUID(), fields, itemEntry)) + bool ok = item->LoadFromDB64(itemGuidDb, GetGUID(), fields, itemEntry); + if (ok) { CharacterDatabasePreparedStatement* stmt = nullptr; // Do not allow to have item limited to another map/zone in alive state if (IsAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zoneId)) { - LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player ({}, name: '{}', map: {}) has item ({}, entry: {}) limited to another map ({}). Deleting item.", + LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player ({}, name: '{}', map: {}) has item ({}, entry: {}) limited to another map ({}). Deleting item.", GetGUID().ToString(), GetName(), GetMapId(), item->GetGUID().ToString(), item->GetEntry(), zoneId); remove = true; } @@ -6004,7 +6006,7 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3 LOG_DEBUG("entities.player.loading", "Player::_LoadInventory: player ({}, name: '{}') has item ({}, entry: {}) with expired refund time ({}). Deleting refund data and removing refundable flag.", GetGUID().ToString(), GetName(), item->GetGUID().ToString(), item->GetEntry(), item->GetPlayedTime()); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_REFUND_INSTANCE); - stmt->SetData(0, item->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(item->GetDbGuid())); trans->Append(stmt); item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE); @@ -6013,7 +6015,7 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3 { // xinef: sync query stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_REFUNDS); - stmt->SetData(0, item->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(item->GetDbGuid())); stmt->SetData(1, GetGUID().GetCounter()); if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) { @@ -6033,7 +6035,7 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3 else if (item->IsBOPTradable()) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_BOP_TRADE); - stmt->SetData(0, item->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(item->GetDbGuid())); if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) { @@ -6083,13 +6085,15 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3 else { LOG_ERROR("entities.player", "Player::_LoadInventory: player ({}, name: '{}') has broken item (GUID: {}, entry: {}) in inventory. Deleting item.", - GetGUID().ToString(), GetName(), itemGuid, itemEntry); + GetGUID().ToString(), GetName(), itemGuidDb, itemEntry); remove = true; } // Remove item from inventory if necessary if (remove) { - Item::DeleteFromInventoryDB(trans, itemGuid); + CharacterDatabasePreparedStatement* delStmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); + delStmt->SetData(0, static_cast(item->GetDbGuid())); + trans->Append(delStmt); item->FSetState(ITEM_REMOVED); item->SaveToDB(trans); // it also deletes item object! item = nullptr; @@ -6099,8 +6103,12 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3 { LOG_ERROR("entities.player", "Player::_LoadInventory: player ({}, name: '{}') has unknown item (entry: {}) in inventory. Deleting item.", GetGUID().ToString(), GetName(), itemEntry); - Item::DeleteFromInventoryDB(trans, itemGuid); - Item::DeleteFromDB(trans, itemGuid); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); + stmt->SetData(0, static_cast(itemGuidDb)); + trans->Append(stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->SetData(0, static_cast(itemGuidDb)); + trans->Append(stmt); } return item; } @@ -6108,19 +6116,19 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3 // load mailed item which should receive current player Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint32 mailId, Mail* mail, Field* fields) { - ObjectGuid::LowType itemGuid = fields[11].Get(); + uint64 itemGuidDb = fields[11].Get(); uint32 itemEntry = fields[12].Get(); ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry); if (!proto) { LOG_ERROR("entities.player", "Player {} ({}) has unknown item in mailed items (GUID: {}, Entry: {}) in mail ({}), deleted.", - player ? player->GetName() : "", playerGuid.ToString(), itemGuid, itemEntry, mailId); + player ? player->GetName() : "", playerGuid.ToString(), itemGuidDb, itemEntry, mailId); CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM); - stmt->SetData(0, itemGuid); + stmt->SetData(0, static_cast(itemGuidDb)); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); @@ -6130,12 +6138,12 @@ Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint Item* item = NewItemOrBag(proto); ObjectGuid ownerGuid = fields[13].Get() ? ObjectGuid::Create(fields[13].Get()) : ObjectGuid::Empty; - if (!item->LoadFromDB(itemGuid, ownerGuid, fields, itemEntry)) + if (!item->LoadFromDB64(itemGuidDb, ownerGuid, fields, itemEntry)) { - LOG_ERROR("entities.player", "Player::_LoadMailedItems: Item (GUID: {}) in mail ({}) doesn't exist, deleted from mail.", itemGuid, mailId); + LOG_ERROR("entities.player", "Player::_LoadMailedItems: Item (GUID: {}) in mail ({}) doesn't exist, deleted from mail.", itemGuidDb, mailId); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM); - stmt->SetData(0, itemGuid); + stmt->SetData(0, static_cast(itemGuidDb)); CharacterDatabase.Execute(stmt); item->FSetState(ITEM_REMOVED); @@ -6147,7 +6155,7 @@ Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint if (mail) { - mail->AddItem(itemGuid, itemEntry); + mail->AddItem(item->GetGUID().GetCounter(), itemEntry); } if (player) @@ -7271,11 +7279,11 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans) } stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); - stmt->SetData(0, item->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(item->GetDbGuid())); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); - stmt->SetData(0, item->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(item->GetDbGuid())); trans->Append(stmt); m_items[i]->FSetState(ITEM_NEW); @@ -7369,16 +7377,23 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans) { case ITEM_NEW: case ITEM_CHANGED: + // Ensure a DB GUID is assigned before writing character_inventory + if (item->GetDbGuid() == 0) + { + uint64 newDbGuid = sItemGuidMap->AcquireDbGuid(); + sItemGuidMap->Bind(newDbGuid, item->GetGUID().GetCounter()); + item->SetDbGuid(newDbGuid); + } stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_INVENTORY_ITEM); stmt->SetData(0, lowGuid); stmt->SetData(1, bag_guid); stmt->SetData (2, item->GetSlot()); - stmt->SetData(3, item->GetGUID().GetCounter()); + stmt->SetData(3, static_cast(item->GetDbGuid())); trans->Append(stmt); break; case ITEM_REMOVED: stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); - stmt->SetData(0, item->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(item->GetDbGuid())); trans->Append(stmt); case ITEM_UNCHANGED: break; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index ff904bb177..8404a9b2a0 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -27,6 +27,7 @@ #include "DBCStructure.h" #include "DatabaseEnv.h" #include "DisableMgr.h" +#include "Entities/Item/ItemGuidMap.h" #include "GameEventMgr.h" #include "GameObjectAIFactory.h" #include "GameTime.h" @@ -6327,7 +6328,7 @@ void ObjectMgr::ReturnOrDeleteOldMails(bool serverUp) do { Field* fields = items->Fetch(); - item.item_guid = fields[0].Get(); + item.item_guid = fields[0].Get(); item.item_template = fields[1].Get(); uint32 mailId = fields[2].Get(); itemsCache[mailId].push_back(item); @@ -7195,14 +7196,11 @@ void ObjectMgr::SetHighestGuids() GetGuidSequenceGenerator().Set((*result)[0].Get() + 1); result = CharacterDatabase.Query("SELECT MAX(guid) FROM item_instance"); - if (result) - GetGuidSequenceGenerator().Set((*result)[0].Get() + 1); + // Initialize persistent item DB GUID starting point (64-bit) + // NOTE: In decoupled mode, item low GUIDs are process-local and not persisted. + if (QueryResult itemMax = CharacterDatabase.Query("SELECT MAX(guid) FROM item_instance")) + sItemGuidMap->InitDbGuidSeed((*itemMax)[0].Get() + 1u); - // Cleanup other tables from not existed guids ( >= _hiItemGuid) - CharacterDatabase.Execute("DELETE FROM character_inventory WHERE item >= '{}'", GetGuidSequenceGenerator().GetNextAfterMaxUsed()); // One-time query - CharacterDatabase.Execute("DELETE FROM mail_items WHERE item_guid >= '{}'", GetGuidSequenceGenerator().GetNextAfterMaxUsed()); // One-time query - CharacterDatabase.Execute("DELETE FROM auctionhouse WHERE itemguid >= '{}'", GetGuidSequenceGenerator().GetNextAfterMaxUsed()); // One-time query - CharacterDatabase.Execute("DELETE FROM guild_bank_item WHERE item_guid >= '{}'", GetGuidSequenceGenerator().GetNextAfterMaxUsed()); // One-time query result = WorldDatabase.Query("SELECT MAX(guid) FROM transports"); if (result) diff --git a/src/server/game/Guilds/Guild.cpp b/src/server/game/Guilds/Guild.cpp index bd9bbafc66..77e8b9d9d8 100644 --- a/src/server/game/Guilds/Guild.cpp +++ b/src/server/game/Guilds/Guild.cpp @@ -387,25 +387,26 @@ void Guild::BankTab::LoadFromDB(Field* fields) bool Guild::BankTab::LoadItemFromDB(Field* fields) { uint8 slotId = fields[13].Get(); - ObjectGuid::LowType itemGuid = fields[14].Get(); + uint64 itemGuidDb = fields[14].Get(); uint32 itemEntry = fields[15].Get(); if (slotId >= GUILD_BANK_MAX_SLOTS) { - LOG_ERROR("guild", "Invalid slot for item (GUID: {}, id: {}) in guild bank, skipped.", itemGuid, itemEntry); + LOG_ERROR("guild", "Invalid slot for item (GUID: {}, id: {}) in guild bank, skipped.", itemGuidDb, itemEntry); return false; } ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry); if (!proto) { - LOG_ERROR("guild", "Unknown item (GUID: {}, id: {}) in guild bank, skipped.", itemGuid, itemEntry); + LOG_ERROR("guild", "Unknown item (GUID: {}, id: {}) in guild bank, skipped.", itemGuidDb, itemEntry); return false; } Item* pItem = NewItemOrBag(proto); - if (!pItem->LoadFromDB(itemGuid, ObjectGuid::Empty, fields, itemEntry)) + bool ok = pItem->LoadFromDB64(itemGuidDb, ObjectGuid::Empty, fields, itemEntry); + if (!ok) { - LOG_ERROR("guild", "Item (GUID {}, id: {}) not found in item_instance, deleting from guild bank!", itemGuid, itemEntry); + LOG_ERROR("guild", "Item (GUID {}, id: {}) not found in item_instance, deleting from guild bank!", itemGuidDb, itemEntry); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NONEXISTENT_GUILD_BANK_ITEM); stmt->SetData(0, m_guildId); diff --git a/src/server/game/Handlers/MailHandler.cpp b/src/server/game/Handlers/MailHandler.cpp index 76cb7fc0d1..90a3c2a9d7 100644 --- a/src/server/game/Handlers/MailHandler.cpp +++ b/src/server/game/Handlers/MailHandler.cpp @@ -31,6 +31,7 @@ #include "ScriptMgr.h" #include "WorldPacket.h" #include "WorldSession.h" +#include "Entities/Item/ItemGuidMap.h" #define MAX_INBOX_CLIENT_CAPACITY 50 @@ -73,14 +74,14 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) recvData >> subject; + recvData >> body; + // prevent client crash if (subject.find("| |") != std::string::npos || body.find("| |") != std::string::npos) { return; } - recvData >> body; - recvData >> unk1; // stationery? recvData >> unk2; // 0x00000000 @@ -712,7 +713,7 @@ void WorldSession::HandleGetMailList(WorldPacket& recvData) for (uint8 i = 0; i < item_count; ++i) { - Item* item = player->GetMItem(mail->items[i].item_guid); + Item* item = player->GetMItem(static_cast(mail->items[i].item_guid)); // item index (0-6?) data << uint8(i); // item guid low? @@ -775,7 +776,7 @@ void WorldSession::HandleMailCreateTextItem(WorldPacket& recvData) } Item* bodyItem = new Item; // This is not bag and then can be used new Item. - if (!bodyItem->Create(sObjectMgr->GetGenerator().Generate(), MAIL_BODY_ITEM_TEMPLATE, player)) + if (!bodyItem->Create(sItemGuidMap->AcquireForNew(), MAIL_BODY_ITEM_TEMPLATE, player)) { delete bodyItem; return; diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index 6117637db6..aa3be2ba3c 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -273,7 +273,7 @@ void WorldSession::HandleOpenItemOpcode(WorldPacket& recvPacket) if (item->IsWrapped())// wrapped? { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_GIFT_BY_ITEM); - stmt->SetData(0, item->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(item->GetDbGuid())); _queryProcessor.AddCallback(CharacterDatabase.AsyncQuery(stmt) .WithPreparedCallback(std::bind(&WorldSession::HandleOpenWrappedItemCallback, this, bagIndex, slot, item->GetGUID().GetCounter(), std::placeholders::_1))); } @@ -318,7 +318,7 @@ void WorldSession::HandleOpenWrappedItemCallback(uint8 bagIndex, uint8 slot, Obj GetPlayer()->SaveInventoryAndGoldToDB(trans); CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GIFT); - stmt->SetData(0, item->GetGUID().GetCounter()); + stmt->SetData(0, static_cast(item->GetDbGuid())); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); diff --git a/src/server/game/Mails/Mail.cpp b/src/server/game/Mails/Mail.cpp index f8d838776f..044f0d5c53 100644 --- a/src/server/game/Mails/Mail.cpp +++ b/src/server/game/Mails/Mail.cpp @@ -246,7 +246,7 @@ void MailDraft::SendMailTo(CharacterDatabaseTransaction trans, MailReceiver cons { stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_MAIL_ITEM); stmt->SetData(0, mailId); - stmt->SetData(1, mailItemIter->second->GetGUID().GetCounter()); + stmt->SetData(1, static_cast(mailItemIter->second->GetDbGuid())); stmt->SetData(2, receiver.GetPlayerGUIDLow()); trans->Append(stmt); } diff --git a/src/server/game/Mails/Mail.h b/src/server/game/Mails/Mail.h index f89a5e1271..a38bf661de 100644 --- a/src/server/game/Mails/Mail.h +++ b/src/server/game/Mails/Mail.h @@ -158,7 +158,7 @@ private: struct MailItemInfo { - ObjectGuid::LowType item_guid; + uint64 item_guid; uint32 item_template; }; typedef std::vector MailItemInfoVec;