mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30: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
|
|
@ -249,5 +249,26 @@
|
||||||
"CMSG_MAIL_DELETE": "0x249",
|
"CMSG_MAIL_DELETE": "0x249",
|
||||||
"CMSG_MAIL_MARK_AS_READ": "0x247",
|
"CMSG_MAIL_MARK_AS_READ": "0x247",
|
||||||
"SMSG_RECEIVED_MAIL": "0x285",
|
"SMSG_RECEIVED_MAIL": "0x285",
|
||||||
"MSG_QUERY_NEXT_MAIL_TIME": "0x284"
|
"MSG_QUERY_NEXT_MAIL_TIME": "0x284",
|
||||||
|
|
||||||
|
"CMSG_BANKER_ACTIVATE": "0x1B7",
|
||||||
|
"SMSG_SHOW_BANK": "0x1B8",
|
||||||
|
"CMSG_BUY_BANK_SLOT": "0x1B9",
|
||||||
|
"SMSG_BUY_BANK_SLOT_RESULT": "0x1BA",
|
||||||
|
"CMSG_AUTOSTORE_BANK_ITEM": "0x282",
|
||||||
|
"CMSG_AUTOBANK_ITEM": "0x283",
|
||||||
|
|
||||||
|
"MSG_AUCTION_HELLO": "0x255",
|
||||||
|
"CMSG_AUCTION_SELL_ITEM": "0x256",
|
||||||
|
"CMSG_AUCTION_REMOVE_ITEM": "0x257",
|
||||||
|
"CMSG_AUCTION_LIST_ITEMS": "0x258",
|
||||||
|
"CMSG_AUCTION_LIST_OWNER_ITEMS": "0x259",
|
||||||
|
"CMSG_AUCTION_PLACE_BID": "0x25A",
|
||||||
|
"SMSG_AUCTION_COMMAND_RESULT": "0x25B",
|
||||||
|
"SMSG_AUCTION_LIST_RESULT": "0x25C",
|
||||||
|
"SMSG_AUCTION_OWNER_LIST_RESULT": "0x25D",
|
||||||
|
"SMSG_AUCTION_OWNER_NOTIFICATION": "0x25E",
|
||||||
|
"SMSG_AUCTION_BIDDER_NOTIFICATION": "0x260",
|
||||||
|
"CMSG_AUCTION_LIST_BIDDER_ITEMS": "0x264",
|
||||||
|
"SMSG_AUCTION_BIDDER_LIST_RESULT": "0x265"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -785,6 +785,51 @@ public:
|
||||||
void mailMarkAsRead(uint32_t mailId);
|
void mailMarkAsRead(uint32_t mailId);
|
||||||
void refreshMailList();
|
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
|
// Trainer
|
||||||
bool isTrainerWindowOpen() const { return trainerWindowOpen_; }
|
bool isTrainerWindowOpen() const { return trainerWindowOpen_; }
|
||||||
const TrainerListData& getTrainerSpells() const { return currentTrainerList_; }
|
const TrainerListData& getTrainerSpells() const { return currentTrainerList_; }
|
||||||
|
|
@ -1010,6 +1055,20 @@ private:
|
||||||
void handleArenaTeamEvent(network::Packet& packet);
|
void handleArenaTeamEvent(network::Packet& packet);
|
||||||
void handleArenaError(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 ----
|
// ---- Mail handlers ----
|
||||||
void handleShowMailbox(network::Packet& packet);
|
void handleShowMailbox(network::Packet& packet);
|
||||||
void handleMailListResult(network::Packet& packet);
|
void handleMailListResult(network::Packet& packet);
|
||||||
|
|
@ -1360,6 +1419,31 @@ private:
|
||||||
bool showMailCompose_ = false;
|
bool showMailCompose_ = false;
|
||||||
bool hasNewMail_ = 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
|
// Vendor
|
||||||
bool vendorWindowOpen = false;
|
bool vendorWindowOpen = false;
|
||||||
ListInventoryData currentVendorItems;
|
ListInventoryData currentVendorItems;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ public:
|
||||||
static constexpr int NUM_EQUIP_SLOTS = 23;
|
static constexpr int NUM_EQUIP_SLOTS = 23;
|
||||||
static constexpr int NUM_BAG_SLOTS = 4;
|
static constexpr int NUM_BAG_SLOTS = 4;
|
||||||
static constexpr int MAX_BAG_SIZE = 36;
|
static constexpr int MAX_BAG_SIZE = 36;
|
||||||
|
static constexpr int BANK_SLOTS = 28;
|
||||||
|
static constexpr int BANK_BAG_SLOTS = 7;
|
||||||
|
|
||||||
Inventory();
|
Inventory();
|
||||||
|
|
||||||
|
|
@ -76,6 +78,19 @@ public:
|
||||||
const ItemSlot& getBagSlot(int bagIndex, int slotIndex) const;
|
const ItemSlot& getBagSlot(int bagIndex, int slotIndex) const;
|
||||||
bool setBagSlot(int bagIndex, int slotIndex, const ItemDef& item);
|
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
|
// Utility
|
||||||
int findFreeBackpackSlot() const;
|
int findFreeBackpackSlot() const;
|
||||||
bool addItem(const ItemDef& item);
|
bool addItem(const ItemDef& item);
|
||||||
|
|
@ -92,6 +107,11 @@ private:
|
||||||
std::array<ItemSlot, MAX_BAG_SIZE> slots{};
|
std::array<ItemSlot, MAX_BAG_SIZE> slots{};
|
||||||
};
|
};
|
||||||
std::array<BagData, NUM_BAG_SLOTS> bags{};
|
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);
|
const char* getQualityName(ItemQuality quality);
|
||||||
|
|
|
||||||
|
|
@ -382,6 +382,39 @@ enum class LogicalOpcode : uint16_t {
|
||||||
SMSG_RECEIVED_MAIL,
|
SMSG_RECEIVED_MAIL,
|
||||||
MSG_QUERY_NEXT_MAIL_TIME,
|
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
|
// Sentinel
|
||||||
COUNT
|
COUNT
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ enum class UF : uint16_t {
|
||||||
PLAYER_QUEST_LOG_START,
|
PLAYER_QUEST_LOG_START,
|
||||||
PLAYER_FIELD_INV_SLOT_HEAD,
|
PLAYER_FIELD_INV_SLOT_HEAD,
|
||||||
PLAYER_FIELD_PACK_SLOT_1,
|
PLAYER_FIELD_PACK_SLOT_1,
|
||||||
|
PLAYER_FIELD_BANK_SLOT_1,
|
||||||
|
PLAYER_FIELD_BANKBAG_SLOT_1,
|
||||||
PLAYER_SKILL_INFO_START,
|
PLAYER_SKILL_INFO_START,
|
||||||
PLAYER_EXPLORED_ZONES_START,
|
PLAYER_EXPLORED_ZONES_START,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2262,5 +2262,213 @@ public:
|
||||||
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId);
|
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 game
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,9 @@ private:
|
||||||
void renderChatBubbles(game::GameHandler& gameHandler);
|
void renderChatBubbles(game::GameHandler& gameHandler);
|
||||||
void renderMailWindow(game::GameHandler& gameHandler);
|
void renderMailWindow(game::GameHandler& gameHandler);
|
||||||
void renderMailComposeWindow(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
|
* Inventory screen
|
||||||
|
|
@ -254,6 +257,19 @@ private:
|
||||||
char mailBodyBuffer_[2048] = "";
|
char mailBodyBuffer_[2048] = "";
|
||||||
int mailComposeMoney_[3] = {0, 0, 0}; // gold, silver, copper
|
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
|
// Left-click targeting: distinguish click from camera drag
|
||||||
glm::vec2 leftClickPressPos_ = glm::vec2(0.0f);
|
glm::vec2 leftClickPressPos_ = glm::vec2(0.0f);
|
||||||
bool leftClickWasPress_ = false;
|
bool leftClickWasPress_ = false;
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,11 @@ void GameHandler::update(float deltaTime) {
|
||||||
clearTarget();
|
clearTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auctionSearchDelayTimer_ > 0.0f) {
|
||||||
|
auctionSearchDelayTimer_ -= deltaTime;
|
||||||
|
if (auctionSearchDelayTimer_ < 0.0f) auctionSearchDelayTimer_ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
if (pendingMoneyDeltaTimer_ > 0.0f) {
|
if (pendingMoneyDeltaTimer_ > 0.0f) {
|
||||||
pendingMoneyDeltaTimer_ -= deltaTime;
|
pendingMoneyDeltaTimer_ -= deltaTime;
|
||||||
if (pendingMoneyDeltaTimer_ <= 0.0f) {
|
if (pendingMoneyDeltaTimer_ <= 0.0f) {
|
||||||
|
|
@ -1526,6 +1531,36 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
handleQueryNextMailTime(packet);
|
handleQueryNextMailTime(packet);
|
||||||
break;
|
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:
|
default:
|
||||||
// In pre-world states we need full visibility (char create/login handshakes).
|
// 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.
|
// 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;
|
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;
|
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
|
// Only mark equipment dirty if equipped item displayInfoIds actually changed
|
||||||
std::array<uint32_t, 19> currentEquipDisplayIds{};
|
std::array<uint32_t, 19> currentEquipDisplayIds{};
|
||||||
for (int i = 0; i < 19; i++) {
|
for (int i = 0; i < 19; i++) {
|
||||||
|
|
@ -7450,17 +7612,46 @@ void GameHandler::interactWithGameObject(uint64_t guid) {
|
||||||
|
|
||||||
void GameHandler::selectGossipOption(uint32_t optionId) {
|
void GameHandler::selectGossipOption(uint32_t optionId) {
|
||||||
if (state != WorldState::IN_WORLD || !socket || !gossipWindowOpen) return;
|
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);
|
auto packet = GossipSelectOptionPacket::build(currentGossip.npcGuid, currentGossip.menuId, optionId);
|
||||||
socket->send(packet);
|
socket->send(packet);
|
||||||
|
|
||||||
// If this is an innkeeper "make this inn your home" option, send binder activate.
|
|
||||||
for (const auto& opt : currentGossip.options) {
|
for (const auto& opt : currentGossip.options) {
|
||||||
if (opt.id != optionId) continue;
|
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::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)); });
|
[](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);
|
auto bindPkt = BinderActivatePacket::build(currentGossip.npcGuid);
|
||||||
socket->send(bindPkt);
|
socket->send(bindPkt);
|
||||||
LOG_INFO("Sent CMSG_BINDER_ACTIVATE for npc=0x", std::hex, currentGossip.npcGuid, std::dec);
|
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);
|
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 game
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,46 @@ bool Inventory::setBagSlot(int bagIndex, int slotIndex, const ItemDef& item) {
|
||||||
return true;
|
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 {
|
int Inventory::findFreeBackpackSlot() const {
|
||||||
for (int i = 0; i < BACKPACK_SLOTS; i++) {
|
for (int i = 0; i < BACKPACK_SLOTS; i++) {
|
||||||
if (backpack[i].empty()) return 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},
|
{"CMSG_MAIL_MARK_AS_READ", LogicalOpcode::CMSG_MAIL_MARK_AS_READ},
|
||||||
{"SMSG_RECEIVED_MAIL", LogicalOpcode::SMSG_RECEIVED_MAIL},
|
{"SMSG_RECEIVED_MAIL", LogicalOpcode::SMSG_RECEIVED_MAIL},
|
||||||
{"MSG_QUERY_NEXT_MAIL_TIME", LogicalOpcode::MSG_QUERY_NEXT_MAIL_TIME},
|
{"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
|
// clang-format on
|
||||||
|
|
||||||
|
|
@ -615,6 +645,36 @@ void OpcodeTable::loadWotlkDefaults() {
|
||||||
{LogicalOpcode::CMSG_MAIL_MARK_AS_READ, 0x247},
|
{LogicalOpcode::CMSG_MAIL_MARK_AS_READ, 0x247},
|
||||||
{LogicalOpcode::SMSG_RECEIVED_MAIL, 0x285},
|
{LogicalOpcode::SMSG_RECEIVED_MAIL, 0x285},
|
||||||
{LogicalOpcode::MSG_QUERY_NEXT_MAIL_TIME, 0x284},
|
{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();
|
logicalToWire_.clear();
|
||||||
|
|
|
||||||
|
|
@ -673,9 +673,10 @@ bool ClassicPacketParsers::parseGossipMessage(network::Packet& packet, GossipMes
|
||||||
opt.id = packet.readUInt32();
|
opt.id = packet.readUInt32();
|
||||||
opt.icon = packet.readUInt8();
|
opt.icon = packet.readUInt8();
|
||||||
opt.isCoded = (packet.readUInt8() != 0);
|
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.text = packet.readString();
|
||||||
opt.boxText = packet.readString();
|
opt.boxText = "";
|
||||||
data.options.push_back(opt);
|
data.options.push_back(opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ static const UFNameEntry kUFNames[] = {
|
||||||
{"PLAYER_QUEST_LOG_START", UF::PLAYER_QUEST_LOG_START},
|
{"PLAYER_QUEST_LOG_START", UF::PLAYER_QUEST_LOG_START},
|
||||||
{"PLAYER_FIELD_INV_SLOT_HEAD", UF::PLAYER_FIELD_INV_SLOT_HEAD},
|
{"PLAYER_FIELD_INV_SLOT_HEAD", UF::PLAYER_FIELD_INV_SLOT_HEAD},
|
||||||
{"PLAYER_FIELD_PACK_SLOT_1", UF::PLAYER_FIELD_PACK_SLOT_1},
|
{"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_SKILL_INFO_START", UF::PLAYER_SKILL_INFO_START},
|
||||||
{"PLAYER_EXPLORED_ZONES_START", UF::PLAYER_EXPLORED_ZONES_START},
|
{"PLAYER_EXPLORED_ZONES_START", UF::PLAYER_EXPLORED_ZONES_START},
|
||||||
{"GAMEOBJECT_DISPLAYID", UF::GAMEOBJECT_DISPLAYID},
|
{"GAMEOBJECT_DISPLAYID", UF::GAMEOBJECT_DISPLAYID},
|
||||||
|
|
@ -84,6 +86,8 @@ void UpdateFieldTable::loadWotlkDefaults() {
|
||||||
{UF::PLAYER_QUEST_LOG_START, 158},
|
{UF::PLAYER_QUEST_LOG_START, 158},
|
||||||
{UF::PLAYER_FIELD_INV_SLOT_HEAD, 324},
|
{UF::PLAYER_FIELD_INV_SLOT_HEAD, 324},
|
||||||
{UF::PLAYER_FIELD_PACK_SLOT_1, 370},
|
{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_SKILL_INFO_START, 636},
|
||||||
{UF::PLAYER_EXPLORED_ZONES_START, 1041},
|
{UF::PLAYER_EXPLORED_ZONES_START, 1041},
|
||||||
{UF::GAMEOBJECT_DISPLAYID, 8},
|
{UF::GAMEOBJECT_DISPLAYID, 8},
|
||||||
|
|
|
||||||
|
|
@ -3480,5 +3480,316 @@ bool PacketParsers::parseMailList(network::Packet& packet, std::vector<MailMessa
|
||||||
return true;
|
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 game
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,9 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderTaxiWindow(gameHandler);
|
renderTaxiWindow(gameHandler);
|
||||||
renderMailWindow(gameHandler);
|
renderMailWindow(gameHandler);
|
||||||
renderMailComposeWindow(gameHandler);
|
renderMailComposeWindow(gameHandler);
|
||||||
|
renderBankWindow(gameHandler);
|
||||||
|
renderGuildBankWindow(gameHandler);
|
||||||
|
renderAuctionHouseWindow(gameHandler);
|
||||||
// renderQuestMarkers(gameHandler); // Disabled - using 3D billboard markers now
|
// renderQuestMarkers(gameHandler); // Disabled - using 3D billboard markers now
|
||||||
renderMinimapMarkers(gameHandler);
|
renderMinimapMarkers(gameHandler);
|
||||||
renderDeathScreen(gameHandler);
|
renderDeathScreen(gameHandler);
|
||||||
|
|
@ -4177,18 +4180,62 @@ void GameScreen::renderGossipWindow(game::GameHandler& gameHandler) {
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
// Gossip options
|
// Gossip option icons - matches WoW GossipOptionIcon enum
|
||||||
static const char* gossipIcons[] = {"[Chat]", "[Vendor]", "[Taxi]", "[Trainer]", "[Spiritguide]",
|
static const char* gossipIcons[] = {
|
||||||
"[Tabardvendor]", "[Battlemaster]", "[Banker]", "[Petitioner]",
|
"[Chat]", // 0 = GOSSIP_ICON_CHAT
|
||||||
"[Tabarddesigner]", "[Auctioneer]"};
|
"[Vendor]", // 1 = GOSSIP_ICON_VENDOR
|
||||||
|
"[Taxi]", // 2 = GOSSIP_ICON_TAXI
|
||||||
|
"[Trainer]", // 3 = GOSSIP_ICON_TRAINER
|
||||||
|
"[Interact]", // 4 = GOSSIP_ICON_INTERACT_1
|
||||||
|
"[Interact]", // 5 = GOSSIP_ICON_INTERACT_2
|
||||||
|
"[Banker]", // 6 = GOSSIP_ICON_MONEY_BAG (banker)
|
||||||
|
"[Chat]", // 7 = GOSSIP_ICON_TALK
|
||||||
|
"[Tabard]", // 8 = GOSSIP_ICON_TABARD
|
||||||
|
"[Battlemaster]", // 9 = GOSSIP_ICON_BATTLE
|
||||||
|
"[Option]", // 10 = GOSSIP_ICON_DOT
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default text for server-sent gossip option placeholders
|
||||||
|
static const std::unordered_map<std::string, std::string> gossipPlaceholders = {
|
||||||
|
{"GOSSIP_OPTION_BANKER", "I would like to check my deposit box."},
|
||||||
|
{"GOSSIP_OPTION_AUCTIONEER", "I'd like to browse your auctions."},
|
||||||
|
{"GOSSIP_OPTION_VENDOR", "I want to browse your goods."},
|
||||||
|
{"GOSSIP_OPTION_TAXIVENDOR", "I'd like to fly."},
|
||||||
|
{"GOSSIP_OPTION_TRAINER", "I seek training."},
|
||||||
|
{"GOSSIP_OPTION_INNKEEPER", "Make this inn your home."},
|
||||||
|
{"GOSSIP_OPTION_SPIRITGUIDE", "Return me to life."},
|
||||||
|
{"GOSSIP_OPTION_SPIRITHEALER", "Bring me back to life."},
|
||||||
|
{"GOSSIP_OPTION_STABLEPET", "I'd like to stable my pet."},
|
||||||
|
{"GOSSIP_OPTION_ARMORER", "I need to repair my equipment."},
|
||||||
|
{"GOSSIP_OPTION_GOSSIP", "What can you tell me?"},
|
||||||
|
{"GOSSIP_OPTION_BATTLEFIELD", "I'd like to go to the battleground."},
|
||||||
|
{"GOSSIP_OPTION_TABARDDESIGNER", "I want to create a guild tabard."},
|
||||||
|
{"GOSSIP_OPTION_PETITIONER", "I want to create a guild."},
|
||||||
|
};
|
||||||
|
|
||||||
for (const auto& opt : gossip.options) {
|
for (const auto& opt : gossip.options) {
|
||||||
ImGui::PushID(static_cast<int>(opt.id));
|
ImGui::PushID(static_cast<int>(opt.id));
|
||||||
|
|
||||||
|
// Determine icon label - use text-based detection for shared icons
|
||||||
const char* icon = (opt.icon < 11) ? gossipIcons[opt.icon] : "[Option]";
|
const char* icon = (opt.icon < 11) ? gossipIcons[opt.icon] : "[Option]";
|
||||||
std::string processedText = replaceGenderPlaceholders(opt.text, gameHandler);
|
if (opt.text == "GOSSIP_OPTION_AUCTIONEER") icon = "[Auctioneer]";
|
||||||
char label[256];
|
else if (opt.text == "GOSSIP_OPTION_BANKER") icon = "[Banker]";
|
||||||
snprintf(label, sizeof(label), "%s %s", icon, processedText.c_str());
|
else if (opt.text == "GOSSIP_OPTION_VENDOR") icon = "[Vendor]";
|
||||||
if (ImGui::Selectable(label)) {
|
else if (opt.text == "GOSSIP_OPTION_TRAINER") icon = "[Trainer]";
|
||||||
|
else if (opt.text == "GOSSIP_OPTION_INNKEEPER") icon = "[Innkeeper]";
|
||||||
|
else if (opt.text == "GOSSIP_OPTION_STABLEPET") icon = "[Stable Master]";
|
||||||
|
else if (opt.text == "GOSSIP_OPTION_ARMORER") icon = "[Repair]";
|
||||||
|
|
||||||
|
// Resolve placeholder text from server
|
||||||
|
std::string displayText = opt.text;
|
||||||
|
auto placeholderIt = gossipPlaceholders.find(displayText);
|
||||||
|
if (placeholderIt != gossipPlaceholders.end()) {
|
||||||
|
displayText = placeholderIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string processedText = replaceGenderPlaceholders(displayText, gameHandler);
|
||||||
|
std::string label = std::string(icon) + " " + processedText;
|
||||||
|
if (ImGui::Selectable(label.c_str())) {
|
||||||
gameHandler.selectGossipOption(opt.id);
|
gameHandler.selectGossipOption(opt.id);
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
|
|
@ -6464,4 +6511,490 @@ void GameScreen::renderMailComposeWindow(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Bank Window
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void GameScreen::renderBankWindow(game::GameHandler& gameHandler) {
|
||||||
|
if (!gameHandler.isBankOpen()) return;
|
||||||
|
|
||||||
|
bool open = true;
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(480, 420), ImGuiCond_FirstUseEver);
|
||||||
|
if (!ImGui::Begin("Bank", &open)) {
|
||||||
|
ImGui::End();
|
||||||
|
if (!open) gameHandler.closeBank();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& inv = gameHandler.getInventory();
|
||||||
|
|
||||||
|
// Main bank slots (28 = 7 columns × 4 rows)
|
||||||
|
ImGui::Text("Bank Slots");
|
||||||
|
ImGui::Separator();
|
||||||
|
for (int i = 0; i < game::Inventory::BANK_SLOTS; i++) {
|
||||||
|
if (i % 7 != 0) ImGui::SameLine();
|
||||||
|
const auto& slot = inv.getBankSlot(i);
|
||||||
|
|
||||||
|
ImGui::PushID(i + 1000);
|
||||||
|
if (slot.empty()) {
|
||||||
|
ImGui::Button("##bank", ImVec2(42, 42));
|
||||||
|
} else {
|
||||||
|
auto* info = gameHandler.getItemInfo(slot.item.itemId);
|
||||||
|
ImVec4 qc = InventoryScreen::getQualityColor(slot.item.quality);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(qc.x * 0.3f, qc.y * 0.3f, qc.z * 0.3f, 0.8f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(qc.x * 0.5f, qc.y * 0.5f, qc.z * 0.5f, 0.9f));
|
||||||
|
|
||||||
|
std::string label = std::to_string(slot.item.stackCount > 1 ? slot.item.stackCount : 0);
|
||||||
|
if (slot.item.stackCount <= 1) label = "##b" + std::to_string(i);
|
||||||
|
if (ImGui::Button(label.c_str(), ImVec2(42, 42))) {
|
||||||
|
// Right-click to withdraw: bag=0xFF means bank, slot=i
|
||||||
|
// Use CMSG_AUTOSTORE_BANK_ITEM with bank container
|
||||||
|
// WoW bank slots are inventory slots 39-66 (BANK_SLOT_1 = 39)
|
||||||
|
gameHandler.withdrawItem(0xFF, static_cast<uint8_t>(39 + i));
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::TextColored(qc, "%s", slot.item.name.c_str());
|
||||||
|
if (slot.item.stackCount > 1) ImGui::Text("Count: %u", slot.item.stackCount);
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bank bag slots
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Bank Bags");
|
||||||
|
uint8_t purchased = inv.getPurchasedBankBagSlots();
|
||||||
|
for (int i = 0; i < game::Inventory::BANK_BAG_SLOTS; i++) {
|
||||||
|
if (i > 0) ImGui::SameLine();
|
||||||
|
ImGui::PushID(i + 2000);
|
||||||
|
|
||||||
|
int bagSize = inv.getBankBagSize(i);
|
||||||
|
if (i < static_cast<int>(purchased) || bagSize > 0) {
|
||||||
|
if (ImGui::Button(bagSize > 0 ? std::to_string(bagSize).c_str() : "Empty", ImVec2(50, 30))) {
|
||||||
|
// Could open bag contents
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ImGui::Button("Buy", ImVec2(50, 30))) {
|
||||||
|
gameHandler.buyBankSlot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show expanded bank bag contents
|
||||||
|
for (int bagIdx = 0; bagIdx < game::Inventory::BANK_BAG_SLOTS; bagIdx++) {
|
||||||
|
int bagSize = inv.getBankBagSize(bagIdx);
|
||||||
|
if (bagSize <= 0) continue;
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Text("Bank Bag %d (%d slots)", bagIdx + 1, bagSize);
|
||||||
|
for (int s = 0; s < bagSize; s++) {
|
||||||
|
if (s % 7 != 0) ImGui::SameLine();
|
||||||
|
const auto& slot = inv.getBankBagSlot(bagIdx, s);
|
||||||
|
ImGui::PushID(3000 + bagIdx * 100 + s);
|
||||||
|
if (slot.empty()) {
|
||||||
|
ImGui::Button("##bb", ImVec2(42, 42));
|
||||||
|
} else {
|
||||||
|
ImVec4 qc = InventoryScreen::getQualityColor(slot.item.quality);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(qc.x * 0.3f, qc.y * 0.3f, qc.z * 0.3f, 0.8f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(qc.x * 0.5f, qc.y * 0.5f, qc.z * 0.5f, 0.9f));
|
||||||
|
std::string lbl = slot.item.stackCount > 1 ? std::to_string(slot.item.stackCount) : ("##bb" + std::to_string(bagIdx * 100 + s));
|
||||||
|
if (ImGui::Button(lbl.c_str(), ImVec2(42, 42))) {
|
||||||
|
// Withdraw from bank bag: bank bag container indices start at 67
|
||||||
|
gameHandler.withdrawItem(static_cast<uint8_t>(67 + bagIdx), static_cast<uint8_t>(s));
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::TextColored(qc, "%s", slot.item.name.c_str());
|
||||||
|
if (slot.item.stackCount > 1) ImGui::Text("Count: %u", slot.item.stackCount);
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (!open) gameHandler.closeBank();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Guild Bank Window
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void GameScreen::renderGuildBankWindow(game::GameHandler& gameHandler) {
|
||||||
|
if (!gameHandler.isGuildBankOpen()) return;
|
||||||
|
|
||||||
|
bool open = true;
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(520, 500), ImGuiCond_FirstUseEver);
|
||||||
|
if (!ImGui::Begin("Guild Bank", &open)) {
|
||||||
|
ImGui::End();
|
||||||
|
if (!open) gameHandler.closeGuildBank();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& data = gameHandler.getGuildBankData();
|
||||||
|
uint8_t activeTab = gameHandler.getGuildBankActiveTab();
|
||||||
|
|
||||||
|
// Money display
|
||||||
|
uint32_t gold = static_cast<uint32_t>(data.money / 10000);
|
||||||
|
uint32_t silver = static_cast<uint32_t>((data.money / 100) % 100);
|
||||||
|
uint32_t copper = static_cast<uint32_t>(data.money % 100);
|
||||||
|
ImGui::Text("Guild Bank Money: ");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextColored(ImVec4(0.9f, 0.8f, 0.3f, 1.0f), "%ug %us %uc", gold, silver, copper);
|
||||||
|
|
||||||
|
// Tab bar
|
||||||
|
if (!data.tabs.empty()) {
|
||||||
|
for (size_t i = 0; i < data.tabs.size(); i++) {
|
||||||
|
if (i > 0) ImGui::SameLine();
|
||||||
|
bool selected = (i == activeTab);
|
||||||
|
if (selected) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f));
|
||||||
|
std::string tabLabel = data.tabs[i].tabName.empty() ? ("Tab " + std::to_string(i + 1)) : data.tabs[i].tabName;
|
||||||
|
if (ImGui::Button(tabLabel.c_str())) {
|
||||||
|
gameHandler.queryGuildBankTab(static_cast<uint8_t>(i));
|
||||||
|
}
|
||||||
|
if (selected) ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buy tab button
|
||||||
|
if (data.tabs.size() < 6) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Buy Tab")) {
|
||||||
|
gameHandler.buyGuildBankTab();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Tab items (98 slots = 14 columns × 7 rows)
|
||||||
|
for (size_t i = 0; i < data.tabItems.size(); i++) {
|
||||||
|
if (i % 14 != 0) ImGui::SameLine();
|
||||||
|
const auto& item = data.tabItems[i];
|
||||||
|
ImGui::PushID(static_cast<int>(i) + 5000);
|
||||||
|
|
||||||
|
if (item.itemEntry == 0) {
|
||||||
|
ImGui::Button("##gb", ImVec2(34, 34));
|
||||||
|
} else {
|
||||||
|
auto* info = gameHandler.getItemInfo(item.itemEntry);
|
||||||
|
game::ItemQuality quality = game::ItemQuality::COMMON;
|
||||||
|
std::string name = "Item " + std::to_string(item.itemEntry);
|
||||||
|
if (info) {
|
||||||
|
quality = static_cast<game::ItemQuality>(info->quality);
|
||||||
|
name = info->name;
|
||||||
|
}
|
||||||
|
ImVec4 qc = InventoryScreen::getQualityColor(quality);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(qc.x * 0.3f, qc.y * 0.3f, qc.z * 0.3f, 0.8f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(qc.x * 0.5f, qc.y * 0.5f, qc.z * 0.5f, 0.9f));
|
||||||
|
std::string lbl = item.stackCount > 1 ? std::to_string(item.stackCount) : ("##gi" + std::to_string(i));
|
||||||
|
if (ImGui::Button(lbl.c_str(), ImVec2(34, 34))) {
|
||||||
|
// Withdraw: auto-store to first free bag slot
|
||||||
|
gameHandler.guildBankWithdrawItem(activeTab, item.slotId, 0xFF, 0);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::TextColored(qc, "%s", name.c_str());
|
||||||
|
if (item.stackCount > 1) ImGui::Text("Count: %u", item.stackCount);
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Money deposit/withdraw
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Money:");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(60);
|
||||||
|
ImGui::InputInt("##gbg", &guildBankMoneyInput_[0], 0); ImGui::SameLine(); ImGui::Text("g");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(40);
|
||||||
|
ImGui::InputInt("##gbs", &guildBankMoneyInput_[1], 0); ImGui::SameLine(); ImGui::Text("s");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(40);
|
||||||
|
ImGui::InputInt("##gbc", &guildBankMoneyInput_[2], 0); ImGui::SameLine(); ImGui::Text("c");
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Deposit")) {
|
||||||
|
uint32_t amount = guildBankMoneyInput_[0] * 10000 + guildBankMoneyInput_[1] * 100 + guildBankMoneyInput_[2];
|
||||||
|
if (amount > 0) gameHandler.depositGuildBankMoney(amount);
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Withdraw")) {
|
||||||
|
uint32_t amount = guildBankMoneyInput_[0] * 10000 + guildBankMoneyInput_[1] * 100 + guildBankMoneyInput_[2];
|
||||||
|
if (amount > 0) gameHandler.withdrawGuildBankMoney(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.withdrawAmount >= 0) {
|
||||||
|
ImGui::Text("Remaining withdrawals: %d", data.withdrawAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (!open) gameHandler.closeGuildBank();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Auction House Window
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void GameScreen::renderAuctionHouseWindow(game::GameHandler& gameHandler) {
|
||||||
|
if (!gameHandler.isAuctionHouseOpen()) return;
|
||||||
|
|
||||||
|
bool open = true;
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(650, 500), ImGuiCond_FirstUseEver);
|
||||||
|
if (!ImGui::Begin("Auction House", &open)) {
|
||||||
|
ImGui::End();
|
||||||
|
if (!open) gameHandler.closeAuctionHouse();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tab = gameHandler.getAuctionActiveTab();
|
||||||
|
|
||||||
|
// Tab buttons
|
||||||
|
const char* tabNames[] = {"Browse", "Bids", "Auctions"};
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (i > 0) ImGui::SameLine();
|
||||||
|
bool selected = (tab == i);
|
||||||
|
if (selected) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f));
|
||||||
|
if (ImGui::Button(tabNames[i], ImVec2(100, 0))) {
|
||||||
|
gameHandler.setAuctionActiveTab(i);
|
||||||
|
if (i == 1) gameHandler.auctionListBidderItems();
|
||||||
|
else if (i == 2) gameHandler.auctionListOwnerItems();
|
||||||
|
}
|
||||||
|
if (selected) ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (tab == 0) {
|
||||||
|
// Browse tab - Search filters
|
||||||
|
ImGui::SetNextItemWidth(200);
|
||||||
|
ImGui::InputText("Name", auctionSearchName_, sizeof(auctionSearchName_));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(50);
|
||||||
|
ImGui::InputInt("Min Lv", &auctionLevelMin_, 0);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(50);
|
||||||
|
ImGui::InputInt("Max Lv", &auctionLevelMax_, 0);
|
||||||
|
|
||||||
|
const char* qualities[] = {"All", "Poor", "Common", "Uncommon", "Rare", "Epic", "Legendary"};
|
||||||
|
ImGui::SetNextItemWidth(100);
|
||||||
|
ImGui::Combo("Quality", &auctionQuality_, qualities, 7);
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
float delay = gameHandler.getAuctionSearchDelay();
|
||||||
|
if (delay > 0.0f) {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
ImGui::Button("Search...");
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
} else {
|
||||||
|
if (ImGui::Button("Search")) {
|
||||||
|
uint32_t q = auctionQuality_ > 0 ? static_cast<uint32_t>(auctionQuality_ - 1) : 0xFFFFFFFF;
|
||||||
|
gameHandler.auctionSearch(auctionSearchName_,
|
||||||
|
static_cast<uint8_t>(auctionLevelMin_),
|
||||||
|
static_cast<uint8_t>(auctionLevelMax_),
|
||||||
|
q, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// Results table
|
||||||
|
const auto& results = gameHandler.getAuctionBrowseResults();
|
||||||
|
ImGui::Text("%zu results (of %u total)", results.auctions.size(), results.totalCount);
|
||||||
|
|
||||||
|
if (ImGui::BeginChild("AuctionResults", ImVec2(0, -80), true)) {
|
||||||
|
if (ImGui::BeginTable("AuctionTable", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) {
|
||||||
|
ImGui::TableSetupColumn("Item", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
ImGui::TableSetupColumn("Qty", ImGuiTableColumnFlags_WidthFixed, 40);
|
||||||
|
ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed, 60);
|
||||||
|
ImGui::TableSetupColumn("Bid", ImGuiTableColumnFlags_WidthFixed, 90);
|
||||||
|
ImGui::TableSetupColumn("Buyout", ImGuiTableColumnFlags_WidthFixed, 90);
|
||||||
|
ImGui::TableSetupColumn("##act", ImGuiTableColumnFlags_WidthFixed, 60);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < results.auctions.size(); i++) {
|
||||||
|
const auto& auction = results.auctions[i];
|
||||||
|
auto* info = gameHandler.getItemInfo(auction.itemEntry);
|
||||||
|
std::string name = info ? info->name : ("Item #" + std::to_string(auction.itemEntry));
|
||||||
|
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
||||||
|
ImVec4 qc = InventoryScreen::getQualityColor(quality);
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::TextColored(qc, "%s", name.c_str());
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::Text("%u", auction.stackCount);
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(2);
|
||||||
|
// Time left display
|
||||||
|
uint32_t mins = auction.timeLeftMs / 60000;
|
||||||
|
if (mins > 720) ImGui::Text("Long");
|
||||||
|
else if (mins > 120) ImGui::Text("Medium");
|
||||||
|
else ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), "Short");
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(3);
|
||||||
|
{
|
||||||
|
uint32_t bid = auction.currentBid > 0 ? auction.currentBid : auction.startBid;
|
||||||
|
ImGui::Text("%ug%us%uc", bid / 10000, (bid / 100) % 100, bid % 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(4);
|
||||||
|
if (auction.buyoutPrice > 0) {
|
||||||
|
ImGui::Text("%ug%us%uc", auction.buyoutPrice / 10000,
|
||||||
|
(auction.buyoutPrice / 100) % 100, auction.buyoutPrice % 100);
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("--");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(5);
|
||||||
|
ImGui::PushID(static_cast<int>(i) + 7000);
|
||||||
|
if (auction.buyoutPrice > 0 && ImGui::SmallButton("Buy")) {
|
||||||
|
gameHandler.auctionBuyout(auction.auctionId, auction.buyoutPrice);
|
||||||
|
}
|
||||||
|
if (auction.buyoutPrice > 0) ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton("Bid")) {
|
||||||
|
uint32_t bidAmt = auction.currentBid > 0
|
||||||
|
? auction.currentBid + auction.minBidIncrement
|
||||||
|
: auction.startBid;
|
||||||
|
gameHandler.auctionPlaceBid(auction.auctionId, bidAmt);
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
// Sell section
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Sell:");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("Bid:");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(50);
|
||||||
|
ImGui::InputInt("##sbg", &auctionSellBid_[0], 0); ImGui::SameLine(); ImGui::Text("g");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(35);
|
||||||
|
ImGui::InputInt("##sbs", &auctionSellBid_[1], 0); ImGui::SameLine(); ImGui::Text("s");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(35);
|
||||||
|
ImGui::InputInt("##sbc", &auctionSellBid_[2], 0); ImGui::SameLine(); ImGui::Text("c");
|
||||||
|
|
||||||
|
ImGui::Text(" "); ImGui::SameLine();
|
||||||
|
ImGui::Text("Buyout:");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(50);
|
||||||
|
ImGui::InputInt("##sbog", &auctionSellBuyout_[0], 0); ImGui::SameLine(); ImGui::Text("g");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(35);
|
||||||
|
ImGui::InputInt("##sbos", &auctionSellBuyout_[1], 0); ImGui::SameLine(); ImGui::Text("s");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(35);
|
||||||
|
ImGui::InputInt("##sboc", &auctionSellBuyout_[2], 0); ImGui::SameLine(); ImGui::Text("c");
|
||||||
|
|
||||||
|
const char* durations[] = {"12 hours", "24 hours", "48 hours"};
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(90);
|
||||||
|
ImGui::Combo("##dur", &auctionSellDuration_, durations, 3);
|
||||||
|
|
||||||
|
} else if (tab == 1) {
|
||||||
|
// Bids tab
|
||||||
|
const auto& results = gameHandler.getAuctionBidderResults();
|
||||||
|
ImGui::Text("Your Bids: %zu items", results.auctions.size());
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("BidTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
||||||
|
ImGui::TableSetupColumn("Item", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
ImGui::TableSetupColumn("Qty", ImGuiTableColumnFlags_WidthFixed, 40);
|
||||||
|
ImGui::TableSetupColumn("Your Bid", ImGuiTableColumnFlags_WidthFixed, 90);
|
||||||
|
ImGui::TableSetupColumn("Buyout", ImGuiTableColumnFlags_WidthFixed, 90);
|
||||||
|
ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed, 60);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto& a : results.auctions) {
|
||||||
|
auto* info = gameHandler.getItemInfo(a.itemEntry);
|
||||||
|
std::string name = info ? info->name : ("Item #" + std::to_string(a.itemEntry));
|
||||||
|
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::TextColored(InventoryScreen::getQualityColor(quality), "%s", name.c_str());
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::Text("%u", a.stackCount);
|
||||||
|
ImGui::TableSetColumnIndex(2);
|
||||||
|
ImGui::Text("%ug%us%uc", a.currentBid / 10000, (a.currentBid / 100) % 100, a.currentBid % 100);
|
||||||
|
ImGui::TableSetColumnIndex(3);
|
||||||
|
if (a.buyoutPrice > 0)
|
||||||
|
ImGui::Text("%ug%us%uc", a.buyoutPrice / 10000, (a.buyoutPrice / 100) % 100, a.buyoutPrice % 100);
|
||||||
|
else
|
||||||
|
ImGui::TextDisabled("--");
|
||||||
|
ImGui::TableSetColumnIndex(4);
|
||||||
|
uint32_t mins = a.timeLeftMs / 60000;
|
||||||
|
if (mins > 720) ImGui::Text("Long");
|
||||||
|
else if (mins > 120) ImGui::Text("Medium");
|
||||||
|
else ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), "Short");
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (tab == 2) {
|
||||||
|
// Auctions tab (your listings)
|
||||||
|
const auto& results = gameHandler.getAuctionOwnerResults();
|
||||||
|
ImGui::Text("Your Auctions: %zu items", results.auctions.size());
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("OwnerTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
|
||||||
|
ImGui::TableSetupColumn("Item", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
ImGui::TableSetupColumn("Qty", ImGuiTableColumnFlags_WidthFixed, 40);
|
||||||
|
ImGui::TableSetupColumn("Bid", ImGuiTableColumnFlags_WidthFixed, 90);
|
||||||
|
ImGui::TableSetupColumn("Buyout", ImGuiTableColumnFlags_WidthFixed, 90);
|
||||||
|
ImGui::TableSetupColumn("##cancel", ImGuiTableColumnFlags_WidthFixed, 60);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < results.auctions.size(); i++) {
|
||||||
|
const auto& a = results.auctions[i];
|
||||||
|
auto* info = gameHandler.getItemInfo(a.itemEntry);
|
||||||
|
std::string name = info ? info->name : ("Item #" + std::to_string(a.itemEntry));
|
||||||
|
game::ItemQuality quality = info ? static_cast<game::ItemQuality>(info->quality) : game::ItemQuality::COMMON;
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::TextColored(InventoryScreen::getQualityColor(quality), "%s", name.c_str());
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::Text("%u", a.stackCount);
|
||||||
|
ImGui::TableSetColumnIndex(2);
|
||||||
|
{
|
||||||
|
uint32_t bid = a.currentBid > 0 ? a.currentBid : a.startBid;
|
||||||
|
ImGui::Text("%ug%us%uc", bid / 10000, (bid / 100) % 100, bid % 100);
|
||||||
|
}
|
||||||
|
ImGui::TableSetColumnIndex(3);
|
||||||
|
if (a.buyoutPrice > 0)
|
||||||
|
ImGui::Text("%ug%us%uc", a.buyoutPrice / 10000, (a.buyoutPrice / 100) % 100, a.buyoutPrice % 100);
|
||||||
|
else
|
||||||
|
ImGui::TextDisabled("--");
|
||||||
|
ImGui::TableSetColumnIndex(4);
|
||||||
|
ImGui::PushID(static_cast<int>(i) + 8000);
|
||||||
|
if (ImGui::SmallButton("Cancel")) {
|
||||||
|
gameHandler.auctionCancelItem(a.auctionId);
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (!open) gameHandler.closeAuctionHouse();
|
||||||
|
}
|
||||||
|
|
||||||
}} // namespace wowee::ui
|
}} // namespace wowee::ui
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue