mirror of
https://github.com/azerothcore/azerothcore-wotlk.git
synced 2025-11-10 21:04:26 +08:00
feat(DB): decouple item GUIDs to BIGINT UNSIGNED for future-proof database integrity
This commit is contained in:
parent
6381194d8a
commit
18958dfb26
@ -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`);
|
||||
@ -29,6 +29,7 @@
|
||||
#include "UpdateTime.h"
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "Entities/Item/ItemGuidMap.h"
|
||||
#include <vector>
|
||||
|
||||
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<uint64>(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<uint32>();
|
||||
houseId = AuctionHouseId(fields[1].Get<uint8>());
|
||||
item_guid = ObjectGuid::Create<HighGuid::Item>(fields[2].Get<uint32>());
|
||||
{
|
||||
uint64 dbGuid = fields[2].Get<uint64>();
|
||||
uint32 low = sItemGuidMap->Acquire(dbGuid);
|
||||
item_guid = ObjectGuid::Create<HighGuid::Item>(low);
|
||||
}
|
||||
item_template = fields[3].Get<uint32>();
|
||||
itemCount = fields[4].Get<uint32>();
|
||||
owner = ObjectGuid::Create<HighGuid::Player>(fields[5].Get<uint32>());
|
||||
|
||||
@ -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<uint64>(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<uint64>(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<uint64>(m_dbGuid));
|
||||
trans->Append(stmt);
|
||||
|
||||
if (IsWrapped())
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GIFT);
|
||||
stmt->SetData(0, guid);
|
||||
stmt->SetData(0, static_cast<uint64>(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<HighGuid::Player>(fields[0].Get<uint32>()));
|
||||
SetGuidValue(ITEM_FIELD_GIFTCREATOR, ObjectGuid::Create<HighGuid::Player>(fields[1].Get<uint32>()));
|
||||
SetCount( fields[2].Get<uint32>());
|
||||
|
||||
// Duration
|
||||
uint32 duration = fields[3].Get<uint32>();
|
||||
SetUInt32Value(ITEM_FIELD_DURATION, duration);
|
||||
if ((proto->Duration == 0) != (duration == 0))
|
||||
{
|
||||
SetUInt32Value(ITEM_FIELD_DURATION, proto->Duration);
|
||||
need_save = true;
|
||||
}
|
||||
|
||||
// Charges
|
||||
{
|
||||
std::vector<std::string_view> tokens = Acore::Tokenize(fields[4].Get<std::string_view>(), ' ', false);
|
||||
if (tokens.size() == MAX_ITEM_PROTO_SPELLS)
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
|
||||
{
|
||||
if (Optional<int32> charges = Acore::StringTo<int32>(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<uint32>());
|
||||
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<std::string>(), 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<std::string>(), GetGUID().ToString());
|
||||
}
|
||||
|
||||
// Random properties
|
||||
SetInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID, fields[7].Get<int16>());
|
||||
if (GetItemRandomPropertyId() < 0)
|
||||
UpdateItemSuffixFactor();
|
||||
|
||||
// Durability
|
||||
uint32 durability = fields[8].Get<uint16>();
|
||||
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<uint32>());
|
||||
SetText(fields[10].Get<std::string>());
|
||||
|
||||
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<uint64>(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<uint64>(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<uint64>(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<HighGuid::Item>().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<uint64>(GetDbGuid()));
|
||||
trans->Append(stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_REFUND_INSTANCE);
|
||||
stmt->SetData(0, GetGUID().GetCounter());
|
||||
stmt->SetData(0, static_cast<uint64>(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<uint64>(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<uint64>(GetDbGuid()));
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
59
src/server/game/Entities/Item/ItemGuidMap.cpp
Normal file
59
src/server/game/Entities/Item/ItemGuidMap.cpp
Normal file
@ -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<std::mutex> 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<std::mutex> g(_mtx);
|
||||
return NextLow();
|
||||
}
|
||||
|
||||
void ItemGuidMap::Bind(uint64_t dbGuid, uint32_t lowGuid)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_mtx);
|
||||
_dbToLow[dbGuid] = lowGuid;
|
||||
}
|
||||
|
||||
void ItemGuidMap::Release(uint64_t dbGuid)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_mtx);
|
||||
auto it = _dbToLow.find(dbGuid);
|
||||
if (it != _dbToLow.end())
|
||||
{
|
||||
_free.push(it->second);
|
||||
_dbToLow.erase(it);
|
||||
}
|
||||
}
|
||||
56
src/server/game/Entities/Item/ItemGuidMap.h
Normal file
56
src/server/game/Entities/Item/ItemGuidMap.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Lightweight runtime mapper for Items: DB GUID (uint64) <-> transient Low GUID (uint32)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
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<std::mutex> 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<uint64_t, uint32_t> _dbToLow;
|
||||
std::queue<uint32_t> _free;
|
||||
uint32_t _next{1};
|
||||
std::mutex _mtx;
|
||||
std::atomic<uint64_t> _nextDbGuid{0};
|
||||
};
|
||||
|
||||
#define sItemGuidMap ItemGuidMap::instance()
|
||||
@ -63,6 +63,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
|
||||
@ -2561,7 +2562,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<uint64>(pItem->GetDbGuid()));
|
||||
stmt->SetData(1, ss.str());
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
@ -3032,7 +3033,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<uint64>(pItem->GetDbGuid()));
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
@ -5969,20 +5970,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<uint32>();
|
||||
uint64 itemGuidDb = fields[13].Get<uint64>();
|
||||
uint32 itemEntry = fields[14].Get<uint32>();
|
||||
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;
|
||||
}
|
||||
@ -6000,7 +6002,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<uint64>(item->GetDbGuid()));
|
||||
trans->Append(stmt);
|
||||
|
||||
item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE);
|
||||
@ -6009,7 +6011,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<uint64>(item->GetDbGuid()));
|
||||
stmt->SetData(1, GetGUID().GetCounter());
|
||||
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
|
||||
{
|
||||
@ -6029,7 +6031,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<uint64>(item->GetDbGuid()));
|
||||
|
||||
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
|
||||
{
|
||||
@ -6079,13 +6081,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<uint64>(item->GetDbGuid()));
|
||||
trans->Append(delStmt);
|
||||
item->FSetState(ITEM_REMOVED);
|
||||
item->SaveToDB(trans); // it also deletes item object!
|
||||
item = nullptr;
|
||||
@ -6095,8 +6099,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<uint64>(itemGuidDb));
|
||||
trans->Append(stmt);
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
|
||||
stmt->SetData(0, static_cast<uint64>(itemGuidDb));
|
||||
trans->Append(stmt);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@ -6104,19 +6112,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<uint32>();
|
||||
uint64 itemGuidDb = fields[11].Get<uint64>();
|
||||
uint32 itemEntry = fields[12].Get<uint32>();
|
||||
|
||||
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() : "<unknown>", playerGuid.ToString(), itemGuid, itemEntry, mailId);
|
||||
player ? player->GetName() : "<unknown>", 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<uint64>(itemGuidDb));
|
||||
trans->Append(stmt);
|
||||
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
@ -6126,12 +6134,12 @@ Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint
|
||||
Item* item = NewItemOrBag(proto);
|
||||
|
||||
ObjectGuid ownerGuid = fields[13].Get<uint32>() ? ObjectGuid::Create<HighGuid::Player>(fields[13].Get<uint32>()) : 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<uint64>(itemGuidDb));
|
||||
CharacterDatabase.Execute(stmt);
|
||||
|
||||
item->FSetState(ITEM_REMOVED);
|
||||
@ -6143,7 +6151,7 @@ Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint
|
||||
|
||||
if (mail)
|
||||
{
|
||||
mail->AddItem(itemGuid, itemEntry);
|
||||
mail->AddItem(item->GetGUID().GetCounter(), itemEntry);
|
||||
}
|
||||
|
||||
if (player)
|
||||
@ -7267,11 +7275,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<uint64>(item->GetDbGuid()));
|
||||
trans->Append(stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
|
||||
stmt->SetData(0, item->GetGUID().GetCounter());
|
||||
stmt->SetData(0, static_cast<uint64>(item->GetDbGuid()));
|
||||
trans->Append(stmt);
|
||||
m_items[i]->FSetState(ITEM_NEW);
|
||||
|
||||
@ -7365,16 +7373,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<uint64>(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<uint64>(item->GetDbGuid()));
|
||||
trans->Append(stmt);
|
||||
case ITEM_UNCHANGED:
|
||||
break;
|
||||
|
||||
@ -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"
|
||||
@ -6316,7 +6317,7 @@ void ObjectMgr::ReturnOrDeleteOldMails(bool serverUp)
|
||||
do
|
||||
{
|
||||
Field* fields = items->Fetch();
|
||||
item.item_guid = fields[0].Get<uint32>();
|
||||
item.item_guid = fields[0].Get<uint64>();
|
||||
item.item_template = fields[1].Get<uint32>();
|
||||
uint32 mailId = fields[2].Get<uint32>();
|
||||
itemsCache[mailId].push_back(item);
|
||||
@ -7184,14 +7185,11 @@ void ObjectMgr::SetHighestGuids()
|
||||
GetGuidSequenceGenerator<HighGuid::Player>().Set((*result)[0].Get<uint32>() + 1);
|
||||
|
||||
result = CharacterDatabase.Query("SELECT MAX(guid) FROM item_instance");
|
||||
if (result)
|
||||
GetGuidSequenceGenerator<HighGuid::Item>().Set((*result)[0].Get<uint32>() + 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<uint64>() + 1u);
|
||||
|
||||
// Cleanup other tables from not existed guids ( >= _hiItemGuid)
|
||||
CharacterDatabase.Execute("DELETE FROM character_inventory WHERE item >= '{}'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
|
||||
CharacterDatabase.Execute("DELETE FROM mail_items WHERE item_guid >= '{}'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
|
||||
CharacterDatabase.Execute("DELETE FROM auctionhouse WHERE itemguid >= '{}'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
|
||||
CharacterDatabase.Execute("DELETE FROM guild_bank_item WHERE item_guid >= '{}'", GetGuidSequenceGenerator<HighGuid::Item>().GetNextAfterMaxUsed()); // One-time query
|
||||
|
||||
result = WorldDatabase.Query("SELECT MAX(guid) FROM transports");
|
||||
if (result)
|
||||
|
||||
@ -387,25 +387,26 @@ void Guild::BankTab::LoadFromDB(Field* fields)
|
||||
bool Guild::BankTab::LoadItemFromDB(Field* fields)
|
||||
{
|
||||
uint8 slotId = fields[13].Get<uint8>();
|
||||
ObjectGuid::LowType itemGuid = fields[14].Get<uint32>();
|
||||
uint64 itemGuidDb = fields[14].Get<uint64>();
|
||||
uint32 itemEntry = fields[15].Get<uint32>();
|
||||
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);
|
||||
|
||||
@ -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<ObjectGuid::LowType>(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<HighGuid::Item>().Generate(), MAIL_BODY_ITEM_TEMPLATE, player))
|
||||
if (!bodyItem->Create(sItemGuidMap->AcquireForNew(), MAIL_BODY_ITEM_TEMPLATE, player))
|
||||
{
|
||||
delete bodyItem;
|
||||
return;
|
||||
|
||||
@ -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<uint64>(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<uint64>(item->GetDbGuid()));
|
||||
trans->Append(stmt);
|
||||
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
@ -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<uint64>(mailItemIter->second->GetDbGuid()));
|
||||
stmt->SetData(2, receiver.GetPlayerGUIDLow());
|
||||
trans->Append(stmt);
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ private:
|
||||
|
||||
struct MailItemInfo
|
||||
{
|
||||
ObjectGuid::LowType item_guid;
|
||||
uint64 item_guid;
|
||||
uint32 item_template;
|
||||
};
|
||||
typedef std::vector<MailItemInfo> MailItemInfoVec;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user