mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-25 13:03:50 +00:00
Implement bank, guild bank, and auction house systems
Add 27 new opcodes, packet builders/parsers, handler methods, inventory extension with 28 bank slots + 7 bank bags, and UI windows for personal bank, guild bank (6 tabs x 98 slots), and auction house (browse/sell/bid). Fix Classic gossip parser to omit boxMoney/boxText fields not present in Vanilla protocol, fix gossip icon labels with text-based NPC type detection, and add Turtle WoW opcode mappings for bank and auction interactions.
This commit is contained in:
parent
0d4a9c38f7
commit
381d896348
14 changed files with 1839 additions and 15 deletions
|
|
@ -296,6 +296,11 @@ void GameHandler::update(float deltaTime) {
|
|||
clearTarget();
|
||||
}
|
||||
|
||||
if (auctionSearchDelayTimer_ > 0.0f) {
|
||||
auctionSearchDelayTimer_ -= deltaTime;
|
||||
if (auctionSearchDelayTimer_ < 0.0f) auctionSearchDelayTimer_ = 0.0f;
|
||||
}
|
||||
|
||||
if (pendingMoneyDeltaTimer_ > 0.0f) {
|
||||
pendingMoneyDeltaTimer_ -= deltaTime;
|
||||
if (pendingMoneyDeltaTimer_ <= 0.0f) {
|
||||
|
|
@ -1526,6 +1531,36 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
handleQueryNextMailTime(packet);
|
||||
break;
|
||||
|
||||
// ---- Bank ----
|
||||
case Opcode::SMSG_SHOW_BANK:
|
||||
handleShowBank(packet);
|
||||
break;
|
||||
case Opcode::SMSG_BUY_BANK_SLOT_RESULT:
|
||||
handleBuyBankSlotResult(packet);
|
||||
break;
|
||||
|
||||
// ---- Guild Bank ----
|
||||
case Opcode::SMSG_GUILD_BANK_LIST:
|
||||
handleGuildBankList(packet);
|
||||
break;
|
||||
|
||||
// ---- Auction House ----
|
||||
case Opcode::MSG_AUCTION_HELLO:
|
||||
handleAuctionHello(packet);
|
||||
break;
|
||||
case Opcode::SMSG_AUCTION_LIST_RESULT:
|
||||
handleAuctionListResult(packet);
|
||||
break;
|
||||
case Opcode::SMSG_AUCTION_OWNER_LIST_RESULT:
|
||||
handleAuctionOwnerListResult(packet);
|
||||
break;
|
||||
case Opcode::SMSG_AUCTION_BIDDER_LIST_RESULT:
|
||||
handleAuctionBidderListResult(packet);
|
||||
break;
|
||||
case Opcode::SMSG_AUCTION_COMMAND_RESULT:
|
||||
handleAuctionCommandResult(packet);
|
||||
break;
|
||||
|
||||
default:
|
||||
// In pre-world states we need full visibility (char create/login handshakes).
|
||||
// In-world we keep de-duplication to avoid heavy log I/O in busy areas.
|
||||
|
|
@ -5776,6 +5811,34 @@ bool GameHandler::applyInventoryFields(const std::map<uint16_t, uint32_t>& field
|
|||
slotsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Bank slots: 28 slots × 2 fields = 56 fields starting at PLAYER_FIELD_BANK_SLOT_1
|
||||
int bankBase = static_cast<int>(fieldIndex(UF::PLAYER_FIELD_BANK_SLOT_1));
|
||||
if (bankBase != 0xFFFF && key >= static_cast<uint16_t>(bankBase) &&
|
||||
key <= static_cast<uint16_t>(bankBase) + (game::Inventory::BANK_SLOTS * 2 - 1)) {
|
||||
int slotIndex = (key - bankBase) / 2;
|
||||
bool isLow = ((key - bankBase) % 2 == 0);
|
||||
if (slotIndex < static_cast<int>(bankSlotGuids_.size())) {
|
||||
uint64_t& guid = bankSlotGuids_[slotIndex];
|
||||
if (isLow) guid = (guid & 0xFFFFFFFF00000000ULL) | val;
|
||||
else guid = (guid & 0x00000000FFFFFFFFULL) | (uint64_t(val) << 32);
|
||||
slotsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Bank bag slots: 7 slots × 2 fields = 14 fields starting at PLAYER_FIELD_BANKBAG_SLOT_1
|
||||
int bankBagBase = static_cast<int>(fieldIndex(UF::PLAYER_FIELD_BANKBAG_SLOT_1));
|
||||
if (bankBagBase != 0xFFFF && key >= static_cast<uint16_t>(bankBagBase) &&
|
||||
key <= static_cast<uint16_t>(bankBagBase) + (game::Inventory::BANK_BAG_SLOTS * 2 - 1)) {
|
||||
int slotIndex = (key - bankBagBase) / 2;
|
||||
bool isLow = ((key - bankBagBase) % 2 == 0);
|
||||
if (slotIndex < static_cast<int>(bankBagSlotGuids_.size())) {
|
||||
uint64_t& guid = bankBagSlotGuids_[slotIndex];
|
||||
if (isLow) guid = (guid & 0xFFFFFFFF00000000ULL) | val;
|
||||
else guid = (guid & 0x00000000FFFFFFFFULL) | (uint64_t(val) << 32);
|
||||
slotsChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return slotsChanged;
|
||||
|
|
@ -5953,6 +6016,105 @@ void GameHandler::rebuildOnlineInventory() {
|
|||
}
|
||||
}
|
||||
|
||||
// Bank slots (28 main slots)
|
||||
for (int i = 0; i < 28; i++) {
|
||||
uint64_t guid = bankSlotGuids_[i];
|
||||
if (guid == 0) { inventory.clearBankSlot(i); continue; }
|
||||
|
||||
auto itemIt = onlineItems_.find(guid);
|
||||
if (itemIt == onlineItems_.end()) continue;
|
||||
|
||||
ItemDef def;
|
||||
def.itemId = itemIt->second.entry;
|
||||
def.stackCount = itemIt->second.stackCount;
|
||||
def.maxStack = 1;
|
||||
|
||||
auto infoIt = itemInfoCache_.find(itemIt->second.entry);
|
||||
if (infoIt != itemInfoCache_.end()) {
|
||||
def.name = infoIt->second.name;
|
||||
def.quality = static_cast<ItemQuality>(infoIt->second.quality);
|
||||
def.inventoryType = infoIt->second.inventoryType;
|
||||
def.maxStack = std::max(1, infoIt->second.maxStack);
|
||||
def.displayInfoId = infoIt->second.displayInfoId;
|
||||
def.subclassName = infoIt->second.subclassName;
|
||||
def.armor = infoIt->second.armor;
|
||||
def.stamina = infoIt->second.stamina;
|
||||
def.strength = infoIt->second.strength;
|
||||
def.agility = infoIt->second.agility;
|
||||
def.intellect = infoIt->second.intellect;
|
||||
def.spirit = infoIt->second.spirit;
|
||||
def.sellPrice = infoIt->second.sellPrice;
|
||||
def.bagSlots = infoIt->second.containerSlots;
|
||||
} else {
|
||||
def.name = "Item " + std::to_string(def.itemId);
|
||||
queryItemInfo(def.itemId, guid);
|
||||
}
|
||||
|
||||
inventory.setBankSlot(i, def);
|
||||
}
|
||||
|
||||
// Bank bag contents (7 bank bag slots)
|
||||
for (int bagIdx = 0; bagIdx < 7; bagIdx++) {
|
||||
uint64_t bagGuid = bankBagSlotGuids_[bagIdx];
|
||||
if (bagGuid == 0) { inventory.setBankBagSize(bagIdx, 0); continue; }
|
||||
|
||||
int numSlots = 0;
|
||||
auto contIt = containerContents_.find(bagGuid);
|
||||
if (contIt != containerContents_.end()) {
|
||||
numSlots = static_cast<int>(contIt->second.numSlots);
|
||||
}
|
||||
if (numSlots <= 0) {
|
||||
auto bagItemIt = onlineItems_.find(bagGuid);
|
||||
if (bagItemIt != onlineItems_.end()) {
|
||||
auto bagInfoIt = itemInfoCache_.find(bagItemIt->second.entry);
|
||||
if (bagInfoIt != itemInfoCache_.end()) {
|
||||
numSlots = bagInfoIt->second.containerSlots;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numSlots <= 0) continue;
|
||||
|
||||
inventory.setBankBagSize(bagIdx, numSlots);
|
||||
|
||||
if (contIt == containerContents_.end()) continue;
|
||||
const auto& container = contIt->second;
|
||||
for (int s = 0; s < numSlots && s < 36; s++) {
|
||||
uint64_t itemGuid = container.slotGuids[s];
|
||||
if (itemGuid == 0) continue;
|
||||
|
||||
auto itemIt = onlineItems_.find(itemGuid);
|
||||
if (itemIt == onlineItems_.end()) continue;
|
||||
|
||||
ItemDef def;
|
||||
def.itemId = itemIt->second.entry;
|
||||
def.stackCount = itemIt->second.stackCount;
|
||||
def.maxStack = 1;
|
||||
|
||||
auto infoIt = itemInfoCache_.find(itemIt->second.entry);
|
||||
if (infoIt != itemInfoCache_.end()) {
|
||||
def.name = infoIt->second.name;
|
||||
def.quality = static_cast<ItemQuality>(infoIt->second.quality);
|
||||
def.inventoryType = infoIt->second.inventoryType;
|
||||
def.maxStack = std::max(1, infoIt->second.maxStack);
|
||||
def.displayInfoId = infoIt->second.displayInfoId;
|
||||
def.subclassName = infoIt->second.subclassName;
|
||||
def.armor = infoIt->second.armor;
|
||||
def.stamina = infoIt->second.stamina;
|
||||
def.strength = infoIt->second.strength;
|
||||
def.agility = infoIt->second.agility;
|
||||
def.intellect = infoIt->second.intellect;
|
||||
def.spirit = infoIt->second.spirit;
|
||||
def.sellPrice = infoIt->second.sellPrice;
|
||||
def.bagSlots = infoIt->second.containerSlots;
|
||||
} else {
|
||||
def.name = "Item " + std::to_string(def.itemId);
|
||||
queryItemInfo(def.itemId, itemGuid);
|
||||
}
|
||||
|
||||
inventory.setBankBagSlot(bagIdx, s, def);
|
||||
}
|
||||
}
|
||||
|
||||
// Only mark equipment dirty if equipped item displayInfoIds actually changed
|
||||
std::array<uint32_t, 19> currentEquipDisplayIds{};
|
||||
for (int i = 0; i < 19; i++) {
|
||||
|
|
@ -7450,17 +7612,46 @@ void GameHandler::interactWithGameObject(uint64_t guid) {
|
|||
|
||||
void GameHandler::selectGossipOption(uint32_t optionId) {
|
||||
if (state != WorldState::IN_WORLD || !socket || !gossipWindowOpen) return;
|
||||
LOG_INFO("selectGossipOption: optionId=", optionId,
|
||||
" npcGuid=0x", std::hex, currentGossip.npcGuid, std::dec,
|
||||
" menuId=", currentGossip.menuId,
|
||||
" numOptions=", currentGossip.options.size());
|
||||
auto packet = GossipSelectOptionPacket::build(currentGossip.npcGuid, currentGossip.menuId, optionId);
|
||||
socket->send(packet);
|
||||
|
||||
// If this is an innkeeper "make this inn your home" option, send binder activate.
|
||||
for (const auto& opt : currentGossip.options) {
|
||||
if (opt.id != optionId) continue;
|
||||
LOG_INFO(" matched option: id=", opt.id, " icon=", (int)opt.icon, " text='", opt.text, "'");
|
||||
|
||||
// Icon-based NPC interaction fallbacks
|
||||
// Some servers need the specific activate packet in addition to gossip select
|
||||
if (opt.icon == 6) {
|
||||
// GOSSIP_ICON_MONEY_BAG = banker
|
||||
auto pkt = BankerActivatePacket::build(currentGossip.npcGuid);
|
||||
socket->send(pkt);
|
||||
LOG_INFO("Sent CMSG_BANKER_ACTIVATE for npc=0x", std::hex, currentGossip.npcGuid, std::dec);
|
||||
}
|
||||
|
||||
// Text-based NPC type detection for servers using placeholder strings
|
||||
std::string text = opt.text;
|
||||
std::transform(text.begin(), text.end(), text.begin(),
|
||||
std::string textLower = text;
|
||||
std::transform(textLower.begin(), textLower.end(), textLower.begin(),
|
||||
[](unsigned char c){ return static_cast<char>(std::tolower(c)); });
|
||||
if (text.find("make this inn your home") != std::string::npos ||
|
||||
text.find("set your home") != std::string::npos) {
|
||||
|
||||
if (text == "GOSSIP_OPTION_AUCTIONEER" || textLower.find("auction") != std::string::npos) {
|
||||
auto pkt = AuctionHelloPacket::build(currentGossip.npcGuid);
|
||||
socket->send(pkt);
|
||||
LOG_INFO("Sent MSG_AUCTION_HELLO for npc=0x", std::hex, currentGossip.npcGuid, std::dec);
|
||||
}
|
||||
|
||||
if (text == "GOSSIP_OPTION_BANKER" || textLower.find("deposit box") != std::string::npos) {
|
||||
auto pkt = BankerActivatePacket::build(currentGossip.npcGuid);
|
||||
socket->send(pkt);
|
||||
LOG_INFO("Sent CMSG_BANKER_ACTIVATE for npc=0x", std::hex, currentGossip.npcGuid, std::dec);
|
||||
}
|
||||
|
||||
if (textLower.find("make this inn your home") != std::string::npos ||
|
||||
textLower.find("set your home") != std::string::npos) {
|
||||
auto bindPkt = BinderActivatePacket::build(currentGossip.npcGuid);
|
||||
socket->send(bindPkt);
|
||||
LOG_INFO("Sent CMSG_BINDER_ACTIVATE for npc=0x", std::hex, currentGossip.npcGuid, std::dec);
|
||||
|
|
@ -9949,5 +10140,305 @@ glm::vec3 GameHandler::getComposedWorldPosition() {
|
|||
return glm::vec3(movementInfo.x, movementInfo.y, movementInfo.z);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Bank System
|
||||
// ============================================================
|
||||
|
||||
void GameHandler::openBank(uint64_t guid) {
|
||||
if (!isConnected()) return;
|
||||
auto pkt = BankerActivatePacket::build(guid);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::closeBank() {
|
||||
bankOpen_ = false;
|
||||
bankerGuid_ = 0;
|
||||
}
|
||||
|
||||
void GameHandler::buyBankSlot() {
|
||||
if (!isConnected() || !bankOpen_) return;
|
||||
auto pkt = BuyBankSlotPacket::build(bankerGuid_);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::depositItem(uint8_t srcBag, uint8_t srcSlot) {
|
||||
if (!isConnected() || !bankOpen_) return;
|
||||
auto pkt = AutoBankItemPacket::build(srcBag, srcSlot);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::withdrawItem(uint8_t srcBag, uint8_t srcSlot) {
|
||||
if (!isConnected() || !bankOpen_) return;
|
||||
auto pkt = AutoStoreBankItemPacket::build(srcBag, srcSlot);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::handleShowBank(network::Packet& packet) {
|
||||
if (packet.getSize() - packet.getReadPos() < 8) return;
|
||||
bankerGuid_ = packet.readUInt64();
|
||||
bankOpen_ = true;
|
||||
gossipWindowOpen = false; // Close gossip when bank opens
|
||||
// Bank items are already tracked via update fields (bank slot GUIDs)
|
||||
// Trigger rebuild to populate bank slots in inventory
|
||||
rebuildOnlineInventory();
|
||||
LOG_INFO("SMSG_SHOW_BANK: banker=0x", std::hex, bankerGuid_, std::dec);
|
||||
}
|
||||
|
||||
void GameHandler::handleBuyBankSlotResult(network::Packet& packet) {
|
||||
if (packet.getSize() - packet.getReadPos() < 4) return;
|
||||
uint32_t result = packet.readUInt32();
|
||||
if (result == 0) {
|
||||
addSystemChatMessage("Bank slot purchased.");
|
||||
inventory.setPurchasedBankBagSlots(inventory.getPurchasedBankBagSlots() + 1);
|
||||
} else {
|
||||
addSystemChatMessage("Cannot purchase bank slot.");
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Guild Bank System
|
||||
// ============================================================
|
||||
|
||||
void GameHandler::openGuildBank(uint64_t guid) {
|
||||
if (!isConnected()) return;
|
||||
auto pkt = GuildBankerActivatePacket::build(guid);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::closeGuildBank() {
|
||||
guildBankOpen_ = false;
|
||||
guildBankerGuid_ = 0;
|
||||
}
|
||||
|
||||
void GameHandler::queryGuildBankTab(uint8_t tabId) {
|
||||
if (!isConnected() || !guildBankOpen_) return;
|
||||
guildBankActiveTab_ = tabId;
|
||||
auto pkt = GuildBankQueryTabPacket::build(guildBankerGuid_, tabId, true);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::buyGuildBankTab() {
|
||||
if (!isConnected() || !guildBankOpen_) return;
|
||||
uint8_t nextTab = static_cast<uint8_t>(guildBankData_.tabs.size());
|
||||
auto pkt = GuildBankBuyTabPacket::build(guildBankerGuid_, nextTab);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::depositGuildBankMoney(uint32_t amount) {
|
||||
if (!isConnected() || !guildBankOpen_) return;
|
||||
auto pkt = GuildBankDepositMoneyPacket::build(guildBankerGuid_, amount);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::withdrawGuildBankMoney(uint32_t amount) {
|
||||
if (!isConnected() || !guildBankOpen_) return;
|
||||
auto pkt = GuildBankWithdrawMoneyPacket::build(guildBankerGuid_, amount);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::guildBankWithdrawItem(uint8_t tabId, uint8_t bankSlot, uint8_t destBag, uint8_t destSlot) {
|
||||
if (!isConnected() || !guildBankOpen_) return;
|
||||
auto pkt = GuildBankSwapItemsPacket::buildBankToInventory(guildBankerGuid_, tabId, bankSlot, destBag, destSlot);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::guildBankDepositItem(uint8_t tabId, uint8_t bankSlot, uint8_t srcBag, uint8_t srcSlot) {
|
||||
if (!isConnected() || !guildBankOpen_) return;
|
||||
auto pkt = GuildBankSwapItemsPacket::buildInventoryToBank(guildBankerGuid_, tabId, bankSlot, srcBag, srcSlot);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::handleGuildBankList(network::Packet& packet) {
|
||||
GuildBankData data;
|
||||
if (!GuildBankListParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse SMSG_GUILD_BANK_LIST");
|
||||
return;
|
||||
}
|
||||
guildBankData_ = data;
|
||||
guildBankOpen_ = true;
|
||||
guildBankActiveTab_ = data.tabId;
|
||||
|
||||
// Ensure item info for all guild bank items
|
||||
for (const auto& item : data.tabItems) {
|
||||
if (item.itemEntry != 0) ensureItemInfo(item.itemEntry);
|
||||
}
|
||||
|
||||
LOG_INFO("SMSG_GUILD_BANK_LIST: tab=", (int)data.tabId,
|
||||
" items=", data.tabItems.size(),
|
||||
" tabs=", data.tabs.size(),
|
||||
" money=", data.money);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Auction House System
|
||||
// ============================================================
|
||||
|
||||
void GameHandler::openAuctionHouse(uint64_t guid) {
|
||||
if (!isConnected()) return;
|
||||
auto pkt = AuctionHelloPacket::build(guid);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::closeAuctionHouse() {
|
||||
auctionOpen_ = false;
|
||||
auctioneerGuid_ = 0;
|
||||
}
|
||||
|
||||
void GameHandler::auctionSearch(const std::string& name, uint8_t levelMin, uint8_t levelMax,
|
||||
uint32_t quality, uint32_t itemClass, uint32_t itemSubClass,
|
||||
uint32_t invTypeMask, uint8_t usableOnly, uint32_t offset)
|
||||
{
|
||||
if (!isConnected() || !auctionOpen_) return;
|
||||
if (auctionSearchDelayTimer_ > 0.0f) {
|
||||
addSystemChatMessage("Please wait before searching again.");
|
||||
return;
|
||||
}
|
||||
pendingAuctionTarget_ = AuctionResultTarget::BROWSE;
|
||||
auto pkt = AuctionListItemsPacket::build(auctioneerGuid_, offset, name,
|
||||
levelMin, levelMax, invTypeMask,
|
||||
itemClass, itemSubClass, quality, usableOnly, 0);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::auctionSellItem(uint64_t itemGuid, uint32_t stackCount,
|
||||
uint32_t bid, uint32_t buyout, uint32_t duration)
|
||||
{
|
||||
if (!isConnected() || !auctionOpen_) return;
|
||||
auto pkt = AuctionSellItemPacket::build(auctioneerGuid_, itemGuid, stackCount, bid, buyout, duration);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::auctionPlaceBid(uint32_t auctionId, uint32_t amount) {
|
||||
if (!isConnected() || !auctionOpen_) return;
|
||||
auto pkt = AuctionPlaceBidPacket::build(auctioneerGuid_, auctionId, amount);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::auctionBuyout(uint32_t auctionId, uint32_t buyoutPrice) {
|
||||
auctionPlaceBid(auctionId, buyoutPrice);
|
||||
}
|
||||
|
||||
void GameHandler::auctionCancelItem(uint32_t auctionId) {
|
||||
if (!isConnected() || !auctionOpen_) return;
|
||||
auto pkt = AuctionRemoveItemPacket::build(auctioneerGuid_, auctionId);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::auctionListOwnerItems(uint32_t offset) {
|
||||
if (!isConnected() || !auctionOpen_) return;
|
||||
pendingAuctionTarget_ = AuctionResultTarget::OWNER;
|
||||
auto pkt = AuctionListOwnerItemsPacket::build(auctioneerGuid_, offset);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::auctionListBidderItems(uint32_t offset) {
|
||||
if (!isConnected() || !auctionOpen_) return;
|
||||
pendingAuctionTarget_ = AuctionResultTarget::BIDDER;
|
||||
auto pkt = AuctionListBidderItemsPacket::build(auctioneerGuid_, offset);
|
||||
socket->send(pkt);
|
||||
}
|
||||
|
||||
void GameHandler::handleAuctionHello(network::Packet& packet) {
|
||||
size_t pktSize = packet.getSize();
|
||||
size_t readPos = packet.getReadPos();
|
||||
LOG_INFO("handleAuctionHello: packetSize=", pktSize, " readPos=", readPos);
|
||||
// Hex dump first 20 bytes for debugging
|
||||
const auto& rawData = packet.getData();
|
||||
std::string hex;
|
||||
size_t dumpLen = std::min<size_t>(rawData.size(), 20);
|
||||
for (size_t i = 0; i < dumpLen; ++i) {
|
||||
char b[4]; snprintf(b, sizeof(b), "%02x ", rawData[i]);
|
||||
hex += b;
|
||||
}
|
||||
LOG_INFO(" hex dump: ", hex);
|
||||
AuctionHelloData data;
|
||||
if (!AuctionHelloParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse MSG_AUCTION_HELLO response, size=", pktSize, " readPos=", readPos);
|
||||
return;
|
||||
}
|
||||
auctioneerGuid_ = data.auctioneerGuid;
|
||||
auctionHouseId_ = data.auctionHouseId;
|
||||
auctionOpen_ = true;
|
||||
gossipWindowOpen = false; // Close gossip when auction house opens
|
||||
auctionActiveTab_ = 0;
|
||||
auctionBrowseResults_ = AuctionListResult{};
|
||||
auctionOwnerResults_ = AuctionListResult{};
|
||||
auctionBidderResults_ = AuctionListResult{};
|
||||
LOG_INFO("MSG_AUCTION_HELLO: auctioneer=0x", std::hex, data.auctioneerGuid, std::dec,
|
||||
" house=", data.auctionHouseId, " enabled=", (int)data.enabled);
|
||||
}
|
||||
|
||||
void GameHandler::handleAuctionListResult(network::Packet& packet) {
|
||||
AuctionListResult result;
|
||||
if (!AuctionListResultParser::parse(packet, result)) {
|
||||
LOG_WARNING("Failed to parse SMSG_AUCTION_LIST_RESULT");
|
||||
return;
|
||||
}
|
||||
|
||||
auctionBrowseResults_ = result;
|
||||
auctionSearchDelayTimer_ = result.searchDelay / 1000.0f;
|
||||
|
||||
// Ensure item info for all auction items
|
||||
for (const auto& entry : result.auctions) {
|
||||
if (entry.itemEntry != 0) ensureItemInfo(entry.itemEntry);
|
||||
}
|
||||
|
||||
LOG_INFO("SMSG_AUCTION_LIST_RESULT: ", result.auctions.size(), " items, total=", result.totalCount);
|
||||
}
|
||||
|
||||
void GameHandler::handleAuctionOwnerListResult(network::Packet& packet) {
|
||||
AuctionListResult result;
|
||||
if (!AuctionListResultParser::parse(packet, result)) {
|
||||
LOG_WARNING("Failed to parse SMSG_AUCTION_OWNER_LIST_RESULT");
|
||||
return;
|
||||
}
|
||||
auctionOwnerResults_ = result;
|
||||
for (const auto& entry : result.auctions) {
|
||||
if (entry.itemEntry != 0) ensureItemInfo(entry.itemEntry);
|
||||
}
|
||||
LOG_INFO("SMSG_AUCTION_OWNER_LIST_RESULT: ", result.auctions.size(), " items");
|
||||
}
|
||||
|
||||
void GameHandler::handleAuctionBidderListResult(network::Packet& packet) {
|
||||
AuctionListResult result;
|
||||
if (!AuctionListResultParser::parse(packet, result)) {
|
||||
LOG_WARNING("Failed to parse SMSG_AUCTION_BIDDER_LIST_RESULT");
|
||||
return;
|
||||
}
|
||||
auctionBidderResults_ = result;
|
||||
for (const auto& entry : result.auctions) {
|
||||
if (entry.itemEntry != 0) ensureItemInfo(entry.itemEntry);
|
||||
}
|
||||
LOG_INFO("SMSG_AUCTION_BIDDER_LIST_RESULT: ", result.auctions.size(), " items");
|
||||
}
|
||||
|
||||
void GameHandler::handleAuctionCommandResult(network::Packet& packet) {
|
||||
AuctionCommandResult result;
|
||||
if (!AuctionCommandResultParser::parse(packet, result)) {
|
||||
LOG_WARNING("Failed to parse SMSG_AUCTION_COMMAND_RESULT");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* actions[] = {"Create", "Cancel", "Bid", "Buyout"};
|
||||
const char* actionName = (result.action < 4) ? actions[result.action] : "Unknown";
|
||||
|
||||
if (result.errorCode == 0) {
|
||||
std::string msg = std::string("Auction ") + actionName + " successful.";
|
||||
addSystemChatMessage(msg);
|
||||
// Refresh appropriate list
|
||||
if (result.action == 0) auctionListOwnerItems();
|
||||
else if (result.action == 1) auctionListOwnerItems();
|
||||
} else {
|
||||
const char* errors[] = {"OK", "Inventory", "Not enough money", "Item not found",
|
||||
"Higher bid", "Increment", "Not enough items",
|
||||
"DB error", "Restricted account"};
|
||||
const char* errName = (result.errorCode < 9) ? errors[result.errorCode] : "Unknown";
|
||||
std::string msg = std::string("Auction ") + actionName + " failed: " + errName;
|
||||
addSystemChatMessage(msg);
|
||||
}
|
||||
LOG_INFO("SMSG_AUCTION_COMMAND_RESULT: action=", actionName,
|
||||
" error=", result.errorCode);
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -68,6 +68,46 @@ bool Inventory::setBagSlot(int bagIndex, int slotIndex, const ItemDef& item) {
|
|||
return true;
|
||||
}
|
||||
|
||||
const ItemSlot& Inventory::getBankSlot(int index) const {
|
||||
if (index < 0 || index >= BANK_SLOTS) return EMPTY_SLOT;
|
||||
return bankSlots_[index];
|
||||
}
|
||||
|
||||
bool Inventory::setBankSlot(int index, const ItemDef& item) {
|
||||
if (index < 0 || index >= BANK_SLOTS) return false;
|
||||
bankSlots_[index].item = item;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Inventory::clearBankSlot(int index) {
|
||||
if (index < 0 || index >= BANK_SLOTS) return false;
|
||||
bankSlots_[index].item = ItemDef{};
|
||||
return true;
|
||||
}
|
||||
|
||||
const ItemSlot& Inventory::getBankBagSlot(int bagIndex, int slotIndex) const {
|
||||
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return EMPTY_SLOT;
|
||||
if (slotIndex < 0 || slotIndex >= bankBags_[bagIndex].size) return EMPTY_SLOT;
|
||||
return bankBags_[bagIndex].slots[slotIndex];
|
||||
}
|
||||
|
||||
bool Inventory::setBankBagSlot(int bagIndex, int slotIndex, const ItemDef& item) {
|
||||
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return false;
|
||||
if (slotIndex < 0 || slotIndex >= bankBags_[bagIndex].size) return false;
|
||||
bankBags_[bagIndex].slots[slotIndex].item = item;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Inventory::getBankBagSize(int bagIndex) const {
|
||||
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return 0;
|
||||
return bankBags_[bagIndex].size;
|
||||
}
|
||||
|
||||
void Inventory::setBankBagSize(int bagIndex, int size) {
|
||||
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return;
|
||||
bankBags_[bagIndex].size = std::min(size, MAX_BAG_SIZE);
|
||||
}
|
||||
|
||||
int Inventory::findFreeBackpackSlot() const {
|
||||
for (int i = 0; i < BACKPACK_SLOTS; i++) {
|
||||
if (backpack[i].empty()) return i;
|
||||
|
|
|
|||
|
|
@ -307,6 +307,36 @@ static const OpcodeNameEntry kOpcodeNames[] = {
|
|||
{"CMSG_MAIL_MARK_AS_READ", LogicalOpcode::CMSG_MAIL_MARK_AS_READ},
|
||||
{"SMSG_RECEIVED_MAIL", LogicalOpcode::SMSG_RECEIVED_MAIL},
|
||||
{"MSG_QUERY_NEXT_MAIL_TIME", LogicalOpcode::MSG_QUERY_NEXT_MAIL_TIME},
|
||||
// Bank
|
||||
{"CMSG_BANKER_ACTIVATE", LogicalOpcode::CMSG_BANKER_ACTIVATE},
|
||||
{"SMSG_SHOW_BANK", LogicalOpcode::SMSG_SHOW_BANK},
|
||||
{"CMSG_BUY_BANK_SLOT", LogicalOpcode::CMSG_BUY_BANK_SLOT},
|
||||
{"SMSG_BUY_BANK_SLOT_RESULT", LogicalOpcode::SMSG_BUY_BANK_SLOT_RESULT},
|
||||
{"CMSG_AUTOBANK_ITEM", LogicalOpcode::CMSG_AUTOBANK_ITEM},
|
||||
{"CMSG_AUTOSTORE_BANK_ITEM", LogicalOpcode::CMSG_AUTOSTORE_BANK_ITEM},
|
||||
// Guild Bank
|
||||
{"CMSG_GUILD_BANKER_ACTIVATE", LogicalOpcode::CMSG_GUILD_BANKER_ACTIVATE},
|
||||
{"CMSG_GUILD_BANK_QUERY_TAB", LogicalOpcode::CMSG_GUILD_BANK_QUERY_TAB},
|
||||
{"SMSG_GUILD_BANK_LIST", LogicalOpcode::SMSG_GUILD_BANK_LIST},
|
||||
{"CMSG_GUILD_BANK_SWAP_ITEMS", LogicalOpcode::CMSG_GUILD_BANK_SWAP_ITEMS},
|
||||
{"CMSG_GUILD_BANK_BUY_TAB", LogicalOpcode::CMSG_GUILD_BANK_BUY_TAB},
|
||||
{"CMSG_GUILD_BANK_UPDATE_TAB", LogicalOpcode::CMSG_GUILD_BANK_UPDATE_TAB},
|
||||
{"CMSG_GUILD_BANK_DEPOSIT_MONEY", LogicalOpcode::CMSG_GUILD_BANK_DEPOSIT_MONEY},
|
||||
{"CMSG_GUILD_BANK_WITHDRAW_MONEY", LogicalOpcode::CMSG_GUILD_BANK_WITHDRAW_MONEY},
|
||||
// Auction House
|
||||
{"MSG_AUCTION_HELLO", LogicalOpcode::MSG_AUCTION_HELLO},
|
||||
{"CMSG_AUCTION_SELL_ITEM", LogicalOpcode::CMSG_AUCTION_SELL_ITEM},
|
||||
{"CMSG_AUCTION_REMOVE_ITEM", LogicalOpcode::CMSG_AUCTION_REMOVE_ITEM},
|
||||
{"CMSG_AUCTION_LIST_ITEMS", LogicalOpcode::CMSG_AUCTION_LIST_ITEMS},
|
||||
{"CMSG_AUCTION_LIST_OWNER_ITEMS", LogicalOpcode::CMSG_AUCTION_LIST_OWNER_ITEMS},
|
||||
{"CMSG_AUCTION_PLACE_BID", LogicalOpcode::CMSG_AUCTION_PLACE_BID},
|
||||
{"SMSG_AUCTION_COMMAND_RESULT", LogicalOpcode::SMSG_AUCTION_COMMAND_RESULT},
|
||||
{"SMSG_AUCTION_LIST_RESULT", LogicalOpcode::SMSG_AUCTION_LIST_RESULT},
|
||||
{"SMSG_AUCTION_OWNER_LIST_RESULT", LogicalOpcode::SMSG_AUCTION_OWNER_LIST_RESULT},
|
||||
{"SMSG_AUCTION_BIDDER_LIST_RESULT", LogicalOpcode::SMSG_AUCTION_BIDDER_LIST_RESULT},
|
||||
{"SMSG_AUCTION_OWNER_NOTIFICATION", LogicalOpcode::SMSG_AUCTION_OWNER_NOTIFICATION},
|
||||
{"SMSG_AUCTION_BIDDER_NOTIFICATION", LogicalOpcode::SMSG_AUCTION_BIDDER_NOTIFICATION},
|
||||
{"CMSG_AUCTION_LIST_BIDDER_ITEMS", LogicalOpcode::CMSG_AUCTION_LIST_BIDDER_ITEMS},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
|
@ -615,6 +645,36 @@ void OpcodeTable::loadWotlkDefaults() {
|
|||
{LogicalOpcode::CMSG_MAIL_MARK_AS_READ, 0x247},
|
||||
{LogicalOpcode::SMSG_RECEIVED_MAIL, 0x285},
|
||||
{LogicalOpcode::MSG_QUERY_NEXT_MAIL_TIME, 0x284},
|
||||
// Bank
|
||||
{LogicalOpcode::CMSG_BANKER_ACTIVATE, 0x1B7},
|
||||
{LogicalOpcode::SMSG_SHOW_BANK, 0x1B8},
|
||||
{LogicalOpcode::CMSG_BUY_BANK_SLOT, 0x1B9},
|
||||
{LogicalOpcode::SMSG_BUY_BANK_SLOT_RESULT, 0x1BA},
|
||||
{LogicalOpcode::CMSG_AUTOBANK_ITEM, 0x283},
|
||||
{LogicalOpcode::CMSG_AUTOSTORE_BANK_ITEM, 0x282},
|
||||
// Guild Bank
|
||||
{LogicalOpcode::CMSG_GUILD_BANKER_ACTIVATE, 0x3E6},
|
||||
{LogicalOpcode::CMSG_GUILD_BANK_QUERY_TAB, 0x3E7},
|
||||
{LogicalOpcode::SMSG_GUILD_BANK_LIST, 0x3E8},
|
||||
{LogicalOpcode::CMSG_GUILD_BANK_SWAP_ITEMS, 0x3E9},
|
||||
{LogicalOpcode::CMSG_GUILD_BANK_BUY_TAB, 0x3EA},
|
||||
{LogicalOpcode::CMSG_GUILD_BANK_UPDATE_TAB, 0x3EB},
|
||||
{LogicalOpcode::CMSG_GUILD_BANK_DEPOSIT_MONEY, 0x3EC},
|
||||
{LogicalOpcode::CMSG_GUILD_BANK_WITHDRAW_MONEY, 0x3ED},
|
||||
// Auction House
|
||||
{LogicalOpcode::MSG_AUCTION_HELLO, 0x255},
|
||||
{LogicalOpcode::CMSG_AUCTION_SELL_ITEM, 0x256},
|
||||
{LogicalOpcode::CMSG_AUCTION_REMOVE_ITEM, 0x257},
|
||||
{LogicalOpcode::CMSG_AUCTION_LIST_ITEMS, 0x258},
|
||||
{LogicalOpcode::CMSG_AUCTION_LIST_OWNER_ITEMS, 0x259},
|
||||
{LogicalOpcode::CMSG_AUCTION_PLACE_BID, 0x25A},
|
||||
{LogicalOpcode::SMSG_AUCTION_COMMAND_RESULT, 0x25B},
|
||||
{LogicalOpcode::SMSG_AUCTION_LIST_RESULT, 0x25C},
|
||||
{LogicalOpcode::SMSG_AUCTION_OWNER_LIST_RESULT, 0x25D},
|
||||
{LogicalOpcode::SMSG_AUCTION_BIDDER_LIST_RESULT, 0x265},
|
||||
{LogicalOpcode::SMSG_AUCTION_OWNER_NOTIFICATION, 0x25E},
|
||||
{LogicalOpcode::SMSG_AUCTION_BIDDER_NOTIFICATION, 0x260},
|
||||
{LogicalOpcode::CMSG_AUCTION_LIST_BIDDER_ITEMS, 0x264},
|
||||
};
|
||||
|
||||
logicalToWire_.clear();
|
||||
|
|
|
|||
|
|
@ -673,9 +673,10 @@ bool ClassicPacketParsers::parseGossipMessage(network::Packet& packet, GossipMes
|
|||
opt.id = packet.readUInt32();
|
||||
opt.icon = packet.readUInt8();
|
||||
opt.isCoded = (packet.readUInt8() != 0);
|
||||
opt.boxMoney = packet.readUInt32();
|
||||
// Classic/Vanilla: NO boxMoney or boxText fields (commented out in mangoszero)
|
||||
opt.boxMoney = 0;
|
||||
opt.text = packet.readString();
|
||||
opt.boxText = packet.readString();
|
||||
opt.boxText = "";
|
||||
data.options.push_back(opt);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ static const UFNameEntry kUFNames[] = {
|
|||
{"PLAYER_QUEST_LOG_START", UF::PLAYER_QUEST_LOG_START},
|
||||
{"PLAYER_FIELD_INV_SLOT_HEAD", UF::PLAYER_FIELD_INV_SLOT_HEAD},
|
||||
{"PLAYER_FIELD_PACK_SLOT_1", UF::PLAYER_FIELD_PACK_SLOT_1},
|
||||
{"PLAYER_FIELD_BANK_SLOT_1", UF::PLAYER_FIELD_BANK_SLOT_1},
|
||||
{"PLAYER_FIELD_BANKBAG_SLOT_1", UF::PLAYER_FIELD_BANKBAG_SLOT_1},
|
||||
{"PLAYER_SKILL_INFO_START", UF::PLAYER_SKILL_INFO_START},
|
||||
{"PLAYER_EXPLORED_ZONES_START", UF::PLAYER_EXPLORED_ZONES_START},
|
||||
{"GAMEOBJECT_DISPLAYID", UF::GAMEOBJECT_DISPLAYID},
|
||||
|
|
@ -84,6 +86,8 @@ void UpdateFieldTable::loadWotlkDefaults() {
|
|||
{UF::PLAYER_QUEST_LOG_START, 158},
|
||||
{UF::PLAYER_FIELD_INV_SLOT_HEAD, 324},
|
||||
{UF::PLAYER_FIELD_PACK_SLOT_1, 370},
|
||||
{UF::PLAYER_FIELD_BANK_SLOT_1, 402},
|
||||
{UF::PLAYER_FIELD_BANKBAG_SLOT_1, 458},
|
||||
{UF::PLAYER_SKILL_INFO_START, 636},
|
||||
{UF::PLAYER_EXPLORED_ZONES_START, 1041},
|
||||
{UF::GAMEOBJECT_DISPLAYID, 8},
|
||||
|
|
|
|||
|
|
@ -3480,5 +3480,316 @@ bool PacketParsers::parseMailList(network::Packet& packet, std::vector<MailMessa
|
|||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Bank System
|
||||
// ============================================================
|
||||
|
||||
network::Packet BankerActivatePacket::build(uint64_t guid) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_BANKER_ACTIVATE));
|
||||
p.writeUInt64(guid);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet BuyBankSlotPacket::build(uint64_t guid) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_BUY_BANK_SLOT));
|
||||
p.writeUInt64(guid);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet AutoBankItemPacket::build(uint8_t srcBag, uint8_t srcSlot) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_AUTOBANK_ITEM));
|
||||
p.writeUInt8(srcBag);
|
||||
p.writeUInt8(srcSlot);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet AutoStoreBankItemPacket::build(uint8_t srcBag, uint8_t srcSlot) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_AUTOSTORE_BANK_ITEM));
|
||||
p.writeUInt8(srcBag);
|
||||
p.writeUInt8(srcSlot);
|
||||
return p;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Guild Bank System
|
||||
// ============================================================
|
||||
|
||||
network::Packet GuildBankerActivatePacket::build(uint64_t guid) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANKER_ACTIVATE));
|
||||
p.writeUInt64(guid);
|
||||
p.writeUInt8(0); // full slots update
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet GuildBankQueryTabPacket::build(uint64_t guid, uint8_t tabId, bool fullUpdate) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_QUERY_TAB));
|
||||
p.writeUInt64(guid);
|
||||
p.writeUInt8(tabId);
|
||||
p.writeUInt8(fullUpdate ? 1 : 0);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet GuildBankBuyTabPacket::build(uint64_t guid, uint8_t tabId) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_BUY_TAB));
|
||||
p.writeUInt64(guid);
|
||||
p.writeUInt8(tabId);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet GuildBankDepositMoneyPacket::build(uint64_t guid, uint32_t amount) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_DEPOSIT_MONEY));
|
||||
p.writeUInt64(guid);
|
||||
p.writeUInt32(amount);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet GuildBankWithdrawMoneyPacket::build(uint64_t guid, uint32_t amount) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_WITHDRAW_MONEY));
|
||||
p.writeUInt64(guid);
|
||||
p.writeUInt32(amount);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet GuildBankSwapItemsPacket::buildBankToInventory(
|
||||
uint64_t guid, uint8_t tabId, uint8_t bankSlot,
|
||||
uint8_t destBag, uint8_t destSlot, uint32_t splitCount)
|
||||
{
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_SWAP_ITEMS));
|
||||
p.writeUInt64(guid);
|
||||
p.writeUInt8(0); // bankToCharacter = false -> bank source
|
||||
p.writeUInt8(tabId);
|
||||
p.writeUInt8(bankSlot);
|
||||
p.writeUInt32(0); // itemEntry (unused client side)
|
||||
p.writeUInt8(0); // autoStore = false
|
||||
if (splitCount > 0) {
|
||||
p.writeUInt8(splitCount);
|
||||
}
|
||||
p.writeUInt8(destBag);
|
||||
p.writeUInt8(destSlot);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet GuildBankSwapItemsPacket::buildInventoryToBank(
|
||||
uint64_t guid, uint8_t tabId, uint8_t bankSlot,
|
||||
uint8_t srcBag, uint8_t srcSlot, uint32_t splitCount)
|
||||
{
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_GUILD_BANK_SWAP_ITEMS));
|
||||
p.writeUInt64(guid);
|
||||
p.writeUInt8(1); // bankToCharacter = true -> char to bank
|
||||
p.writeUInt8(tabId);
|
||||
p.writeUInt8(bankSlot);
|
||||
p.writeUInt32(0); // itemEntry
|
||||
p.writeUInt8(0); // autoStore
|
||||
if (splitCount > 0) {
|
||||
p.writeUInt8(splitCount);
|
||||
}
|
||||
p.writeUInt8(srcBag);
|
||||
p.writeUInt8(srcSlot);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool GuildBankListParser::parse(network::Packet& packet, GuildBankData& data) {
|
||||
if (packet.getSize() - packet.getReadPos() < 14) return false;
|
||||
|
||||
data.money = packet.readUInt64();
|
||||
data.tabId = packet.readUInt8();
|
||||
data.withdrawAmount = static_cast<int32_t>(packet.readUInt32());
|
||||
uint8_t fullUpdate = packet.readUInt8();
|
||||
|
||||
if (fullUpdate) {
|
||||
uint8_t tabCount = packet.readUInt8();
|
||||
data.tabs.resize(tabCount);
|
||||
for (uint8_t i = 0; i < tabCount; ++i) {
|
||||
data.tabs[i].tabName = packet.readString();
|
||||
data.tabs[i].tabIcon = packet.readString();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t numSlots = packet.readUInt8();
|
||||
data.tabItems.clear();
|
||||
for (uint8_t i = 0; i < numSlots; ++i) {
|
||||
GuildBankItemSlot slot;
|
||||
slot.slotId = packet.readUInt8();
|
||||
slot.itemEntry = packet.readUInt32();
|
||||
if (slot.itemEntry != 0) {
|
||||
// Enchant info
|
||||
uint32_t enchantMask = packet.readUInt32();
|
||||
for (int bit = 0; bit < 10; ++bit) {
|
||||
if (enchantMask & (1u << bit)) {
|
||||
uint32_t enchId = packet.readUInt32();
|
||||
uint32_t enchDur = packet.readUInt32();
|
||||
uint32_t enchCharges = packet.readUInt32();
|
||||
if (bit == 0) slot.enchantId = enchId;
|
||||
(void)enchDur; (void)enchCharges;
|
||||
}
|
||||
}
|
||||
slot.stackCount = packet.readUInt32();
|
||||
/*spare=*/ packet.readUInt32();
|
||||
slot.randomPropertyId = packet.readUInt32();
|
||||
if (slot.randomPropertyId) {
|
||||
/*suffixFactor=*/ packet.readUInt32();
|
||||
}
|
||||
}
|
||||
data.tabItems.push_back(slot);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Auction House System
|
||||
// ============================================================
|
||||
|
||||
network::Packet AuctionHelloPacket::build(uint64_t guid) {
|
||||
network::Packet p(wireOpcode(Opcode::MSG_AUCTION_HELLO));
|
||||
p.writeUInt64(guid);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool AuctionHelloParser::parse(network::Packet& packet, AuctionHelloData& data) {
|
||||
size_t remaining = packet.getSize() - packet.getReadPos();
|
||||
if (remaining < 12) {
|
||||
LOG_WARNING("AuctionHelloParser: too small, remaining=", remaining);
|
||||
return false;
|
||||
}
|
||||
data.auctioneerGuid = packet.readUInt64();
|
||||
data.auctionHouseId = packet.readUInt32();
|
||||
// WotLK has an extra uint8 enabled field; Vanilla does not
|
||||
if (packet.getReadPos() < packet.getSize()) {
|
||||
data.enabled = packet.readUInt8();
|
||||
} else {
|
||||
data.enabled = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
network::Packet AuctionListItemsPacket::build(
|
||||
uint64_t guid, uint32_t offset,
|
||||
const std::string& searchName,
|
||||
uint8_t levelMin, uint8_t levelMax,
|
||||
uint32_t invTypeMask, uint32_t itemClass,
|
||||
uint32_t itemSubClass, uint32_t quality,
|
||||
uint8_t usableOnly, uint8_t exactMatch)
|
||||
{
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_LIST_ITEMS));
|
||||
p.writeUInt64(guid);
|
||||
p.writeUInt32(offset);
|
||||
p.writeString(searchName);
|
||||
p.writeUInt8(levelMin);
|
||||
p.writeUInt8(levelMax);
|
||||
p.writeUInt32(invTypeMask);
|
||||
p.writeUInt32(itemClass);
|
||||
p.writeUInt32(itemSubClass);
|
||||
p.writeUInt32(quality);
|
||||
p.writeUInt8(usableOnly);
|
||||
p.writeUInt8(0); // getAll (0 = normal search)
|
||||
// Sort columns (0 = none)
|
||||
p.writeUInt8(0);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet AuctionSellItemPacket::build(
|
||||
uint64_t auctioneerGuid, uint64_t itemGuid,
|
||||
uint32_t stackCount, uint32_t bid,
|
||||
uint32_t buyout, uint32_t duration)
|
||||
{
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_SELL_ITEM));
|
||||
p.writeUInt64(auctioneerGuid);
|
||||
p.writeUInt32(1); // item count (WotLK supports multiple, we send 1)
|
||||
p.writeUInt64(itemGuid);
|
||||
p.writeUInt32(stackCount);
|
||||
p.writeUInt32(bid);
|
||||
p.writeUInt32(buyout);
|
||||
p.writeUInt32(duration);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet AuctionPlaceBidPacket::build(uint64_t auctioneerGuid, uint32_t auctionId, uint32_t amount) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_PLACE_BID));
|
||||
p.writeUInt64(auctioneerGuid);
|
||||
p.writeUInt32(auctionId);
|
||||
p.writeUInt32(amount);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet AuctionRemoveItemPacket::build(uint64_t auctioneerGuid, uint32_t auctionId) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_REMOVE_ITEM));
|
||||
p.writeUInt64(auctioneerGuid);
|
||||
p.writeUInt32(auctionId);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet AuctionListOwnerItemsPacket::build(uint64_t auctioneerGuid, uint32_t offset) {
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_LIST_OWNER_ITEMS));
|
||||
p.writeUInt64(auctioneerGuid);
|
||||
p.writeUInt32(offset);
|
||||
return p;
|
||||
}
|
||||
|
||||
network::Packet AuctionListBidderItemsPacket::build(
|
||||
uint64_t auctioneerGuid, uint32_t offset,
|
||||
const std::vector<uint32_t>& outbiddedIds)
|
||||
{
|
||||
network::Packet p(wireOpcode(Opcode::CMSG_AUCTION_LIST_BIDDER_ITEMS));
|
||||
p.writeUInt64(auctioneerGuid);
|
||||
p.writeUInt32(offset);
|
||||
p.writeUInt32(static_cast<uint32_t>(outbiddedIds.size()));
|
||||
for (uint32_t id : outbiddedIds)
|
||||
p.writeUInt32(id);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool AuctionListResultParser::parse(network::Packet& packet, AuctionListResult& data) {
|
||||
if (packet.getSize() - packet.getReadPos() < 4) return false;
|
||||
|
||||
uint32_t count = packet.readUInt32();
|
||||
data.auctions.clear();
|
||||
data.auctions.reserve(count);
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
if (packet.getReadPos() + 64 > packet.getSize()) break;
|
||||
AuctionEntry e;
|
||||
e.auctionId = packet.readUInt32();
|
||||
e.itemEntry = packet.readUInt32();
|
||||
// 3 enchant slots: enchantId, duration, charges
|
||||
e.enchantId = packet.readUInt32();
|
||||
packet.readUInt32(); // enchant duration
|
||||
packet.readUInt32(); // enchant charges
|
||||
packet.readUInt32(); // enchant2 id
|
||||
packet.readUInt32(); // enchant2 duration
|
||||
packet.readUInt32(); // enchant2 charges
|
||||
packet.readUInt32(); // enchant3 id
|
||||
packet.readUInt32(); // enchant3 duration
|
||||
packet.readUInt32(); // enchant3 charges
|
||||
e.randomPropertyId = packet.readUInt32();
|
||||
e.suffixFactor = packet.readUInt32();
|
||||
e.stackCount = packet.readUInt32();
|
||||
packet.readUInt32(); // item charges
|
||||
packet.readUInt32(); // item flags (unused)
|
||||
e.ownerGuid = packet.readUInt64();
|
||||
e.startBid = packet.readUInt32();
|
||||
e.minBidIncrement = packet.readUInt32();
|
||||
e.buyoutPrice = packet.readUInt32();
|
||||
e.timeLeftMs = packet.readUInt32();
|
||||
e.bidderGuid = packet.readUInt64();
|
||||
e.currentBid = packet.readUInt32();
|
||||
data.auctions.push_back(e);
|
||||
}
|
||||
|
||||
data.totalCount = packet.readUInt32();
|
||||
data.searchDelay = packet.readUInt32();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuctionCommandResultParser::parse(network::Packet& packet, AuctionCommandResult& data) {
|
||||
if (packet.getSize() - packet.getReadPos() < 12) return false;
|
||||
data.auctionId = packet.readUInt32();
|
||||
data.action = packet.readUInt32();
|
||||
data.errorCode = packet.readUInt32();
|
||||
if (data.errorCode != 0 && data.action == 2 && packet.getReadPos() + 4 <= packet.getSize()) {
|
||||
data.bidError = packet.readUInt32();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue