From d1b09205a20b37971b3dea5e809fce1aa31242f5 Mon Sep 17 00:00:00 2001
From: Vincent-Michael <Vincent_Michael@gmx.de>
Date: Tue, 4 Dec 2012 09:40:35 +0100
Subject: SQL: Update characters_database.sql

---
 sql/base/characters_database.sql | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

(limited to 'sql/base/characters_database.sql')

diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql
index 02efbe77101..d8cdd409bf7 100644
--- a/sql/base/characters_database.sql
+++ b/sql/base/characters_database.sql
@@ -934,6 +934,30 @@ LOCK TABLES `character_queststatus_seasonal` WRITE;
 /*!40000 ALTER TABLE `character_queststatus_seasonal` ENABLE KEYS */;
 UNLOCK TABLES;
 
+--
+-- Table structure for table `character_queststatus_monthly`
+--
+
+DROP TABLE IF EXISTS `character_queststatus_monthly`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `character_queststatus_monthly` (
+  `guid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Global Unique Identifier',
+  `quest` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Quest Identifier',
+  PRIMARY KEY (`guid`,`quest`),
+  KEY `idx_guid` (`guid`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Player System';
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `character_queststatus_monthly`
+--
+
+LOCK TABLES `character_queststatus_monthly` WRITE;
+/*!40000 ALTER TABLE `character_queststatus_monthly` DISABLE KEYS */;
+/*!40000 ALTER TABLE `character_queststatus_monthly` ENABLE KEYS */;
+UNLOCK TABLES;
+
 --
 -- Table structure for table `character_queststatus_weekly`
 --
-- 
cgit v1.2.3


From 04f08d26a72ab29b11fc2aaef65bc54078cc2086 Mon Sep 17 00:00:00 2001
From: MrSmite <mrsmite@att.net>
Date: Fri, 14 Dec 2012 00:46:36 -0500
Subject: Implements saving of loot (items / money) contained inside lootable
 inventory items.

* Unlooted items / money persist across player sessions
* Loot inside items is tied to the item rather than the player so if trading
  partially looted items becomes possible, this implementation will still work

* New tables added:

    characters_database.sql (first time users)
    characters_create_item_loot.sql (existing users)

Implementation Can be tested with:

 Watertight Trunk [21113]
 Bulging Sack of Gems [25422]
 Fat Sack of Coins [11937]

Closes #2048
---
 sql/base/characters_database.sql                   |  35 ++++
 .../characters/characters_create_item_loot.sql     |  52 ++++++
 src/server/game/Entities/Item/Item.cpp             | 196 +++++++++++++++++++++
 src/server/game/Entities/Item/Item.h               |   9 +
 src/server/game/Entities/Player/Player.cpp         |  21 ++-
 src/server/game/Handlers/LootHandler.cpp           |  16 +-
 src/server/game/Loot/LootMgr.cpp                   |  41 +++++
 src/server/game/Loot/LootMgr.h                     |  16 +-
 .../Database/Implementation/CharacterDatabase.cpp  |   9 +
 .../Database/Implementation/CharacterDatabase.h    |   8 +
 10 files changed, 398 insertions(+), 5 deletions(-)
 create mode 100644 sql/updates/characters/characters_create_item_loot.sql

(limited to 'sql/base/characters_database.sql')

diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql
index d8cdd409bf7..9e16db1f381 100644
--- a/sql/base/characters_database.sql
+++ b/sql/base/characters_database.sql
@@ -1954,6 +1954,41 @@ LOCK TABLES `item_instance` WRITE;
 /*!40000 ALTER TABLE `item_instance` ENABLE KEYS */;
 UNLOCK TABLES;
 
+--
+-- Table structure for table `item_loot_items`
+--
+
+DROP TABLE IF EXISTS `item_loot_items`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `item_loot_items` (
+  `container_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)',
+  `item_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'loot item entry (item_instance.itemEntry)',
+  `item_count` int(10) NOT NULL DEFAULT '0' COMMENT 'stack size',
+  `follow_rules` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'follow loot rules',
+  `ffa` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'free-for-all',
+  `blocked` tinyint(1) NOT NULL DEFAULT '0',
+  `counted` tinyint(1) NOT NULL DEFAULT '0',
+  `under_threshold` tinyint(1) NOT NULL DEFAULT '0',
+  `needs_quest` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'quest drop',
+  `rnd_prop` int(10) NOT NULL DEFAULT '0' COMMENT 'random enchantment added when originally rolled',
+  `rnd_suffix` int(10) NOT NULL DEFAULT '0' COMMENT 'random suffix added when originally rolled'
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `item_loot_money`
+--
+
+DROP TABLE IF EXISTS `item_loot_money`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `item_loot_money` (
+  `container_id` int(10) NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)',
+  `money` int(10) NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)'
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
 --
 -- Table structure for table `item_refund_instance`
 --
diff --git a/sql/updates/characters/characters_create_item_loot.sql b/sql/updates/characters/characters_create_item_loot.sql
new file mode 100644
index 00000000000..3c1529e8bdf
--- /dev/null
+++ b/sql/updates/characters/characters_create_item_loot.sql
@@ -0,0 +1,52 @@
+/*
+SQLyog Ultimate v9.63 
+MySQL - 5.5.21 : Database - characters
+*********************************************************************
+*/
+
+/*!40101 SET NAMES utf8 */;
+
+/*!40101 SET SQL_MODE=''*/;
+
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `item_loot_items`
+--
+
+DROP TABLE IF EXISTS `item_loot_items`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `item_loot_items` (
+  `container_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)',
+  `item_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'loot item entry (item_instance.itemEntry)',
+  `item_count` int(10) NOT NULL DEFAULT '0' COMMENT 'stack size',
+  `follow_rules` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'follow loot rules',
+  `ffa` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'free-for-all',
+  `blocked` tinyint(1) NOT NULL DEFAULT '0',
+  `counted` tinyint(1) NOT NULL DEFAULT '0',
+  `under_threshold` tinyint(1) NOT NULL DEFAULT '0',
+  `needs_quest` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'quest drop',
+  `rnd_prop` int(10) NOT NULL DEFAULT '0' COMMENT 'random enchantment added when originally rolled',
+  `rnd_suffix` int(10) NOT NULL DEFAULT '0' COMMENT 'random suffix added when originally rolled'
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `item_loot_money`
+--
+
+DROP TABLE IF EXISTS `item_loot_money`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `item_loot_money` (
+  `container_id` int(10) NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)',
+  `money` int(10) NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)'
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
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 cf573ec8c5a..34d132f6dce 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -8903,7 +8903,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();
@@ -8922,6 +8924,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;
             }
         }
@@ -12675,6 +12683,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();
@@ -24055,6 +24069,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
 };
 
-- 
cgit v1.2.3

