/* * Copyright (C) 2012-2020 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 . */ using Framework.Constants; using Framework.Database; using Game.BattleGrounds; using Game.DataStorage; using Game.Entities; using Game.Networking; using Game.Networking.Packets; using System; using System.Collections.Generic; namespace Game { public partial class WorldSession { [WorldPacketHandler(ClientOpcodes.TabardVendorActivate, Processing = PacketProcessing.Inplace)] void HandleTabardVendorActivate(Hello packet) { Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.TabardDesigner, NPCFlags2.None); if (!unit) { Log.outDebug(LogFilter.Network, "WORLD: HandleTabardVendorActivateOpcode - {0} not found or you can not interact with him.", packet.Unit.ToString()); return; } // remove fake death if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); SendTabardVendorActivate(packet.Unit); } public void SendTabardVendorActivate(ObjectGuid guid) { PlayerTabardVendorActivate packet = new(); packet.Vendor = guid; SendPacket(packet); } [WorldPacketHandler(ClientOpcodes.TrainerList, Processing = PacketProcessing.Inplace)] void HandleTrainerList(Hello packet) { Creature npc = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.Trainer, NPCFlags2.None); if (!npc) { Log.outDebug(LogFilter.Network, $"WorldSession.SendTrainerList - {packet.Unit} not found or you can not interact with him."); return; } uint trainerId = Global.ObjectMgr.GetCreatureDefaultTrainer(npc.GetEntry()); if (trainerId != 0) SendTrainerList(npc, trainerId); else Log.outDebug(LogFilter.Network, $"WorldSession.SendTrainerList - Creature id {npc.GetEntry()} has no trainer data."); } public void SendTrainerList(Creature npc, uint trainerId) { // remove fake death if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); Trainer trainer = Global.ObjectMgr.GetTrainer(trainerId); if (trainer == null) { Log.outDebug(LogFilter.Network, $"WORLD: SendTrainerList - trainer spells not found for trainer {npc.GetGUID()} id {trainerId}"); return; } _player.PlayerTalkClass.GetInteractionData().Reset(); _player.PlayerTalkClass.GetInteractionData().SourceGuid = npc.GetGUID(); _player.PlayerTalkClass.GetInteractionData().TrainerId = trainerId; trainer.SendSpells(npc, _player, GetSessionDbLocaleIndex()); } [WorldPacketHandler(ClientOpcodes.TrainerBuySpell, Processing = PacketProcessing.Inplace)] void HandleTrainerBuySpell(TrainerBuySpell packet) { Creature npc = _player.GetNPCIfCanInteractWith(packet.TrainerGUID, NPCFlags.Trainer, NPCFlags2.None); if (npc == null) { Log.outDebug(LogFilter.Network, $"WORLD: HandleTrainerBuySpell - {packet.TrainerGUID} not found or you can not interact with him."); return; } // remove fake death if (_player.HasUnitState(UnitState.Died)) _player.RemoveAurasByType(AuraType.FeignDeath); if (_player.PlayerTalkClass.GetInteractionData().SourceGuid != packet.TrainerGUID) return; if (_player.PlayerTalkClass.GetInteractionData().TrainerId != packet.TrainerID) return; // check present spell in trainer spell list Trainer trainer = Global.ObjectMgr.GetTrainer(packet.TrainerID); if (trainer == null) return; trainer.TeachSpell(npc, _player, packet.SpellID); } void SendTrainerBuyFailed(ObjectGuid trainerGUID, uint spellID, TrainerFailReason trainerFailedReason) { TrainerBuyFailed trainerBuyFailed = new(); trainerBuyFailed.TrainerGUID = trainerGUID; trainerBuyFailed.SpellID = spellID; // should be same as in packet from client trainerBuyFailed.TrainerFailedReason = trainerFailedReason; // 1 == "Not enough money for trainer service." 0 == "Trainer service %d unavailable." SendPacket(trainerBuyFailed); } [WorldPacketHandler(ClientOpcodes.TalkToGossip, Processing = PacketProcessing.Inplace)] void HandleGossipHello(Hello packet) { Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.Gossip, NPCFlags2.None); if (unit == null) { Log.outDebug(LogFilter.Network, "WORLD: HandleGossipHello - {0} not found or you can not interact with him.", packet.Unit.ToString()); return; } // set faction visible if needed var factionTemplateEntry = CliDB.FactionTemplateStorage.LookupByKey(unit.GetFaction()); if (factionTemplateEntry != null) GetPlayer().GetReputationMgr().SetVisible(factionTemplateEntry); GetPlayer().RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Interacting); // Stop the npc if moving uint pause = unit.GetMovementTemplate().GetInteractionPauseTimer(); if (pause != 0) unit.PauseMovement(pause); unit.SetHomePosition(unit.GetPosition()); // If spiritguide, no need for gossip menu, just put player into resurrect queue if (unit.IsSpiritGuide()) { Battleground bg = GetPlayer().GetBattleground(); if (bg) { bg.AddPlayerToResurrectQueue(unit.GetGUID(), GetPlayer().GetGUID()); Global.BattlegroundMgr.SendAreaSpiritHealerQuery(GetPlayer(), bg, unit.GetGUID()); return; } } _player.PlayerTalkClass.ClearMenus(); if (!unit.GetAI().OnGossipHello(_player)) { GetPlayer().PrepareGossipMenu(unit, unit.GetCreatureTemplate().GossipMenuId, true); GetPlayer().SendPreparedGossip(unit); } } [WorldPacketHandler(ClientOpcodes.GossipSelectOption)] void HandleGossipSelectOption(GossipSelectOption packet) { if (GetPlayer().PlayerTalkClass.GetGossipMenu().GetItem(packet.GossipIndex) == null) return; // Prevent cheating on C# scripted menus if (GetPlayer().PlayerTalkClass.GetInteractionData().SourceGuid != packet.GossipUnit) return; Creature unit = null; GameObject go = null; if (packet.GossipUnit.IsCreatureOrVehicle()) { unit = GetPlayer().GetNPCIfCanInteractWith(packet.GossipUnit, NPCFlags.Gossip, NPCFlags2.None); if (unit == null) { Log.outDebug(LogFilter.Network, "WORLD: HandleGossipSelectOption - {0} not found or you can't interact with him.", packet.GossipUnit.ToString()); return; } } else if (packet.GossipUnit.IsGameObject()) { go = GetPlayer().GetGameObjectIfCanInteractWith(packet.GossipUnit); if (go == null) { Log.outDebug(LogFilter.Network, "WORLD: HandleGossipSelectOption - {0} not found or you can't interact with it.", packet.GossipUnit.ToString()); return; } } else { Log.outDebug(LogFilter.Network, "WORLD: HandleGossipSelectOption - unsupported {0}.", packet.GossipUnit.ToString()); return; } // remove fake death if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); if ((unit && unit.GetScriptId() != unit.LastUsedScriptID) || (go != null && go.GetScriptId() != go.LastUsedScriptID)) { Log.outDebug(LogFilter.Network, "WORLD: HandleGossipSelectOption - Script reloaded while in use, ignoring and set new scipt id"); if (unit != null) unit.LastUsedScriptID = unit.GetScriptId(); if (go != null) go.LastUsedScriptID = go.GetScriptId(); GetPlayer().PlayerTalkClass.SendCloseGossip(); return; } if (!string.IsNullOrEmpty(packet.PromotionCode)) { if (unit != null) { if (!unit.GetAI().OnGossipSelectCode(_player, packet.GossipID, packet.GossipIndex, packet.PromotionCode)) GetPlayer().OnGossipSelect(unit, packet.GossipIndex, packet.GossipID); } else { if (!go.GetAI().OnGossipSelectCode(_player, packet.GossipID, packet.GossipIndex, packet.PromotionCode)) _player.OnGossipSelect(go, packet.GossipIndex, packet.GossipID); } } else { if (unit != null) { if (!unit.GetAI().OnGossipSelect(_player, packet.GossipID, packet.GossipIndex)) GetPlayer().OnGossipSelect(unit, packet.GossipIndex, packet.GossipID); } else { if (!go.GetAI().OnGossipSelect(_player, packet.GossipID, packet.GossipIndex)) GetPlayer().OnGossipSelect(go, packet.GossipIndex, packet.GossipID); } } } [WorldPacketHandler(ClientOpcodes.SpiritHealerActivate)] void HandleSpiritHealerActivate(SpiritHealerActivate packet) { Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Healer, NPCFlags.SpiritHealer, NPCFlags2.None); if (!unit) { Log.outDebug(LogFilter.Network, "WORLD: HandleSpiritHealerActivateOpcode - {0} not found or you can not interact with him.", packet.Healer.ToString()); return; } // remove fake death if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); SendSpiritResurrect(); } void SendSpiritResurrect() { GetPlayer().ResurrectPlayer(0.5f, true); GetPlayer().DurabilityLossAll(0.25f, true); // get corpse nearest graveyard WorldSafeLocsEntry corpseGrave = null; WorldLocation corpseLocation = GetPlayer().GetCorpseLocation(); if (GetPlayer().HasCorpse()) { corpseGrave = Global.ObjectMgr.GetClosestGraveYard(corpseLocation, GetPlayer().GetTeam(), GetPlayer()); } // now can spawn bones GetPlayer().SpawnCorpseBones(); // teleport to nearest from corpse graveyard, if different from nearest to player ghost if (corpseGrave != null) { WorldSafeLocsEntry ghostGrave = Global.ObjectMgr.GetClosestGraveYard(GetPlayer(), GetPlayer().GetTeam(), GetPlayer()); if (corpseGrave != ghostGrave) GetPlayer().TeleportTo(corpseGrave.Loc); } } [WorldPacketHandler(ClientOpcodes.BinderActivate, Processing = PacketProcessing.Inplace)] void HandleBinderActivate(Hello packet) { if (!GetPlayer().IsInWorld || !GetPlayer().IsAlive()) return; Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.Unit, NPCFlags.Innkeeper, NPCFlags2.None); if (!unit) { Log.outDebug(LogFilter.Network, "WORLD: HandleBinderActivate - {0} not found or you can not interact with him.", packet.Unit.ToString()); return; } // remove fake death if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); SendBindPoint(unit); } void SendBindPoint(Creature npc) { // prevent set homebind to instances in any case if (GetPlayer().GetMap().Instanceable()) return; uint bindspell = 3286; // send spell for homebinding (3286) npc.CastSpell(GetPlayer(), bindspell, true); GetPlayer().PlayerTalkClass.SendCloseGossip(); } [WorldPacketHandler(ClientOpcodes.RequestStabledPets, Processing = PacketProcessing.Inplace)] void HandleRequestStabledPets(RequestStabledPets packet) { if (!CheckStableMaster(packet.StableMaster)) return; // remove fake death if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); // remove mounts this fix bug where getting pet from stable while mounted deletes pet. if (GetPlayer().IsMounted()) GetPlayer().RemoveAurasByType(AuraType.Mounted); SendStablePet(packet.StableMaster); } public void SendStablePet(ObjectGuid guid) { PetStableList packet = new(); packet.StableMaster = guid; PetStable petStable = GetPlayer().GetPetStable(); if (petStable == null) { SendPacket(packet); return; } for (uint petSlot = 0; petSlot < petStable.ActivePets.Length; ++petSlot) { if (petStable.ActivePets[petSlot] == null) continue; PetStable.PetInfo pet = petStable.ActivePets[petSlot]; PetStableInfo stableEntry; stableEntry.PetSlot = petSlot + (int)PetSaveMode.FirstActiveSlot; stableEntry.PetNumber = pet.PetNumber; stableEntry.CreatureID = pet.CreatureId; stableEntry.DisplayID = pet.DisplayId; stableEntry.ExperienceLevel = pet.Level; stableEntry.PetFlags = PetStableinfo.Active; stableEntry.PetName = pet.Name; packet.Pets.Add(stableEntry); } for (uint petSlot = 0; petSlot < petStable.StabledPets.Length; ++petSlot) { if (petStable.StabledPets[petSlot] == null) continue; PetStable.PetInfo pet = petStable.StabledPets[petSlot]; PetStableInfo stableEntry; stableEntry.PetSlot = petSlot + (int)PetSaveMode.FirstStableSlot; stableEntry.PetNumber = pet.PetNumber; stableEntry.CreatureID = pet.CreatureId; stableEntry.DisplayID = pet.DisplayId; stableEntry.ExperienceLevel = pet.Level; stableEntry.PetFlags = PetStableinfo.Inactive; stableEntry.PetName = pet.Name; packet.Pets.Add(stableEntry); } SendPacket(packet); } void SendPetStableResult(StableResult result) { PetStableResult petStableResult = new(); petStableResult.Result = result; SendPacket(petStableResult); } [WorldPacketHandler(ClientOpcodes.SetPetSlot)] void HandleSetPetSlot(SetPetSlot setPetSlot) { if (!CheckStableMaster(setPetSlot.StableMaster) || setPetSlot.DestSlot >= (byte)PetSaveMode.LastStableSlot) { SendPetStableResult(StableResult.InternalError); return; } GetPlayer().RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Interacting); PetStable petStable = GetPlayer().GetPetStable(); if (petStable == null) { SendPetStableResult(StableResult.InternalError); return; } (PetStable.PetInfo srcPet, PetSaveMode srcPetSlot) = Pet.GetLoadPetInfo(petStable, 0, setPetSlot.PetNumber, null); PetSaveMode dstPetSlot = (PetSaveMode)setPetSlot.DestSlot; PetStable.PetInfo dstPet = Pet.GetLoadPetInfo(petStable, 0, 0, dstPetSlot).Item1; if (srcPet == null || srcPet.Type != PetType.Hunter) { SendPetStableResult(StableResult.InternalError); return; } if (dstPet != null && dstPet.Type != PetType.Hunter) { SendPetStableResult(StableResult.InternalError); return; } PetStable.PetInfo src = null; PetStable.PetInfo dst = null; PetSaveMode? newActivePetIndex = null; if (SharedConst.IsActivePetSlot(srcPetSlot) && SharedConst.IsActivePetSlot(dstPetSlot)) { // active<.active: only swap ActivePets and CurrentPetIndex (do not despawn pets) src = petStable.ActivePets[srcPetSlot - PetSaveMode.FirstActiveSlot]; dst = petStable.ActivePets[dstPetSlot - PetSaveMode.FirstActiveSlot]; if (petStable.GetCurrentActivePetIndex().Value == (uint)srcPetSlot) newActivePetIndex = dstPetSlot; else if (petStable.GetCurrentActivePetIndex().Value == (uint)dstPetSlot) newActivePetIndex = srcPetSlot; } else if (SharedConst.IsStabledPetSlot(srcPetSlot) && SharedConst.IsStabledPetSlot(dstPetSlot)) { // stabled<.stabled: only swap StabledPets src = petStable.StabledPets[srcPetSlot - PetSaveMode.FirstStableSlot]; dst = petStable.StabledPets[dstPetSlot - PetSaveMode.FirstStableSlot]; } else if (SharedConst.IsActivePetSlot(srcPetSlot) && SharedConst.IsStabledPetSlot(dstPetSlot)) { // active<.stabled: swap petStable contents and despawn active pet if it is involved in swap if (petStable.CurrentPetIndex.Value == (uint)srcPetSlot) { Pet oldPet = _player.GetPet(); if (oldPet != null && !oldPet.IsAlive()) { SendPetStableResult(StableResult.InternalError); return; } _player.RemovePet(oldPet, PetSaveMode.NotInSlot); } if (dstPet != null) { CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(dstPet.CreatureId); if (creatureInfo == null || !creatureInfo.IsTameable(_player.CanTameExoticPets())) { SendPetStableResult(StableResult.CantControlExotic); return; } } src = petStable.ActivePets[srcPetSlot - PetSaveMode.FirstActiveSlot]; dst = petStable.StabledPets[dstPetSlot - PetSaveMode.FirstStableSlot]; } else if (SharedConst.IsStabledPetSlot(srcPetSlot) && SharedConst.IsActivePetSlot(dstPetSlot)) { // stabled<.active: swap petStable contents and despawn active pet if it is involved in swap if (petStable.CurrentPetIndex.Value == (uint)dstPetSlot) { Pet oldPet = _player.GetPet(); if (oldPet != null && !oldPet.IsAlive()) { SendPetStableResult(StableResult.InternalError); return; } _player.RemovePet(oldPet, PetSaveMode.NotInSlot); } CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(srcPet.CreatureId); if (creatureInfo == null || !creatureInfo.IsTameable(_player.CanTameExoticPets())) { SendPetStableResult(StableResult.CantControlExotic); return; } src = petStable.StabledPets[srcPetSlot - PetSaveMode.FirstStableSlot]; dst = petStable.ActivePets[dstPetSlot - PetSaveMode.FirstActiveSlot]; } SQLTransaction trans = new(); PreparedStatement stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID); stmt.AddValue(0, (short)dstPetSlot); stmt.AddValue(1, _player.GetGUID().GetCounter()); stmt.AddValue(2, srcPet.PetNumber); trans.Append(stmt); if (dstPet != null) { stmt = DB.Characters.GetPreparedStatement(CharStatements.UPD_CHAR_PET_SLOT_BY_ID); stmt.AddValue(0, (short)srcPetSlot); stmt.AddValue(1, _player.GetGUID().GetCounter()); stmt.AddValue(2, dstPet.PetNumber); trans.Append(stmt); } AddTransactionCallback(DB.Characters.AsyncCommitTransaction(trans)).AfterComplete(success => { var currentPlayerGuid = _player.GetGUID(); if (_player && _player.GetGUID() == currentPlayerGuid) { if (success) { Extensions.Swap(ref src, ref dst); if (newActivePetIndex.HasValue) GetPlayer().GetPetStable().SetCurrentActivePetIndex((uint)newActivePetIndex.Value); SendPetStableResult(StableResult.StableSuccess); } else { SendPetStableResult(StableResult.InternalError); } } }); } [WorldPacketHandler(ClientOpcodes.RepairItem, Processing = PacketProcessing.Inplace)] void HandleRepairItem(RepairItem packet) { Creature unit = GetPlayer().GetNPCIfCanInteractWith(packet.NpcGUID, NPCFlags.Repair, NPCFlags2.None); if (!unit) { Log.outDebug(LogFilter.Network, "WORLD: HandleRepairItemOpcode - {0} not found or you can not interact with him.", packet.NpcGUID.ToString()); return; } // remove fake death if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); // reputation discount float discountMod = GetPlayer().GetReputationPriceDiscount(unit); if (!packet.ItemGUID.IsEmpty()) { Log.outDebug(LogFilter.Network, "ITEM: Repair {0}, at {1}", packet.ItemGUID.ToString(), packet.NpcGUID.ToString()); Item item = GetPlayer().GetItemByGuid(packet.ItemGUID); if (item) GetPlayer().DurabilityRepair(item.GetPos(), true, discountMod); } else { Log.outDebug(LogFilter.Network, "ITEM: Repair all items at {0}", packet.NpcGUID.ToString()); GetPlayer().DurabilityRepairAll(true, discountMod, packet.UseGuildBank); } } [WorldPacketHandler(ClientOpcodes.ListInventory, Processing = PacketProcessing.Inplace)] void HandleListInventory(Hello packet) { if (!GetPlayer().IsAlive()) return; SendListInventory(packet.Unit); } public void SendListInventory(ObjectGuid vendorGuid) { Creature vendor = GetPlayer().GetNPCIfCanInteractWith(vendorGuid, NPCFlags.Vendor, NPCFlags2.None); if (vendor == null) { Log.outDebug(LogFilter.Network, "WORLD: SendListInventory - {0} not found or you can not interact with him.", vendorGuid.ToString()); GetPlayer().SendSellError(SellResult.CantFindVendor, null, ObjectGuid.Empty); return; } // remove fake death if (GetPlayer().HasUnitState(UnitState.Died)) GetPlayer().RemoveAurasByType(AuraType.FeignDeath); // Stop the npc if moving uint pause = vendor.GetMovementTemplate().GetInteractionPauseTimer(); if (pause != 0) vendor.PauseMovement(pause); vendor.SetHomePosition(vendor.GetPosition()); VendorItemData vendorItems = vendor.GetVendorItems(); int rawItemCount = vendorItems != null ? vendorItems.GetItemCount() : 0; VendorInventory packet = new(); packet.Vendor = vendor.GetGUID(); float discountMod = GetPlayer().GetReputationPriceDiscount(vendor); byte count = 0; for (uint slot = 0; slot < rawItemCount; ++slot) { VendorItem vendorItem = vendorItems.GetItem(slot); if (vendorItem == null) continue; VendorItemPkt item = new(); PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(vendorItem.PlayerConditionId); if (playerCondition != null) if (!ConditionManager.IsPlayerMeetingCondition(_player, playerCondition)) item.PlayerConditionFailed = (int)playerCondition.Id; if (vendorItem.Type == ItemVendorType.Item) { ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(vendorItem.item); if (itemTemplate == null) continue; int leftInStock = vendorItem.maxcount == 0 ? -1 : (int)vendor.GetVendorItemCurrentCount(vendorItem); if (!GetPlayer().IsGameMaster()) { if (!Convert.ToBoolean(itemTemplate.GetAllowableClass() & GetPlayer().GetClassMask()) && itemTemplate.GetBonding() == ItemBondingType.OnAcquire) continue; if ((itemTemplate.HasFlag(ItemFlags2.FactionHorde) && GetPlayer().GetTeam() == Team.Alliance) || (itemTemplate.HasFlag(ItemFlags2.FactionAlliance) && GetPlayer().GetTeam() == Team.Horde)) continue; if (leftInStock == 0) continue; } if (!Global.ConditionMgr.IsObjectMeetingVendorItemConditions(vendor.GetEntry(), vendorItem.item, _player, vendor)) { Log.outDebug(LogFilter.Condition, "SendListInventory: conditions not met for creature entry {0} item {1}", vendor.GetEntry(), vendorItem.item); continue; } int price = (int)(vendorItem.IsGoldRequired(itemTemplate) ? Math.Floor(itemTemplate.GetBuyPrice() * discountMod) : 0); int priceMod = GetPlayer().GetTotalAuraModifier(AuraType.ModVendorItemsPrices); if (priceMod != 0) price -= MathFunctions.CalculatePct(price, priceMod); item.MuID = (int)slot + 1; item.Durability = (int)itemTemplate.MaxDurability; item.ExtendedCostID = (int)vendorItem.ExtendedCost; item.Type = (int)vendorItem.Type; item.Quantity = leftInStock; item.StackCount = (int)itemTemplate.GetBuyCount(); item.Price = (ulong)price; item.DoNotFilterOnVendor = vendorItem.IgnoreFiltering; item.Refundable = itemTemplate.HasFlag(ItemFlags.ItemPurchaseRecord) && vendorItem.ExtendedCost != 0 && itemTemplate.GetMaxStackSize() == 1; item.Item.ItemID = vendorItem.item; if (!vendorItem.BonusListIDs.Empty()) { item.Item.ItemBonus = new(); item.Item.ItemBonus.BonusListIDs = vendorItem.BonusListIDs; } packet.Items.Add(item); } else if (vendorItem.Type == ItemVendorType.Currency) { CurrencyTypesRecord currencyTemplate = CliDB.CurrencyTypesStorage.LookupByKey(vendorItem.item); if (currencyTemplate == null) continue; if (vendorItem.ExtendedCost == 0) continue; // there's no price defined for currencies, only extendedcost is used item.MuID = (int)slot + 1; // client expects counting to start at 1 item.ExtendedCostID = (int)vendorItem.ExtendedCost; item.Item.ItemID = vendorItem.item; item.Type = (int)vendorItem.Type; item.StackCount = (int)vendorItem.maxcount; item.DoNotFilterOnVendor = vendorItem.IgnoreFiltering; packet.Items.Add(item); } else continue; if (++count >= SharedConst.MaxVendorItems) break; } packet.Reason = (byte)(count != 0 ? VendorInventoryReason.None : VendorInventoryReason.Empty); SendPacket(packet); } } }