refactor(game): split GameHandler into domain handlers

Extract domain-specific logic from the monolithic GameHandler into
dedicated handler classes, each owning its own opcode registration,
state, and packet parsing:

- CombatHandler: combat, XP, kill, PvP, loot roll (~26 methods)
- SpellHandler: spells, auras, pet stable, talent (~3+ methods)
- SocialHandler: friends, guild, groups, BG, RAF, PvP AFK (~14+ methods)
- ChatHandler: chat messages, channels, GM tickets, server messages,
               defense/area-trigger messages (~7+ methods)
- InventoryHandler: items, trade, loot, mail, vendor, equipment sets,
                    read item (~3+ methods)
- QuestHandler: gossip, quests, completed quest response (~5+ methods)
- MovementHandler: movement, follow, transport (~2 methods)
- WardenHandler: Warden anti-cheat module

Each handler registers its own dispatch table entries via
registerOpcodes(DispatchTable&), called from
GameHandler::registerOpcodeHandlers(). GameHandler retains core
orchestration: auth/session handshake, update-object parsing,
opcode routing, and cross-handler coordination.

game_handler.cpp reduced from ~10,188 to ~9,432 lines.

Also add a POST_BUILD CMake step to symlink Data/ next to the
executable so expansion profiles and opcode tables are found at
runtime when running from build/bin/.
This commit is contained in:
Paul 2026-03-28 09:42:37 +03:00
parent 3762dceaa6
commit b2710258dc
21 changed files with 20771 additions and 17858 deletions

View file

@ -0,0 +1,401 @@
#pragma once
#include "game/world_packets.hpp"
#include "game/opcode_table.hpp"
#include "game/inventory.hpp"
#include "game/handler_types.hpp"
#include "network/packet.hpp"
#include <array>
#include <chrono>
#include <deque>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
class InventoryHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit InventoryHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// ---- Item text (books / readable items) ----
bool isItemTextOpen() const { return itemTextOpen_; }
const std::string& getItemText() const { return itemText_; }
void closeItemText() { itemTextOpen_ = false; }
void queryItemText(uint64_t itemGuid);
// ---- Trade ----
enum class TradeStatus : uint8_t {
None = 0, PendingIncoming, Open, Accepted, Complete
};
static constexpr int TRADE_SLOT_COUNT = 6;
struct TradeSlot {
uint32_t itemId = 0;
uint32_t displayId = 0;
uint32_t stackCount = 0;
uint64_t itemGuid = 0;
};
TradeStatus getTradeStatus() const { return tradeStatus_; }
bool hasPendingTradeRequest() const { return tradeStatus_ == TradeStatus::PendingIncoming; }
bool isTradeOpen() const { return tradeStatus_ == TradeStatus::Open; }
const std::string& getTradePeerName() const { return tradePeerName_; }
const std::array<TradeSlot, TRADE_SLOT_COUNT>& getMyTradeSlots() const { return myTradeSlots_; }
const std::array<TradeSlot, TRADE_SLOT_COUNT>& getPeerTradeSlots() const { return peerTradeSlots_; }
uint64_t getMyTradeGold() const { return myTradeGold_; }
uint64_t getPeerTradeGold() const { return peerTradeGold_; }
void acceptTradeRequest();
void declineTradeRequest();
void acceptTrade();
void cancelTrade();
void setTradeItem(uint8_t tradeSlot, uint8_t srcBag, uint8_t srcSlot);
void clearTradeItem(uint8_t tradeSlot);
void setTradeGold(uint64_t amount);
// ---- Loot ----
void lootTarget(uint64_t targetGuid);
void lootItem(uint8_t slotIndex);
void closeLoot();
bool isLootWindowOpen() const { return lootWindowOpen_; }
const LootResponseData& getCurrentLoot() const { return currentLoot_; }
void setAutoLoot(bool enabled) { autoLoot_ = enabled; }
bool isAutoLoot() const { return autoLoot_; }
void setAutoSellGrey(bool enabled) { autoSellGrey_ = enabled; }
bool isAutoSellGrey() const { return autoSellGrey_; }
void setAutoRepair(bool enabled) { autoRepair_ = enabled; }
bool isAutoRepair() const { return autoRepair_; }
// Master loot candidates (from SMSG_LOOT_MASTER_LIST)
const std::vector<uint64_t>& getMasterLootCandidates() const { return masterLootCandidates_; }
bool hasMasterLootCandidates() const { return !masterLootCandidates_.empty(); }
void lootMasterGive(uint8_t lootSlot, uint64_t targetGuid);
// Group loot roll (aliased from handler_types.hpp)
using LootRollEntry = game::LootRollEntry;
bool hasPendingLootRoll() const { return pendingLootRollActive_; }
const LootRollEntry& getPendingLootRoll() const { return pendingLootRoll_; }
void sendLootRoll(uint64_t objectGuid, uint32_t slot, uint8_t rollType);
// ---- Equipment Sets (aliased from handler_types.hpp) ----
using EquipmentSetInfo = game::EquipmentSetInfo;
const std::vector<EquipmentSetInfo>& getEquipmentSets() const { return equipmentSetInfo_; }
bool supportsEquipmentSets() const;
void useEquipmentSet(uint32_t setId);
void saveEquipmentSet(const std::string& name, const std::string& iconName = "INV_Misc_QuestionMark",
uint64_t existingGuid = 0, uint32_t setIndex = 0xFFFFFFFF);
void deleteEquipmentSet(uint64_t setGuid);
// ---- Vendor ----
struct BuybackItem {
uint64_t itemGuid = 0;
ItemDef item;
uint32_t count = 1;
};
void openVendor(uint64_t npcGuid);
void closeVendor();
void buyItem(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count);
void sellItem(uint64_t vendorGuid, uint64_t itemGuid, uint32_t count);
void sellItemBySlot(int backpackIndex);
void sellItemInBag(int bagIndex, int slotIndex);
void buyBackItem(uint32_t buybackSlot);
void repairItem(uint64_t vendorGuid, uint64_t itemGuid);
void repairAll(uint64_t vendorGuid, bool useGuildBank = false);
const std::deque<BuybackItem>& getBuybackItems() const { return buybackItems_; }
void autoEquipItemBySlot(int backpackIndex);
void autoEquipItemInBag(int bagIndex, int slotIndex);
void useItemBySlot(int backpackIndex);
void useItemInBag(int bagIndex, int slotIndex);
void openItemBySlot(int backpackIndex);
void openItemInBag(int bagIndex, int slotIndex);
void destroyItem(uint8_t bag, uint8_t slot, uint8_t count = 1);
void splitItem(uint8_t srcBag, uint8_t srcSlot, uint8_t count);
void swapContainerItems(uint8_t srcBag, uint8_t srcSlot, uint8_t dstBag, uint8_t dstSlot);
void swapBagSlots(int srcBagIndex, int dstBagIndex);
void unequipToBackpack(EquipSlot equipSlot);
void useItemById(uint32_t itemId);
bool isVendorWindowOpen() const { return vendorWindowOpen_; }
const ListInventoryData& getVendorItems() const { return currentVendorItems_; }
void setVendorCanRepair(bool v) { currentVendorItems_.canRepair = v; }
uint64_t getVendorGuid() const { return currentVendorItems_.vendorGuid; }
// ---- Mail ----
static constexpr int MAIL_MAX_ATTACHMENTS = 12;
struct MailAttachSlot {
uint64_t itemGuid = 0;
game::ItemDef item;
uint8_t srcBag = 0xFF;
uint8_t srcSlot = 0;
bool occupied() const { return itemGuid != 0; }
};
bool isMailboxOpen() const { return mailboxOpen_; }
const std::vector<MailMessage>& getMailInbox() const { return mailInbox_; }
int getSelectedMailIndex() const { return selectedMailIndex_; }
void setSelectedMailIndex(int idx) { selectedMailIndex_ = idx; }
bool isMailComposeOpen() const { return showMailCompose_; }
void openMailCompose() { showMailCompose_ = true; clearMailAttachments(); }
void closeMailCompose() { showMailCompose_ = false; clearMailAttachments(); }
bool hasNewMail() const { return hasNewMail_; }
void closeMailbox();
void sendMail(const std::string& recipient, const std::string& subject,
const std::string& body, uint64_t money, uint64_t cod = 0);
bool attachItemFromBackpack(int backpackIndex);
bool attachItemFromBag(int bagIndex, int slotIndex);
bool detachMailAttachment(int attachIndex);
void clearMailAttachments();
const std::array<MailAttachSlot, 12>& getMailAttachments() const { return mailAttachments_; }
int getMailAttachmentCount() const;
void mailTakeMoney(uint32_t mailId);
void mailTakeItem(uint32_t mailId, uint32_t itemGuidLow);
void mailDelete(uint32_t mailId);
void mailMarkAsRead(uint32_t mailId);
void refreshMailList();
// ---- Bank ----
void openBank(uint64_t guid);
void closeBank();
void buyBankSlot();
void depositItem(uint8_t srcBag, uint8_t srcSlot);
void withdrawItem(uint8_t srcBag, uint8_t srcSlot);
bool isBankOpen() const { return bankOpen_; }
uint64_t getBankerGuid() const { return bankerGuid_; }
int getEffectiveBankSlots() const { return effectiveBankSlots_; }
int getEffectiveBankBagSlots() const { return effectiveBankBagSlots_; }
// ---- 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 ----
struct TrainerTab {
std::string name;
std::vector<const TrainerSpell*> spells;
};
bool isTrainerWindowOpen() const { return trainerWindowOpen_; }
const TrainerListData& getTrainerSpells() const { return currentTrainerList_; }
void trainSpell(uint32_t spellId);
void closeTrainer();
const std::vector<TrainerTab>& getTrainerTabs() const { return trainerTabs_; }
void resetTradeState();
// ---- Methods moved from GameHandler ----
void initiateTrade(uint64_t targetGuid);
uint32_t getTempEnchantRemainingMs(uint32_t slot) const;
void addMoneyCopper(uint32_t amount);
// ---- Inventory field / rebuild methods (moved from GameHandler) ----
void queryItemInfo(uint32_t entry, uint64_t guid);
uint64_t resolveOnlineItemGuid(uint32_t itemId) const;
void detectInventorySlotBases(const std::map<uint16_t, uint32_t>& fields);
bool applyInventoryFields(const std::map<uint16_t, uint32_t>& fields);
void extractContainerFields(uint64_t containerGuid, const std::map<uint16_t, uint32_t>& fields);
void rebuildOnlineInventory();
void maybeDetectVisibleItemLayout();
void updateOtherPlayerVisibleItems(uint64_t guid, const std::map<uint16_t, uint32_t>& fields);
void emitOtherPlayerEquipment(uint64_t guid);
void emitAllOtherPlayerEquipment();
void handleItemQueryResponse(network::Packet& packet);
private:
// --- Packet handlers ---
void handleLootResponse(network::Packet& packet);
void handleLootReleaseResponse(network::Packet& packet);
void handleLootRemoved(network::Packet& packet);
void handleListInventory(network::Packet& packet);
void handleTrainerList(network::Packet& packet);
void handleItemTextQueryResponse(network::Packet& packet);
void handleTradeStatus(network::Packet& packet);
void handleTradeStatusExtended(network::Packet& packet);
void handleLootRoll(network::Packet& packet);
void handleLootRollWon(network::Packet& packet);
void handleShowBank(network::Packet& packet);
void handleBuyBankSlotResult(network::Packet& packet);
void handleGuildBankList(network::Packet& packet);
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);
void handleShowMailbox(network::Packet& packet);
void handleMailListResult(network::Packet& packet);
void handleSendMailResult(network::Packet& packet);
void handleReceivedMail(network::Packet& packet);
void handleQueryNextMailTime(network::Packet& packet);
void handleEquipmentSetList(network::Packet& packet);
void categorizeTrainerSpells();
void handleTrainerBuySucceeded(network::Packet& packet);
void handleTrainerBuyFailed(network::Packet& packet);
GameHandler& owner_;
// ---- Item text state ----
bool itemTextOpen_ = false;
std::string itemText_;
// ---- Trade state ----
TradeStatus tradeStatus_ = TradeStatus::None;
uint64_t tradePeerGuid_= 0;
std::string tradePeerName_;
std::array<TradeSlot, TRADE_SLOT_COUNT> myTradeSlots_{};
std::array<TradeSlot, TRADE_SLOT_COUNT> peerTradeSlots_{};
uint64_t myTradeGold_ = 0;
uint64_t peerTradeGold_ = 0;
// ---- Loot state ----
bool lootWindowOpen_ = false;
bool autoLoot_ = false;
bool autoSellGrey_ = false;
bool autoRepair_ = false;
LootResponseData currentLoot_;
std::vector<uint64_t> masterLootCandidates_;
// Group loot roll state
bool pendingLootRollActive_ = false;
LootRollEntry pendingLootRoll_;
struct LocalLootState {
LootResponseData data;
bool moneyTaken = false;
bool itemAutoLootSent = false;
};
std::unordered_map<uint64_t, LocalLootState> localLootState_;
struct PendingLootRetry {
uint64_t guid = 0;
float timer = 0.0f;
uint8_t remainingRetries = 0;
bool sendLoot = false;
};
std::vector<PendingLootRetry> pendingGameObjectLootRetries_;
struct PendingLootOpen {
uint64_t guid = 0;
float timer = 0.0f;
};
std::vector<PendingLootOpen> pendingGameObjectLootOpens_;
uint64_t lastInteractedGoGuid_ = 0;
uint64_t pendingLootMoneyGuid_ = 0;
uint32_t pendingLootMoneyAmount_ = 0;
float pendingLootMoneyNotifyTimer_ = 0.0f;
std::unordered_map<uint64_t, float> recentLootMoneyAnnounceCooldowns_;
// ---- Vendor state ----
bool vendorWindowOpen_ = false;
ListInventoryData currentVendorItems_;
std::deque<BuybackItem> buybackItems_;
std::unordered_map<uint64_t, BuybackItem> pendingSellToBuyback_;
int pendingBuybackSlot_ = -1;
uint32_t pendingBuybackWireSlot_ = 0;
uint32_t pendingBuyItemId_ = 0;
uint32_t pendingBuyItemSlot_ = 0;
// ---- Mail state ----
bool mailboxOpen_ = false;
uint64_t mailboxGuid_ = 0;
std::vector<MailMessage> mailInbox_;
int selectedMailIndex_ = -1;
bool showMailCompose_ = false;
bool hasNewMail_ = false;
std::array<MailAttachSlot, MAIL_MAX_ATTACHMENTS> mailAttachments_{};
// ---- Bank state ----
bool bankOpen_ = false;
uint64_t bankerGuid_ = 0;
std::array<uint64_t, 28> bankSlotGuids_{};
std::array<uint64_t, 7> bankBagSlotGuids_{};
int effectiveBankSlots_ = 28;
int effectiveBankBagSlots_ = 7;
// ---- Guild Bank state ----
bool guildBankOpen_ = false;
uint64_t guildBankerGuid_ = 0;
GuildBankData guildBankData_;
uint8_t guildBankActiveTab_ = 0;
// ---- Auction House state ----
bool auctionOpen_ = false;
uint64_t auctioneerGuid_ = 0;
uint32_t auctionHouseId_ = 0;
AuctionListResult auctionBrowseResults_;
AuctionListResult auctionOwnerResults_;
AuctionListResult auctionBidderResults_;
int auctionActiveTab_ = 0;
float auctionSearchDelayTimer_ = 0.0f;
struct AuctionSearchParams {
std::string name;
uint8_t levelMin = 0, levelMax = 0;
uint32_t quality = 0xFFFFFFFF;
uint32_t itemClass = 0xFFFFFFFF;
uint32_t itemSubClass = 0xFFFFFFFF;
uint32_t invTypeMask = 0;
uint8_t usableOnly = 0;
uint32_t offset = 0;
};
AuctionSearchParams lastAuctionSearch_;
enum class AuctionResultTarget { BROWSE, OWNER, BIDDER };
AuctionResultTarget pendingAuctionTarget_ = AuctionResultTarget::BROWSE;
// ---- Trainer state ----
bool trainerWindowOpen_ = false;
TrainerListData currentTrainerList_;
std::vector<TrainerTab> trainerTabs_;
// ---- Equipment set state ----
struct EquipmentSet {
uint64_t setGuid = 0;
uint32_t setId = 0;
std::string name;
std::string iconName;
uint32_t ignoreSlotMask = 0;
std::array<uint64_t, 19> itemGuids{};
};
std::vector<EquipmentSet> equipmentSets_;
std::string pendingSaveSetName_;
std::string pendingSaveSetIcon_;
std::vector<EquipmentSetInfo> equipmentSetInfo_;
};
} // namespace game
} // namespace wowee