Files
CypherCore/Source/Game/Handlers/ItemHandler.cs
T

1112 lines
46 KiB
C#

/*
* Copyright (C) 2012-2020 CypherCore <http://github.com/CypherCore>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Framework.Database;
using Game.BattlePets;
using Game.DataStorage;
using Game.Entities;
using Game.Network;
using Game.Network.Packets;
using System;
using System.Collections.Generic;
namespace Game
{
public partial class WorldSession
{
[WorldPacketHandler(ClientOpcodes.SplitItem)]
void HandleSplitItem(SplitItem splitItem)
{
if (splitItem.Inv.Items.Count != 0)
{
Log.outError(LogFilter.Network, "WORLD: HandleSplitItemOpcode - Invalid itemCount ({0})", splitItem.Inv.Items.Count);
return;
}
ushort src = (ushort)((splitItem.FromPackSlot << 8) | splitItem.FromSlot);
ushort dst = (ushort)((splitItem.ToPackSlot << 8) | splitItem.ToSlot);
if (src == dst)
return;
if (splitItem.Quantity == 0)
return; //check count - if zero it's fake packet
if (!_player.IsValidPos(splitItem.FromPackSlot, splitItem.FromSlot, true))
{
_player.SendEquipError(InventoryResult.ItemNotFound);
return;
}
if (!_player.IsValidPos(splitItem.ToPackSlot, splitItem.ToSlot, false)) // can be autostore pos
{
_player.SendEquipError(InventoryResult.WrongSlot);
return;
}
_player.SplitItem(src, dst, (uint)splitItem.Quantity);
}
[WorldPacketHandler(ClientOpcodes.SwapInvItem)]
void HandleSwapInvenotryItem(SwapInvItem swapInvItem)
{
if (swapInvItem.Inv.Items.Count != 2)
{
Log.outError(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - Invalid itemCount ({0})", swapInvItem.Inv.Items.Count);
return;
}
// prevent attempt swap same item to current position generated by client at special checting sequence
if (swapInvItem.Slot1 == swapInvItem.Slot2)
return;
if (!GetPlayer().IsValidPos(InventorySlots.Bag0, swapInvItem.Slot1, true))
{
GetPlayer().SendEquipError(InventoryResult.ItemNotFound);
return;
}
if (!GetPlayer().IsValidPos(InventorySlots.Bag0, swapInvItem.Slot2, true))
{
GetPlayer().SendEquipError(InventoryResult.WrongSlot);
return;
}
if (Player.IsBankPos(InventorySlots.Bag0, swapInvItem.Slot1) && !CanUseBank())
{
Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString());
return;
}
if (Player.IsBankPos(InventorySlots.Bag0, swapInvItem.Slot2) && !CanUseBank())
{
Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString());
return;
}
ushort src = (ushort)((InventorySlots.Bag0 << 8) | swapInvItem.Slot1);
ushort dst = (ushort)((InventorySlots.Bag0 << 8) | swapInvItem.Slot2);
GetPlayer().SwapItem(src, dst);
}
[WorldPacketHandler(ClientOpcodes.AutoEquipItemSlot)]
void HandleAutoEquipItemSlot(AutoEquipItemSlot packet)
{
// cheating attempt, client should never send opcode in that case
if (packet.Inv.Items.Count != 1 || !Player.IsEquipmentPos(InventorySlots.Bag0, packet.ItemDstSlot))
return;
Item item = GetPlayer().GetItemByGuid(packet.Item);
ushort dstPos = (ushort)(packet.ItemDstSlot | (InventorySlots.Bag0 << 8));
ushort srcPos = (ushort)(packet.Inv.Items[0].Slot | (packet.Inv.Items[0].ContainerSlot << 8));
if (item == null || item.GetPos() != srcPos || srcPos == dstPos)
return;
GetPlayer().SwapItem(srcPos, dstPos);
}
[WorldPacketHandler(ClientOpcodes.SwapItem)]
void HandleSwapItem(SwapItem swapItem)
{
if (swapItem.Inv.Items.Count != 2)
{
Log.outError(LogFilter.Network, "WORLD: HandleSwapItem - Invalid itemCount ({0})", swapItem.Inv.Items.Count);
return;
}
ushort src = (ushort)((swapItem.ContainerSlotA << 8) | swapItem.SlotA);
ushort dst = (ushort)((swapItem.ContainerSlotB << 8) | swapItem.SlotB);
var pl = GetPlayer();
// prevent attempt swap same item to current position generated by client at special checting sequence
if (src == dst)
return;
if (!pl.IsValidPos(swapItem.ContainerSlotA, swapItem.SlotA, true))
{
pl.SendEquipError(InventoryResult.ItemNotFound);
return;
}
if (!pl.IsValidPos(swapItem.ContainerSlotB, swapItem.SlotB, true))
{
pl.SendEquipError(InventoryResult.WrongSlot);
return;
}
if (Player.IsBankPos(swapItem.ContainerSlotA, swapItem.SlotA) && !CanUseBank())
{
Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString());
return;
}
if (Player.IsBankPos(swapItem.ContainerSlotB, swapItem.SlotB) && !CanUseBank())
{
Log.outDebug(LogFilter.Network, "WORLD: HandleSwapInvItemOpcode - {0} not found or you can't interact with him.", m_currentBankerGUID.ToString());
return;
}
pl.SwapItem(src, dst);
}
[WorldPacketHandler(ClientOpcodes.AutoEquipItem)]
void HandleAutoEquipItem(AutoEquipItem autoEquipItem)
{
if (autoEquipItem.Inv.Items.Count != 1)
{
Log.outError(LogFilter.Network, "WORLD: HandleAutoEquipItem - Invalid itemCount ({0})", autoEquipItem.Inv.Items.Count);
return;
}
var pl = GetPlayer();
Item srcItem = pl.GetItemByPos(autoEquipItem.PackSlot, autoEquipItem.Slot);
if (srcItem == null)
return; // only at cheat
ushort dest;
InventoryResult msg = pl.CanEquipItem(ItemConst.NullSlot, out dest, srcItem, !srcItem.IsBag());
if (msg != InventoryResult.Ok)
{
pl.SendEquipError(msg, srcItem);
return;
}
ushort src = srcItem.GetPos();
if (dest == src) // prevent equip in same slot, only at cheat
return;
Item dstItem = pl.GetItemByPos(dest);
if (dstItem == null) // empty slot, simple case
{
if (!srcItem.GetChildItem().IsEmpty())
{
InventoryResult childEquipResult = _player.CanEquipChildItem(srcItem);
if (childEquipResult != InventoryResult.Ok)
{
_player.SendEquipError(msg, srcItem);
return;
}
}
pl.RemoveItem(autoEquipItem.PackSlot, autoEquipItem.Slot, true);
pl.EquipItem(dest, srcItem, true);
if (!srcItem.GetChildItem().IsEmpty())
_player.EquipChildItem(autoEquipItem.PackSlot, autoEquipItem.Slot, srcItem);
pl.AutoUnequipOffhandIfNeed();
}
else // have currently equipped item, not simple case
{
byte dstbag = dstItem.GetBagSlot();
byte dstslot = dstItem.GetSlot();
msg = pl.CanUnequipItem(dest, !srcItem.IsBag());
if (msg != InventoryResult.Ok)
{
pl.SendEquipError(msg, dstItem);
return;
}
if (!dstItem.HasItemFlag(ItemFieldFlags.Child))
{
// check dest.src move possibility
List<ItemPosCount> sSrc = new List<ItemPosCount>();
ushort eSrc = 0;
if (pl.IsInventoryPos(src))
{
msg = pl.CanStoreItem(autoEquipItem.PackSlot, autoEquipItem.Slot, sSrc, dstItem, true);
if (msg != InventoryResult.Ok)
msg = pl.CanStoreItem(autoEquipItem.PackSlot, ItemConst.NullSlot, sSrc, dstItem, true);
if (msg != InventoryResult.Ok)
msg = pl.CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true);
}
else if (Player.IsBankPos(src))
{
msg = pl.CanBankItem(autoEquipItem.PackSlot, autoEquipItem.Slot, sSrc, dstItem, true);
if (msg != InventoryResult.Ok)
msg = pl.CanBankItem(autoEquipItem.PackSlot, ItemConst.NullSlot, sSrc, dstItem, true);
if (msg != InventoryResult.Ok)
msg = pl.CanBankItem(ItemConst.NullBag, ItemConst.NullSlot, sSrc, dstItem, true);
}
else if (Player.IsEquipmentPos(src))
{
msg = pl.CanEquipItem(autoEquipItem.Slot, out eSrc, dstItem, true);
if (msg == InventoryResult.Ok)
msg = pl.CanUnequipItem(eSrc, true);
}
if (msg == InventoryResult.Ok && Player.IsEquipmentPos(dest) && !srcItem.GetChildItem().IsEmpty())
msg = _player.CanEquipChildItem(srcItem);
if (msg != InventoryResult.Ok)
{
pl.SendEquipError(msg, dstItem, srcItem);
return;
}
// now do moves, remove...
pl.RemoveItem(dstbag, dstslot, false);
pl.RemoveItem(autoEquipItem.PackSlot, autoEquipItem.Slot, false);
// add to dest
pl.EquipItem(dest, srcItem, true);
// add to src
if (pl.IsInventoryPos(src))
pl.StoreItem(sSrc, dstItem, true);
else if (Player.IsBankPos(src))
pl.BankItem(sSrc, dstItem, true);
else if (Player.IsEquipmentPos(src))
pl.EquipItem(eSrc, dstItem, true);
if (Player.IsEquipmentPos(dest) && !srcItem.GetChildItem().IsEmpty())
_player.EquipChildItem(autoEquipItem.PackSlot, autoEquipItem.Slot, srcItem);
}
else
{
Item parentItem = _player.GetItemByGuid(dstItem.GetCreator());
if (parentItem)
{
if (Player.IsEquipmentPos(dest))
{
_player.AutoUnequipChildItem(parentItem);
// dest is now empty
_player.SwapItem(src, dest);
// src is now empty
_player.SwapItem(parentItem.GetPos(), src);
}
}
}
pl.AutoUnequipOffhandIfNeed();
// 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 ((autoEquipItem.PackSlot == InventorySlots.Bag0 && autoEquipItem.Slot < InventorySlots.BagEnd) || (dstbag == InventorySlots.Bag0 && dstslot < InventorySlots.BagEnd))
pl.ApplyItemDependentAuras(null, false);
}
}
[WorldPacketHandler(ClientOpcodes.DestroyItem)]
void HandleDestroyItem(DestroyItem destroyItem)
{
ushort pos = (ushort)((destroyItem.ContainerId << 8) | destroyItem.SlotNum);
// prevent drop unequipable items (in combat, for example) and non-empty bags
if (Player.IsEquipmentPos(pos) || Player.IsBagPos(pos))
{
InventoryResult msg = _player.CanUnequipItem(pos, false);
if (msg != InventoryResult.Ok)
{
_player.SendEquipError(msg, _player.GetItemByPos(pos));
return;
}
}
Item pItem = _player.GetItemByPos(destroyItem.ContainerId, destroyItem.SlotNum);
if (pItem == null)
{
_player.SendEquipError(InventoryResult.ItemNotFound);
return;
}
if (pItem.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.NoUserDestroy))
{
_player.SendEquipError(InventoryResult.DropBoundItem);
return;
}
if (destroyItem.Count != 0)
{
uint i_count = destroyItem.Count;
_player.DestroyItemCount(pItem, ref i_count, true);
}
else
_player.DestroyItem(destroyItem.ContainerId, destroyItem.SlotNum, true);
}
[WorldPacketHandler(ClientOpcodes.ReadItem)]
void HandleReadItem(ReadItem readItem)
{
Item item = _player.GetItemByPos(readItem.PackSlot, readItem.Slot);
if (item != null && item.GetTemplate().GetPageText() != 0)
{
InventoryResult msg = _player.CanUseItem(item);
if (msg == InventoryResult.Ok)
{
ReadItemResultOK packet = new ReadItemResultOK();
packet.Item = item.GetGUID();
SendPacket(packet);
}
else
{
// @todo: 6.x research new values
/*WorldPackets.Item.ReadItemResultFailed packet;
packet.Item = item.GetGUID();
packet.Subcode = ??;
packet.Delay = ??;
SendPacket(packet);*/
Log.outInfo(LogFilter.Network, "STORAGE: Unable to read item");
_player.SendEquipError(msg, item);
}
}
else
_player.SendEquipError(InventoryResult.ItemNotFound);
}
[WorldPacketHandler(ClientOpcodes.SellItem)]
void HandleSellItem(SellItem packet)
{
if (packet.ItemGUID.IsEmpty())
return;
var pl = GetPlayer();
Creature creature = pl.GetNPCIfCanInteractWith(packet.VendorGUID, NPCFlags.Vendor, NPCFlags2.None);
if (creature == null)
{
Log.outDebug(LogFilter.Network, "WORLD: HandleSellItemOpcode - {0} not found or you can not interact with him.", packet.VendorGUID.ToString());
pl.SendSellError(SellResult.CantFindVendor, null, packet.ItemGUID);
return;
}
// remove fake death
if (pl.HasUnitState(UnitState.Died))
pl.RemoveAurasByType(AuraType.FeignDeath);
Item pItem = pl.GetItemByGuid(packet.ItemGUID);
if (pItem != null)
{
// prevent sell not owner item
if (pl.GetGUID() != pItem.GetOwnerGUID())
{
pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
return;
}
// prevent sell non empty bag by drag-and-drop at vendor's item list
if (pItem.IsNotEmptyBag())
{
pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
return;
}
// prevent sell currently looted item
if (pl.GetLootGUID() == pItem.GetGUID())
{
pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
return;
}
// prevent selling item for sellprice when the item is still refundable
// this probably happens when right clicking a refundable item, the client sends both
// CMSG_SELL_ITEM and CMSG_REFUND_ITEM (unverified)
if (pItem.HasItemFlag(ItemFieldFlags.Refundable))
return; // Therefore, no feedback to client
// special case at auto sell (sell all)
if (packet.Amount == 0)
packet.Amount = pItem.GetCount();
else
{
// prevent sell more items that exist in stack (possible only not from client)
if (packet.Amount > pItem.GetCount())
{
pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
return;
}
}
ItemTemplate pProto = pItem.GetTemplate();
if (pProto != null)
{
if (pProto.GetSellPrice() > 0)
{
ulong money = pProto.GetSellPrice() * packet.Amount;
if (!_player.ModifyMoney((long)money)) // ensure player doesn't exceed gold limit
{
_player.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
return;
}
_player.UpdateCriteria(CriteriaTypes.MoneyFromVendors, money);
if (packet.Amount < pItem.GetCount()) // need split items
{
Item pNewItem = pItem.CloneItem(packet.Amount, pl);
if (pNewItem == null)
{
Log.outError(LogFilter.Network, "WORLD: HandleSellItemOpcode - could not create clone of item {0}; count = {1}", pItem.GetEntry(), packet.Amount);
pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
return;
}
pItem.SetCount(pItem.GetCount() - packet.Amount);
pl.ItemRemovedQuestCheck(pItem.GetEntry(), packet.Amount);
if (pl.IsInWorld)
pItem.SendUpdateToPlayer(pl);
pItem.SetState(ItemUpdateState.Changed, pl);
pl.AddItemToBuyBackSlot(pNewItem);
if (pl.IsInWorld)
pNewItem.SendUpdateToPlayer(pl);
}
else
{
pl.ItemRemovedQuestCheck(pItem.GetEntry(), pItem.GetCount());
pl.RemoveItem(pItem.GetBagSlot(), pItem.GetSlot(), true);
Item.RemoveItemFromUpdateQueueOf(pItem, pl);
pl.AddItemToBuyBackSlot(pItem);
}
}
else
pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
return;
}
}
pl.SendSellError(SellResult.CantSellItem, creature, packet.ItemGUID);
return;
}
[WorldPacketHandler(ClientOpcodes.BuyBackItem)]
void HandleBuybackItem(BuyBackItem packet)
{
Creature creature = _player.GetNPCIfCanInteractWith(packet.VendorGUID, NPCFlags.Vendor, NPCFlags2.None);
if (creature == null)
{
Log.outDebug(LogFilter.Network, "WORLD: HandleBuybackItem - {0} not found or you can not interact with him.", packet.VendorGUID.ToString());
_player.SendSellError(SellResult.CantFindVendor, null, ObjectGuid.Empty);
return;
}
// remove fake death
if (_player.HasUnitState(UnitState.Died))
_player.RemoveAurasByType(AuraType.FeignDeath);
Item pItem = _player.GetItemFromBuyBackSlot(packet.Slot);
if (pItem != null)
{
uint price = _player.m_activePlayerData.BuybackPrice[(int)(packet.Slot - InventorySlots.BuyBackStart)];
if (!_player.HasEnoughMoney(price))
{
_player.SendBuyError(BuyResult.NotEnoughtMoney, creature, pItem.GetEntry());
return;
}
List<ItemPosCount> dest = new List<ItemPosCount>();
InventoryResult msg = _player.CanStoreItem(ItemConst.NullBag, ItemConst.NullSlot, dest, pItem, false);
if (msg == InventoryResult.Ok)
{
_player.ModifyMoney(-price);
_player.RemoveItemFromBuyBackSlot(packet.Slot, false);
_player.MoveItemToInventory(dest, pItem, true);
}
else
_player.SendEquipError(msg, pItem);
return;
}
else
_player.SendBuyError(BuyResult.CantFindItem, creature, 0);
}
[WorldPacketHandler(ClientOpcodes.BuyItem)]
void HandleBuyItem(BuyItem packet)
{
// client expects count starting at 1, and we send vendorslot+1 to client already
if (packet.Muid > 0)
--packet.Muid;
else
return; // cheating
switch (packet.ItemType)
{
case ItemVendorType.Item:
Item bagItem = GetPlayer().GetItemByGuid(packet.ContainerGUID);
byte bag = ItemConst.NullBag;
if (bagItem != null && bagItem.IsBag())
bag = bagItem.GetSlot();
else if (packet.ContainerGUID == GetPlayer().GetGUID()) // The client sends the player guid when trying to store an item in the default backpack
bag = InventorySlots.Bag0;
GetPlayer().BuyItemFromVendorSlot(packet.VendorGUID, packet.Muid, packet.Item.ItemID, (byte)packet.Quantity, bag, (byte)packet.Slot);
break;
case ItemVendorType.Currency:
GetPlayer().BuyCurrencyFromVendorSlot(packet.VendorGUID, packet.Muid, packet.Item.ItemID, (byte)packet.Quantity);
break;
default:
Log.outDebug(LogFilter.Network, "WORLD: received wrong itemType {0} in HandleBuyItem", packet.ItemType);
break;
}
}
[WorldPacketHandler(ClientOpcodes.AutoStoreBagItem)]
void HandleAutoStoreBagItem(AutoStoreBagItem packet)
{
if (!packet.Inv.Items.Empty())
{
Log.outError(LogFilter.Network, "HandleAutoStoreBagItemOpcode - Invalid itemCount ({0})", packet.Inv.Items.Count);
return;
}
Item item = GetPlayer().GetItemByPos(packet.ContainerSlotA, packet.SlotA);
if (!item)
return;
if (!GetPlayer().IsValidPos(packet.ContainerSlotB, ItemConst.NullSlot, false)) // can be autostore pos
{
GetPlayer().SendEquipError(InventoryResult.WrongSlot);
return;
}
ushort src = item.GetPos();
InventoryResult msg;
// check unequip potability for equipped items and bank bags
if (Player.IsEquipmentPos(src) || Player.IsBagPos(src))
{
msg = GetPlayer().CanUnequipItem(src, !Player.IsBagPos(src));
if (msg != InventoryResult.Ok)
{
GetPlayer().SendEquipError(msg, item);
return;
}
}
List<ItemPosCount> dest = new List<ItemPosCount>();
msg = GetPlayer().CanStoreItem(packet.ContainerSlotB, ItemConst.NullSlot, dest, item, false);
if (msg != InventoryResult.Ok)
{
GetPlayer().SendEquipError(msg, item);
return;
}
// no-op: placed in same slot
if (dest.Count == 1 && dest[0].pos == src)
{
// just remove grey item state
GetPlayer().SendEquipError(InventoryResult.InternalBagError, item);
return;
}
GetPlayer().RemoveItem(packet.ContainerSlotA, packet.SlotA, true);
GetPlayer().StoreItem(dest, item, true);
}
public void SendEnchantmentLog(ObjectGuid owner, ObjectGuid caster, ObjectGuid itemGuid, uint itemId, uint enchantId, uint slot)
{
EnchantmentLog packet = new EnchantmentLog();
packet.Caster = caster;
packet.Owner = owner;
packet.ItemGUID = itemGuid;
packet.ItemID = itemId;
packet.Enchantment = enchantId;
packet.EnchantSlot = slot;
GetPlayer().SendMessageToSet(packet, true);
}
public void SendItemEnchantTimeUpdate(ObjectGuid Playerguid, ObjectGuid Itemguid, uint slot, uint Duration)
{
ItemEnchantTimeUpdate data = new ItemEnchantTimeUpdate();
data.ItemGuid = Itemguid;
data.DurationLeft = Duration;
data.Slot = slot;
data.OwnerGuid = Playerguid;
SendPacket(data);
}
[WorldPacketHandler(ClientOpcodes.WrapItem)]
void HandleWrapItem(WrapItem packet)
{
if (packet.Inv.Items.Count != 2)
{
Log.outError(LogFilter.Network, "HandleWrapItem - Invalid itemCount ({0})", packet.Inv.Items.Count);
return;
}
// Gift
byte giftContainerSlot = packet.Inv.Items[0].ContainerSlot;
byte giftSlot = packet.Inv.Items[0].Slot;
// Item
byte itemContainerSlot = packet.Inv.Items[1].ContainerSlot;
byte itemSlot = packet.Inv.Items[1].Slot;
Item gift = GetPlayer().GetItemByPos(giftContainerSlot, giftSlot);
if (!gift)
{
GetPlayer().SendEquipError(InventoryResult.ItemNotFound, gift);
return;
}
if (!gift.GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsWrapper)) // cheating: non-wrapper wrapper
{
GetPlayer().SendEquipError(InventoryResult.ItemNotFound, gift);
return;
}
Item item = GetPlayer().GetItemByPos(itemContainerSlot, itemSlot);
if (!item)
{
GetPlayer().SendEquipError(InventoryResult.ItemNotFound, item);
return;
}
if (item == gift) // not possable with pacjket from real client
{
GetPlayer().SendEquipError(InventoryResult.CantWrapWrapped, item);
return;
}
if (item.IsEquipped())
{
GetPlayer().SendEquipError(InventoryResult.CantWrapEquipped, item);
return;
}
if (!item.GetGiftCreator().IsEmpty()) // HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_WRAPPED);
{
GetPlayer().SendEquipError(InventoryResult.CantWrapWrapped, item);
return;
}
if (item.IsBag())
{
GetPlayer().SendEquipError(InventoryResult.CantWrapBags, item);
return;
}
if (item.IsSoulBound())
{
GetPlayer().SendEquipError(InventoryResult.CantWrapBound, item);
return;
}
if (item.GetMaxStackCount() != 1)
{
GetPlayer().SendEquipError(InventoryResult.CantWrapStackable, item);
return;
}
// maybe not correct check (it is better than nothing)
if (item.GetTemplate().GetMaxCount() > 0)
{
GetPlayer().SendEquipError(InventoryResult.CantWrapUnique, item);
return;
}
SQLTransaction trans = new SQLTransaction();
PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.INS_CHAR_GIFT);
stmt.AddValue(0, item.GetOwnerGUID().GetCounter());
stmt.AddValue(1, item.GetGUID().GetCounter());
stmt.AddValue(2, item.GetEntry());
stmt.AddValue(3, (uint)item.m_itemData.DynamicFlags);
trans.Append(stmt);
item.SetEntry(gift.GetEntry());
switch (item.GetEntry())
{
case 5042:
item.SetEntry(5043);
break;
case 5048:
item.SetEntry(5044);
break;
case 17303:
item.SetEntry(17302);
break;
case 17304:
item.SetEntry(17305);
break;
case 17307:
item.SetEntry(17308);
break;
case 21830:
item.SetEntry(21831);
break;
}
item.SetGiftCreator(GetPlayer().GetGUID());
item.SetItemFlags(ItemFieldFlags.Wrapped);
item.SetState(ItemUpdateState.Changed, GetPlayer());
if (item.GetState() == ItemUpdateState.New) // save new item, to have alway for `character_gifts` record in `item_instance`
{
// after save it will be impossible to remove the item from the queue
Item.RemoveItemFromUpdateQueueOf(item, GetPlayer());
item.SaveToDB(trans); // item gave inventory record unchanged and can be save standalone
}
DB.Characters.CommitTransaction(trans);
uint count = 1;
GetPlayer().DestroyItemCount(gift, ref count, true);
}
[WorldPacketHandler(ClientOpcodes.SocketGems)]
void HandleSocketGems(SocketGems socketGems)
{
if (socketGems.ItemGuid.IsEmpty())
return;
//cheat . tried to socket same gem multiple times
if ((!socketGems.GemItem[0].IsEmpty() && (socketGems.GemItem[0] == socketGems.GemItem[1] || socketGems.GemItem[0] == socketGems.GemItem[2])) ||
(!socketGems.GemItem[1].IsEmpty() && (socketGems.GemItem[1] == socketGems.GemItem[2])))
return;
Item itemTarget = GetPlayer().GetItemByGuid(socketGems.ItemGuid);
if (!itemTarget) //missing item to socket
return;
ItemTemplate itemProto = itemTarget.GetTemplate();
if (itemProto == null)
return;
//this slot is excepted when applying / removing meta gem bonus
byte slot = itemTarget.IsEquipped() ? itemTarget.GetSlot() : ItemConst.NullSlot;
Item[] gems = new Item[ItemConst.MaxGemSockets];
ItemDynamicFieldGems[] gemData = new ItemDynamicFieldGems[ItemConst.MaxGemSockets];
GemPropertiesRecord[] gemProperties = new GemPropertiesRecord[ItemConst.MaxGemSockets];
SocketedGem[] oldGemData = new SocketedGem[ItemConst.MaxGemSockets];
for (int i = 0; i < ItemConst.MaxGemSockets; ++i)
{
Item gem = _player.GetItemByGuid(socketGems.GemItem[i]);
if (gem)
{
gems[i] = gem;
gemData[i].ItemId = gem.GetEntry();
gemData[i].Context = (byte)gem.m_itemData.Context;
for (int b = 0; b < ((List<uint>)gem.m_itemData.BonusListIDs).Count && b < 16; ++b)
gemData[i].BonusListIDs[b] = (ushort)((List<uint>)gem.m_itemData.BonusListIDs)[b];
gemProperties[i] = CliDB.GemPropertiesStorage.LookupByKey(gem.GetTemplate().GetGemProperties());
}
oldGemData[i] = itemTarget.GetGem((ushort)i);
}
// Find first prismatic socket
uint firstPrismatic = 0;
while (firstPrismatic < ItemConst.MaxGemSockets && itemTarget.GetSocketColor(firstPrismatic) != 0)
++firstPrismatic;
for (uint i = 0; i < ItemConst.MaxGemSockets; ++i) //check for hack maybe
{
if (gemProperties[i] == null)
continue;
// tried to put gem in socket where no socket exists (take care about prismatic sockets)
if (itemTarget.GetSocketColor(i) == 0)
{
// no prismatic socket
if (itemTarget.GetEnchantmentId(EnchantmentSlot.Prismatic) == 0)
return;
if (i != firstPrismatic)
return;
}
// Gem must match socket color
if (ItemConst.SocketColorToGemTypeMask[(int)itemTarget.GetSocketColor(i)] != gemProperties[i].Type)
{
// unless its red, blue, yellow or prismatic
if (!ItemConst.SocketColorToGemTypeMask[(int)itemTarget.GetSocketColor(i)].HasAnyFlag(SocketColor.Prismatic) || !gemProperties[i].Type.HasAnyFlag(SocketColor.Prismatic))
return;
}
}
// check unique-equipped conditions
for (int i = 0; i < ItemConst.MaxGemSockets; ++i)
{
if (!gems[i])
continue;
// continue check for case when attempt add 2 similar unique equipped gems in one item.
ItemTemplate iGemProto = gems[i].GetTemplate();
// unique item (for new and already placed bit removed enchantments
if (iGemProto.GetFlags().HasAnyFlag(ItemFlags.UniqueEquippable))
{
for (int j = 0; j < ItemConst.MaxGemSockets; ++j)
{
if (i == j) // skip self
continue;
if (gems[j])
{
if (iGemProto.GetId() == gems[j].GetEntry())
{
GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget);
return;
}
}
else if (oldGemData[j] != null)
{
if (iGemProto.GetId() == oldGemData[j].ItemId)
{
GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget);
return;
}
}
}
}
// unique limit type item
int limit_newcount = 0;
if (iGemProto.GetItemLimitCategory() != 0)
{
ItemLimitCategoryRecord limitEntry = CliDB.ItemLimitCategoryStorage.LookupByKey(iGemProto.GetItemLimitCategory());
if (limitEntry != null)
{
// NOTE: limitEntry.mode is not checked because if item has limit then it is applied in equip case
for (int j = 0; j < ItemConst.MaxGemSockets; ++j)
{
if (gems[j])
{
// new gem
if (iGemProto.GetItemLimitCategory() == gems[j].GetTemplate().GetItemLimitCategory())
++limit_newcount;
}
else if (oldGemData[j] != null)
{
// existing gem
ItemTemplate jProto = Global.ObjectMgr.GetItemTemplate(oldGemData[j].ItemId);
if (jProto != null)
if (iGemProto.GetItemLimitCategory() == jProto.GetItemLimitCategory())
++limit_newcount;
}
}
if (limit_newcount > 0 && limit_newcount > _player.GetItemLimitCategoryQuantity(limitEntry))
{
GetPlayer().SendEquipError(InventoryResult.ItemUniqueEquippableSocketed, itemTarget);
return;
}
}
}
// for equipped item check all equipment for duplicate equipped gems
if (itemTarget.IsEquipped())
{
InventoryResult res = GetPlayer().CanEquipUniqueItem(gems[i], slot, (uint)Math.Max(limit_newcount, 0));
if (res != 0)
{
GetPlayer().SendEquipError(res, itemTarget);
return;
}
}
}
bool SocketBonusActivated = itemTarget.GemsFitSockets(); //save state of socketbonus
GetPlayer().ToggleMetaGemsActive(slot, false); //turn off all metagems (except for the target item)
//if a meta gem is being equipped, all information has to be written to the item before testing if the conditions for the gem are met
//remove ALL mods - gem can change item level
if (itemTarget.IsEquipped())
_player._ApplyItemMods(itemTarget, itemTarget.GetSlot(), false);
for (ushort i = 0; i < ItemConst.MaxGemSockets; ++i)
{
if (gems[i])
{
uint gemScalingLevel = _player.GetLevel();
uint fixedLevel = gems[i].GetModifier(ItemModifier.TimewalkerLevel);
if (fixedLevel != 0)
gemScalingLevel = fixedLevel;
itemTarget.SetGem(i, gemData[i], gemScalingLevel);
if (gemProperties[i] != null && gemProperties[i].EnchantId != 0)
itemTarget.SetEnchantment(EnchantmentSlot.Sock1 + i, gemProperties[i].EnchantId, 0, 0, GetPlayer().GetGUID());
uint gemCount = 1;
GetPlayer().DestroyItemCount(gems[i], ref gemCount, true);
}
}
if (itemTarget.IsEquipped())
_player._ApplyItemMods(itemTarget, itemTarget.GetSlot(), true);
Item childItem = _player.GetChildItemByGuid(itemTarget.GetChildItem());
if (childItem)
{
if (childItem.IsEquipped())
_player._ApplyItemMods(childItem, childItem.GetSlot(), false);
childItem.CopyArtifactDataFromParent(itemTarget);
if (childItem.IsEquipped())
_player._ApplyItemMods(childItem, childItem.GetSlot(), true);
}
bool SocketBonusToBeActivated = itemTarget.GemsFitSockets();//current socketbonus state
if (SocketBonusActivated ^ SocketBonusToBeActivated) //if there was a change...
{
GetPlayer().ApplyEnchantment(itemTarget, EnchantmentSlot.Bonus, false);
itemTarget.SetEnchantment(EnchantmentSlot.Bonus, SocketBonusToBeActivated ? itemTarget.GetTemplate().GetSocketBonus() : 0, 0, 0, GetPlayer().GetGUID());
GetPlayer().ApplyEnchantment(itemTarget, EnchantmentSlot.Bonus, true);
//it is not displayed, client has an inbuilt system to determine if the bonus is activated
}
GetPlayer().ToggleMetaGemsActive(slot, true); //turn on all metagems (except for target item)
GetPlayer().RemoveTradeableItem(itemTarget);
itemTarget.ClearSoulboundTradeable(GetPlayer()); // clear tradeable flag
itemTarget.SendUpdateSockets();
}
[WorldPacketHandler(ClientOpcodes.CancelTempEnchantment)]
void HandleCancelTempEnchantment(CancelTempEnchantment packet)
{
// apply only to equipped item
if (!Player.IsEquipmentPos(InventorySlots.Bag0, (byte)packet.Slot))
return;
Item item = GetPlayer().GetItemByPos(InventorySlots.Bag0, (byte)packet.Slot);
if (!item)
return;
if (item.GetEnchantmentId(EnchantmentSlot.Temp) == 0)
return;
GetPlayer().ApplyEnchantment(item, EnchantmentSlot.Temp, false);
item.ClearEnchantment(EnchantmentSlot.Temp);
}
[WorldPacketHandler(ClientOpcodes.GetItemPurchaseData)]
void HandleGetItemPurchaseData(GetItemPurchaseData packet)
{
Item item = GetPlayer().GetItemByGuid(packet.ItemGUID);
if (!item)
{
Log.outDebug(LogFilter.Network, "HandleGetItemPurchaseData: Item {0} not found!", packet.ItemGUID.ToString());
return;
}
GetPlayer().SendRefundInfo(item);
}
[WorldPacketHandler(ClientOpcodes.ItemPurchaseRefund)]
void HandleItemRefund(ItemPurchaseRefund packet)
{
Item item = GetPlayer().GetItemByGuid(packet.ItemGUID);
if (!item)
{
Log.outDebug(LogFilter.Network, "WorldSession.HandleItemRefund: Item {0} not found!", packet.ItemGUID.ToString());
return;
}
// Don't try to refund item currently being disenchanted
if (GetPlayer().GetLootGUID() == packet.ItemGUID)
return;
GetPlayer().RefundItem(item);
}
bool CanUseBank(ObjectGuid bankerGUID = default)
{
// bankerGUID parameter is optional, set to 0 by default.
if (bankerGUID.IsEmpty())
bankerGUID = m_currentBankerGUID;
bool isUsingBankCommand = (bankerGUID == GetPlayer().GetGUID() && bankerGUID == m_currentBankerGUID);
if (!isUsingBankCommand)
{
Creature creature = GetPlayer().GetNPCIfCanInteractWith(bankerGUID, NPCFlags.Banker, NPCFlags2.None);
if (!creature)
return false;
}
return true;
}
[WorldPacketHandler(ClientOpcodes.UseCritterItem)]
void HandleUseCritterItem(UseCritterItem useCritterItem)
{
Item item = GetPlayer().GetItemByGuid(useCritterItem.ItemGuid);
if (!item)
return;
if (item.GetBonus().EffectCount < 2)
return;
uint spellToLearn = (uint)item.GetEffect(1).SpellID;
var entry = Global.SpellMgr.GetBattlePetSpecies(spellToLearn);
if (entry != null)
{
GetBattlePetMgr().AddPet(entry.Id, entry.CreatureID, BattlePetMgr.RollPetBreed(entry.Id), BattlePetMgr.GetDefaultPetQuality(entry.Id));
_player.UpdateCriteria(CriteriaTypes.OwnBattlePetCount);
}
GetPlayer().DestroyItem(item.GetBagSlot(), item.GetSlot(), true);
}
[WorldPacketHandler(ClientOpcodes.SortBags)]
void HandleSortBags(SortBags sortBags)
{
// TODO: Implement sorting
// Placeholder to prevent completely locking out bags clientside
SendPacket(new SortBagsResult());
}
[WorldPacketHandler(ClientOpcodes.SortBankBags)]
void HandleSortBankBags(SortBankBags sortBankBags)
{
// TODO: Implement sorting
// Placeholder to prevent completely locking out bags clientside
SendPacket(new SortBagsResult());
}
[WorldPacketHandler(ClientOpcodes.SortReagentBankBags)]
void HandleSortReagentBankBags(SortReagentBankBags sortReagentBankBags)
{
// TODO: Implement sorting
// Placeholder to prevent completely locking out bags clientside
SendPacket(new SortBagsResult());
}
[WorldPacketHandler(ClientOpcodes.RemoveNewItem)]
void HandleRemoveNewItem(RemoveNewItem removeNewItem)
{
Item item = _player.GetItemByGuid(removeNewItem.ItemGuid);
if (!item)
{
Log.outDebug(LogFilter.Network, $"WorldSession.HandleRemoveNewItem: Item ({removeNewItem.ItemGuid}) not found for {GetPlayerInfo()}!");
return;
}
if (item.HasItemFlag(ItemFieldFlags.NewItem))
{
item.RemoveItemFlag(ItemFieldFlags.NewItem);
item.SetState(ItemUpdateState.Changed, _player);
}
}
}
}