mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue