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:
Kelsi 2026-02-16 21:11:18 -08:00
parent 0d4a9c38f7
commit 381d896348
14 changed files with 1839 additions and 15 deletions

View file

@ -785,6 +785,51 @@ public:
void mailMarkAsRead(uint32_t mailId);
void refreshMailList();
// Bank
void openBank(uint64_t guid);
void closeBank();
void buyBankSlot();
void depositItem(uint8_t srcBag, uint8_t srcSlot);
void withdrawItem(uint8_t srcBag, uint8_t srcSlot);
bool isBankOpen() const { return bankOpen_; }
uint64_t getBankerGuid() const { return bankerGuid_; }
// Guild Bank
void openGuildBank(uint64_t guid);
void closeGuildBank();
void queryGuildBankTab(uint8_t tabId);
void buyGuildBankTab();
void depositGuildBankMoney(uint32_t amount);
void withdrawGuildBankMoney(uint32_t amount);
void guildBankWithdrawItem(uint8_t tabId, uint8_t bankSlot, uint8_t destBag, uint8_t destSlot);
void guildBankDepositItem(uint8_t tabId, uint8_t bankSlot, uint8_t srcBag, uint8_t srcSlot);
bool isGuildBankOpen() const { return guildBankOpen_; }
const GuildBankData& getGuildBankData() const { return guildBankData_; }
uint8_t getGuildBankActiveTab() const { return guildBankActiveTab_; }
void setGuildBankActiveTab(uint8_t tab) { guildBankActiveTab_ = tab; }
// Auction House
void openAuctionHouse(uint64_t guid);
void closeAuctionHouse();
void 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 = 0);
void auctionSellItem(uint64_t itemGuid, uint32_t stackCount, uint32_t bid,
uint32_t buyout, uint32_t duration);
void auctionPlaceBid(uint32_t auctionId, uint32_t amount);
void auctionBuyout(uint32_t auctionId, uint32_t buyoutPrice);
void auctionCancelItem(uint32_t auctionId);
void auctionListOwnerItems(uint32_t offset = 0);
void auctionListBidderItems(uint32_t offset = 0);
bool isAuctionHouseOpen() const { return auctionOpen_; }
uint64_t getAuctioneerGuid() const { return auctioneerGuid_; }
const AuctionListResult& getAuctionBrowseResults() const { return auctionBrowseResults_; }
const AuctionListResult& getAuctionOwnerResults() const { return auctionOwnerResults_; }
const AuctionListResult& getAuctionBidderResults() const { return auctionBidderResults_; }
int getAuctionActiveTab() const { return auctionActiveTab_; }
void setAuctionActiveTab(int tab) { auctionActiveTab_ = tab; }
float getAuctionSearchDelay() const { return auctionSearchDelayTimer_; }
// Trainer
bool isTrainerWindowOpen() const { return trainerWindowOpen_; }
const TrainerListData& getTrainerSpells() const { return currentTrainerList_; }
@ -1010,6 +1055,20 @@ private:
void handleArenaTeamEvent(network::Packet& packet);
void handleArenaError(network::Packet& packet);
// ---- Bank handlers ----
void handleShowBank(network::Packet& packet);
void handleBuyBankSlotResult(network::Packet& packet);
// ---- Guild Bank handlers ----
void handleGuildBankList(network::Packet& packet);
// ---- Auction House handlers ----
void handleAuctionHello(network::Packet& packet);
void handleAuctionListResult(network::Packet& packet);
void handleAuctionOwnerListResult(network::Packet& packet);
void handleAuctionBidderListResult(network::Packet& packet);
void handleAuctionCommandResult(network::Packet& packet);
// ---- Mail handlers ----
void handleShowMailbox(network::Packet& packet);
void handleMailListResult(network::Packet& packet);
@ -1360,6 +1419,31 @@ private:
bool showMailCompose_ = false;
bool hasNewMail_ = false;
// Bank
bool bankOpen_ = false;
uint64_t bankerGuid_ = 0;
std::array<uint64_t, 28> bankSlotGuids_{};
std::array<uint64_t, 7> bankBagSlotGuids_{};
// Guild Bank
bool guildBankOpen_ = false;
uint64_t guildBankerGuid_ = 0;
GuildBankData guildBankData_;
uint8_t guildBankActiveTab_ = 0;
// Auction House
bool auctionOpen_ = false;
uint64_t auctioneerGuid_ = 0;
uint32_t auctionHouseId_ = 0;
AuctionListResult auctionBrowseResults_;
AuctionListResult auctionOwnerResults_;
AuctionListResult auctionBidderResults_;
int auctionActiveTab_ = 0; // 0=Browse, 1=Bids, 2=Auctions
float auctionSearchDelayTimer_ = 0.0f;
// Routing: which result vector to populate from next SMSG_AUCTION_LIST_RESULT
enum class AuctionResultTarget { BROWSE, OWNER, BIDDER };
AuctionResultTarget pendingAuctionTarget_ = AuctionResultTarget::BROWSE;
// Vendor
bool vendorWindowOpen = false;
ListInventoryData currentVendorItems;

View file

@ -56,6 +56,8 @@ public:
static constexpr int NUM_EQUIP_SLOTS = 23;
static constexpr int NUM_BAG_SLOTS = 4;
static constexpr int MAX_BAG_SIZE = 36;
static constexpr int BANK_SLOTS = 28;
static constexpr int BANK_BAG_SLOTS = 7;
Inventory();
@ -76,6 +78,19 @@ public:
const ItemSlot& getBagSlot(int bagIndex, int slotIndex) const;
bool setBagSlot(int bagIndex, int slotIndex, const ItemDef& item);
// Bank slots (28 main + 7 bank bags)
const ItemSlot& getBankSlot(int index) const;
bool setBankSlot(int index, const ItemDef& item);
bool clearBankSlot(int index);
const ItemSlot& getBankBagSlot(int bagIndex, int slotIndex) const;
bool setBankBagSlot(int bagIndex, int slotIndex, const ItemDef& item);
int getBankBagSize(int bagIndex) const;
void setBankBagSize(int bagIndex, int size);
uint8_t getPurchasedBankBagSlots() const { return purchasedBankBagSlots_; }
void setPurchasedBankBagSlots(uint8_t count) { purchasedBankBagSlots_ = count; }
// Utility
int findFreeBackpackSlot() const;
bool addItem(const ItemDef& item);
@ -92,6 +107,11 @@ private:
std::array<ItemSlot, MAX_BAG_SIZE> slots{};
};
std::array<BagData, NUM_BAG_SLOTS> bags{};
// Bank
std::array<ItemSlot, BANK_SLOTS> bankSlots_{};
std::array<BagData, BANK_BAG_SLOTS> bankBags_{};
uint8_t purchasedBankBagSlots_ = 0;
};
const char* getQualityName(ItemQuality quality);

View file

@ -382,6 +382,39 @@ enum class LogicalOpcode : uint16_t {
SMSG_RECEIVED_MAIL,
MSG_QUERY_NEXT_MAIL_TIME,
// ---- Bank ----
CMSG_BANKER_ACTIVATE,
SMSG_SHOW_BANK,
CMSG_BUY_BANK_SLOT,
SMSG_BUY_BANK_SLOT_RESULT,
CMSG_AUTOBANK_ITEM,
CMSG_AUTOSTORE_BANK_ITEM,
// ---- Guild Bank ----
CMSG_GUILD_BANKER_ACTIVATE,
CMSG_GUILD_BANK_QUERY_TAB,
SMSG_GUILD_BANK_LIST,
CMSG_GUILD_BANK_SWAP_ITEMS,
CMSG_GUILD_BANK_BUY_TAB,
CMSG_GUILD_BANK_UPDATE_TAB,
CMSG_GUILD_BANK_DEPOSIT_MONEY,
CMSG_GUILD_BANK_WITHDRAW_MONEY,
// ---- Auction House ----
MSG_AUCTION_HELLO,
CMSG_AUCTION_SELL_ITEM,
CMSG_AUCTION_REMOVE_ITEM,
CMSG_AUCTION_LIST_ITEMS,
CMSG_AUCTION_LIST_OWNER_ITEMS,
CMSG_AUCTION_PLACE_BID,
SMSG_AUCTION_COMMAND_RESULT,
SMSG_AUCTION_LIST_RESULT,
SMSG_AUCTION_OWNER_LIST_RESULT,
SMSG_AUCTION_BIDDER_LIST_RESULT,
SMSG_AUCTION_OWNER_NOTIFICATION,
SMSG_AUCTION_BIDDER_NOTIFICATION,
CMSG_AUCTION_LIST_BIDDER_ITEMS,
// Sentinel
COUNT
};

View file

@ -44,6 +44,8 @@ enum class UF : uint16_t {
PLAYER_QUEST_LOG_START,
PLAYER_FIELD_INV_SLOT_HEAD,
PLAYER_FIELD_PACK_SLOT_1,
PLAYER_FIELD_BANK_SLOT_1,
PLAYER_FIELD_BANKBAG_SLOT_1,
PLAYER_SKILL_INFO_START,
PLAYER_EXPLORED_ZONES_START,

View file

@ -2262,5 +2262,213 @@ public:
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId);
};
// ============================================================
// Bank System
// ============================================================
/** CMSG_BANKER_ACTIVATE packet builder */
class BankerActivatePacket {
public:
static network::Packet build(uint64_t guid);
};
/** CMSG_BUY_BANK_SLOT packet builder */
class BuyBankSlotPacket {
public:
static network::Packet build(uint64_t guid);
};
/** CMSG_AUTOBANK_ITEM packet builder (deposit item to bank) */
class AutoBankItemPacket {
public:
static network::Packet build(uint8_t srcBag, uint8_t srcSlot);
};
/** CMSG_AUTOSTORE_BANK_ITEM packet builder (withdraw item from bank) */
class AutoStoreBankItemPacket {
public:
static network::Packet build(uint8_t srcBag, uint8_t srcSlot);
};
// ============================================================
// Guild Bank System
// ============================================================
struct GuildBankItemSlot {
uint8_t slotId = 0;
uint32_t itemEntry = 0;
uint32_t stackCount = 1;
uint32_t enchantId = 0;
uint32_t randomPropertyId = 0;
};
struct GuildBankTab {
std::string tabName;
std::string tabIcon;
std::vector<GuildBankItemSlot> items;
};
struct GuildBankData {
uint64_t money = 0;
uint8_t tabId = 0;
int32_t withdrawAmount = -1; // -1 = unlimited
std::vector<GuildBankTab> tabs; // Only populated on fullUpdate
std::vector<GuildBankItemSlot> tabItems; // Current tab items
};
/** CMSG_GUILD_BANKER_ACTIVATE packet builder */
class GuildBankerActivatePacket {
public:
static network::Packet build(uint64_t guid);
};
/** CMSG_GUILD_BANK_QUERY_TAB packet builder */
class GuildBankQueryTabPacket {
public:
static network::Packet build(uint64_t guid, uint8_t tabId, bool fullUpdate);
};
/** CMSG_GUILD_BANK_BUY_TAB packet builder */
class GuildBankBuyTabPacket {
public:
static network::Packet build(uint64_t guid, uint8_t tabId);
};
/** CMSG_GUILD_BANK_DEPOSIT_MONEY packet builder */
class GuildBankDepositMoneyPacket {
public:
static network::Packet build(uint64_t guid, uint32_t amount);
};
/** CMSG_GUILD_BANK_WITHDRAW_MONEY packet builder */
class GuildBankWithdrawMoneyPacket {
public:
static network::Packet build(uint64_t guid, uint32_t amount);
};
/** CMSG_GUILD_BANK_SWAP_ITEMS packet builder */
class GuildBankSwapItemsPacket {
public:
// Bank to inventory
static network::Packet buildBankToInventory(uint64_t guid, uint8_t tabId, uint8_t bankSlot,
uint8_t destBag, uint8_t destSlot, uint32_t splitCount = 0);
// Inventory to bank
static network::Packet buildInventoryToBank(uint64_t guid, uint8_t tabId, uint8_t bankSlot,
uint8_t srcBag, uint8_t srcSlot, uint32_t splitCount = 0);
};
/** SMSG_GUILD_BANK_LIST parser */
class GuildBankListParser {
public:
static bool parse(network::Packet& packet, GuildBankData& data);
};
// ============================================================
// Auction House System
// ============================================================
struct AuctionEntry {
uint32_t auctionId = 0;
uint32_t itemEntry = 0;
uint32_t stackCount = 1;
uint32_t enchantId = 0;
uint32_t randomPropertyId = 0;
uint32_t suffixFactor = 0;
uint64_t ownerGuid = 0;
uint32_t startBid = 0;
uint32_t minBidIncrement = 0;
uint32_t buyoutPrice = 0;
uint32_t timeLeftMs = 0;
uint64_t bidderGuid = 0;
uint32_t currentBid = 0;
};
struct AuctionListResult {
std::vector<AuctionEntry> auctions;
uint32_t totalCount = 0;
uint32_t searchDelay = 0;
};
struct AuctionCommandResult {
uint32_t auctionId = 0;
uint32_t action = 0; // 0=create, 1=cancel, 2=bid, 3=buyout
uint32_t errorCode = 0; // 0=success
uint32_t bidError = 0; // secondary error for bid actions
};
struct AuctionHelloData {
uint64_t auctioneerGuid = 0;
uint32_t auctionHouseId = 0;
uint8_t enabled = 1;
};
/** MSG_AUCTION_HELLO packet builder */
class AuctionHelloPacket {
public:
static network::Packet build(uint64_t guid);
};
/** MSG_AUCTION_HELLO parser (server response) */
class AuctionHelloParser {
public:
static bool parse(network::Packet& packet, AuctionHelloData& data);
};
/** CMSG_AUCTION_LIST_ITEMS packet builder */
class AuctionListItemsPacket {
public:
static network::Packet 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);
};
/** CMSG_AUCTION_SELL_ITEM packet builder */
class AuctionSellItemPacket {
public:
static network::Packet build(uint64_t auctioneerGuid, uint64_t itemGuid,
uint32_t stackCount, uint32_t bid,
uint32_t buyout, uint32_t duration);
};
/** CMSG_AUCTION_PLACE_BID packet builder */
class AuctionPlaceBidPacket {
public:
static network::Packet build(uint64_t auctioneerGuid, uint32_t auctionId, uint32_t amount);
};
/** CMSG_AUCTION_REMOVE_ITEM packet builder */
class AuctionRemoveItemPacket {
public:
static network::Packet build(uint64_t auctioneerGuid, uint32_t auctionId);
};
/** CMSG_AUCTION_LIST_OWNER_ITEMS packet builder */
class AuctionListOwnerItemsPacket {
public:
static network::Packet build(uint64_t auctioneerGuid, uint32_t offset);
};
/** CMSG_AUCTION_LIST_BIDDER_ITEMS packet builder */
class AuctionListBidderItemsPacket {
public:
static network::Packet build(uint64_t auctioneerGuid, uint32_t offset,
const std::vector<uint32_t>& outbiddedIds = {});
};
/** SMSG_AUCTION_LIST_RESULT parser (shared for browse/owner/bidder) */
class AuctionListResultParser {
public:
static bool parse(network::Packet& packet, AuctionListResult& data);
};
/** SMSG_AUCTION_COMMAND_RESULT parser */
class AuctionCommandResultParser {
public:
static bool parse(network::Packet& packet, AuctionCommandResult& data);
};
} // namespace game
} // namespace wowee

View file

@ -187,6 +187,9 @@ private:
void renderChatBubbles(game::GameHandler& gameHandler);
void renderMailWindow(game::GameHandler& gameHandler);
void renderMailComposeWindow(game::GameHandler& gameHandler);
void renderBankWindow(game::GameHandler& gameHandler);
void renderGuildBankWindow(game::GameHandler& gameHandler);
void renderAuctionHouseWindow(game::GameHandler& gameHandler);
/**
* Inventory screen
@ -254,6 +257,19 @@ private:
char mailBodyBuffer_[2048] = "";
int mailComposeMoney_[3] = {0, 0, 0}; // gold, silver, copper
// Auction house UI state
char auctionSearchName_[256] = "";
int auctionLevelMin_ = 0;
int auctionLevelMax_ = 0;
int auctionQuality_ = 0;
int auctionSellDuration_ = 2; // 0=12h, 1=24h, 2=48h
int auctionSellBid_[3] = {0, 0, 0}; // gold, silver, copper
int auctionSellBuyout_[3] = {0, 0, 0}; // gold, silver, copper
int auctionSelectedItem_ = -1;
// Guild bank money input
int guildBankMoneyInput_[3] = {0, 0, 0}; // gold, silver, copper
// Left-click targeting: distinguish click from camera drag
glm::vec2 leftClickPressPos_ = glm::vec2(0.0f);
bool leftClickWasPress_ = false;