diff options
-rw-r--r-- | sql/base/characters_database.sql | 5 | ||||
-rw-r--r-- | sql/updates/characters/3.3.5/2017_04_12_00_characters.sql | 3 | ||||
-rw-r--r-- | src/server/database/Database/Implementation/CharacterDatabase.cpp | 6 | ||||
-rw-r--r-- | src/server/game/Entities/Item/Item.cpp | 193 | ||||
-rw-r--r-- | src/server/game/Entities/Item/Item.h | 8 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 43 | ||||
-rw-r--r-- | src/server/game/Handlers/LootHandler.cpp | 3 | ||||
-rw-r--r-- | src/server/game/Loot/LootItemStorage.cpp | 353 | ||||
-rw-r--r-- | src/server/game/Loot/LootItemStorage.h | 93 | ||||
-rw-r--r-- | src/server/game/Loot/LootMgr.cpp | 28 | ||||
-rw-r--r-- | src/server/game/Loot/LootMgr.h | 9 | ||||
-rw-r--r-- | src/server/game/World/World.cpp | 4 |
12 files changed, 498 insertions, 250 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index f2b494ff947..9e02a5cada7 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -2093,7 +2093,8 @@ DROP TABLE IF EXISTS `item_loot_money`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `item_loot_money` ( `container_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)', - `money` int(10) NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)' + `money` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)', + PRIMARY KEY (`container_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -2567,7 +2568,7 @@ CREATE TABLE `updates` ( LOCK TABLES `updates` WRITE; /*!40000 ALTER TABLE `updates` DISABLE KEYS */; -INSERT INTO `updates` VALUES ('2015_03_20_00_characters.sql','B761760804EA73BD297F296C5C1919687DF7191C','ARCHIVED','2015-03-21 21:44:15',0),('2015_03_20_01_characters.sql','894F08B70449A5481FFAF394EE5571D7FC4D8A3A','ARCHIVED','2015-03-21 21:44:15',0),('2015_03_20_02_characters.sql','97D7BE0CAADC79F3F11B9FD296B8C6CD40FE593B','ARCHIVED','2015-03-21 21:44:51',0),('2015_06_26_00_characters_335.sql','C2CC6E50AFA1ACCBEBF77CC519AAEB09F3BBAEBC','ARCHIVED','2015-07-13 23:49:22',0),('2015_09_28_00_characters_335.sql','F8682A431D50E54BDC4AC0E7DBED21AE8AAB6AD4','ARCHIVED','2015-09-28 21:00:00',0),('2015_08_26_00_characters_335.sql','C7D6A3A00FECA3EBFF1E71744CA40D3076582374','ARCHIVED','2015-08-26 21:00:00',0),('2015_10_06_00_characters.sql','16842FDD7E8547F2260D3312F53EFF8761EFAB35','ARCHIVED','2015-10-06 16:06:38',0),('2015_10_07_00_characters.sql','E15AB463CEBE321001D7BFDEA4B662FF618728FD','ARCHIVED','2015-10-07 23:32:00',0),('2015_10_12_00_characters.sql','D6F9927BDED72AD0A81D6EC2C6500CBC34A39FA2','ARCHIVED','2015-10-12 15:35:47',0),('2015_10_28_00_characters.sql','622A9CA8FCE690429EBE23BA071A37C7A007BF8B','ARCHIVED','2015-10-19 14:32:22',0),('2015_10_29_00_characters_335.sql','4555A7F35C107E54C13D74D20F141039ED42943E','ARCHIVED','2015-10-29 17:05:43',0),('2015_11_03_00_characters.sql','CC045717B8FDD9733351E52A5302560CD08AAD57','ARCHIVED','2015-10-12 15:23:33',0),('2015_11_07_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','ARCHIVED','2016-04-11 00:42:36',94),('2016_02_10_00_characters.sql','F1B4DA202819CABC7319A4470A2D224A34609E97','ARCHIVED','2016-02-10 00:00:00',0),('2016_03_13_2016_01_05_00_characters.sql','0EAD24977F40DE2476B4567DA2B477867CC0DA1A','ARCHIVED','2016-03-13 20:03:56',0),('2016_04_11_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','ARCHIVED','2016-04-11 03:18:17',0),('2016_09_13_00_characters.sql','27A04615B11B2CFC3A26778F52F74C071E4F9C54','ARCHIVED','2016-07-06 18:55:18',0),('2016_10_16_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','ARCHIVED','2016-10-16 14:02:49',35),('2016_10_30_00_characters.sql','7E2D5B226907B5A9AF320797F46E86DC27B7EC90','ARCHIVED','2016-10-30 00:00:00',0),('2017_04_03_00_characters.sql','CB072C56692C9FBF170C4036F15773DD86D368B5','RELEASED','2017-04-03 00:00:00',0); +INSERT INTO `updates` VALUES ('2015_03_20_00_characters.sql','B761760804EA73BD297F296C5C1919687DF7191C','ARCHIVED','2015-03-21 21:44:15',0),('2015_03_20_01_characters.sql','894F08B70449A5481FFAF394EE5571D7FC4D8A3A','ARCHIVED','2015-03-21 21:44:15',0),('2015_03_20_02_characters.sql','97D7BE0CAADC79F3F11B9FD296B8C6CD40FE593B','ARCHIVED','2015-03-21 21:44:51',0),('2015_06_26_00_characters_335.sql','C2CC6E50AFA1ACCBEBF77CC519AAEB09F3BBAEBC','ARCHIVED','2015-07-13 23:49:22',0),('2015_09_28_00_characters_335.sql','F8682A431D50E54BDC4AC0E7DBED21AE8AAB6AD4','ARCHIVED','2015-09-28 21:00:00',0),('2015_08_26_00_characters_335.sql','C7D6A3A00FECA3EBFF1E71744CA40D3076582374','ARCHIVED','2015-08-26 21:00:00',0),('2015_10_06_00_characters.sql','16842FDD7E8547F2260D3312F53EFF8761EFAB35','ARCHIVED','2015-10-06 16:06:38',0),('2015_10_07_00_characters.sql','E15AB463CEBE321001D7BFDEA4B662FF618728FD','ARCHIVED','2015-10-07 23:32:00',0),('2015_10_12_00_characters.sql','D6F9927BDED72AD0A81D6EC2C6500CBC34A39FA2','ARCHIVED','2015-10-12 15:35:47',0),('2015_10_28_00_characters.sql','622A9CA8FCE690429EBE23BA071A37C7A007BF8B','ARCHIVED','2015-10-19 14:32:22',0),('2015_10_29_00_characters_335.sql','4555A7F35C107E54C13D74D20F141039ED42943E','ARCHIVED','2015-10-29 17:05:43',0),('2015_11_03_00_characters.sql','CC045717B8FDD9733351E52A5302560CD08AAD57','ARCHIVED','2015-10-12 15:23:33',0),('2015_11_07_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','ARCHIVED','2016-04-11 00:42:36',94),('2016_02_10_00_characters.sql','F1B4DA202819CABC7319A4470A2D224A34609E97','ARCHIVED','2016-02-10 00:00:00',0),('2016_03_13_2016_01_05_00_characters.sql','0EAD24977F40DE2476B4567DA2B477867CC0DA1A','ARCHIVED','2016-03-13 20:03:56',0),('2016_04_11_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','ARCHIVED','2016-04-11 03:18:17',0),('2016_09_13_00_characters.sql','27A04615B11B2CFC3A26778F52F74C071E4F9C54','ARCHIVED','2016-07-06 18:55:18',0),('2016_10_16_00_characters.sql','0ACDD35EC9745231BCFA701B78056DEF94D0CC53','ARCHIVED','2016-10-16 14:02:49',35),('2016_10_30_00_characters.sql','7E2D5B226907B5A9AF320797F46E86DC27B7EC90','ARCHIVED','2016-10-30 00:00:00',0),('2017_04_03_00_characters.sql','CB072C56692C9FBF170C4036F15773DD86D368B5','RELEASED','2017-04-03 00:00:00',0),('2017_04_12_00_characters.sql','4FE3C6866A6DCD4926D451F6009464D290C2EF1F','RELEASED','2017-04-12 00:00:00',0); /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/characters/3.3.5/2017_04_12_00_characters.sql b/sql/updates/characters/3.3.5/2017_04_12_00_characters.sql new file mode 100644 index 00000000000..117e3baa78d --- /dev/null +++ b/sql/updates/characters/3.3.5/2017_04_12_00_characters.sql @@ -0,0 +1,3 @@ +ALTER TABLE `item_loot_money` +CHANGE `money` `money` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)', +ADD PRIMARY KEY (`container_id`); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 80be3a59bee..819ff8ecc62 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -543,11 +543,11 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?", CONNECTION_ASYNC); // Items that hold loot or money - PrepareStatement(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); + PrepareStatement(CHAR_SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix FROM item_loot_items", CONNECTION_SYNCH); PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEMS, "DELETE FROM item_loot_items WHERE container_id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ? AND item_count = ?", CONNECTION_ASYNC); PrepareStatement(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); - PrepareStatement(CHAR_SEL_ITEMCONTAINER_MONEY, "SELECT money FROM item_loot_money WHERE container_id = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_ITEMCONTAINER_MONEY, "SELECT container_id, money FROM item_loot_money", CONNECTION_SYNCH); PrepareStatement(CHAR_DEL_ITEMCONTAINER_MONEY, "DELETE FROM item_loot_money WHERE container_id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_ITEMCONTAINER_MONEY, "INSERT INTO item_loot_money (container_id, money) VALUES (?, ?)", CONNECTION_ASYNC); diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index e19f8ddc881..6ea1665ce35 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -22,6 +22,7 @@ #include "WorldPacket.h" #include "DatabaseEnv.h" #include "ItemEnchantmentMgr.h" +#include "LootItemStorage.h" #include "SpellMgr.h" #include "SpellInfo.h" #include "ScriptMgr.h" @@ -384,7 +385,7 @@ void Item::SaveToDB(SQLTransaction& trans) // Delete the items if this is a container if (!loot.isLooted()) - ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + sLootItemStorage->RemoveStoredLootForContainer(GetGUID().GetCounter()); delete this; return; @@ -495,7 +496,7 @@ void Item::DeleteFromDB(SQLTransaction& trans) // Delete the items if this is a container if (!loot.isLooted()) - ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + sLootItemStorage->RemoveStoredLootForContainer(GetGUID().GetCounter()); } /*static*/ @@ -1220,194 +1221,6 @@ 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; - - ObjectGuid::LowType container_id = GetGUID().GetCounter(); - 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; - // Conditions are not checked when loot is generated, it is checked when loot is sent to a player. - // For items that are lootable, loot is saved to the DB immediately, that means that loot can be - // saved to the DB that the player never should have gotten. This check prevents that, so that only - // items that the player should get in loot are in the DB. - // IE: Horde items are not saved to the DB for Ally players. - Player* const guid = GetOwner(); - if (!_li->AllowedForPlayer(guid)) - 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->setInt32(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; - - ObjectGuid::LowType container_id = GetGUID().GetCounter(); - - // 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].GetInt32(); - 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.AddAllowedLooter(GetOwner()); - - // 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 - ObjectGuid::LowType containerId = GetGUID().GetCounter(); - 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 - ObjectGuid::LowType containerId = GetGUID().GetCounter(); - 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 - ObjectGuid::LowType containerId = GetGUID().GetCounter(); - 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(); -} - void Item::SetCount(uint32 value) { SetUInt32Value(ITEM_FIELD_STACK_COUNT, value); diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index d501ceb6299..421d13b9aa1 100644 --- a/src/server/game/Entities/Item/Item.h +++ b/src/server/game/Entities/Item/Item.h @@ -230,14 +230,6 @@ class TC_GAME_API Item : public Object virtual void DeleteFromDB(SQLTransaction& trans); static void DeleteFromInventoryDB(SQLTransaction& trans, ObjectGuid::LowType 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 2eb6e1cbe5a..6f2ba8dcdd8 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -54,6 +54,7 @@ #include "LFGMgr.h" #include "Language.h" #include "Log.h" +#include "LootItemStorage.h" #include "MapManager.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" @@ -8624,9 +8625,12 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) loot = &item->loot; + // Store container id + loot->containerID = item->GetGUID().GetCounter(); + // 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()) + // fails then this is first time opening, generate loot + if (!item->m_lootGenerated && !sLootItemStorage->LoadStoredLoot(item, this)) { item->m_lootGenerated = true; loot->clear(); @@ -8649,7 +8653,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) // 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(); + sLootItemStorage->AddNewStoredLoot(loot, this); break; } @@ -12492,6 +12496,7 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update) ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount()); sScriptMgr->OnItemRemove(this, pItem); + ItemTemplate const* pProto = pItem->GetTemplate(); if (bag == INVENTORY_SLOT_BAG_0) { SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid::Empty); @@ -12499,7 +12504,6 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update) // equipment and equipped bags can have applied bonuses if (slot < INVENTORY_SLOT_BAG_END) { - ItemTemplate const* pProto = pItem->GetTemplate(); // item set bonuses applied only at equip and removed at unequip, and still active for broken items if (pProto && pProto->ItemSet) @@ -12537,9 +12541,8 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool 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_FLAG_HAS_LOOT) - pItem->ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + if (pProto->Flags & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter()); if (IsInWorld() && update) { @@ -13291,7 +13294,7 @@ void Player::SwapItem(uint16 src, uint16 dst) { if (Item* bagItem = bag->GetItemByPos(i)) { - if (bagItem->m_lootGenerated) + if (bagItem->GetGUID() == GetLootGUID()) { m_session->DoLootRelease(GetLootGUID()); released = true; // so we don't need to look at dstBag @@ -13308,7 +13311,7 @@ void Player::SwapItem(uint16 src, uint16 dst) { if (Item* bagItem = bag->GetItemByPos(i)) { - if (bagItem->m_lootGenerated) + if (bagItem->GetGUID() == GetLootGUID()) { m_session->DoLootRelease(GetLootGUID()); break; @@ -13396,7 +13399,12 @@ void Player::RemoveItemFromBuyBackSlot(uint32 slot, bool del) { pItem->RemoveFromWorld(); if (del) + { pItem->SetState(ITEM_REMOVED, this); + if (ItemTemplate const* itemTemplate = pItem->GetTemplate()) + if (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter()); + } } m_items[slot] = nullptr; @@ -19662,8 +19670,17 @@ void Player::_SaveInventory(SQLTransaction& trans) for (uint8 i = BUYBACK_SLOT_START; i < BUYBACK_SLOT_END; ++i) { Item* item = m_items[i]; - if (!item || item->GetState() == ITEM_NEW) + if (!item) continue; + + if (item->GetState() == ITEM_NEW) + { + if (ItemTemplate const* itemTemplate = item->GetTemplate()) + if (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter()); + + continue; + } stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); stmt->setUInt32(0, item->GetGUID().GetCounter()); @@ -19673,6 +19690,10 @@ void Player::_SaveInventory(SQLTransaction& trans) stmt->setUInt32(0, item->GetGUID().GetCounter()); trans->Append(stmt); m_items[i]->FSetState(ITEM_NEW); + + if (ItemTemplate const* itemTemplate = item->GetTemplate()) + if (itemTemplate->Flags & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter()); } // Updated played time for refundable items. We don't do this in Player::Update because there's simply no need for it, @@ -24720,7 +24741,7 @@ void Player::StoreLootItem(uint8 lootSlot, Loot* loot) // LootItem is being removed (looted) from the container, delete it from the DB. if (loot->containerID > 0) - loot->DeleteLootItemFromContainerItemDB(item->itemid); + sLootItemStorage->RemoveStoredLootItemForContainer(loot->containerID, item->itemid, item->count); } else diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index 1e740b33bb9..4a0013b71c1 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -22,6 +22,7 @@ #include "Creature.h" #include "GameObject.h" #include "Group.h" +#include "LootItemStorage.h" #include "LootMgr.h" #include "ObjectAccessor.h" #include "Object.h" @@ -206,7 +207,7 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/) // Delete the money loot record from the DB if (loot->containerID > 0) - loot->DeleteLootMoneyFromContainerItemDB(); + sLootItemStorage->RemoveStoredMoneyForContainer(loot->containerID); // Delete container if empty if (loot->isLooted() && guid.IsItem()) diff --git a/src/server/game/Loot/LootItemStorage.cpp b/src/server/game/Loot/LootItemStorage.cpp new file mode 100644 index 00000000000..45f1027932b --- /dev/null +++ b/src/server/game/Loot/LootItemStorage.cpp @@ -0,0 +1,353 @@ +/* +* Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* +* You should have received a copy of the GNU General Public License along +* with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "LootItemStorage.h" +#include "ItemTemplate.h" +#include "Log.h" +#include "LootMgr.h" +#include "ObjectMgr.h" +#include "Player.h" + +#include <boost/thread/shared_mutex.hpp> +#include <boost/thread/locks.hpp> + +#include <unordered_map> + +namespace +{ + std::unordered_map<uint32, StoredLootContainer> _lootItemStore; +} + +StoredLootItem::StoredLootItem(LootItem const& lootItem) : ItemId(lootItem.itemid), Count(lootItem.count), FollowRules(lootItem.follow_loot_rules), +FFA(lootItem.freeforall), Blocked(lootItem.is_blocked), Counted(lootItem.is_counted), UnderThreshold(lootItem.is_underthreshold), +NeedsQuest(lootItem.needs_quest), RandomPropertyId(lootItem.randomPropertyId), RandomSuffix(lootItem.randomSuffix) +{ +} + +LootItemStorage* LootItemStorage::instance() +{ + static LootItemStorage instance; + return &instance; +} + +boost::shared_mutex* LootItemStorage::GetLock() +{ + static boost::shared_mutex _lock; + return &_lock; +} + +void LootItemStorage::LoadStorageFromDB() +{ + uint32 oldMSTime = getMSTime(); + _lootItemStore.clear(); + uint32 count = 0; + + SQLTransaction trans = SQLTransaction(nullptr); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_ITEMS); + PreparedQueryResult result = CharacterDatabase.Query(stmt); + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 key = fields[0].GetUInt32(); + auto itr = _lootItemStore.find(key); + if (itr == _lootItemStore.end()) + { + bool added; + std::tie(itr, added) = _lootItemStore.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(key)); + + ASSERT(added); + } + + StoredLootContainer& storedContainer = itr->second; + + LootItem lootItem; + lootItem.itemid = fields[1].GetUInt32(); + lootItem.count = fields[2].GetUInt32(); + lootItem.follow_loot_rules = fields[3].GetBool(); + lootItem.freeforall = fields[4].GetBool(); + lootItem.is_blocked = fields[5].GetBool(); + lootItem.is_counted = fields[6].GetBool(); + lootItem.is_underthreshold = fields[7].GetBool(); + lootItem.needs_quest = fields[8].GetBool(); + lootItem.randomPropertyId = fields[9].GetInt32(); + lootItem.randomSuffix = fields[10].GetUInt32(); + + storedContainer.AddLootItem(lootItem, trans); + + ++count; + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u stored item loots in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + } + else + TC_LOG_INFO("server.loading", ">> Loaded 0 stored item loots"); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_MONEY); + result = CharacterDatabase.Query(stmt); + if (result) + { + count = 0; + do + { + Field* fields = result->Fetch(); + + uint32 key = fields[0].GetUInt32(); + auto itr = _lootItemStore.find(key); + if (itr == _lootItemStore.end()) + { + bool added; + std::tie(itr, added) = _lootItemStore.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(key)); + + ASSERT(added); + } + + StoredLootContainer& storedContainer = itr->second; + storedContainer.AddMoney(fields[1].GetUInt32(), trans); + + ++count; + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u stored item money in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + } + else + TC_LOG_INFO("server.loading", ">> Loaded 0 stored item money"); +} + +bool LootItemStorage::LoadStoredLoot(Item* item, Player* player) +{ + Loot* loot = &item->loot; + StoredLootContainer const* container = nullptr; + + // read + { + boost::shared_lock<boost::shared_mutex> lock(*GetLock()); + + auto itr = _lootItemStore.find(loot->containerID); + if (itr == _lootItemStore.end()) + return false; + + container = &itr->second; + } + + // container is never null at this point + loot->gold = container->GetMoney(); + + if (LootTemplate const* lt = LootTemplates_Item.GetLootFor(item->GetEntry())) + { + for (auto const& storedItemPair : container->GetLootItems()) + { + LootItem li; + li.itemid = storedItemPair.first; + li.count = storedItemPair.second.Count; + li.follow_loot_rules = storedItemPair.second.FollowRules; + li.freeforall = storedItemPair.second.FFA; + li.is_blocked = storedItemPair.second.Blocked; + li.is_counted = storedItemPair.second.Counted; + li.is_underthreshold = storedItemPair.second.UnderThreshold; + li.needs_quest = storedItemPair.second.NeedsQuest; + li.randomPropertyId = storedItemPair.second.RandomPropertyId; + li.randomSuffix = storedItemPair.second.RandomSuffix; + + // Copy the extra loot conditions from the item in the loot template + lt->CopyConditions(&li); + + // If container item is in a bag, add that player as an allowed looter + if (item->GetBagSlot()) + li.AddAllowedLooter(player); + + // Finally add the LootItem to the container + loot->items.push_back(li); + + // Increment unlooted count + ++loot->unlootedCount; + } + } + + // Mark the item if it has loot so it won't be generated again on open + item->m_lootGenerated = true; + return true; +} + +void LootItemStorage::RemoveStoredMoneyForContainer(uint32 containerId) +{ + // write + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + + auto itr = _lootItemStore.find(containerId); + if (itr == _lootItemStore.end()) + return; + + itr->second.RemoveMoney(); +} + +void LootItemStorage::RemoveStoredLootForContainer(uint32 containerId) +{ + // write + { + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + _lootItemStore.erase(containerId); + } + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); + stmt->setUInt32(0, containerId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt32(0, containerId); + trans->Append(stmt); + + CharacterDatabase.CommitTransaction(trans); +} + +void LootItemStorage::RemoveStoredLootItemForContainer(uint32 containerId, uint32 itemId, uint32 count) +{ + // write + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + + auto itr = _lootItemStore.find(containerId); + if (itr == _lootItemStore.end()) + return; + + itr->second.RemoveItem(itemId, count); +} + +void LootItemStorage::AddNewStoredLoot(Loot* loot, Player* player) +{ + // Saves the money and item loot associated with an openable item to the DB + if (loot->isLooted()) // no money and no loot + return; + + // read + { + boost::shared_lock<boost::shared_mutex> lock(*GetLock()); + + auto itr = _lootItemStore.find(loot->containerID); + if (itr != _lootItemStore.end()) + { + TC_LOG_ERROR("misc", "Trying to store item loot by player: %s for container id: %u that is already in storage!", player->GetGUID().ToString().c_str(), loot->containerID); + return; + } + } + + StoredLootContainer container(loot->containerID); + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + if (loot->gold) + container.AddMoney(loot->gold, trans); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); + stmt->setUInt32(0, loot->containerID); + trans->Append(stmt); + + for (LootItem const& li : loot->items) + { + // Conditions are not checked when loot is generated, it is checked when loot is sent to a player. + // For items that are lootable, loot is saved to the DB immediately, that means that loot can be + // saved to the DB that the player never should have gotten. This check prevents that, so that only + // items that the player should get in loot are in the DB. + // IE: Horde items are not saved to the DB for Ally players. + if (!li.AllowedForPlayer(player)) + continue; + + // Don't save currency tokens + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(li.itemid); + if (!itemTemplate || itemTemplate->IsCurrencyToken()) + continue; + + container.AddLootItem(li, trans); + } + + CharacterDatabase.CommitTransaction(trans); + + // write + { + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + _lootItemStore.emplace(loot->containerID, std::move(container)); + } +} + +void StoredLootContainer::AddLootItem(LootItem const& lootItem, SQLTransaction& trans) +{ + _lootItems.emplace(std::piecewise_construct, std::forward_as_tuple(lootItem.itemid), std::forward_as_tuple(lootItem)); + if (!trans) + return; + + PreparedStatement* stmt = 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->setUInt32(0, _containerId); + stmt->setUInt32(1, lootItem.itemid); + stmt->setUInt32(2, lootItem.count); + stmt->setBool(3, lootItem.follow_loot_rules); + stmt->setBool(4, lootItem.freeforall); + stmt->setBool(5, lootItem.is_blocked); + stmt->setBool(6, lootItem.is_counted); + stmt->setBool(7, lootItem.is_underthreshold); + stmt->setBool(8, lootItem.needs_quest); + stmt->setInt32(9, lootItem.randomPropertyId); + stmt->setUInt32(10, lootItem.randomSuffix); + trans->Append(stmt); +} + +void StoredLootContainer::AddMoney(uint32 money, SQLTransaction& trans) +{ + _money = money; + if (!trans) + return; + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt32(0, _containerId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_MONEY); + stmt->setUInt32(0, _containerId); + stmt->setUInt32(1, _money); + trans->Append(stmt); +} + +void StoredLootContainer::RemoveMoney() +{ + _money = 0; + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt32(0, _containerId); + CharacterDatabase.Execute(stmt); +} + +void StoredLootContainer::RemoveItem(uint32 itemId, uint32 count) +{ + auto bounds = _lootItems.equal_range(itemId); + for (auto itr = bounds.first; itr != bounds.second; ++itr) + { + if (itr->second.Count == count) + { + _lootItems.erase(itr); + break; + } + } + + // 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); + stmt->setUInt32(2, count); + CharacterDatabase.Execute(stmt); +} diff --git a/src/server/game/Loot/LootItemStorage.h b/src/server/game/Loot/LootItemStorage.h new file mode 100644 index 00000000000..871300e7fcd --- /dev/null +++ b/src/server/game/Loot/LootItemStorage.h @@ -0,0 +1,93 @@ +/* +* Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* +* You should have received a copy of the GNU General Public License along +* with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __LOOTITEMSTORAGE_H +#define __LOOTITEMSTORAGE_H + +#include "Define.h" + +#include <vector> + +class Item; +class Player; +struct Loot; +struct LootItem; +namespace boost +{ + class shared_mutex; +} + +struct StoredLootItem +{ + explicit StoredLootItem(LootItem const& lootItem); + + uint32 ItemId; + uint32 Count; + bool FollowRules; + bool FFA; + bool Blocked; + bool Counted; + bool UnderThreshold; + bool NeedsQuest; + int32 RandomPropertyId; + uint32 RandomSuffix; +}; + +class StoredLootContainer +{ + public: + typedef std::unordered_multimap<uint32 /*itemId*/, StoredLootItem> StoredLootItemContainer; + + explicit StoredLootContainer(uint32 containerId) : _containerId(containerId), _money(0) { } + + void AddLootItem(LootItem const& lootItem, SQLTransaction& trans); + void AddMoney(uint32 money, SQLTransaction& trans); + + void RemoveMoney(); + void RemoveItem(uint32 itemId, uint32 count); + + uint32 GetContainer() const { return _containerId; } + uint32 GetMoney() const { return _money; } + StoredLootItemContainer const& GetLootItems() const { return _lootItems; } + + private: + StoredLootItemContainer _lootItems; + uint32 const _containerId; + uint32 _money; +}; + +class LootItemStorage +{ + public: + static LootItemStorage* instance(); + static boost::shared_mutex* GetLock(); + + void LoadStorageFromDB(); + bool LoadStoredLoot(Item* item, Player* player); + void RemoveStoredMoneyForContainer(uint32 containerId); + void RemoveStoredLootForContainer(uint32 containerId); + void RemoveStoredLootItemForContainer(uint32 containerId, uint32 itemId, uint32 count); + void AddNewStoredLoot(Loot* loot, Player* player); + + private: + LootItemStorage() { } + ~LootItemStorage() { } +}; + +#define sLootItemStorage LootItemStorage::instance() + +#endif diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index a301426f556..34712308070 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -366,7 +366,6 @@ LootItem::LootItem(LootStoreItem const& li) is_underthreshold = 0; is_counted = 0; rollWinnerGUID = ObjectGuid::Empty; - canSave = true; } // Basic checks for player/item compatibility - if false no chance to see the item in the loot @@ -692,33 +691,6 @@ 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 = false; - 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, NotNormalLootItem* *qitem, NotNormalLootItem* *ffaitem, NotNormalLootItem* *conditem) { LootItem* item = NULL; diff --git a/src/server/game/Loot/LootMgr.h b/src/server/game/Loot/LootMgr.h index 0aa85bb0186..4069d5731b5 100644 --- a/src/server/game/Loot/LootMgr.h +++ b/src/server/game/Loot/LootMgr.h @@ -164,7 +164,6 @@ struct TC_GAME_API 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 (reference = 0) @@ -172,8 +171,8 @@ struct TC_GAME_API LootItem // Empty constructor for creating an empty LootItem to be filled in with DB data LootItem() : itemid(0), randomSuffix(0), randomPropertyId(0), count(0), is_looted(false), is_blocked(false), - freeforall(false), is_underthreshold(false), is_counted(false), needs_quest(false), follow_loot_rules(false), - canSave(true){ }; + freeforall(false), is_underthreshold(false), is_counted(false), needs_quest(false), follow_loot_rules(false) + { }; // Basic checks for player/item compatibility - if false no chance to see the item in the loot bool AllowedForPlayer(Player const* player) const; @@ -328,10 +327,6 @@ struct TC_GAME_API Loot Loot(uint32 _gold = 0) : gold(_gold), unlootedCount(0), roundRobinPlayer(), loot_type(LOOT_NONE), maxDuplicates(1), 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/game/World/World.cpp b/src/server/game/World/World.cpp index 38015c6bdab..6cd5960ab9e 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -47,6 +47,7 @@ #include "InstanceSaveMgr.h" #include "Language.h" #include "LFGMgr.h" +#include "LootItemStorage.h" #include "MapManager.h" #include "Memory.h" #include "MMapFactory.h" @@ -1841,6 +1842,9 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Calendar data..."); sCalendarMgr->LoadFromDB(); + TC_LOG_INFO("server.loading", "Loading Item loot..."); + sLootItemStorage->LoadStorageFromDB(); + TC_LOG_INFO("server.loading", "Initialize query data..."); sObjectMgr->InitializeQueriesData(QUERY_DATA_ALL); |