// Copyright (c) CypherCore All rights reserved. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. using Framework.Constants; using Framework.Database; using Game.BattleFields; using Game.BattleGrounds; using Game.DataStorage; using Game.Groups; using Game.Guilds; using Game.Loots; using Game.Mails; using Game.Maps; using Game.Networking.Packets; using Game.Spells; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Game.Entities { public partial class Player { //Refund void AddRefundReference(ObjectGuid it) { m_refundableItems.Add(it); } public void DeleteRefundReference(ObjectGuid it) { m_refundableItems.Remove(it); } public void RefundItem(Item item) { if (!item.IsRefundable()) { Log.outDebug(LogFilter.Player, "Item refund: item not refundable!"); return; } if (item.IsRefundExpired()) // item refund has expired { item.SetNotRefundable(this); SendItemRefundResult(item, null, 10); return; } if (GetGUID() != item.GetRefundRecipient()) // Formerly refundable item got traded { Log.outDebug(LogFilter.Player, "Item refund: item was traded!"); item.SetNotRefundable(this); return; } ItemExtendedCostRecord iece = CliDB.ItemExtendedCostStorage.LookupByKey(item.GetPaidExtendedCost()); if (iece == null) { Log.outDebug(LogFilter.Player, "Item refund: cannot find extendedcost data."); return; } bool store_error = false; for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) { uint count = iece.ItemCount[i]; uint itemid = iece.ItemID[i]; if (count != 0 && itemid != 0) { List dest = new(); InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemid, count); if (msg != InventoryResult.Ok) { store_error = true; break; } } } if (store_error) { SendItemRefundResult(item, iece, 10); return; } SendItemRefundResult(item, iece, 0); ulong moneyRefund = item.GetPaidMoney(); // item. will be invalidated in DestroyItem // Save all relevant data to DB to prevent desynchronisation exploits SQLTransaction trans = new(); // Delete any references to the refund data item.SetNotRefundable(this, true, trans, false); GetSession().GetCollectionMgr().RemoveTemporaryAppearance(item); // Destroy item DestroyItem(item.GetBagSlot(), item.GetSlot(), true); // Grant back extendedcost items for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) { uint count = iece.ItemCount[i]; uint itemid = iece.ItemID[i]; if (count != 0 && itemid != 0) { List dest = new(); InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemid, count); Cypher.Assert(msg == InventoryResult.Ok); // Already checked before Item it = StoreNewItem(dest, itemid, true); SendNewItem(it, count, true, false, true); } } // Grant back currencies for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) { if (iece.Flags.HasAnyFlag((byte)((int)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) continue; uint count = iece.CurrencyCount[i]; uint currencyid = iece.CurrencyID[i]; if (count != 0 && currencyid != 0) AddCurrency(currencyid, count, CurrencyGainSource.ItemRefund); } // Grant back money if (moneyRefund != 0) ModifyMoney((long)moneyRefund); // Saved in SaveInventoryAndGoldToDB SaveInventoryAndGoldToDB(trans); DB.Characters.CommitTransaction(trans); } public void SendRefundInfo(Item item) { // This function call unsets ITEM_FLAGS_REFUNDABLE if played time is over 2 hours. item.UpdatePlayedTime(this); if (!item.IsRefundable()) { Log.outDebug(LogFilter.Player, "Item refund: item not refundable!"); return; } if (GetGUID() != item.GetRefundRecipient()) // Formerly refundable item got traded { Log.outDebug(LogFilter.Player, "Item refund: item was traded!"); item.SetNotRefundable(this); return; } ItemExtendedCostRecord iece = CliDB.ItemExtendedCostStorage.LookupByKey(item.GetPaidExtendedCost()); if (iece == null) { Log.outDebug(LogFilter.Player, "Item refund: cannot find extendedcost data."); return; } SetItemPurchaseData setItemPurchaseData = new(); setItemPurchaseData.ItemGUID = item.GetGUID(); setItemPurchaseData.PurchaseTime = GetTotalPlayedTime() - item.GetPlayedTime(); setItemPurchaseData.Contents.Money = item.GetPaidMoney(); for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) // item cost data { setItemPurchaseData.Contents.Items[i].ItemCount = iece.ItemCount[i]; setItemPurchaseData.Contents.Items[i].ItemID = iece.ItemID[i]; } for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) // currency cost data { if (iece.Flags.HasAnyFlag((byte)((int)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) continue; setItemPurchaseData.Contents.Currencies[i].CurrencyCount = iece.CurrencyCount[i]; setItemPurchaseData.Contents.Currencies[i].CurrencyID = iece.CurrencyID[i]; } SendPacket(setItemPurchaseData); } public void SendItemRefundResult(Item item, ItemExtendedCostRecord iece, byte error) { ItemPurchaseRefundResult itemPurchaseRefundResult = new(); itemPurchaseRefundResult.ItemGUID = item.GetGUID(); itemPurchaseRefundResult.Result = error; if (error == 0) { itemPurchaseRefundResult.Contents = new(); itemPurchaseRefundResult.Contents.Money = item.GetPaidMoney(); for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) // item cost data { itemPurchaseRefundResult.Contents.Items[i].ItemCount = iece.ItemCount[i]; itemPurchaseRefundResult.Contents.Items[i].ItemID = iece.ItemID[i]; } for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) // currency cost data { if (iece.Flags.HasAnyFlag((byte)((uint)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) continue; itemPurchaseRefundResult.Contents.Currencies[i].CurrencyCount = iece.CurrencyCount[i]; itemPurchaseRefundResult.Contents.Currencies[i].CurrencyID = iece.CurrencyID[i]; } } SendPacket(itemPurchaseRefundResult); } //Trade void AddTradeableItem(Item item) { m_itemSoulboundTradeable.Add(item.GetGUID()); } public void RemoveTradeableItem(Item item) { m_itemSoulboundTradeable.Remove(item.GetGUID()); } void UpdateSoulboundTradeItems() { // also checks for garbage data foreach (var guid in m_itemSoulboundTradeable.ToList()) { Item item = GetItemByGuid(guid); if (item == null || item.GetOwnerGUID() != GetGUID() || item.CheckSoulboundTradeExpire()) m_itemSoulboundTradeable.Remove(guid); } } public void SetTradeData(TradeData data) { m_trade = data; } public Player GetTrader() { return m_trade?.GetTrader(); } public TradeData GetTradeData() { return m_trade; } public void TradeCancel(bool sendback) { if (m_trade != null) { Player trader = m_trade.GetTrader(); // send yellow "Trade canceled" message to both traders if (sendback) GetSession().SendCancelTrade(); trader.GetSession().SendCancelTrade(); // cleanup m_trade = null; trader.m_trade = null; } } //Durability public void DurabilityLossAll(double percent, bool inventory) { for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; i++) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) DurabilityLoss(pItem, percent); } if (inventory) { int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = InventorySlots.ItemStart; i < inventoryEnd; i++) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) DurabilityLoss(pItem, percent); } for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { Bag pBag = GetBagByPos(i); if (pBag != null) { for (byte j = 0; j < pBag.GetBagSize(); j++) { Item pItem = GetItemByPos(i, j); if (pItem != null) DurabilityLoss(pItem, percent); } } } } } public void DurabilityLoss(Item item, double percent) { if (item == null) return; uint pMaxDurability = item.m_itemData.MaxDurability; if (pMaxDurability == 0) return; percent /= GetTotalAuraMultiplier(AuraType.ModDurabilityLoss); int pDurabilityLoss = (int)(pMaxDurability * percent); if (pDurabilityLoss < 1) pDurabilityLoss = 1; DurabilityPointsLoss(item, pDurabilityLoss); } public void DurabilityPointsLossAll(int points, bool inventory) { for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; i++) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) DurabilityPointsLoss(pItem, points); } if (inventory) { int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = InventorySlots.ItemStart; i < inventoryEnd; i++) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) DurabilityPointsLoss(pItem, points); } for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { Bag pBag = (Bag)GetItemByPos(InventorySlots.Bag0, i); if (pBag != null) for (byte j = 0; j < pBag.GetBagSize(); j++) { Item pItem = GetItemByPos(i, j); if (pItem != null) DurabilityPointsLoss(pItem, points); } } } } public void DurabilityPointsLoss(Item item, int points) { if (HasAuraType(AuraType.PreventDurabilityLoss)) return; uint pMaxDurability = item.m_itemData.MaxDurability; uint pOldDurability = item.m_itemData.Durability; int pNewDurability = (int)(pOldDurability - points); if (pNewDurability < 0) pNewDurability = 0; else if (pNewDurability > pMaxDurability) pNewDurability = (int)pMaxDurability; if (pOldDurability != pNewDurability) { // modify item stats _before_ Durability set to 0 to pass _ApplyItemMods internal check if (pNewDurability == 0 && pOldDurability > 0 && item.IsEquipped()) _ApplyItemMods(item, item.GetSlot(), false); item.SetDurability((uint)pNewDurability); // modify item stats _after_ restore durability to pass _ApplyItemMods internal check if (pNewDurability > 0 && pOldDurability == 0 && item.IsEquipped()) _ApplyItemMods(item, item.GetSlot(), true); item.SetState(ItemUpdateState.Changed, this); } } public void DurabilityPointLossForEquipSlot(byte slot) { if (HasAuraType(AuraType.PreventDurabilityLossFromCombat)) return; Item pItem = GetItemByPos(InventorySlots.Bag0, slot); if (pItem != null) DurabilityPointsLoss(pItem, 1); } public void DurabilityRepairAll(bool takeCost, float discountMod, bool guildBank) { // Collecting all items that can be repaired and repair costs List<(Item item, ulong cost)> itemRepairCostStore = new(); // equipped, backpack, bags itself int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = EquipmentSlot.Start; i < inventoryEnd; i++) { Item item = GetItemByPos((ushort)((InventorySlots.Bag0 << 8) | i)); if (item != null) { ulong cost = item.CalculateDurabilityRepairCost(discountMod); if (cost != 0) itemRepairCostStore.Add((item, cost)); } } // items in inventory bags for (byte j = InventorySlots.BagStart; j < InventorySlots.BagEnd; j++) { for (byte i = 0; i < ItemConst.MaxBagSize; i++) { Item item = GetItemByPos((ushort)((j << 8) | i)); if (item != null) { ulong cost = item.CalculateDurabilityRepairCost(discountMod); if (cost != 0) itemRepairCostStore.Add((item, cost)); } } } // Handling a free repair case - just repair every item without taking cost. if (!takeCost) { foreach (var (item, _) in itemRepairCostStore) DurabilityRepair(item.GetPos(), false, 0.0f); return; } if (guildBank) { // Handling a repair for guild money case. // We have to repair items one by one until the guild bank has enough money available for withdrawal or until all items are repaired. Guild guild = GetGuild(); if (guild == null) return; // silent return, client shouldn't display this button for players without guild. ulong availableGuildMoney = guild.GetMemberAvailableMoneyForRepairItems(GetGUID()); if (availableGuildMoney == 0) return; // Sort the items by repair cost from lowest to highest itemRepairCostStore.OrderByDescending(a => a.cost); // We must calculate total repair cost and take money once to avoid spam in the guild bank log and reduce number of transactions in the database ulong totalCost = 0; foreach (var (item, cost) in itemRepairCostStore) { ulong newTotalCost = totalCost + cost; if (newTotalCost > availableGuildMoney || newTotalCost > PlayerConst.MaxMoneyAmount) break; totalCost = newTotalCost; // Repair item without taking cost. We'll do it later. DurabilityRepair(item.GetPos(), false, 0.0f); } // Take money for repairs from the guild bank guild.HandleMemberWithdrawMoney(GetSession(), totalCost, true); } else { // Handling a repair for player's money case. // Unlike repairing for guild money, in this case we must first check if player has enough money to repair all the items at once. ulong totalCost = 0; foreach (var (_, cost) in itemRepairCostStore) totalCost += cost; if (!HasEnoughMoney(totalCost)) return; // silent return, client should display error by itself and not send opcode. ModifyMoney(-(int)totalCost); // Payment for repair has already been taken, so just repair every item without taking cost. foreach (var (item, cost) in itemRepairCostStore) DurabilityRepair(item.GetPos(), false, 0.0f); } } public void DurabilityRepair(ushort pos, bool takeCost, float discountMod) { Item item = GetItemByPos(pos); if (item == null) return; if (takeCost) { ulong cost = item.CalculateDurabilityRepairCost(discountMod); if (!HasEnoughMoney(cost)) { Log.outDebug(LogFilter.PlayerItems, $"Player::DurabilityRepair: Player '{GetName()}' ({GetGUID()}) has not enough money to repair item"); return; } ModifyMoney(-(int)cost); } bool isBroken = item.IsBroken(); item.SetDurability(item.m_itemData.MaxDurability); item.SetState(ItemUpdateState.Changed, this); // reapply mods for total broken and repaired item if equipped if (IsEquipmentPos(pos) && isBroken) _ApplyItemMods(item, (byte)(pos & 255), true); } //Store Item public InventoryResult CanStoreItem(byte bag, byte slot, List dest, Item pItem, bool swap = false) { if (pItem == null) return InventoryResult.ItemNotFound; return CanStoreItem(bag, slot, dest, pItem.GetEntry(), pItem.GetCount(), pItem, swap); } InventoryResult CanStoreItem(byte bag, byte slot, List dest, uint entry, uint count, Item pItem, bool swap) { return CanStoreItem(bag, slot, dest, entry, count, pItem, swap, out _); } InventoryResult CanStoreItem(byte bag, byte slot, List dest, uint entry, uint count, Item pItem, bool swap, out uint no_space_count) { no_space_count = 0; Log.outDebug(LogFilter.Player, "STORAGE: CanStoreItem bag = {0}, slot = {1}, item = {2}, count = {3}", bag, slot, entry, count); ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(entry); if (pProto == null) { no_space_count = count; return swap ? InventoryResult.CantSwap : InventoryResult.ItemNotFound; } if (pItem != null) { // item used if (pItem.m_lootGenerated) { no_space_count = count; return InventoryResult.LootGone; } if (pItem.IsBindedNotWith(this)) { no_space_count = count; return InventoryResult.NotOwner; } } // check count of items (skip for auto move for same player from bank) uint no_similar_count = 0; // can't store this amount similar items InventoryResult res = CanTakeMoreSimilarItems(entry, count, pItem, ref no_similar_count); if (res != InventoryResult.Ok) { if (count == no_similar_count) { no_space_count = no_similar_count; return res; } count -= no_similar_count; } // in specific slot if (bag != ItemConst.NullBag && slot != ItemConst.NullSlot) { res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, ref count, swap, pItem); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } // not specific slot or have space for partly store only in specific slot byte inventoryEnd = (byte)(InventorySlots.ItemStart + GetInventorySlotCount()); // in specific bag if (bag != ItemConst.NullBag) { // search stack in bag for merge to if (pProto.GetMaxStackSize() != 1) { if (bag == InventorySlots.Bag0) // inventory { res = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, pProto, ref count, true, pItem, bag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } res = CanStoreItem_InInventorySlots(InventorySlots.ItemStart, inventoryEnd, dest, pProto, ref count, true, pItem, bag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } else // equipped bag { // we need check 2 time (specialized/non_specialized), use NULL_BAG to prevent skipping bag res = CanStoreItem_InBag(bag, dest, pProto, ref count, true, false, pItem, ItemConst.NullBag, slot); if (res != InventoryResult.Ok) res = CanStoreItem_InBag(bag, dest, pProto, ref count, true, true, pItem, ItemConst.NullBag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } } // search free slot in bag for place to if (bag == InventorySlots.Bag0) // inventory { if (pItem != null && pItem.HasItemFlag(ItemFieldFlags.Child)) { res = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, pProto, ref count, false, pItem, bag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } res = CanStoreItem_InInventorySlots(InventorySlots.ItemStart, inventoryEnd, dest, pProto, ref count, false, pItem, bag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } else // equipped bag { res = CanStoreItem_InBag(bag, dest, pProto, ref count, false, false, pItem, ItemConst.NullBag, slot); if (res != InventoryResult.Ok) res = CanStoreItem_InBag(bag, dest, pProto, ref count, false, true, pItem, ItemConst.NullBag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } } // not specific bag or have space for partly store only in specific bag // search stack for merge to if (pProto.GetMaxStackSize() != 1) { res = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, pProto, ref count, true, pItem, bag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } res = CanStoreItem_InInventorySlots(InventorySlots.ItemStart, inventoryEnd, dest, pProto, ref count, true, pItem, bag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } if (pProto.GetBagFamily() != 0) { for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { res = CanStoreItem_InBag(i, dest, pProto, ref count, true, false, pItem, bag, slot); if (res != InventoryResult.Ok) continue; if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } } for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { res = CanStoreItem_InBag(i, dest, pProto, ref count, true, true, pItem, bag, slot); if (res != InventoryResult.Ok) continue; if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } } // search free slot - special bag case if (pProto.GetBagFamily() != 0) { for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { res = CanStoreItem_InBag(i, dest, pProto, ref count, false, false, pItem, bag, slot); if (res != InventoryResult.Ok) continue; if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } } if (pItem != null && pItem.IsNotEmptyBag()) return InventoryResult.BagInBag; if (pItem != null && pItem.HasItemFlag(ItemFieldFlags.Child)) { res = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, pProto, ref count, false, pItem, bag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } // search free slot byte searchSlotStart = InventorySlots.ItemStart; // new bags can be directly equipped if (pItem == null && pProto.GetClass() == ItemClass.Container && (ItemSubClassContainer)pProto.GetSubClass() == ItemSubClassContainer.Container && (pProto.GetBonding() == ItemBondingType.None || pProto.GetBonding() == ItemBondingType.OnAcquire)) searchSlotStart = InventorySlots.BagStart; res = CanStoreItem_InInventorySlots(searchSlotStart, inventoryEnd, dest, pProto, ref count, false, pItem, bag, slot); if (res != InventoryResult.Ok) { no_space_count = count + no_similar_count; return res; } if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } for (var i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { res = CanStoreItem_InBag(i, dest, pProto, ref count, false, true, pItem, bag, slot); if (res != InventoryResult.Ok) continue; if (count == 0) { if (no_similar_count == 0) return InventoryResult.Ok; no_space_count = count + no_similar_count; return InventoryResult.ItemMaxCount; } } no_space_count = count + no_similar_count; return InventoryResult.InvFull; } public InventoryResult CanStoreItems(Item[] items, int count, ref uint offendingItemId) { Item item2; // fill space tables, creating a mock-up of the player's inventory // counts uint[] inventoryCounts = new uint[InventorySlots.ItemEnd - InventorySlots.ItemStart]; uint[][] bagCounts = new uint[InventorySlots.BagEnd - InventorySlots.BagStart][]; // Item array Item[] inventoryPointers = new Item[InventorySlots.ItemEnd - InventorySlots.ItemStart]; Item[][] bagPointers = new Item[InventorySlots.BagEnd - InventorySlots.BagStart][]; int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); // filling inventory for (byte i = InventorySlots.ItemStart; i < inventoryEnd; i++) { // build items in stock backpack item2 = GetItemByPos(InventorySlots.Bag0, i); if (item2 != null && !item2.IsInTrade()) { inventoryCounts[i - InventorySlots.ItemStart] = item2.GetCount(); inventoryPointers[i - InventorySlots.ItemStart] = item2; } } for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { Bag pBag = GetBagByPos(i); if (pBag != null) { bagCounts[i - InventorySlots.BagStart] = new uint[ItemConst.MaxBagSize]; bagPointers[i - InventorySlots.BagStart] = new Item[ItemConst.MaxBagSize]; for (byte j = 0; j < pBag.GetBagSize(); j++) { // build item counts in equippable bags item2 = GetItemByPos(i, j); if (item2 != null && !item2.IsInTrade()) { bagCounts[i - InventorySlots.BagStart][j] = item2.GetCount(); bagPointers[i - InventorySlots.BagStart][j] = item2; } } } } // check free space for all items that we wish to add for (int k = 0; k < count; ++k) { // Incoming item Item item = items[k]; // no item if (item == null) continue; uint remaining_count = item.GetCount(); Log.outDebug(LogFilter.Player, $"STORAGE: CanStoreItems {k + 1}. item = {item.GetEntry()}, count = {remaining_count}"); ItemTemplate pProto = item.GetTemplate(); // strange item if (pProto == null) return InventoryResult.ItemNotFound; // item used if (item.m_lootGenerated) return InventoryResult.LootGone; // item it 'bind' if (item.IsBindedNotWith(this)) return InventoryResult.NotOwner; ItemTemplate pBagProto; // item is 'one item only' InventoryResult res = CanTakeMoreSimilarItems(item, ref offendingItemId); if (res != InventoryResult.Ok) return res; bool b_found = false; // search stack for merge to if (pProto.GetMaxStackSize() != 1) { for (byte t = InventorySlots.ItemStart; t < inventoryEnd; ++t) { item2 = inventoryPointers[t - InventorySlots.ItemStart]; if (item2 != null && item2.CanBeMergedPartlyWith(pProto) == InventoryResult.Ok && inventoryCounts[t - InventorySlots.ItemStart] < pProto.GetMaxStackSize()) { inventoryCounts[t - InventorySlots.ItemStart] += remaining_count; remaining_count = inventoryCounts[t - InventorySlots.ItemStart] < pProto.GetMaxStackSize() ? 0 : inventoryCounts[t - InventorySlots.ItemStart] - pProto.GetMaxStackSize(); b_found = remaining_count == 0; // if no pieces of the stack remain, then stop checking stock bag if (b_found) break; } } if (b_found) continue; for (byte t = InventorySlots.BagStart; !b_found && t < InventorySlots.BagEnd; ++t) { Bag bag = GetBagByPos(t); if (bag != null) { if (!Item.ItemCanGoIntoBag(item.GetTemplate(), bag.GetTemplate())) continue; for (byte j = 0; j < bag.GetBagSize(); j++) { item2 = bagPointers[t - InventorySlots.BagStart][j]; if (item2 != null && item2.CanBeMergedPartlyWith(pProto) == InventoryResult.Ok && bagCounts[t - InventorySlots.BagStart][j] < pProto.GetMaxStackSize()) { // add count to stack so that later items in the list do not double-book bagCounts[t - InventorySlots.BagStart][j] += remaining_count; remaining_count = bagCounts[t - InventorySlots.BagStart][j] < pProto.GetMaxStackSize() ? 0 : bagCounts[t - InventorySlots.BagStart][j] - pProto.GetMaxStackSize(); b_found = remaining_count == 0; // if no pieces of the stack remain, then stop checking equippable bags if (b_found) break; } } } } if (b_found) continue; } b_found = false; // special bag case if (pProto.GetBagFamily() != 0) { for (byte t = InventorySlots.BagStart; !b_found && t < InventorySlots.BagEnd; ++t) { Bag bag = GetBagByPos(t); if (bag != null) { pBagProto = bag.GetTemplate(); // not plain container check if (pBagProto != null && (pBagProto.GetClass() != ItemClass.Container || pBagProto.GetSubClass() != (uint)ItemSubClassContainer.Container) && Item.ItemCanGoIntoBag(pProto, pBagProto)) { for (uint j = 0; j < bag.GetBagSize(); j++) { if (bagCounts[t - InventorySlots.BagStart][j] == 0) { bagCounts[t - InventorySlots.BagStart][j] = remaining_count; bagPointers[t - InventorySlots.BagStart][j] = item; b_found = true; break; } } } } } if (b_found) continue; } // search free slot b_found = false; for (int t = InventorySlots.ItemStart; t < inventoryEnd; ++t) { if (inventoryCounts[t - InventorySlots.ItemStart] == 0) { inventoryCounts[t - InventorySlots.ItemStart] = 1; inventoryPointers[t - InventorySlots.ItemStart] = item; b_found = true; break; } } if (b_found) continue; // search free slot in bags for (byte t = InventorySlots.BagStart; !b_found && t < InventorySlots.BagEnd; ++t) { Bag bag = GetBagByPos(t); if (bag != null) { pBagProto = bag.GetTemplate(); // special bag already checked if (pBagProto != null && (pBagProto.GetClass() != ItemClass.Container || pBagProto.GetSubClass() != (uint)ItemSubClassContainer.Container)) continue; for (uint j = 0; j < bag.GetBagSize(); j++) { if (bagCounts[t - InventorySlots.BagStart][j] == 0) { bagCounts[t - InventorySlots.BagStart][j] = remaining_count; bagPointers[t - InventorySlots.BagStart][j] = item; b_found = true; break; } } } } // if no free slot found for all pieces of the item, then return an error if (!b_found) return InventoryResult.BagFull; } return InventoryResult.Ok; } public InventoryResult CanStoreNewItem(byte bag, byte slot, List dest, uint item, uint count, out uint no_space_count) { return CanStoreItem(bag, slot, dest, item, count, null, false, out no_space_count); } public InventoryResult CanStoreNewItem(byte bag, byte slot, List dest, uint item, uint count) { return CanStoreItem(bag, slot, dest, item, count, null, false, out _); } Item _StoreItem(ushort pos, Item pItem, uint count, bool clone, bool update) { if (pItem == null) return null; byte bag = (byte)(pos >> 8); byte slot = (byte)(pos & 255); Log.outDebug(LogFilter.Player, "STORAGE: StoreItem bag = {0}, slot = {1}, item = {2}, count = {3}, guid = {4}", bag, slot, pItem.GetEntry(), count, pItem.GetGUID().ToString()); Item pItem2 = GetItemByPos(bag, slot); if (pItem2 == null) { if (clone) pItem = pItem.CloneItem(count, this); else pItem.SetCount(count); if (pItem == null) return null; if (pItem.GetBonding() == ItemBondingType.OnAcquire || pItem.GetBonding() == ItemBondingType.Quest || (pItem.GetBonding() == ItemBondingType.OnEquip && IsBagPos(pos))) pItem.SetBinding(true); Bag pBag = bag == InventorySlots.Bag0 ? null : GetBagByPos(bag); if (pBag == null) { m_items[slot] = pItem; SetInvSlot(slot, pItem.GetGUID()); pItem.SetContainedIn(GetGUID()); pItem.SetOwnerGUID(GetGUID()); pItem.SetSlot(slot); pItem.SetContainer(null); } else pBag.StoreItem(slot, pItem, update); if (IsInWorld && update) { pItem.AddToWorld(); pItem.SendUpdateToPlayer(this); } pItem.SetState(ItemUpdateState.Changed, this); if (pBag != null) pBag.SetState(ItemUpdateState.Changed, this); AddEnchantmentDurations(pItem); AddItemDurations(pItem); if (bag == InventorySlots.Bag0 || (bag >= InventorySlots.BagStart && bag < InventorySlots.BagEnd)) ApplyItemObtainSpells(pItem, true); return pItem; } else { if (pItem2.GetBonding() == ItemBondingType.OnAcquire || pItem2.GetBonding() == ItemBondingType.Quest || (pItem2.GetBonding() == ItemBondingType.OnEquip && IsBagPos(pos))) pItem2.SetBinding(true); pItem2.SetCount(pItem2.GetCount() + count); if (IsInWorld && update) pItem2.SendUpdateToPlayer(this); if (!clone) { // delete item (it not in any slot currently) if (IsInWorld && update) { pItem.RemoveFromWorld(); pItem.DestroyForPlayer(this); } RemoveEnchantmentDurations(pItem); RemoveItemDurations(pItem); pItem.SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor pItem.SetNotRefundable(this); pItem.ClearSoulboundTradeable(this); RemoveTradeableItem(pItem); pItem.SetState(ItemUpdateState.Removed, this); } AddEnchantmentDurations(pItem2); pItem2.SetState(ItemUpdateState.Changed, this); if (bag == InventorySlots.Bag0 || (bag >= InventorySlots.BagStart && bag < InventorySlots.BagEnd)) ApplyItemObtainSpells(pItem2, true); return pItem2; } } public Item StoreItem(List dest, Item pItem, bool update) { if (pItem == null) return null; Item lastItem = pItem; for (var i = 0; i < dest.Count; i++) { var itemPosCount = dest[i]; ushort pos = itemPosCount.pos; uint count = itemPosCount.count; if (i == dest.Count - 1) { lastItem = _StoreItem(pos, pItem, count, false, update); break; } lastItem = _StoreItem(pos, pItem, count, true, update); } AutoUnequipChildItem(lastItem); return lastItem; } bool StoreNewItemInBestSlots(uint itemId, uint amount, ItemContext context) { Log.outDebug(LogFilter.Player, "STORAGE: Creating initial item, itemId = {0}, count = {1}", itemId, amount); InventoryResult msg; // attempt equip by one while (amount > 0) { msg = CanEquipNewItem(ItemConst.NullSlot, out ushort eDest, itemId, false); if (msg != InventoryResult.Ok) break; EquipNewItem(eDest, itemId, context, true); AutoUnequipOffhandIfNeed(); --amount; } if (amount == 0) return true; // equipped // attempt store List sDest = new(); // store in main bag to simplify second pass (special bags can be not equipped yet at this moment) msg = CanStoreNewItem(InventorySlots.Bag0, ItemConst.NullSlot, sDest, itemId, amount); if (msg == InventoryResult.Ok) { StoreNewItem(sDest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId), null, context); return true; // stored } // item can't be added Log.outError(LogFilter.Player, "STORAGE: Can't equip or store initial item {0} for race {1} class {2}, error msg = {3}", itemId, GetRace(), GetClass(), msg); return false; } public Item StoreNewItem(List pos, uint itemId, bool update, uint randomBonusListId = 0, List allowedLooters = null, ItemContext context = 0, List bonusListIDs = null, bool addToCollection = true) { uint count = 0; foreach (var itemPosCount in pos) count += itemPosCount.count; // quest objectives must be processed twice - QUEST_OBJECTIVE_FLAG_2_QUEST_BOUND_ITEM prevents item creation ItemAddedQuestCheck(itemId, count, true, out bool hadBoundItemObjective); if (hadBoundItemObjective) return null; Item item = Item.CreateItem(itemId, count, context, this, bonusListIDs == null); if (item != null) { item.SetItemFlag(ItemFieldFlags.NewItem); if (bonusListIDs != null) item.SetBonuses(bonusListIDs); item = StoreItem(pos, item, update); ItemAddedQuestCheck(itemId, count, false); UpdateCriteria(CriteriaType.ObtainAnyItem, itemId, count); UpdateCriteria(CriteriaType.AcquireItem, itemId, count); item.SetFixedLevel(GetLevel()); item.SetItemRandomBonusList(randomBonusListId); if (allowedLooters != null && allowedLooters.Count > 1 && item.GetTemplate().GetMaxStackSize() == 1 && item.IsSoulBound()) { item.SetSoulboundTradeable(allowedLooters); item.SetCreatePlayedTime(GetTotalPlayedTime()); AddTradeableItem(item); // save data StringBuilder ss = new(); foreach (var guid in allowedLooters) ss.AppendFormat("{0} ", guid); PreparedStatement stmt = CharacterDatabase.GetPreparedStatement(CharStatements.INS_ITEM_BOP_TRADE); stmt.AddValue(0, item.GetGUID().GetCounter()); stmt.AddValue(1, ss.ToString()); DB.Characters.Execute(stmt); } if (addToCollection) GetSession().GetCollectionMgr().OnItemAdded(item); ItemChildEquipmentRecord childItemEntry = Global.DB2Mgr.GetItemChildEquipment(itemId); if (childItemEntry != null) { ItemTemplate childTemplate = Global.ObjectMgr.GetItemTemplate(childItemEntry.ChildItemID); if (childTemplate != null) { List childDest = new(); CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, childDest, childTemplate, ref count, false, null, ItemConst.NullBag, ItemConst.NullSlot); Item childItem = StoreNewItem(childDest, childTemplate.GetId(), update, 0, null, context, null, addToCollection); if (childItem != null) { childItem.SetCreator(item.GetGUID()); childItem.SetItemFlag(ItemFieldFlags.Child); item.SetChildItem(childItem.GetGUID()); } } } if (item.GetTemplate().GetInventoryType() != InventoryType.NonEquip) UpdateAverageItemLevelTotal(); } return item; } //Move Item InventoryResult CanTakeMoreSimilarItems(Item pItem) { uint notused = 0; return CanTakeMoreSimilarItems(pItem.GetEntry(), pItem.GetCount(), pItem, ref notused); } InventoryResult CanTakeMoreSimilarItems(Item pItem, ref uint offendingItemId) { uint notused = 0; return CanTakeMoreSimilarItems(pItem.GetEntry(), pItem.GetCount(), pItem, ref notused, ref offendingItemId); } InventoryResult CanTakeMoreSimilarItems(uint entry, uint count, Item pItem, ref uint no_space_count) { uint notused = 0; return CanTakeMoreSimilarItems(entry, count, pItem, ref no_space_count, ref notused); } InventoryResult CanTakeMoreSimilarItems(uint entry, uint count, Item pItem, ref uint no_space_count, ref uint offendingItemId) { ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(entry); if (pProto == null) { no_space_count = count; return InventoryResult.ItemMaxCount; } if (pItem != null && pItem.m_lootGenerated) return InventoryResult.LootGone; // no maximum if ((pProto.GetMaxCount() <= 0 && pProto.GetItemLimitCategory() == 0) || pProto.GetMaxCount() == 2147483647) return InventoryResult.Ok; if (pProto.GetMaxCount() > 0) { uint curcount = GetItemCount(pProto.GetId(), true, pItem); if (curcount + count > pProto.GetMaxCount()) { no_space_count = count + curcount - pProto.GetMaxCount(); return InventoryResult.ItemMaxCount; } } // check unique-equipped limit if (pProto.GetItemLimitCategory() != 0) { ItemLimitCategoryRecord limitEntry = CliDB.ItemLimitCategoryStorage.LookupByKey(pProto.GetItemLimitCategory()); if (limitEntry == null) { no_space_count = count; return InventoryResult.NotEquippable; } if (limitEntry.Flags == 0) { byte limitQuantity = GetItemLimitCategoryQuantity(limitEntry); uint curcount = GetItemCountWithLimitCategory(pProto.GetItemLimitCategory(), pItem); if (curcount + count > limitQuantity) { no_space_count = count + curcount - limitQuantity; offendingItemId = pProto.GetId(); return InventoryResult.ItemMaxLimitCategoryCountExceededIs; } } } return InventoryResult.Ok; } //UseItem public InventoryResult CanUseItem(Item pItem, bool not_loading = true) { if (pItem != null) { Log.outDebug(LogFilter.Player, "ItemStorage: CanUseItem item = {0}", pItem.GetEntry()); if (!IsAlive() && not_loading) return InventoryResult.PlayerDead; ItemTemplate pProto = pItem.GetTemplate(); if (pProto != null) { if (pItem.IsBindedNotWith(this)) return InventoryResult.NotOwner; if (GetLevel() < pItem.GetRequiredLevel()) return InventoryResult.CantEquipLevelI; InventoryResult res = CanUseItem(pProto); if (res != InventoryResult.Ok) return res; if (pItem.GetSkill() != 0) { bool allowEquip = false; SkillType itemSkill = pItem.GetSkill(); // Armor that is binded to account can "morph" from plate to mail, etc. if skill is not learned yet. if (pProto.GetQuality() == ItemQuality.Heirloom && pProto.GetClass() == ItemClass.Armor && !HasSkill(itemSkill)) { // TODO: when you right-click already equipped item it throws EQUIP_ERR_PROFICIENCY_NEEDED. // In fact it's a visual bug, everything works properly... I need sniffs of operations with // binded to account items from off server. switch (GetClass()) { case Class.Hunter: case Class.Shaman: allowEquip = (itemSkill == SkillType.Mail); break; case Class.Paladin: case Class.Warrior: allowEquip = (itemSkill == SkillType.PlateMail); break; } } if (!allowEquip && GetSkillValue(itemSkill) == 0) return InventoryResult.ProficiencyNeeded; } return InventoryResult.Ok; } } return InventoryResult.ItemNotFound; } public InventoryResult CanUseItem(ItemTemplate proto, bool skipRequiredLevelCheck = false) { // Used by group, function GroupLoot, to know if a prototype can be used by a player if (proto == null) return InventoryResult.ItemNotFound; if (proto.HasFlag(ItemFlags2.InternalItem)) return InventoryResult.CantEquipEver; if (proto.HasFlag(ItemFlags2.FactionHorde) && GetTeam() != Team.Horde) return InventoryResult.CantEquipEver; if (proto.HasFlag(ItemFlags2.FactionAlliance) && GetTeam() != Team.Alliance) return InventoryResult.CantEquipEver; if ((proto.GetAllowableClass() & GetClassMask()) == 0 || !proto.GetAllowableRace().HasRace(GetRace())) return InventoryResult.CantEquipEver; if (proto.GetRequiredSkill() != 0) { if (GetSkillValue((SkillType)proto.GetRequiredSkill()) == 0) return InventoryResult.ProficiencyNeeded; else if (GetSkillValue((SkillType)proto.GetRequiredSkill()) < proto.GetRequiredSkillRank()) return InventoryResult.CantEquipSkill; } if (proto.GetRequiredSpell() != 0 && !HasSpell(proto.GetRequiredSpell())) return InventoryResult.ProficiencyNeeded; if (!skipRequiredLevelCheck && GetLevel() < proto.GetBaseRequiredLevel()) return InventoryResult.CantEquipLevelI; // If World Event is not active, prevent using event dependant items if (proto.GetHolidayID() != 0 && !Global.GameEventMgr.IsHolidayActive(proto.GetHolidayID())) return InventoryResult.ClientLockedOut; if (proto.GetRequiredReputationFaction() != 0 && (uint)GetReputationRank(proto.GetRequiredReputationFaction()) < proto.GetRequiredReputationRank()) return InventoryResult.CantEquipReputation; // learning (recipes, mounts, pets, etc.) if (proto.Effects.Count >= 2) { if (proto.Effects[0].SpellID == 483 || proto.Effects[0].SpellID == 55884) if (HasSpell((uint)proto.Effects[1].SpellID)) return InventoryResult.InternalBagError; } ArtifactRecord artifact = CliDB.ArtifactStorage.LookupByKey(proto.GetArtifactID()); if (artifact != null) if ((ChrSpecialization)artifact.ChrSpecializationID != GetPrimarySpecialization()) return InventoryResult.CantUseItem; return InventoryResult.Ok; } //Equip/Unequip Item InventoryResult CanUnequipItems(uint item, uint count) { InventoryResult res = InventoryResult.Ok; uint tempcount = 0; bool result = ForEachItem(ItemSearchLocation.Equipment, pItem => { if (pItem.GetEntry() == item) { InventoryResult ires = CanUnequipItem(pItem.GetPos(), false); if (ires == InventoryResult.Ok) { tempcount += pItem.GetCount(); if (tempcount >= count) return false; } else res = ires; } return true; }); if (!result) // we stopped early due to a sucess return InventoryResult.Ok; return res; // return latest error if any } Item EquipNewItem(ushort pos, uint item, ItemContext context, bool update) { Item pItem = Item.CreateItem(item, 1, context, this); if (pItem != null) { UpdateCriteria(CriteriaType.ObtainAnyItem, item, 1); Item equippedItem = EquipItem(pos, pItem, update); ItemAddedQuestCheck(item, 1); return equippedItem; } return null; } public Item EquipItem(ushort pos, Item pItem, bool update) { AddEnchantmentDurations(pItem); AddItemDurations(pItem); byte bag = (byte)(pos >> 8); byte slot = (byte)(pos & 255); Item pItem2 = GetItemByPos(bag, slot); if (pItem2 == null) { VisualizeItem(slot, pItem); if (IsAlive()) { 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.AddItemsSetItem(this, pItem); _ApplyItemMods(pItem, slot, true); if (pProto != null && IsInCombat() && (pProto.GetClass() == ItemClass.Weapon || pProto.GetInventoryType() == InventoryType.Relic) && m_weaponChangeTimer == 0) { uint cooldownSpell = (uint)(GetClass() == Class.Rogue ? 6123 : 6119); var spellProto = Global.SpellMgr.GetSpellInfo(cooldownSpell, Difficulty.None); if (spellProto == null) Log.outError(LogFilter.Player, "Weapon switch cooldown spell {0} couldn't be found in Spell.dbc", cooldownSpell); else { m_weaponChangeTimer = spellProto.StartRecoveryTime; GetSpellHistory().AddGlobalCooldown(spellProto, TimeSpan.FromMilliseconds(m_weaponChangeTimer)); SpellCooldownPkt spellCooldown = new(); spellCooldown.Caster = GetGUID(); spellCooldown.Flags = SpellCooldownFlags.IncludeGCD; spellCooldown.SpellCooldowns.Add(new SpellCooldownStruct(cooldownSpell, 0)); SendPacket(spellCooldown); } } } pItem.SetItemFlag2(ItemFieldFlags2.Equipped); if (IsInWorld && update) { pItem.AddToWorld(); pItem.SendUpdateToPlayer(this); } ApplyEquipCooldown(pItem); // update expertise and armor penetration - passive auras may need it if (slot == EquipmentSlot.MainHand) UpdateExpertise(WeaponAttackType.BaseAttack); else if (slot == EquipmentSlot.OffHand) UpdateExpertise(WeaponAttackType.OffAttack); switch (slot) { case EquipmentSlot.MainHand: case EquipmentSlot.OffHand: RecalculateRating(CombatRating.ArmorPenetration); break; } } else { pItem2.SetCount(pItem2.GetCount() + pItem.GetCount()); if (IsInWorld && update) pItem2.SendUpdateToPlayer(this); if (IsInWorld && update) { pItem.RemoveFromWorld(); pItem.DestroyForPlayer(this); } RemoveEnchantmentDurations(pItem); RemoveItemDurations(pItem); pItem.SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor pItem.SetNotRefundable(this); pItem.ClearSoulboundTradeable(this); RemoveTradeableItem(pItem); pItem.SetState(ItemUpdateState.Removed, this); pItem2.SetState(ItemUpdateState.Changed, this); ApplyEquipCooldown(pItem2); return pItem2; } if (slot == EquipmentSlot.MainHand || slot == EquipmentSlot.OffHand) CheckTitanGripPenalty(); // only for full equip instead adding to stack UpdateCriteria(CriteriaType.EquipItem, pItem.GetEntry()); UpdateCriteria(CriteriaType.EquipItemInSlot, slot, pItem.GetEntry()); UpdateAverageItemLevelEquipped(); return pItem; } public void EquipChildItem(byte parentBag, byte parentSlot, Item parentItem) { ItemChildEquipmentRecord itemChildEquipment = Global.DB2Mgr.GetItemChildEquipment(parentItem.GetEntry()); if (itemChildEquipment != null) { Item childItem = GetChildItemByGuid(parentItem.GetChildItem()); if (childItem != null) { ushort childDest = (ushort)((InventorySlots.Bag0 << 8) | itemChildEquipment.ChildItemEquipSlot); if (childItem.GetPos() != childDest) { Item dstItem = GetItemByPos(childDest); if (dstItem == null) // empty slot, simple case { RemoveItem(childItem.GetBagSlot(), childItem.GetSlot(), true); EquipItem(childDest, childItem, true); AutoUnequipOffhandIfNeed(); } else // have currently equipped item, not simple case { byte dstbag = dstItem.GetBagSlot(); byte dstslot = dstItem.GetSlot(); InventoryResult msg = CanUnequipItem(childDest, !childItem.IsBag()); if (msg != InventoryResult.Ok) { SendEquipError(msg, dstItem); return; } // check dest.src move possibility but try to store currently equipped item in the bag where the parent item is List sSrc = new(); ushort eSrc = 0; if (IsInventoryPos(parentBag, parentSlot)) { msg = CanStoreItem(parentBag, ItemConst.NullSlot, sSrc, dstItem, true); if (msg != InventoryResult.Ok) msg = CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true); } else if (IsBankPos(parentBag, parentSlot)) { msg = CanBankItem(parentBag, ItemConst.NullSlot, sSrc, dstItem, true); if (msg != InventoryResult.Ok) msg = CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true); } else if (IsEquipmentPos(parentBag, parentSlot)) { msg = CanEquipItem(parentSlot, out eSrc, dstItem, true); if (msg == InventoryResult.Ok) msg = CanUnequipItem(eSrc, true); } if (msg != InventoryResult.Ok) { SendEquipError(msg, dstItem, childItem); return; } // now do moves, remove... RemoveItem(dstbag, dstslot, false); RemoveItem(childItem.GetBagSlot(), childItem.GetSlot(), false); // add to dest EquipItem(childDest, childItem, true); // add to src if (IsInventoryPos(parentBag, parentSlot)) StoreItem(sSrc, dstItem, true); else if (IsBankPos(parentBag, parentSlot)) BankItem(sSrc, dstItem, true); else if (IsEquipmentPos(parentBag, parentSlot)) EquipItem(eSrc, dstItem, true); AutoUnequipOffhandIfNeed(); } } } } } public void AutoUnequipChildItem(Item parentItem) { if (Global.DB2Mgr.GetItemChildEquipment(parentItem.GetEntry()) != null) { Item childItem = GetChildItemByGuid(parentItem.GetChildItem()); if (childItem != null) { if (IsChildEquipmentPos(childItem.GetPos())) return; List dest = new(); uint count = childItem.GetCount(); InventoryResult result = CanStoreItem_InInventorySlots(InventorySlots.ChildEquipmentStart, InventorySlots.ChildEquipmentEnd, dest, childItem.GetTemplate(), ref count, false, childItem, ItemConst.NullBag, ItemConst.NullSlot); if (result != InventoryResult.Ok) return; RemoveItem(childItem.GetBagSlot(), childItem.GetSlot(), true); StoreItem(dest, childItem, true); } } } void QuickEquipItem(ushort pos, Item pItem) { if (pItem != null) { AddEnchantmentDurations(pItem); AddItemDurations(pItem); byte slot = (byte)(pos & 255); VisualizeItem(slot, pItem); pItem.SetItemFlag2(ItemFieldFlags2.Equipped); if (IsInWorld) { pItem.AddToWorld(); pItem.SendUpdateToPlayer(this); } if (slot == EquipmentSlot.MainHand || slot == EquipmentSlot.OffHand) CheckTitanGripPenalty(); UpdateCriteria(CriteriaType.EquipItem, pItem.GetEntry()); UpdateCriteria(CriteriaType.EquipItemInSlot, slot, pItem.GetEntry()); } } public void SendEquipError(InventoryResult msg, Item item1 = null, Item item2 = null, uint itemId = 0) { InventoryChangeFailure failure = new(); failure.BagResult = msg; if (msg != InventoryResult.Ok) { if (item1 != null) failure.Item[0] = item1.GetGUID(); if (item2 != null) failure.Item[1] = item2.GetGUID(); failure.ContainerBSlot = 0; // bag equip slot, used with EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM and EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2 switch (msg) { case InventoryResult.CantEquipLevelI: case InventoryResult.PurchaseLevelTooLow: { failure.Level = (item1 != null ? item1.GetRequiredLevel() : 0); break; } case InventoryResult.EventAutoequipBindConfirm: // no idea about this one... { //failure.SrcContainer //failure.SrcSlot //failure.DstContainer break; } case InventoryResult.ItemMaxLimitCategoryCountExceededIs: case InventoryResult.ItemMaxLimitCategorySocketedExceededIs: case InventoryResult.ItemMaxLimitCategoryEquippedExceededIs: { ItemTemplate proto = item1 != null ? item1.GetTemplate() : Global.ObjectMgr.GetItemTemplate(itemId); failure.LimitCategory = (int)(proto != null ? proto.GetItemLimitCategory() : 0u); break; } default: break; } } SendPacket(failure); } //Add/Remove/Misc Item public bool AddItem(uint itemId, uint count) { uint noSpaceForCount; List dest = new(); InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, itemId, count, out noSpaceForCount); if (msg != InventoryResult.Ok) count -= noSpaceForCount; if (count == 0 || dest.Empty()) { // @todo Send to mailbox if no space SendSysMessage("You don't have any space in your bags."); return false; } Item item = StoreNewItem(dest, itemId, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(itemId)); if (item != null) SendNewItem(item, count, true, false); else return false; return true; } public void RemoveItem(byte bag, byte slot, bool update) { // note: removeitem does not actually change the item // it only takes the item out of storage temporarily // note2: if removeitem is to be used for delinking // the item must be removed from the player's updatequeue Item pItem = GetItemByPos(bag, slot); if (pItem != null) { Log.outDebug(LogFilter.Player, "STORAGE: RemoveItem bag = {0}, slot = {1}, item = {2}", bag, slot, pItem.GetEntry()); RemoveEnchantmentDurations(pItem); RemoveItemDurations(pItem); RemoveTradeableItem(pItem); if (bag == InventorySlots.Bag0) { if (slot < InventorySlots.BagEnd) { // item set bonuses applied only at equip and removed at unequip, and still active for broken items ItemTemplate pProto = pItem.GetTemplate(); if (pProto != null && pProto.GetItemSet() != 0) Item.RemoveItemsSetItem(this, pItem); _ApplyItemMods(pItem, slot, false, update); pItem.RemoveItemFlag2(ItemFieldFlags2.Equipped); // remove item dependent auras and casts (only weapon and armor slots) if (slot < ProfessionSlots.End) { // update expertise if (slot == EquipmentSlot.MainHand) { // clear main hand only enchantments for (EnchantmentSlot enchantSlot = 0; enchantSlot < EnchantmentSlot.Max; ++enchantSlot) { var enchantment = CliDB.SpellItemEnchantmentStorage.LookupByKey(pItem.GetEnchantmentId(enchantSlot)); if (enchantment != null && enchantment.HasFlag(SpellItemEnchantmentFlags.MainhandOnly)) pItem.ClearEnchantment(enchantSlot); } UpdateExpertise(WeaponAttackType.BaseAttack); } else if (slot == EquipmentSlot.OffHand) UpdateExpertise(WeaponAttackType.OffAttack); // update armor penetration - passive auras may need it switch (slot) { case EquipmentSlot.MainHand: case EquipmentSlot.OffHand: RecalculateRating(CombatRating.ArmorPenetration); break; } } } m_items[slot] = null; SetInvSlot(slot, ObjectGuid.Empty); if (slot < EquipmentSlot.End) { SetVisibleItemSlot(slot, null); if (slot == EquipmentSlot.MainHand || slot == EquipmentSlot.OffHand) CheckTitanGripPenalty(); } } Bag pBag = GetBagByPos(bag); if (pBag != null) pBag.RemoveItem(slot, update); pItem.SetContainedIn(ObjectGuid.Empty); pItem.SetSlot(ItemConst.NullSlot); if (IsInWorld && update) pItem.SendUpdateToPlayer(this); AutoUnequipChildItem(pItem); if (bag == InventorySlots.Bag0) UpdateAverageItemLevelEquipped(); } } public void SplitItem(ushort src, ushort dst, uint count) { byte srcbag = (byte)(src >> 8); byte srcslot = (byte)(src & 255); byte dstbag = (byte)(dst >> 8); byte dstslot = (byte)(dst & 255); Item pSrcItem = GetItemByPos(srcbag, srcslot); if (pSrcItem == null) { SendEquipError(InventoryResult.ItemNotFound, pSrcItem); return; } if (pSrcItem.m_lootGenerated) // prevent split looting item (item { //best error message found for attempting to split while looting SendEquipError(InventoryResult.SplitFailed, pSrcItem); return; } // not let split all items (can be only at cheating) if (pSrcItem.GetCount() == count) { SendEquipError(InventoryResult.SplitFailed, pSrcItem); return; } // not let split more existed items (can be only at cheating) if (pSrcItem.GetCount() < count) { SendEquipError(InventoryResult.TooFewToSplit, pSrcItem); return; } //! If trading TradeData tradeData = GetTradeData(); if (tradeData != null) { //! If current item is in trade window (only possible with packet spoofing - silent return) if (tradeData.GetTradeSlotForItem(pSrcItem.GetGUID()) != TradeSlots.Invalid) return; } Log.outDebug(LogFilter.Player, "STORAGE: SplitItem bag = {0}, slot = {1}, item = {2}, count = {3}", dstbag, dstslot, pSrcItem.GetEntry(), count); Item pNewItem = pSrcItem.CloneItem(count, this); if (pNewItem == null) { SendEquipError(InventoryResult.ItemNotFound, pSrcItem); return; } if (IsInventoryPos(dst)) { // change item amount before check (for unique max count check) pSrcItem.SetCount(pSrcItem.GetCount() - count); List dest = new(); InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pNewItem, false); if (msg != InventoryResult.Ok) { pSrcItem.SetCount(pSrcItem.GetCount() + count); SendEquipError(msg, pSrcItem); return; } if (IsInWorld) pSrcItem.SendUpdateToPlayer(this); pSrcItem.SetState(ItemUpdateState.Changed, this); StoreItem(dest, pNewItem, true); } else if (IsBankPos(dst)) { // change item amount before check (for unique max count check) pSrcItem.SetCount(pSrcItem.GetCount() - count); List dest = new(); InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pNewItem, false); if (msg != InventoryResult.Ok) { pSrcItem.SetCount(pSrcItem.GetCount() + count); SendEquipError(msg, pSrcItem); return; } if (IsInWorld) pSrcItem.SendUpdateToPlayer(this); pSrcItem.SetState(ItemUpdateState.Changed, this); BankItem(dest, pNewItem, true); } else if (IsEquipmentPos(dst)) { // change item amount before check (for unique max count check), provide space for splitted items pSrcItem.SetCount(pSrcItem.GetCount() - count); ushort dest; InventoryResult msg = CanEquipItem(dstslot, out dest, pNewItem, false); if (msg != InventoryResult.Ok) { pSrcItem.SetCount(pSrcItem.GetCount() + count); SendEquipError(msg, pSrcItem); return; } if (IsInWorld) pSrcItem.SendUpdateToPlayer(this); pSrcItem.SetState(ItemUpdateState.Changed, this); EquipItem(dest, pNewItem, true); AutoUnequipOffhandIfNeed(); } } public void SwapItem(ushort src, ushort dst) { byte srcbag = (byte)(src >> 8); byte srcslot = (byte)(src & 255); byte dstbag = (byte)(dst >> 8); byte dstslot = (byte)(dst & 255); Item pSrcItem = GetItemByPos(srcbag, srcslot); Item pDstItem = GetItemByPos(dstbag, dstslot); if (pSrcItem == null) return; if (pSrcItem.HasItemFlag(ItemFieldFlags.Child)) { Item parentItem = GetItemByGuid(pSrcItem.m_itemData.Creator); if (parentItem != null) { if (IsEquipmentPos(src)) { AutoUnequipChildItem(parentItem); // we need to unequip child first since it cannot go into whatever is going to happen next SwapItem(dst, src); // src is now empty SwapItem(parentItem.GetPos(), dst);// dst is now empty return; } } } else if (pDstItem != null && pDstItem.HasItemFlag(ItemFieldFlags.Child)) { Item parentItem = GetItemByGuid(pDstItem.m_itemData.Creator); if (parentItem != null) { if (IsEquipmentPos(dst)) { AutoUnequipChildItem(parentItem); // we need to unequip child first since it cannot go into whatever is going to happen next SwapItem(src, dst); // dst is now empty SwapItem(parentItem.GetPos(), src);// src is now empty return; } } } Log.outDebug(LogFilter.Player, "STORAGE: SwapItem bag = {0}, slot = {1}, item = {2}", dstbag, dstslot, pSrcItem.GetEntry()); if (!IsAlive()) { SendEquipError(InventoryResult.PlayerDead, pSrcItem, pDstItem); return; } // SRC checks // check unequip potability for equipped items and bank bags if (IsEquipmentPos(src) || IsBagPos(src)) { // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later) InventoryResult msg = CanUnequipItem(src, !IsBagPos(src) || IsBagPos(dst) || (pDstItem != null && pDstItem.ToBag() != null && pDstItem.ToBag().IsEmpty())); if (msg != InventoryResult.Ok) { SendEquipError(msg, pSrcItem, pDstItem); return; } } // prevent put equipped/bank bag in self if (IsBagPos(src) && srcslot == dstbag) { SendEquipError(InventoryResult.BagInBag, pSrcItem, pDstItem); return; } // prevent equipping bag in the same slot from its inside if (IsBagPos(dst) && srcbag == dstslot) { SendEquipError(InventoryResult.CantSwap, pSrcItem, pDstItem); return; } // DST checks if (pDstItem != null) { // check unequip potability for equipped items and bank bags if (IsEquipmentPos(dst) || IsBagPos(dst)) { // bags can be swapped with empty bag slots, or with empty bag (items move possibility checked later) InventoryResult msg = CanUnequipItem(dst, !IsBagPos(dst) || IsBagPos(src) || (pSrcItem.ToBag() != null && pSrcItem.ToBag().IsEmpty())); if (msg != InventoryResult.Ok) { SendEquipError(msg, pSrcItem, pDstItem); return; } } } if (IsReagentBankPos(dst) && !IsReagentBankUnlocked()) { SendEquipError(InventoryResult.ReagentBankLocked, pSrcItem, pDstItem); return; } // NOW this is or item move (swap with empty), or swap with another item (including bags in bag possitions) // or swap empty bag with another empty or not empty bag (with items exchange) // Move case if (pDstItem == null) { if (IsInventoryPos(dst)) { List dest = new(); InventoryResult msg = CanStoreItem(dstbag, dstslot, dest, pSrcItem, false); if (msg != InventoryResult.Ok) { SendEquipError(msg, pSrcItem); return; } RemoveItem(srcbag, srcslot, true); StoreItem(dest, pSrcItem, true); if (IsBankPos(src)) ItemAddedQuestCheck(pSrcItem.GetEntry(), pSrcItem.GetCount()); } else if (IsBankPos(dst)) { List dest = new(); InventoryResult msg = CanBankItem(dstbag, dstslot, dest, pSrcItem, false); if (msg != InventoryResult.Ok) { SendEquipError(msg, pSrcItem); return; } RemoveItem(srcbag, srcslot, true); BankItem(dest, pSrcItem, true); if (!IsReagentBankPos(dst)) ItemRemovedQuestCheck(pSrcItem.GetEntry(), pSrcItem.GetCount()); } else if (IsEquipmentPos(dst)) { ushort _dest; InventoryResult msg = CanEquipItem(dstslot, out _dest, pSrcItem, false); if (msg != InventoryResult.Ok) { SendEquipError(msg, pSrcItem); return; } RemoveItem(srcbag, srcslot, true); EquipItem(_dest, pSrcItem, true); AutoUnequipOffhandIfNeed(); } return; } // attempt merge to / fill target item if (!pSrcItem.IsBag() && !pDstItem.IsBag()) { InventoryResult msg; List sDest = new(); ushort eDest = 0; if (IsInventoryPos(dst)) msg = CanStoreItem(dstbag, dstslot, sDest, pSrcItem, false); else if (IsBankPos(dst)) msg = CanBankItem(dstbag, dstslot, sDest, pSrcItem, false); else if (IsEquipmentPos(dst)) msg = CanEquipItem(dstslot, out eDest, pSrcItem, false); else return; if (msg == InventoryResult.Ok && IsEquipmentPos(dst) && !pSrcItem.GetChildItem().IsEmpty()) msg = CanEquipChildItem(pSrcItem); // can be merge/fill if (msg == InventoryResult.Ok) { if (pSrcItem.GetCount() + pDstItem.GetCount() <= pSrcItem.GetTemplate().GetMaxStackSize()) { RemoveItem(srcbag, srcslot, true); if (IsInventoryPos(dst)) StoreItem(sDest, pSrcItem, true); else if (IsBankPos(dst)) BankItem(sDest, pSrcItem, true); else if (IsEquipmentPos(dst)) { EquipItem(eDest, pSrcItem, true); if (!pSrcItem.GetChildItem().IsEmpty()) EquipChildItem(srcbag, srcslot, pSrcItem); AutoUnequipOffhandIfNeed(); } } else { pSrcItem.SetCount(pSrcItem.GetCount() + pDstItem.GetCount() - pSrcItem.GetTemplate().GetMaxStackSize()); pDstItem.SetCount(pSrcItem.GetTemplate().GetMaxStackSize()); pSrcItem.SetState(ItemUpdateState.Changed, this); pDstItem.SetState(ItemUpdateState.Changed, this); if (IsInWorld) { pSrcItem.SendUpdateToPlayer(this); pDstItem.SendUpdateToPlayer(this); } } SendRefundInfo(pDstItem); return; } } // impossible merge/fill, do real swap InventoryResult _msg = InventoryResult.Ok; // check src.dest move possibility List _sDest = new(); ushort _eDest = 0; if (IsInventoryPos(dst)) _msg = CanStoreItem(dstbag, dstslot, _sDest, pSrcItem, true); else if (IsBankPos(dst)) _msg = CanBankItem(dstbag, dstslot, _sDest, pSrcItem, true); else if (IsEquipmentPos(dst)) { _msg = CanEquipItem(dstslot, out _eDest, pSrcItem, true); if (_msg == InventoryResult.Ok) _msg = CanUnequipItem(_eDest, true); } if (_msg != InventoryResult.Ok) { SendEquipError(_msg, pSrcItem, pDstItem); return; } // check dest.src move possibility List sDest2 = new(); ushort eDest2 = 0; if (IsInventoryPos(src)) _msg = CanStoreItem(srcbag, srcslot, sDest2, pDstItem, true); else if (IsBankPos(src)) _msg = CanBankItem(srcbag, srcslot, sDest2, pDstItem, true); else if (IsEquipmentPos(src)) { _msg = CanEquipItem(srcslot, out eDest2, pDstItem, true); if (_msg == InventoryResult.Ok) _msg = CanUnequipItem(eDest2, true); } if (_msg == InventoryResult.Ok && IsEquipmentPos(dst) && !pSrcItem.GetChildItem().IsEmpty()) _msg = CanEquipChildItem(pSrcItem); if (_msg != InventoryResult.Ok) { SendEquipError(_msg, pDstItem, pSrcItem); return; } // Check bag swap with item exchange (one from empty in not bag possition (equipped (not possible in fact) or store) Bag srcBag = pSrcItem.ToBag(); if (srcBag != null) { Bag dstBag = pDstItem.ToBag(); if (dstBag != null) { Bag emptyBag = null; Bag fullBag = null; if (srcBag.IsEmpty() && !IsBagPos(src)) { emptyBag = srcBag; fullBag = dstBag; } else if (dstBag.IsEmpty() && !IsBagPos(dst)) { emptyBag = dstBag; fullBag = srcBag; } // bag swap (with items exchange) case if (emptyBag != null && fullBag != null) { ItemTemplate emptyProto = emptyBag.GetTemplate(); byte count = 0; for (byte i = 0; i < fullBag.GetBagSize(); ++i) { Item bagItem = fullBag.GetItemByPos(i); if (bagItem == null) continue; ItemTemplate bagItemProto = bagItem.GetTemplate(); if (bagItemProto == null || !Item.ItemCanGoIntoBag(bagItemProto, emptyProto)) { // one from items not go to empty target bag SendEquipError(InventoryResult.BagInBag, pSrcItem, pDstItem); return; } ++count; } if (count > emptyBag.GetBagSize()) { // too small targeted bag SendEquipError(InventoryResult.CantSwap, pSrcItem, pDstItem); return; } // Items swap count = 0; // will pos in new bag for (byte i = 0; i < fullBag.GetBagSize(); ++i) { Item bagItem = fullBag.GetItemByPos(i); if (bagItem == null) continue; fullBag.RemoveItem(i, true); emptyBag.StoreItem(count, bagItem, true); bagItem.SetState(ItemUpdateState.Changed, this); ++count; } } } } // now do moves, remove... RemoveItem(dstbag, dstslot, false); RemoveItem(srcbag, srcslot, false); // add to dest if (IsInventoryPos(dst)) StoreItem(_sDest, pSrcItem, true); else if (IsBankPos(dst)) BankItem(_sDest, pSrcItem, true); else if (IsEquipmentPos(dst)) { EquipItem(_eDest, pSrcItem, true); if (!pSrcItem.GetChildItem().IsEmpty()) EquipChildItem(srcbag, srcslot, pSrcItem); } // add to src if (IsInventoryPos(src)) StoreItem(sDest2, pDstItem, true); else if (IsBankPos(src)) BankItem(sDest2, pDstItem, true); else if (IsEquipmentPos(src)) EquipItem(eDest2, pDstItem, true); // if inventory item was moved, check if we can remove dependent auras, because they were not removed in Player::RemoveItem (update was set to false) // do this after swaps are done, we pass nullptr because both weapons could be swapped and none of them should be ignored if ((srcbag == InventorySlots.Bag0 && srcslot < InventorySlots.BagEnd) || (dstbag == InventorySlots.Bag0 && dstslot < InventorySlots.BagEnd)) ApplyItemDependentAuras(null, false); // if player is moving bags and is looting an item inside this bag // release the loot if (!GetAELootView().Empty()) { bool released = false; if (IsBagPos(src)) { Bag bag = pSrcItem.ToBag(); for (byte i = 0; i < bag.GetBagSize(); ++i) { Item bagItem = bag.GetItemByPos(i); if (bagItem != null) { if (GetLootByWorldObjectGUID(bagItem.GetGUID()) != null) { GetSession().DoLootReleaseAll(); released = true; // so we don't need to look at dstBag break; } } } } if (!released && IsBagPos(dst)) { Bag bag = pDstItem.ToBag(); for (byte i = 0; i < bag.GetBagSize(); ++i) { Item bagItem = bag.GetItemByPos(i); if (bagItem != null) { if (GetLootByWorldObjectGUID(bagItem.GetGUID()) != null) { GetSession().DoLootReleaseAll(); break; } } } } } AutoUnequipOffhandIfNeed(); } bool _StoreOrEquipNewItem(uint vendorslot, uint item, byte count, byte bag, byte slot, long price, ItemTemplate pProto, Creature pVendor, VendorItem crItem, bool bStore) { uint stacks = count / pProto.GetBuyCount(); List vDest = new(); ushort uiDest = 0; InventoryResult msg = bStore ? CanStoreNewItem(bag, slot, vDest, item, count) : CanEquipNewItem(slot, out uiDest, item, false); if (msg != InventoryResult.Ok) { SendEquipError(msg, null, null, item); return false; } ModifyMoney(-price); if (crItem.ExtendedCost != 0) // case for new honor system { var iece = CliDB.ItemExtendedCostStorage.LookupByKey(crItem.ExtendedCost); for (int i = 0; i < ItemConst.MaxItemExtCostItems; ++i) { if (iece.ItemID[i] != 0) DestroyItemCount(iece.ItemID[i], iece.ItemCount[i] * stacks, true); } for (int i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) { if (iece.Flags.HasAnyFlag((byte)((int)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) continue; if (iece.CurrencyID[i] != 0) RemoveCurrency(iece.CurrencyID[i], (int)(iece.CurrencyCount[i] * stacks), CurrencyDestroyReason.Vendor); } } Item it = bStore ? StoreNewItem(vDest, item, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(item), null, ItemContext.Vendor, crItem.BonusListIDs, false) : EquipNewItem(uiDest, item, ItemContext.Vendor, true); if (it != null) { uint new_count = pVendor.UpdateVendorItemCurrentCount(crItem, count); BuySucceeded packet = new(); packet.VendorGUID = pVendor.GetGUID(); packet.Muid = vendorslot + 1; packet.NewQuantity = crItem.maxcount > 0 ? new_count : 0xFFFFFFFF; packet.QuantityBought = count; SendPacket(packet); SendNewItem(it, count, true, false, false); if (!bStore) AutoUnequipOffhandIfNeed(); if (pProto.HasFlag(ItemFlags.ItemPurchaseRecord) && crItem.ExtendedCost != 0 && pProto.GetMaxStackSize() == 1) { it.SetItemFlag(ItemFieldFlags.Refundable); it.SetRefundRecipient(GetGUID()); it.SetPaidMoney((uint)price); it.SetPaidExtendedCost(crItem.ExtendedCost); it.SaveRefundDataToDB(); AddRefundReference(it.GetGUID()); } GetSession().GetCollectionMgr().OnItemAdded(it); } return true; } public void SendNewItem(Item item, uint quantity, bool pushed, bool created, bool broadcast = false, uint dungeonEncounterId = 0) { if (item == null) // prevent crash return; ItemPushResult packet = new(); packet.PlayerGUID = GetGUID(); packet.Slot = item.GetBagSlot(); packet.SlotInBag = item.GetCount() == quantity ? item.GetSlot() : -1; packet.Item = new ItemInstance(item); packet.QuestLogItemID = item.GetTemplate().QuestLogItemId; packet.Quantity = quantity; packet.QuantityInInventory = GetItemCount(item.GetEntry()); packet.BattlePetSpeciesID = (int)item.GetModifier(ItemModifier.BattlePetSpeciesId); packet.BattlePetBreedID = (int)item.GetModifier(ItemModifier.BattlePetBreedData) & 0xFFFFFF; packet.BattlePetBreedQuality = (item.GetModifier(ItemModifier.BattlePetBreedData) >> 24) & 0xFF; packet.BattlePetLevel = (int)item.GetModifier(ItemModifier.BattlePetLevel); packet.ItemGUID = item.GetGUID(); packet.Pushed = pushed; packet.DisplayText = ItemPushResult.DisplayType.Normal; packet.Created = created; //packet.IsBonusRoll; if (dungeonEncounterId != 0) { packet.DisplayText = ItemPushResult.DisplayType.EncounterLoot; packet.DungeonEncounterID = (int)dungeonEncounterId; packet.IsEncounterLoot = true; } if (broadcast && GetGroup() != null && !item.GetTemplate().HasFlag(ItemFlags3.DontReportLootLogToParty)) GetGroup().BroadcastPacket(packet, true); else SendPacket(packet); } //Item Durations void RemoveItemDurations(Item item) { m_itemDuration.Remove(item); } void AddItemDurations(Item item) { if (item.m_itemData.Expiration != 0) { m_itemDuration.Add(item); item.SendTimeUpdate(this); } } void UpdateItemDuration(uint time, bool realtimeonly = false) { if (m_itemDuration.Empty()) return; Log.outDebug(LogFilter.Player, "Player:UpdateItemDuration({0}, {1})", time, realtimeonly); foreach (var item in m_itemDuration) { if (!realtimeonly || item.GetTemplate().HasFlag(ItemFlags.RealDuration)) item.UpdateDuration(this, time); } } void SendEnchantmentDurations() { foreach (var enchantDuration in m_enchantDuration) GetSession().SendItemEnchantTimeUpdate(GetGUID(), enchantDuration.item.GetGUID(), (uint)enchantDuration.slot, enchantDuration.leftduration / 1000); } void SendItemDurations() { foreach (var item in m_itemDuration) item.SendTimeUpdate(this); } public void ToggleMetaGemsActive(uint exceptslot, bool apply) { //cycle all equipped items for (byte slot = EquipmentSlot.Start; slot < EquipmentSlot.End; ++slot) { //enchants for the slot being socketed are handled by WorldSession.HandleSocketOpcode(WorldPacket& recvData) if (slot == exceptslot) continue; Item pItem = GetItemByPos(InventorySlots.Bag0, slot); if (pItem == null || pItem.GetSocketColor(0) == 0) //if item has no sockets or no item is equipped go to next item continue; //cycle all (gem)enchants for (EnchantmentSlot enchant_slot = EnchantmentSlot.Sock1; enchant_slot < EnchantmentSlot.Sock1 + 3; ++enchant_slot) { uint enchant_id = pItem.GetEnchantmentId(enchant_slot); if (enchant_id == 0) //if no enchant go to next enchant(slot) continue; SpellItemEnchantmentRecord enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); if (enchantEntry == null) continue; //only metagems to be (de)activated, so only enchants with condition uint condition = enchantEntry.ConditionID; if (condition != 0) ApplyEnchantment(pItem, enchant_slot, apply); } } } public float GetAverageItemLevel() { float sum = 0; uint count = 0; for (int i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) { // don't check tabard, ranged, offhand or shirt if (i == EquipmentSlot.Tabard || i == EquipmentSlot.Ranged || i == EquipmentSlot.OffHand || i == EquipmentSlot.Shirt) continue; if (m_items[i] != null) sum += m_items[i].GetItemLevel(this); ++count; } return sum / count; } public List GetCraftingReagentItemsToDeposit() { List itemList = new(); ForEachItem(ItemSearchLocation.Inventory, item => { if (item.GetTemplate().IsCraftingReagent()) itemList.Add(item); return true; }); return itemList; } public Item GetItemByGuid(ObjectGuid guid) { Item result = null; ForEachItem(ItemSearchLocation.Everywhere, item => { if (item.GetGUID() == guid) { result = item; return false; } return true; }); return result; } public uint GetItemCount(uint item, bool inBankAlso = false, Item skipItem = null) { bool countGems = skipItem != null && skipItem.GetTemplate().GetGemProperties() != 0; ItemSearchLocation location = ItemSearchLocation.Equipment | ItemSearchLocation.Inventory | ItemSearchLocation.ReagentBank; if (inBankAlso) location |= ItemSearchLocation.Bank; uint count = 0; ForEachItem(location, pItem => { if (pItem != skipItem) { if (pItem.GetEntry() == item) count += pItem.GetCount(); if (countGems) count += pItem.GetGemCountWithID(item); } return true; }); return count; } public Item GetUseableItemByPos(byte bag, byte slot) { Item item = GetItemByPos(bag, slot); if (item == null) return null; if (!CanUseAttackType(GetAttackBySlot(slot, item.GetTemplate().GetInventoryType()))) return null; return item; } public Item GetItemByPos(ushort pos) { byte bag = (byte)(pos >> 8); byte slot = (byte)(pos & 255); return GetItemByPos(bag, slot); } public Item GetItemByPos(byte bag, byte slot) { if (bag == InventorySlots.Bag0 && slot < (int)PlayerSlots.End && (slot < InventorySlots.BuyBackStart || slot >= InventorySlots.BuyBackEnd)) return m_items[slot]; Bag pBag = GetBagByPos(bag); if (pBag != null) return pBag.GetItemByPos(slot); return null; } public Item GetItemByEntry(uint entry, ItemSearchLocation where = ItemSearchLocation.Default) { Item result = null; ForEachItem(where, item => { if (item.GetEntry() == entry) { result = item; return false; } return true; }); return result; } public List GetItemListByEntry(uint entry, bool inBankAlso = false) { ItemSearchLocation location = ItemSearchLocation.Equipment | ItemSearchLocation.Inventory | ItemSearchLocation.ReagentBank; if (inBankAlso) location |= ItemSearchLocation.Bank; List itemList = new(); ForEachItem(location, item => { if (item.GetEntry() == entry) itemList.Add(item); return true; }); return itemList; } public bool HasItemCount(uint item, uint count = 1, bool inBankAlso = false) { ItemSearchLocation location = ItemSearchLocation.Equipment | ItemSearchLocation.Inventory | ItemSearchLocation.ReagentBank; if (inBankAlso) location |= ItemSearchLocation.Bank; uint currentCount = 0; return !ForEachItem(location, pItem => { if (pItem != null && pItem.GetEntry() == item && !pItem.IsInTrade()) { currentCount += pItem.GetCount(); if (currentCount >= count) return false; } return true; }); } public static bool IsChildEquipmentPos(byte bag, byte slot) { return bag == InventorySlots.Bag0 && (slot >= InventorySlots.ChildEquipmentStart && slot < InventorySlots.ChildEquipmentEnd); } public bool IsValidPos(byte bag, byte slot, bool explicit_pos) { // post selected if (bag == ItemConst.NullBag && !explicit_pos) return true; if (bag == InventorySlots.Bag0) { // any post selected if (slot == ItemConst.NullSlot && !explicit_pos) return true; // equipment if (slot < EquipmentSlot.End) return true; // profession equipment if (slot >= ProfessionSlots.Start && slot < ProfessionSlots.End) return true; // bag equip slots if (slot >= InventorySlots.BagStart && slot < InventorySlots.BagEnd) return true; // reagent bag equip slots if (slot >= InventorySlots.ReagentBagStart && slot < InventorySlots.ReagentBagEnd) return true; // backpack slots if (slot >= InventorySlots.ItemStart && slot < InventorySlots.ItemStart + GetInventorySlotCount()) return true; // bank main slots if (slot >= InventorySlots.BankItemStart && slot < InventorySlots.BankItemEnd) return true; // bank bag slots if (slot >= InventorySlots.BankBagStart && slot < InventorySlots.BankBagEnd) return true; // reagent bank bag slots if (slot >= InventorySlots.ReagentStart && slot < InventorySlots.ReagentEnd) return true; return false; } // bag content slots // bank bag content slots Bag pBag = GetBagByPos(bag); if (pBag != null) { // any post selected if (slot == ItemConst.NullSlot && !explicit_pos) return true; return slot < pBag.GetBagSize(); } // where this? return false; } public Item GetChildItemByGuid(ObjectGuid guid) { Item result = null; ForEachItem(ItemSearchLocation.Equipment | ItemSearchLocation.Inventory, item => { if (item.GetGUID() == guid) { result = item; return false; } return true; }); return result; } uint GetItemCountWithLimitCategory(uint limitCategory, Item skipItem) { uint count = 0; ForEachItem(ItemSearchLocation.Everywhere, item => { if (item != skipItem) { ItemTemplate pProto = item.GetTemplate(); if (pProto != null) if (pProto.GetItemLimitCategory() == limitCategory) count += item.GetCount(); } return true; }); return count; } public byte GetItemLimitCategoryQuantity(ItemLimitCategoryRecord limitEntry) { byte limit = limitEntry.Quantity; var limitConditions = Global.DB2Mgr.GetItemLimitCategoryConditions(limitEntry.Id); foreach (ItemLimitCategoryConditionRecord limitCondition in limitConditions) { if (ConditionManager.IsPlayerMeetingCondition(this, limitCondition.PlayerConditionID)) limit += (byte)limitCondition.AddQuantity; } return limit; } public void DestroyConjuredItems(bool update) { // used when entering arena // destroys all conjured items Log.outDebug(LogFilter.Player, "STORAGE: DestroyConjuredItems"); // in inventory int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = InventorySlots.ItemStart; i < inventoryEnd; i++) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) { if (pItem.IsConjuredConsumable()) DestroyItem(InventorySlots.Bag0, i, update); } } // in inventory bags for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { Bag pBag = GetBagByPos(i); if (pBag != null) { for (byte j = 0; j < pBag.GetBagSize(); j++) { Item pItem = pBag.GetItemByPos(j); if (pItem != null) if (pItem.IsConjuredConsumable()) DestroyItem(i, j, update); } } } // in equipment and bag list for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; i++) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) if (pItem.IsConjuredConsumable()) DestroyItem(InventorySlots.Bag0, i, update); } } void DestroyZoneLimitedItem(bool update, uint new_zone) { Log.outDebug(LogFilter.Player, "STORAGE: DestroyZoneLimitedItem in map {0} and area {1}", GetMapId(), new_zone); // in inventory int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = InventorySlots.ItemStart; i < inventoryEnd; i++) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) if (pItem.IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) DestroyItem(InventorySlots.Bag0, i, update); } // in inventory bags for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { Bag pBag = GetBagByPos(i); if (pBag != null) { for (byte j = 0; j < pBag.GetBagSize(); j++) { Item pItem = pBag.GetItemByPos(j); if (pItem != null) if (pItem.IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) DestroyItem(i, j, update); } } } // in equipment and bag list for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; i++) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) if (pItem.IsLimitedToAnotherMapOrZone(GetMapId(), new_zone)) DestroyItem(InventorySlots.Bag0, i, update); } } public InventoryResult CanRollNeedForItem(ItemTemplate proto, Map map, bool restrictOnlyLfg) { if (restrictOnlyLfg) { if (GetGroup() == null || !GetGroup().IsLFGGroup()) return InventoryResult.Ok; // not in LFG group // check if looted object is inside the lfg dungeon if (!Global.LFGMgr.InLfgDungeonMap(GetGroup().GetGUID(), map.GetId(), map.GetDifficultyID())) return InventoryResult.Ok; } if (proto == null) return InventoryResult.ItemNotFound; // Used by group, function GroupLoot, to know if a prototype can be used by a player if ((proto.GetAllowableClass() & GetClassMask()) == 0 || !proto.GetAllowableRace().HasRace(GetRace())) return InventoryResult.CantEquipEver; if (proto.GetRequiredSpell() != 0 && !HasSpell(proto.GetRequiredSpell())) return InventoryResult.ProficiencyNeeded; if (proto.GetRequiredSkill() != 0) { if (GetSkillValue((SkillType)proto.GetRequiredSkill()) == 0) return InventoryResult.ProficiencyNeeded; else if (GetSkillValue((SkillType)proto.GetRequiredSkill()) < proto.GetRequiredSkillRank()) return InventoryResult.CantEquipSkill; } if (proto.GetClass() == ItemClass.Weapon && GetSkillValue(proto.GetSkill()) == 0) return InventoryResult.ProficiencyNeeded; if (proto.GetClass() == ItemClass.Armor && proto.GetInventoryType() != InventoryType.Cloak) { ChrClassesRecord classesEntry = CliDB.ChrClassesStorage.LookupByKey(GetClass()); if ((classesEntry.ArmorTypeMask & 1 << (int)proto.GetSubClass()) == 0) return InventoryResult.ClientLockedOut; } return InventoryResult.Ok; } public void AddItemToBuyBackSlot(Item pItem) { if (pItem != null) { uint slot = m_currentBuybackSlot; // if current back slot non-empty search oldest or free if (m_items[slot] != null) { long oldest_time = m_activePlayerData.BuybackTimestamp[0]; uint oldest_slot = InventorySlots.BuyBackStart; for (byte i = InventorySlots.BuyBackStart + 1; i < InventorySlots.BuyBackEnd; ++i) { // found empty if (m_items[i] == null) { oldest_slot = i; break; } long i_time = m_activePlayerData.BuybackTimestamp[i - InventorySlots.BuyBackStart]; if (oldest_time > i_time) { oldest_time = i_time; oldest_slot = i; } } // find oldest slot = oldest_slot; } RemoveItemFromBuyBackSlot(slot, true); Log.outDebug(LogFilter.Player, "STORAGE: AddItemToBuyBackSlot item = {0}, slot = {1}", pItem.GetEntry(), slot); m_items[slot] = pItem; var time = GameTime.GetGameTime(); uint etime = (uint)(time - m_logintime + (30 * 3600)); uint eslot = slot - InventorySlots.BuyBackStart; SetInvSlot(slot, pItem.GetGUID()); ItemTemplate proto = pItem.GetTemplate(); if (proto != null) SetBuybackPrice(eslot, proto.GetSellPrice() * pItem.GetCount()); else SetBuybackPrice(eslot, 0); SetBuybackTimestamp(eslot, etime); // move to next (for non filled list is move most optimized choice) if (m_currentBuybackSlot < InventorySlots.BuyBackEnd - 1) ++m_currentBuybackSlot; } } public bool BuyCurrencyFromVendorSlot(ObjectGuid vendorGuid, uint vendorSlot, uint currency, uint count) { // cheating attempt if (count < 1) count = 1; if (!IsAlive()) return false; CurrencyTypesRecord proto = CliDB.CurrencyTypesStorage.LookupByKey(currency); if (proto == null) { SendBuyError(BuyResult.CantFindItem, null, currency); return false; } Creature creature = GetNPCIfCanInteractWith(vendorGuid, NPCFlags.Vendor, NPCFlags2.None); if (creature == null) { Log.outDebug(LogFilter.Network, "WORLD: BuyCurrencyFromVendorSlot - {0} not found or you can't interact with him.", vendorGuid.ToString()); SendBuyError(BuyResult.DistanceTooFar, null, currency); return false; } VendorItemData vItems = creature.GetVendorItems(); if (vItems == null || vItems.Empty()) { SendBuyError(BuyResult.CantFindItem, creature, currency); return false; } if (vendorSlot >= vItems.GetItemCount()) { SendBuyError(BuyResult.CantFindItem, creature, currency); return false; } VendorItem crItem = vItems.GetItem(vendorSlot); // store diff item (cheating) if (crItem == null || crItem.item != currency || crItem.Type != ItemVendorType.Currency) { SendBuyError(BuyResult.CantFindItem, creature, currency); return false; } if ((count % crItem.maxcount) != 0) { SendEquipError(InventoryResult.CantBuyQuantity); return false; } uint stacks = count / crItem.maxcount; ItemExtendedCostRecord iece; if (crItem.ExtendedCost != 0) { iece = CliDB.ItemExtendedCostStorage.LookupByKey(crItem.ExtendedCost); if (iece == null) { Log.outError(LogFilter.Player, "Currency {0} have wrong ExtendedCost field value {1}", currency, crItem.ExtendedCost); return false; } for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) { if (iece.ItemID[i] != 0 && !HasItemCount(iece.ItemID[i], (iece.ItemCount[i] * stacks))) { SendEquipError(InventoryResult.VendorMissingTurnins); return false; } } for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) { if (iece.CurrencyID[i] == 0) continue; CurrencyTypesRecord entry = CliDB.CurrencyTypesStorage.LookupByKey(iece.CurrencyID[i]); if (entry == null) { SendBuyError(BuyResult.CantFindItem, creature, currency); // Find correct error return false; } if (iece.Flags.HasAnyFlag((byte)((uint)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) { // Not implemented SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error return false; } else if (!HasCurrency(iece.CurrencyID[i], (iece.CurrencyCount[i] * stacks))) { SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error return false; } } // check for personal arena rating requirement if (GetMaxPersonalArenaRatingRequirement(iece.ArenaBracket) < iece.RequiredArenaRating) { // probably not the proper equip err SendEquipError(InventoryResult.CantEquipRank); return false; } if (iece.MinFactionID != 0 && (uint)GetReputationRank(iece.MinFactionID) < iece.RequiredAchievement) { SendBuyError(BuyResult.ReputationRequire, creature, currency); return false; } if (iece.Flags.HasAnyFlag((byte)ItemExtendedCostFlags.RequireGuild) && GetGuildId() == 0) { SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error return false; } if (iece.RequiredAchievement != 0 && !HasAchieved(iece.RequiredAchievement)) { SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error return false; } } else // currencies have no price defined, can only be bought with ExtendedCost { SendBuyError(BuyResult.CantFindItem, null, currency); return false; } AddCurrency(currency, count, CurrencyGainSource.Vendor); if (iece != null) { for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) { if (iece.ItemID[i] == 0) continue; DestroyItemCount(iece.ItemID[i], iece.ItemCount[i] * stacks, true); } for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) { if (iece.CurrencyID[i] == 0) continue; if (iece.Flags.HasAnyFlag((byte)((uint)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) continue; RemoveCurrency(iece.CurrencyID[i], (int)(iece.CurrencyCount[i] * stacks), CurrencyDestroyReason.Vendor); } } return true; } public bool BuyItemFromVendorSlot(ObjectGuid vendorguid, uint vendorslot, uint item, byte count, byte bag, byte slot) { // cheating attempt if (count < 1) count = 1; // cheating attempt if (slot > ItemConst.MaxBagSize && slot != ItemConst.NullSlot) return false; if (!IsAlive()) return false; ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(item); if (pProto == null) { SendBuyError(BuyResult.CantFindItem, null, item); return false; } if (!Convert.ToBoolean(pProto.GetAllowableClass() & GetClassMask()) && pProto.GetBonding() == ItemBondingType.OnAcquire && !IsGameMaster()) { SendBuyError(BuyResult.CantFindItem, null, item); return false; } if (!IsGameMaster() && ((pProto.HasFlag(ItemFlags2.FactionHorde) && GetTeam() == Team.Alliance) || (pProto.HasFlag(ItemFlags2.FactionAlliance) && GetTeam() == Team.Horde))) return false; Creature creature = GetNPCIfCanInteractWith(vendorguid, NPCFlags.Vendor, NPCFlags2.None); if (creature == null) { Log.outDebug(LogFilter.Network, "WORLD: BuyItemFromVendor - {0} not found or you can't interact with him.", vendorguid.ToString()); SendBuyError(BuyResult.DistanceTooFar, null, item); return false; } if (!Global.ConditionMgr.IsObjectMeetingVendorItemConditions(creature.GetEntry(), item, this, creature)) { Log.outDebug(LogFilter.Condition, "BuyItemFromVendor: conditions not met for creature entry {0} item {1}", creature.GetEntry(), item); SendBuyError(BuyResult.CantFindItem, creature, item); return false; } VendorItemData vItems = creature.GetVendorItems(); if (vItems == null || vItems.Empty()) { SendBuyError(BuyResult.CantFindItem, creature, item); return false; } if (vendorslot >= vItems.GetItemCount()) { SendBuyError(BuyResult.CantFindItem, creature, item); return false; } VendorItem crItem = vItems.GetItem(vendorslot); // store diff item (cheating) if (crItem == null || crItem.item != item) { SendBuyError(BuyResult.CantFindItem, creature, item); return false; } if (!ConditionManager.IsPlayerMeetingCondition(this, crItem.PlayerConditionId)) { SendEquipError(InventoryResult.ItemLocked); return false; } // check current item amount if it limited if (crItem.maxcount != 0) { if (creature.GetVendorItemCurrentCount(crItem) < count) { SendBuyError(BuyResult.ItemAlreadySold, creature, item); return false; } } if (pProto.GetRequiredReputationFaction() != 0 && ((uint)GetReputationRank(pProto.GetRequiredReputationFaction()) < pProto.GetRequiredReputationRank())) { SendBuyError(BuyResult.ReputationRequire, creature, item); return false; } if (crItem.ExtendedCost != 0) { // Can only buy full stacks for extended cost if ((count % pProto.GetBuyCount()) != 0) { SendEquipError(InventoryResult.CantBuyQuantity); return false; } uint stacks = count / pProto.GetBuyCount(); var iece = CliDB.ItemExtendedCostStorage.LookupByKey(crItem.ExtendedCost); if (iece == null) { Log.outError(LogFilter.Player, "Item {0} have wrong ExtendedCost field value {1}", pProto.GetId(), crItem.ExtendedCost); return false; } for (byte i = 0; i < ItemConst.MaxItemExtCostItems; ++i) { if (iece.ItemID[i] != 0 && !HasItemCount(iece.ItemID[i], iece.ItemCount[i] * stacks)) { SendEquipError(InventoryResult.VendorMissingTurnins); return false; } } for (byte i = 0; i < ItemConst.MaxItemExtCostCurrencies; ++i) { if (iece.CurrencyID[i] == 0) continue; var entry = CliDB.CurrencyTypesStorage.LookupByKey(iece.CurrencyID[i]); if (entry == null) { SendBuyError(BuyResult.CantFindItem, creature, item); return false; } if (iece.Flags.HasAnyFlag((byte)((uint)ItemExtendedCostFlags.RequireSeasonEarned1 << i))) { SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error return false; } else if (!HasCurrency(iece.CurrencyID[i], iece.CurrencyCount[i] * stacks)) { SendEquipError(InventoryResult.VendorMissingTurnins); return false; } } // check for personal arena rating requirement if (GetMaxPersonalArenaRatingRequirement(iece.ArenaBracket) < iece.RequiredArenaRating) { // probably not the proper equip err SendEquipError(InventoryResult.CantEquipRank); return false; } if (iece.MinFactionID != 0 && (uint)GetReputationRank(iece.MinFactionID) < iece.MinReputation) { SendBuyError(BuyResult.ReputationRequire, creature, item); return false; } if (iece.Flags.HasAnyFlag((byte)ItemExtendedCostFlags.RequireGuild) && GetGuildId() == 0) { SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error return false; } if (iece.RequiredAchievement != 0 && !HasAchieved(iece.RequiredAchievement)) { SendEquipError(InventoryResult.VendorMissingTurnins); // Find correct error return false; } } ulong price = 0; if (pProto.GetBuyPrice() > 0) //Assume price cannot be negative (do not know why it is int32) { float buyPricePerItem = (float)pProto.GetBuyPrice() / pProto.GetBuyCount(); ulong maxCount = (ulong)(PlayerConst.MaxMoneyAmount / buyPricePerItem); if (count > maxCount) { Log.outError(LogFilter.Player, "Player {0} tried to buy {1} item id {2}, causing overflow", GetName(), count, pProto.GetId()); count = (byte)maxCount; } price = (ulong)(buyPricePerItem * count); //it should not exceed MAX_MONEY_AMOUNT // reputation discount price = (ulong)Math.Floor(price * GetReputationPriceDiscount(creature)); price = pProto.GetBuyPrice() > 0 ? Math.Max(1ul, price) : price; int priceMod = GetTotalAuraModifier(AuraType.ModVendorItemsPrices); if (priceMod != 0) price -= MathFunctions.CalculatePct(price, priceMod); if (!HasEnoughMoney(price)) { SendBuyError(BuyResult.NotEnoughtMoney, creature, item); return false; } } if ((bag == ItemConst.NullBag && slot == ItemConst.NullSlot) || IsInventoryPos(bag, slot)) { if (!_StoreOrEquipNewItem(vendorslot, item, count, bag, slot, (int)price, pProto, creature, crItem, true)) return false; } else if (IsEquipmentPos(bag, slot)) { if (count != 1) { SendEquipError(InventoryResult.NotEquippable); return false; } if (!_StoreOrEquipNewItem(vendorslot, item, count, bag, slot, (int)price, pProto, creature, crItem, false)) return false; } else { SendEquipError(InventoryResult.WrongSlot); return false; } UpdateCriteria(CriteriaType.BuyItemsFromVendors, 1); if (crItem.maxcount != 0) // bought { if (pProto.GetQuality() > ItemQuality.Epic || (pProto.GetQuality() == ItemQuality.Epic && pProto.GetBaseItemLevel() >= GuildConst.MinNewsItemLevel)) { Guild guild = GetGuild(); if (guild != null) guild.AddGuildNews(GuildNews.ItemPurchased, GetGUID(), 0, item); } return true; } return false; } public uint GetMaxPersonalArenaRatingRequirement(uint minarenaslot) { // returns the maximal personal arena rating that can be used to purchase items requiring this condition // so return max[in arenateams](personalrating[teamtype]) uint max_personal_rating = 0; for (byte i = (byte)minarenaslot; i < SharedConst.MaxArenaSlot; ++i) { uint p_rating = GetArenaPersonalRating(i); if (max_personal_rating < p_rating) max_personal_rating = p_rating; } return max_personal_rating; } public void SendItemRetrievalMail(uint itemEntry, uint count, ItemContext context) { MailSender sender = new(MailMessageType.Creature, 34337); MailDraft draft = new("Recovered Item", "We recovered a lost item in the twisting nether and noted that it was yours.$B$BPlease find said object enclosed."); // This is the text used in Cataclysm, it probably wasn't changed. SQLTransaction trans = new(); Item item = Item.CreateItem(itemEntry, count, context, null); if (item != null) { item.SaveToDB(trans); draft.AddItem(item); } draft.SendMailTo(trans, new MailReceiver(this, GetGUID().GetCounter()), sender); DB.Characters.CommitTransaction(trans); } public void SetBuybackPrice(uint slot, uint price) { SetUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.BuybackPrice, (int)slot), price); } public void SetBuybackTimestamp(uint slot, long timestamp) { SetUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.BuybackTimestamp, (int)slot), timestamp); } public Item GetItemFromBuyBackSlot(uint slot) { Log.outDebug(LogFilter.Player, "STORAGE: GetItemFromBuyBackSlot slot = {0}", slot); if (slot >= InventorySlots.BuyBackStart && slot < InventorySlots.BuyBackEnd) return m_items[slot]; return null; } public void RemoveItemFromBuyBackSlot(uint slot, bool del) { Log.outDebug(LogFilter.Player, "STORAGE: RemoveItemFromBuyBackSlot slot = {0}", slot); if (slot >= InventorySlots.BuyBackStart && slot < InventorySlots.BuyBackEnd) { Item pItem = m_items[slot]; if (pItem != null) { pItem.RemoveFromWorld(); if (del) { ItemTemplate itemTemplate = pItem.GetTemplate(); if (itemTemplate != null) if (itemTemplate.HasFlag(ItemFlags.HasLoot)) Global.LootItemStorage.RemoveStoredLootForContainer(pItem.GetGUID().GetCounter()); pItem.SetState(ItemUpdateState.Removed, this); } } m_items[slot] = null; uint eslot = slot - InventorySlots.BuyBackStart; SetInvSlot(slot, ObjectGuid.Empty); SetBuybackPrice(eslot, 0); SetBuybackTimestamp(eslot, 0); // if current backslot is filled set to now free slot if (m_items[m_currentBuybackSlot] != null) m_currentBuybackSlot = slot; } } public bool HasItemTotemCategory(uint TotemCategory) { foreach (AuraEffect providedTotemCategory in GetAuraEffectsByType(AuraType.ProvideTotemCategory)) if (Global.DB2Mgr.IsTotemCategoryCompatibleWith((uint)providedTotemCategory.GetMiscValueB(), TotemCategory)) return true; int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = EquipmentSlot.Start; i < inventoryEnd; ++i) { Item item = GetUseableItemByPos(InventorySlots.Bag0, i); if (item != null && Global.DB2Mgr.IsTotemCategoryCompatibleWith(item.GetTemplate().GetTotemCategory(), TotemCategory)) return true; } for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) { Bag bag = GetBagByPos(i); if (bag != null) { for (byte j = 0; j < bag.GetBagSize(); ++j) { Item item = GetUseableItemByPos(i, j); if (item != null && Global.DB2Mgr.IsTotemCategoryCompatibleWith(item.GetTemplate().GetTotemCategory(), TotemCategory)) return true; } } } for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) { Item item = GetUseableItemByPos(InventorySlots.Bag0, i); if (item != null && Global.DB2Mgr.IsTotemCategoryCompatibleWith(item.GetTemplate().GetTotemCategory(), TotemCategory)) return true; } return false; } public void _ApplyItemMods(Item item, byte slot, bool apply, bool updateItemAuras = true) { if (slot >= InventorySlots.BagEnd || item == null) return; ItemTemplate proto = item.GetTemplate(); if (proto == null) return; // not apply/remove mods for broken item if (item.IsBroken()) return; Log.outInfo(LogFilter.Player, "applying mods for item {0} ", item.GetGUID().ToString()); if (item.GetSocketColor(0) != 0) //only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items CorrectMetaGemEnchants(slot, apply); _ApplyItemBonuses(item, slot, apply); ApplyItemEquipSpell(item, apply); if (updateItemAuras) { ApplyItemDependentAuras(item, apply); WeaponAttackType attackType = Player.GetAttackBySlot(slot, item.GetTemplate().GetInventoryType()); if (attackType != WeaponAttackType.Max) UpdateWeaponDependentAuras(attackType); } ApplyArtifactPowers(item, apply); ApplyAzeritePowers(item, apply); ApplyEnchantment(item, apply); Log.outDebug(LogFilter.Player, "_ApplyItemMods complete."); } public void _ApplyItemBonuses(Item item, byte slot, bool apply) { ItemTemplate proto = item.GetTemplate(); if (slot >= InventorySlots.BagEnd || proto == null) return; uint itemLevel = item.GetItemLevel(this); float combatRatingMultiplier = 1.0f; GtGenericMultByILvlRecord ratingMult = CliDB.CombatRatingsMultByILvlGameTable.GetRow(itemLevel); if (ratingMult != null) combatRatingMultiplier = CliDB.GetIlvlStatMultiplier(ratingMult, proto.GetInventoryType()); for (byte i = 0; i < ItemConst.MaxStats; ++i) { int statType = item.GetItemStatType(i); if (statType == -1) continue; float val = item.GetItemStatValue(i, this); if (val == 0) continue; switch ((ItemModType)statType) { case ItemModType.Mana: HandleStatFlatModifier(UnitMods.Mana, UnitModifierFlatType.Base, (float)val, apply); break; case ItemModType.Health: // modify HP HandleStatFlatModifier(UnitMods.Health, UnitModifierFlatType.Base, (float)val, apply); break; case ItemModType.Agility: // modify agility HandleStatFlatModifier(UnitMods.StatAgility, UnitModifierFlatType.Base, (float)val, apply); UpdateStatBuffMod(Stats.Agility); break; case ItemModType.Strength: //modify strength HandleStatFlatModifier(UnitMods.StatStrength, UnitModifierFlatType.Base, (float)val, apply); UpdateStatBuffMod(Stats.Strength); break; case ItemModType.Intellect: //modify intellect HandleStatFlatModifier(UnitMods.StatIntellect, UnitModifierFlatType.Base, (float)val, apply); UpdateStatBuffMod(Stats.Intellect); break; //case ItemModType.Spirit: //modify spirit //HandleStatModifier(UnitMods.StatSpirit, UnitModifierType.BaseValue, (float)val, apply); //ApplyStatBuffMod(Stats.Spirit, MathFunctions.CalculatePct(val, GetModifierValue(UnitMods.StatSpirit, UnitModifierType.BasePCTExcludeCreate)), apply); //break; case ItemModType.Stamina: //modify stamina GtGenericMultByILvlRecord staminaMult = CliDB.StaminaMultByILvlGameTable.GetRow(itemLevel); if (staminaMult != null) val = (int)(val * CliDB.GetIlvlStatMultiplier(staminaMult, proto.GetInventoryType())); HandleStatFlatModifier(UnitMods.StatStamina, UnitModifierFlatType.Base, (float)val, apply); UpdateStatBuffMod(Stats.Stamina); break; case ItemModType.DefenseSkillRating: ApplyRatingMod(CombatRating.DefenseSkill, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.DodgeRating: ApplyRatingMod(CombatRating.Dodge, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.ParryRating: ApplyRatingMod(CombatRating.Parry, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.BlockRating: ApplyRatingMod(CombatRating.Block, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.HitMeleeRating: ApplyRatingMod(CombatRating.HitMelee, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.HitRangedRating: ApplyRatingMod(CombatRating.HitRanged, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.HitSpellRating: ApplyRatingMod(CombatRating.HitSpell, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.CritMeleeRating: ApplyRatingMod(CombatRating.CritMelee, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.CritRangedRating: ApplyRatingMod(CombatRating.CritRanged, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.CritSpellRating: ApplyRatingMod(CombatRating.CritSpell, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.CritTakenRangedRating: ApplyRatingMod(CombatRating.CritRanged, (int)val, apply); break; case ItemModType.HasteMeleeRating: ApplyRatingMod(CombatRating.HasteMelee, (int)val, apply); break; case ItemModType.HasteRangedRating: ApplyRatingMod(CombatRating.HasteRanged, (int)val, apply); break; case ItemModType.HasteSpellRating: ApplyRatingMod(CombatRating.HasteSpell, (int)val, apply); break; case ItemModType.HitRating: ApplyRatingMod(CombatRating.HitMelee, (int)(val * combatRatingMultiplier), apply); ApplyRatingMod(CombatRating.HitRanged, (int)(val * combatRatingMultiplier), apply); ApplyRatingMod(CombatRating.HitSpell, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.CritRating: ApplyRatingMod(CombatRating.CritMelee, (int)(val * combatRatingMultiplier), apply); ApplyRatingMod(CombatRating.CritRanged, (int)(val * combatRatingMultiplier), apply); ApplyRatingMod(CombatRating.CritSpell, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.ResilienceRating: ApplyRatingMod(CombatRating.ResiliencePlayerDamage, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.HasteRating: ApplyRatingMod(CombatRating.HasteMelee, (int)(val * combatRatingMultiplier), apply); ApplyRatingMod(CombatRating.HasteRanged, (int)(val * combatRatingMultiplier), apply); ApplyRatingMod(CombatRating.HasteSpell, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.ExpertiseRating: ApplyRatingMod(CombatRating.Expertise, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.AttackPower: HandleStatFlatModifier(UnitMods.AttackPower, UnitModifierFlatType.Total, (float)val, apply); HandleStatFlatModifier(UnitMods.AttackPowerRanged, UnitModifierFlatType.Total, (float)val, apply); break; case ItemModType.RangedAttackPower: HandleStatFlatModifier(UnitMods.AttackPowerRanged, UnitModifierFlatType.Total, (float)val, apply); break; case ItemModType.Versatility: ApplyRatingMod(CombatRating.VersatilityDamageDone, (int)(val * combatRatingMultiplier), apply); ApplyRatingMod(CombatRating.VersatilityDamageTaken, (int)(val * combatRatingMultiplier), apply); ApplyRatingMod(CombatRating.VersatilityHealingDone, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.ManaRegeneration: ApplyManaRegenBonus((int)val, apply); break; case ItemModType.ArmorPenetrationRating: ApplyRatingMod(CombatRating.ArmorPenetration, (int)val, apply); break; case ItemModType.SpellPower: ApplySpellPowerBonus((int)val, apply); break; case ItemModType.HealthRegen: ApplyHealthRegenBonus((int)val, apply); break; case ItemModType.SpellPenetration: ApplySpellPenetrationBonus((int)val, apply); break; case ItemModType.MasteryRating: ApplyRatingMod(CombatRating.Mastery, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.ExtraArmor: HandleStatFlatModifier(UnitMods.Armor, UnitModifierFlatType.Total, (float)val, apply); break; case ItemModType.FireResistance: HandleStatFlatModifier(UnitMods.ResistanceFire, UnitModifierFlatType.Base, (float)val, apply); break; case ItemModType.FrostResistance: HandleStatFlatModifier(UnitMods.ResistanceFrost, UnitModifierFlatType.Base, (float)val, apply); break; case ItemModType.HolyResistance: HandleStatFlatModifier(UnitMods.ResistanceHoly, UnitModifierFlatType.Base, (float)val, apply); break; case ItemModType.ShadowResistance: HandleStatFlatModifier(UnitMods.ResistanceShadow, UnitModifierFlatType.Base, (float)val, apply); break; case ItemModType.NatureResistance: HandleStatFlatModifier(UnitMods.ResistanceNature, UnitModifierFlatType.Base, (float)val, apply); break; case ItemModType.ArcaneResistance: HandleStatFlatModifier(UnitMods.ResistanceArcane, UnitModifierFlatType.Base, (float)val, apply); break; case ItemModType.PvpPower: ApplyRatingMod(CombatRating.PvpPower, (int)val, apply); break; case ItemModType.Corruption: ApplyRatingMod(CombatRating.Corruption, (int)val, apply); break; case ItemModType.CorruptionResistance: ApplyRatingMod(CombatRating.CorruptionResistance, (int)val, apply); break; case ItemModType.CrSpeed: ApplyRatingMod(CombatRating.Speed, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.CrLifesteal: ApplyRatingMod(CombatRating.Lifesteal, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.CrAvoidance: ApplyRatingMod(CombatRating.Avoidance, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.CrSturdiness: ApplyRatingMod(CombatRating.Studiness, (int)(val * combatRatingMultiplier), apply); break; case ItemModType.AgiStrInt: HandleStatFlatModifier(UnitMods.StatAgility, UnitModifierFlatType.Base, val, apply); HandleStatFlatModifier(UnitMods.StatStrength, UnitModifierFlatType.Base, val, apply); HandleStatFlatModifier(UnitMods.StatIntellect, UnitModifierFlatType.Base, val, apply); UpdateStatBuffMod(Stats.Agility); UpdateStatBuffMod(Stats.Strength); UpdateStatBuffMod(Stats.Intellect); break; case ItemModType.AgiStr: HandleStatFlatModifier(UnitMods.StatAgility, UnitModifierFlatType.Base, val, apply); HandleStatFlatModifier(UnitMods.StatStrength, UnitModifierFlatType.Base, val, apply); UpdateStatBuffMod(Stats.Agility); UpdateStatBuffMod(Stats.Strength); break; case ItemModType.AgiInt: HandleStatFlatModifier(UnitMods.StatAgility, UnitModifierFlatType.Base, val, apply); HandleStatFlatModifier(UnitMods.StatIntellect, UnitModifierFlatType.Base, val, apply); UpdateStatBuffMod(Stats.Agility); UpdateStatBuffMod(Stats.Intellect); break; case ItemModType.StrInt: HandleStatFlatModifier(UnitMods.StatStrength, UnitModifierFlatType.Base, val, apply); HandleStatFlatModifier(UnitMods.StatIntellect, UnitModifierFlatType.Base, val, apply); UpdateStatBuffMod(Stats.Strength); UpdateStatBuffMod(Stats.Intellect); break; } } uint armor = proto.GetArmor(itemLevel); if (armor != 0) { HandleStatFlatModifier(UnitMods.Armor, UnitModifierFlatType.Total, (float)armor, apply); if (proto.GetClass() == ItemClass.Armor && (ItemSubClassArmor)proto.GetSubClass() == ItemSubClassArmor.Shield) SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ShieldBlock), apply ? (uint)(armor * 2.5f) : 0); } WeaponAttackType attType = GetAttackBySlot(slot, proto.GetInventoryType()); if (attType != WeaponAttackType.Max) _ApplyWeaponDamage(slot, item, apply); } void ApplyItemEquipSpell(Item item, bool apply, bool formChange = false) { if (item == null || item.GetTemplate().HasFlag(ItemFlags.Legacy)) return; foreach (ItemEffectRecord effectData in item.GetEffects()) { // wrong triggering type if (apply && effectData.TriggerType != ItemSpelltriggerType.OnEquip) continue; // check if it is valid spell SpellInfo spellproto = Global.SpellMgr.GetSpellInfo((uint)effectData.SpellID, Difficulty.None); if (spellproto == null) continue; if (effectData.ChrSpecializationID != 0 && (ChrSpecialization)effectData.ChrSpecializationID != GetPrimarySpecialization()) continue; ApplyEquipSpell(spellproto, item, apply, formChange); } } public void ApplyEquipSpell(SpellInfo spellInfo, Item item, bool apply, bool formChange = false) { if (apply) { // Cannot be used in this stance/form if (spellInfo.CheckShapeshift(GetShapeshiftForm()) != SpellCastResult.SpellCastOk) return; if (formChange) // check aura active state from other form { var range = GetAppliedAuras(); foreach (var pair in range) { if (pair.Key != spellInfo.Id) continue; if (item == null || pair.Value.GetBase().GetCastItemGUID() == item.GetGUID()) return; } } Log.outDebug(LogFilter.Player, "WORLD: cast {0} Equip spellId - {1}", (item != null ? "item" : "itemset"), spellInfo.Id); CastSpell(this, spellInfo.Id, new CastSpellExtraArgs(item)); } else { if (formChange) // check aura compatibility { // Cannot be used in this stance/form if (spellInfo.CheckShapeshift(GetShapeshiftForm()) == SpellCastResult.SpellCastOk) return; // and remove only not compatible at form change } if (item != null) RemoveAurasDueToItemSpell(spellInfo.Id, item.GetGUID()); // un-apply all spells, not only at-equipped else RemoveAurasDueToSpell(spellInfo.Id); // un-apply spell (item set case) } } void ApplyEquipCooldown(Item pItem) { if (pItem.GetTemplate().HasFlag(ItemFlags.NoEquipCooldown)) return; DateTime now = GameTime.Now(); foreach (ItemEffectRecord effectData in pItem.GetEffects()) { SpellInfo effectSpellInfo = Global.SpellMgr.GetSpellInfo((uint)effectData.SpellID, Difficulty.None); if (effectSpellInfo == null) continue; // apply proc cooldown to equip auras if we have any if (effectData.TriggerType == ItemSpelltriggerType.OnEquip) { SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(effectSpellInfo); if (procEntry == null) continue; Aura itemAura = GetAura((uint)effectData.SpellID, GetGUID(), pItem.GetGUID()); if (itemAura != null) itemAura.AddProcCooldown(procEntry, now); continue; } // no spell if (effectData.SpellID == 0) continue; // wrong triggering type if (effectData.TriggerType != ItemSpelltriggerType.OnUse) continue; // Don't replace longer cooldowns by equip cooldown if we have any. if (GetSpellHistory().GetRemainingCooldown(effectSpellInfo) > TimeSpan.FromSeconds(30)) continue; GetSpellHistory().AddCooldown((uint)effectData.SpellID, pItem.GetEntry(), TimeSpan.FromSeconds(30)); ItemCooldown data = new(); data.ItemGuid = pItem.GetGUID(); data.SpellID = (uint)effectData.SpellID; data.Cooldown = 30 * Time.InMilliseconds; //Always 30secs? SendPacket(data); } } public void ApplyItemLootedSpell(Item item, bool apply) { if (item.GetTemplate().HasFlag(ItemFlags.Legacy)) return; var lootedEffect = item.GetEffects().FirstOrDefault(effectData => effectData.TriggerType == ItemSpelltriggerType.OnLooted); if (lootedEffect != null) { if (apply) CastSpell(this, (uint)lootedEffect.SpellID, item); else RemoveAurasDueToItemSpell((uint)lootedEffect.SpellID, item.GetGUID()); } } public void ApplyItemLootedSpell(ItemTemplate itemTemplate) { if (itemTemplate.HasFlag(ItemFlags.Legacy)) return; foreach (var effect in itemTemplate.Effects) { if (effect.TriggerType != ItemSpelltriggerType.OnLooted) continue; CastSpell(this, (uint)effect.SpellID, true); } } void _RemoveAllItemMods() { Log.outDebug(LogFilter.Player, "_RemoveAllItemMods start."); for (byte i = 0; i < InventorySlots.BagEnd; ++i) { if (m_items[i] != null) { ItemTemplate proto = m_items[i].GetTemplate(); if (proto == null) continue; // item set bonuses not dependent from item broken state if (proto.GetItemSet() != 0) Item.RemoveItemsSetItem(this, m_items[i]); if (m_items[i].IsBroken() || !CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) continue; ApplyItemEquipSpell(m_items[i], false); ApplyEnchantment(m_items[i], false); ApplyArtifactPowers(m_items[i], false); } } for (byte i = 0; i < InventorySlots.BagEnd; ++i) { if (m_items[i] != null) { if (m_items[i].IsBroken() || !CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) continue; ApplyItemDependentAuras(m_items[i], false); _ApplyItemBonuses(m_items[i], i, false); } } Log.outDebug(LogFilter.Player, "_RemoveAllItemMods complete."); } void _ApplyAllItemMods() { Log.outDebug(LogFilter.Player, "_ApplyAllItemMods start."); for (byte i = 0; i < InventorySlots.BagEnd; ++i) { if (m_items[i] != null) { if (m_items[i].IsBroken() || !CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) continue; ApplyItemDependentAuras(m_items[i], true); _ApplyItemBonuses(m_items[i], i, true); WeaponAttackType attackType = Player.GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()); if (attackType != WeaponAttackType.Max) UpdateWeaponDependentAuras(attackType); } } for (byte i = 0; i < InventorySlots.BagEnd; ++i) { if (m_items[i] != null) { ItemTemplate proto = m_items[i].GetTemplate(); if (proto == null) continue; // item set bonuses not dependent from item broken state if (proto.GetItemSet() != 0) Item.AddItemsSetItem(this, m_items[i]); if (m_items[i].IsBroken() || !CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) continue; ApplyItemEquipSpell(m_items[i], true); ApplyArtifactPowers(m_items[i], true); ApplyEnchantment(m_items[i], true); } } Log.outDebug(LogFilter.Player, "_ApplyAllItemMods complete."); } public void _ApplyAllLevelScaleItemMods(bool apply) { for (byte i = 0; i < InventorySlots.BagEnd; ++i) { if (m_items[i] != null) { if (!CanUseAttackType(GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) continue; _ApplyItemMods(m_items[i], i, apply); // Update item sets for heirlooms if (Global.DB2Mgr.GetHeirloomByItemId(m_items[i].GetEntry()) != null && m_items[i].GetTemplate().GetItemSet() != 0) { if (apply) Item.AddItemsSetItem(this, m_items[i]); else Item.RemoveItemsSetItem(this, m_items[i]); } } } } void ApplyAllAzeriteItemMods(bool apply) { for (byte i = 0; i < InventorySlots.BagEnd; ++i) { if (m_items[i] != null) { if (!m_items[i].IsAzeriteItem() || m_items[i].IsBroken() || !CanUseAttackType(Player.GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) continue; ApplyAzeritePowers(m_items[i], apply); } } } public void ApplyAllAzeriteEmpoweredItemMods(bool apply) { for (byte i = 0; i < InventorySlots.BagEnd; ++i) { if (m_items[i] != null) { if (!m_items[i].IsAzeriteEmpoweredItem() || m_items[i].IsBroken() || !CanUseAttackType(Player.GetAttackBySlot(i, m_items[i].GetTemplate().GetInventoryType()))) continue; ApplyAzeritePowers(m_items[i], apply); } } } public Loot GetLootByWorldObjectGUID(ObjectGuid lootWorldObjectGuid) { return m_AELootView.FirstOrDefault(pair => pair.Value.GetOwnerGUID() == lootWorldObjectGuid).Value; } public LootRoll GetLootRoll(ObjectGuid lootObjectGuid, byte lootListId) { return m_lootRolls.Find(roll => roll.IsLootItem(lootObjectGuid, lootListId)); } public void AddLootRoll(LootRoll roll) { m_lootRolls.Add(roll); } public void RemoveLootRoll(LootRoll roll) { m_lootRolls.Remove(roll); } //Inventory public bool IsInventoryPos(ushort pos) { return IsInventoryPos((byte)(pos >> 8), (byte)(pos & 255)); } public static bool IsInventoryPos(byte bag, byte slot) { if (bag == InventorySlots.Bag0 && slot == ItemConst.NullSlot) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ItemStart && slot < InventorySlots.ItemEnd)) return true; if (bag >= InventorySlots.BagStart && bag < InventorySlots.BagEnd) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ReagentStart && slot < InventorySlots.ReagentEnd)) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ChildEquipmentStart && slot < InventorySlots.ChildEquipmentEnd)) return true; return false; } InventoryResult CanStoreItem_InInventorySlots(byte slot_begin, byte slot_end, List dest, ItemTemplate pProto, ref uint count, bool merge, Item pSrcItem, byte skip_bag, byte skip_slot) { //this is never called for non-bag slots so we can do this if (pSrcItem != null && pSrcItem.IsNotEmptyBag()) return InventoryResult.DestroyNonemptyBag; for (var j = slot_begin; j < slot_end; j++) { // skip specific slot already processed in first called CanStoreItem_InSpecificSlot if (InventorySlots.Bag0 == skip_bag && j == skip_slot) continue; Item pItem2 = GetItemByPos(InventorySlots.Bag0, j); // ignore move item (this slot will be empty at move) if (pItem2 == pSrcItem) pItem2 = null; // if merge skip empty, if !merge skip non-empty if ((pItem2 != null) != merge) continue; uint need_space = pProto.GetMaxStackSize(); if (pItem2 != null) { // can be merged at least partly InventoryResult res = pItem2.CanBeMergedPartlyWith(pProto); if (res != InventoryResult.Ok) continue; // descrease at current stacksize need_space -= pItem2.GetCount(); } if (need_space > count) need_space = count; ItemPosCount newPosition = new((ushort)(InventorySlots.Bag0 << 8 | j), need_space); if (!newPosition.IsContainedIn(dest)) { dest.Add(newPosition); count -= need_space; if (count == 0) return InventoryResult.Ok; } } return InventoryResult.Ok; } InventoryResult CanStoreItem_InSpecificSlot(byte bag, byte slot, List dest, ItemTemplate pProto, ref uint count, bool swap, Item pSrcItem) { Item pItem2 = GetItemByPos(bag, slot); // ignore move item (this slot will be empty at move) if (pItem2 == pSrcItem) pItem2 = null; uint need_space; if (pSrcItem != null) { if (pSrcItem.IsNotEmptyBag() && !IsBagPos((ushort)((ushort)bag << 8 | slot))) return InventoryResult.DestroyNonemptyBag; if (pSrcItem.HasItemFlag(ItemFieldFlags.Child) && !IsEquipmentPos(bag, slot) && !IsChildEquipmentPos(bag, slot)) return InventoryResult.WrongBagType3; if (!pSrcItem.HasItemFlag(ItemFieldFlags.Child) && IsChildEquipmentPos(bag, slot)) return InventoryResult.WrongBagType3; } // empty specific slot - check item fit to slot if (pItem2 == null || swap) { if (bag == InventorySlots.Bag0) { // prevent cheating if ((slot >= InventorySlots.BuyBackStart && slot < InventorySlots.BuyBackEnd) || slot >= (byte)PlayerSlots.End) return InventoryResult.WrongBagType; // can't store anything else than crafting reagents in Reagent Bank if (IsReagentBankPos(bag, slot) && (!IsReagentBankUnlocked() || !pProto.IsCraftingReagent())) return InventoryResult.WrongBagType; } else { Bag pBag = GetBagByPos(bag); if (pBag == null) return InventoryResult.WrongBagType; ItemTemplate pBagProto = pBag.GetTemplate(); if (pBagProto == null) return InventoryResult.WrongBagType; if (slot >= pBagProto.GetContainerSlots()) return InventoryResult.WrongBagType; if (!Item.ItemCanGoIntoBag(pProto, pBagProto)) return InventoryResult.WrongBagType; } // non empty stack with space need_space = pProto.GetMaxStackSize(); } // non empty slot, check item type else { // can be merged at least partly InventoryResult res = pItem2.CanBeMergedPartlyWith(pProto); if (res != InventoryResult.Ok) return res; // free stack space or infinity need_space = pProto.GetMaxStackSize() - pItem2.GetCount(); } if (need_space > count) need_space = count; ItemPosCount newPosition = new((ushort)(bag << 8 | slot), need_space); if (!newPosition.IsContainedIn(dest)) { dest.Add(newPosition); count -= need_space; } return InventoryResult.Ok; } public void MoveItemFromInventory(byte bag, byte slot, bool update) { Item it = GetItemByPos(bag, slot); if (it != null) { RemoveItem(bag, slot, update); ItemRemovedQuestCheck(it.GetEntry(), it.GetCount()); it.SetNotRefundable(this, false, null, false); Item.RemoveItemFromUpdateQueueOf(it, this); GetSession().GetCollectionMgr().RemoveTemporaryAppearance(it); if (it.IsInWorld) { it.RemoveFromWorld(); it.DestroyForPlayer(this); } } } public void MoveItemToInventory(List dest, Item pItem, bool update, bool in_characterInventoryDB = false) { uint itemId = pItem.GetEntry(); uint count = pItem.GetCount(); // store item Item pLastItem = StoreItem(dest, pItem, update); // only set if not merged to existed stack if (pLastItem == pItem) { // update owner for last item (this can be original item with wrong owner if (pLastItem.GetOwnerGUID() != GetGUID()) pLastItem.SetOwnerGUID(GetGUID()); // if this original item then it need create record in inventory // in case trade we already have item in other player inventory pLastItem.SetState(in_characterInventoryDB ? ItemUpdateState.Changed : ItemUpdateState.New, this); if (pLastItem.IsBOPTradeable()) AddTradeableItem(pLastItem); } // update quest counters ItemAddedQuestCheck(itemId, count); UpdateCriteria(CriteriaType.ObtainAnyItem, itemId, count); } //Bank public static bool IsBankPos(ushort pos) { return IsBankPos((byte)(pos >> 8), (byte)(pos & 255)); } public static bool IsBankPos(byte bag, byte slot) { if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BankItemStart && slot < InventorySlots.BankItemEnd)) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BankBagStart && slot < InventorySlots.BankBagEnd)) return true; if (bag >= InventorySlots.BankBagStart && bag < InventorySlots.BankBagEnd) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ReagentStart && slot < InventorySlots.ReagentEnd)) return true; return false; } public InventoryResult CanBankItem(byte bag, byte slot, List dest, Item pItem, bool swap, bool not_loading = true, bool reagentBankOnly = false) { if (pItem == null) return swap ? InventoryResult.CantSwap : InventoryResult.ItemNotFound; // different slots range if we're trying to store item in Reagent Bank if (reagentBankOnly) { Cypher.Assert(bag == ItemConst.NullBag && slot == ItemConst.NullSlot); // when reagentBankOnly is true then bag & slot must be hardcoded constants, not client input } if ((IsReagentBankPos(bag, slot) || reagentBankOnly) && !IsReagentBankUnlocked()) return InventoryResult.ReagentBankLocked; byte slotStart = reagentBankOnly ? InventorySlots.ReagentStart : InventorySlots.BankItemStart; byte slotEnd = reagentBankOnly ? InventorySlots.ReagentEnd : InventorySlots.BankItemEnd; uint count = pItem.GetCount(); Log.outDebug(LogFilter.Player, "STORAGE: CanBankItem bag = {0}, slot = {1}, item = {2}, count = {3}", bag, slot, pItem.GetEntry(), count); ItemTemplate pProto = pItem.GetTemplate(); if (pProto == null) return swap ? InventoryResult.CantSwap : InventoryResult.ItemNotFound; // item used if (pItem.m_lootGenerated) return InventoryResult.LootGone; if (pItem.IsBindedNotWith(this)) return InventoryResult.NotOwner; // Currency tokens are not supposed to be swapped out of their hidden bag if (pItem.IsCurrencyToken()) { Log.outError(LogFilter.Player, "Possible hacking attempt: Player {0} [guid: {1}] tried to move token [guid: {2}, entry: {3}] out of the currency bag!", GetName(), GetGUID().ToString(), pItem.GetGUID().ToString(), pProto.GetId()); return InventoryResult.CantSwap; } // check count of items (skip for auto move for same player from bank) InventoryResult res = CanTakeMoreSimilarItems(pItem); if (res != InventoryResult.Ok) return res; // in specific slot if (bag != ItemConst.NullBag && slot != ItemConst.NullSlot) { if (slot >= InventorySlots.BagStart && slot < InventorySlots.BagEnd) { if (!pItem.IsBag()) return InventoryResult.WrongSlot; if (slot - InventorySlots.BagStart >= GetBankBagSlotCount()) return InventoryResult.NoBankSlot; res = CanUseItem(pItem, not_loading); if (res != InventoryResult.Ok) return res; } res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, ref count, swap, pItem); if (res != InventoryResult.Ok) return res; if (count == 0) return InventoryResult.Ok; } // not specific slot or have space for partly store only in specific slot // in specific bag if (bag != ItemConst.NullBag) { if (pItem.IsNotEmptyBag()) return InventoryResult.BagInBag; // search stack in bag for merge to if (pProto.GetMaxStackSize() != 1) { if (bag == InventorySlots.Bag0) { res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, ref count, true, pItem, bag, slot); if (res != InventoryResult.Ok) return res; if (count == 0) return InventoryResult.Ok; } else { res = CanStoreItem_InBag(bag, dest, pProto, ref count, true, false, pItem, ItemConst.NullBag, slot); if (res != InventoryResult.Ok) res = CanStoreItem_InBag(bag, dest, pProto, ref count, true, true, pItem, ItemConst.NullBag, slot); if (res != InventoryResult.Ok) return res; if (count == 0) return InventoryResult.Ok; } } // search free slot in bag if (bag == InventorySlots.Bag0) { res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, ref count, false, pItem, bag, slot); if (res != InventoryResult.Ok) return res; if (count == 0) return InventoryResult.Ok; } else { res = CanStoreItem_InBag(bag, dest, pProto, ref count, false, false, pItem, ItemConst.NullBag, slot); if (res != InventoryResult.Ok) res = CanStoreItem_InBag(bag, dest, pProto, ref count, false, true, pItem, ItemConst.NullBag, slot); if (res != InventoryResult.Ok) return res; if (count == 0) return InventoryResult.Ok; } } // not specific bag or have space for partly store only in specific bag // search stack for merge to if (pProto.GetMaxStackSize() != 1) { // in slots res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, ref count, true, pItem, bag, slot); if (res != InventoryResult.Ok) return res; if (count == 0) return InventoryResult.Ok; // don't try to store reagents anywhere else than in Reagent Bank if we're on it if (!reagentBankOnly) { // in special bags if (pProto.GetBagFamily() != BagFamilyMask.None) { for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) { res = CanStoreItem_InBag(i, dest, pProto, ref count, true, false, pItem, bag, slot); if (res != InventoryResult.Ok) continue; if (count == 0) return InventoryResult.Ok; } } // in regular bags for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) { res = CanStoreItem_InBag(i, dest, pProto, ref count, true, true, pItem, bag, slot); if (res != InventoryResult.Ok) continue; if (count == 0) return InventoryResult.Ok; } } } // search free place in special bag if (!reagentBankOnly && pProto.GetBagFamily() != BagFamilyMask.None) { for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) { res = CanStoreItem_InBag(i, dest, pProto, ref count, false, false, pItem, bag, slot); if (res != InventoryResult.Ok) continue; if (count == 0) return InventoryResult.Ok; } } // search free space res = CanStoreItem_InInventorySlots(slotStart, slotEnd, dest, pProto, ref count, false, pItem, bag, slot); if (res != InventoryResult.Ok) return res; if (count == 0) return InventoryResult.Ok; // search free space in regular bags (don't try to store reagents anywhere else than in Reagent Bank if we're on it) if (!reagentBankOnly) { for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) { res = CanStoreItem_InBag(i, dest, pProto, ref count, false, true, pItem, bag, slot); if (res != InventoryResult.Ok) continue; if (count == 0) return InventoryResult.Ok; } } return reagentBankOnly ? InventoryResult.ReagentBankFull : InventoryResult.BankFull; } public Item BankItem(List dest, Item pItem, bool update) { return StoreItem(dest, pItem, update); } public uint GetFreeInventorySlotCount(ItemSearchLocation location = ItemSearchLocation.Inventory) { uint freeSlotCount = 0; if (location.HasFlag(ItemSearchLocation.Equipment)) { for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) if (GetItemByPos(InventorySlots.Bag0, i) == null) ++freeSlotCount; for (byte i = ProfessionSlots.Start; i < ProfessionSlots.End; ++i) if (GetItemByPos(InventorySlots.Bag0, i) == null) ++freeSlotCount; } if (location.HasFlag(ItemSearchLocation.Inventory)) { int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = InventorySlots.ItemStart; i < inventoryEnd; ++i) if (GetItemByPos(InventorySlots.Bag0, i) == null) ++freeSlotCount; for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) { Bag bag = GetBagByPos(i); if (bag != null) { for (byte j = 0; j < bag.GetBagSize(); ++j) if (bag.GetItemByPos(j) == null) ++freeSlotCount; } } } if (location.HasFlag(ItemSearchLocation.Bank)) { for (byte i = InventorySlots.BankItemStart; i < InventorySlots.BankItemEnd; ++i) if (GetItemByPos(InventorySlots.Bag0, i) == null) ++freeSlotCount; for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; ++i) { Bag bag = GetBagByPos(i); if (bag != null) { for (byte j = 0; j < bag.GetBagSize(); ++j) if (bag.GetItemByPos(j) == null) ++freeSlotCount; } } } if (location.HasFlag(ItemSearchLocation.ReagentBank)) { for (byte i = InventorySlots.ReagentBagStart; i < InventorySlots.ReagentBagEnd; ++i) { Bag bag = GetBagByPos(i); if (bag != null) for (byte j = 0; j < bag.GetBagSize(); ++j) if (bag.GetItemByPos(j) == null) ++freeSlotCount; } for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) if (GetItemByPos(InventorySlots.Bag0, i) == null) ++freeSlotCount; } return freeSlotCount; } public uint GetFreeInventorySpace() { uint freeSpace = 0; // Check backpack for (byte slot = InventorySlots.ItemStart; slot < InventorySlots.ItemEnd; ++slot) { Item item = GetItemByPos(InventorySlots.Bag0, slot); if (item == null) freeSpace += 1; } // Check bags for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { Bag bag = GetBagByPos(i); if (bag != null) freeSpace += bag.GetFreeSlots(); } return freeSpace; } //Reagent public static bool IsReagentBankPos(ushort pos) { return IsReagentBankPos((byte)(pos >> 8), (byte)(pos & 255)); } public static bool IsReagentBankPos(byte bag, byte slot) { if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ReagentStart && slot < InventorySlots.ReagentEnd)) return true; return false; } //Bags public Bag GetBagByPos(byte bag) { if ((bag >= InventorySlots.BagStart && bag < InventorySlots.BagEnd) || (bag >= InventorySlots.BankBagStart && bag < InventorySlots.BankBagEnd) || (bag >= InventorySlots.ReagentBagStart && bag < InventorySlots.ReagentBagEnd)) { Item item = GetItemByPos(InventorySlots.Bag0, bag); if (item != null) return item.ToBag(); } return null; } public static bool IsBagPos(ushort pos) { byte bag = (byte)(pos >> 8); byte slot = (byte)(pos & 255); if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BagStart && slot < InventorySlots.BagEnd)) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BankBagStart && slot < InventorySlots.BankBagEnd)) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ReagentBagStart && slot < InventorySlots.ReagentBagEnd)) return true; return false; } InventoryResult CanStoreItem_InBag(byte bag, List dest, ItemTemplate pProto, ref uint count, bool merge, bool non_specialized, Item pSrcItem, byte skip_bag, byte skip_slot) { // skip specific bag already processed in first called CanStoreItem_InBag if (bag == skip_bag) return InventoryResult.WrongBagType; // skip not existed bag or self targeted bag Bag pBag = GetBagByPos(bag); if (pBag == null || pBag == pSrcItem) return InventoryResult.WrongBagType; if (pSrcItem != null) { if (pSrcItem.IsNotEmptyBag()) return InventoryResult.DestroyNonemptyBag; if (pSrcItem.HasItemFlag(ItemFieldFlags.Child)) return InventoryResult.WrongBagType3; } ItemTemplate pBagProto = pBag.GetTemplate(); if (pBagProto == null) return InventoryResult.WrongBagType; // specialized bag mode or non-specilized if (non_specialized != (pBagProto.GetClass() == ItemClass.Container && pBagProto.GetSubClass() == (uint)ItemSubClassContainer.Container)) return InventoryResult.WrongBagType; if (!Item.ItemCanGoIntoBag(pProto, pBagProto)) return InventoryResult.WrongBagType; for (byte j = 0; j < pBag.GetBagSize(); j++) { // skip specific slot already processed in first called CanStoreItem_InSpecificSlot if (j == skip_slot) continue; Item pItem2 = GetItemByPos(bag, j); // ignore move item (this slot will be empty at move) if (pItem2 == pSrcItem) pItem2 = null; // if merge skip empty, if !merge skip non-empty if ((pItem2 != null) != merge) continue; uint need_space = pProto.GetMaxStackSize(); if (pItem2 != null) { // can be merged at least partly InventoryResult res = pItem2.CanBeMergedPartlyWith(pProto); if (res != InventoryResult.Ok) continue; // descrease at current stacksize need_space -= pItem2.GetCount(); } if (need_space > count) need_space = count; ItemPosCount newPosition = new((ushort)(bag << 8 | j), need_space); if (!newPosition.IsContainedIn(dest)) { dest.Add(newPosition); count -= need_space; if (count == 0) return InventoryResult.Ok; } } return InventoryResult.Ok; } //Equipment public static bool IsEquipmentPos(ushort pos) { return IsEquipmentPos((byte)(pos >> 8), (byte)(pos & 255)); } public static bool IsEquipmentPos(byte bag, byte slot) { if (bag == InventorySlots.Bag0 && (slot < EquipmentSlot.End)) return true; if (bag == InventorySlots.Bag0 && (slot >= ProfessionSlots.Start && slot < ProfessionSlots.End)) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.BagStart && slot < InventorySlots.BagEnd)) return true; if (bag == InventorySlots.Bag0 && (slot >= InventorySlots.ReagentBagStart && slot < InventorySlots.ReagentBagEnd)) return true; return false; } byte FindEquipSlot(Item item, uint slot, bool swap) { byte[] slots = new byte[4]; slots[0] = ItemConst.NullSlot; slots[1] = ItemConst.NullSlot; slots[2] = ItemConst.NullSlot; slots[3] = ItemConst.NullSlot; switch (item.GetTemplate().GetInventoryType()) { case InventoryType.Head: slots[0] = EquipmentSlot.Head; break; case InventoryType.Neck: slots[0] = EquipmentSlot.Neck; break; case InventoryType.Shoulders: slots[0] = EquipmentSlot.Shoulders; break; case InventoryType.Body: slots[0] = EquipmentSlot.Shirt; break; case InventoryType.Chest: slots[0] = EquipmentSlot.Chest; break; case InventoryType.Robe: slots[0] = EquipmentSlot.Chest; break; case InventoryType.Waist: slots[0] = EquipmentSlot.Waist; break; case InventoryType.Legs: slots[0] = EquipmentSlot.Legs; break; case InventoryType.Feet: slots[0] = EquipmentSlot.Feet; break; case InventoryType.Wrists: slots[0] = EquipmentSlot.Wrist; break; case InventoryType.Hands: slots[0] = EquipmentSlot.Hands; break; case InventoryType.Finger: slots[0] = EquipmentSlot.Finger1; slots[1] = EquipmentSlot.Finger2; break; case InventoryType.Trinket: slots[0] = EquipmentSlot.Trinket1; slots[1] = EquipmentSlot.Trinket2; break; case InventoryType.Cloak: slots[0] = EquipmentSlot.Cloak; break; case InventoryType.Weapon: { slots[0] = EquipmentSlot.MainHand; // suggest offhand slot only if know dual wielding // (this will be replace mainhand weapon at auto equip instead unwonted "you don't known dual wielding" ... if (CanDualWield()) slots[1] = EquipmentSlot.OffHand; break; } case InventoryType.Shield: slots[0] = EquipmentSlot.OffHand; break; case InventoryType.Ranged: slots[0] = EquipmentSlot.MainHand; break; case InventoryType.Weapon2Hand: slots[0] = EquipmentSlot.MainHand; if (CanDualWield() && CanTitanGrip()) slots[1] = EquipmentSlot.OffHand; break; case InventoryType.Tabard: slots[0] = EquipmentSlot.Tabard; break; case InventoryType.WeaponMainhand: slots[0] = EquipmentSlot.MainHand; break; case InventoryType.WeaponOffhand: slots[0] = EquipmentSlot.OffHand; break; case InventoryType.Holdable: slots[0] = EquipmentSlot.OffHand; break; case InventoryType.RangedRight: slots[0] = EquipmentSlot.MainHand; break; case InventoryType.Bag: slots[0] = InventorySlots.BagStart + 0; slots[1] = InventorySlots.BagStart + 1; slots[2] = InventorySlots.BagStart + 2; slots[3] = InventorySlots.BagStart + 3; break; case InventoryType.ProfessionTool: case InventoryType.ProfessionGear: { bool isProfessionTool = item.GetTemplate().GetInventoryType() == InventoryType.ProfessionTool; // Validate item class if (!(item.GetTemplate().GetClass() == ItemClass.Profession)) return ItemConst.NullSlot; // Check if player has profession skill uint itemSkill = (uint)item.GetTemplate().GetSkill(); if (!HasSkill(itemSkill)) return ItemConst.NullSlot; switch ((ItemSubclassProfession)item.GetTemplate().GetSubClass()) { case ItemSubclassProfession.Cooking: slots[0] = isProfessionTool ? ProfessionSlots.CookingTool : ProfessionSlots.CookingGear1; break; case ItemSubclassProfession.Fishing: { // Fishing doesn't make use of gear slots (clientside) if (!isProfessionTool) return ItemConst.NullSlot; slots[0] = ProfessionSlots.FishingTool; break; } case ItemSubclassProfession.Blacksmithing: case ItemSubclassProfession.Leatherworking: case ItemSubclassProfession.Alchemy: case ItemSubclassProfession.Herbalism: case ItemSubclassProfession.Mining: case ItemSubclassProfession.Tailoring: case ItemSubclassProfession.Engineering: case ItemSubclassProfession.Enchanting: case ItemSubclassProfession.Skinning: case ItemSubclassProfession.Jewelcrafting: case ItemSubclassProfession.Inscription: { int professionSlot = GetProfessionSlotFor(itemSkill); if (professionSlot == -1) return ItemConst.NullSlot; if (isProfessionTool) slots[0] = (byte)(ProfessionSlots.Profession1Tool + professionSlot * ProfessionSlots.MaxCount); else { slots[0] = (byte)(ProfessionSlots.Profession1Gear1 + professionSlot * ProfessionSlots.MaxCount); slots[0] = (byte)(ProfessionSlots.Profession1Gear2 + professionSlot * ProfessionSlots.MaxCount); } break; } default: return ItemConst.NullSlot; } break; } default: return ItemConst.NullSlot; } if (slot != ItemConst.NullSlot) { if (swap || GetItemByPos(InventorySlots.Bag0, (byte)slot) == null) for (byte i = 0; i < 4; ++i) if (slots[i] == slot) return (byte)slot; } else { // search free slot at first for (byte i = 0; i < 4; ++i) if (slots[i] != ItemConst.NullSlot && GetItemByPos(InventorySlots.Bag0, slots[i]) == null) // in case 2hand equipped weapon (without titan grip) offhand slot empty but not free if (slots[i] != EquipmentSlot.OffHand || !IsTwoHandUsed()) return slots[i]; // if not found free and can swap return slot with lower item level equipped if (swap) { uint minItemLevel = uint.MaxValue; byte minItemLevelIndex = 0; for (byte i = 0; i < 4; ++i) { if (slots[i] != ItemConst.NullSlot) { Item equipped = GetItemByPos(InventorySlots.Bag0, slots[i]); if (equipped != null) { uint itemLevel = equipped.GetItemLevel(this); if (itemLevel < minItemLevel) { minItemLevel = itemLevel; minItemLevelIndex = i; } } } } return slots[minItemLevelIndex]; } } // no free position return ItemConst.NullSlot; } InventoryResult CanEquipNewItem(byte slot, out ushort dest, uint item, bool swap) { dest = 0; Item pItem = Item.CreateItem(item, 1, ItemContext.None, this); if (pItem != null) { InventoryResult result = CanEquipItem(slot, out dest, pItem, swap); return result; } return InventoryResult.ItemNotFound; } public InventoryResult CanEquipItem(byte slot, out ushort dest, Item pItem, bool swap, bool not_loading = true) { dest = 0; if (pItem != null) { Log.outDebug(LogFilter.Player, "STORAGE: CanEquipItem slot = {0}, item = {1}, count = {2}", slot, pItem.GetEntry(), pItem.GetCount()); ItemTemplate pProto = pItem.GetTemplate(); if (pProto != null) { // item used if (pItem.m_lootGenerated) return InventoryResult.LootGone; if (pItem.IsBindedNotWith(this)) return InventoryResult.NotOwner; // check count of items (skip for auto move for same player from bank) InventoryResult res = CanTakeMoreSimilarItems(pItem); if (res != InventoryResult.Ok) return res; // check this only in game if (not_loading) { // May be here should be more stronger checks; STUNNED checked // ROOT, CONFUSED, DISTRACTED, FLEEING this needs to be checked. if (HasUnitState(UnitState.Stunned)) return InventoryResult.GenericStunned; if (IsCharmed()) return InventoryResult.CantDoThatRightNow; // @todo is this the correct error? // do not allow equipping gear except weapons, offhands, projectiles, relics in // - combat // - in-progress arenas if (!pProto.CanChangeEquipStateInCombat()) { if (IsInCombat()) return InventoryResult.NotInCombat; Battleground bg = GetBattleground(); if (bg != null) if (bg.IsArena() && bg.GetStatus() == BattlegroundStatus.InProgress) return InventoryResult.NotDuringArenaMatch; } if (IsInCombat() && (pProto.GetClass() == ItemClass.Weapon || pProto.GetInventoryType() == InventoryType.Relic) && m_weaponChangeTimer != 0) return InventoryResult.ItemCooldown; Spell currentGenericSpell = GetCurrentSpell(CurrentSpellTypes.Generic); if (currentGenericSpell != null) if (!currentGenericSpell.GetSpellInfo().HasAttribute(SpellAttr6.AllowEquipWhileCasting)) return InventoryResult.ClientLockedOut; Spell currentChanneledSpell = GetCurrentSpell(CurrentSpellTypes.Channeled); if (currentChanneledSpell != null) if (!currentChanneledSpell.GetSpellInfo().HasAttribute(SpellAttr6.AllowEquipWhileCasting)) return InventoryResult.ClientLockedOut; } ContentTuningLevels? requiredLevels = null; // check allowed level (extend range to upper values if MaxLevel more or equal max player level, this let GM set high level with 1...max range items) if (pItem.GetQuality() == ItemQuality.Heirloom) requiredLevels = Global.DB2Mgr.GetContentTuningData(pItem.GetScalingContentTuningId(), 0, true); if (requiredLevels.HasValue && requiredLevels.Value.MaxLevel < SharedConst.DefaultMaxLevel && requiredLevels.Value.MaxLevel < GetLevel() && Global.DB2Mgr.GetHeirloomByItemId(pProto.GetId()) == null) return InventoryResult.NotEquippable; byte eslot = FindEquipSlot(pItem, slot, swap); if (eslot == ItemConst.NullSlot) return InventoryResult.NotEquippable; res = CanUseItem(pItem, not_loading); if (res != InventoryResult.Ok) return res; if (!swap && GetItemByPos(InventorySlots.Bag0, eslot) != null) return InventoryResult.NoSlotAvailable; // if we are swapping 2 equiped items, CanEquipUniqueItem check // should ignore the item we are trying to swap, and not the // destination item. CanEquipUniqueItem should ignore destination // item only when we are swapping weapon from bag byte ignore = ItemConst.NullSlot; switch (eslot) { case EquipmentSlot.MainHand: ignore = EquipmentSlot.OffHand; break; case EquipmentSlot.OffHand: ignore = EquipmentSlot.MainHand; break; case EquipmentSlot.Finger1: ignore = EquipmentSlot.Finger2; break; case EquipmentSlot.Finger2: ignore = EquipmentSlot.Finger1; break; case EquipmentSlot.Trinket1: ignore = EquipmentSlot.Trinket2; break; case EquipmentSlot.Trinket2: ignore = EquipmentSlot.Trinket1; break; case ProfessionSlots.Profession1Gear1: ignore = ProfessionSlots.Profession1Gear2; break; case ProfessionSlots.Profession1Gear2: ignore = ProfessionSlots.Profession1Gear1; break; case ProfessionSlots.Profession2Gear1: ignore = ProfessionSlots.Profession2Gear2; break; case ProfessionSlots.Profession2Gear2: ignore = ProfessionSlots.Profession2Gear1; break; } if (ignore == ItemConst.NullSlot || pItem != GetItemByPos(InventorySlots.Bag0, ignore)) ignore = eslot; // if swap ignore item (equipped also) InventoryResult res2 = CanEquipUniqueItem(pItem, swap ? ignore : ItemConst.NullSlot); if (res2 != InventoryResult.Ok) return res2; // check unique-equipped special item classes if (pProto.GetClass() == ItemClass.Quiver) for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; ++i) { Item pBag = GetItemByPos(InventorySlots.Bag0, i); if (pBag != null) { if (pBag != pItem) { ItemTemplate pBagProto = pBag.GetTemplate(); if (pBagProto != null) if (pBagProto.GetClass() == pProto.GetClass() && (!swap || pBag.GetSlot() != eslot)) return (pBagProto.GetSubClass() == (uint)ItemSubClassQuiver.AmmoPouch) ? InventoryResult.OnlyOneAmmo : InventoryResult.OnlyOneQuiver; } } } InventoryType type = pProto.GetInventoryType(); if (eslot == EquipmentSlot.OffHand) { // Do not allow polearm to be equipped in the offhand (rare case for the only 1h polearm 41750) if (type == InventoryType.Weapon && pProto.GetSubClass() == (uint)ItemSubClassWeapon.Polearm) return InventoryResult.TwoHandSkillNotFound; else if (type == InventoryType.Weapon) { if (!CanDualWield()) return InventoryResult.TwoHandSkillNotFound; } else if (type == InventoryType.WeaponOffhand) { if (!CanDualWield() && !pProto.HasFlag(ItemFlags3.AlwaysAllowDualWield)) return InventoryResult.TwoHandSkillNotFound; } else if (type == InventoryType.Weapon2Hand) { if (!CanDualWield() || !CanTitanGrip()) return InventoryResult.TwoHandSkillNotFound; } if (IsTwoHandUsed()) return InventoryResult.Equipped2handed; } // equip two-hand weapon case (with possible unequip 2 items) if (type == InventoryType.Weapon2Hand) { if (eslot == EquipmentSlot.OffHand) { if (!CanTitanGrip()) return InventoryResult.NotEquippable; } else if (eslot != EquipmentSlot.MainHand) return InventoryResult.NotEquippable; if (!CanTitanGrip()) { // offhand item must can be stored in inventory for offhand item and it also must be unequipped Item offItem = GetItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); List off_dest = new(); if (offItem != null && (!not_loading || CanUnequipItem(((int)InventorySlots.Bag0 << 8) | (int)EquipmentSlot.OffHand, false) != InventoryResult.Ok || CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, off_dest, offItem, false) != InventoryResult.Ok)) return swap ? InventoryResult.CantSwap : InventoryResult.InvFull; } } dest = (ushort)(((uint)InventorySlots.Bag0 << 8) | eslot); return InventoryResult.Ok; } } return !swap ? InventoryResult.ItemNotFound : InventoryResult.CantSwap; } public InventoryResult CanEquipChildItem(Item parentItem) { Item childItem = GetChildItemByGuid(parentItem.GetChildItem()); if (childItem == null) return InventoryResult.Ok; ItemChildEquipmentRecord childEquipement = Global.DB2Mgr.GetItemChildEquipment(parentItem.GetEntry()); if (childEquipement == null) return InventoryResult.Ok; Item dstItem = GetItemByPos(InventorySlots.Bag0, childEquipement.ChildItemEquipSlot); if (dstItem == null) return InventoryResult.Ok; ushort childDest = (ushort)((InventorySlots.Bag0 << 8) | childEquipement.ChildItemEquipSlot); InventoryResult msg = CanUnequipItem(childDest, !childItem.IsBag()); if (msg != InventoryResult.Ok) return msg; // check dest.src move possibility ushort src = parentItem.GetPos(); List dest = new(); if (IsInventoryPos(src)) { msg = CanStoreItem(parentItem.GetBagSlot(), ItemConst.NullSlot, dest, dstItem, true); if (msg != InventoryResult.Ok) msg = CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, dest, dstItem, true); } else if (IsBankPos(src)) { msg = CanBankItem(parentItem.GetBagSlot(), ItemConst.NullSlot, dest, dstItem, true); if (msg != InventoryResult.Ok) msg = CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, dest, dstItem, true); } else if (IsEquipmentPos(src)) return InventoryResult.CantSwap; return msg; } public InventoryResult CanEquipUniqueItem(Item pItem, byte eslot = ItemConst.NullSlot, uint limit_count = 1) { ItemTemplate pProto = pItem.GetTemplate(); // proto based limitations InventoryResult res = CanEquipUniqueItem(pProto, eslot, limit_count); if (res != InventoryResult.Ok) return res; // check unique-equipped on gems foreach (SocketedGem gemData in pItem.m_itemData.Gems) { ItemTemplate pGem = Global.ObjectMgr.GetItemTemplate(gemData.ItemId); if (pGem == null) continue; // include for check equip another gems with same limit category for not equipped item (and then not counted) uint gem_limit_count = (uint)(!pItem.IsEquipped() && pGem.GetItemLimitCategory() != 0 ? pItem.GetGemCountWithLimitCategory(pGem.GetItemLimitCategory()) : 1); InventoryResult ress = CanEquipUniqueItem(pGem, eslot, gem_limit_count); if (ress != InventoryResult.Ok) return ress; } return InventoryResult.Ok; } public InventoryResult CanEquipUniqueItem(ItemTemplate itemProto, byte except_slot = ItemConst.NullSlot, uint limit_count = 1) { // check unique-equipped on item if (itemProto.HasFlag(ItemFlags.UniqueEquippable)) { // there is an equip limit on this item if (HasItemOrGemWithIdEquipped(itemProto.GetId(), 1, except_slot)) return InventoryResult.ItemUniqueEquippable; } // check unique-equipped limit if (itemProto.GetItemLimitCategory() != 0) { ItemLimitCategoryRecord limitEntry = CliDB.ItemLimitCategoryStorage.LookupByKey(itemProto.GetItemLimitCategory()); if (limitEntry == null) return InventoryResult.NotEquippable; // NOTE: limitEntry.mode not checked because if item have have-limit then it applied and to equip case byte limitQuantity = GetItemLimitCategoryQuantity(limitEntry); if (limit_count > limitQuantity) return InventoryResult.ItemMaxLimitCategoryEquippedExceededIs; // there is an equip limit on this item if (HasItemWithLimitCategoryEquipped(itemProto.GetItemLimitCategory(), limitQuantity - limit_count + 1, except_slot)) return InventoryResult.ItemMaxLimitCategoryEquippedExceededIs; else if (HasGemWithLimitCategoryEquipped(itemProto.GetItemLimitCategory(), limitQuantity - limit_count + 1, except_slot)) return InventoryResult.ItemMaxCountEquippedSocketed; } return InventoryResult.Ok; } public InventoryResult CanUnequipItem(ushort pos, bool swap) { // Applied only to equipped items and bank bags if (!IsEquipmentPos(pos) && !IsBagPos(pos)) return InventoryResult.Ok; Item pItem = GetItemByPos(pos); // Applied only to existed equipped item if (pItem == null) return InventoryResult.Ok; Log.outDebug(LogFilter.Player, "STORAGE: CanUnequipItem slot = {0}, item = {1}, count = {2}", pos, pItem.GetEntry(), pItem.GetCount()); ItemTemplate pProto = pItem.GetTemplate(); if (pProto == null) return InventoryResult.ItemNotFound; // item used if (pItem.m_lootGenerated) return InventoryResult.LootGone; if (IsCharmed()) return InventoryResult.CantDoThatRightNow; // @todo is this the correct error? // do not allow unequipping gear except weapons, offhands, projectiles, relics in // - combat // - in-progress arenas if (!pProto.CanChangeEquipStateInCombat()) { if (IsInCombat()) return InventoryResult.NotInCombat; Battleground bg = GetBattleground(); if (bg != null) if (bg.IsArena() && bg.GetStatus() == BattlegroundStatus.InProgress) return InventoryResult.NotDuringArenaMatch; } if (!swap && pItem.IsNotEmptyBag()) return InventoryResult.DestroyNonemptyBag; return InventoryResult.Ok; } //Child public static bool IsChildEquipmentPos(ushort pos) { return IsChildEquipmentPos((byte)(pos >> 8), (byte)(pos & 255)); } //Artifact void ApplyArtifactPowers(Item item, bool apply) { if (item.IsArtifactDisabled()) return; foreach (ArtifactPower artifactPower in item.m_itemData.ArtifactPowers) { byte rank = artifactPower.CurrentRankWithBonus; if (rank == 0) continue; if (CliDB.ArtifactPowerStorage[artifactPower.ArtifactPowerId].HasFlag(ArtifactPowerFlag.ScalesWithNumPowers)) rank = 1; ArtifactPowerRankRecord artifactPowerRank = Global.DB2Mgr.GetArtifactPowerRank(artifactPower.ArtifactPowerId, (byte)(rank - 1)); if (artifactPowerRank == null) continue; ApplyArtifactPowerRank(item, artifactPowerRank, apply); } ArtifactAppearanceRecord artifactAppearance = CliDB.ArtifactAppearanceStorage.LookupByKey(item.GetModifier(ItemModifier.ArtifactAppearanceId)); if (artifactAppearance != null) if (artifactAppearance.OverrideShapeshiftDisplayID != 0 && GetShapeshiftForm() == (ShapeShiftForm)artifactAppearance.OverrideShapeshiftFormID) RestoreDisplayId(); } public void ApplyArtifactPowerRank(Item artifact, ArtifactPowerRankRecord artifactPowerRank, bool apply) { SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(artifactPowerRank.SpellID, Difficulty.None); if (spellInfo == null) return; if (spellInfo.IsPassive()) { AuraApplication powerAura = GetAuraApplication(artifactPowerRank.SpellID, ObjectGuid.Empty, artifact.GetGUID()); if (powerAura != null) { if (apply) { foreach (AuraEffect auraEffect in powerAura.GetBase().GetAuraEffects()) { if (auraEffect == null) continue; if (powerAura.HasEffect(auraEffect.GetEffIndex())) auraEffect.ChangeAmount((int)(artifactPowerRank.AuraPointsOverride != 0 ? artifactPowerRank.AuraPointsOverride : auraEffect.GetSpellEffectInfo().CalcValue())); } } else RemoveAura(powerAura); } else if (apply) { CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); args.SetCastItem(artifact); if (artifactPowerRank.AuraPointsOverride != 0) { foreach (var spellEffectInfo in spellInfo.GetEffects()) args.AddSpellMod(SpellValueMod.BasePoint0 + (int)spellEffectInfo.EffectIndex, (int)artifactPowerRank.AuraPointsOverride); } CastSpell(this, artifactPowerRank.SpellID, args); } } else { if (apply && !HasSpell(artifactPowerRank.SpellID)) { AddTemporarySpell(artifactPowerRank.SpellID); LearnedSpells learnedSpells = new(); LearnedSpellInfo learnedSpellInfo = new(); learnedSpellInfo.SpellID = artifactPowerRank.SpellID; learnedSpells.SuppressMessaging = true; learnedSpells.ClientLearnedSpellData.Add(learnedSpellInfo); SendPacket(learnedSpells); } else if (!apply) { RemoveTemporarySpell(artifactPowerRank.SpellID); UnlearnedSpells unlearnedSpells = new(); unlearnedSpells.SuppressMessaging = true; unlearnedSpells.SpellID.Add(artifactPowerRank.SpellID); SendPacket(unlearnedSpells); } } } void ApplyAzeritePowers(Item item, bool apply) { AzeriteItem azeriteItem = item.ToAzeriteItem(); if (azeriteItem != null) { // milestone powers foreach (uint azeriteItemMilestonePowerId in azeriteItem.m_azeriteItemData.UnlockedEssenceMilestones) ApplyAzeriteItemMilestonePower(azeriteItem, CliDB.AzeriteItemMilestonePowerStorage.LookupByKey(azeriteItemMilestonePowerId), apply); // essences SelectedAzeriteEssences selectedEssences = azeriteItem.GetSelectedAzeriteEssences(); if (selectedEssences != null) { for (byte slot = 0; slot < SharedConst.MaxAzeriteEssenceSlot; ++slot) if (selectedEssences.AzeriteEssenceID[slot] != 0) ApplyAzeriteEssence(azeriteItem, selectedEssences.AzeriteEssenceID[slot], azeriteItem.GetEssenceRank(selectedEssences.AzeriteEssenceID[slot]), (AzeriteItemMilestoneType)Global.DB2Mgr.GetAzeriteItemMilestonePower(slot).Type == AzeriteItemMilestoneType.MajorEssence, apply); } } else { AzeriteEmpoweredItem azeriteEmpoweredItem = item.ToAzeriteEmpoweredItem(); if (azeriteEmpoweredItem != null) { if (!apply || GetItemByEntry(PlayerConst.ItemIdHeartOfAzeroth, ItemSearchLocation.Equipment) != null) { for (int i = 0; i < SharedConst.MaxAzeriteEmpoweredTier; ++i) { AzeritePowerRecord azeritePower = CliDB.AzeritePowerStorage.LookupByKey(azeriteEmpoweredItem.GetSelectedAzeritePower(i)); if (azeritePower != null) ApplyAzeritePower(azeriteEmpoweredItem, azeritePower, apply); } } } } } public void ApplyAzeriteItemMilestonePower(AzeriteItem item, AzeriteItemMilestonePowerRecord azeriteItemMilestonePower, bool apply) { AzeriteItemMilestoneType type = (AzeriteItemMilestoneType)azeriteItemMilestonePower.Type; if (type == AzeriteItemMilestoneType.BonusStamina) { AzeritePowerRecord azeritePower = CliDB.AzeritePowerStorage.LookupByKey(azeriteItemMilestonePower.AzeritePowerID); if (azeritePower != null) { if (apply) CastSpell(this, azeritePower.SpellID, item); else RemoveAurasDueToItemSpell(azeritePower.SpellID, item.GetGUID()); } } } public void ApplyAzeriteEssence(AzeriteItem item, uint azeriteEssenceId, uint rank, bool major, bool apply) { for (uint currentRank = 1; currentRank <= rank; ++currentRank) { AzeriteEssencePowerRecord azeriteEssencePower = Global.DB2Mgr.GetAzeriteEssencePower(azeriteEssenceId, currentRank); if (azeriteEssencePower != null) { ApplyAzeriteEssencePower(item, azeriteEssencePower, major, apply); if (major && currentRank == 1) { if (apply) { CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); args.AddSpellMod(SpellValueMod.BasePoint0, (int)azeriteEssencePower.MajorPowerDescription); CastSpell(this, PlayerConst.SpellIdHeartEssenceActionBarOverride, args); } else RemoveAurasDueToSpell(PlayerConst.SpellIdHeartEssenceActionBarOverride); } } } } void ApplyAzeriteEssencePower(AzeriteItem item, AzeriteEssencePowerRecord azeriteEssencePower, bool major, bool apply) { SpellInfo powerSpell = Global.SpellMgr.GetSpellInfo(azeriteEssencePower.MinorPowerDescription, Difficulty.None); if (powerSpell != null) { if (apply) CastSpell(this, powerSpell.Id, item); else RemoveAurasDueToItemSpell(powerSpell.Id, item.GetGUID()); } if (major) { powerSpell = Global.SpellMgr.GetSpellInfo(azeriteEssencePower.MajorPowerDescription, Difficulty.None); if (powerSpell != null) { if (powerSpell.IsPassive()) { if (apply) CastSpell(this, powerSpell.Id, item); else RemoveAurasDueToItemSpell(powerSpell.Id, item.GetGUID()); } else { if (apply) LearnSpell(powerSpell.Id, true, 0, true); else RemoveSpell(powerSpell.Id, false, false, true); } } } } public void ApplyAzeritePower(AzeriteEmpoweredItem item, AzeritePowerRecord azeritePower, bool apply) { if (apply) { if (azeritePower.SpecSetID == 0 || Global.DB2Mgr.IsSpecSetMember(azeritePower.SpecSetID, (uint)GetPrimarySpecialization())) CastSpell(this, azeritePower.SpellID, item); } else RemoveAurasDueToItemSpell(azeritePower.SpellID, item.GetGUID()); } public bool HasItemOrGemWithIdEquipped(uint item, uint count, byte except_slot = ItemConst.NullSlot) { uint tempcount = 0; ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(item); bool includeGems = pProto?.GetGemProperties() != 0; return !ForEachItem(ItemSearchLocation.Equipment, pItem => { if (pItem.GetSlot() != except_slot) { if (pItem.GetEntry() == item) tempcount += pItem.GetCount(); if (includeGems) tempcount += pItem.GetGemCountWithID(item); if (tempcount >= count) return false; } return true; }); } bool HasItemWithLimitCategoryEquipped(uint limitCategory, uint count, byte except_slot) { uint tempcount = 0; return !ForEachItem(ItemSearchLocation.Equipment, pItem => { if (pItem.GetSlot() == except_slot) return true; if (pItem.GetTemplate().GetItemLimitCategory() != limitCategory) return true; tempcount += pItem.GetCount(); if (tempcount >= count) return false; return true; }); } bool HasGemWithLimitCategoryEquipped(uint limitCategory, uint count, byte except_slot) { uint tempcount = 0; return !ForEachItem(ItemSearchLocation.Equipment, pItem => { if (pItem.GetSlot() == except_slot) return true; ItemTemplate pProto = pItem.GetTemplate(); if (pProto == null) return true; tempcount += pItem.GetGemCountWithLimitCategory(limitCategory); if (tempcount >= count) return false; return true; }); } //Visual public void SetVisibleItemSlot(uint slot, Item pItem) { var itemField = m_values.ModifyValue(m_playerData).ModifyValue(m_playerData.VisibleItems, (int)slot); if (pItem != null) { SetUpdateFieldValue(itemField.ModifyValue(itemField.ItemID), pItem.GetVisibleEntry(this)); SetUpdateFieldValue(itemField.ModifyValue(itemField.SecondaryItemModifiedAppearanceID), pItem.GetVisibleSecondaryModifiedAppearanceId(this)); SetUpdateFieldValue(itemField.ModifyValue(itemField.ItemAppearanceModID), pItem.GetVisibleAppearanceModId(this)); SetUpdateFieldValue(itemField.ModifyValue(itemField.ItemVisual), pItem.GetVisibleItemVisual(this)); } else { SetUpdateFieldValue(itemField.ModifyValue(itemField.ItemID), 0u); SetUpdateFieldValue(itemField.ModifyValue(itemField.SecondaryItemModifiedAppearanceID), 0u); SetUpdateFieldValue(itemField.ModifyValue(itemField.ItemAppearanceModID), (ushort)0); SetUpdateFieldValue(itemField.ModifyValue(itemField.ItemVisual), (ushort)0); } } void VisualizeItem(uint slot, Item pItem) { if (pItem == null) return; // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory) if (pItem.GetBonding() == ItemBondingType.OnEquip || pItem.GetBonding() == ItemBondingType.OnAcquire || pItem.GetBonding() == ItemBondingType.Quest) { pItem.SetBinding(true); if (IsInWorld) GetSession().GetCollectionMgr().AddItemAppearance(pItem); } Log.outDebug(LogFilter.Player, "STORAGE: EquipItem slot = {0}, item = {1}", slot, pItem.GetEntry()); m_items[slot] = pItem; SetInvSlot(slot, pItem.GetGUID()); pItem.SetContainedIn(GetGUID()); pItem.SetOwnerGUID(GetGUID()); pItem.SetSlot((byte)slot); pItem.SetContainer(null); if (slot < EquipmentSlot.End) SetVisibleItemSlot(slot, pItem); pItem.SetState(ItemUpdateState.Changed, this); } public void DestroyItem(byte bag, byte slot, bool update) { Item pItem = GetItemByPos(bag, slot); if (pItem != null) { Log.outDebug(LogFilter.Player, "STORAGE: DestroyItem bag = {0}, slot = {1}, item = {2}", bag, slot, pItem.GetEntry()); // Also remove all contained items if the item is a bag. // This if () prevents item saving crashes if the condition for a bag to be empty before being destroyed was bypassed somehow. if (pItem.IsNotEmptyBag()) for (byte i = 0; i < ItemConst.MaxBagSize; ++i) DestroyItem(slot, i, update); if (pItem.IsWrapped()) { PreparedStatement stmt = CharacterDatabase.GetPreparedStatement(CharStatements.DEL_GIFT); stmt.AddValue(0, pItem.GetGUID().GetCounter()); DB.Characters.Execute(stmt); } RemoveEnchantmentDurations(pItem); RemoveItemDurations(pItem); pItem.SetNotRefundable(this); pItem.ClearSoulboundTradeable(this); RemoveTradeableItem(pItem); ApplyItemObtainSpells(pItem, false); ApplyItemLootedSpell(pItem, false); Global.ScriptMgr.OnItemRemove(this, pItem); Bag pBag; ItemTemplate pProto = pItem.GetTemplate(); if (bag == InventorySlots.Bag0) { SetInvSlot(slot, ObjectGuid.Empty); // equipment and equipped bags can have applied bonuses if (slot < InventorySlots.BagEnd) { // 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, pItem); _ApplyItemMods(pItem, slot, false); } if (slot < EquipmentSlot.End) { // update expertise and armor penetration - passive auras may need it switch (slot) { case EquipmentSlot.MainHand: case EquipmentSlot.OffHand: RecalculateRating(CombatRating.ArmorPenetration); break; default: break; } if (slot == EquipmentSlot.MainHand) UpdateExpertise(WeaponAttackType.BaseAttack); else if (slot == EquipmentSlot.OffHand) UpdateExpertise(WeaponAttackType.OffAttack); // equipment visual show SetVisibleItemSlot(slot, null); } m_items[slot] = null; } else if ((pBag = GetBagByPos(bag)) != null) pBag.RemoveItem(slot, update); // Delete rolled money / loot from db. // MUST be done before RemoveFromWorld() or GetTemplate() fails if (pProto.HasFlag(ItemFlags.HasLoot)) Global.LootItemStorage.RemoveStoredLootForContainer(pItem.GetGUID().GetCounter()); ItemRemovedQuestCheck(pItem.GetEntry(), pItem.GetCount()); if (IsInWorld && update) { pItem.RemoveFromWorld(); pItem.DestroyForPlayer(this); } //pItem.SetOwnerGUID(ObjectGuid.Empty); pItem.SetContainedIn(ObjectGuid.Empty); pItem.SetSlot(ItemConst.NullSlot); pItem.SetState(ItemUpdateState.Removed, this); if (pProto.GetInventoryType() != InventoryType.NonEquip) UpdateAverageItemLevelTotal(); if (bag == InventorySlots.Bag0) UpdateAverageItemLevelEquipped(); } } public uint DestroyItemCount(uint itemEntry, uint count, bool update, bool unequip_check = true) { Log.outDebug(LogFilter.Player, "STORAGE: DestroyItemCount item = {0}, count = {1}", itemEntry, count); uint remcount = 0; // in inventory int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = InventorySlots.ItemStart; i < inventoryEnd; ++i) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) { if (item.GetEntry() == itemEntry && !item.IsInTrade()) { if (item.GetCount() + remcount <= count) { // all items in inventory can unequipped remcount += item.GetCount(); DestroyItem(InventorySlots.Bag0, i, update); if (remcount >= count) return remcount; } else { item.SetCount(item.GetCount() - count + remcount); ItemRemovedQuestCheck(item.GetEntry(), count - remcount); if (IsInWorld && update) item.SendUpdateToPlayer(this); item.SetState(ItemUpdateState.Changed, this); return count; } } } } // in inventory bags for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { Bag bag = GetBagByPos(i); if (bag != null) { for (byte j = 0; j < bag.GetBagSize(); j++) { Item item = bag.GetItemByPos(j); if (item != null) { if (item.GetEntry() == itemEntry && !item.IsInTrade()) { // all items in bags can be unequipped if (item.GetCount() + remcount <= count) { remcount += item.GetCount(); DestroyItem(i, j, update); if (remcount >= count) return remcount; } else { item.SetCount(item.GetCount() - count + remcount); ItemRemovedQuestCheck(item.GetEntry(), count - remcount); if (IsInWorld && update) item.SendUpdateToPlayer(this); item.SetState(ItemUpdateState.Changed, this); return count; } } } } } } // in equipment and bag list for (byte i = EquipmentSlot.Start; i < InventorySlots.BagEnd; i++) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) { if (item.GetEntry() == itemEntry && !item.IsInTrade()) { if (item.GetCount() + remcount <= count) { if (!unequip_check || CanUnequipItem((ushort)(InventorySlots.Bag0 << 8 | i), false) == InventoryResult.Ok) { remcount += item.GetCount(); DestroyItem(InventorySlots.Bag0, i, update); if (remcount >= count) return remcount; } } else { item.SetCount(item.GetCount() - count + remcount); ItemRemovedQuestCheck(item.GetEntry(), count - remcount); if (IsInWorld && update) item.SendUpdateToPlayer(this); item.SetState(ItemUpdateState.Changed, this); return count; } } } } // in bank for (byte i = InventorySlots.BankItemStart; i < InventorySlots.BankItemEnd; i++) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) { if (item.GetEntry() == itemEntry && !item.IsInTrade()) { if (item.GetCount() + remcount <= count) { remcount += item.GetCount(); DestroyItem(InventorySlots.Bag0, i, update); if (remcount >= count) return remcount; } else { item.SetCount(item.GetCount() - count + remcount); ItemRemovedQuestCheck(item.GetEntry(), count - remcount); if (IsInWorld && update) item.SendUpdateToPlayer(this); item.SetState(ItemUpdateState.Changed, this); return count; } } } } // in bank bags for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) { Bag bag = GetBagByPos(i); if (bag != null) { for (byte j = 0; j < bag.GetBagSize(); j++) { Item item = bag.GetItemByPos(j); if (item != null) { if (item.GetEntry() == itemEntry && !item.IsInTrade()) { // all items in bags can be unequipped if (item.GetCount() + remcount <= count) { remcount += item.GetCount(); DestroyItem(i, j, update); if (remcount >= count) return remcount; } else { item.SetCount(item.GetCount() - count + remcount); ItemRemovedQuestCheck(item.GetEntry(), count - remcount); if (IsInWorld && update) item.SendUpdateToPlayer(this); item.SetState(ItemUpdateState.Changed, this); return count; } } } } } } // in bank bag list for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; i++) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) { if (item.GetEntry() == itemEntry && !item.IsInTrade()) { if (item.GetCount() + remcount <= count) { if (!unequip_check || CanUnequipItem((ushort)(InventorySlots.Bag0 << 8 | i), false) == InventoryResult.Ok) { remcount += item.GetCount(); DestroyItem(InventorySlots.Bag0, i, update); if (remcount >= count) return remcount; } } else { item.SetCount(item.GetCount() - count + remcount); ItemRemovedQuestCheck(item.GetEntry(), count - remcount); if (IsInWorld && update) item.SendUpdateToPlayer(this); item.SetState(ItemUpdateState.Changed, this); return count; } } } } for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) { if (item.GetEntry() == itemEntry && !item.IsInTrade()) { if (item.GetCount() + remcount <= count) { // all keys can be unequipped remcount += item.GetCount(); DestroyItem(InventorySlots.Bag0, i, update); if (remcount >= count) return remcount; } else { item.SetCount(item.GetCount() - count + remcount); ItemRemovedQuestCheck(item.GetEntry(), count - remcount); if (IsInWorld && update) item.SendUpdateToPlayer(this); item.SetState(ItemUpdateState.Changed, this); return count; } } } } for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) { if (item.GetEntry() == itemEntry && !item.IsInTrade()) { if (item.GetCount() + remcount <= count) { // all keys can be unequipped remcount += item.GetCount(); DestroyItem(InventorySlots.Bag0, i, update); if (remcount >= count) return remcount; } else { item.SetCount(item.GetCount() - count + remcount); ItemRemovedQuestCheck(item.GetEntry(), count - remcount); if (IsInWorld && update) item.SendUpdateToPlayer(this); item.SetState(ItemUpdateState.Changed, this); return count; } } } } return remcount; } public void DestroyItemCount(Item pItem, ref uint count, bool update) { if (pItem == null) return; Log.outDebug(LogFilter.Player, "STORAGE: DestroyItemCount item (GUID: {0}, Entry: {1}) count = {2}", pItem.GetGUID().ToString(), pItem.GetEntry(), count); if (pItem.GetCount() <= count) { count -= pItem.GetCount(); DestroyItem(pItem.GetBagSlot(), pItem.GetSlot(), update); } else { ItemRemovedQuestCheck(pItem.GetEntry(), count); pItem.SetCount(pItem.GetCount() - count); count = 0; if (IsInWorld && update) pItem.SendUpdateToPlayer(this); pItem.SetState(ItemUpdateState.Changed, this); } } public void AutoStoreLoot(uint loot_id, LootStore store, ItemContext context = 0, bool broadcast = false, bool createdByPlayer = false) { AutoStoreLoot(ItemConst.NullBag, ItemConst.NullSlot, loot_id, store, context, broadcast); } void AutoStoreLoot(byte bag, byte slot, uint loot_id, LootStore store, ItemContext context = 0, bool broadcast = false, bool createdByPlayer = false) { Loot loot = new(null, ObjectGuid.Empty, LootType.None, null); loot.FillLoot(loot_id, store, this, true, false, LootModes.Default, context); loot.AutoStore(this, bag, slot, broadcast, createdByPlayer); Unit.ProcSkillsAndAuras(this, null, new ProcFlagsInit(ProcFlags.Looted), new ProcFlagsInit(ProcFlags.None), ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); } public byte GetInventorySlotCount() { return m_activePlayerData.NumBackpackSlots; } public void SetInventorySlotCount(byte slots) { //ASSERT(slots <= (INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START)); if (slots < GetInventorySlotCount()) { List unstorableItems = new(); for (byte slot = (byte)(InventorySlots.ItemStart + slots); slot < InventorySlots.ItemEnd; ++slot) { Item unstorableItem = GetItemByPos(InventorySlots.Bag0, slot); if (unstorableItem != null) unstorableItems.Add(unstorableItem); } if (!unstorableItems.Empty()) { int fullBatches = unstorableItems.Count / SharedConst.MaxMailItems; int remainder = unstorableItems.Count % SharedConst.MaxMailItems; SQLTransaction trans = new(); var sendItemsBatch = new Action((batchNumber, batchSize) => { MailDraft draft = new(Global.ObjectMgr.GetCypherString(CypherStrings.NotEquippedItem), "There were problems with equipping item(s)."); for (int j = 0; j < batchSize; ++j) draft.AddItem(unstorableItems[batchNumber * SharedConst.MaxMailItems + j]); draft.SendMailTo(trans, this, new MailSender(this, MailStationery.Gm), MailCheckMask.Copied); }); for (int batch = 0; batch < fullBatches; ++batch) sendItemsBatch(batch, SharedConst.MaxMailItems); if (remainder != 0) sendItemsBatch(fullBatches, remainder); DB.Characters.CommitTransaction(trans); SendPacket(new InventoryFullOverflow()); } } SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.NumBackpackSlots), slots); } public byte GetBankBagSlotCount() { return m_activePlayerData.NumBankSlots; } public void SetBankBagSlotCount(byte count) { SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.NumBankSlots), count); } //Loot public ObjectGuid GetLootGUID() { return m_playerData.LootTargetGUID; } public void SetLootGUID(ObjectGuid guid) { SetUpdateFieldValue(m_values.ModifyValue(m_playerData).ModifyValue(m_playerData.LootTargetGUID), guid); } public void StoreLootItem(ObjectGuid lootWorldObjectGuid, byte lootSlot, Loot loot, AELootResult aeResult = null) { LootItem item = loot.LootItemInSlot(lootSlot, this, out NotNormalLootItem ffaItem); if (item == null || item.is_looted) { SendEquipError(InventoryResult.LootGone); return; } if (!item.HasAllowedLooter(GetGUID())) { SendLootReleaseAll(); return; } if (item.is_blocked) { SendLootReleaseAll(); return; } // dont allow protected item to be looted by someone else if (!item.rollWinnerGUID.IsEmpty() && item.rollWinnerGUID != GetGUID()) { SendLootReleaseAll(); return; } List dest = new(); InventoryResult msg = CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, item.itemid, item.count); if (msg == InventoryResult.Ok) { Item newitem = StoreNewItem(dest, item.itemid, true, item.randomBonusListId, item.GetAllowedLooters(), item.context); if (ffaItem != null) { //freeforall case, notify only one player of the removal ffaItem.is_looted = true; SendNotifyLootItemRemoved(loot.GetGUID(), loot.GetOwnerGUID(), lootSlot); } else //not freeforall, notify everyone loot.NotifyItemRemoved(lootSlot, GetMap()); //if only one person is supposed to loot the item, then set it to looted if (!item.freeforall) item.is_looted = true; --loot.unlootedCount; if (newitem != null && (newitem.GetQuality() > ItemQuality.Epic || (newitem.GetQuality() == ItemQuality.Epic && newitem.GetItemLevel(this) >= GuildConst.MinNewsItemLevel))) { Guild guild = GetGuild(); if (guild != null) guild.AddGuildNews(GuildNews.ItemLooted, GetGUID(), 0, item.itemid); } // if aeLooting then we must delay sending out item so that it appears properly stacked in chat if (aeResult == null || newitem == null) { SendNewItem(newitem, item.count, false, false, true, loot.GetDungeonEncounterId()); UpdateCriteria(CriteriaType.LootItem, item.itemid, item.count); UpdateCriteria(CriteriaType.GetLootByType, item.itemid, item.count, (uint)SharedConst.GetLootTypeForClient(loot.loot_type)); UpdateCriteria(CriteriaType.LootAnyItem, item.itemid, item.count); } else aeResult.Add(newitem, item.count, SharedConst.GetLootTypeForClient(loot.loot_type), loot.GetDungeonEncounterId()); // LootItem is being removed (looted) from the container, delete it from the DB. if (loot.loot_type == LootType.Item) Global.LootItemStorage.RemoveStoredLootItemForContainer(lootWorldObjectGuid.GetCounter(), item.itemid, item.count, item.LootListId); if (newitem != null) ApplyItemLootedSpell(newitem, true); else ApplyItemLootedSpell(Global.ObjectMgr.GetItemTemplate(item.itemid)); } else SendEquipError(msg, null, null, item.itemid); } public Dictionary GetAELootView() { return m_AELootView; } /// /// if in a Battleground a player dies, and an enemy removes the insignia, the player's bones is lootable /// Called by remove insignia spell effect /// /// public void RemovedInsignia(Player looterPlr) { // If player is not in battleground and not in worldpvpzone if (GetBattlegroundId() == 0 && !IsInWorldPvpZone()) return; // If not released spirit, do it ! if (m_deathTimer > 0) { m_deathTimer = 0; BuildPlayerRepop(); RepopAtGraveyard(); } _corpseLocation = new WorldLocation(); // We have to convert player corpse to bones, not to be able to resurrect there // SpawnCorpseBones isn't handy, 'cos it saves player while he in BG Corpse bones = GetMap().ConvertCorpseToBones(GetGUID(), true); if (bones == null) return; // Now we must make bones lootable, and send player loot bones.SetCorpseDynamicFlag(CorpseDynFlags.Lootable); bones.loot = new Loot(GetMap(), bones.GetGUID(), LootType.Insignia, looterPlr.GetGroup()); // For AV Achievement Battleground bg = GetBattleground(); if (bg != null) { if (bg.GetTypeID() == BattlegroundTypeId.AV) bones.loot.FillLoot(1, LootStorage.Creature, this, true); } // For wintergrasp Quests else if (GetZoneId() == (uint)AreaId.Wintergrasp) bones.loot.FillLoot(1, LootStorage.Creature, this, true); // It may need a better formula // Now it works like this: lvl10: ~6copper, lvl70: ~9silver bones.loot.gold = (uint)(RandomHelper.URand(50, 150) * 0.016f * Math.Pow((float)GetLevel() / 5.76f, 2.5f) * WorldConfig.GetFloatValue(WorldCfg.RateDropMoney)); bones.lootRecipient = looterPlr; looterPlr.SendLoot(bones.loot); } public void SendLootRelease(ObjectGuid guid) { LootReleaseResponse packet = new(); packet.LootObj = guid; packet.Owner = GetGUID(); SendPacket(packet); } public void SendLootReleaseAll() { SendPacket(new LootReleaseAll()); } public void SendLoot(Loot loot, bool aeLooting = false) { if (!GetLootGUID().IsEmpty() && !aeLooting) _session.DoLootReleaseAll(); Log.outDebug(LogFilter.Loot, $"Player::SendLoot: Player: '{GetName()}' ({GetGUID()}), Loot: {loot.GetOwnerGUID()}"); if (!loot.GetOwnerGUID().IsItem() && !aeLooting) SetLootGUID(loot.GetOwnerGUID()); LootResponse packet = new(); packet.Owner = loot.GetOwnerGUID(); packet.LootObj = loot.GetGUID(); packet.LootMethod = loot.GetLootMethod(); packet.AcquireReason = (byte)SharedConst.GetLootTypeForClient(loot.loot_type); packet.Acquired = true; // false == No Loot (this too^^) packet.AELooting = aeLooting; loot.BuildLootResponse(packet, this); SendPacket(packet); // add 'this' player as one of the players that are looting 'loot' loot.OnLootOpened(GetMap(), GetGUID()); m_AELootView[loot.GetGUID()] = loot; if (loot.loot_type == LootType.Corpse && !loot.GetOwnerGUID().IsItem()) SetUnitFlag(UnitFlags.Looting); } public void SendLootError(ObjectGuid lootObj, ObjectGuid owner, LootError error) { LootResponse packet = new(); packet.LootObj = lootObj; packet.Owner = owner; packet.Acquired = false; packet.FailureReason = error; SendPacket(packet); } public void SendNotifyLootMoneyRemoved(ObjectGuid lootObj) { CoinRemoved packet = new(); packet.LootObj = lootObj; SendPacket(packet); } public void SendNotifyLootItemRemoved(ObjectGuid lootObj, ObjectGuid owner, byte lootListId) { LootRemoved packet = new(); packet.LootObj = lootObj; packet.Owner = owner; packet.LootListID = lootListId; SendPacket(packet); } void SendEquipmentSetList() { LoadEquipmentSet data = new(); foreach (var pair in _equipmentSets) { if (pair.Value.state == EquipmentSetUpdateState.Deleted) continue; data.SetData.Add(pair.Value.Data); } SendPacket(data); } public void SetEquipmentSet(EquipmentSetInfo.EquipmentSetData newEqSet) { if (newEqSet.Guid != 0) { // something wrong... var equipmentSetInfo = _equipmentSets.LookupByKey(newEqSet.Guid); if (equipmentSetInfo == null || equipmentSetInfo.Data.Guid != newEqSet.Guid) { Log.outError(LogFilter.Player, "Player {0} tried to save equipment set {1} (index: {2}), but that equipment set not found!", GetName(), newEqSet.Guid, newEqSet.SetID); return; } } ulong setGuid = (newEqSet.Guid != 0) ? newEqSet.Guid : Global.ObjectMgr.GenerateEquipmentSetGuid(); if (!_equipmentSets.ContainsKey(setGuid)) _equipmentSets[setGuid] = new EquipmentSetInfo(); EquipmentSetInfo eqSlot = _equipmentSets[setGuid]; eqSlot.Data = newEqSet; if (eqSlot.Data.Guid == 0) { eqSlot.Data.Guid = setGuid; EquipmentSetID data = new(); data.GUID = eqSlot.Data.Guid; data.Type = (int)eqSlot.Data.Type; data.SetID = eqSlot.Data.SetID; SendPacket(data); } eqSlot.state = eqSlot.state == EquipmentSetUpdateState.New ? EquipmentSetUpdateState.New : EquipmentSetUpdateState.Changed; } public void DeleteEquipmentSet(ulong id) { foreach (var pair in _equipmentSets) { if (pair.Value.Data.Guid == id) { if (pair.Value.state == EquipmentSetUpdateState.New) _equipmentSets.Remove(pair.Key); else pair.Value.state = EquipmentSetUpdateState.Deleted; break; } } } //Void Storage public bool IsVoidStorageUnlocked() { return HasPlayerFlag(PlayerFlags.VoidUnlocked); } public void UnlockVoidStorage() { SetPlayerFlag(PlayerFlags.VoidUnlocked); } public void LockVoidStorage() { RemovePlayerFlag(PlayerFlags.VoidUnlocked); } public byte GetNextVoidStorageFreeSlot() { for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) if (_voidStorageItems[i] == null) // unused item return i; return SharedConst.VoidStorageMaxSlot; } public byte GetNumOfVoidStorageFreeSlots() { byte count = 0; for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) if (_voidStorageItems[i] == null) count++; return count; } public byte AddVoidStorageItem(VoidStorageItem item) { byte slot = GetNextVoidStorageFreeSlot(); if (slot >= SharedConst.VoidStorageMaxSlot) { GetSession().SendVoidStorageTransferResult(VoidTransferError.Full); return 255; } _voidStorageItems[slot] = item; return slot; } public void DeleteVoidStorageItem(byte slot) { if (slot >= SharedConst.VoidStorageMaxSlot) { GetSession().SendVoidStorageTransferResult(VoidTransferError.InternalError1); return; } _voidStorageItems[slot] = null; } public bool SwapVoidStorageItem(byte oldSlot, byte newSlot) { if (oldSlot >= SharedConst.VoidStorageMaxSlot || newSlot >= SharedConst.VoidStorageMaxSlot || oldSlot == newSlot) return false; _voidStorageItems.Swap(newSlot, oldSlot); return true; } public VoidStorageItem GetVoidStorageItem(byte slot) { if (slot >= SharedConst.VoidStorageMaxSlot) { GetSession().SendVoidStorageTransferResult(VoidTransferError.InternalError1); return null; } return _voidStorageItems[slot]; } public VoidStorageItem GetVoidStorageItem(ulong id, out byte slot) { slot = 0; for (byte i = 0; i < SharedConst.VoidStorageMaxSlot; ++i) { if (_voidStorageItems[i] != null && _voidStorageItems[i].ItemId == id) { slot = i; return _voidStorageItems[i]; } } return null; } //Misc void UpdateItemLevelAreaBasedScaling() { // @todo Activate pvp item levels during world pvp Map map = GetMap(); bool pvpActivity = map.IsBattlegroundOrArena() || ((int)map.GetEntry().Flags[1]).HasAnyFlag(0x40) || HasPvpRulesEnabled(); if (_usePvpItemLevels != pvpActivity) { float healthPct = GetHealthPct(); _RemoveAllItemMods(); ActivatePvpItemLevels(pvpActivity); _ApplyAllItemMods(); SetHealth(MathFunctions.CalculatePct(GetMaxHealth(), healthPct)); } // @todo other types of power scaling such as timewalking } public bool ForEachItem(ItemSearchLocation location, Func callback) { if (location.HasAnyFlag(ItemSearchLocation.Equipment)) { for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; i++) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) if (!callback(item)) return false; } for (byte i = ProfessionSlots.Start; i < ProfessionSlots.End; ++i) { Item pItem = GetItemByPos(InventorySlots.Bag0, i); if (pItem != null) if (!callback(pItem)) return false; } } if (location.HasAnyFlag(ItemSearchLocation.Inventory)) { int inventoryEnd = InventorySlots.ItemStart + GetInventorySlotCount(); for (byte i = InventorySlots.ItemStart; i < inventoryEnd; i++) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) if (!callback(item)) return false; } for (byte i = InventorySlots.ChildEquipmentStart; i < InventorySlots.ChildEquipmentEnd; ++i) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) if (!callback(item)) return false; } for (byte i = InventorySlots.BagStart; i < InventorySlots.BagEnd; i++) { Bag bag = GetBagByPos(i); if (bag != null) { for (byte j = 0; j < bag.GetBagSize(); ++j) { Item pItem = bag.GetItemByPos(j); if (pItem != null) if (!callback(pItem)) return false; } } } } if (location.HasAnyFlag(ItemSearchLocation.Bank)) { for (byte i = InventorySlots.BankItemStart; i < InventorySlots.BankItemEnd; ++i) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) if (!callback(item)) return false; } for (byte i = InventorySlots.BankBagStart; i < InventorySlots.BankBagEnd; ++i) { Bag bag = GetBagByPos(i); if (bag != null) { for (byte j = 0; j < bag.GetBagSize(); ++j) { Item pItem = bag.GetItemByPos(j); if (pItem != null) if (!callback(pItem)) return false; } } } } if (location.HasAnyFlag(ItemSearchLocation.ReagentBank)) { for (byte i = InventorySlots.ReagentBagStart; i < InventorySlots.ReagentBagEnd; ++i) { Bag bag = GetBagByPos(i); if (bag != null) { for (byte j = 0; j < bag.GetBagSize(); ++j) { Item pItem = bag.GetItemByPos(j); if (pItem != null) if (!callback(pItem)) return false; } } } for (byte i = InventorySlots.ReagentStart; i < InventorySlots.ReagentEnd; ++i) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) if (!callback(item)) return false; } } return true; } delegate void EquipmentSlotDelegate(byte equipmentSlot, bool checkDuplicateGuid = false); bool ForEachEquipmentSlot(InventoryType inventoryType, bool canDualWield, bool canTitanGrip, EquipmentSlotDelegate callback) { switch (inventoryType) { case InventoryType.Head: callback(EquipmentSlot.Head); return true; case InventoryType.Neck: callback(EquipmentSlot.Neck); return true; case InventoryType.Shoulders: callback(EquipmentSlot.Shoulders); return true; case InventoryType.Body: callback(EquipmentSlot.Shirt); return true; case InventoryType.Robe: case InventoryType.Chest: callback(EquipmentSlot.Chest); return true; case InventoryType.Waist: callback(EquipmentSlot.Waist); return true; case InventoryType.Legs: callback(EquipmentSlot.Legs); return true; case InventoryType.Feet: callback(EquipmentSlot.Feet); return true; case InventoryType.Wrists: callback(EquipmentSlot.Wrist); return true; case InventoryType.Hands: callback(EquipmentSlot.Hands); return true; case InventoryType.Cloak: callback(EquipmentSlot.Cloak); return true; case InventoryType.Finger: callback(EquipmentSlot.Finger1); callback(EquipmentSlot.Finger2, true); return true; case InventoryType.Trinket: callback(EquipmentSlot.Trinket1); callback(EquipmentSlot.Trinket2, true); return true; case InventoryType.Weapon: callback(EquipmentSlot.MainHand); if (canDualWield) callback(EquipmentSlot.OffHand, true); return true; case InventoryType.Weapon2Hand: callback(EquipmentSlot.MainHand); if (canDualWield && canTitanGrip) callback(EquipmentSlot.OffHand, true); return true; case InventoryType.Ranged: case InventoryType.RangedRight: case InventoryType.WeaponMainhand: callback(EquipmentSlot.MainHand); return true; case InventoryType.Shield: case InventoryType.Holdable: case InventoryType.WeaponOffhand: callback(EquipmentSlot.OffHand); return true; default: return false; } } public void UpdateAverageItemLevelTotal() { (InventoryType inventoryType, uint itemLevel, ObjectGuid guid)[] bestItemLevels = new (InventoryType inventoryType, uint itemLevel, ObjectGuid guid)[EquipmentSlot.End]; float sum = 0; ForEachItem(ItemSearchLocation.Everywhere, item => { ItemTemplate itemTemplate = item.GetTemplate(); if (itemTemplate != null && itemTemplate.GetInventoryType() < InventoryType.ProfessionTool) { ushort dest; if (item.IsEquipped()) { uint itemLevel = item.GetItemLevel(this); InventoryType inventoryType = itemTemplate.GetInventoryType(); ref var slotData = ref bestItemLevels[item.GetSlot()]; if (itemLevel > slotData.Item2) { sum += itemLevel - slotData.Item2; slotData = (inventoryType, itemLevel, item.GetGUID()); } } else if (CanEquipItem(ItemConst.NullSlot, out dest, item, true, false) == InventoryResult.Ok) { uint itemLevel = item.GetItemLevel(this); InventoryType inventoryType = itemTemplate.GetInventoryType(); ForEachEquipmentSlot(inventoryType, m_canDualWield, m_canTitanGrip, (slot, checkDuplicateGuid) => { if (checkDuplicateGuid) { foreach (var slotData1 in bestItemLevels) if (slotData1.guid == item.GetGUID()) return; } ref var slotData = ref bestItemLevels[slot]; if (itemLevel > slotData.itemLevel) { sum += itemLevel - slotData.itemLevel; slotData = (inventoryType, itemLevel, item.GetGUID()); } }); } } return true; }); // If main hand is a 2h weapon, count it twice var mainHand = bestItemLevels[EquipmentSlot.MainHand]; if (!m_canTitanGrip && mainHand.inventoryType == InventoryType.Weapon2Hand) sum += mainHand.itemLevel; sum /= 16.0f; SetAverageItemLevelTotal(sum); } public void UpdateAverageItemLevelEquipped() { float totalItemLevel = 0; for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; i++) { Item item = GetItemByPos(InventorySlots.Bag0, i); if (item != null) { uint itemLevel = item.GetItemLevel(this); totalItemLevel += itemLevel; if (!m_canTitanGrip && i == EquipmentSlot.MainHand && item.GetTemplate().GetInventoryType() == InventoryType.Weapon2Hand) // 2h weapon counts twice totalItemLevel += itemLevel; } } totalItemLevel /= 16.0f; SetAverageItemLevelEquipped(totalItemLevel); } } }