diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 465fa88db9..4453cd1a11 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -52,7 +52,7 @@ typedef std::list VendorItemCounts; // max different by z coordinate for creature aggro reaction #define CREATURE_Z_ATTACK_RANGE 3 -#define MAX_VENDOR_ITEMS 150 // Limitation in 4.x.x item count in SMSG_LIST_INVENTORY +#define MAX_VENDOR_ITEMS 150 // Limitation in 4.x.x item count in SMSG_VENDOR_INVENTORY //used for handling non-repeatable random texts typedef std::vector CreatureTextRepeatIds; diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index 73d77c8465..d2e3e0be43 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -23,6 +23,7 @@ #include "DBCStores.h" #include "Item.h" #include "Log.h" +#include "NPCPackets.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "Player.h" @@ -604,7 +605,7 @@ void WorldSession::HandleListInventoryOpcode(WorldPacket& recvData) void WorldSession::SendListInventory(ObjectGuid vendorGuid) { - TC_LOG_DEBUG("network", "WORLD: Sent SMSG_LIST_INVENTORY"); + TC_LOG_DEBUG("network", "WORLD: Sent SMSG_VENDOR_INVENTORY"); Creature* vendor = GetPlayer()->GetNPCIfCanInteractWith(vendorGuid, UNIT_NPC_FLAG_VENDOR); if (!vendor) @@ -625,12 +626,10 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid) VendorItemData const* vendorItems = vendor->GetVendorItems(); uint32 rawItemCount = vendorItems ? vendorItems->GetItemCount() : 0; - //if (rawItemCount > 300), - // rawItemCount = 300; // client cap but uint8 max value is 255 + WorldPackets::NPC::VendorInventory packet; + packet.Vendor = vendor->GetGUID(); - ByteBuffer itemsData(32 * rawItemCount); - std::vector enablers; - enablers.reserve(2 * rawItemCount); + packet.Items.resize(rawItemCount); const float discountMod = _player->GetReputationPriceDiscount(vendor); uint8 count = 0; @@ -640,13 +639,22 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid) if (!vendorItem) continue; + WorldPackets::NPC::VendorItem& item = packet.Items[count]; + + /* + // Todo: implement dbc file and handling + if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(vendorItem->PlayerConditionId)) + if (!ConditionMgr::IsPlayerMeetingCondition(_player, playerCondition)) + item.PlayerConditionFailed = playerCondition->ID; + */ + if (vendorItem->Type == ITEM_VENDOR_TYPE_ITEM) { ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(vendorItem->item); if (!itemTemplate) continue; - uint32 leftInStock = !vendorItem->maxcount ? 0xFFFFFFFF : vendor->GetVendorItemCurrentCount(vendorItem); + int32 leftInStock = !vendorItem->maxcount ? -1 : vendor->GetVendorItemCurrentCount(vendorItem); if (!_player->IsGameMaster()) // ignore conditions if GM on { // Respect allowed class @@ -669,34 +677,22 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid) continue; } - int32 price = vendorItem->IsGoldRequired(itemTemplate) ? uint32(floor(itemTemplate->GetBuyPrice() * discountMod)) : 0; + int32 price = vendorItem->IsGoldRequired(itemTemplate) ? uint32(std::floor(itemTemplate->GetBuyPrice() * discountMod)) : 0; if (int32 priceMod = _player->GetTotalAuraModifier(SPELL_AURA_MOD_VENDOR_ITEMS_PRICES)) price -= CalculatePct(price, priceMod); - itemsData << uint32(slot + 1); // client expects counting to start at 1 - itemsData << uint32(itemTemplate->MaxDurability); - + item.MuID = slot + 1; // client expects counting to start at 1 + item.Durability = itemTemplate->MaxDurability; + item.ExtendedCostID = vendorItem->ExtendedCost; + item.Type = vendorItem->Type; + item.Quantity = leftInStock; + item.StackCount = itemTemplate->GetBuyCount(); + item.Price = price; + item.ItemID = vendorItem->item; + item.ItemDisplayInfoID = itemTemplate->GetDisplayID(); if (vendorItem->ExtendedCost) - { - enablers.push_back(0); - itemsData << uint32(vendorItem->ExtendedCost); - } - else - enablers.push_back(1); - - enablers.push_back(1); // item is unlocked - - itemsData << uint32(vendorItem->item); - itemsData << uint32(vendorItem->Type); // 1 is items, 2 is currency - itemsData << uint32(price); - itemsData << uint32(itemTemplate->GetDisplayID()); - // if (!unk "enabler") data << uint32(something); - itemsData << int32(leftInStock); - itemsData << uint32(itemTemplate->GetBuyCount()); - - if (++count >= MAX_VENDOR_ITEMS) - break; + item.ExtendedCostID = vendorItem->ExtendedCost; } else if (vendorItem->Type == ITEM_VENDOR_TYPE_CURRENCY) { @@ -707,70 +703,23 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid) if (!vendorItem->ExtendedCost) continue; // there's no price defined for currencies, only extendedcost is used - itemsData << uint32(slot + 1); // client expects counting to start at 1 - itemsData << uint32(0); // max durability - - enablers.push_back(0); - itemsData << uint32(vendorItem->ExtendedCost); - - enablers.push_back(1); // item is unlocked - - itemsData << uint32(vendorItem->item); - itemsData << uint32(vendorItem->Type); // 1 is items, 2 is currency - itemsData << uint32(0); // price, only seen currency types that have Extended cost - itemsData << uint32(0); // displayId - // if (!unk "enabler") data << uint32(something); - itemsData << int32(-1); - itemsData << uint32(vendorItem->maxcount); - - if (++count >= MAX_VENDOR_ITEMS) - break; + item.MuID = slot + 1; // client expects counting to start at 1 + item.ExtendedCostID = vendorItem->ExtendedCost; + item.ItemID = vendorItem->item; + item.Type = vendorItem->Type; + item.StackCount = vendorItem->maxcount; } - // else error + else + continue; + + if (++count >= MAX_VENDOR_ITEMS) + break; } - ObjectGuid guid = vendorGuid; + // Resize vector to real size (some items can be skipped due to checks) + packet.Items.resize(count); - WorldPacket data(SMSG_LIST_INVENTORY, 12 + itemsData.size()); - - data.WriteBit(guid[1]); - data.WriteBit(guid[0]); - - data.WriteBits(count, 21); // item count - - data.WriteBit(guid[3]); - data.WriteBit(guid[6]); - data.WriteBit(guid[5]); - data.WriteBit(guid[2]); - data.WriteBit(guid[7]); - - for (std::vector::const_iterator itr = enablers.begin(); itr != enablers.end(); ++itr) - data.WriteBit(*itr); - - data.WriteBit(guid[4]); - - data.FlushBits(); - data.append(itemsData); - - data.WriteByteSeq(guid[5]); - data.WriteByteSeq(guid[4]); - data.WriteByteSeq(guid[1]); - data.WriteByteSeq(guid[0]); - data.WriteByteSeq(guid[6]); - - // It doesn't matter what value is used here (PROBABLY its full vendor size) - // What matters is that if count of items we can see is 0 and this field is 1 - // then client will open the vendor list, otherwise it won't - if (rawItemCount) - data << uint8(rawItemCount); - else - data << uint8(vendor->IsArmorer()); - - data.WriteByteSeq(guid[2]); - data.WriteByteSeq(guid[3]); - data.WriteByteSeq(guid[7]); - - SendPacket(&data); + SendPacket(packet.Write()); } void WorldSession::HandleAutoStoreBagItemOpcode(WorldPacket& recvData) diff --git a/src/server/game/Server/Packets/NPCPackets.cpp b/src/server/game/Server/Packets/NPCPackets.cpp index 2c8dbe4d14..4ccb62a593 100644 --- a/src/server/game/Server/Packets/NPCPackets.cpp +++ b/src/server/game/Server/Packets/NPCPackets.cpp @@ -16,6 +16,7 @@ */ #include "NPCPackets.h" +#include "Optional.h" void WorldPackets::NPC::Hello::Read() { @@ -70,3 +71,64 @@ WorldPacket const* WorldPackets::NPC::TrainerBuySucceeded::Write() return &_worldPacket; } + + +ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::NPC::VendorItem const& item) +{ + data << int32(item.MuID); + data << int32(item.Durability); + + if (item.ExtendedCostID) + data << uint32(*item.ExtendedCostID); + + data << uint32(item.ItemID); + data << uint32(item.Type); + data << uint32(item.Price); + data << uint32(item.ItemDisplayInfoID); + + if (item.PlayerConditionFailed.is_initialized()) + data << int32(*item.PlayerConditionFailed); + + data << int32(item.Quantity); + data << uint32(item.StackCount); + + return data; +} + +WorldPacket const* WorldPackets::NPC::VendorInventory::Write() +{ + _worldPacket.WriteBit(Vendor[1]); + _worldPacket.WriteBit(Vendor[0]); + _worldPacket.WriteBits(Items.size(), 21); + _worldPacket.WriteBit(Vendor[3]); + _worldPacket.WriteBit(Vendor[6]); + _worldPacket.WriteBit(Vendor[5]); + _worldPacket.WriteBit(Vendor[2]); + _worldPacket.WriteBit(Vendor[7]); + + for (VendorItem const& item : Items) + { + _worldPacket.WriteBit(!item.ExtendedCostID.is_initialized()); + _worldPacket.WriteBit(!item.PlayerConditionFailed.is_initialized()); + } + + _worldPacket.WriteBit(Vendor[4]); + _worldPacket.FlushBits(); + + for (VendorItem const& item : Items) + _worldPacket << item; + + _worldPacket.WriteByteSeq(Vendor[5]); + _worldPacket.WriteByteSeq(Vendor[4]); + _worldPacket.WriteByteSeq(Vendor[1]); + _worldPacket.WriteByteSeq(Vendor[0]); + _worldPacket.WriteByteSeq(Vendor[6]); + + _worldPacket << uint8(Reason); + + _worldPacket.WriteByteSeq(Vendor[2]); + _worldPacket.WriteByteSeq(Vendor[3]); + _worldPacket.WriteByteSeq(Vendor[7]); + + return &_worldPacket; +} diff --git a/src/server/game/Server/Packets/NPCPackets.h b/src/server/game/Server/Packets/NPCPackets.h index d4c8913359..3957f2f999 100644 --- a/src/server/game/Server/Packets/NPCPackets.h +++ b/src/server/game/Server/Packets/NPCPackets.h @@ -20,6 +20,7 @@ #include "Packet.h" #include "ObjectGuid.h" +#include "OptionalFwd.h" #include namespace WorldPackets @@ -103,6 +104,32 @@ namespace WorldPackets ObjectGuid TrainerGUID; int32 SpellID = 0; }; + + struct VendorItem + { + int32 MuID = 0; + int32 Type = 0; + int32 ItemID = 0; + int32 ItemDisplayInfoID = 0; + int32 Quantity = -1; + uint32 Price = 0; + int32 Durability = 0; + int32 StackCount = 0; + Optional ExtendedCostID = 0; + Optional PlayerConditionFailed = 0; + }; + + class VendorInventory final : public ServerPacket + { + public: + VendorInventory() : ServerPacket(SMSG_VENDOR_INVENTORY, 600) { } + + WorldPacket const* Write() override; + + uint8 Reason = 0; + std::vector Items; + ObjectGuid Vendor; + }; } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index a271c7e0bb..59c347455f 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -988,7 +988,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_LF_GUILD_MEMBERSHIP_LIST_UPDATED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_LF_GUILD_POST_UPDATED, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_LF_GUILD_RECRUIT_LIST_UPDATED, STATUS_NEVER, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_LIST_INVENTORY, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_VENDOR_INVENTORY, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_LOAD_CUF_PROFILES, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_LOGIN_SETTIMESPEED, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_LOGIN_VERIFY_WORLD, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); diff --git a/src/server/game/Server/Protocol/Opcodes.h b/src/server/game/Server/Protocol/Opcodes.h index 8ddca2be2b..4a4d586fd7 100644 --- a/src/server/game/Server/Protocol/Opcodes.h +++ b/src/server/game/Server/Protocol/Opcodes.h @@ -936,7 +936,7 @@ enum OpcodeServer SMSG_LF_GUILD_MEMBERSHIP_LIST_UPDATED = 0x1CA5, SMSG_LF_GUILD_POST_UPDATED = 0x35B7, SMSG_LF_GUILD_RECRUIT_LIST_UPDATED = 0x1CB2, - SMSG_LIST_INVENTORY = 0x7CB0, + SMSG_VENDOR_INVENTORY = 0x7CB0, SMSG_LOAD_CUF_PROFILES = 0x50B1, SMSG_LOGIN_SETTIMESPEED = 0x4D15, SMSG_LOGIN_VERIFY_WORLD = 0x2005,