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,73 @@
#pragma once
#include "game/world_packets.hpp"
#include "game/opcode_table.hpp"
#include "game/handler_types.hpp"
#include "network/packet.hpp"
#include <deque>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
class ChatHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit ChatHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// --- Public API (delegated from GameHandler) ---
void sendChatMessage(ChatType type, const std::string& message, const std::string& target = "");
void sendTextEmote(uint32_t textEmoteId, uint64_t targetGuid = 0);
void joinChannel(const std::string& channelName, const std::string& password = "");
void leaveChannel(const std::string& channelName);
std::string getChannelByIndex(int index) const;
int getChannelIndex(const std::string& channelName) const;
const std::vector<std::string>& getJoinedChannels() const { return joinedChannels_; }
void autoJoinDefaultChannels();
void addLocalChatMessage(const MessageChatData& msg);
void addSystemChatMessage(const std::string& message);
void toggleAfk(const std::string& message);
void toggleDnd(const std::string& message);
void replyToLastWhisper(const std::string& message);
// ---- Methods moved from GameHandler ----
void submitGmTicket(const std::string& text);
void handleMotd(network::Packet& packet);
void handleNotification(network::Packet& packet);
// --- State accessors ---
std::deque<MessageChatData>& getChatHistory() { return chatHistory_; }
const std::deque<MessageChatData>& getChatHistory() const { return chatHistory_; }
size_t getMaxChatHistory() const { return maxChatHistory_; }
void setMaxChatHistory(size_t n) { maxChatHistory_ = n; }
// Chat auto-join settings (aliased from handler_types.hpp)
using ChatAutoJoin = game::ChatAutoJoin;
ChatAutoJoin chatAutoJoin;
private:
// --- Packet handlers ---
void handleMessageChat(network::Packet& packet);
void handleTextEmote(network::Packet& packet);
void handleChannelNotify(network::Packet& packet);
void handleChannelList(network::Packet& packet);
GameHandler& owner_;
// --- State ---
std::deque<MessageChatData> chatHistory_;
size_t maxChatHistory_ = 100;
std::vector<std::string> joinedChannels_;
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,191 @@
#pragma once
#include "game/world_packets.hpp"
#include "game/opcode_table.hpp"
#include "game/spell_defines.hpp"
#include "network/packet.hpp"
#include <chrono>
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
class Entity;
class CombatHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit CombatHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// --- Public API (delegated from GameHandler) ---
void startAutoAttack(uint64_t targetGuid);
void stopAutoAttack();
bool isAutoAttacking() const { return autoAttacking_; }
bool hasAutoAttackIntent() const { return autoAttackRequested_; }
bool isInCombat() const { return autoAttacking_ || !hostileAttackers_.empty(); }
bool isInCombatWith(uint64_t guid) const {
return guid != 0 &&
((autoAttacking_ && autoAttackTarget_ == guid) ||
(hostileAttackers_.count(guid) > 0));
}
uint64_t getAutoAttackTargetGuid() const { return autoAttackTarget_; }
bool isAggressiveTowardPlayer(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
uint64_t getLastMeleeSwingMs() const { return lastMeleeSwingMs_; }
// Floating combat text
const std::vector<CombatTextEntry>& getCombatText() const { return combatText_; }
void updateCombatText(float deltaTime);
// Combat log (persistent rolling history)
const std::deque<CombatLogEntry>& getCombatLog() const { return combatLog_; }
void clearCombatLog() { combatLog_.clear(); }
// Threat
struct ThreatEntry {
uint64_t victimGuid = 0;
uint32_t threat = 0;
};
const std::vector<ThreatEntry>* getThreatList(uint64_t unitGuid) const {
auto it = threatLists_.find(unitGuid);
return (it != threatLists_.end()) ? &it->second : nullptr;
}
// Hostile attacker tracking
bool isHostileAttacker(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
void clearHostileAttackers() { hostileAttackers_.clear(); }
// Forced faction reactions
const std::unordered_map<uint32_t, uint8_t>& getForcedReactions() const { return forcedReactions_; }
// Auto-attack timing state (read by GameHandler::update for retry/resend logic)
bool& autoAttackOutOfRangeRef() { return autoAttackOutOfRange_; }
float& autoAttackOutOfRangeTimeRef() { return autoAttackOutOfRangeTime_; }
float& autoAttackRangeWarnCooldownRef() { return autoAttackRangeWarnCooldown_; }
float& autoAttackResendTimerRef() { return autoAttackResendTimer_; }
float& autoAttackFacingSyncTimerRef() { return autoAttackFacingSyncTimer_; }
bool& autoAttackRetryPendingRef() { return autoAttackRetryPending_; }
// Combat text creation (used by other handlers, e.g. spell handler for periodic damage)
void addCombatText(CombatTextEntry::Type type, int32_t amount, uint32_t spellId,
bool isPlayerSource, uint8_t powerType = 0,
uint64_t srcGuid = 0, uint64_t dstGuid = 0);
// Spellsteal dedup (used by aura update handler)
bool shouldLogSpellstealAura(uint64_t casterGuid, uint64_t victimGuid, uint32_t spellId);
// Called from GameHandler::update() each frame
void updateAutoAttack(float deltaTime);
// --- Targeting ---
void setTarget(uint64_t guid);
void clearTarget();
std::shared_ptr<Entity> getTarget() const;
void setFocus(uint64_t guid);
void clearFocus();
std::shared_ptr<Entity> getFocus() const;
void setMouseoverGuid(uint64_t guid);
void targetLastTarget();
void targetEnemy(bool reverse);
void targetFriend(bool reverse);
void tabTarget(float playerX, float playerY, float playerZ);
void assistTarget();
// --- PvP ---
void togglePvp();
// --- Death / Resurrection ---
void releaseSpirit();
bool canReclaimCorpse() const;
float getCorpseReclaimDelaySec() const;
void reclaimCorpse();
void useSelfRes();
void activateSpiritHealer(uint64_t npcGuid);
void acceptResurrect();
void declineResurrect();
// --- XP ---
static uint32_t killXp(uint32_t playerLevel, uint32_t victimLevel);
void handleXpGain(network::Packet& packet);
// State management (for resets, entity cleanup)
void resetAllCombatState();
void removeHostileAttacker(uint64_t guid);
void clearCombatText();
void removeCombatTextForGuid(uint64_t guid);
private:
// --- Packet handlers ---
void handleAttackStart(network::Packet& packet);
void handleAttackStop(network::Packet& packet);
void handleAttackerStateUpdate(network::Packet& packet);
void handleSpellDamageLog(network::Packet& packet);
void handleSpellHealLog(network::Packet& packet);
void handleSetForcedReactions(network::Packet& packet);
void handleHealthUpdate(network::Packet& packet);
void handlePowerUpdate(network::Packet& packet);
void handleUpdateComboPoints(network::Packet& packet);
void handlePvpCredit(network::Packet& packet);
void handleProcResist(network::Packet& packet);
void handleEnvironmentalDamageLog(network::Packet& packet);
void handleSpellDamageShield(network::Packet& packet);
void handleSpellOrDamageImmune(network::Packet& packet);
void handleResistLog(network::Packet& packet);
void handlePetTameFailure(network::Packet& packet);
void handlePetActionFeedback(network::Packet& packet);
void handlePetCastFailed(network::Packet& packet);
void handlePetBroken(network::Packet& packet);
void handlePetLearnedSpell(network::Packet& packet);
void handlePetUnlearnedSpell(network::Packet& packet);
void handlePetMode(network::Packet& packet);
void handleResurrectFailed(network::Packet& packet);
void autoTargetAttacker(uint64_t attackerGuid);
GameHandler& owner_;
// --- Combat state ---
bool autoAttacking_ = false;
bool autoAttackRequested_ = false;
bool autoAttackRetryPending_ = false;
uint64_t autoAttackTarget_ = 0;
bool autoAttackOutOfRange_ = false;
float autoAttackOutOfRangeTime_ = 0.0f;
float autoAttackRangeWarnCooldown_ = 0.0f;
float autoAttackResendTimer_ = 0.0f;
float autoAttackFacingSyncTimer_ = 0.0f;
std::unordered_set<uint64_t> hostileAttackers_;
std::vector<CombatTextEntry> combatText_;
static constexpr size_t MAX_COMBAT_LOG = 500;
std::deque<CombatLogEntry> combatLog_;
struct RecentSpellstealLogEntry {
uint64_t casterGuid = 0;
uint64_t victimGuid = 0;
uint32_t spellId = 0;
std::chrono::steady_clock::time_point timestamp{};
};
static constexpr size_t MAX_RECENT_SPELLSTEAL_LOGS = 32;
std::deque<RecentSpellstealLogEntry> recentSpellstealLogs_;
uint64_t lastMeleeSwingMs_ = 0;
// unitGuid → sorted threat list (descending by threat value)
std::unordered_map<uint64_t, std::vector<ThreatEntry>> threatLists_;
// Forced faction reactions
std::unordered_map<uint32_t, uint8_t> forcedReactions_;
};
} // namespace game
} // namespace wowee

View file

@ -7,6 +7,10 @@
#include "game/inventory.hpp"
#include "game/spell_defines.hpp"
#include "game/group_defines.hpp"
#include "game/handler_types.hpp"
#include "game/combat_handler.hpp"
#include "game/spell_handler.hpp"
#include "game/quest_handler.hpp"
#include "network/packet.hpp"
#include <glm/glm.hpp>
#include <memory>
@ -31,6 +35,11 @@ namespace wowee::game {
class WardenModule;
class WardenModuleManager;
class PacketParsers;
class ChatHandler;
class MovementHandler;
class InventoryHandler;
class SocialHandler;
class WardenHandler;
}
namespace wowee {
@ -115,25 +124,9 @@ using WorldConnectFailureCallback = std::function<void(const std::string& reason
*/
class GameHandler {
public:
// Talent data structures (must be public for use in templates)
struct TalentEntry {
uint32_t talentId = 0;
uint32_t tabId = 0; // Which talent tree
uint8_t row = 0; // Tier (0-10)
uint8_t column = 0; // Column (0-3)
uint32_t rankSpells[5] = {}; // Spell IDs for ranks 1-5
uint32_t prereqTalent[3] = {}; // Required talents
uint8_t prereqRank[3] = {}; // Required ranks
uint8_t maxRank = 0; // Number of ranks (1-5)
};
struct TalentTabEntry {
uint32_t tabId = 0;
std::string name;
uint32_t classMask = 0; // Which classes can use this tab
uint8_t orderIndex = 0; // Display order (0-2)
std::string backgroundFile; // Texture path
};
// Talent data structures (aliased from handler_types.hpp)
using TalentEntry = game::TalentEntry;
using TalentTabEntry = game::TalentTabEntry;
GameHandler();
~GameHandler();
@ -262,19 +255,14 @@ public:
void sendTextEmote(uint32_t textEmoteId, uint64_t targetGuid = 0);
void joinChannel(const std::string& channelName, const std::string& password = "");
void leaveChannel(const std::string& channelName);
const std::vector<std::string>& getJoinedChannels() const { return joinedChannels_; }
const std::vector<std::string>& getJoinedChannels() const;
std::string getChannelByIndex(int index) const;
int getChannelIndex(const std::string& channelName) const;
// Chat auto-join settings (set by UI before autoJoinDefaultChannels)
struct ChatAutoJoin {
bool general = true;
bool trade = true;
bool localDefense = true;
bool lfg = true;
bool local = true;
};
// Chat auto-join settings (aliased from handler_types.hpp)
using ChatAutoJoin = game::ChatAutoJoin;
ChatAutoJoin chatAutoJoin;
void autoJoinDefaultChannels();
// Chat bubble callback: (senderGuid, message, isYell)
using ChatBubbleCallback = std::function<void(uint64_t, const std::string&, bool)>;
@ -327,8 +315,8 @@ public:
* @param maxMessages Maximum number of messages to return (0 = all)
* @return Vector of chat messages
*/
const std::deque<MessageChatData>& getChatHistory() const { return chatHistory; }
void clearChatHistory() { chatHistory.clear(); }
const std::deque<MessageChatData>& getChatHistory() const;
void clearChatHistory();
/**
* Add a locally-generated chat message (e.g., emote feedback)
@ -430,27 +418,8 @@ public:
// Inspection
void inspectTarget();
struct InspectArenaTeam {
uint32_t teamId = 0;
uint8_t type = 0; // bracket size: 2, 3, or 5
uint32_t weekGames = 0;
uint32_t weekWins = 0;
uint32_t seasonGames = 0;
uint32_t seasonWins = 0;
std::string name;
uint32_t personalRating = 0;
};
struct InspectResult {
uint64_t guid = 0;
std::string playerName;
uint32_t totalTalents = 0;
uint32_t unspentTalents = 0;
uint8_t talentGroups = 0;
uint8_t activeTalentGroup = 0;
std::array<uint32_t, 19> itemEntries{}; // 0=head…18=ranged
std::array<uint16_t, 19> enchantIds{}; // permanent enchant per slot (0 = none)
std::vector<InspectArenaTeam> arenaTeams; // from MSG_INSPECT_ARENA_TEAMS (WotLK)
};
using InspectArenaTeam = game::InspectArenaTeam;
using InspectResult = game::InspectResult;
const InspectResult* getInspectResult() const {
return inspectResult_.guid ? &inspectResult_ : nullptr;
}
@ -462,15 +431,7 @@ public:
uint32_t getTotalTimePlayed() const { return totalTimePlayed_; }
uint32_t getLevelTimePlayed() const { return levelTimePlayed_; }
// Who results (structured, from last SMSG_WHO response)
struct WhoEntry {
std::string name;
std::string guildName;
uint32_t level = 0;
uint32_t classId = 0;
uint32_t raceId = 0;
uint32_t zoneId = 0;
};
using WhoEntry = game::WhoEntry;
const std::vector<WhoEntry>& getWhoResults() const { return whoResults_; }
uint32_t getWhoOnlineCount() const { return whoOnlineCount_; }
std::string getWhoAreaName(uint32_t zoneId) const { return getAreaName(zoneId); }
@ -486,28 +447,11 @@ public:
// Random roll
void randomRoll(uint32_t minRoll = 1, uint32_t maxRoll = 100);
// Battleground queue slot (public so UI can read invite details)
struct BgQueueSlot {
uint32_t queueSlot = 0;
uint32_t bgTypeId = 0;
uint8_t arenaType = 0;
uint32_t statusId = 0; // 0=none, 1=wait_queue, 2=wait_join, 3=in_progress
uint32_t inviteTimeout = 80;
uint32_t avgWaitTimeSec = 0; // server-estimated average wait (STATUS_WAIT_QUEUE)
uint32_t timeInQueueSec = 0; // time already spent in queue (STATUS_WAIT_QUEUE)
std::chrono::steady_clock::time_point inviteReceivedTime{};
std::string bgName; // human-readable BG/arena name
};
// Battleground queue slot (aliased from handler_types.hpp)
using BgQueueSlot = game::BgQueueSlot;
// Available BG list (populated by SMSG_BATTLEFIELD_LIST)
struct AvailableBgInfo {
uint32_t bgTypeId = 0;
bool isRegistered = false;
bool isHoliday = false;
uint32_t minLevel = 0;
uint32_t maxLevel = 0;
std::vector<uint32_t> instanceIds;
};
// Available BG list (aliased from handler_types.hpp)
using AvailableBgInfo = game::AvailableBgInfo;
// Battleground
bool hasPendingBgInvite() const;
@ -516,42 +460,17 @@ public:
const std::array<BgQueueSlot, 3>& getBgQueues() const { return bgQueues_; }
const std::vector<AvailableBgInfo>& getAvailableBgs() const { return availableBgs_; }
// BG scoreboard (MSG_PVP_LOG_DATA)
struct BgPlayerScore {
uint64_t guid = 0;
std::string name;
uint8_t team = 0; // 0=Horde, 1=Alliance
uint32_t killingBlows = 0;
uint32_t deaths = 0;
uint32_t honorableKills = 0;
uint32_t bonusHonor = 0;
std::vector<std::pair<std::string, uint32_t>> bgStats; // BG-specific fields
};
struct ArenaTeamScore {
std::string teamName;
uint32_t ratingChange = 0; // signed delta packed as uint32
uint32_t newRating = 0;
};
struct BgScoreboardData {
std::vector<BgPlayerScore> players;
bool hasWinner = false;
uint8_t winner = 0; // 0=Horde, 1=Alliance
bool isArena = false;
// Arena-only fields (valid when isArena=true)
ArenaTeamScore arenaTeams[2]; // team 0 = first, team 1 = second
};
// BG scoreboard (aliased from handler_types.hpp)
using BgPlayerScore = game::BgPlayerScore;
using ArenaTeamScore = game::ArenaTeamScore;
using BgScoreboardData = game::BgScoreboardData;
void requestPvpLog();
const BgScoreboardData* getBgScoreboard() const {
return bgScoreboard_.players.empty() ? nullptr : &bgScoreboard_;
}
// BG flag carrier / important player positions (MSG_BATTLEGROUND_PLAYER_POSITIONS)
struct BgPlayerPosition {
uint64_t guid = 0;
float wowX = 0.0f; // canonical WoW X (north)
float wowY = 0.0f; // canonical WoW Y (west)
int group = 0; // 0 = first list (usually ally flag carriers), 1 = second list
};
// BG flag carrier positions (aliased from handler_types.hpp)
using BgPlayerPosition = game::BgPlayerPosition;
const std::vector<BgPlayerPosition>& getBgPlayerPositions() const { return bgPlayerPositions_; }
// Network latency (milliseconds, updated each PONG response)
@ -656,19 +575,8 @@ public:
uint64_t getPetitionNpcGuid() const { return petitionNpcGuid_; }
// Petition signatures (guild charter signing flow)
struct PetitionSignature {
uint64_t playerGuid = 0;
std::string playerName; // resolved later or empty
};
struct PetitionInfo {
uint64_t petitionGuid = 0;
uint64_t ownerGuid = 0;
std::string guildName;
uint32_t signatureCount = 0;
uint32_t signaturesRequired = 9; // guild default; arena teams differ
std::vector<PetitionSignature> signatures;
bool showUI = false;
};
using PetitionSignature = game::PetitionSignature;
using PetitionInfo = game::PetitionInfo;
const PetitionInfo& getPetitionInfo() const { return petitionInfo_; }
bool hasPetitionSignaturesUI() const { return petitionInfo_.showUI; }
void clearPetitionSignaturesUI() { petitionInfo_.showUI = false; }
@ -682,11 +590,7 @@ public:
// Returns the guildId for a player entity (from PLAYER_GUILDID update field).
uint32_t getEntityGuildId(uint64_t guid) const;
// Ready check
struct ReadyCheckResult {
std::string name;
bool ready = false;
};
using ReadyCheckResult = game::ReadyCheckResult;
void initiateReadyCheck();
void respondToReadyCheck(bool ready);
bool hasPendingReadyCheck() const { return pendingReadyCheck_; }
@ -720,6 +624,8 @@ public:
void initiateTrade(uint64_t targetGuid);
void reportPlayer(uint64_t targetGuid, const std::string& reason);
void stopCasting();
void resetCastState(); // force-clear all cast/craft/queue state without sending packets
void clearUnitCaches(); // clear per-unit cast states and aura caches
// ---- Phase 1: Name queries ----
void queryPlayerName(uint64_t guid);
@ -753,28 +659,26 @@ public:
return (it != creatureInfoCache.end()) ? it->second.family : 0;
}
// ---- Phase 2: Combat ----
// ---- Phase 2: Combat (delegated to CombatHandler) ----
void startAutoAttack(uint64_t targetGuid);
void stopAutoAttack();
bool isAutoAttacking() const { return autoAttacking; }
bool hasAutoAttackIntent() const { return autoAttackRequested_; }
bool isInCombat() const { return autoAttacking || !hostileAttackers_.empty(); }
bool isInCombatWith(uint64_t guid) const {
return guid != 0 &&
((autoAttacking && autoAttackTarget == guid) ||
(hostileAttackers_.count(guid) > 0));
}
uint64_t getAutoAttackTargetGuid() const { return autoAttackTarget; }
bool isAggressiveTowardPlayer(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
bool isAutoAttacking() const;
bool hasAutoAttackIntent() const;
bool isInCombat() const;
bool isInCombatWith(uint64_t guid) const;
uint64_t getAutoAttackTargetGuid() const;
bool isAggressiveTowardPlayer(uint64_t guid) const;
// Timestamp (ms since epoch) of the most recent player melee auto-attack.
// Zero if no swing has occurred this session.
uint64_t getLastMeleeSwingMs() const { return lastMeleeSwingMs_; }
const std::vector<CombatTextEntry>& getCombatText() const { return combatText; }
uint64_t getLastMeleeSwingMs() const;
const std::vector<CombatTextEntry>& getCombatText() const;
void clearCombatText();
void updateCombatText(float deltaTime);
void clearHostileAttackers();
// Combat log (persistent rolling history, max MAX_COMBAT_LOG entries)
const std::deque<CombatLogEntry>& getCombatLog() const { return combatLog_; }
void clearCombatLog() { combatLog_.clear(); }
const std::deque<CombatLogEntry>& getCombatLog() const;
void clearCombatLog();
// Area trigger messages (SMSG_AREA_TRIGGER_MESSAGE) — drained by UI each frame
bool hasAreaTriggerMsg() const { return !areaTriggerMsgs_.empty(); }
@ -786,19 +690,9 @@ public:
}
// Threat
struct ThreatEntry {
uint64_t victimGuid = 0;
uint32_t threat = 0;
};
// Returns the current threat list for a given unit GUID (from last SMSG_THREAT_UPDATE)
const std::vector<ThreatEntry>* getThreatList(uint64_t unitGuid) const {
auto it = threatLists_.find(unitGuid);
return (it != threatLists_.end()) ? &it->second : nullptr;
}
// Returns the threat list for the player's current target, or nullptr
const std::vector<ThreatEntry>* getTargetThreatList() const {
return targetGuid ? getThreatList(targetGuid) : nullptr;
}
using ThreatEntry = CombatHandler::ThreatEntry;
const std::vector<ThreatEntry>* getThreatList(uint64_t unitGuid) const;
const std::vector<ThreatEntry>* getTargetThreatList() const;
// ---- Phase 3: Spells ----
void castSpell(uint32_t spellId, uint64_t targetGuid = 0);
@ -833,14 +727,13 @@ public:
void sendPetAction(uint32_t action, uint64_t targetGuid = 0);
// Toggle autocast for a pet spell via CMSG_PET_SPELL_AUTOCAST
void togglePetSpellAutocast(uint32_t spellId);
const std::unordered_set<uint32_t>& getKnownSpells() const { return knownSpells; }
const std::unordered_set<uint32_t>& getKnownSpells() const {
static const std::unordered_set<uint32_t> empty;
return spellHandler_ ? spellHandler_->getKnownSpells() : empty;
}
// Spell book tabs — groups known spells by class skill line for Lua API
struct SpellBookTab {
std::string name;
std::string texture; // icon path
std::vector<uint32_t> spellIds; // spells in this tab
};
using SpellBookTab = SpellHandler::SpellBookTab;
const std::vector<SpellBookTab>& getSpellBookTabs();
// ---- Pet Stable ----
@ -887,15 +780,15 @@ public:
minimapPings_.end());
}
bool isCasting() const { return casting; }
bool isChanneling() const { return casting && castIsChannel; }
bool isCasting() const { return spellHandler_ ? spellHandler_->isCasting() : false; }
bool isChanneling() const { return spellHandler_ ? spellHandler_->isChanneling() : false; }
bool isGameObjectInteractionCasting() const {
return casting && currentCastSpellId == 0 && pendingGameObjectInteractGuid_ != 0;
return spellHandler_ ? spellHandler_->isGameObjectInteractionCasting() : false;
}
uint32_t getCurrentCastSpellId() const { return currentCastSpellId; }
float getCastProgress() const { return castTimeTotal > 0 ? (castTimeTotal - castTimeRemaining) / castTimeTotal : 0.0f; }
float getCastTimeRemaining() const { return castTimeRemaining; }
float getCastTimeTotal() const { return castTimeTotal; }
uint32_t getCurrentCastSpellId() const { return spellHandler_ ? spellHandler_->getCurrentCastSpellId() : 0; }
float getCastProgress() const { return spellHandler_ ? spellHandler_->getCastProgress() : 0.0f; }
float getCastTimeRemaining() const { return spellHandler_ ? spellHandler_->getCastTimeRemaining() : 0.0f; }
float getCastTimeTotal() const { return spellHandler_ ? spellHandler_->getCastTimeTotal() : 0.0f; }
// Repeat-craft queue
void startCraftQueue(uint32_t spellId, int count);
@ -907,39 +800,19 @@ public:
uint32_t getQueuedSpellId() const { return queuedSpellId_; }
void cancelQueuedSpell() { queuedSpellId_ = 0; queuedSpellTarget_ = 0; }
// Unit cast state (tracked per GUID for target frame + boss frames)
struct UnitCastState {
bool casting = false;
bool isChannel = false; ///< true for channels (MSG_CHANNEL_START), false for casts (SMSG_SPELL_START)
uint32_t spellId = 0;
float timeRemaining = 0.0f;
float timeTotal = 0.0f;
bool interruptible = true; ///< false when SPELL_ATTR_EX_NOT_INTERRUPTIBLE is set
};
// Returns cast state for any unit by GUID (empty/non-casting if not found)
// Unit cast state (aliased from handler_types.hpp)
using UnitCastState = game::UnitCastState;
// Returns cast state for any unit by GUID (delegates to SpellHandler)
const UnitCastState* getUnitCastState(uint64_t guid) const {
auto it = unitCastStates_.find(guid);
return (it != unitCastStates_.end() && it->second.casting) ? &it->second : nullptr;
if (spellHandler_) return spellHandler_->getUnitCastState(guid);
return nullptr;
}
// Convenience helpers for the current target
bool isTargetCasting() const { return getUnitCastState(targetGuid) != nullptr; }
uint32_t getTargetCastSpellId() const {
auto* s = getUnitCastState(targetGuid);
return s ? s->spellId : 0;
}
float getTargetCastProgress() const {
auto* s = getUnitCastState(targetGuid);
return (s && s->timeTotal > 0.0f)
? (s->timeTotal - s->timeRemaining) / s->timeTotal : 0.0f;
}
float getTargetCastTimeRemaining() const {
auto* s = getUnitCastState(targetGuid);
return s ? s->timeRemaining : 0.0f;
}
bool isTargetCastInterruptible() const {
auto* s = getUnitCastState(targetGuid);
return s ? s->interruptible : true;
}
bool isTargetCasting() const { return spellHandler_ ? spellHandler_->isTargetCasting() : false; }
uint32_t getTargetCastSpellId() const { return spellHandler_ ? spellHandler_->getTargetCastSpellId() : 0; }
float getTargetCastProgress() const { return spellHandler_ ? spellHandler_->getTargetCastProgress() : 0.0f; }
float getTargetCastTimeRemaining() const { return spellHandler_ ? spellHandler_->getTargetCastTimeRemaining() : 0.0f; }
bool isTargetCastInterruptible() const { return spellHandler_ ? spellHandler_->isTargetCastInterruptible() : true; }
// Talents
uint8_t getActiveTalentSpec() const { return activeTalentSpec_; }
@ -998,13 +871,21 @@ public:
void loadCharacterConfig();
static std::string getCharacterConfigDir();
// Auras
const std::vector<AuraSlot>& getPlayerAuras() const { return playerAuras; }
const std::vector<AuraSlot>& getTargetAuras() const { return targetAuras; }
// Auras — delegate to SpellHandler as canonical authority
const std::vector<AuraSlot>& getPlayerAuras() const {
if (spellHandler_) return spellHandler_->getPlayerAuras();
static const std::vector<AuraSlot> empty;
return empty;
}
const std::vector<AuraSlot>& getTargetAuras() const {
if (spellHandler_) return spellHandler_->getTargetAuras();
static const std::vector<AuraSlot> empty;
return empty;
}
// Per-unit aura cache (populated for party members and any unit we receive updates for)
const std::vector<AuraSlot>* getUnitAuras(uint64_t guid) const {
auto it = unitAurasCache_.find(guid);
return (it != unitAurasCache_.end()) ? &it->second : nullptr;
if (spellHandler_) return spellHandler_->getUnitAuras(guid);
return nullptr;
}
// Completed quests (populated from SMSG_QUERY_QUESTS_COMPLETED_RESPONSE)
@ -1257,7 +1138,10 @@ public:
// Cooldowns
float getSpellCooldown(uint32_t spellId) const;
const std::unordered_map<uint32_t, float>& getSpellCooldowns() const { return spellCooldowns; }
const std::unordered_map<uint32_t, float>& getSpellCooldowns() const {
static const std::unordered_map<uint32_t, float> empty;
return spellHandler_ ? spellHandler_->getSpellCooldowns() : empty;
}
// Player GUID
uint64_t getPlayerGuid() const { return playerGuid; }
@ -1448,14 +1332,8 @@ public:
return rem > 0.0f ? rem : 0.0f;
}
// ---- Instance lockouts ----
struct InstanceLockout {
uint32_t mapId = 0;
uint32_t difficulty = 0; // 0=normal,1=heroic/10man,2=25man,3=25man heroic
uint64_t resetTime = 0; // Unix timestamp of instance reset
bool locked = false;
bool extended = false;
};
// Instance lockouts (aliased from handler_types.hpp)
using InstanceLockout = game::InstanceLockout;
const std::vector<InstanceLockout>& getInstanceLockouts() const { return instanceLockouts_; }
// Boss encounter unit tracking (SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT)
@ -1483,16 +1361,8 @@ public:
void setRaidMark(uint64_t guid, uint8_t icon);
// ---- LFG / Dungeon Finder ----
enum class LfgState : uint8_t {
None = 0,
RoleCheck = 1,
Queued = 2,
Proposal = 3,
Boot = 4,
InDungeon = 5,
FinishedDungeon= 6,
RaidBrowser = 7,
};
// LFG state (aliased from handler_types.hpp)
using LfgState = game::LfgState;
// roles bitmask: 0x02=tank, 0x04=healer, 0x08=dps; pass LFGDungeonEntry ID
void lfgJoin(uint32_t dungeonId, uint8_t roles);
@ -1517,36 +1387,14 @@ public:
const std::string& getLfgBootTargetName() const { return lfgBootTargetName_; }
const std::string& getLfgBootReason() const { return lfgBootReason_; }
// ---- Arena Team Stats ----
struct ArenaTeamStats {
uint32_t teamId = 0;
uint32_t rating = 0;
uint32_t weekGames = 0;
uint32_t weekWins = 0;
uint32_t seasonGames = 0;
uint32_t seasonWins = 0;
uint32_t rank = 0;
std::string teamName;
uint32_t teamType = 0; // 2, 3, or 5
};
// Arena team stats (aliased from handler_types.hpp)
using ArenaTeamStats = game::ArenaTeamStats;
const std::vector<ArenaTeamStats>& getArenaTeamStats() const { return arenaTeamStats_; }
void requestArenaTeamRoster(uint32_t teamId);
// ---- Arena Team Roster ----
struct ArenaTeamMember {
uint64_t guid = 0;
std::string name;
bool online = false;
uint32_t weekGames = 0;
uint32_t weekWins = 0;
uint32_t seasonGames = 0;
uint32_t seasonWins = 0;
uint32_t personalRating = 0;
};
struct ArenaTeamRoster {
uint32_t teamId = 0;
std::vector<ArenaTeamMember> members;
};
// Arena team roster (aliased from handler_types.hpp)
using ArenaTeamMember = game::ArenaTeamMember;
using ArenaTeamRoster = game::ArenaTeamRoster;
// Returns roster for the given teamId, or nullptr if not yet received
const ArenaTeamRoster* getArenaTeamRoster(uint32_t teamId) const {
for (const auto& r : arenaTeamRosters_) {
@ -1574,36 +1422,15 @@ public:
bool hasMasterLootCandidates() const { return !masterLootCandidates_.empty(); }
void lootMasterGive(uint8_t lootSlot, uint64_t targetGuid);
// Group loot roll
struct LootRollEntry {
uint64_t objectGuid = 0;
uint32_t slot = 0;
uint32_t itemId = 0;
std::string itemName;
uint8_t itemQuality = 0;
uint32_t rollCountdownMs = 60000; // Duration of roll window in ms
uint8_t voteMask = 0xFF; // Bitmask: 0x01=pass, 0x02=need, 0x04=greed, 0x08=disenchant
std::chrono::steady_clock::time_point rollStartedAt{};
struct PlayerRollResult {
std::string playerName;
uint8_t rollNum = 0;
uint8_t rollType = 0; // 0=need,1=greed,2=disenchant,96=pass
};
std::vector<PlayerRollResult> playerRolls; // live roll results from group members
};
// 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);
// rollType: 0=need, 1=greed, 2=disenchant, 96=pass
// Equipment Sets (WotLK): saved gear loadouts
struct EquipmentSetInfo {
uint64_t setGuid = 0;
uint32_t setId = 0;
std::string name;
std::string iconName;
};
// 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);
@ -1638,14 +1465,8 @@ public:
}
const QuestDetailsData& getQuestDetails() const { return currentQuestDetails; }
// Gossip / quest map POI markers (SMSG_GOSSIP_POI)
struct GossipPoi {
float x = 0.0f; // WoW canonical X (north)
float y = 0.0f; // WoW canonical Y (west)
uint32_t icon = 0; // POI icon type
uint32_t data = 0;
std::string name;
};
// Gossip POI (aliased from handler_types.hpp)
using GossipPoi = game::GossipPoi;
const std::vector<GossipPoi>& getGossipPois() const { return gossipPois_; }
void clearGossipPois() { gossipPois_.clear(); }
@ -1661,37 +1482,7 @@ public:
void closeQuestOfferReward();
// Quest log
struct QuestLogEntry {
uint32_t questId = 0;
std::string title;
std::string objectives;
bool complete = false;
// Objective kill counts: npcOrGoEntry -> (current, required)
std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> killCounts;
// Quest item progress: itemId -> current count
std::unordered_map<uint32_t, uint32_t> itemCounts;
// Server-authoritative quest item requirements from REQUEST_ITEMS
std::unordered_map<uint32_t, uint32_t> requiredItemCounts;
// Structured kill objectives parsed from SMSG_QUEST_QUERY_RESPONSE.
// Index 0-3 map to the server's objective slot order (packed into update fields).
// npcOrGoId != 0 => entity objective (kill NPC or interact with GO).
struct KillObjective {
int32_t npcOrGoId = 0; // negative = game-object entry
uint32_t required = 0;
};
std::array<KillObjective, 4> killObjectives{}; // zeroed by default
// Required item objectives parsed from SMSG_QUEST_QUERY_RESPONSE.
// itemId != 0 => collect items of that type.
struct ItemObjective {
uint32_t itemId = 0;
uint32_t required = 0;
};
std::array<ItemObjective, 6> itemObjectives{}; // zeroed by default
// Reward data parsed from SMSG_QUEST_QUERY_RESPONSE
int32_t rewardMoney = 0; // copper; positive=reward, negative=cost
std::array<QuestRewardItem, 4> rewardItems{}; // guaranteed reward items
std::array<QuestRewardItem, 6> rewardChoiceItems{}; // player picks one of these
};
using QuestLogEntry = QuestHandler::QuestLogEntry;
const std::vector<QuestLogEntry>& getQuestLog() const { return questLog_; }
int getSelectedQuestLogIndex() const { return selectedQuestLogIndex_; }
void setSelectedQuestLogIndex(int idx) { selectedQuestLogIndex_ = idx; }
@ -1879,7 +1670,7 @@ public:
void setWatchedFactionId(uint32_t factionId);
uint32_t getLastContactListMask() const { return lastContactListMask_; }
uint32_t getLastContactListCount() const { return lastContactListCount_; }
bool isServerMovementAllowed() const { return serverMovementAllowed_; }
bool isServerMovementAllowed() const;
// Quest giver status (! and ? markers)
QuestGiverStatus getQuestGiverStatus(uint64_t guid) const {
@ -2052,7 +1843,7 @@ public:
void setOpenLfgCallback(OpenLfgCallback cb) { openLfgCallback_ = std::move(cb); }
bool isMounted() const { return currentMountDisplayId_ != 0; }
bool isHostileAttacker(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
bool isHostileAttacker(uint64_t guid) const;
bool isHostileFactionPublic(uint32_t factionTemplateId) const { return isHostileFaction(factionTemplateId); }
float getServerRunSpeed() const { return serverRunSpeed_; }
float getServerWalkSpeed() const { return serverWalkSpeed_; }
@ -2337,7 +2128,16 @@ public:
void resetDbcCaches();
private:
void autoTargetAttacker(uint64_t attackerGuid);
friend class ChatHandler;
friend class MovementHandler;
friend class CombatHandler;
friend class SpellHandler;
friend class InventoryHandler;
friend class SocialHandler;
friend class QuestHandler;
friend class WardenHandler;
// Dead: autoTargetAttacker moved to CombatHandler
/**
* Handle incoming packet from world server
@ -2397,7 +2197,6 @@ private:
* Handle SMSG_WARDEN_DATA gate packet from server.
* We do not implement anti-cheat exchange for third-party realms.
*/
void handleWardenData(network::Packet& packet);
/**
* Handle SMSG_ACCOUNT_DATA_TIMES from server
@ -2432,14 +2231,6 @@ private:
*/
void handleDestroyObject(network::Packet& packet);
/**
* Handle SMSG_MESSAGECHAT from server
*/
void handleMessageChat(network::Packet& packet);
void handleTextEmote(network::Packet& packet);
void handleChannelNotify(network::Packet& packet);
void autoJoinDefaultChannels();
// ---- Phase 1 handlers ----
void handleNameQueryResponse(network::Packet& packet);
void handleCreatureQueryResponse(network::Packet& packet);
@ -2447,7 +2238,6 @@ private:
void handleGameObjectPageText(network::Packet& packet);
void handlePageTextQueryResponse(network::Packet& packet);
void handleItemQueryResponse(network::Packet& packet);
void handleInspectResults(network::Packet& packet);
void queryItemInfo(uint32_t entry, uint64_t guid);
void rebuildOnlineInventory();
void maybeDetectVisibleItemLayout();
@ -2459,56 +2249,22 @@ private:
void extractContainerFields(uint64_t containerGuid, const std::map<uint16_t, uint32_t>& fields);
uint64_t resolveOnlineItemGuid(uint32_t itemId) const;
// ---- Phase 2 handlers ----
void handleAttackStart(network::Packet& packet);
void handleAttackStop(network::Packet& packet);
void handleAttackerStateUpdate(network::Packet& packet);
void handleSpellDamageLog(network::Packet& packet);
void handleSpellHealLog(network::Packet& packet);
// ---- Phase 2 handlers (dead — dispatched via CombatHandler) ----
// handleAttackStart, handleAttackStop, handleAttackerStateUpdate,
// handleSpellDamageLog, handleSpellHealLog removed
// ---- Equipment set handler ----
void handleEquipmentSetList(network::Packet& packet);
void handleUpdateAuraDuration(uint8_t slot, uint32_t durationMs);
void handleSetForcedReactions(network::Packet& packet);
// handleSetForcedReactions — dispatched via CombatHandler
// ---- Phase 3 handlers ----
void handleInitialSpells(network::Packet& packet);
void handleCastFailed(network::Packet& packet);
void handleSpellStart(network::Packet& packet);
void handleSpellGo(network::Packet& packet);
void handleSpellCooldown(network::Packet& packet);
void handleCooldownEvent(network::Packet& packet);
void handleAchievementEarned(network::Packet& packet);
void handleAuraUpdate(network::Packet& packet, bool isAll);
void handleLearnedSpell(network::Packet& packet);
void handleSupercededSpell(network::Packet& packet);
void handleRemovedSpell(network::Packet& packet);
void handleUnlearnSpells(network::Packet& packet);
// ---- Talent handlers ----
void handleTalentsInfo(network::Packet& packet);
// ---- Phase 4 handlers ----
void handleGroupInvite(network::Packet& packet);
void handleGroupDecline(network::Packet& packet);
void handleGroupList(network::Packet& packet);
void handleGroupUninvite(network::Packet& packet);
void handlePartyCommandResult(network::Packet& packet);
void handlePartyMemberStats(network::Packet& packet, bool isFull);
// ---- Guild handlers ----
void handleGuildInfo(network::Packet& packet);
void handleGuildRoster(network::Packet& packet);
void handleGuildQueryResponse(network::Packet& packet);
void handleGuildEvent(network::Packet& packet);
void handleGuildInvite(network::Packet& packet);
void handleGuildCommandResult(network::Packet& packet);
void handlePetitionShowlist(network::Packet& packet);
void handlePetitionQueryResponse(network::Packet& packet);
void handlePetitionShowSignatures(network::Packet& packet);
void handlePetitionSignResults(network::Packet& packet);
void handlePetSpells(network::Packet& packet);
void handleTurnInPetitionResults(network::Packet& packet);
// ---- Character creation handler ----
void handleCharCreateResponse(network::Packet& packet);
@ -2517,25 +2273,10 @@ private:
void handleXpGain(network::Packet& packet);
// ---- Creature movement handler ----
void handleMonsterMove(network::Packet& packet);
void handleCompressedMoves(network::Packet& packet);
void handleMonsterMoveTransport(network::Packet& packet);
// ---- Other player movement (MSG_MOVE_* from server) ----
void handleOtherPlayerMovement(network::Packet& packet);
void handleMoveSetSpeed(network::Packet& packet);
// ---- Phase 5 handlers ----
void handleLootResponse(network::Packet& packet);
void handleLootReleaseResponse(network::Packet& packet);
void handleLootRemoved(network::Packet& packet);
void handleGossipMessage(network::Packet& packet);
void handleQuestgiverQuestList(network::Packet& packet);
void handleGossipComplete(network::Packet& packet);
void handleQuestPoiQueryResponse(network::Packet& packet);
void handleQuestDetails(network::Packet& packet);
void handleQuestRequestItems(network::Packet& packet);
void handleQuestOfferReward(network::Packet& packet);
void clearPendingQuestAccept(uint32_t questId);
void triggerQuestAcceptResync(uint32_t questId, uint64_t npcGuid, const char* reason);
bool hasQuestInLog(uint32_t questId) const;
@ -2546,101 +2287,43 @@ private:
int findQuestLogSlotIndexFromServer(uint32_t questId) const;
void addQuestToLocalLogIfMissing(uint32_t questId, const std::string& title, const std::string& objectives);
bool resyncQuestLogFromServerSlots(bool forceQueryMetadata);
void handleListInventory(network::Packet& packet);
void addMoneyCopper(uint32_t amount);
// ---- Teleport handler ----
void handleTeleportAck(network::Packet& packet);
void handleNewWorld(network::Packet& packet);
// ---- Movement ACK handlers ----
void handleForceRunSpeedChange(network::Packet& packet);
void handleForceSpeedChange(network::Packet& packet, const char* name, Opcode ackOpcode, float* speedStorage);
void handleForceMoveRootState(network::Packet& packet, bool rooted);
void handleForceMoveFlagChange(network::Packet& packet, const char* name, Opcode ackOpcode, uint32_t flag, bool set);
void handleMoveSetCollisionHeight(network::Packet& packet);
void handleMoveKnockBack(network::Packet& packet);
// ---- Area trigger detection ----
void loadAreaTriggerDbc();
void checkAreaTriggers();
// ---- Instance lockout handler ----
void handleRaidInstanceInfo(network::Packet& packet);
void handleItemTextQueryResponse(network::Packet& packet);
void handleQuestConfirmAccept(network::Packet& packet);
void handleSummonRequest(network::Packet& packet);
void handleTradeStatus(network::Packet& packet);
void handleTradeStatusExtended(network::Packet& packet);
void resetTradeState();
void handleDuelRequested(network::Packet& packet);
void handleDuelComplete(network::Packet& packet);
void handleDuelWinner(network::Packet& packet);
void handleLootRoll(network::Packet& packet);
void handleLootRollWon(network::Packet& packet);
// ---- LFG / Dungeon Finder handlers ----
void handleLfgJoinResult(network::Packet& packet);
void handleLfgQueueStatus(network::Packet& packet);
void handleLfgProposalUpdate(network::Packet& packet);
void handleLfgRoleCheckUpdate(network::Packet& packet);
void handleLfgUpdatePlayer(network::Packet& packet);
void handleLfgPlayerReward(network::Packet& packet);
void handleLfgBootProposalUpdate(network::Packet& packet);
void handleLfgTeleportDenied(network::Packet& packet);
// ---- Arena / Battleground handlers ----
void handleBattlefieldStatus(network::Packet& packet);
void handleInstanceDifficulty(network::Packet& packet);
void handleArenaTeamCommandResult(network::Packet& packet);
void handleArenaTeamQueryResponse(network::Packet& packet);
void handleArenaTeamRoster(network::Packet& packet);
void handleArenaTeamInvite(network::Packet& packet);
void handleArenaTeamEvent(network::Packet& packet);
void handleArenaTeamStats(network::Packet& packet);
void handleArenaError(network::Packet& packet);
void handlePvpLogData(network::Packet& packet);
// ---- Bank handlers ----
void handleShowBank(network::Packet& packet);
void handleBuyBankSlotResult(network::Packet& packet);
// ---- Guild Bank handlers ----
void handleGuildBankList(network::Packet& packet);
// ---- Auction House handlers ----
void handleAuctionHello(network::Packet& packet);
void handleAuctionListResult(network::Packet& packet);
void handleAuctionOwnerListResult(network::Packet& packet);
void handleAuctionBidderListResult(network::Packet& packet);
void handleAuctionCommandResult(network::Packet& packet);
// ---- Mail handlers ----
void handleShowMailbox(network::Packet& packet);
void handleMailListResult(network::Packet& packet);
void handleSendMailResult(network::Packet& packet);
void handleReceivedMail(network::Packet& packet);
void handleQueryNextMailTime(network::Packet& packet);
// ---- Taxi handlers ----
void handleShowTaxiNodes(network::Packet& packet);
void handleActivateTaxiReply(network::Packet& packet);
void loadTaxiDbc();
// ---- Server info handlers ----
void handleQueryTimeResponse(network::Packet& packet);
void handlePlayedTime(network::Packet& packet);
void handleWho(network::Packet& packet);
// ---- Social handlers ----
void handleFriendList(network::Packet& packet); // Classic SMSG_FRIEND_LIST
void handleContactList(network::Packet& packet); // WotLK SMSG_CONTACT_LIST (full parse)
void handleFriendStatus(network::Packet& packet);
void handleRandomRoll(network::Packet& packet);
// ---- Logout handlers ----
void handleLogoutResponse(network::Packet& packet);
void handleLogoutComplete(network::Packet& packet);
void addCombatText(CombatTextEntry::Type type, int32_t amount, uint32_t spellId, bool isPlayerSource, uint8_t powerType = 0,
uint64_t srcGuid = 0, uint64_t dstGuid = 0);
@ -2677,6 +2360,16 @@ private:
float localOrientation);
void clearTransportAttachment(uint64_t childGuid);
// Domain handlers — each manages a specific concern extracted from GameHandler
std::unique_ptr<ChatHandler> chatHandler_;
std::unique_ptr<MovementHandler> movementHandler_;
std::unique_ptr<CombatHandler> combatHandler_;
std::unique_ptr<SpellHandler> spellHandler_;
std::unique_ptr<InventoryHandler> inventoryHandler_;
std::unique_ptr<SocialHandler> socialHandler_;
std::unique_ptr<QuestHandler> questHandler_;
std::unique_ptr<WardenHandler> wardenHandler_;
// Opcode dispatch table — built once in registerOpcodeHandlers(), called by handlePacket()
using PacketHandler = std::function<void(network::Packet&)>;
std::unordered_map<LogicalOpcode, PacketHandler> dispatchTable_;
@ -2736,10 +2429,7 @@ private:
// Entity tracking
EntityManager entityManager; // Manages all entities in view
// Chat
std::deque<MessageChatData> chatHistory; // Recent chat messages
size_t maxChatHistory = 100; // Maximum chat messages to keep
std::vector<std::string> joinedChannels_; // Active channel memberships
// Chat (state lives in ChatHandler; callbacks remain here for cross-domain access)
ChatBubbleCallback chatBubbleCallback_;
AddonChatCallback addonChatCallback_;
AddonEventCallback addonEventCallback_;
@ -2885,32 +2575,9 @@ private:
std::unordered_set<uint64_t> pendingAutoInspect_;
float inspectRateLimit_ = 0.0f;
// ---- Phase 2: Combat ----
bool autoAttacking = false;
bool autoAttackRequested_ = false; // local intent (CMSG_ATTACKSWING sent)
bool autoAttackRetryPending_ = false; // one-shot retry after local start or server stop
uint64_t autoAttackTarget = 0;
bool autoAttackOutOfRange_ = false;
float autoAttackOutOfRangeTime_ = 0.0f;
float autoAttackRangeWarnCooldown_ = 0.0f;
float autoAttackResendTimer_ = 0.0f; // Re-send CMSG_ATTACKSWING every ~1s while attacking
float autoAttackFacingSyncTimer_ = 0.0f; // Periodic facing sync while meleeing
std::unordered_set<uint64_t> hostileAttackers_;
// ---- Phase 2: Combat (state moved to CombatHandler) ----
bool wasCombat_ = false; // Previous frame combat state for PLAYER_REGEN edge detection
std::vector<CombatTextEntry> combatText;
static constexpr size_t MAX_COMBAT_LOG = 500;
struct RecentSpellstealLogEntry {
uint64_t casterGuid = 0;
uint64_t victimGuid = 0;
uint32_t spellId = 0;
std::chrono::steady_clock::time_point timestamp{};
};
static constexpr size_t MAX_RECENT_SPELLSTEAL_LOGS = 32;
std::deque<CombatLogEntry> combatLog_;
std::deque<RecentSpellstealLogEntry> recentSpellstealLogs_;
std::deque<std::string> areaTriggerMsgs_;
// unitGuid → sorted threat list (descending by threat value)
std::unordered_map<uint64_t, std::vector<ThreatEntry>> threatLists_;
// ---- Phase 3: Spells ----
WorldEntryCallback worldEntryCallback_;
@ -3022,7 +2689,6 @@ private:
// ---- Available battleground list (SMSG_BATTLEFIELD_LIST) ----
std::vector<AvailableBgInfo> availableBgs_;
void handleBattlefieldList(network::Packet& packet);
// Instance difficulty
uint32_t instanceDifficulty_ = 0;
@ -3293,11 +2959,8 @@ private:
uint32_t knownTaxiMask_[12] = {}; // Track previously known nodes for discovery alerts
bool taxiMaskInitialized_ = false; // First SMSG_SHOWTAXINODES seeds mask without alerts
std::unordered_map<uint32_t, uint32_t> taxiCostMap_; // destNodeId -> total cost in copper
void buildTaxiCostMap();
void applyTaxiMountForCurrentNode();
uint32_t nextMovementTimestampMs();
void sanitizeMovementForTaxi();
void startClientTaxiPath(const std::vector<uint32_t>& pathNodes);
void updateClientTaxi(float deltaTime);
// Mail
@ -3397,7 +3060,6 @@ private:
// Per-player achievement data from SMSG_RESPOND_INSPECT_ACHIEVEMENTS
// Key: inspected player's GUID; value: set of earned achievement IDs
std::unordered_map<uint64_t, std::unordered_set<uint32_t>> inspectedPlayerAchievements_;
void handleRespondInspectAchievements(network::Packet& packet);
// Area name cache (lazy-loaded from WorldMapArea.dbc; maps AreaTable ID → display name)
mutable std::unordered_map<uint32_t, std::string> areaNameCache_;
@ -3416,7 +3078,6 @@ private:
void loadLfgDungeonDbc() const;
std::string getLfgDungeonName(uint32_t dungeonId) const;
std::vector<TrainerTab> trainerTabs_;
void handleTrainerList(network::Packet& packet);
void loadSpellNameCache() const;
void preloadDBCCaches() const;
void categorizeTrainerSpells();
@ -3466,7 +3127,6 @@ private:
std::vector<WardenCREntry> wardenCREntries_;
// Module-specific check type opcodes [9]: MEM, PAGE_A, PAGE_B, MPQ, LUA, DRIVER, TIMING, PROC, MODULE
uint8_t wardenCheckOpcodes_[9] = {};
bool loadWardenCRFile(const std::string& moduleHashHex);
// Async Warden response: avoids 5-second main-loop stalls from PAGE_A/PAGE_B code pattern searches
std::future<std::vector<uint8_t>> wardenPendingEncrypted_; // encrypted response bytes
@ -3530,7 +3190,7 @@ private:
AppearanceChangedCallback appearanceChangedCallback_;
GhostStateCallback ghostStateCallback_;
MeleeSwingCallback meleeSwingCallback_;
uint64_t lastMeleeSwingMs_ = 0; // system_clock ms at last player auto-attack swing
// lastMeleeSwingMs_ moved to CombatHandler
SpellCastAnimCallback spellCastAnimCallback_;
SpellCastFailedCallback spellCastFailedCallback_;
UnitAnimHintCallback unitAnimHintCallback_;
@ -3615,8 +3275,7 @@ private:
std::string pendingSaveSetIcon_;
std::vector<EquipmentSetInfo> equipmentSetInfo_; // public-facing copy
// ---- Forced faction reactions (SMSG_SET_FORCED_REACTIONS) ----
std::unordered_map<uint32_t, uint8_t> forcedReactions_; // factionId -> reaction tier
// forcedReactions_ moved to CombatHandler
// ---- Server-triggered audio ----
PlayMusicCallback playMusicCallback_;

View file

@ -0,0 +1,27 @@
#pragma once
#include "game/expansion_profile.hpp"
#include "core/application.hpp"
namespace wowee {
namespace game {
inline bool isActiveExpansion(const char* expansionId) {
auto& app = core::Application::getInstance();
auto* registry = app.getExpansionRegistry();
if (!registry) return false;
auto* profile = registry->getActive();
if (!profile) return false;
return profile->id == expansionId;
}
inline bool isClassicLikeExpansion() {
return isActiveExpansion("classic") || isActiveExpansion("turtle");
}
inline bool isPreWotlk() {
return isClassicLikeExpansion() || isActiveExpansion("tbc");
}
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,270 @@
#pragma once
/**
* handler_types.hpp Shared struct definitions used by GameHandler and domain handlers.
*
* These types were previously duplicated across GameHandler, SpellHandler, SocialHandler,
* ChatHandler, QuestHandler, and InventoryHandler. Now they live here at namespace scope,
* and each class provides a `using` alias for backward compatibility
* (e.g. GameHandler::TalentEntry == game::TalentEntry).
*/
#include <array>
#include <chrono>
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace game {
// ---- Talent DBC data ----
struct TalentEntry {
uint32_t talentId = 0;
uint32_t tabId = 0;
uint8_t row = 0;
uint8_t column = 0;
uint32_t rankSpells[5] = {};
uint32_t prereqTalent[3] = {};
uint8_t prereqRank[3] = {};
uint8_t maxRank = 0;
};
struct TalentTabEntry {
uint32_t tabId = 0;
std::string name;
uint32_t classMask = 0;
uint8_t orderIndex = 0;
std::string backgroundFile;
};
// ---- Spell / cast state ----
struct UnitCastState {
bool casting = false;
bool isChannel = false;
uint32_t spellId = 0;
float timeRemaining = 0.0f;
float timeTotal = 0.0f;
bool interruptible = true;
};
// ---- Equipment sets (WotLK) ----
struct EquipmentSetInfo {
uint64_t setGuid = 0;
uint32_t setId = 0;
std::string name;
std::string iconName;
};
// ---- Inspection ----
struct InspectArenaTeam {
uint32_t teamId = 0;
uint8_t type = 0;
uint32_t weekGames = 0;
uint32_t weekWins = 0;
uint32_t seasonGames = 0;
uint32_t seasonWins = 0;
std::string name;
uint32_t personalRating = 0;
};
struct InspectResult {
uint64_t guid = 0;
std::string playerName;
uint32_t totalTalents = 0;
uint32_t unspentTalents = 0;
uint8_t talentGroups = 0;
uint8_t activeTalentGroup = 0;
std::array<uint32_t, 19> itemEntries{};
std::array<uint16_t, 19> enchantIds{};
std::vector<InspectArenaTeam> arenaTeams;
};
// ---- Who ----
struct WhoEntry {
std::string name;
std::string guildName;
uint32_t level = 0;
uint32_t classId = 0;
uint32_t raceId = 0;
uint32_t zoneId = 0;
};
// ---- Battleground ----
struct BgQueueSlot {
uint32_t queueSlot = 0;
uint32_t bgTypeId = 0;
uint8_t arenaType = 0;
uint32_t statusId = 0;
uint32_t inviteTimeout = 80;
uint32_t avgWaitTimeSec = 0;
uint32_t timeInQueueSec = 0;
std::chrono::steady_clock::time_point inviteReceivedTime{};
std::string bgName;
};
struct AvailableBgInfo {
uint32_t bgTypeId = 0;
bool isRegistered = false;
bool isHoliday = false;
uint32_t minLevel = 0;
uint32_t maxLevel = 0;
std::vector<uint32_t> instanceIds;
};
struct BgPlayerScore {
uint64_t guid = 0;
std::string name;
uint8_t team = 0;
uint32_t killingBlows = 0;
uint32_t deaths = 0;
uint32_t honorableKills = 0;
uint32_t bonusHonor = 0;
std::vector<std::pair<std::string, uint32_t>> bgStats;
};
struct ArenaTeamScore {
std::string teamName;
uint32_t ratingChange = 0;
uint32_t newRating = 0;
};
struct BgScoreboardData {
std::vector<BgPlayerScore> players;
bool hasWinner = false;
uint8_t winner = 0;
bool isArena = false;
ArenaTeamScore arenaTeams[2];
};
struct BgPlayerPosition {
uint64_t guid = 0;
float wowX = 0.0f;
float wowY = 0.0f;
int group = 0;
};
// ---- Guild petition ----
struct PetitionSignature {
uint64_t playerGuid = 0;
std::string playerName;
};
struct PetitionInfo {
uint64_t petitionGuid = 0;
uint64_t ownerGuid = 0;
std::string guildName;
uint32_t signatureCount = 0;
uint32_t signaturesRequired = 9;
std::vector<PetitionSignature> signatures;
bool showUI = false;
};
// ---- Ready check ----
struct ReadyCheckResult {
std::string name;
bool ready = false;
};
// ---- Chat ----
struct ChatAutoJoin {
bool general = true;
bool trade = true;
bool localDefense = true;
bool lfg = true;
bool local = true;
};
// ---- Quest / gossip ----
struct GossipPoi {
float x = 0.0f;
float y = 0.0f;
uint32_t icon = 0;
uint32_t data = 0;
std::string name;
};
// ---- Instance lockouts ----
struct InstanceLockout {
uint32_t mapId = 0;
uint32_t difficulty = 0;
uint64_t resetTime = 0;
bool locked = false;
bool extended = false;
};
// ---- LFG ----
enum class LfgState : uint8_t {
None = 0,
RoleCheck = 1,
Queued = 2,
Proposal = 3,
Boot = 4,
InDungeon = 5,
FinishedDungeon= 6,
RaidBrowser = 7,
};
// ---- Arena teams ----
struct ArenaTeamStats {
uint32_t teamId = 0;
uint32_t rating = 0;
uint32_t weekGames = 0;
uint32_t weekWins = 0;
uint32_t seasonGames = 0;
uint32_t seasonWins = 0;
uint32_t rank = 0;
std::string teamName;
uint32_t teamType = 0;
};
struct ArenaTeamMember {
uint64_t guid = 0;
std::string name;
bool online = false;
uint32_t weekGames = 0;
uint32_t weekWins = 0;
uint32_t seasonGames = 0;
uint32_t seasonWins = 0;
uint32_t personalRating = 0;
};
struct ArenaTeamRoster {
uint32_t teamId = 0;
std::vector<ArenaTeamMember> members;
};
// ---- Group loot roll ----
struct LootRollEntry {
uint64_t objectGuid = 0;
uint32_t slot = 0;
uint32_t itemId = 0;
std::string itemName;
uint8_t itemQuality = 0;
uint32_t rollCountdownMs = 60000;
uint8_t voteMask = 0xFF;
std::chrono::steady_clock::time_point rollStartedAt{};
struct PlayerRollResult {
std::string playerName;
uint8_t rollNum = 0;
uint8_t rollType = 0;
};
std::vector<PlayerRollResult> playerRolls;
};
} // namespace game
} // namespace wowee

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

View file

@ -0,0 +1,272 @@
#pragma once
#include "game/world_packets.hpp"
#include "game/opcode_table.hpp"
#include "network/packet.hpp"
#include <glm/glm.hpp>
#include <chrono>
#include <deque>
#include <functional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
class MovementHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit MovementHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// --- Public API (delegated from GameHandler) ---
void sendMovement(Opcode opcode);
void setPosition(float x, float y, float z);
void setOrientation(float orientation);
void setMovementPitch(float radians) { movementInfo.pitch = radians; }
void dismount();
// Follow target (moved from GameHandler)
void followTarget();
void cancelFollow();
// Area trigger detection
void loadAreaTriggerDbc();
void checkAreaTriggers();
// Transport attachment
void setTransportAttachment(uint64_t childGuid, ObjectType type, uint64_t transportGuid,
const glm::vec3& localOffset, bool hasLocalOrientation,
float localOrientation);
void clearTransportAttachment(uint64_t childGuid);
void updateAttachedTransportChildren(float deltaTime);
// Movement info accessors
const MovementInfo& getMovementInfo() const { return movementInfo; }
MovementInfo& getMovementInfoMut() { return movementInfo; }
// Speed accessors
float getServerRunSpeed() const { return serverRunSpeed_; }
float getServerWalkSpeed() const { return serverWalkSpeed_; }
float getServerSwimSpeed() const { return serverSwimSpeed_; }
float getServerSwimBackSpeed() const { return serverSwimBackSpeed_; }
float getServerFlightSpeed() const { return serverFlightSpeed_; }
float getServerFlightBackSpeed() const { return serverFlightBackSpeed_; }
float getServerRunBackSpeed() const { return serverRunBackSpeed_; }
float getServerTurnRate() const { return serverTurnRate_; }
// Movement flag queries
bool isPlayerRooted() const {
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::ROOT)) != 0;
}
bool isGravityDisabled() const {
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::LEVITATING)) != 0;
}
bool isFeatherFalling() const {
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::FEATHER_FALL)) != 0;
}
bool isWaterWalking() const {
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::WATER_WALK)) != 0;
}
bool isPlayerFlying() const {
const uint32_t flyMask = static_cast<uint32_t>(MovementFlags::CAN_FLY) |
static_cast<uint32_t>(MovementFlags::FLYING);
return (movementInfo.flags & flyMask) == flyMask;
}
bool isHovering() const {
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::HOVER)) != 0;
}
bool isSwimming() const {
return (movementInfo.flags & static_cast<uint32_t>(MovementFlags::SWIMMING)) != 0;
}
// Taxi / Flight Paths
bool isTaxiWindowOpen() const { return taxiWindowOpen_; }
void closeTaxi();
void activateTaxi(uint32_t destNodeId);
bool isOnTaxiFlight() const { return onTaxiFlight_; }
bool isTaxiMountActive() const { return taxiMountActive_; }
bool isTaxiActivationPending() const { return taxiActivatePending_; }
void forceClearTaxiAndMovementState();
const std::string& getTaxiDestName() const { return taxiDestName_; }
const ShowTaxiNodesData& getTaxiData() const { return currentTaxiData_; }
uint32_t getTaxiCurrentNode() const { return currentTaxiData_.nearestNode; }
struct TaxiNode {
uint32_t id = 0;
uint32_t mapId = 0;
float x = 0, y = 0, z = 0;
std::string name;
uint32_t mountDisplayIdAlliance = 0;
uint32_t mountDisplayIdHorde = 0;
};
struct TaxiPathEdge {
uint32_t pathId = 0;
uint32_t fromNode = 0, toNode = 0;
uint32_t cost = 0;
};
struct TaxiPathNode {
uint32_t id = 0;
uint32_t pathId = 0;
uint32_t nodeIndex = 0;
uint32_t mapId = 0;
float x = 0, y = 0, z = 0;
};
const std::unordered_map<uint32_t, TaxiNode>& getTaxiNodes() const { return taxiNodes_; }
bool isKnownTaxiNode(uint32_t nodeId) const {
if (nodeId == 0 || nodeId > 384) return false;
uint32_t idx = nodeId - 1;
return (knownTaxiMask_[idx / 32] & (1u << (idx % 32))) != 0;
}
uint32_t getTaxiCostTo(uint32_t destNodeId) const;
bool taxiNpcHasRoutes(uint64_t guid) const {
auto it = taxiNpcHasRoutes_.find(guid);
return it != taxiNpcHasRoutes_.end() && it->second;
}
void updateClientTaxi(float deltaTime);
uint32_t nextMovementTimestampMs();
void sanitizeMovementForTaxi();
// Heartbeat / movement timing (for GameHandler::update())
float& timeSinceLastMoveHeartbeatRef() { return timeSinceLastMoveHeartbeat_; }
float getMoveHeartbeatInterval() const { return moveHeartbeatInterval_; }
bool isServerMovementAllowed() const { return serverMovementAllowed_; }
void setServerMovementAllowed(bool v) { serverMovementAllowed_ = v; }
uint32_t& monsterMovePacketsThisTickRef() { return monsterMovePacketsThisTick_; }
uint32_t& monsterMovePacketsDroppedThisTickRef() { return monsterMovePacketsDroppedThisTick_; }
// Taxi state references for GameHandler update/processing
bool& onTaxiFlightRef() { return onTaxiFlight_; }
bool& taxiMountActiveRef() { return taxiMountActive_; }
uint32_t& taxiMountDisplayIdRef() { return taxiMountDisplayId_; }
bool& taxiActivatePendingRef() { return taxiActivatePending_; }
float& taxiActivateTimerRef() { return taxiActivateTimer_; }
bool& taxiClientActiveRef() { return taxiClientActive_; }
float& taxiLandingCooldownRef() { return taxiLandingCooldown_; }
float& taxiStartGraceRef() { return taxiStartGrace_; }
bool& taxiRecoverPendingRef() { return taxiRecoverPending_; }
uint32_t& taxiRecoverMapIdRef() { return taxiRecoverMapId_; }
glm::vec3& taxiRecoverPosRef() { return taxiRecoverPos_; }
std::unordered_map<uint64_t, bool>& taxiNpcHasRoutesRef() { return taxiNpcHasRoutes_; }
uint32_t* knownTaxiMaskPtr() { return knownTaxiMask_; }
bool& taxiMaskInitializedRef() { return taxiMaskInitialized_; }
uint64_t& taxiNpcGuidRef() { return taxiNpcGuid_; }
// Other-player movement timing (for cleanup on despawn etc.)
std::unordered_map<uint64_t, uint32_t>& otherPlayerMoveTimeMsRef() { return otherPlayerMoveTimeMs_; }
std::unordered_map<uint64_t, float>& otherPlayerSmoothedIntervalMsRef() { return otherPlayerSmoothedIntervalMs_; }
// Methods also called from GameHandler's registerOpcodeHandlers
void handleCompressedMoves(network::Packet& packet);
void handleForceMoveFlagChange(network::Packet& packet, const char* name, Opcode ackOpcode, uint32_t flag, bool set);
void handleMoveSetCollisionHeight(network::Packet& packet);
void applyTaxiMountForCurrentNode();
private:
// --- Packet handlers ---
void handleMonsterMove(network::Packet& packet);
void handleMonsterMoveTransport(network::Packet& packet);
void handleOtherPlayerMovement(network::Packet& packet);
void handleMoveSetSpeed(network::Packet& packet);
void handleForceRunSpeedChange(network::Packet& packet);
void handleForceSpeedChange(network::Packet& packet, const char* name, Opcode ackOpcode, float* speedStorage);
void handleForceMoveRootState(network::Packet& packet, bool rooted);
void handleMoveKnockBack(network::Packet& packet);
void handleTeleportAck(network::Packet& packet);
void handleNewWorld(network::Packet& packet);
void handleShowTaxiNodes(network::Packet& packet);
void handleClientControlUpdate(network::Packet& packet);
void handleActivateTaxiReply(network::Packet& packet);
void loadTaxiDbc();
// --- Private helpers ---
void buildTaxiCostMap();
void startClientTaxiPath(const std::vector<uint32_t>& pathNodes);
friend class GameHandler;
GameHandler& owner_;
// --- Movement state ---
// Reference to GameHandler's movementInfo to avoid desync
MovementInfo& movementInfo;
std::chrono::steady_clock::time_point movementClockStart_ = std::chrono::steady_clock::now();
uint32_t lastMovementTimestampMs_ = 0;
bool serverMovementAllowed_ = true;
uint32_t monsterMovePacketsThisTick_ = 0;
uint32_t monsterMovePacketsDroppedThisTick_ = 0;
// Fall/jump tracking
bool isFalling_ = false;
uint32_t fallStartMs_ = 0;
// Heartbeat timing
float timeSinceLastMoveHeartbeat_ = 0.0f;
float moveHeartbeatInterval_ = 0.5f;
uint32_t lastHeartbeatSendTimeMs_ = 0;
float lastHeartbeatX_ = 0.0f;
float lastHeartbeatY_ = 0.0f;
float lastHeartbeatZ_ = 0.0f;
uint32_t lastHeartbeatFlags_ = 0;
uint64_t lastHeartbeatTransportGuid_ = 0;
uint32_t lastNonHeartbeatMoveSendTimeMs_ = 0;
uint32_t lastFacingSendTimeMs_ = 0;
float lastFacingSentOrientation_ = 0.0f;
// Speed state
float serverRunSpeed_ = 7.0f;
float serverWalkSpeed_ = 2.5f;
float serverRunBackSpeed_ = 4.5f;
float serverSwimSpeed_ = 4.722f;
float serverSwimBackSpeed_ = 2.5f;
float serverFlightSpeed_ = 7.0f;
float serverFlightBackSpeed_ = 4.5f;
float serverTurnRate_ = 3.14159f;
float serverPitchRate_ = 3.14159f;
// Other-player movement smoothing
std::unordered_map<uint64_t, uint32_t> otherPlayerMoveTimeMs_;
std::unordered_map<uint64_t, float> otherPlayerSmoothedIntervalMs_;
// --- Taxi / Flight Path state ---
std::unordered_map<uint64_t, bool> taxiNpcHasRoutes_;
std::unordered_map<uint32_t, TaxiNode> taxiNodes_;
std::vector<TaxiPathEdge> taxiPathEdges_;
std::unordered_map<uint32_t, std::vector<TaxiPathNode>> taxiPathNodes_;
bool taxiDbcLoaded_ = false;
bool taxiWindowOpen_ = false;
ShowTaxiNodesData currentTaxiData_;
uint64_t taxiNpcGuid_ = 0;
bool onTaxiFlight_ = false;
std::string taxiDestName_;
bool taxiMountActive_ = false;
uint32_t taxiMountDisplayId_ = 0;
bool taxiActivatePending_ = false;
float taxiActivateTimer_ = 0.0f;
bool taxiClientActive_ = false;
float taxiLandingCooldown_ = 0.0f;
float taxiStartGrace_ = 0.0f;
size_t taxiClientIndex_ = 0;
std::vector<glm::vec3> taxiClientPath_;
float taxiClientSpeed_ = 32.0f;
float taxiClientSegmentProgress_ = 0.0f;
bool taxiRecoverPending_ = false;
uint32_t taxiRecoverMapId_ = 0;
glm::vec3 taxiRecoverPos_{0.0f};
uint32_t knownTaxiMask_[12] = {};
bool taxiMaskInitialized_ = false;
std::unordered_map<uint32_t, uint32_t> taxiCostMap_;
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,199 @@
#pragma once
#include "game/world_packets.hpp"
#include "game/opcode_table.hpp"
#include "game/handler_types.hpp"
#include "network/packet.hpp"
#include <array>
#include <chrono>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
enum class QuestGiverStatus : uint8_t;
class QuestHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit QuestHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// --- Public API (delegated from GameHandler) ---
// NPC Gossip
void selectGossipOption(uint32_t optionId);
void selectGossipQuest(uint32_t questId);
void acceptQuest();
void declineQuest();
void closeGossip();
void offerQuestFromItem(uint64_t itemGuid, uint32_t questId);
bool isGossipWindowOpen() const { return gossipWindowOpen_; }
const GossipMessageData& getCurrentGossip() const { return currentGossip_; }
// Quest details
bool isQuestDetailsOpen() {
if (questDetailsOpen_) return true;
if (questDetailsOpenTime_ != std::chrono::steady_clock::time_point{}) {
if (std::chrono::steady_clock::now() >= questDetailsOpenTime_) {
questDetailsOpen_ = true;
questDetailsOpenTime_ = std::chrono::steady_clock::time_point{};
return true;
}
}
return false;
}
const QuestDetailsData& getQuestDetails() const { return currentQuestDetails_; }
// Gossip / quest map POI markers (aliased from handler_types.hpp)
using GossipPoi = game::GossipPoi;
const std::vector<GossipPoi>& getGossipPois() const { return gossipPois_; }
void clearGossipPois() { gossipPois_.clear(); }
// Quest turn-in
bool isQuestRequestItemsOpen() const { return questRequestItemsOpen_; }
const QuestRequestItemsData& getQuestRequestItems() const { return currentQuestRequestItems_; }
void completeQuest();
void closeQuestRequestItems();
bool isQuestOfferRewardOpen() const { return questOfferRewardOpen_; }
const QuestOfferRewardData& getQuestOfferReward() const { return currentQuestOfferReward_; }
void chooseQuestReward(uint32_t rewardIndex);
void closeQuestOfferReward();
// Quest log
struct QuestLogEntry {
uint32_t questId = 0;
std::string title;
std::string objectives;
bool complete = false;
std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> killCounts;
std::unordered_map<uint32_t, uint32_t> itemCounts;
std::unordered_map<uint32_t, uint32_t> requiredItemCounts;
struct KillObjective {
int32_t npcOrGoId = 0;
uint32_t required = 0;
};
std::array<KillObjective, 4> killObjectives{};
struct ItemObjective {
uint32_t itemId = 0;
uint32_t required = 0;
};
std::array<ItemObjective, 6> itemObjectives{};
int32_t rewardMoney = 0;
std::array<QuestRewardItem, 4> rewardItems{};
std::array<QuestRewardItem, 6> rewardChoiceItems{};
};
const std::vector<QuestLogEntry>& getQuestLog() const { return questLog_; }
int getSelectedQuestLogIndex() const { return selectedQuestLogIndex_; }
void setSelectedQuestLogIndex(int idx) { selectedQuestLogIndex_ = idx; }
void abandonQuest(uint32_t questId);
void shareQuestWithParty(uint32_t questId);
bool requestQuestQuery(uint32_t questId, bool force = false);
bool isQuestTracked(uint32_t questId) const { return trackedQuestIds_.count(questId) > 0; }
void setQuestTracked(uint32_t questId, bool tracked);
const std::unordered_set<uint32_t>& getTrackedQuestIds() const { return trackedQuestIds_; }
bool isQuestQueryPending(uint32_t questId) const {
return pendingQuestQueryIds_.count(questId) > 0;
}
void clearQuestQueryPending(uint32_t questId) { pendingQuestQueryIds_.erase(questId); }
// Quest giver status (! and ? markers)
QuestGiverStatus getQuestGiverStatus(uint64_t guid) const;
const std::unordered_map<uint64_t, QuestGiverStatus>& getNpcQuestStatuses() const { return npcQuestStatus_; }
// Shared quest
bool hasPendingSharedQuest() const { return pendingSharedQuest_; }
uint32_t getSharedQuestId() const { return sharedQuestId_; }
const std::string& getSharedQuestTitle() const { return sharedQuestTitle_; }
const std::string& getSharedQuestSharerName() const { return sharedQuestSharerName_; }
void acceptSharedQuest();
void declineSharedQuest();
// --- Internal helpers called from GameHandler ---
bool hasQuestInLog(uint32_t questId) const;
int findQuestLogSlotIndexFromServer(uint32_t questId) const;
void addQuestToLocalLogIfMissing(uint32_t questId, const std::string& title, const std::string& objectives);
bool resyncQuestLogFromServerSlots(bool forceQueryMetadata);
void applyQuestStateFromFields(const std::map<uint16_t, uint32_t>& fields);
void applyPackedKillCountsFromFields(QuestLogEntry& quest);
void clearPendingQuestAccept(uint32_t questId);
void triggerQuestAcceptResync(uint32_t questId, uint64_t npcGuid, const char* reason);
// Pending quest accept timeout state (used by GameHandler::update)
std::unordered_map<uint32_t, float>& pendingQuestAcceptTimeoutsRef() { return pendingQuestAcceptTimeouts_; }
std::unordered_map<uint32_t, uint64_t>& pendingQuestAcceptNpcGuidsRef() { return pendingQuestAcceptNpcGuids_; }
bool& pendingLoginQuestResyncRef() { return pendingLoginQuestResync_; }
float& pendingLoginQuestResyncTimeoutRef() { return pendingLoginQuestResyncTimeout_; }
// Direct state access for vendor/gossip interaction in GameHandler
bool& gossipWindowOpenRef() { return gossipWindowOpen_; }
GossipMessageData& currentGossipRef() { return currentGossip_; }
std::unordered_map<uint64_t, QuestGiverStatus>& npcQuestStatusRef() { return npcQuestStatus_; }
private:
// --- Packet handlers ---
void handleGossipMessage(network::Packet& packet);
void handleQuestgiverQuestList(network::Packet& packet);
void handleGossipComplete(network::Packet& packet);
void handleQuestPoiQueryResponse(network::Packet& packet);
void handleQuestDetails(network::Packet& packet);
void handleQuestRequestItems(network::Packet& packet);
void handleQuestOfferReward(network::Packet& packet);
void handleQuestConfirmAccept(network::Packet& packet);
GameHandler& owner_;
// --- State ---
// Gossip
bool gossipWindowOpen_ = false;
GossipMessageData currentGossip_;
std::vector<GossipPoi> gossipPois_;
// Quest details
bool questDetailsOpen_ = false;
std::chrono::steady_clock::time_point questDetailsOpenTime_{};
QuestDetailsData currentQuestDetails_;
// Quest turn-in
bool questRequestItemsOpen_ = false;
QuestRequestItemsData currentQuestRequestItems_;
uint32_t pendingTurnInQuestId_ = 0;
uint64_t pendingTurnInNpcGuid_ = 0;
bool pendingTurnInRewardRequest_ = false;
std::unordered_map<uint32_t, float> pendingQuestAcceptTimeouts_;
std::unordered_map<uint32_t, uint64_t> pendingQuestAcceptNpcGuids_;
bool questOfferRewardOpen_ = false;
QuestOfferRewardData currentQuestOfferReward_;
// Quest log
std::vector<QuestLogEntry> questLog_;
int selectedQuestLogIndex_ = 0;
std::unordered_set<uint32_t> pendingQuestQueryIds_;
std::unordered_set<uint32_t> trackedQuestIds_;
bool pendingLoginQuestResync_ = false;
float pendingLoginQuestResyncTimeout_ = 0.0f;
// Quest giver status per NPC
std::unordered_map<uint64_t, QuestGiverStatus> npcQuestStatus_;
// Shared quest state
bool pendingSharedQuest_ = false;
uint32_t sharedQuestId_ = 0;
std::string sharedQuestTitle_;
std::string sharedQuestSharerName_;
uint64_t sharedQuestSharerGuid_ = 0;
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,444 @@
#pragma once
#include "game/world_packets.hpp"
#include "game/opcode_table.hpp"
#include "game/group_defines.hpp"
#include "game/handler_types.hpp"
#include "network/packet.hpp"
#include <array>
#include <chrono>
#include <functional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
class SocialHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit SocialHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// ---- Structs (aliased from handler_types.hpp) ----
using InspectArenaTeam = game::InspectArenaTeam;
using InspectResult = game::InspectResult;
using WhoEntry = game::WhoEntry;
using BgQueueSlot = game::BgQueueSlot;
using AvailableBgInfo = game::AvailableBgInfo;
using BgPlayerScore = game::BgPlayerScore;
using ArenaTeamScore = game::ArenaTeamScore;
using BgScoreboardData = game::BgScoreboardData;
using BgPlayerPosition = game::BgPlayerPosition;
using PetitionSignature = game::PetitionSignature;
using PetitionInfo = game::PetitionInfo;
using ReadyCheckResult = game::ReadyCheckResult;
using InstanceLockout = game::InstanceLockout;
using LfgState = game::LfgState;
using ArenaTeamStats = game::ArenaTeamStats;
using ArenaTeamMember = game::ArenaTeamMember;
using ArenaTeamRoster = game::ArenaTeamRoster;
// ---- Public API ----
// Inspection
void inspectTarget();
const InspectResult* getInspectResult() const {
return inspectResult_.guid ? &inspectResult_ : nullptr;
}
// Server info / who
void queryServerTime();
void requestPlayedTime();
void queryWho(const std::string& playerName = "");
uint32_t getTotalTimePlayed() const { return totalTimePlayed_; }
uint32_t getLevelTimePlayed() const { return levelTimePlayed_; }
const std::vector<WhoEntry>& getWhoResults() const { return whoResults_; }
uint32_t getWhoOnlineCount() const { return whoOnlineCount_; }
std::string getWhoAreaName(uint32_t zoneId) const;
// Social commands
void addFriend(const std::string& playerName, const std::string& note = "");
void removeFriend(const std::string& playerName);
void setFriendNote(const std::string& playerName, const std::string& note);
void addIgnore(const std::string& playerName);
void removeIgnore(const std::string& playerName);
// Random roll
void randomRoll(uint32_t minRoll = 1, uint32_t maxRoll = 100);
// Battleground
bool hasPendingBgInvite() const;
void acceptBattlefield(uint32_t queueSlot = 0xFFFFFFFF);
void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF);
const std::array<BgQueueSlot, 3>& getBgQueues() const { return bgQueues_; }
const std::vector<AvailableBgInfo>& getAvailableBgs() const { return availableBgs_; }
void requestPvpLog();
const BgScoreboardData* getBgScoreboard() const {
return bgScoreboard_.players.empty() ? nullptr : &bgScoreboard_;
}
const std::vector<BgPlayerPosition>& getBgPlayerPositions() const { return bgPlayerPositions_; }
// Logout
void requestLogout();
void cancelLogout();
bool isLoggingOut() const { return loggingOut_; }
float getLogoutCountdown() const { return logoutCountdown_; }
// Guild
void requestGuildInfo();
void requestGuildRoster();
void setGuildMotd(const std::string& motd);
void promoteGuildMember(const std::string& playerName);
void demoteGuildMember(const std::string& playerName);
void leaveGuild();
void inviteToGuild(const std::string& playerName);
void kickGuildMember(const std::string& playerName);
void disbandGuild();
void setGuildLeader(const std::string& name);
void setGuildPublicNote(const std::string& name, const std::string& note);
void setGuildOfficerNote(const std::string& name, const std::string& note);
void acceptGuildInvite();
void declineGuildInvite();
void queryGuildInfo(uint32_t guildId);
void createGuild(const std::string& guildName);
void addGuildRank(const std::string& rankName);
void deleteGuildRank();
void requestPetitionShowlist(uint64_t npcGuid);
void buyPetition(uint64_t npcGuid, const std::string& guildName);
// Guild state accessors
bool isInGuild() const;
const std::string& getGuildName() const { return guildName_; }
const GuildRosterData& getGuildRoster() const { return guildRoster_; }
bool hasGuildRoster() const { return hasGuildRoster_; }
const std::vector<std::string>& getGuildRankNames() const { return guildRankNames_; }
bool hasPendingGuildInvite() const { return pendingGuildInvite_; }
const std::string& getPendingGuildInviterName() const { return pendingGuildInviterName_; }
const std::string& getPendingGuildInviteGuildName() const { return pendingGuildInviteGuildName_; }
const GuildInfoData& getGuildInfoData() const { return guildInfoData_; }
const GuildQueryResponseData& getGuildQueryData() const { return guildQueryData_; }
bool hasGuildInfoData() const { return guildInfoData_.isValid(); }
// Petition
bool hasPetitionShowlist() const { return showPetitionDialog_; }
void clearPetitionDialog() { showPetitionDialog_ = false; }
uint32_t getPetitionCost() const { return petitionCost_; }
uint64_t getPetitionNpcGuid() const { return petitionNpcGuid_; }
const PetitionInfo& getPetitionInfo() const { return petitionInfo_; }
bool hasPetitionSignaturesUI() const { return petitionInfo_.showUI; }
void clearPetitionSignaturesUI() { petitionInfo_.showUI = false; }
void signPetition(uint64_t petitionGuid);
void turnInPetition(uint64_t petitionGuid);
// Guild name lookup
const std::string& lookupGuildName(uint32_t guildId);
uint32_t getEntityGuildId(uint64_t guid) const;
// Ready check
void initiateReadyCheck();
void respondToReadyCheck(bool ready);
bool hasPendingReadyCheck() const { return pendingReadyCheck_; }
void dismissReadyCheck() { pendingReadyCheck_ = false; }
const std::string& getReadyCheckInitiator() const { return readyCheckInitiator_; }
const std::vector<ReadyCheckResult>& getReadyCheckResults() const { return readyCheckResults_; }
// Duel
void acceptDuel();
void forfeitDuel();
void proposeDuel(uint64_t targetGuid);
void reportPlayer(uint64_t targetGuid, const std::string& reason);
bool hasPendingDuelRequest() const { return pendingDuelRequest_; }
const std::string& getDuelChallengerName() const { return duelChallengerName_; }
float getDuelCountdownRemaining() const {
if (duelCountdownMs_ == 0) return 0.0f;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - duelCountdownStartedAt_).count();
float rem = (static_cast<float>(duelCountdownMs_) - static_cast<float>(elapsed)) / 1000.0f;
return rem > 0.0f ? rem : 0.0f;
}
// Party/Raid
void inviteToGroup(const std::string& playerName);
void acceptGroupInvite();
void declineGroupInvite();
void leaveGroup();
void convertToRaid();
void sendSetLootMethod(uint32_t method, uint32_t threshold, uint64_t masterLooterGuid);
bool isInGroup() const { return !partyData.isEmpty(); }
const GroupListData& getPartyData() const { return partyData; }
bool hasPendingGroupInvite() const { return pendingGroupInvite; }
const std::string& getPendingInviterName() const { return pendingInviterName; }
void uninvitePlayer(const std::string& playerName);
void leaveParty();
void setMainTank(uint64_t targetGuid);
void setMainAssist(uint64_t targetGuid);
void clearMainTank();
void clearMainAssist();
void setRaidMark(uint64_t guid, uint8_t icon);
void requestRaidInfo();
// Instance lockouts
const std::vector<InstanceLockout>& getInstanceLockouts() const { return instanceLockouts_; }
// Minimap ping
void sendMinimapPing(float wowX, float wowY);
// Summon request
void handleSummonRequest(network::Packet& packet);
void acceptSummon();
void declineSummon();
// Battlefield Manager
void acceptBfMgrInvite();
void declineBfMgrInvite();
// Calendar
void requestCalendar();
// ---- Methods moved from GameHandler ----
void sendSetDifficulty(uint32_t difficulty);
void toggleHelm();
void toggleCloak();
void setStandState(uint8_t standState);
void sendAlterAppearance(uint32_t hairStyle, uint32_t hairColor, uint32_t facialHair);
void deleteGmTicket();
void requestGmTicket();
// Utility methods for delegation from GameHandler
void updateLogoutCountdown(float deltaTime);
void resetTransferState();
GroupListData& mutablePartyData() { return partyData; }
InspectResult& mutableInspectResult() { return inspectResult_; }
void setRaidTargetGuid(uint8_t icon, uint64_t guid) {
if (icon < kRaidMarkCount) raidTargetGuids_[icon] = guid;
}
void setEncounterUnitGuid(uint32_t slot, uint64_t guid) {
if (slot < kMaxEncounterSlots) encounterUnitGuids_[slot] = guid;
}
// Encounter unit tracking
static constexpr uint32_t kMaxEncounterSlots = 5;
uint64_t getEncounterUnitGuid(uint32_t slot) const {
return (slot < kMaxEncounterSlots) ? encounterUnitGuids_[slot] : 0;
}
// Raid target markers (0-7: Star, Circle, Diamond, Triangle, Moon, Square, Cross, Skull)
static constexpr uint32_t kRaidMarkCount = 8;
uint64_t getRaidMarkGuid(uint32_t icon) const {
return (icon < kRaidMarkCount) ? raidTargetGuids_[icon] : 0;
}
uint8_t getEntityRaidMark(uint64_t guid) const {
if (guid == 0) return 0xFF;
for (uint32_t i = 0; i < kRaidMarkCount; ++i)
if (raidTargetGuids_[i] == guid) return static_cast<uint8_t>(i);
return 0xFF;
}
// LFG / Dungeon Finder
void lfgJoin(uint32_t dungeonId, uint8_t roles);
void lfgLeave();
void lfgSetRoles(uint8_t roles);
void lfgAcceptProposal(uint32_t proposalId, bool accept);
void lfgSetBootVote(bool vote);
void lfgTeleport(bool toLfgDungeon = true);
LfgState getLfgState() const { return lfgState_; }
bool isLfgQueued() const { return lfgState_ == LfgState::Queued; }
bool isLfgInDungeon() const { return lfgState_ == LfgState::InDungeon; }
uint32_t getLfgDungeonId() const { return lfgDungeonId_; }
std::string getCurrentLfgDungeonName() const;
uint32_t getLfgProposalId() const { return lfgProposalId_; }
int32_t getLfgAvgWaitSec() const { return lfgAvgWaitSec_; }
uint32_t getLfgTimeInQueueMs() const { return lfgTimeInQueueMs_; }
uint32_t getLfgBootVotes() const { return lfgBootVotes_; }
uint32_t getLfgBootTotal() const { return lfgBootTotal_; }
uint32_t getLfgBootTimeLeft() const { return lfgBootTimeLeft_; }
uint32_t getLfgBootNeeded() const { return lfgBootNeeded_; }
const std::string& getLfgBootTargetName() const { return lfgBootTargetName_; }
const std::string& getLfgBootReason() const { return lfgBootReason_; }
// Arena
const std::vector<ArenaTeamStats>& getArenaTeamStats() const { return arenaTeamStats_; }
void requestArenaTeamRoster(uint32_t teamId);
const ArenaTeamRoster* getArenaTeamRoster(uint32_t teamId) const {
for (const auto& r : arenaTeamRosters_)
if (r.teamId == teamId) return &r;
return nullptr;
}
private:
// ---- Packet handlers ----
void handleInspectResults(network::Packet& packet);
void handleQueryTimeResponse(network::Packet& packet);
void handlePlayedTime(network::Packet& packet);
void handleWho(network::Packet& packet);
void handleFriendList(network::Packet& packet);
void handleContactList(network::Packet& packet);
void handleFriendStatus(network::Packet& packet);
void handleRandomRoll(network::Packet& packet);
void handleLogoutResponse(network::Packet& packet);
void handleLogoutComplete(network::Packet& packet);
void handleGroupInvite(network::Packet& packet);
void handleGroupDecline(network::Packet& packet);
void handleGroupList(network::Packet& packet);
void handleGroupUninvite(network::Packet& packet);
void handlePartyCommandResult(network::Packet& packet);
void handlePartyMemberStats(network::Packet& packet, bool isFull);
void handleGuildInfo(network::Packet& packet);
void handleGuildRoster(network::Packet& packet);
void handleGuildQueryResponse(network::Packet& packet);
void handleGuildEvent(network::Packet& packet);
void handleGuildInvite(network::Packet& packet);
void handleGuildCommandResult(network::Packet& packet);
void handlePetitionShowlist(network::Packet& packet);
void handlePetitionQueryResponse(network::Packet& packet);
void handlePetitionShowSignatures(network::Packet& packet);
void handlePetitionSignResults(network::Packet& packet);
void handleTurnInPetitionResults(network::Packet& packet);
void handleBattlefieldStatus(network::Packet& packet);
void handleBattlefieldList(network::Packet& packet);
void handleRaidInstanceInfo(network::Packet& packet);
void handleInstanceDifficulty(network::Packet& packet);
void handleDuelRequested(network::Packet& packet);
void handleDuelComplete(network::Packet& packet);
void handleDuelWinner(network::Packet& packet);
void handleLfgJoinResult(network::Packet& packet);
void handleLfgQueueStatus(network::Packet& packet);
void handleLfgProposalUpdate(network::Packet& packet);
void handleLfgRoleCheckUpdate(network::Packet& packet);
void handleLfgUpdatePlayer(network::Packet& packet);
void handleLfgPlayerReward(network::Packet& packet);
void handleLfgBootProposalUpdate(network::Packet& packet);
void handleLfgTeleportDenied(network::Packet& packet);
void handleArenaTeamCommandResult(network::Packet& packet);
void handleArenaTeamQueryResponse(network::Packet& packet);
void handleArenaTeamRoster(network::Packet& packet);
void handleArenaTeamInvite(network::Packet& packet);
void handleArenaTeamEvent(network::Packet& packet);
void handleArenaTeamStats(network::Packet& packet);
void handleArenaError(network::Packet& packet);
void handlePvpLogData(network::Packet& packet);
void handleInitializeFactions(network::Packet& packet);
void handleSetFactionStanding(network::Packet& packet);
void handleSetFactionAtWar(network::Packet& packet);
void handleSetFactionVisible(network::Packet& packet);
void handleGroupSetLeader(network::Packet& packet);
GameHandler& owner_;
// ---- State ----
// Inspect
InspectResult inspectResult_;
// Logout
bool loggingOut_ = false;
float logoutCountdown_ = 0.0f;
// Time played
uint32_t totalTimePlayed_ = 0;
uint32_t levelTimePlayed_ = 0;
// Who results
std::vector<WhoEntry> whoResults_;
uint32_t whoOnlineCount_ = 0;
// Duel
bool pendingDuelRequest_ = false;
uint64_t duelChallengerGuid_= 0;
uint64_t duelFlagGuid_ = 0;
std::string duelChallengerName_;
uint32_t duelCountdownMs_ = 0;
std::chrono::steady_clock::time_point duelCountdownStartedAt_{};
// Guild
std::string guildName_;
std::vector<std::string> guildRankNames_;
GuildRosterData guildRoster_;
GuildInfoData guildInfoData_;
GuildQueryResponseData guildQueryData_;
bool hasGuildRoster_ = false;
std::unordered_map<uint32_t, std::string> guildNameCache_;
std::unordered_set<uint32_t> pendingGuildNameQueries_;
bool pendingGuildInvite_ = false;
std::string pendingGuildInviterName_;
std::string pendingGuildInviteGuildName_;
bool showPetitionDialog_ = false;
uint32_t petitionCost_ = 0;
uint64_t petitionNpcGuid_ = 0;
PetitionInfo petitionInfo_;
// Group
GroupListData partyData;
bool pendingGroupInvite = false;
std::string pendingInviterName;
// Ready check
bool pendingReadyCheck_ = false;
uint32_t readyCheckReadyCount_ = 0;
uint32_t readyCheckNotReadyCount_ = 0;
std::string readyCheckInitiator_;
std::vector<ReadyCheckResult> readyCheckResults_;
// Instance
std::vector<InstanceLockout> instanceLockouts_;
uint32_t instanceDifficulty_ = 0;
bool instanceIsHeroic_ = false;
bool inInstance_ = false;
// Raid marks
std::array<uint64_t, kRaidMarkCount> raidTargetGuids_ = {};
// Encounter units
std::array<uint64_t, kMaxEncounterSlots> encounterUnitGuids_ = {};
// Arena
std::vector<ArenaTeamStats> arenaTeamStats_;
std::vector<ArenaTeamRoster> arenaTeamRosters_;
// Battleground
std::array<BgQueueSlot, 3> bgQueues_{};
std::vector<AvailableBgInfo> availableBgs_;
BgScoreboardData bgScoreboard_;
std::vector<BgPlayerPosition> bgPlayerPositions_;
// LFG / Dungeon Finder
LfgState lfgState_ = LfgState::None;
uint32_t lfgDungeonId_ = 0;
uint32_t lfgProposalId_ = 0;
int32_t lfgAvgWaitSec_ = -1;
uint32_t lfgTimeInQueueMs_= 0;
uint32_t lfgBootVotes_ = 0;
uint32_t lfgBootTotal_ = 0;
uint32_t lfgBootTimeLeft_ = 0;
uint32_t lfgBootNeeded_ = 0;
std::string lfgBootTargetName_;
std::string lfgBootReason_;
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,331 @@
#pragma once
#include "game/world_packets.hpp"
#include "game/opcode_table.hpp"
#include "game/spell_defines.hpp"
#include "game/handler_types.hpp"
#include "network/packet.hpp"
#include <array>
#include <chrono>
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
class SpellHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit SpellHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// Talent data structures (aliased from handler_types.hpp)
using TalentEntry = game::TalentEntry;
using TalentTabEntry = game::TalentTabEntry;
// --- Spell book tabs ---
struct SpellBookTab {
std::string name;
std::string texture; // icon path
std::vector<uint32_t> spellIds; // spells in this tab
};
// Unit cast state (aliased from handler_types.hpp)
using UnitCastState = game::UnitCastState;
// Equipment set info (aliased from handler_types.hpp)
using EquipmentSetInfo = game::EquipmentSetInfo;
// --- Public API (delegated from GameHandler) ---
void castSpell(uint32_t spellId, uint64_t targetGuid = 0);
void cancelCast();
void cancelAura(uint32_t spellId);
// Known spells
const std::unordered_set<uint32_t>& getKnownSpells() const { return knownSpells_; }
const std::unordered_map<uint32_t, float>& getSpellCooldowns() const { return spellCooldowns_; }
float getSpellCooldown(uint32_t spellId) const;
// Cast state
bool isCasting() const { return casting_; }
bool isChanneling() const { return casting_ && castIsChannel_; }
bool isGameObjectInteractionCasting() const;
uint32_t getCurrentCastSpellId() const { return currentCastSpellId_; }
float getCastProgress() const { return castTimeTotal_ > 0 ? (castTimeTotal_ - castTimeRemaining_) / castTimeTotal_ : 0.0f; }
float getCastTimeRemaining() const { return castTimeRemaining_; }
float getCastTimeTotal() const { return castTimeTotal_; }
// Repeat-craft queue
void startCraftQueue(uint32_t spellId, int count);
void cancelCraftQueue();
int getCraftQueueRemaining() const { return craftQueueRemaining_; }
uint32_t getCraftQueueSpellId() const { return craftQueueSpellId_; }
// Spell queue (400ms window)
uint32_t getQueuedSpellId() const { return queuedSpellId_; }
void cancelQueuedSpell() { queuedSpellId_ = 0; queuedSpellTarget_ = 0; }
// Unit cast state (tracked per GUID for target frame + boss frames)
const UnitCastState* getUnitCastState(uint64_t guid) const {
auto it = unitCastStates_.find(guid);
return (it != unitCastStates_.end() && it->second.casting) ? &it->second : nullptr;
}
// Target cast helpers
bool isTargetCasting() const;
uint32_t getTargetCastSpellId() const;
float getTargetCastProgress() const;
float getTargetCastTimeRemaining() const;
bool isTargetCastInterruptible() const;
// Talents
uint8_t getActiveTalentSpec() const { return activeTalentSpec_; }
uint8_t getUnspentTalentPoints() const { return unspentTalentPoints_[activeTalentSpec_]; }
uint8_t getUnspentTalentPoints(uint8_t spec) const { return spec < 2 ? unspentTalentPoints_[spec] : 0; }
const std::unordered_map<uint32_t, uint8_t>& getLearnedTalents() const { return learnedTalents_[activeTalentSpec_]; }
const std::unordered_map<uint32_t, uint8_t>& getLearnedTalents(uint8_t spec) const {
static std::unordered_map<uint32_t, uint8_t> empty;
return spec < 2 ? learnedTalents_[spec] : empty;
}
static constexpr uint8_t MAX_GLYPH_SLOTS = 6;
const std::array<uint16_t, MAX_GLYPH_SLOTS>& getGlyphs() const { return learnedGlyphs_[activeTalentSpec_]; }
const std::array<uint16_t, MAX_GLYPH_SLOTS>& getGlyphs(uint8_t spec) const {
static std::array<uint16_t, MAX_GLYPH_SLOTS> empty{};
return spec < 2 ? learnedGlyphs_[spec] : empty;
}
uint8_t getTalentRank(uint32_t talentId) const {
auto it = learnedTalents_[activeTalentSpec_].find(talentId);
return (it != learnedTalents_[activeTalentSpec_].end()) ? it->second : 0;
}
void learnTalent(uint32_t talentId, uint32_t requestedRank);
void switchTalentSpec(uint8_t newSpec);
// Talent DBC access
const TalentEntry* getTalentEntry(uint32_t talentId) const {
auto it = talentCache_.find(talentId);
return (it != talentCache_.end()) ? &it->second : nullptr;
}
const TalentTabEntry* getTalentTabEntry(uint32_t tabId) const {
auto it = talentTabCache_.find(tabId);
return (it != talentTabCache_.end()) ? &it->second : nullptr;
}
const std::unordered_map<uint32_t, TalentEntry>& getAllTalents() const { return talentCache_; }
const std::unordered_map<uint32_t, TalentTabEntry>& getAllTalentTabs() const { return talentTabCache_; }
void loadTalentDbc();
// Auras
const std::vector<AuraSlot>& getPlayerAuras() const { return playerAuras_; }
const std::vector<AuraSlot>& getTargetAuras() const { return targetAuras_; }
const std::vector<AuraSlot>* getUnitAuras(uint64_t guid) const {
auto it = unitAurasCache_.find(guid);
return (it != unitAurasCache_.end()) ? &it->second : nullptr;
}
// Global Cooldown (GCD)
float getGCDRemaining() const {
if (gcdTotal_ <= 0.0f) return 0.0f;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - gcdStartedAt_).count() / 1000.0f;
float rem = gcdTotal_ - elapsed;
return rem > 0.0f ? rem : 0.0f;
}
float getGCDTotal() const { return gcdTotal_; }
bool isGCDActive() const { return getGCDRemaining() > 0.0f; }
// Spell book tabs
const std::vector<SpellBookTab>& getSpellBookTabs();
// Talent wipe confirm dialog
bool showTalentWipeConfirmDialog() const { return talentWipePending_; }
uint32_t getTalentWipeCost() const { return talentWipeCost_; }
void confirmTalentWipe();
void cancelTalentWipe() { talentWipePending_ = false; }
// Pet talent respec confirm
bool showPetUnlearnDialog() const { return petUnlearnPending_; }
uint32_t getPetUnlearnCost() const { return petUnlearnCost_; }
void confirmPetUnlearn();
void cancelPetUnlearn() { petUnlearnPending_ = false; }
// Item use
void useItemBySlot(int backpackIndex);
void useItemInBag(int bagIndex, int slotIndex);
void useItemById(uint32_t itemId);
// Equipment sets
const std::vector<EquipmentSetInfo>& getEquipmentSets() const { return equipmentSetInfo_; }
// Pet spells
void sendPetAction(uint32_t action, uint64_t targetGuid = 0);
void dismissPet();
void togglePetSpellAutocast(uint32_t spellId);
void renamePet(const std::string& newName);
// Spell DBC accessors
const int32_t* getSpellEffectBasePoints(uint32_t spellId) const;
float getSpellDuration(uint32_t spellId) const;
const std::string& getSpellName(uint32_t spellId) const;
const std::string& getSpellRank(uint32_t spellId) const;
const std::string& getSpellDescription(uint32_t spellId) const;
std::string getEnchantName(uint32_t enchantId) const;
uint8_t getSpellDispelType(uint32_t spellId) const;
bool isSpellInterruptible(uint32_t spellId) const;
uint32_t getSpellSchoolMask(uint32_t spellId) const;
const std::string& getSkillLineName(uint32_t spellId) const;
// Cast state
void stopCasting();
void resetCastState();
void clearUnitCaches();
// Aura duration
void handleUpdateAuraDuration(uint8_t slot, uint32_t durationMs);
// Skill DBC
void loadSkillLineDbc();
void extractSkillFields(const std::map<uint16_t, uint32_t>& fields);
void extractExploredZoneFields(const std::map<uint16_t, uint32_t>& fields);
// Update per-frame timers (call from GameHandler::update)
void updateTimers(float dt);
private:
// --- Packet handlers ---
void handleInitialSpells(network::Packet& packet);
void handleCastFailed(network::Packet& packet);
void handleSpellStart(network::Packet& packet);
void handleSpellGo(network::Packet& packet);
void handleSpellCooldown(network::Packet& packet);
void handleCooldownEvent(network::Packet& packet);
void handleAuraUpdate(network::Packet& packet, bool isAll);
void handleLearnedSpell(network::Packet& packet);
void handlePetSpells(network::Packet& packet);
void handleListStabledPets(network::Packet& packet);
// Pet stable
void requestStabledPetList();
void stablePet(uint8_t slot);
void unstablePet(uint32_t petNumber);
void handleCastResult(network::Packet& packet);
void handleSpellFailedOther(network::Packet& packet);
void handleClearCooldown(network::Packet& packet);
void handleModifyCooldown(network::Packet& packet);
void handlePlaySpellVisual(network::Packet& packet);
void handleSpellModifier(network::Packet& packet, bool isFlat);
void handleSpellDelayed(network::Packet& packet);
void handleSpellLogMiss(network::Packet& packet);
void handleSpellFailure(network::Packet& packet);
void handleItemCooldown(network::Packet& packet);
void handleDispelFailed(network::Packet& packet);
void handleTotemCreated(network::Packet& packet);
void handlePeriodicAuraLog(network::Packet& packet);
void handleSpellEnergizeLog(network::Packet& packet);
void handleExtraAuraInfo(network::Packet& packet, bool isInit);
void handleSpellDispelLog(network::Packet& packet);
void handleSpellStealLog(network::Packet& packet);
void handleSpellChanceProcLog(network::Packet& packet);
void handleSpellInstaKillLog(network::Packet& packet);
void handleSpellLogExecute(network::Packet& packet);
void handleClearExtraAuraInfo(network::Packet& packet);
void handleItemEnchantTimeUpdate(network::Packet& packet);
void handleResumeCastBar(network::Packet& packet);
void handleChannelStart(network::Packet& packet);
void handleChannelUpdate(network::Packet& packet);
// --- Internal helpers ---
void loadSpellNameCache() const;
void loadSkillLineAbilityDbc();
void categorizeTrainerSpells();
void handleSupercededSpell(network::Packet& packet);
void handleRemovedSpell(network::Packet& packet);
void handleUnlearnSpells(network::Packet& packet);
void handleTalentsInfo(network::Packet& packet);
void handleAchievementEarned(network::Packet& packet);
void handleEquipmentSetList(network::Packet& packet);
friend class GameHandler;
friend class InventoryHandler;
friend class CombatHandler;
GameHandler& owner_;
// --- Spell state ---
std::unordered_set<uint32_t> knownSpells_;
std::unordered_map<uint32_t, float> spellCooldowns_; // spellId -> remaining seconds
uint8_t castCount_ = 0;
bool casting_ = false;
bool castIsChannel_ = false;
uint32_t currentCastSpellId_ = 0;
float castTimeRemaining_ = 0.0f;
float castTimeTotal_ = 0.0f;
// Repeat-craft queue
uint32_t craftQueueSpellId_ = 0;
int craftQueueRemaining_ = 0;
// Spell queue (400ms window)
uint32_t queuedSpellId_ = 0;
uint64_t queuedSpellTarget_ = 0;
// Per-unit cast state
std::unordered_map<uint64_t, UnitCastState> unitCastStates_;
// Talents (dual-spec support)
uint8_t activeTalentSpec_ = 0;
uint8_t unspentTalentPoints_[2] = {0, 0};
std::unordered_map<uint32_t, uint8_t> learnedTalents_[2];
std::array<std::array<uint16_t, MAX_GLYPH_SLOTS>, 2> learnedGlyphs_{};
std::unordered_map<uint32_t, TalentEntry> talentCache_;
std::unordered_map<uint32_t, TalentTabEntry> talentTabCache_;
bool talentDbcLoaded_ = false;
bool talentsInitialized_ = false;
// Auras
std::vector<AuraSlot> playerAuras_;
std::vector<AuraSlot> targetAuras_;
std::unordered_map<uint64_t, std::vector<AuraSlot>> unitAurasCache_;
// Global Cooldown
float gcdTotal_ = 0.0f;
std::chrono::steady_clock::time_point gcdStartedAt_{};
// Spell book tabs
std::vector<SpellBookTab> spellBookTabs_;
bool spellBookTabsDirty_ = true;
// Talent wipe confirm dialog
bool talentWipePending_ = false;
uint64_t talentWipeNpcGuid_ = 0;
uint32_t talentWipeCost_ = 0;
// Pet talent respec confirm dialog
bool petUnlearnPending_ = false;
uint64_t petUnlearnGuid_ = 0;
uint32_t petUnlearnCost_ = 0;
// Equipment sets
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::vector<EquipmentSetInfo> equipmentSetInfo_;
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,103 @@
#pragma once
#include "game/opcode_table.hpp"
#include "network/packet.hpp"
#include <cstdint>
#include <functional>
#include <future>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace wowee {
namespace game {
class GameHandler;
class WardenCrypto;
class WardenMemory;
class WardenModule;
class WardenModuleManager;
class WardenHandler {
public:
using PacketHandler = std::function<void(network::Packet&)>;
using DispatchTable = std::unordered_map<LogicalOpcode, PacketHandler>;
explicit WardenHandler(GameHandler& owner);
void registerOpcodes(DispatchTable& table);
// --- Public API ---
/** Reset all warden state (called on connect / disconnect). */
void reset();
/** Initialize warden module manager (called once from GameHandler ctor). */
void initModuleManager();
/** Whether the server requires Warden (gates char enum / create). */
bool requiresWarden() const { return requiresWarden_; }
void setRequiresWarden(bool v) { requiresWarden_ = v; }
bool wardenGateSeen() const { return wardenGateSeen_; }
/** Increment packet-after-gate counter (called from handlePacket). */
void notifyPacketAfterGate() { ++wardenPacketsAfterGate_; }
bool wardenCharEnumBlockedLogged() const { return wardenCharEnumBlockedLogged_; }
void setWardenCharEnumBlockedLogged(bool v) { wardenCharEnumBlockedLogged_ = v; }
/** Called from GameHandler::update() to drain async warden response + log gate timing. */
void update(float deltaTime);
private:
void handleWardenData(network::Packet& packet);
bool loadWardenCRFile(const std::string& moduleHashHex);
GameHandler& owner_;
// --- Warden state ---
bool requiresWarden_ = false;
bool wardenGateSeen_ = false;
float wardenGateElapsed_ = 0.0f;
float wardenGateNextStatusLog_ = 2.0f;
uint32_t wardenPacketsAfterGate_ = 0;
bool wardenCharEnumBlockedLogged_ = false;
std::unique_ptr<WardenCrypto> wardenCrypto_;
std::unique_ptr<WardenMemory> wardenMemory_;
std::unique_ptr<WardenModuleManager> wardenModuleManager_;
// Warden module download state
enum class WardenState {
WAIT_MODULE_USE, // Waiting for first SMSG (MODULE_USE)
WAIT_MODULE_CACHE, // Sent MODULE_MISSING, receiving module chunks
WAIT_HASH_REQUEST, // Module received, waiting for HASH_REQUEST
WAIT_CHECKS, // Hash sent, waiting for check requests
};
WardenState wardenState_ = WardenState::WAIT_MODULE_USE;
std::vector<uint8_t> wardenModuleHash_; // 16 bytes MD5
std::vector<uint8_t> wardenModuleKey_; // 16 bytes RC4
uint32_t wardenModuleSize_ = 0;
std::vector<uint8_t> wardenModuleData_; // Downloaded module chunks
std::vector<uint8_t> wardenLoadedModuleImage_; // Parsed module image for key derivation
std::shared_ptr<WardenModule> wardenLoadedModule_; // Loaded Warden module
// Pre-computed challenge/response entries from .cr file
struct WardenCREntry {
uint8_t seed[16];
uint8_t reply[20];
uint8_t clientKey[16]; // Encrypt key (client→server)
uint8_t serverKey[16]; // Decrypt key (server→client)
};
std::vector<WardenCREntry> wardenCREntries_;
// Module-specific check type opcodes [9]: MEM, PAGE_A, PAGE_B, MPQ, LUA, DRIVER, TIMING, PROC, MODULE
uint8_t wardenCheckOpcodes_[9] = {};
// Async Warden response: avoids 5-second main-loop stalls from PAGE_A/PAGE_B code pattern searches
std::future<std::vector<uint8_t>> wardenPendingEncrypted_; // encrypted response bytes
bool wardenResponsePending_ = false;
};
} // namespace game
} // namespace wowee