Core/Loading: refactor inventory loading routine and add more informational error logging to it.

This commit is contained in:
Azazel
2011-03-12 00:54:37 +06:00
parent 9b2d62b72d
commit 7200363835
4 changed files with 178 additions and 182 deletions
+17 -5
View File
@@ -451,18 +451,30 @@ bool Item::LoadFromDB(uint32 guid, uint64 owner_guid, Field* fields, uint32 entr
return true; return true;
} }
void Item::DeleteFromDB(SQLTransaction& trans) /*static*/
void Item::DeleteFromDB(SQLTransaction& trans, uint32 itemGuid)
{ {
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
stmt->setUInt32(0, GetGUIDLow()); stmt->setUInt32(0, itemGuid);
trans->Append(stmt);
}
void Item::DeleteFromDB(SQLTransaction& trans)
{
DeleteFromDB(trans, GetGUIDLow());
}
/*static*/
void Item::DeleteFromInventoryDB(SQLTransaction& trans, uint32 itemGuid)
{
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM);
stmt->setUInt32(0, itemGuid);
trans->Append(stmt); trans->Append(stmt);
} }
void Item::DeleteFromInventoryDB(SQLTransaction& trans) void Item::DeleteFromInventoryDB(SQLTransaction& trans)
{ {
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); DeleteFromInventoryDB(trans, GetGUIDLow());
stmt->setUInt32(0, GetGUIDLow());
trans->Append(stmt);
} }
ItemPrototype const *Item::GetProto() const ItemPrototype const *Item::GetProto() const
+2
View File
@@ -246,7 +246,9 @@ class Item : public Object
bool IsBoundByEnchant() const; bool IsBoundByEnchant() const;
virtual void SaveToDB(SQLTransaction& trans); virtual void SaveToDB(SQLTransaction& trans);
virtual bool LoadFromDB(uint32 guid, uint64 owner_guid, Field* fields, uint32 entry); virtual bool LoadFromDB(uint32 guid, uint64 owner_guid, Field* fields, uint32 entry);
static void DeleteFromDB(SQLTransaction& trans, uint32 itemGuid);
virtual void DeleteFromDB(SQLTransaction& trans); virtual void DeleteFromDB(SQLTransaction& trans);
static void DeleteFromInventoryDB(SQLTransaction& trans, uint32 itemGuid);
void DeleteFromInventoryDB(SQLTransaction& trans); void DeleteFromInventoryDB(SQLTransaction& trans);
void SaveRefundDataToDB(); void SaveRefundDataToDB();
void DeleteRefundDataFromDB(); void DeleteRefundDataFromDB();
+134 -153
View File
@@ -16995,87 +16995,145 @@ void Player::LoadCorpse()
} }
} }
void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff) void Player::_LoadInventory(PreparedQueryResult result, uint32 timeDiff)
{ {
//QueryResult *result = CharacterDatabase.PQuery("SELECT data,text,bag,slot,item,item_template FROM character_inventory JOIN item_instance ON character_inventory.item = item_instance.guid WHERE character_inventory.guid = '%u' ORDER BY bag,slot", GetGUIDLow()); //QueryResult *result = CharacterDatabase.PQuery("SELECT data,text,bag,slot,item,item_template FROM character_inventory JOIN item_instance ON character_inventory.item = item_instance.guid WHERE character_inventory.guid = '%u' ORDER BY bag,slot", GetGUIDLow());
std::map<uint64, Bag*> bagMap; // fast guid lookup for bags
//NOTE: the "order by `bag`" is important because it makes sure //NOTE: the "order by `bag`" is important because it makes sure
//the bagMap is filled before items in the bags are loaded //the bagMap is filled before items in the bags are loaded
//NOTE2: the "order by `slot`" is needed because mainhand weapons are (wrongly?) //NOTE2: the "order by `slot`" is needed because mainhand weapons are (wrongly?)
//expected to be equipped before offhand items (TODO: fixme) //expected to be equipped before offhand items (TODO: fixme)
uint32 zone = GetZoneId();
if (result) if (result)
{ {
uint32 zoneId = GetZoneId();
std::map<uint64, Bag*> bagMap; // fast guid lookup for bags
std::list<Item*> problematicItems; std::list<Item*> problematicItems;
SQLTransaction trans = CharacterDatabase.BeginTransaction(); SQLTransaction trans = CharacterDatabase.BeginTransaction();
// prevent items from being added to the queue when stored // Prevent items from being added to the queue while loading
m_itemUpdateQueueBlocked = true; m_itemUpdateQueueBlocked = true;
do do
{ {
Field* fields = result->Fetch(); Field* fields = result->Fetch();
if (Item* item = _LoadItem(trans, zoneId, timeDiff, fields))
uint32 bag_guid = fields[11].GetUInt32(); {
uint32 bagGuid = fields[11].GetUInt32();
uint8 slot = fields[12].GetUInt8(); uint8 slot = fields[12].GetUInt8();
uint32 item_guid = fields[13].GetUInt32();
uint32 item_id = fields[14].GetUInt32();
ItemPrototype const * proto = ObjectMgr::GetItemPrototype(item_id); uint8 err = EQUIP_ERR_OK;
// Item is not in bag
if (!proto) if (!bagGuid)
{ {
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); item->SetContainer(NULL);
stmt->setUInt32(0, item_guid); item->SetSlot(slot);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); if (IsInventoryPos(INVENTORY_SLOT_BAG_0, slot))
stmt->setUInt32(0, item_guid); {
trans->Append(stmt); ItemPosCountVec dest;
sLog->outError("Player::_LoadInventory: Player %s has an unknown item (id: #%u) in inventory, deleted.", GetName(),item_id); err = CanStoreItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false);
continue; if (err == EQUIP_ERR_OK)
item = StoreItem(dest, item, true);
}
else if (IsEquipmentPos(INVENTORY_SLOT_BAG_0, slot))
{
uint16 dest;
err = CanEquipItem(slot, dest, item, false, false);
if (err == EQUIP_ERR_OK)
QuickEquipItem(dest, item);
}
else if (IsBankPos(INVENTORY_SLOT_BAG_0, slot))
{
ItemPosCountVec dest;
err = CanBankItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false, false);
if (err == EQUIP_ERR_OK)
item = BankItem(dest, item, true);
} }
Item *item = NewItemOrBag(proto); // Remember bags that may contain items in them
if (err == EQUIP_ERR_OK)
if (!item->LoadFromDB(item_guid, GetGUID(), fields, item_id)) if (item->IsBag() && IsBagPos(item->GetPos()))
bagMap[item->GetGUIDLow()] = (Bag*)item;
}
else
{ {
sLog->outError("Player::_LoadInventory: Player %s has broken item (id: #%u) in inventory, deleted.", GetName(),item_id); item->SetSlot(NULL_SLOT);
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); // Item is in the bag, find the bag
stmt->setUInt32(0, item_guid); std::map<uint64, Bag*>::iterator itr = bagMap.find(bagGuid);
trans->Append(stmt); if (itr != bagMap.end())
item->FSetState(ITEM_REMOVED); {
item->SaveToDB(trans); // it also deletes item object ! ItemPosCountVec dest;
continue; err = CanStoreItem(itr->second->GetSlot(), slot, dest, item);
if (err == EQUIP_ERR_OK)
itr->second->StoreItem(slot, item, true);
}
} }
// not allow have in alive state item limited to another map/zone // Item's state may have changed after storing
if (isAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zone)) if (err == EQUIP_ERR_OK)
item->SetState(ITEM_UNCHANGED, this);
else
{ {
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); sLog->outError("Player::_LoadInventory: player (GUID: %u, name: '%s') has item (GUID: %u, entry: %u) which can't be loaded into inventory (Bag GUID: %u, slot: %u) by reason %u. Item will be sent by mail.",
stmt->setUInt32(0, item_guid); GetGUIDLow(), GetName(), item->GetGUIDLow(), item->GetEntry(), bagGuid, slot, err);
trans->Append(stmt); item->DeleteFromInventoryDB(trans);
item->FSetState(ITEM_REMOVED); problematicItems.push_back(item);
item->SaveToDB(trans); // it also deletes item object ! }
continue; }
} while (result->NextRow());
m_itemUpdateQueueBlocked = false;
// Send problematic items by mail
while (!problematicItems.empty())
{
std::string subject = GetSession()->GetTrinityString(LANG_NOT_EQUIPPED_ITEM);
MailDraft draft(subject, "There were problems with equipping item(s).");
for (uint8 i = 0; !problematicItems.empty() && i < MAX_MAIL_ITEMS; ++i)
{
draft.AddItem(problematicItems.front());
problematicItems.pop_front();
}
draft.SendMailTo(trans, this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
}
CharacterDatabase.CommitTransaction(trans);
}
//if (isAlive())
_ApplyAllItemMods();
} }
Item* Player::_LoadItem(SQLTransaction& trans, uint32 zoneId, uint32 timeDiff, Field* fields)
{
Item* item = NULL;
uint32 itemGuid = fields[13].GetUInt32();
uint32 itemEntry = fields[14].GetUInt32();
if (ItemPrototype const * proto = ObjectMgr::GetItemPrototype(itemEntry))
{
bool remove = false;
item = NewItemOrBag(proto);
if (item->LoadFromDB(itemGuid, GetGUID(), fields, itemEntry))
{
// Do not allow to have item limited to another map/zone in alive state
if (isAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zoneId))
{
sLog->outDebug(LOG_FILTER_PLAYER_LOADING, "Player::_LoadInventory: player (GUID: %u, name: '%s', map: %u) has item (GUID: %u, entry: %u) limited to another map (%u). Deleting item.",
GetGUIDLow(), GetName(), GetMapId(), item->GetGUIDLow(), item->GetEntry(), zoneId);
remove = true;
}
// "Conjured items disappear if you are logged out for more than 15 minutes" // "Conjured items disappear if you are logged out for more than 15 minutes"
if (timediff > 15*MINUTE && proto->Flags & ITEM_PROTO_FLAG_CONJURED) else if (timeDiff > 15 * MINUTE && proto->Flags & ITEM_PROTO_FLAG_CONJURED)
{ {
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); sLog->outDebug(LOG_FILTER_PLAYER_LOADING, "Player::_LoadInventory: player (GUID: %u, name: '%s', diff: %u) has conjured item (GUID: %u, entry: %u) with expired lifetime (15 minutes). Deleting item.",
stmt->setUInt32(0, item_guid); GetGUIDLow(), GetName(), timeDiff, item->GetGUIDLow(), item->GetEntry());
trans->Append(stmt); remove = true;
item->FSetState(ITEM_REMOVED);
item->SaveToDB(trans); // it also deletes item object !
continue;
} }
else if (item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_REFUNDABLE))
if (item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_REFUNDABLE))
{ {
if (item->GetPlayedTime() > (2 * HOUR)) if (item->GetPlayedTime() > (2 * HOUR))
{ {
sLog->outDebug(LOG_FILTER_PLAYER_LOADING, "Item::LoadFromDB, Item GUID: %u: refund time expired, deleting refund data and removing refundable flag.", item->GetGUIDLow()); sLog->outDebug(LOG_FILTER_PLAYER_LOADING, "Player::_LoadInventory: player (GUID: %u, name: '%s') has item (GUID: %u, entry: %u) with expired refund time (%u). Deleting refund data and removing refundable flag.",
GetGUIDLow(), GetName(), item->GetGUIDLow(), item->GetEntry(), item->GetPlayedTime());
trans->PAppend("DELETE FROM item_refund_instance WHERE item_guid = '%u'", item->GetGUIDLow()); trans->PAppend("DELETE FROM item_refund_instance WHERE item_guid = '%u'", item->GetGUIDLow());
item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_REFUNDABLE); item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_REFUNDABLE);
} }
@@ -17084,19 +17142,18 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff)
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_ITEM_REFUNDS); PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_ITEM_REFUNDS);
stmt->setUInt32(0, item->GetGUIDLow()); stmt->setUInt32(0, item->GetGUIDLow());
stmt->setUInt32(1, GetGUIDLow()); stmt->setUInt32(1, GetGUIDLow());
PreparedQueryResult result2 = CharacterDatabase.Query(stmt); if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
if (!result2)
{ {
sLog->outDebug(LOG_FILTER_PLAYER_LOADING, "Item::LoadFromDB, Item GUID: %u has field flags & ITEM_FLAGS_REFUNDABLE but has no data in item_refund_instance, removing flag.", item->GetGUIDLow()); item->SetRefundRecipient((*result)[0].GetUInt32());
item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_REFUNDABLE); item->SetPaidMoney((*result)[1].GetUInt32());
item->SetPaidExtendedCost((*result)[2].GetUInt16());
AddRefundReference(item->GetGUIDLow());
} }
else else
{ {
Field* fields2 = result2->Fetch(); sLog->outDebug(LOG_FILTER_PLAYER_LOADING, "Player::_LoadInventory: player (GUID: %u, name: '%s') has item (GUID: %u, entry: %u) with refundable flags, but without data in item_refund_instance. Removing flag.",
item->SetRefundRecipient(fields2[0].GetUInt32()); GetGUIDLow(), GetName(), item->GetGUIDLow(), item->GetEntry());
item->SetPaidMoney(fields2[1].GetUInt32()); item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_REFUNDABLE);
item->SetPaidExtendedCost(fields2[2].GetUInt16());
AddRefundReference(item->GetGUIDLow());
} }
} }
} }
@@ -17104,16 +17161,9 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff)
{ {
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_ITEM_BOP_TRADE); PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_ITEM_BOP_TRADE);
stmt->setUInt32(0, item->GetGUIDLow()); stmt->setUInt32(0, item->GetGUIDLow());
PreparedQueryResult result2 = CharacterDatabase.Query(stmt); if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
if (!result2)
{ {
sLog->outDebug(LOG_FILTER_PLAYER_LOADING, "Item::LoadFromDB, Item GUID: %u has flag ITEM_FLAG_BOP_TRADEABLE but has no data in item_soulbound_trade_data, removing flag.", item->GetGUIDLow()); std::string strGUID = (*result)[0].GetString();
item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE);
}
else
{
Field* fields2 = result2->Fetch();
std::string strGUID = fields2[0].GetString();
Tokens GUIDlist(strGUID, ' '); Tokens GUIDlist(strGUID, ' ');
AllowedLooterSet looters; AllowedLooterSet looters;
for (Tokens::iterator itr = GUIDlist.begin(); itr != GUIDlist.end(); ++itr) for (Tokens::iterator itr = GUIDlist.begin(); itr != GUIDlist.end(); ++itr)
@@ -17121,106 +17171,37 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff)
item->SetSoulboundTradeable(&looters, this, true); item->SetSoulboundTradeable(&looters, this, true);
m_itemSoulboundTradeable.push_back(item); m_itemSoulboundTradeable.push_back(item);
} }
}
bool success = true;
if (!bag_guid)
{
// the item is not in a bag
item->SetContainer(NULL);
item->SetSlot(slot);
if (IsInventoryPos(INVENTORY_SLOT_BAG_0, slot))
{
ItemPosCountVec dest;
if (CanStoreItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false) == EQUIP_ERR_OK)
item = StoreItem(dest, item, true);
else else
success = false;
}
else if (IsEquipmentPos(INVENTORY_SLOT_BAG_0, slot))
{ {
uint16 dest; sLog->outDebug(LOG_FILTER_PLAYER_LOADING, "Player::_LoadInventory: player (GUID: %u, name: '%s') has item (GUID: %u, entry: %u) with ITEM_FLAG_BOP_TRADEABLE flag, but without data in item_soulbound_trade_data. Removing flag.",
if (CanEquipItem(slot, dest, item, false, false) == EQUIP_ERR_OK) GetGUIDLow(), GetName(), item->GetGUIDLow(), item->GetEntry());
QuickEquipItem(dest, item); item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE);
else
success = false;
} }
else if (IsBankPos(INVENTORY_SLOT_BAG_0, slot))
{
ItemPosCountVec dest;
if (CanBankItem(INVENTORY_SLOT_BAG_0, slot, dest, item, false, false) == EQUIP_ERR_OK)
item = BankItem(dest, item, true);
else
success = false;
}
if (success)
{
// store bags that may contain items in them
if (item->IsBag() && IsBagPos(item->GetPos()))
bagMap[item_guid] = (Bag*)item;
} }
} }
else else
{ {
item->SetSlot(NULL_SLOT); sLog->outError("Player::_LoadInventory: player (GUID: %u, name: '%s') has broken item (GUID: %u, entry: %u) in inventory. Deleting item.",
// the item is in a bag, find the bag GetGUIDLow(), GetName(), itemGuid, itemEntry);
std::map<uint64, Bag*>::iterator itr = bagMap.find(bag_guid); remove = true;
if (itr != bagMap.end()) }
// Remove item from inventory if necessary
if (remove)
{ {
ItemPosCountVec dest; Item::DeleteFromInventoryDB(trans, itemGuid);
uint8 result = CanStoreItem(itr->second->GetSlot(), slot, dest, item); item->FSetState(ITEM_REMOVED);
if (result == EQUIP_ERR_OK) item->SaveToDB(trans); // it also deletes item object!
itr->second->StoreItem(slot, item, true); item = NULL;
else
{
sLog->outError("Player::_LoadInventory: Player %s has item (GUID: %u Entry: %u) can't be loaded to inventory (Bag GUID: %u Slot: %u) by reason %u.", GetName(),item_guid, item_id, bag_guid, slot, result);
success = false;
} }
} }
else
success = false;
}
// item's state may have changed after stored
if (success)
item->SetState(ITEM_UNCHANGED, this);
else else
{ {
sLog->outError("Player::_LoadInventory: Player %s has item (GUID: %u Entry: %u) can't be loaded to inventory (Bag GUID: %u Slot: %u) by some reason, will send by mail.", GetName(),item_guid, item_id, bag_guid, slot); sLog->outError("Player::_LoadInventory: player (GUID: %u, name: '%s') has unknown item (entry: %u) in inventory. Deleting item.",
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); GetGUIDLow(), GetName(), itemEntry);
stmt->setUInt32(0, item_guid); Item::DeleteFromInventoryDB(trans, itemGuid);
trans->Append(stmt); Item::DeleteFromDB(trans, itemGuid);
problematicItems.push_back(item);
} }
} while (result->NextRow()); return item;
m_itemUpdateQueueBlocked = false;
// send by mail problematic items
while (!problematicItems.empty())
{
std::string subject = GetSession()->GetTrinityString(LANG_NOT_EQUIPPED_ITEM);
// fill mail
MailDraft draft(subject, "There's were problems with equipping item(s).");
for (uint8 i = 0; !problematicItems.empty() && i < MAX_MAIL_ITEMS; ++i)
{
Item* item = problematicItems.front();
problematicItems.pop_front();
draft.AddItem(item);
}
draft.SendMailTo(trans, this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
}
CharacterDatabase.CommitTransaction(trans);
}
//if (isAlive())
_ApplyAllItemMods();
} }
// load mailed item which should receive current player // load mailed item which should receive current player
+2 -1
View File
@@ -2446,7 +2446,7 @@ class Player : public Unit, public GridObject<Player>
void _LoadAuras(PreparedQueryResult result, uint32 timediff); void _LoadAuras(PreparedQueryResult result, uint32 timediff);
void _LoadGlyphAuras(); void _LoadGlyphAuras();
void _LoadBoundInstances(PreparedQueryResult result); void _LoadBoundInstances(PreparedQueryResult result);
void _LoadInventory(PreparedQueryResult result, uint32 timediff); void _LoadInventory(PreparedQueryResult result, uint32 timeDiff);
void _LoadMailInit(PreparedQueryResult resultUnread, PreparedQueryResult resultDelivery); void _LoadMailInit(PreparedQueryResult resultUnread, PreparedQueryResult resultDelivery);
void _LoadMail(); void _LoadMail();
void _LoadMailedItems(Mail *mail); void _LoadMailedItems(Mail *mail);
@@ -2662,6 +2662,7 @@ class Player : public Unit, public GridObject<Player>
uint8 _CanStoreItem_InBag(uint8 bag, ItemPosCountVec& dest, ItemPrototype const *pProto, uint32& count, bool merge, bool non_specialized, Item *pSrcItem, uint8 skip_bag, uint8 skip_slot) const; uint8 _CanStoreItem_InBag(uint8 bag, ItemPosCountVec& dest, ItemPrototype const *pProto, uint32& count, bool merge, bool non_specialized, Item *pSrcItem, uint8 skip_bag, uint8 skip_slot) const;
uint8 _CanStoreItem_InInventorySlots(uint8 slot_begin, uint8 slot_end, ItemPosCountVec& dest, ItemPrototype const *pProto, uint32& count, bool merge, Item *pSrcItem, uint8 skip_bag, uint8 skip_slot) const; uint8 _CanStoreItem_InInventorySlots(uint8 slot_begin, uint8 slot_end, ItemPosCountVec& dest, ItemPrototype const *pProto, uint32& count, bool merge, Item *pSrcItem, uint8 skip_bag, uint8 skip_slot) const;
Item* _StoreItem(uint16 pos, Item *pItem, uint32 count, bool clone, bool update); Item* _StoreItem(uint16 pos, Item *pItem, uint32 count, bool clone, bool update);
Item* _LoadItem(SQLTransaction& trans, uint32 zoneId, uint32 timeDiff, Field* fields);
std::set<uint32> m_refundableItems; std::set<uint32> m_refundableItems;
void SendRefundInfo(Item* item); void SendRefundInfo(Item* item);