diff --git a/Source/Framework/Database/Databases/CharacterDatabase.cs b/Source/Framework/Database/Databases/CharacterDatabase.cs index 9c1073745..a2c674e61 100644 --- a/Source/Framework/Database/Databases/CharacterDatabase.cs +++ b/Source/Framework/Database/Databases/CharacterDatabase.cs @@ -682,11 +682,11 @@ namespace Framework.Database PrepareStatement(CharStatements.DEL_GUILD_FINDER_GUILD_SETTINGS, "DELETE FROM guild_finder_guild_settings WHERE guildId = ?"); // Items that hold loot or money - PrepareStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS, "SELECT item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items WHERE container_id = ?"); + PrepareStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items"); PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS, "DELETE FROM item_loot_items WHERE container_id = ?"); - PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ?"); + PrepareStatement(CharStatements.DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ? AND item_count = ?"); PrepareStatement(CharStatements.INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - PrepareStatement(CharStatements.SEL_ITEMCONTAINER_MONEY, "SELECT money FROM item_loot_money WHERE container_id = ?"); + PrepareStatement(CharStatements.SEL_ITEMCONTAINER_MONEY, "SELECT container_id, money FROM item_loot_money"); PrepareStatement(CharStatements.DEL_ITEMCONTAINER_MONEY, "DELETE FROM item_loot_money WHERE container_id = ?"); PrepareStatement(CharStatements.INS_ITEMCONTAINER_MONEY, "INSERT INTO item_loot_money (container_id, money) VALUES (?, ?)"); diff --git a/Source/Game/Entities/Item/Item.cs b/Source/Game/Entities/Item/Item.cs index 552e294ce..d5bd3fa17 100644 --- a/Source/Game/Entities/Item/Item.cs +++ b/Source/Game/Entities/Item/Item.cs @@ -366,7 +366,7 @@ namespace Game.Entities // Delete the items if this is a container if (!loot.IsLooted()) - ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + Global.LootItemStorage.RemoveStoredLootForContainer(GetGUID().GetCounter()); Dispose(); return; @@ -662,7 +662,7 @@ namespace Game.Entities // Delete the items if this is a container if (!loot.IsLooted()) - ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + Global.LootItemStorage.RemoveStoredLootForContainer(GetGUID().GetCounter()); } public static void DeleteFromInventoryDB(SQLTransaction trans, ulong itemGuid) @@ -1744,188 +1744,6 @@ namespace Game.Entities return proto.GetSellPrice(); } - public void ItemContainerSaveLootToDB() - { - // Saves the money and item loot associated with an openable item to the DB - if (loot.IsLooted()) // no money and no loot - return; - - SQLTransaction trans = new SQLTransaction(); - - loot.containerID = GetGUID(); // Save this for when a LootItem is removed - - // Save money - if (loot.gold > 0) - { - PreparedStatement stmt_money = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); - stmt_money.AddValue(0, loot.containerID.GetCounter()); - trans.Append(stmt_money); - - stmt_money = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_MONEY); - stmt_money.AddValue(0, loot.containerID.GetCounter()); - stmt_money.AddValue(1, loot.gold); - trans.Append(stmt_money); - } - - // Save items - if (!loot.IsLooted()) - { - PreparedStatement stmt_items = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS); - stmt_items.AddValue(0, loot.containerID.GetCounter()); - trans.Append(stmt_items); - - // Now insert the items - foreach (var _li in loot.items) - { - // 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; - - Player guid = GetOwner(); - if (!_li.AllowedForPlayer(guid)) - continue; - - stmt_items = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_ITEMS); - - // container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, context, bonus_list_ids - stmt_items.AddValue(0, loot.containerID.GetCounter()); - stmt_items.AddValue(1, _li.itemid); - stmt_items.AddValue(2, _li.count); - stmt_items.AddValue(3, _li.follow_loot_rules); - stmt_items.AddValue(4, _li.freeforall); - stmt_items.AddValue(5, _li.is_blocked); - stmt_items.AddValue(6, _li.is_counted); - stmt_items.AddValue(7, _li.is_underthreshold); - stmt_items.AddValue(8, _li.needs_quest); - stmt_items.AddValue(9, _li.randomBonusListId); - stmt_items.AddValue(10, _li.context); - - string bonusListIDs = ""; - foreach (int bonusListID in _li.BonusListIDs) - bonusListIDs += bonusListID + ' '; - - stmt_items.AddValue(11, bonusListIDs); - trans.Append(stmt_items); - } - } - DB.Characters.CommitTransaction(trans); - } - - public bool 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; - - // Save this for later use - loot.containerID = GetGUID(); - - // First, see if there was any money loot. This gets added directly to the container. - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEMCONTAINER_MONEY); - stmt.AddValue(0, loot.containerID.GetCounter()); - SQLResult money_result = DB.Characters.Query(stmt); - - if (!money_result.IsEmpty()) - { - loot.gold = money_result.Read(0); - } - - // Next, load any items that were saved - stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS); - stmt.AddValue(0, loot.containerID.GetCounter()); - SQLResult item_result = DB.Characters.Query(stmt); - - if (!item_result.IsEmpty()) - { - // Get a LootTemplate for the container item. This is where - // the saved loot was originally rolled from, we will copy conditions from it - LootTemplate lt = LootStorage.Items.GetLootFor(GetEntry()); - if (lt != null) - { - do - { - // Create an empty LootItem - LootItem loot_item = new LootItem(); - - // item_id, itm_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, context, bonus_list_ids - loot_item.itemid = item_result.Read(0); - loot_item.count = item_result.Read(1); - loot_item.follow_loot_rules = item_result.Read(2); - loot_item.freeforall = item_result.Read(3); - loot_item.is_blocked = item_result.Read(4); - loot_item.is_counted = item_result.Read(5); - loot_item.canSave = true; - loot_item.is_underthreshold = item_result.Read(6); - loot_item.needs_quest = item_result.Read(7); - loot_item.randomBonusListId = item_result.Read(8); - loot_item.context = (ItemContext)item_result.Read(9); - - StringArray bonusLists = new StringArray(item_result.Read(10), ' '); - if (!bonusLists.IsEmpty()) - { - foreach (string line in bonusLists) - { - if (uint.TryParse(line, out uint id)) - loot_item.BonusListIDs.Add(id); - } - } - - // 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() != 0) - loot_item.AddAllowedLooter(GetOwner()); - - // Finally add the LootItem to the container - loot.items.Add(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 ItemContainerDeleteLootItemsFromDB() - { - // Deletes items associated with an openable item from the DB - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS); - stmt.AddValue(0, GetGUID().GetCounter()); - DB.Characters.Execute(stmt); - } - - void ItemContainerDeleteLootItemFromDB(uint itemID) - { - // Deletes a single item associated with an openable item from the DB - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEM); - stmt.AddValue(0, GetGUID().GetCounter()); - stmt.AddValue(1, itemID); - DB.Characters.Execute(stmt); - } - - void ItemContainerDeleteLootMoneyFromDB() - { - // Deletes the money loot associated with an openable item from the DB - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); - stmt.AddValue(0, GetGUID().GetCounter()); - DB.Characters.Execute(stmt); - } - - public void ItemContainerDeleteLootMoneyAndLootItemsFromDB() - { - // Deletes money and items associated with an openable item from the DB - ItemContainerDeleteLootMoneyFromDB(); - ItemContainerDeleteLootItemsFromDB(); - } - public uint GetItemLevel(Player owner) { ItemTemplate itemTemplate = GetTemplate(); diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index 75720b01d..e37c4d171 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -1507,12 +1507,27 @@ namespace Game.Entities for (var i = InventorySlots.BuyBackStart; i < InventorySlots.BuyBackEnd; ++i) { Item item = m_items[i]; - if (item == null || item.GetState() == ItemUpdateState.New) + if (item == null) continue; + ItemTemplate itemTemplate = item.GetTemplate(); + + if (item.GetState() == ItemUpdateState.New) + { + if (itemTemplate != null) + if (itemTemplate.GetFlags().HasAnyFlag(ItemFlags.HasLoot)) + Global.LootItemStorage.RemoveStoredLootForContainer(item.GetGUID().GetCounter()); + + continue; + } + item.DeleteFromInventoryDB(trans); item.DeleteFromDB(trans); m_items[i].FSetState(ItemUpdateState.New); + + if (itemTemplate != null) + if (itemTemplate.GetFlags().HasAnyFlag(ItemFlags.HasLoot)) + Global.LootItemStorage.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, diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index 9489a39c2..7f82bdf13 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -2449,7 +2449,7 @@ namespace Game.Entities Item bagItem = bag.GetItemByPos(i); if (bagItem != null) { - if (bagItem.m_lootGenerated) + if (bagItem.GetGUID() == GetLootGUID()) { GetSession().DoLootRelease(GetLootGUID()); released = true; // so we don't need to look at dstBag @@ -2467,7 +2467,7 @@ namespace Game.Entities Item bagItem = bag.GetItemByPos(i); if (bagItem != null) { - if (bagItem.m_lootGenerated) + if (bagItem.GetGUID() == GetLootGUID()) { GetSession().DoLootRelease(GetLootGUID()); break; @@ -3911,7 +3911,13 @@ namespace Game.Entities { pItem.RemoveFromWorld(); if (del) + { pItem.SetState(ItemUpdateState.Removed, this); + ItemTemplate itemTemplate = pItem.GetTemplate(); + if (itemTemplate != null) + if (itemTemplate.GetFlags().HasAnyFlag(ItemFlags.HasLoot)) + Global.LootItemStorage.RemoveStoredLootForContainer(pItem.GetGUID().GetCounter()); + } } m_items[slot] = null; @@ -5811,7 +5817,10 @@ namespace Game.Entities ApplyItemObtainSpells(pItem, false); ItemRemovedQuestCheck(pItem.GetEntry(), pItem.GetCount()); + Global.ScriptMgr.OnItemRemove(this, pItem); + Bag pBag; + ItemTemplate pProto = pItem.GetTemplate(); if (bag == InventorySlots.Bag0) { SetInvSlot(slot, ObjectGuid.Empty); @@ -5819,8 +5828,6 @@ namespace Game.Entities // equipment and equipped bags can have applied bonuses if (slot < InventorySlots.BagEnd) { - ItemTemplate pProto = pItem.GetTemplate(); - // item set bonuses applied only at equip and removed at unequip, and still active for broken items if (pProto != null && pProto.GetItemSet() != 0) Item.RemoveItemsSetItem(this, pProto); @@ -5856,10 +5863,8 @@ namespace Game.Entities // Delete rolled money / loot from db. // MUST be done before RemoveFromWorld() or GetTemplate() fails - ItemTemplate pTmp = pItem.GetTemplate(); - if (pTmp != null) - if (Convert.ToBoolean(pTmp.GetFlags() & ItemFlags.HasLoot)) - pItem.ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + if (pProto.GetFlags().HasAnyFlag(ItemFlags.HasLoot)) + Global.LootItemStorage.RemoveStoredLootForContainer(pItem.GetGUID().GetCounter()); if (IsInWorld && update) { @@ -6325,7 +6330,7 @@ namespace Game.Entities // LootItem is being removed (looted) from the container, delete it from the DB. if (!loot.containerID.IsEmpty()) - loot.DeleteLootItemFromContainerItemDB(item.itemid); + Global.LootItemStorage.RemoveStoredLootItemForContainer(loot.containerID.GetCounter(), item.itemid, item.count); } else @@ -6517,9 +6522,12 @@ namespace Game.Entities loot = item.loot; + // Store container id + loot.containerID = item.GetGUID(); + // 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 && !Global.LootItemStorage.LoadStoredLoot(item, this)) { item.m_lootGenerated = true; loot.Clear(); @@ -6542,7 +6550,7 @@ namespace Game.Entities // 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(); + Global.LootItemStorage.AddNewStoredLoot(loot, this); break; } diff --git a/Source/Game/Globals/Global.cs b/Source/Game/Globals/Global.cs index 1e7e0ffa6..9cc95f044 100644 --- a/Source/Game/Globals/Global.cs +++ b/Source/Game/Globals/Global.cs @@ -36,6 +36,7 @@ using Game.Scripting; using Game.SupportSystem; using Game.Services; using Game.Cache; +using Game.Loots; public static class Global { @@ -95,6 +96,7 @@ public static class Global public static CharacterTemplateDataStorage CharacterTemplateDataStorage { get { return CharacterTemplateDataStorage.Instance; } } public static ConversationDataStorage ConversationDataStorage { get { return ConversationDataStorage.Instance; } } public static CharacterCache CharacterCacheStorage { get { return CharacterCache.Instance; } } + public static LootItemStorage LootItemStorage { get { return LootItemStorage.Instance; } } //Misc public static ConditionManager ConditionMgr { get { return ConditionManager.Instance; } } diff --git a/Source/Game/Handlers/LootHandler.cs b/Source/Game/Handlers/LootHandler.cs index e875ea0e3..7e87c7927 100644 --- a/Source/Game/Handlers/LootHandler.cs +++ b/Source/Game/Handlers/LootHandler.cs @@ -229,7 +229,7 @@ namespace Game // Delete the money loot record from the DB if (!loot.containerID.IsEmpty()) - loot.DeleteLootMoneyFromContainerItemDB(); + Global.LootItemStorage.RemoveStoredMoneyForContainer(loot.containerID.GetCounter()); // Delete container if empty if (loot.IsLooted() && guid.IsItem()) diff --git a/Source/Game/Loot/Loot.cs b/Source/Game/Loot/Loot.cs index 19a42d305..6c555bfe5 100644 --- a/Source/Game/Loot/Loot.cs +++ b/Source/Game/Loot/Loot.cs @@ -29,6 +29,7 @@ namespace Game.Loots { public class LootItem { + public LootItem() { } public LootItem(LootStoreItem li) { itemid = li.itemid; @@ -41,12 +42,6 @@ namespace Game.Loots needs_quest = li.needs_quest; randomBonusListId = ItemEnchantmentManager.GenerateItemRandomBonusListId(itemid); - canSave = true; - } - - public LootItem() - { - canSave = true; } public bool AllowedForPlayer(Player player) @@ -100,7 +95,6 @@ namespace Game.Loots public bool is_counted; public bool needs_quest; // quest drop public bool follow_loot_rules; - public bool canSave; } public class NotNormalLootItem @@ -440,33 +434,6 @@ namespace Game.Loots } } - public void DeleteLootItemFromContainerItemDB(uint itemID) - { - // Deletes a single item associated with an openable item from the DB - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEM); - stmt.AddValue(0, containerID.GetCounter()); - stmt.AddValue(1, itemID); - DB.Characters.Execute(stmt); - - // Mark the item looted to prevent resaving - foreach (var lootItem in items) - { - if (lootItem.itemid != itemID) - continue; - - lootItem.canSave = false; - break; - } - } - - public void DeleteLootMoneyFromContainerItemDB() - { - // Deletes money loot associated with an openable item from the DB - PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); - stmt.AddValue(0, containerID.GetCounter()); - DB.Characters.Execute(stmt); - } - public LootItem LootItemInSlot(uint lootSlot, Player player) { return LootItemInSlot(lootSlot, player, out _, out _, out _); diff --git a/Source/Game/Loot/LootItemStorage.cs b/Source/Game/Loot/LootItemStorage.cs new file mode 100644 index 000000000..c80a1cbaf --- /dev/null +++ b/Source/Game/Loot/LootItemStorage.cs @@ -0,0 +1,337 @@ +using Framework.Constants; +using Framework.Database; +using Game.Entities; +using Game.Loots; +using System; +using System.Collections.Generic; +using System.Text; +using Game.Loots; +using Framework.Collections; +using System.Collections.Concurrent; + +namespace Game.Loots +{ + public class LootItemStorage : Singleton + { + LootItemStorage() { } + + public void LoadStorageFromDB() + { + uint oldMSTime = Time.GetMSTime(); + _lootItemStorage.Clear(); + uint count = 0; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEMCONTAINER_ITEMS); + SQLResult result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + do + { + ulong key = result.Read(0); + var itr = _lootItemStorage.LookupByKey(key); + if (!_lootItemStorage.ContainsKey(key)) + _lootItemStorage[key] = new StoredLootContainer(key); + + StoredLootContainer storedContainer = _lootItemStorage[key]; + + LootItem lootItem = new LootItem(); + lootItem.itemid = result.Read(1); + lootItem.count = result.Read(2); + lootItem.follow_loot_rules = result.Read(3); + lootItem.freeforall = result.Read(4); + lootItem.is_blocked = result.Read(5); + lootItem.is_counted = result.Read(6); + lootItem.is_underthreshold = result.Read(7); + lootItem.needs_quest = result.Read(8); + lootItem.randomBonusListId = result.Read(9); + lootItem.context = (ItemContext)result.Read(10); + StringArray bonusLists = new StringArray(result.Read(11), ' '); + + foreach (string str in bonusLists) + lootItem.BonusListIDs.Add(uint.Parse(str)); + + storedContainer.AddLootItem(lootItem, null); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} stored item loots in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + } + else + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 stored item loots"); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.SEL_ITEMCONTAINER_MONEY); + result = DB.Characters.Query(stmt); + if (!result.IsEmpty()) + { + count = 0; + do + { + ulong key = result.Read(0); + if (!_lootItemStorage.ContainsKey(key)) + _lootItemStorage.TryAdd(key, new StoredLootContainer(key)); + + StoredLootContainer storedContainer = _lootItemStorage[key]; + storedContainer.AddMoney(result.Read(1), null); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} stored item money in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + } + else + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 stored item money"); + } + + public bool LoadStoredLoot(Item item, Player player) + { + Loot loot = item.loot; + if (!_lootItemStorage.ContainsKey(loot.containerID.GetCounter())) + return false; + + var container = _lootItemStorage[loot.containerID.GetCounter()]; + loot.gold = container.GetMoney(); + + LootTemplate lt = LootStorage.Items.GetLootFor(item.GetEntry()); + if (lt != null) + { + foreach (var storedItemPair in container.GetLootItems()) + { + LootItem li = new LootItem(); + li.itemid = storedItemPair.Key; + li.count = (byte)storedItemPair.Value.Count; + li.follow_loot_rules = storedItemPair.Value.FollowRules; + li.freeforall = storedItemPair.Value.FFA; + li.is_blocked = storedItemPair.Value.Blocked; + li.is_counted = storedItemPair.Value.Counted; + li.is_underthreshold = storedItemPair.Value.UnderThreshold; + li.needs_quest = storedItemPair.Value.NeedsQuest; + li.randomBonusListId = storedItemPair.Value.RandomBonusListId; + li.context = storedItemPair.Value.Context; + li.BonusListIDs = storedItemPair.Value.BonusListIDs; + + // 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() != 0) + li.AddAllowedLooter(player); + + // Finally add the LootItem to the container + loot.items.Add(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; + } + + public void RemoveStoredMoneyForContainer(ulong containerId) + { + if (!_lootItemStorage.ContainsKey(containerId)) + return; + + _lootItemStorage[containerId].RemoveMoney(); + } + + public void RemoveStoredLootForContainer(ulong containerId) + { + _lootItemStorage.TryRemove(containerId, out _); + + SQLTransaction trans = new SQLTransaction(); + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS); + stmt.AddValue(0, containerId); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); + stmt.AddValue(0, containerId); + trans.Append(stmt); + + DB.Characters.CommitTransaction(trans); + } + + public void RemoveStoredLootItemForContainer(ulong containerId, uint itemId, uint count) + { + if (!_lootItemStorage.ContainsKey(containerId)) + return; + + _lootItemStorage[containerId].RemoveItem(itemId, count); + } + + public void 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; + + if (_lootItemStorage.ContainsKey(loot.containerID.GetCounter())) + { + Log.outError(LogFilter.Misc, $"Trying to store item loot by player: {player.GetGUID()} for container id: {loot.containerID.GetCounter()} that is already in storage!"); + return; + } + + StoredLootContainer container = new StoredLootContainer(loot.containerID.GetCounter()); + + SQLTransaction trans = new SQLTransaction(); + if (loot.gold != 0) + container.AddMoney(loot.gold, trans); + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEMS); + stmt.AddValue(0, loot.containerID.GetCounter()); + trans.Append(stmt); + + foreach (LootItem li in 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 itemTemplate = Global.ObjectMgr.GetItemTemplate(li.itemid); + if (itemTemplate == null || itemTemplate.IsCurrencyToken()) + continue; + + container.AddLootItem(li, trans); + } + + DB.Characters.CommitTransaction(trans); + + _lootItemStorage.TryAdd(loot.containerID.GetCounter(), container); + } + + ConcurrentDictionary _lootItemStorage = new ConcurrentDictionary(); + } + + class StoredLootContainer + { + public StoredLootContainer(ulong containerId) + { + _containerId = containerId; + } + + public void AddLootItem(LootItem lootItem, SQLTransaction trans) + { + _lootItems.Add(lootItem.itemid, new StoredLootItem(lootItem)); + if (trans == null) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_ITEMS); + + // container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix + stmt.AddValue(0, _containerId); + stmt.AddValue(1, lootItem.itemid); + stmt.AddValue(2, lootItem.count); + stmt.AddValue(3, lootItem.follow_loot_rules); + stmt.AddValue(4, lootItem.freeforall); + stmt.AddValue(5, lootItem.is_blocked); + stmt.AddValue(6, lootItem.is_counted); + stmt.AddValue(7, lootItem.is_underthreshold); + stmt.AddValue(8, lootItem.needs_quest); + stmt.AddValue(9, lootItem.randomBonusListId); + stmt.AddValue(10, (uint)lootItem.context); + + foreach (uint token in lootItem.BonusListIDs) + { + StringBuilder bonusListIDs = new StringBuilder(); + foreach (int bonusListID in lootItem.BonusListIDs) + bonusListIDs.Append(bonusListID + ' '); + + stmt.AddValue(11, bonusListIDs.ToString()); + trans.Append(stmt); + } + } + + public void AddMoney(uint money, SQLTransaction trans) + { + _money = money; + if (trans == null) + return; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); + stmt.AddValue(0, _containerId); + trans.Append(stmt); + + stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_ITEMCONTAINER_MONEY); + stmt.AddValue(0, _containerId); + stmt.AddValue(1, _money); + trans.Append(stmt); + } + + public void RemoveMoney() + { + _money = 0; + + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_MONEY); + stmt.AddValue(0, _containerId); + DB.Characters.Execute(stmt); + } + + public void RemoveItem(uint itemId, uint count) + { + var bounds = _lootItems.LookupByKey(itemId); + foreach (var itr in bounds) + { + if (itr.Count == count) + { + _lootItems.Remove(itr.ItemId); + break; + } + } + + // Deletes a single item associated with an openable item from the DB + PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.DEL_ITEMCONTAINER_ITEM); + stmt.AddValue(0, _containerId); + stmt.AddValue(1, itemId); + stmt.AddValue(2, count); + DB.Characters.Execute(stmt); + } + + ulong GetContainer() { return _containerId; } + + public uint GetMoney() { return _money; } + + public MultiMap GetLootItems() { return _lootItems; } + + MultiMap _lootItems = new MultiMap(); + ulong _containerId; + uint _money; + } + + class StoredLootItem + { + public StoredLootItem(LootItem 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; + RandomBonusListId = lootItem.randomBonusListId; + Context = lootItem.context; + BonusListIDs = lootItem.BonusListIDs; + } + + public uint ItemId; + public uint Count; + public bool FollowRules; + public bool FFA; + public bool Blocked; + public bool Counted; + public bool UnderThreshold; + public bool NeedsQuest; + public uint RandomBonusListId; + public ItemContext Context; + public List BonusListIDs = new List(); + } +} diff --git a/Source/Game/Server/WorldManager.cs b/Source/Game/Server/WorldManager.cs index 6069edf95..a731665ed 100644 --- a/Source/Game/Server/WorldManager.cs +++ b/Source/Game/Server/WorldManager.cs @@ -836,6 +836,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Calendar data..."); Global.CalendarMgr.LoadFromDB(); + Log.outInfo(LogFilter.ServerLoading, "Loading Item loot..."); + Global.LootItemStorage.LoadStorageFromDB(); + Log.outInfo(LogFilter.ServerLoading, "Initialize query data..."); Global.ObjectMgr.InitializeQueriesData(QueryDataGroup.All); diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index 23240d7d9..625ad4ef7 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -481,9 +481,9 @@ DROP TABLE IF EXISTS `character_arena_stats`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `character_arena_stats` ( - `guid` bigint(20) NOT NULL, - `slot` tinyint(3) NOT NULL, - `matchMakerRating` smallint(5) NOT NULL, + `guid` bigint(20) unsigned NOT NULL DEFAULT '0', + `slot` tinyint(3) unsigned NOT NULL DEFAULT '0', + `matchMakerRating` smallint(5) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`guid`,`slot`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; @@ -738,8 +738,8 @@ DROP TABLE IF EXISTS `character_equipmentsets`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `character_equipmentsets` ( - `guid` bigint(20) NOT NULL DEFAULT '0', - `setguid` bigint(20) NOT NULL AUTO_INCREMENT, + `guid` bigint(20) unsigned NOT NULL DEFAULT '0', + `setguid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `setindex` tinyint(3) unsigned NOT NULL DEFAULT '0', `name` varchar(31) NOT NULL, `iconname` varchar(100) NOT NULL, @@ -1895,7 +1895,7 @@ DROP TABLE IF EXISTS `creature_respawn`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `creature_respawn` ( `guid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'Global Unique Identifier', - `respawnTime` int(10) unsigned NOT NULL DEFAULT '0', + `respawnTime` bigint(20) unsigned NOT NULL DEFAULT '0', `mapId` smallint(10) unsigned NOT NULL DEFAULT '0', `instanceId` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Instance Identifier', PRIMARY KEY (`guid`,`instanceId`), @@ -1969,7 +1969,7 @@ DROP TABLE IF EXISTS `gameobject_respawn`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `gameobject_respawn` ( `guid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'Global Unique Identifier', - `respawnTime` int(10) unsigned NOT NULL DEFAULT '0', + `respawnTime` bigint(20) unsigned NOT NULL DEFAULT '0', `mapId` smallint(10) unsigned NOT NULL DEFAULT '0', `instanceId` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Instance Identifier', PRIMARY KEY (`guid`,`instanceId`), @@ -2616,7 +2616,7 @@ DROP TABLE IF EXISTS `instance`; CREATE TABLE `instance` ( `id` int(10) unsigned NOT NULL DEFAULT '0', `map` smallint(5) unsigned NOT NULL DEFAULT '0', - `resettime` int(10) unsigned NOT NULL DEFAULT '0', + `resettime` bigint(20) unsigned NOT NULL DEFAULT '0', `difficulty` tinyint(3) unsigned NOT NULL DEFAULT '0', `completedEncounters` int(10) unsigned NOT NULL DEFAULT '0', `data` tinytext NOT NULL, @@ -2647,7 +2647,7 @@ DROP TABLE IF EXISTS `instance_reset`; CREATE TABLE `instance_reset` ( `mapid` smallint(5) unsigned NOT NULL DEFAULT '0', `difficulty` tinyint(3) unsigned NOT NULL DEFAULT '0', - `resettime` int(10) unsigned NOT NULL DEFAULT '0', + `resettime` bigint(20) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`mapid`,`difficulty`), KEY `difficulty` (`difficulty`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -3180,8 +3180,8 @@ 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` bigint(20) NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)', - `money` int(10) NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)', + `container_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)', + `money` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)', PRIMARY KEY (`container_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; @@ -3771,7 +3771,11 @@ INSERT INTO `updates` VALUES ('2019_12_05_00_characters.sql','EA381C9634A5646A3168F15DF4E06A708A622762','RELEASED','2019-12-05 20:56:58',0), ('2020_02_17_00_characters.sql','E1519A81D35F19B48B3C75A83A270CB4BA0B84F2','RELEASED','2020-02-17 21:55:17',0), ('2020_04_20_00_characters.sql','977B5E0C894E0A7E80B2A9626F17CA636A69BD22','RELEASED','2020-04-20 19:08:18',0), -('2020_04_24_00_characters.sql','85E2E0395A9457A53D73A9E0A7BB39B7E4C429BF','RELEASED','2020-04-24 22:04:59',0); +('2020_04_24_00_characters.sql','85E2E0395A9457A53D73A9E0A7BB39B7E4C429BF','RELEASED','2020-04-24 22:04:59',0), +('2020_04_25_00_characters_2017_04_03_00_characters.sql','00FA3EFADAF807AC96619A3FE47216E21C3FCB19','RELEASED','2020-04-25 00:00:00',0), +('2020_04_26_00_characters_2017_04_12_00_characters.sql','86AA94DA9B1EA283101100886C10F648C0CE6494','RELEASED','2020-04-26 00:00:00',0), +('2020_04_26_01_characters_2017_04_12_01_characters.sql','5A8A1215E3A2356722F52CD7A64BBE03D21FBEA3','RELEASED','2020-04-26 00:00:00',0); + /*!40000 ALTER TABLE `updates` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/updates/characters/master/2020_04_26_00_characters_2017_04_12_00_characters.sql b/sql/updates/characters/master/2020_04_26_00_characters_2017_04_12_00_characters.sql new file mode 100644 index 000000000..5340fbfca --- /dev/null +++ b/sql/updates/characters/master/2020_04_26_00_characters_2017_04_12_00_characters.sql @@ -0,0 +1,2 @@ +ALTER TABLE `item_loot_money` +CHANGE `money` `money` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)';