aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/game/Entities/Item/Item.cpp196
-rw-r--r--src/server/game/Entities/Item/Item.h9
-rw-r--r--src/server/game/Entities/Player/Player.cpp21
-rw-r--r--src/server/game/Handlers/LootHandler.cpp16
-rw-r--r--src/server/game/Loot/LootMgr.cpp41
-rw-r--r--src/server/game/Loot/LootMgr.h16
-rw-r--r--src/server/shared/Database/Implementation/CharacterDatabase.cpp9
-rw-r--r--src/server/shared/Database/Implementation/CharacterDatabase.h8
8 files changed, 311 insertions, 5 deletions
diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp
index bdaf11ad9b8..5303fb8dc38 100644
--- a/src/server/game/Entities/Item/Item.cpp
+++ b/src/server/game/Entities/Item/Item.cpp
@@ -378,6 +378,10 @@ void Item::SaveToDB(SQLTransaction& trans)
if (!isInTransaction)
CharacterDatabase.CommitTransaction(trans);
+ // Delete the items if this is a container
+ if (!loot.isLooted())
+ ItemContainerDeleteLootMoneyAndLootItemsFromDB();
+
delete this;
return;
}
@@ -483,6 +487,10 @@ void Item::DeleteFromDB(SQLTransaction& trans, uint32 itemGuid)
void Item::DeleteFromDB(SQLTransaction& trans)
{
DeleteFromDB(trans, GetGUIDLow());
+
+ // Delete the items if this is a container
+ if (!loot.isLooted())
+ ItemContainerDeleteLootMoneyAndLootItemsFromDB();
}
/*static*/
@@ -1198,3 +1206,191 @@ bool Item::CheckSoulboundTradeExpire()
return false;
}
+
+void Item::ItemContainerSaveLootToDB()
+{
+ // Saves the money and item loot associated with an openable item to the DB
+
+ if (loot.isLooted()) // no money and no loot
+ return;
+
+ uint32 container_id = GetGUIDLow();
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+
+ loot.containerID = container_id; // Save this for when a LootItem is removed
+
+ // Save money
+ if (loot.gold > 0)
+ {
+ PreparedStatement* stmt_money = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY);
+ stmt_money->setUInt32(0, container_id);
+ trans->Append(stmt_money);
+
+ stmt_money = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_MONEY);
+ stmt_money->setUInt32(0, container_id);
+ stmt_money->setUInt32(1, loot.gold);
+ trans->Append(stmt_money);
+ }
+
+ // Save items
+ if (!loot.isLooted())
+ {
+
+ PreparedStatement* stmt_items = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS);
+ stmt_items->setUInt32(0, container_id);
+ trans->Append(stmt_items);
+
+ // Now insert the items
+ for (LootItemList::const_iterator _li = loot.items.begin(); _li != loot.items.end(); _li++)
+ {
+ // When an item is looted, it doesn't get removed from the items collection
+ // but we don't want to resave it.
+ if (!_li->canSave)
+ continue;
+
+ stmt_items = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_ITEMS);
+
+ // container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
+ stmt_items->setUInt32(0, container_id);
+ stmt_items->setUInt32(1, _li->itemid);
+ stmt_items->setUInt32(2, _li->count);
+ stmt_items->setBool(3, _li->follow_loot_rules);
+ stmt_items->setBool(4, _li->freeforall);
+ stmt_items->setBool(5, _li->is_blocked);
+ stmt_items->setBool(6, _li->is_counted);
+ stmt_items->setBool(7, _li->is_underthreshold);
+ stmt_items->setBool(8, _li->needs_quest);
+ stmt_items->setUInt32(9, _li->randomPropertyId);
+ stmt_items->setUInt32(10, _li->randomSuffix);
+ trans->Append(stmt_items);
+ }
+ }
+
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+bool Item::ItemContainerLoadLootFromDB()
+{
+ // Loads the money and item loot associated with an openable item from the DB
+
+ // Default. If there are no records for this item then it will be rolled for in Player::SendLoot()
+ m_lootGenerated = false;
+
+ uint32 container_id = GetGUIDLow();
+
+ // Save this for later use
+ loot.containerID = container_id;
+
+ // First, see if there was any money loot. This gets added directly to the container.
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_MONEY);
+ stmt->setUInt32(0, container_id);
+ PreparedQueryResult money_result = CharacterDatabase.Query(stmt);
+
+ if (money_result)
+ {
+ Field* fields = money_result->Fetch();
+ loot.gold = fields[0].GetUInt32();
+ }
+
+ // Next, load any items that were saved
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_ITEMS);
+ stmt->setUInt32(0, container_id);
+ PreparedQueryResult item_result = CharacterDatabase.Query(stmt);
+
+ if (item_result)
+ {
+ // Get a LootTemplate for the container item. This is where
+ // the saved loot was originally rolled from, we will copy conditions from it
+ LootTemplate const* lt = LootTemplates_Item.GetLootFor(GetEntry());
+
+ if (lt)
+ {
+ do
+ {
+ // Create an empty LootItem
+ LootItem loot_item = LootItem();
+
+ // Fill in the rest of the LootItem from the DB
+ Field* fields = item_result->Fetch();
+
+ // item_id, itm_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
+ loot_item.itemid = fields[0].GetUInt32();
+ loot_item.count = fields[1].GetUInt32();
+ loot_item.follow_loot_rules = fields[2].GetBool();
+ loot_item.freeforall = fields[3].GetBool();
+ loot_item.is_blocked = fields[4].GetBool();
+ loot_item.is_counted = fields[5].GetBool();
+ loot_item.canSave = true;
+ loot_item.is_underthreshold = fields[6].GetBool();
+ loot_item.needs_quest = fields[7].GetBool();
+ loot_item.randomPropertyId = fields[8].GetUInt32();
+ loot_item.randomSuffix = fields[9].GetUInt32();
+
+ // Copy the extra loot conditions from the item in the loot template
+ lt->CopyConditions(&loot_item);
+
+ // If container item is in a bag, add that player as an allowed looter
+ if (GetBagSlot())
+ loot_item.allowedGUIDs.insert(GetOwner()->GetGUIDLow());
+
+ // Finally add the LootItem to the container
+ loot.items.push_back(loot_item);
+
+ // Increment unlooted count
+ loot.unlootedCount++;
+
+ } while (item_result->NextRow());
+ }
+ }
+
+ // Mark the item if it has loot so it won't be generated again on open
+ m_lootGenerated = !loot.isLooted();
+
+ return m_lootGenerated;
+}
+
+void Item::ItemContainerDeleteLootItemsFromDB()
+{
+ // Deletes items associated with an openable item from the DB
+
+ uint32 containerId = GetGUIDLow();
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS);
+ stmt->setUInt32(0, containerId);
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ trans->Append(stmt);
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+void Item::ItemContainerDeleteLootItemFromDB(uint32 itemID)
+{
+ // Deletes a single item associated with an openable item from the DB
+
+ uint32 containerId = GetGUIDLow();
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM);
+ stmt->setUInt32(0, containerId);
+ stmt->setUInt32(1, itemID);
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ trans->Append(stmt);
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+void Item::ItemContainerDeleteLootMoneyFromDB()
+{
+ // Deletes the money loot associated with an openable item from the DB
+
+ uint32 containerId = GetGUIDLow();
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY);
+ stmt->setUInt32(0, containerId);
+ SQLTransaction trans = CharacterDatabase.BeginTransaction();
+ trans->Append(stmt);
+ CharacterDatabase.CommitTransaction(trans);
+}
+
+void Item::ItemContainerDeleteLootMoneyAndLootItemsFromDB()
+{
+ // Deletes money and items associated with an openable item from the DB
+
+ ItemContainerDeleteLootMoneyFromDB();
+ ItemContainerDeleteLootItemsFromDB();
+}
+
diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h
index a5f9ed5c008..2e1956250f3 100644
--- a/src/server/game/Entities/Item/Item.h
+++ b/src/server/game/Entities/Item/Item.h
@@ -231,6 +231,15 @@ class Item : public Object
static void DeleteFromDB(SQLTransaction& trans, uint32 itemGuid);
virtual void DeleteFromDB(SQLTransaction& trans);
static void DeleteFromInventoryDB(SQLTransaction& trans, uint32 itemGuid);
+
+ // Lootable items and their contents
+ void ItemContainerSaveLootToDB();
+ bool ItemContainerLoadLootFromDB();
+ void ItemContainerDeleteLootItemsFromDB();
+ void ItemContainerDeleteLootItemFromDB(uint32 itemID);
+ void ItemContainerDeleteLootMoneyFromDB();
+ void ItemContainerDeleteLootMoneyAndLootItemsFromDB();
+
void DeleteFromInventoryDB(SQLTransaction& trans);
void SaveRefundDataToDB();
void DeleteRefundDataFromDB(SQLTransaction* trans);
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 1fa6cbd9308..af7692b87c5 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -8850,7 +8850,9 @@ void Player::SendLoot(uint64 guid, LootType loot_type)
loot = &item->loot;
- if (!item->m_lootGenerated)
+ // If item doesn't already have loot, attempt to load it. If that
+ // fails then this is first time opening, generate loot
+ if (!item->m_lootGenerated && !item->ItemContainerLoadLootFromDB())
{
item->m_lootGenerated = true;
loot->clear();
@@ -8869,6 +8871,12 @@ void Player::SendLoot(uint64 guid, LootType loot_type)
default:
loot->generateMoneyLoot(item->GetTemplate()->MinMoneyLoot, item->GetTemplate()->MaxMoneyLoot);
loot->FillLoot(item->GetEntry(), LootTemplates_Item, this, true, loot->gold != 0);
+
+ // Force save the loot and money items that were just rolled
+ // Also saves the container item ID in Loot struct (not to DB)
+ if (loot->gold > 0 || loot->unlootedCount > 0)
+ item->ItemContainerSaveLootToDB();
+
break;
}
}
@@ -12621,6 +12629,12 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update)
else if (Bag* pBag = GetBagByPos(bag))
pBag->RemoveItem(slot, update);
+ // Delete rolled money / loot from db.
+ // MUST be done before RemoveFromWorld() or GetTemplate() fails
+ if (ItemTemplate const* pTmp = pItem->GetTemplate())
+ if (pTmp->Flags & ITEM_PROTO_FLAG_OPENABLE)
+ pItem->ItemContainerDeleteLootMoneyAndLootItemsFromDB();
+
if (IsInWorld() && update)
{
pItem->RemoveFromWorld();
@@ -24062,6 +24076,11 @@ void Player::StoreLootItem(uint8 lootSlot, Loot* loot)
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, item->itemid, item->count);
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_TYPE, loot->loot_type, item->count);
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_EPIC_ITEM, item->itemid, item->count);
+
+ // LootItem is being removed (looted) from the container, delete it from the DB.
+ if (loot->containerID > 0)
+ loot->DeleteLootItemFromContainerItemDB(item->itemid);
+
}
else
SendEquipError(msg, NULL, NULL, item->itemid);
diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp
index 752eace536b..b15636e75d2 100644
--- a/src/server/game/Handlers/LootHandler.cpp
+++ b/src/server/game/Handlers/LootHandler.cpp
@@ -93,6 +93,10 @@ void WorldSession::HandleAutostoreLootItemOpcode(WorldPacket& recvData)
}
player->StoreLootItem(lootSlot, loot);
+
+ // If player is removing the last LootItem, delete the empty container.
+ if (loot->isLooted() && IS_ITEM_GUID(lguid))
+ player->GetSession()->DoLootRelease(lguid);
}
void WorldSession::HandleLootMoneyOpcode(WorldPacket & /*recvData*/)
@@ -200,6 +204,14 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket & /*recvData*/)
}
loot->gold = 0;
+
+ // Delete the money loot record from the DB
+ if (loot->containerID > 0)
+ loot->DeleteLootMoneyFromContainerItemDB();
+
+ // Delete container if empty
+ if (loot->isLooted() && IS_ITEM_GUID(guid))
+ player->GetSession()->DoLootRelease(guid);
}
}
@@ -381,8 +393,8 @@ void WorldSession::DoLootRelease(uint64 lguid)
player->DestroyItemCount(pItem, count, true);
}
else
- // FIXME: item must not be deleted in case not fully looted state. But this pre-request implement loot saving in DB at item save. Or cheating possible.
- player->DestroyItem(pItem->GetBagSlot(), pItem->GetSlot(), true);
+ if (pItem->loot.isLooted()) // Only delete item if no loot or money (unlooted loot is saved to db)
+ player->DestroyItem(pItem->GetBagSlot(), pItem->GetSlot(), true);
return; // item can be looted only single player
}
else
diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp
index ef3d2b9fbd6..098fcc657b3 100644
--- a/src/server/game/Loot/LootMgr.cpp
+++ b/src/server/game/Loot/LootMgr.cpp
@@ -334,6 +334,7 @@ LootItem::LootItem(LootStoreItem const& li)
is_blocked = 0;
is_underthreshold = 0;
is_counted = 0;
+ canSave = true;
}
// Basic checks for player/item compatibility - if false no chance to see the item in the loot
@@ -654,6 +655,33 @@ void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount)
}
}
+void Loot::DeleteLootItemFromContainerItemDB(uint32 itemID)
+{
+ // Deletes a single item associated with an openable item from the DB
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM);
+ stmt->setUInt32(0, containerID);
+ stmt->setUInt32(1, itemID);
+ CharacterDatabase.Execute(stmt);
+
+ // Mark the item looted to prevent resaving
+ for (LootItemList::iterator _itr = items.begin(); _itr != items.end(); _itr++)
+ {
+ if (!_itr->itemid == itemID)
+ continue;
+
+ _itr->canSave = true;
+ break;
+ }
+}
+
+void Loot::DeleteLootMoneyFromContainerItemDB()
+{
+ // Deletes money loot associated with an openable item from the DB
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY);
+ stmt->setUInt32(0, containerID);
+ CharacterDatabase.Execute(stmt);
+}
+
LootItem* Loot::LootItemInSlot(uint32 lootSlot, Player* player, QuestItem* *qitem, QuestItem* *ffaitem, QuestItem* *conditem)
{
LootItem* item = NULL;
@@ -1239,6 +1267,19 @@ void LootTemplate::CopyConditions(ConditionList conditions)
i->CopyConditions(conditions);
}
+void LootTemplate::CopyConditions(LootItem* li) const
+{
+ // Copies the conditions list from a template item to a LootItem
+ for (LootStoreItemList::const_iterator _iter = Entries.begin(); _iter != Entries.end(); ++_iter)
+ {
+ if (!_iter->itemid == li->itemid)
+ continue;
+
+ li->conditions = _iter->conditions;
+ break;
+ }
+}
+
// Rolls for every item in the template and adds the rolled items the the loot
void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId) const
{
diff --git a/src/server/game/Loot/LootMgr.h b/src/server/game/Loot/LootMgr.h
index 45fc5c7983c..cfa5d370e3b 100644
--- a/src/server/game/Loot/LootMgr.h
+++ b/src/server/game/Loot/LootMgr.h
@@ -141,14 +141,17 @@ struct LootItem
bool is_counted : 1;
bool needs_quest : 1; // quest drop
bool follow_loot_rules : 1;
+ bool canSave;
// Constructor, copies most fields from LootStoreItem, generates random count and random suffixes/properties
// Should be called for non-reference LootStoreItem entries only (mincountOrRef > 0)
explicit LootItem(LootStoreItem const& li);
+ // Empty constructor for creating an empty LootItem to be filled in with DB data
+ LootItem() : canSave(true){};
+
// Basic checks for player/item compatibility - if false no chance to see the item in the loot
bool AllowedForPlayer(Player const* player) const;
-
void AddAllowedLooter(Player const* player);
const AllowedLooterSet & GetAllowedLooters() const { return allowedGUIDs; }
};
@@ -223,6 +226,7 @@ class LootTemplate
// Rolls for every item in the template and adds the rolled items the the loot
void Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId = 0) const;
void CopyConditions(ConditionList conditions);
+ void CopyConditions(LootItem* li) const;
// True if template includes at least 1 quest drop entry
bool HasQuestDrop(LootTemplateMap const& store, uint8 groupId = 0) const;
@@ -286,10 +290,18 @@ struct Loot
uint8 unlootedCount;
uint64 roundRobinPlayer; // GUID of the player having the Round-Robin ownership for the loot. If 0, round robin owner has released.
LootType loot_type; // required for achievement system
+
+ // GUIDLow of container that holds this loot (item_instance.entry)
+ // Only set for inventory items that can be right-click looted
+ uint32 containerID;
- Loot(uint32 _gold = 0) : gold(_gold), unlootedCount(0), loot_type(LOOT_CORPSE) {}
+ Loot(uint32 _gold = 0) : gold(_gold), unlootedCount(0), loot_type(LOOT_CORPSE), containerID(0) {}
~Loot() { clear(); }
+ // For deleting items at loot removal since there is no backward interface to the Item()
+ void DeleteLootItemFromContainerItemDB(uint32 itemID);
+ void DeleteLootMoneyFromContainerItemDB();
+
// if loot becomes invalid this reference is used to inform the listener
void addLootValidatorRef(LootValidatorRef* pLootValidatorRef)
{
diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.cpp b/src/server/shared/Database/Implementation/CharacterDatabase.cpp
index 33f0cab5170..3ef94f3aafd 100644
--- a/src/server/shared/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/shared/Database/Implementation/CharacterDatabase.cpp
@@ -540,6 +540,15 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PREPARE_STATEMENT(CHAR_DEL_CHAR_ACTION_EXCEPT_SPEC, "DELETE FROM character_action WHERE spec<>? AND guid = ?", CONNECTION_ASYNC);
PREPARE_STATEMENT(CHAR_SEL_CHAR_PET_BY_ENTRY_AND_SLOT, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, curhealth, curmana, curhappiness, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? AND slot = ?", CONNECTION_SYNCH);
+ // Items that hold loot or money
+ PREPARE_STATEMENT(CHAR_SEL_ITEMCONTAINER_ITEMS, "SELECT item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix FROM item_loot_items WHERE container_id = ?", CONNECTION_SYNCH);
+ PREPARE_STATEMENT(CHAR_DEL_ITEMCONTAINER_ITEMS, "DELETE FROM item_loot_items WHERE container_id = ?", CONNECTION_ASYNC);
+ PREPARE_STATEMENT(CHAR_DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ?", CONNECTION_ASYNC);
+ PREPARE_STATEMENT(CHAR_INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix) VALUES (?,?,?,?,?,?,?,?,?,?,?)", CONNECTION_ASYNC);
+ PREPARE_STATEMENT(CHAR_SEL_ITEMCONTAINER_MONEY, "SELECT money FROM item_loot_money WHERE container_id = ?", CONNECTION_SYNCH);
+ PREPARE_STATEMENT(CHAR_DEL_ITEMCONTAINER_MONEY, "DELETE FROM item_loot_money WHERE container_id = ?", CONNECTION_ASYNC);
+ PREPARE_STATEMENT(CHAR_INS_ITEMCONTAINER_MONEY," INSERT INTO item_loot_money (container_id, money) VALUES (?,?)", CONNECTION_ASYNC);
+
// Calendar
PREPARE_STATEMENT(CHAR_REP_CALENDAR_EVENT, "REPLACE INTO calendar_events (id, creator, title, description, type, dungeon, eventtime, flags, time2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PREPARE_STATEMENT(CHAR_DEL_CALENDAR_EVENT, "DELETE FROM calendar_events WHERE id = ?", CONNECTION_ASYNC);
diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.h b/src/server/shared/Database/Implementation/CharacterDatabase.h
index 181161978df..d2e69fb0d17 100644
--- a/src/server/shared/Database/Implementation/CharacterDatabase.h
+++ b/src/server/shared/Database/Implementation/CharacterDatabase.h
@@ -505,6 +505,14 @@ enum CharacterDatabaseStatements
CHAR_REP_CALENDAR_INVITE,
CHAR_DEL_CALENDAR_INVITE,
+ CHAR_SEL_ITEMCONTAINER_ITEMS,
+ CHAR_DEL_ITEMCONTAINER_ITEMS,
+ CHAR_DEL_ITEMCONTAINER_ITEM,
+ CHAR_INS_ITEMCONTAINER_ITEMS,
+ CHAR_SEL_ITEMCONTAINER_MONEY,
+ CHAR_DEL_ITEMCONTAINER_MONEY,
+ CHAR_INS_ITEMCONTAINER_MONEY,
+
MAX_CHARACTERDATABASE_STATEMENTS
};