#pragma once #include "game/world_packets.hpp" #include "game/character.hpp" #include "game/opcode_table.hpp" #include "game/update_field_table.hpp" #include "game/inventory.hpp" #include "game/spell_defines.hpp" #include "game/group_defines.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace wowee::game { class TransportManager; class WardenCrypto; class WardenMemory; class WardenModule; class WardenModuleManager; class PacketParsers; } namespace wowee { namespace network { class WorldSocket; class Packet; } namespace game { struct PlayerSkill { uint32_t skillId = 0; uint16_t value = 0; // base + permanent item bonuses uint16_t maxValue = 0; uint16_t bonusTemp = 0; // temporary buff bonus (food, potions, etc.) uint16_t bonusPerm = 0; // permanent spec/misc bonus (rarely non-zero) uint16_t effectiveValue() const { return value + bonusTemp + bonusPerm; } }; /** * Quest giver status values (WoW 3.3.5a) */ enum class QuestGiverStatus : uint8_t { NONE = 0, UNAVAILABLE = 1, INCOMPLETE = 5, // ? (gray) REWARD_REP = 6, AVAILABLE_LOW = 7, // ! (gray, low-level) AVAILABLE = 8, // ! (yellow) REWARD = 10 // ? (yellow) }; /** * A single contact list entry (friend, ignore, or mute). */ struct ContactEntry { uint64_t guid = 0; std::string name; std::string note; uint32_t flags = 0; // 0x1=friend, 0x2=ignore, 0x4=mute uint8_t status = 0; // 0=offline, 1=online, 2=AFK, 3=DND uint32_t areaId = 0; uint32_t level = 0; uint32_t classId = 0; bool isFriend() const { return (flags & 0x1) != 0; } bool isIgnored() const { return (flags & 0x2) != 0; } bool isOnline() const { return status != 0; } }; /** * World connection state */ enum class WorldState { DISCONNECTED, // Not connected CONNECTING, // TCP connection in progress CONNECTED, // Connected, waiting for challenge CHALLENGE_RECEIVED, // Received SMSG_AUTH_CHALLENGE AUTH_SENT, // Sent CMSG_AUTH_SESSION, encryption initialized AUTHENTICATED, // Received SMSG_AUTH_RESPONSE success READY, // Ready for character/world operations CHAR_LIST_REQUESTED, // CMSG_CHAR_ENUM sent CHAR_LIST_RECEIVED, // SMSG_CHAR_ENUM received ENTERING_WORLD, // CMSG_PLAYER_LOGIN sent IN_WORLD, // In game world FAILED // Connection or authentication failed }; /** * World connection callbacks */ using WorldConnectSuccessCallback = std::function; using WorldConnectFailureCallback = std::function; /** * GameHandler - Manages world server connection and game protocol * * Handles: * - Connection to world server * - Authentication with session key from auth server * - RC4 header encryption * - Character enumeration * - World entry * - Game packets */ 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 }; GameHandler(); ~GameHandler(); /** Access the active opcode table (wire ↔ logical mapping). */ const OpcodeTable& getOpcodeTable() const { return opcodeTable_; } OpcodeTable& getOpcodeTable() { return opcodeTable_; } const UpdateFieldTable& getUpdateFieldTable() const { return updateFieldTable_; } UpdateFieldTable& getUpdateFieldTable() { return updateFieldTable_; } PacketParsers* getPacketParsers() { return packetParsers_.get(); } void setPacketParsers(std::unique_ptr parsers); /** * Connect to world server * * @param host World server hostname/IP * @param port World server port (default 8085) * @param sessionKey 40-byte session key from auth server * @param accountName Account name (will be uppercased) * @param build Client build number (default 12340 for 3.3.5a) * @return true if connection initiated */ bool connect(const std::string& host, uint16_t port, const std::vector& sessionKey, const std::string& accountName, uint32_t build = 12340, uint32_t realmId = 0); /** * Disconnect from world server */ void disconnect(); /** * Check if connected to world server */ bool isConnected() const; /** * Get current connection state */ WorldState getState() const { return state; } /** * Request character list from server * Must be called when state is READY or AUTHENTICATED */ void requestCharacterList(); /** * Get list of characters (available after CHAR_LIST_RECEIVED state) */ const std::vector& getCharacters() const { return characters; } void createCharacter(const CharCreateData& data); void deleteCharacter(uint64_t characterGuid); using CharCreateCallback = std::function; void setCharCreateCallback(CharCreateCallback cb) { charCreateCallback_ = std::move(cb); } using CharDeleteCallback = std::function; void setCharDeleteCallback(CharDeleteCallback cb) { charDeleteCallback_ = std::move(cb); } uint8_t getLastCharDeleteResult() const { return lastCharDeleteResult_; } using CharLoginFailCallback = std::function; void setCharLoginFailCallback(CharLoginFailCallback cb) { charLoginFailCallback_ = std::move(cb); } /** * Select and log in with a character * @param characterGuid GUID of character to log in with */ void selectCharacter(uint64_t characterGuid); void setActiveCharacterGuid(uint64_t guid) { activeCharacterGuid_ = guid; } uint64_t getActiveCharacterGuid() const { return activeCharacterGuid_; } const Character* getActiveCharacter() const; const Character* getFirstCharacter() const; /** * Get current player movement info */ const MovementInfo& getMovementInfo() const { return movementInfo; } uint32_t getCurrentMapId() const { return currentMapId_; } bool getHomeBind(uint32_t& mapId, glm::vec3& pos) const { if (!hasHomeBind_) return false; mapId = homeBindMapId_; pos = homeBindPos_; return true; } uint32_t getHomeBindZoneId() const { return homeBindZoneId_; } /** * Send a movement packet * @param opcode Movement opcode (MSG_MOVE_START_FORWARD, etc.) */ void sendMovement(Opcode opcode); /** * Update player position * @param x X coordinate * @param y Y coordinate * @param z Z coordinate */ void setPosition(float x, float y, float z); /** * Update player orientation * @param orientation Facing direction in radians */ void setOrientation(float orientation); /** * Get entity manager (for accessing entities in view) */ EntityManager& getEntityManager() { return entityManager; } const EntityManager& getEntityManager() const { return entityManager; } /** * Send a chat message * @param type Chat type (SAY, YELL, WHISPER, etc.) * @param message Message text * @param target Target name (for whispers, empty otherwise) */ 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); const std::vector& getJoinedChannels() const { return joinedChannels_; } 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; }; ChatAutoJoin chatAutoJoin; // Chat bubble callback: (senderGuid, message, isYell) using ChatBubbleCallback = std::function; void setChatBubbleCallback(ChatBubbleCallback cb) { chatBubbleCallback_ = std::move(cb); } // Emote animation callback: (entityGuid, animationId) using EmoteAnimCallback = std::function; void setEmoteAnimCallback(EmoteAnimCallback cb) { emoteAnimCallback_ = std::move(cb); } /** * Get chat history (recent messages) * @param maxMessages Maximum number of messages to return (0 = all) * @return Vector of chat messages */ const std::deque& getChatHistory() const { return chatHistory; } void clearChatHistory() { chatHistory.clear(); } /** * Add a locally-generated chat message (e.g., emote feedback) */ void addLocalChatMessage(const MessageChatData& msg); // Money (copper) uint64_t getMoneyCopper() const { return playerMoneyCopper_; } // Server-authoritative armor (UNIT_FIELD_RESISTANCES[0]) int32_t getArmorRating() const { return playerArmorRating_; } // Server-authoritative elemental resistances (UNIT_FIELD_RESISTANCES[1-6]). // school: 1=Holy, 2=Fire, 3=Nature, 4=Frost, 5=Shadow, 6=Arcane. Returns 0 if not received. int32_t getResistance(int school) const { if (school < 1 || school > 6) return 0; return playerResistances_[school - 1]; } // Server-authoritative primary stats (UNIT_FIELD_STAT0-4: STR, AGI, STA, INT, SPI). // Returns -1 if the server hasn't sent the value yet. int32_t getPlayerStat(int idx) const { if (idx < 0 || idx > 4) return -1; return playerStats_[idx]; } // Server-authoritative attack power (WotLK: UNIT_FIELD_ATTACK_POWER / RANGED). // Returns -1 if not yet received. int32_t getMeleeAttackPower() const { return playerMeleeAP_; } int32_t getRangedAttackPower() const { return playerRangedAP_; } // Server-authoritative spell damage / healing bonus (WotLK: PLAYER_FIELD_MOD_*). // getSpellPower returns the max damage bonus across magic schools 1-6 (Holy/Fire/Nature/Frost/Shadow/Arcane). // Returns -1 if not yet received. int32_t getSpellPower() const { int32_t sp = -1; for (int i = 1; i <= 6; ++i) { if (playerSpellDmgBonus_[i] > sp) sp = playerSpellDmgBonus_[i]; } return sp; } int32_t getHealingPower() const { return playerHealBonus_; } // Server-authoritative combat chance percentages (WotLK: PLAYER_* float fields). // Returns -1.0f if not yet received. float getDodgePct() const { return playerDodgePct_; } float getParryPct() const { return playerParryPct_; } float getBlockPct() const { return playerBlockPct_; } float getCritPct() const { return playerCritPct_; } float getRangedCritPct() const { return playerRangedCritPct_; } // Spell crit by school (0=Physical,1=Holy,2=Fire,3=Nature,4=Frost,5=Shadow,6=Arcane) float getSpellCritPct(int school = 1) const { if (school < 0 || school > 6) return -1.0f; return playerSpellCritPct_[school]; } // Server-authoritative combat ratings (WotLK: PLAYER_FIELD_COMBAT_RATING_1+idx). // Returns -1 if not yet received. Indices match AzerothCore CombatRating enum. int32_t getCombatRating(int cr) const { if (cr < 0 || cr > 24) return -1; return playerCombatRatings_[cr]; } // Inventory Inventory& getInventory() { return inventory; } const Inventory& getInventory() const { return inventory; } bool consumeOnlineEquipmentDirty() { bool d = onlineEquipDirty_; onlineEquipDirty_ = false; return d; } void resetEquipmentDirtyTracking() { lastEquipDisplayIds_ = {}; onlineEquipDirty_ = true; } void unequipToBackpack(EquipSlot equipSlot); // Targeting void setTarget(uint64_t guid); void clearTarget(); uint64_t getTargetGuid() const { return targetGuid; } std::shared_ptr getTarget() const; bool hasTarget() const { return targetGuid != 0; } void tabTarget(float playerX, float playerY, float playerZ); // Focus targeting void setFocus(uint64_t guid); void clearFocus(); uint64_t getFocusGuid() const { return focusGuid; } std::shared_ptr getFocus() const; bool hasFocus() const { return focusGuid != 0; } // Advanced targeting void targetLastTarget(); void targetEnemy(bool reverse = false); void targetFriend(bool reverse = false); // 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 itemEntries{}; // 0=head…18=ranged std::array enchantIds{}; // permanent enchant per slot (0 = none) std::vector arenaTeams; // from MSG_INSPECT_ARENA_TEAMS (WotLK) }; const InspectResult* getInspectResult() const { return inspectResult_.guid ? &inspectResult_ : nullptr; } // Server info commands void queryServerTime(); void requestPlayedTime(); void queryWho(const std::string& playerName = ""); 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; }; const std::vector& getWhoResults() const { return whoResults_; } uint32_t getWhoOnlineCount() const { return whoOnlineCount_; } std::string getWhoAreaName(uint32_t zoneId) const { return getAreaName(zoneId); } // 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); const std::unordered_map& getIgnoreCache() const { return ignoreCache; } // 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{}; }; // 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 instanceIds; }; // Battleground bool hasPendingBgInvite() const; void acceptBattlefield(uint32_t queueSlot = 0xFFFFFFFF); void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF); const std::array& getBgQueues() const { return bgQueues_; } const std::vector& 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> 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 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 }; void requestPvpLog(); const BgScoreboardData* getBgScoreboard() const { return bgScoreboard_.players.empty() ? nullptr : &bgScoreboard_; } // Network latency (milliseconds, updated each PONG response) uint32_t getLatencyMs() const { return lastLatency; } // Logout commands void requestLogout(); void cancelLogout(); bool isLoggingOut() const { return loggingOut_; } float getLogoutCountdown() const { return logoutCountdown_; } // Stand state void setStandState(uint8_t state); // 0=stand, 1=sit, 2=sit_chair, 3=sleep, 4=sit_low_chair, 5=sit_medium_chair, 6=sit_high_chair, 7=dead, 8=kneel, 9=submerged uint8_t getStandState() const { return standState_; } bool isSitting() const { return standState_ >= 1 && standState_ <= 6; } bool isDead() const { return standState_ == 7; } bool isKneeling() const { return standState_ == 8; } // Display toggles void toggleHelm(); void toggleCloak(); bool isHelmVisible() const { return helmVisible_; } bool isCloakVisible() const { return cloakVisible_; } // Follow/Assist void followTarget(); void cancelFollow(); // Stop following current target void assistTarget(); // PvP void togglePvp(); // Minimap ping (Ctrl+click on minimap; wowX/wowY in canonical WoW coords) void sendMinimapPing(float wowX, float wowY); // Guild commands 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(); // GM Ticket void submitGmTicket(const std::string& text); void deleteGmTicket(); void requestGmTicket(); ///< Send CMSG_GMTICKET_GETTICKET to query open ticket // GM ticket status accessors bool hasActiveGmTicket() const { return gmTicketActive_; } const std::string& getGmTicketText() const { return gmTicketText_; } bool isGmSupportAvailable() const { return gmSupportAvailable_; } float getGmTicketWaitHours() const { return gmTicketWaitHours_; } // Battlefield Manager (Wintergrasp) bool hasBfMgrInvite() const { return bfMgrInvitePending_; } bool isInBfMgrZone() const { return bfMgrActive_; } uint32_t getBfMgrZoneId() const { return bfMgrZoneId_; } void acceptBfMgrInvite(); void declineBfMgrInvite(); // WotLK Calendar uint32_t getCalendarPendingInvites() const { return calendarPendingInvites_; } void requestCalendar(); ///< Send CMSG_CALENDAR_GET_CALENDAR to the server 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 { if (!guildName_.empty()) return true; const Character* ch = getActiveCharacter(); return ch && ch->hasGuild(); } const std::string& getGuildName() const { return guildName_; } const GuildRosterData& getGuildRoster() const { return guildRoster_; } bool hasGuildRoster() const { return hasGuildRoster_; } const std::vector& 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(); } bool hasPetitionShowlist() const { return showPetitionDialog_; } void clearPetitionDialog() { showPetitionDialog_ = false; } uint32_t getPetitionCost() const { return petitionCost_; } uint64_t getPetitionNpcGuid() const { return petitionNpcGuid_; } // Ready check struct ReadyCheckResult { std::string name; bool ready = false; }; 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& getReadyCheckResults() const { return readyCheckResults_; } // Duel void forfeitDuel(); // AFK/DND status void toggleAfk(const std::string& message = ""); void toggleDnd(const std::string& message = ""); bool isAfk() const { return afkStatus_; } bool isDnd() const { return dndStatus_; } void replyToLastWhisper(const std::string& message); std::string getLastWhisperSender() const { return lastWhisperSender_; } void setLastWhisperSender(const std::string& name) { lastWhisperSender_ = name; } // Party/Raid management void uninvitePlayer(const std::string& playerName); void leaveParty(); void setMainTank(uint64_t targetGuid); void setMainAssist(uint64_t targetGuid); void clearMainTank(); void clearMainAssist(); void requestRaidInfo(); // Combat and Trade void proposeDuel(uint64_t targetGuid); void initiateTrade(uint64_t targetGuid); void stopCasting(); // ---- Phase 1: Name queries ---- void queryPlayerName(uint64_t guid); void queryCreatureInfo(uint32_t entry, uint64_t guid); void queryGameObjectInfo(uint32_t entry, uint64_t guid); const GameObjectQueryResponseData* getCachedGameObjectInfo(uint32_t entry) const { auto it = gameObjectInfoCache_.find(entry); return (it != gameObjectInfoCache_.end()) ? &it->second : nullptr; } std::string getCachedPlayerName(uint64_t guid) const; std::string getCachedCreatureName(uint32_t entry) const; // Returns the creature subname/title (e.g. ""), empty if not cached std::string getCachedCreatureSubName(uint32_t entry) const { auto it = creatureInfoCache.find(entry); return (it != creatureInfoCache.end()) ? it->second.subName : ""; } // Returns the creature rank (0=Normal,1=Elite,2=RareElite,3=Boss,4=Rare) // or -1 if not cached yet int getCreatureRank(uint32_t entry) const { auto it = creatureInfoCache.find(entry); return (it != creatureInfoCache.end()) ? static_cast(it->second.rank) : -1; } // ---- Phase 2: Combat ---- 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; } // 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& getCombatText() const { return combatText; } void updateCombatText(float deltaTime); // Combat log (persistent rolling history, max MAX_COMBAT_LOG entries) const std::deque& getCombatLog() const { return combatLog_; } void clearCombatLog() { combatLog_.clear(); } // Area trigger messages (SMSG_AREA_TRIGGER_MESSAGE) — drained by UI each frame bool hasAreaTriggerMsg() const { return !areaTriggerMsgs_.empty(); } std::string popAreaTriggerMsg() { if (areaTriggerMsgs_.empty()) return {}; std::string msg = areaTriggerMsgs_.front(); areaTriggerMsgs_.pop_front(); return msg; } // 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* 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* getTargetThreatList() const { return targetGuid ? getThreatList(targetGuid) : nullptr; } // ---- Phase 3: Spells ---- void castSpell(uint32_t spellId, uint64_t targetGuid = 0); void cancelCast(); void cancelAura(uint32_t spellId); void dismissPet(); void renamePet(const std::string& newName); bool hasPet() const { return petGuid_ != 0; } uint64_t getPetGuid() const { return petGuid_; } // ---- Pet state (populated by SMSG_PET_SPELLS / SMSG_PET_MODE) ---- // 10 action bar slots; each entry is a packed uint32: // bits 0-23 = spell ID (or 0 for empty) // bits 24-31 = action type (0x00=cast, 0xC0=autocast on, 0x40=autocast off) static constexpr int PET_ACTION_BAR_SLOTS = 10; uint32_t getPetActionSlot(int idx) const { if (idx < 0 || idx >= PET_ACTION_BAR_SLOTS) return 0; return petActionSlots_[idx]; } // Pet command/react state from SMSG_PET_MODE or SMSG_PET_SPELLS uint8_t getPetCommand() const { return petCommand_; } // 0=stay,1=follow,2=attack,3=dismiss uint8_t getPetReact() const { return petReact_; } // 0=passive,1=defensive,2=aggressive // Spells the pet knows (from SMSG_PET_SPELLS spell list) const std::vector& getPetSpells() const { return petSpellList_; } // Pet autocast set (spellIds that have autocast enabled) bool isPetSpellAutocast(uint32_t spellId) const { return petAutocastSpells_.count(spellId) != 0; } // Send CMSG_PET_ACTION to issue a pet command void sendPetAction(uint32_t action, uint64_t targetGuid = 0); const std::unordered_set& getKnownSpells() const { return knownSpells; } // ---- Pet Stable ---- struct StabledPet { uint32_t petNumber = 0; // server-side pet number (used for unstable/swap) uint32_t entry = 0; // creature entry ID uint32_t level = 0; std::string name; uint32_t displayId = 0; bool isActive = false; // true = currently summoned/active slot }; bool isStableWindowOpen() const { return stableWindowOpen_; } void closeStableWindow() { stableWindowOpen_ = false; } uint64_t getStableMasterGuid() const { return stableMasterGuid_; } uint8_t getStableSlots() const { return stableNumSlots_; } const std::vector& getStabledPets() const { return stabledPets_; } void requestStabledPetList(); // CMSG MSG_LIST_STABLED_PETS void stablePet(uint8_t slot); // CMSG_STABLE_PET (store active pet in slot) void unstablePet(uint32_t petNumber); // CMSG_UNSTABLE_PET (retrieve to active) // Player proficiency bitmasks (from SMSG_SET_PROFICIENCY) // itemClass 2 = Weapon (subClassMask bits: 0=Axe1H,1=Axe2H,2=Bow,3=Gun,4=Mace1H,5=Mace2H,6=Polearm,7=Sword1H,8=Sword2H,10=Staff,13=Fist,14=Misc,15=Dagger,16=Thrown,17=Crossbow,18=Wand,19=Fishing) // itemClass 4 = Armor (subClassMask bits: 1=Cloth,2=Leather,3=Mail,4=Plate,6=Shield) uint32_t getWeaponProficiency() const { return weaponProficiency_; } uint32_t getArmorProficiency() const { return armorProficiency_; } bool canUseWeaponSubclass(uint32_t subClass) const { return (weaponProficiency_ >> subClass) & 1u; } bool canUseArmorSubclass(uint32_t subClass) const { return (armorProficiency_ >> subClass) & 1u; } // Minimap pings from party members struct MinimapPing { uint64_t senderGuid = 0; float wowX = 0.0f; // canonical WoW X (north) float wowY = 0.0f; // canonical WoW Y (west) float age = 0.0f; // seconds since received static constexpr float LIFETIME = 5.0f; bool isExpired() const { return age >= LIFETIME; } }; const std::vector& getMinimapPings() const { return minimapPings_; } void tickMinimapPings(float dt) { for (auto& p : minimapPings_) p.age += dt; minimapPings_.erase( std::remove_if(minimapPings_.begin(), minimapPings_.end(), [](const MinimapPing& p){ return p.isExpired(); }), minimapPings_.end()); } bool isCasting() const { return casting; } bool isChanneling() const { return casting && castIsChannel; } bool isGameObjectInteractionCasting() const { return casting && currentCastSpellId == 0 && pendingGameObjectInteractGuid_ != 0; } uint32_t getCurrentCastSpellId() const { return currentCastSpellId; } float getCastProgress() const { return castTimeTotal > 0 ? (castTimeTotal - castTimeRemaining) / castTimeTotal : 0.0f; } float getCastTimeRemaining() const { return castTimeRemaining; } // Unit cast state (tracked per GUID for target frame + boss frames) struct UnitCastState { bool casting = false; uint32_t spellId = 0; float timeRemaining = 0.0f; float timeTotal = 0.0f; }; // Returns cast state for any unit by GUID (empty/non-casting if not found) const UnitCastState* getUnitCastState(uint64_t guid) const { auto it = unitCastStates_.find(guid); return (it != unitCastStates_.end() && it->second.casting) ? &it->second : 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; } // 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& getLearnedTalents() const { return learnedTalents_[activeTalentSpec_]; } const std::unordered_map& getLearnedTalents(uint8_t spec) const { static std::unordered_map empty; return spec < 2 ? learnedTalents_[spec] : empty; } // Glyphs (WotLK): up to 6 glyph slots per spec (3 major + 3 minor) static constexpr uint8_t MAX_GLYPH_SLOTS = 6; const std::array& getGlyphs() const { return learnedGlyphs_[activeTalentSpec_]; } const std::array& getGlyphs(uint8_t spec) const { static std::array 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& getAllTalents() const { return talentCache_; } const std::unordered_map& getAllTalentTabs() const { return talentTabCache_; } void loadTalentDbc(); // Action bar — 4 bars × 12 slots = 48 total // Bar 0 (slots 0-11): main bottom bar (1-0, -, =) // Bar 1 (slots 12-23): second bar above main (Shift+1 ... Shift+=) // Bar 2 (slots 24-35): right side vertical bar // Bar 3 (slots 36-47): left side vertical bar static constexpr int SLOTS_PER_BAR = 12; static constexpr int ACTION_BARS = 4; static constexpr int ACTION_BAR_SLOTS = SLOTS_PER_BAR * ACTION_BARS; // 48 std::array& getActionBar() { return actionBar; } const std::array& getActionBar() const { return actionBar; } void setActionBarSlot(int slot, ActionBarSlot::Type type, uint32_t id); void saveCharacterConfig(); void loadCharacterConfig(); static std::string getCharacterConfigDir(); // Auras const std::vector& getPlayerAuras() const { return playerAuras; } const std::vector& getTargetAuras() const { return targetAuras; } // Per-unit aura cache (populated for party members and any unit we receive updates for) const std::vector* getUnitAuras(uint64_t guid) const { auto it = unitAurasCache_.find(guid); return (it != unitAurasCache_.end()) ? &it->second : nullptr; } // Completed quests (populated from SMSG_QUERY_QUESTS_COMPLETED_RESPONSE) bool isQuestCompleted(uint32_t questId) const { return completedQuests_.count(questId) > 0; } const std::unordered_set& getCompletedQuests() const { return completedQuests_; } // NPC death callback (for animations) using NpcDeathCallback = std::function; void setNpcDeathCallback(NpcDeathCallback cb) { npcDeathCallback_ = std::move(cb); } using NpcAggroCallback = std::function; void setNpcAggroCallback(NpcAggroCallback cb) { npcAggroCallback_ = std::move(cb); } // NPC respawn callback (health 0 → >0, resets animation to idle) using NpcRespawnCallback = std::function; void setNpcRespawnCallback(NpcRespawnCallback cb) { npcRespawnCallback_ = std::move(cb); } // Stand state animation callback — fired when SMSG_STANDSTATE_UPDATE confirms a new state // standState: 0=stand, 1-6=sit variants, 7=dead, 8=kneel using StandStateCallback = std::function; void setStandStateCallback(StandStateCallback cb) { standStateCallback_ = std::move(cb); } // Ghost state callback — fired when player enters or leaves ghost (spirit) form using GhostStateCallback = std::function; void setGhostStateCallback(GhostStateCallback cb) { ghostStateCallback_ = std::move(cb); } // Melee swing callback (for driving animation/SFX) using MeleeSwingCallback = std::function; void setMeleeSwingCallback(MeleeSwingCallback cb) { meleeSwingCallback_ = std::move(cb); } // Spell cast animation callbacks — true=start cast/channel, false=finish/cancel // guid: caster (may be player or another unit), isChannel: channel vs regular cast using SpellCastAnimCallback = std::function; void setSpellCastAnimCallback(SpellCastAnimCallback cb) { spellCastAnimCallback_ = std::move(cb); } // Unit animation hint: signal jump (animId=38) for other players/NPCs using UnitAnimHintCallback = std::function; void setUnitAnimHintCallback(UnitAnimHintCallback cb) { unitAnimHintCallback_ = std::move(cb); } // Unit move-flags callback: fired on every MSG_MOVE_* for other players with the raw flags field. // Drives Walk(4) vs Run(5) selection and swim state initialization from heartbeat packets. using UnitMoveFlagsCallback = std::function; void setUnitMoveFlagsCallback(UnitMoveFlagsCallback cb) { unitMoveFlagsCallback_ = std::move(cb); } // NPC swing callback (plays attack animation on NPC) using NpcSwingCallback = std::function; void setNpcSwingCallback(NpcSwingCallback cb) { npcSwingCallback_ = std::move(cb); } // NPC greeting callback (plays voice line when NPC is clicked) using NpcGreetingCallback = std::function; void setNpcGreetingCallback(NpcGreetingCallback cb) { npcGreetingCallback_ = std::move(cb); } using NpcFarewellCallback = std::function; void setNpcFarewellCallback(NpcFarewellCallback cb) { npcFarewellCallback_ = std::move(cb); } using NpcVendorCallback = std::function; void setNpcVendorCallback(NpcVendorCallback cb) { npcVendorCallback_ = std::move(cb); } // XP tracking uint32_t getPlayerXp() const { return playerXp_; } uint32_t getPlayerNextLevelXp() const { return playerNextLevelXp_; } uint32_t getPlayerRestedXp() const { return playerRestedXp_; } bool isPlayerResting() const { return isResting_; } uint32_t getPlayerLevel() const { return serverPlayerLevel_; } const std::vector& getPlayerExploredZoneMasks() const { return playerExploredZones_; } bool hasPlayerExploredZoneMasks() const { return hasPlayerExploredZones_; } static uint32_t killXp(uint32_t playerLevel, uint32_t victimLevel); // Server time (for deterministic moon phases, etc.) float getGameTime() const { return gameTime_; } float getTimeSpeed() const { return timeSpeed_; } // Global Cooldown (GCD) — set when the server sends a spellId=0 cooldown entry float getGCDRemaining() const { if (gcdTotal_ <= 0.0f) return 0.0f; auto elapsed = std::chrono::duration_cast( 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; } // Weather state (updated by SMSG_WEATHER) // weatherType: 0=clear, 1=rain, 2=snow, 3=storm/fog uint32_t getWeatherType() const { return weatherType_; } float getWeatherIntensity() const { return weatherIntensity_; } bool isRaining() const { return weatherType_ == 1 && weatherIntensity_ > 0.05f; } bool isSnowing() const { return weatherType_ == 2 && weatherIntensity_ > 0.05f; } uint32_t getOverrideLightId() const { return overrideLightId_; } uint32_t getOverrideLightTransMs() const { return overrideLightTransMs_; } // Player skills const std::map& getPlayerSkills() const { return playerSkills_; } const std::string& getSkillName(uint32_t skillId) const; uint32_t getSkillCategory(uint32_t skillId) const; // World entry callback (online mode - triggered when entering world) // Parameters: mapId, x, y, z (canonical WoW coords), isInitialEntry=true on first login or reconnect using WorldEntryCallback = std::function; void setWorldEntryCallback(WorldEntryCallback cb) { worldEntryCallback_ = std::move(cb); } // Knockback callback: called when server sends SMSG_MOVE_KNOCK_BACK for the player. // Parameters: vcos, vsin (render-space direction), hspeed, vspeed (raw from packet). using KnockBackCallback = std::function; void setKnockBackCallback(KnockBackCallback cb) { knockBackCallback_ = std::move(cb); } // Camera shake callback: called when server sends SMSG_CAMERA_SHAKE. // Parameters: magnitude (world units), frequency (Hz), duration (seconds). using CameraShakeCallback = std::function; void setCameraShakeCallback(CameraShakeCallback cb) { cameraShakeCallback_ = std::move(cb); } // Unstuck callback (resets player Z to floor height) using UnstuckCallback = std::function; void setUnstuckCallback(UnstuckCallback cb) { unstuckCallback_ = std::move(cb); } void unstuck(); void setUnstuckGyCallback(UnstuckCallback cb) { unstuckGyCallback_ = std::move(cb); } void unstuckGy(); void setUnstuckHearthCallback(UnstuckCallback cb) { unstuckHearthCallback_ = std::move(cb); } void unstuckHearth(); using BindPointCallback = std::function; void setBindPointCallback(BindPointCallback cb) { bindPointCallback_ = std::move(cb); } // Called when the player starts casting Hearthstone so terrain at the bind // point can be pre-loaded during the cast time. // Parameters: mapId and canonical (x, y, z) of the bind location. using HearthstonePreloadCallback = std::function; void setHearthstonePreloadCallback(HearthstonePreloadCallback cb) { hearthstonePreloadCallback_ = std::move(cb); } // Creature spawn callback (online mode - triggered when creature enters view) // Parameters: guid, displayId, x, y, z (canonical), orientation, scale (OBJECT_FIELD_SCALE_X) using CreatureSpawnCallback = std::function; void setCreatureSpawnCallback(CreatureSpawnCallback cb) { creatureSpawnCallback_ = std::move(cb); } // Creature despawn callback (online mode - triggered when creature leaves view) using CreatureDespawnCallback = std::function; void setCreatureDespawnCallback(CreatureDespawnCallback cb) { creatureDespawnCallback_ = std::move(cb); } // Player spawn callback (online mode - triggered when a player enters view). // Players need appearance data so the renderer can build the right body/hair textures. using PlayerSpawnCallback = std::function; void setPlayerSpawnCallback(PlayerSpawnCallback cb) { playerSpawnCallback_ = std::move(cb); } using PlayerDespawnCallback = std::function; void setPlayerDespawnCallback(PlayerDespawnCallback cb) { playerDespawnCallback_ = std::move(cb); } // Online player equipment visuals callback. // Sends a best-effort view of equipped items for players in view using ItemDisplayInfo IDs. // Arrays are indexed by EquipSlot (0..18). Values are 0 when unknown/unavailable. using PlayerEquipmentCallback = std::function& displayInfoIds, const std::array& inventoryTypes)>; void setPlayerEquipmentCallback(PlayerEquipmentCallback cb) { playerEquipmentCallback_ = std::move(cb); } // GameObject spawn callback (online mode - triggered when gameobject enters view) // Parameters: guid, entry, displayId, x, y, z (canonical), orientation, scale (OBJECT_FIELD_SCALE_X) using GameObjectSpawnCallback = std::function; void setGameObjectSpawnCallback(GameObjectSpawnCallback cb) { gameObjectSpawnCallback_ = std::move(cb); } // GameObject move callback (online mode - triggered when gameobject position updates) // Parameters: guid, x, y, z (canonical), orientation using GameObjectMoveCallback = std::function; void setGameObjectMoveCallback(GameObjectMoveCallback cb) { gameObjectMoveCallback_ = std::move(cb); } // GameObject despawn callback (online mode - triggered when gameobject leaves view) using GameObjectDespawnCallback = std::function; void setGameObjectDespawnCallback(GameObjectDespawnCallback cb) { gameObjectDespawnCallback_ = std::move(cb); } using GameObjectCustomAnimCallback = std::function; void setGameObjectCustomAnimCallback(GameObjectCustomAnimCallback cb) { gameObjectCustomAnimCallback_ = std::move(cb); } // Faction hostility map (populated from FactionTemplate.dbc by Application) void setFactionHostileMap(std::unordered_map map) { factionHostileMap_ = std::move(map); } // Creature move callback (online mode - triggered by SMSG_MONSTER_MOVE) // Parameters: guid, x, y, z (canonical), duration_ms (0 = instant) using CreatureMoveCallback = std::function; void setCreatureMoveCallback(CreatureMoveCallback cb) { creatureMoveCallback_ = std::move(cb); } // Transport move callback (online mode - triggered when transport position updates) // Parameters: guid, x, y, z (canonical), orientation using TransportMoveCallback = std::function; void setTransportMoveCallback(TransportMoveCallback cb) { transportMoveCallback_ = std::move(cb); } // Transport spawn callback (online mode - triggered when transport GameObject is first detected) // Parameters: guid, entry, displayId, x, y, z (canonical), orientation using TransportSpawnCallback = std::function; void setTransportSpawnCallback(TransportSpawnCallback cb) { transportSpawnCallback_ = std::move(cb); } // Notify that a transport has been spawned (called after WMO instance creation) void notifyTransportSpawned(uint64_t guid, uint32_t entry, uint32_t displayId, float x, float y, float z, float orientation) { if (transportSpawnCallback_) { transportSpawnCallback_(guid, entry, displayId, x, y, z, orientation); } } // Transport state for player-on-transport bool isOnTransport() const { return playerTransportGuid_ != 0; } uint64_t getPlayerTransportGuid() const { return playerTransportGuid_; } glm::vec3 getPlayerTransportOffset() const { return playerTransportOffset_; } // Check if a GUID is a known transport bool isTransportGuid(uint64_t guid) const { return transportGuids_.count(guid) > 0; } bool hasServerTransportUpdate(uint64_t guid) const { return serverUpdatedTransportGuids_.count(guid) > 0; } glm::vec3 getComposedWorldPosition(); // Compose transport transform * local offset TransportManager* getTransportManager() { return transportManager_.get(); } void setPlayerOnTransport(uint64_t transportGuid, const glm::vec3& localOffset) { // Validate transport is registered before attaching player // (defer if transport not yet registered to prevent desyncs) if (transportGuid != 0 && !isTransportGuid(transportGuid)) { return; // Transport not yet registered; skip attachment } playerTransportGuid_ = transportGuid; playerTransportOffset_ = localOffset; playerTransportStickyGuid_ = transportGuid; playerTransportStickyTimer_ = 8.0f; movementInfo.transportGuid = transportGuid; } void setPlayerTransportOffset(const glm::vec3& offset) { playerTransportOffset_ = offset; } void clearPlayerTransport() { if (playerTransportGuid_ != 0) { playerTransportStickyGuid_ = playerTransportGuid_; playerTransportStickyTimer_ = std::max(playerTransportStickyTimer_, 1.5f); } playerTransportGuid_ = 0; playerTransportOffset_ = glm::vec3(0.0f); movementInfo.transportGuid = 0; } // Cooldowns float getSpellCooldown(uint32_t spellId) const; const std::unordered_map& getSpellCooldowns() const { return spellCooldowns; } // Player GUID uint64_t getPlayerGuid() const { return playerGuid; } // Look up a display name for any guid: checks playerNameCache then entity manager. // Returns empty string if unknown. Used by chat display to resolve names at render time. const std::string& lookupName(uint64_t guid) const { static const std::string kEmpty; auto it = playerNameCache.find(guid); if (it != playerNameCache.end()) return it->second; auto entity = entityManager.getEntity(guid); if (entity) { if (auto* unit = dynamic_cast(entity.get())) { if (!unit->getName().empty()) return unit->getName(); } } return kEmpty; } uint8_t getPlayerClass() const { const Character* ch = getActiveCharacter(); return ch ? static_cast(ch->characterClass) : 0; } uint8_t getPlayerRace() const { const Character* ch = getActiveCharacter(); return ch ? static_cast(ch->race) : 0; } void setPlayerGuid(uint64_t guid) { playerGuid = guid; } // Player death state bool isPlayerDead() const { return playerDead_; } bool isPlayerGhost() const { return releasedSpirit_; } bool showDeathDialog() const { return playerDead_ && !releasedSpirit_; } bool showResurrectDialog() const { return resurrectRequestPending_; } const std::string& getResurrectCasterName() const { return resurrectCasterName_; } bool showTalentWipeConfirmDialog() const { return talentWipePending_; } uint32_t getTalentWipeCost() const { return talentWipeCost_; } void confirmTalentWipe(); void cancelTalentWipe() { talentWipePending_ = false; } /** True when ghost is within 40 yards of corpse position (same map). */ bool canReclaimCorpse() const; /** Distance (yards) from ghost to corpse, or -1 if no corpse data. */ float getCorpseDistance() const { if (corpseMapId_ == 0 || currentMapId_ != corpseMapId_) return -1.0f; // movementInfo is canonical (x=north=server_y, y=west=server_x); // corpse coords are raw server (x=west, y=north) — swap to compare. float dx = movementInfo.x - corpseY_; float dy = movementInfo.y - corpseX_; float dz = movementInfo.z - corpseZ_; return std::sqrt(dx*dx + dy*dy + dz*dz); } /** Corpse position in canonical WoW coords (X=north, Y=west). * Returns false if no corpse data or on a different map. */ bool getCorpseCanonicalPos(float& outX, float& outY) const { if (corpseMapId_ == 0 || currentMapId_ != corpseMapId_) return false; outX = corpseY_; // server Y = canonical X (north) outY = corpseX_; // server X = canonical Y (west) return true; } /** Send CMSG_RECLAIM_CORPSE; noop if not a ghost or not near corpse. */ void reclaimCorpse(); void releaseSpirit(); void acceptResurrect(); void declineResurrect(); // ---- Phase 4: Group ---- void inviteToGroup(const std::string& playerName); void acceptGroupInvite(); void declineGroupInvite(); void leaveGroup(); bool isInGroup() const { return !partyData.isEmpty(); } const GroupListData& getPartyData() const { return partyData; } const std::vector& getContacts() const { return contacts_; } bool hasPendingGroupInvite() const { return pendingGroupInvite; } const std::string& getPendingInviterName() const { return pendingInviterName; } // ---- 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); // ---- 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(); // ---- Summon ---- bool hasPendingSummonRequest() const { return pendingSummonRequest_; } const std::string& getSummonerName() const { return summonerName_; } float getSummonTimeoutSec() const { return summonTimeoutSec_; } void acceptSummon(); void declineSummon(); void tickSummonTimeout(float dt) { if (!pendingSummonRequest_) return; summonTimeoutSec_ -= dt; if (summonTimeoutSec_ <= 0.0f) { pendingSummonRequest_ = false; summonTimeoutSec_ = 0.0f; } } // ---- Trade ---- enum class TradeStatus : uint8_t { None = 0, PendingIncoming, Open, Accepted, Complete }; static constexpr int TRADE_SLOT_COUNT = 6; // WoW has 6 normal trade slots + slot 6 for non-trade item struct TradeSlot { uint32_t itemId = 0; uint32_t displayId = 0; uint32_t stackCount = 0; uint64_t itemGuid = 0; uint8_t bag = 0xFF; // 0xFF = not set uint8_t bagSlot = 0xFF; bool occupied = false; }; TradeStatus getTradeStatus() const { return tradeStatus_; } bool hasPendingTradeRequest() const { return tradeStatus_ == TradeStatus::PendingIncoming; } bool isTradeOpen() const { return tradeStatus_ == TradeStatus::Open || tradeStatus_ == TradeStatus::Accepted; } const std::string& getTradePeerName() const { return tradePeerName_; } // My trade slots (what I'm offering) const std::array& getMyTradeSlots() const { return myTradeSlots_; } // Peer's trade slots (what they're offering) const std::array& getPeerTradeSlots() const { return peerTradeSlots_; } uint64_t getMyTradeGold() const { return myTradeGold_; } uint64_t getPeerTradeGold() const { return peerTradeGold_; } void acceptTradeRequest(); // respond to incoming SMSG_TRADE_STATUS(1) with CMSG_BEGIN_TRADE void declineTradeRequest(); // respond with CMSG_CANCEL_TRADE void acceptTrade(); // lock in offer: CMSG_ACCEPT_TRADE void cancelTrade(); // CMSG_CANCEL_TRADE void setTradeItem(uint8_t tradeSlot, uint8_t bag, uint8_t bagSlot); void clearTradeItem(uint8_t tradeSlot); void setTradeGold(uint64_t copper); // ---- Duel ---- bool hasPendingDuelRequest() const { return pendingDuelRequest_; } const std::string& getDuelChallengerName() const { return duelChallengerName_; } void acceptDuel(); // forfeitDuel() already declared at line ~399 // Returns remaining duel countdown seconds, or 0 if no active countdown float getDuelCountdownRemaining() const { if (duelCountdownMs_ == 0) return 0.0f; auto elapsed = std::chrono::duration_cast( std::chrono::steady_clock::now() - duelCountdownStartedAt_).count(); float rem = (static_cast(duelCountdownMs_) - static_cast(elapsed)) / 1000.0f; 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; }; const std::vector& getInstanceLockouts() const { return instanceLockouts_; } // Boss encounter unit tracking (SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT) static constexpr uint32_t kMaxEncounterSlots = 5; // Returns boss unit guid for the given encounter slot (0 if none) uint64_t getEncounterUnitGuid(uint32_t slot) const { return (slot < kMaxEncounterSlots) ? encounterUnitGuids_[slot] : 0; } // Raid target markers (MSG_RAID_TARGET_UPDATE) // Icon indices 0-7: Star, Circle, Diamond, Triangle, Moon, Square, Cross, Skull static constexpr uint32_t kRaidMarkCount = 8; // Returns the GUID marked with the given icon (0 = no mark) uint64_t getRaidMarkGuid(uint32_t icon) const { return (icon < kRaidMarkCount) ? raidTargetGuids_[icon] : 0; } // Returns the raid mark icon for a given guid (0xFF = no mark) 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(i); return 0xFF; } // Set or clear a raid mark on a guid (icon 0-7, or 0xFF to clear) 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, }; // roles bitmask: 0x02=tank, 0x04=healer, 0x08=dps; pass LFGDungeonEntry ID void lfgJoin(uint32_t dungeonId, uint8_t roles); void lfgLeave(); 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 { return getLfgDungeonName(lfgDungeonId_); } std::string getMapName(uint32_t mapId) 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 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; }; const std::vector& getArenaTeamStats() const { return arenaTeamStats_; } // ---- 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 members; }; // Returns roster for the given teamId, or nullptr if not yet received const ArenaTeamRoster* getArenaTeamRoster(uint32_t teamId) const { for (const auto& r : arenaTeamRosters_) { if (r.teamId == teamId) return &r; } return nullptr; } // ---- Phase 5: Loot ---- void lootTarget(uint64_t guid); void lootItem(uint8_t slotIndex); void closeLoot(); void activateSpiritHealer(uint64_t npcGuid); bool isLootWindowOpen() const { return lootWindowOpen; } const LootResponseData& getCurrentLoot() const { return currentLoot; } void setAutoLoot(bool enabled) { autoLoot_ = enabled; } bool isAutoLoot() const { return autoLoot_; } // Master loot candidates (from SMSG_LOOT_MASTER_LIST) const std::vector& getMasterLootCandidates() const { return masterLootCandidates_; } 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 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 playerRolls; // live roll results from group members }; 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; }; const std::vector& getEquipmentSets() const { return equipmentSetInfo_; } void useEquipmentSet(uint32_t setId); // NPC Gossip void interactWithNpc(uint64_t guid); void interactWithGameObject(uint64_t guid); void selectGossipOption(uint32_t optionId); void selectGossipQuest(uint32_t questId); void acceptQuest(); void declineQuest(); void closeGossip(); bool isGossipWindowOpen() const { return gossipWindowOpen; } const GossipMessageData& getCurrentGossip() const { return currentGossip; } bool isQuestDetailsOpen() { // Check if delayed opening timer has expired 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 (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; }; const std::vector& getGossipPois() const { return gossipPois_; } void clearGossipPois() { gossipPois_.clear(); } // Quest turn-in bool isQuestRequestItemsOpen() const { return questRequestItemsOpen_; } const QuestRequestItemsData& getQuestRequestItems() const { return currentQuestRequestItems_; } void completeQuest(); // Send CMSG_QUESTGIVER_COMPLETE_QUEST void closeQuestRequestItems(); bool isQuestOfferRewardOpen() const { return questOfferRewardOpen_; } const QuestOfferRewardData& getQuestOfferReward() const { return currentQuestOfferReward_; } void chooseQuestReward(uint32_t rewardIndex); // Send CMSG_QUESTGIVER_CHOOSE_REWARD 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> killCounts; // Quest item progress: itemId -> current count std::unordered_map itemCounts; // Server-authoritative quest item requirements from REQUEST_ITEMS std::unordered_map 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 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 itemObjectives{}; // zeroed by default // Reward data parsed from SMSG_QUEST_QUERY_RESPONSE int32_t rewardMoney = 0; // copper; positive=reward, negative=cost std::array rewardItems{}; // guaranteed reward items std::array rewardChoiceItems{}; // player picks one of these }; const std::vector& getQuestLog() const { return questLog_; } void abandonQuest(uint32_t questId); void shareQuestWithParty(uint32_t questId); // CMSG_PUSHQUESTTOPARTY 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) { if (tracked) trackedQuestIds_.insert(questId); else trackedQuestIds_.erase(questId); } const std::unordered_set& getTrackedQuestIds() const { return trackedQuestIds_; } bool isQuestQueryPending(uint32_t questId) const { return pendingQuestQueryIds_.count(questId) > 0; } void clearQuestQueryPending(uint32_t questId) { pendingQuestQueryIds_.erase(questId); } const std::unordered_map& getWorldStates() const { return worldStates_; } std::optional getWorldState(uint32_t key) const { auto it = worldStates_.find(key); if (it == worldStates_.end()) return std::nullopt; return it->second; } uint32_t getWorldStateMapId() const { return worldStateMapId_; } uint32_t getWorldStateZoneId() const { return worldStateZoneId_; } // Mirror timers (0=fatigue, 1=breath, 2=feigndeath) struct MirrorTimer { int32_t value = 0; int32_t maxValue = 0; int32_t scale = 0; // +1 = counting up, -1 = counting down bool paused = false; bool active = false; }; const MirrorTimer& getMirrorTimer(int type) const { static MirrorTimer empty; return (type >= 0 && type < 3) ? mirrorTimers_[type] : empty; } // Combo points uint8_t getComboPoints() const { return comboPoints_; } uint64_t getComboTarget() const { return comboTarget_; } // Death Knight rune state (6 runes: 0-1=Blood, 2-3=Unholy, 4-5=Frost; may become Death=3) enum class RuneType : uint8_t { Blood = 0, Unholy = 1, Frost = 2, Death = 3 }; struct RuneSlot { RuneType type = RuneType::Blood; bool ready = true; // Server-confirmed ready state float readyFraction = 1.0f; // 0.0=depleted → 1.0=full (from server sync) }; const std::array& getPlayerRunes() const { return playerRunes_; } // Talent-driven spell modifiers (SMSG_SET_FLAT_SPELL_MODIFIER / SMSG_SET_PCT_SPELL_MODIFIER) // SpellModOp matches WotLK SpellModOp enum (server-side). enum class SpellModOp : uint8_t { Damage = 0, Duration = 1, Threat = 2, Effect1 = 3, Charges = 4, Range = 5, Radius = 6, CritChance = 7, AllEffects = 8, NotLoseCastingTime = 9, CastingTime = 10, Cooldown = 11, Effect2 = 12, IgnoreArmor = 13, Cost = 14, CritDamageBonus = 15, ResistMissChance = 16, JumpTargets = 17, ChanceOfSuccess = 18, ActivationTime = 19, Efficiency = 20, MultipleValue = 21, ResistDispelChance = 22, Effect3 = 23, BonusMultiplier = 24, ProcPerMinute = 25, ValueMultiplier = 26, ResistPushback = 27, MechanicDuration = 28, StartCooldown = 29, PeriodicBonus = 30, AttackPower = 31, }; static constexpr int SPELL_MOD_OP_COUNT = 32; // Key: (SpellModOp, groupIndex) — value: accumulated flat or pct modifier // pct values are stored in integer percent (e.g. -20 means -20% reduction). struct SpellModKey { SpellModOp op; uint8_t group; bool operator==(const SpellModKey& o) const { return op == o.op && group == o.group; } }; struct SpellModKeyHash { std::size_t operator()(const SpellModKey& k) const { return std::hash()( (static_cast(static_cast(k.op)) << 8) | k.group); } }; // Returns the sum of all flat modifiers for a given op across all groups. // (Callers that need per-group resolution can use getSpellFlatMods() directly.) int32_t getSpellFlatMod(SpellModOp op) const { int32_t total = 0; for (const auto& [k, v] : spellFlatMods_) if (k.op == op) total += v; return total; } // Returns the sum of all pct modifiers for a given op across all groups (in %). int32_t getSpellPctMod(SpellModOp op) const { int32_t total = 0; for (const auto& [k, v] : spellPctMods_) if (k.op == op) total += v; return total; } // Convenience: apply flat+pct modifier to a base value. // result = (base + flatMod) * (1.0 + pctMod/100.0), clamped to >= 0. static int32_t applySpellMod(int32_t base, int32_t flat, int32_t pct) { int64_t v = static_cast(base) + flat; if (pct != 0) v = v + (v * pct + 50) / 100; // round half-up return static_cast(v < 0 ? 0 : v); } struct FactionStandingInit { uint8_t flags = 0; int32_t standing = 0; }; // Faction flag bitmask constants (from Faction.dbc ReputationFlags / SMSG_INITIALIZE_FACTIONS) static constexpr uint8_t FACTION_FLAG_VISIBLE = 0x01; // shown in reputation list static constexpr uint8_t FACTION_FLAG_AT_WAR = 0x02; // player is at war static constexpr uint8_t FACTION_FLAG_HIDDEN = 0x04; // never shown static constexpr uint8_t FACTION_FLAG_INVISIBLE_FORCED = 0x08; static constexpr uint8_t FACTION_FLAG_PEACE_FORCED = 0x10; const std::vector& getInitialFactions() const { return initialFactions_; } const std::unordered_map& getFactionStandings() const { return factionStandings_; } // Returns true if the player has "at war" toggled for the faction at repListId bool isFactionAtWar(uint32_t repListId) const { if (repListId >= initialFactions_.size()) return false; return (initialFactions_[repListId].flags & FACTION_FLAG_AT_WAR) != 0; } // Returns true if the faction is visible in the reputation list bool isFactionVisible(uint32_t repListId) const { if (repListId >= initialFactions_.size()) return false; const uint8_t f = initialFactions_[repListId].flags; if (f & FACTION_FLAG_HIDDEN) return false; if (f & FACTION_FLAG_INVISIBLE_FORCED) return false; return (f & FACTION_FLAG_VISIBLE) != 0; } // Returns the faction ID for a given repListId (0 if unknown) uint32_t getFactionIdByRepListId(uint32_t repListId) const; // Returns the repListId for a given faction ID (0xFFFFFFFF if not found) uint32_t getRepListIdByFactionId(uint32_t factionId) const; // Shaman totems (4 slots: 0=Earth, 1=Fire, 2=Water, 3=Air) struct TotemSlot { uint32_t spellId = 0; uint32_t durationMs = 0; std::chrono::steady_clock::time_point placedAt{}; bool active() const { return spellId != 0 && remainingMs() > 0; } float remainingMs() const { if (spellId == 0 || durationMs == 0) return 0.0f; auto elapsed = std::chrono::duration_cast( std::chrono::steady_clock::now() - placedAt).count(); float rem = static_cast(durationMs) - static_cast(elapsed); return rem > 0.0f ? rem : 0.0f; } }; static constexpr int NUM_TOTEM_SLOTS = 4; const TotemSlot& getTotemSlot(int slot) const { static TotemSlot empty; return (slot >= 0 && slot < NUM_TOTEM_SLOTS) ? activeTotemSlots_[slot] : empty; } const std::string& getFactionNamePublic(uint32_t factionId) const; uint32_t getWatchedFactionId() const { return watchedFactionId_; } void setWatchedFactionId(uint32_t id) { watchedFactionId_ = id; } uint32_t getLastContactListMask() const { return lastContactListMask_; } uint32_t getLastContactListCount() const { return lastContactListCount_; } bool isServerMovementAllowed() const { return serverMovementAllowed_; } // Quest giver status (! and ? markers) QuestGiverStatus getQuestGiverStatus(uint64_t guid) const { auto it = npcQuestStatus_.find(guid); return (it != npcQuestStatus_.end()) ? it->second : QuestGiverStatus::NONE; } const std::unordered_map& getNpcQuestStatuses() const { return npcQuestStatus_; } // Charge callback — fires when player casts a charge spell toward target // Parameters: targetGuid, targetX, targetY, targetZ (canonical WoW coordinates) using ChargeCallback = std::function; void setChargeCallback(ChargeCallback cb) { chargeCallback_ = std::move(cb); } // Level-up callback — fires when the player gains a level (newLevel > 1) using LevelUpCallback = std::function; void setLevelUpCallback(LevelUpCallback cb) { levelUpCallback_ = std::move(cb); } // Stat deltas from the last SMSG_LEVELUP_INFO (valid until next level-up) struct LevelUpDeltas { uint32_t hp = 0; uint32_t mana = 0; uint32_t str = 0, agi = 0, sta = 0, intel = 0, spi = 0; }; const LevelUpDeltas& getLastLevelUpDeltas() const { return lastLevelUpDeltas_; } // Temporary weapon enchant timers (from SMSG_ITEM_ENCHANT_TIME_UPDATE) // Slot: 0=main-hand, 1=off-hand, 2=ranged. Value: expire time (steady_clock ms). struct TempEnchantTimer { uint32_t slot = 0; uint64_t expireMs = 0; // std::chrono::steady_clock ms timestamp when it expires }; const std::vector& getTempEnchantTimers() const { return tempEnchantTimers_; } // Returns remaining ms for a given slot, or 0 if absent/expired. uint32_t getTempEnchantRemainingMs(uint32_t slot) const; static constexpr const char* kTempEnchantSlotNames[] = { "Main Hand", "Off Hand", "Ranged" }; // ---- Readable text (books / scrolls / notes) ---- // Populated by handlePageTextQueryResponse(); multi-page items chain via nextPageId. struct BookPage { uint32_t pageId = 0; std::string text; }; const std::vector& getBookPages() const { return bookPages_; } bool hasBookOpen() const { return !bookPages_.empty(); } void clearBook() { bookPages_.clear(); } // Other player level-up callback — fires when another player gains a level using OtherPlayerLevelUpCallback = std::function; void setOtherPlayerLevelUpCallback(OtherPlayerLevelUpCallback cb) { otherPlayerLevelUpCallback_ = std::move(cb); } // Achievement earned callback — fires when SMSG_ACHIEVEMENT_EARNED is received using AchievementEarnedCallback = std::function; void setAchievementEarnedCallback(AchievementEarnedCallback cb) { achievementEarnedCallback_ = std::move(cb); } const std::unordered_set& getEarnedAchievements() const { return earnedAchievements_; } // Title system — earned title bits and the currently displayed title const std::unordered_set& getKnownTitleBits() const { return knownTitleBits_; } int32_t getChosenTitleBit() const { return chosenTitleBit_; } /// Returns the formatted title string for a given bit (replaces %s with player name), or empty. std::string getFormattedTitle(uint32_t bit) const; /// Send CMSG_SET_TITLE to activate a title (bit >= 0) or clear it (bit = -1). void sendSetTitle(int32_t bit); // Area discovery callback — fires when SMSG_EXPLORATION_EXPERIENCE is received using AreaDiscoveryCallback = std::function; void setAreaDiscoveryCallback(AreaDiscoveryCallback cb) { areaDiscoveryCallback_ = std::move(cb); } // Quest objective progress callback — fires on SMSG_QUESTUPDATE_ADD_KILL / ADD_ITEM // questTitle: name of the quest; objectiveName: creature/item name; current/required counts using QuestProgressCallback = std::function; void setQuestProgressCallback(QuestProgressCallback cb) { questProgressCallback_ = std::move(cb); } const std::unordered_map& getCriteriaProgress() const { return criteriaProgress_; } /// Returns the WoW PackedTime earn date for an achievement, or 0 if unknown. uint32_t getAchievementDate(uint32_t id) const { auto it = achievementDates_.find(id); return (it != achievementDates_.end()) ? it->second : 0u; } /// Returns the name of an achievement by ID, or empty string if unknown. const std::string& getAchievementName(uint32_t id) const { auto it = achievementNameCache_.find(id); if (it != achievementNameCache_.end()) return it->second; static const std::string kEmpty; return kEmpty; } /// Returns the description of an achievement by ID, or empty string if unknown. const std::string& getAchievementDescription(uint32_t id) const { auto it = achievementDescCache_.find(id); if (it != achievementDescCache_.end()) return it->second; static const std::string kEmpty; return kEmpty; } /// Returns the point value of an achievement by ID, or 0 if unknown. uint32_t getAchievementPoints(uint32_t id) const { auto it = achievementPointsCache_.find(id); return (it != achievementPointsCache_.end()) ? it->second : 0u; } /// Returns the set of achievement IDs earned by an inspected player (via SMSG_RESPOND_INSPECT_ACHIEVEMENTS). /// Returns nullptr if no inspect data is available for the given GUID. const std::unordered_set* getInspectedPlayerAchievements(uint64_t guid) const { auto it = inspectedPlayerAchievements_.find(guid); return (it != inspectedPlayerAchievements_.end()) ? &it->second : nullptr; } // Server-triggered music callback — fires when SMSG_PLAY_MUSIC is received. // The soundId corresponds to a SoundEntries.dbc record. The receiver is // responsible for looking up the file path and forwarding to MusicManager. using PlayMusicCallback = std::function; void setPlayMusicCallback(PlayMusicCallback cb) { playMusicCallback_ = std::move(cb); } // Server-triggered 2-D sound effect callback — fires when SMSG_PLAY_SOUND is received. // The soundId corresponds to a SoundEntries.dbc record. using PlaySoundCallback = std::function; void setPlaySoundCallback(PlaySoundCallback cb) { playSoundCallback_ = std::move(cb); } // Server-triggered 3-D positional sound callback — fires for SMSG_PLAY_OBJECT_SOUND and // SMSG_PLAY_SPELL_IMPACT. Includes sourceGuid so the receiver can look up world position. using PlayPositionalSoundCallback = std::function; void setPlayPositionalSoundCallback(PlayPositionalSoundCallback cb) { playPositionalSoundCallback_ = std::move(cb); } // UI error frame: prominent on-screen error messages (spell can't be cast, etc.) using UIErrorCallback = std::function; void setUIErrorCallback(UIErrorCallback cb) { uiErrorCallback_ = std::move(cb); } void addUIError(const std::string& msg) { if (uiErrorCallback_) uiErrorCallback_(msg); } // Reputation change toast: factionName, delta, new standing using RepChangeCallback = std::function; void setRepChangeCallback(RepChangeCallback cb) { repChangeCallback_ = std::move(cb); } // PvP honor credit callback (honorable kill or BG reward) using PvpHonorCallback = std::function; void setPvpHonorCallback(PvpHonorCallback cb) { pvpHonorCallback_ = std::move(cb); } // Item looted / received callback (SMSG_ITEM_PUSH_RESULT when showInChat is set) using ItemLootCallback = std::function; void setItemLootCallback(ItemLootCallback cb) { itemLootCallback_ = std::move(cb); } // Quest turn-in completion callback using QuestCompleteCallback = std::function; void setQuestCompleteCallback(QuestCompleteCallback cb) { questCompleteCallback_ = std::move(cb); } // Mount state using MountCallback = std::function; // 0 = dismount void setMountCallback(MountCallback cb) { mountCallback_ = std::move(cb); } // Taxi terrain precaching callback using TaxiPrecacheCallback = std::function&)>; void setTaxiPrecacheCallback(TaxiPrecacheCallback cb) { taxiPrecacheCallback_ = std::move(cb); } // Taxi orientation callback (for mount rotation: yaw, pitch, roll in radians) using TaxiOrientationCallback = std::function; void setTaxiOrientationCallback(TaxiOrientationCallback cb) { taxiOrientationCallback_ = std::move(cb); } // Callback for when taxi flight is about to start (after mounting delay, before movement begins) using TaxiFlightStartCallback = std::function; void setTaxiFlightStartCallback(TaxiFlightStartCallback cb) { taxiFlightStartCallback_ = std::move(cb); } // Callback fired when server sends SMSG_OPEN_LFG_DUNGEON_FINDER (open dungeon finder UI) using OpenLfgCallback = std::function; 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; } 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_; } bool isPlayerRooted() const { return (movementInfo.flags & static_cast(MovementFlags::ROOT)) != 0; } bool isGravityDisabled() const { return (movementInfo.flags & static_cast(MovementFlags::LEVITATING)) != 0; } bool isFeatherFalling() const { return (movementInfo.flags & static_cast(MovementFlags::FEATHER_FALL)) != 0; } bool isWaterWalking() const { return (movementInfo.flags & static_cast(MovementFlags::WATER_WALK)) != 0; } bool isPlayerFlying() const { const uint32_t flyMask = static_cast(MovementFlags::CAN_FLY) | static_cast(MovementFlags::FLYING); return (movementInfo.flags & flyMask) == flyMask; } bool isHovering() const { return (movementInfo.flags & static_cast(MovementFlags::HOVER)) != 0; } bool isSwimming() const { return (movementInfo.flags & static_cast(MovementFlags::SWIMMING)) != 0; } // Set the character pitch angle (radians) for movement packets (flight / swimming). // Positive = nose up, negative = nose down. void setMovementPitch(float radians) { movementInfo.pitch = radians; } void dismount(); // 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& getTaxiNodes() const { return taxiNodes_; } uint32_t getTaxiCostTo(uint32_t destNodeId) const; bool taxiNpcHasRoutes(uint64_t guid) const { auto it = taxiNpcHasRoutes_.find(guid); return it != taxiNpcHasRoutes_.end() && it->second; } // Vehicle (WotLK) bool isInVehicle() const { return vehicleId_ != 0; } uint32_t getVehicleId() const { return vehicleId_; } void sendRequestVehicleExit(); // Vendor 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); struct BuybackItem { uint64_t itemGuid = 0; ItemDef item; uint32_t count = 1; }; void buyBackItem(uint32_t buybackSlot); void repairItem(uint64_t vendorGuid, uint64_t itemGuid); void repairAll(uint64_t vendorGuid, bool useGuildBank = false); const std::deque& 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 destroyItem(uint8_t bag, uint8_t slot, uint8_t count = 1); void swapContainerItems(uint8_t srcBag, uint8_t srcSlot, uint8_t dstBag, uint8_t dstSlot); void swapBagSlots(int srcBagIndex, int dstBagIndex); void useItemById(uint32_t itemId); bool isVendorWindowOpen() const { return vendorWindowOpen; } const ListInventoryData& getVendorItems() const { return currentVendorItems; } void setVendorCanRepair(bool v) { currentVendorItems.canRepair = v; } // Mail bool isMailboxOpen() const { return mailboxOpen_; } const std::vector& 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, uint32_t money, uint32_t cod = 0); // Mail attachments (max 12 per WotLK) static constexpr int MAIL_MAX_ATTACHMENTS = 12; struct MailAttachSlot { uint64_t itemGuid = 0; game::ItemDef item; uint8_t srcBag = 0xFF; // source container for return uint8_t srcSlot = 0; bool occupied() const { return itemGuid != 0; } }; bool attachItemFromBackpack(int backpackIndex); bool attachItemFromBag(int bagIndex, int slotIndex); bool detachMailAttachment(int attachIndex); void clearMailAttachments(); const std::array& 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 bool isTrainerWindowOpen() const { return trainerWindowOpen_; } const TrainerListData& getTrainerSpells() const { return currentTrainerList_; } void trainSpell(uint32_t spellId); void closeTrainer(); const std::string& getSpellName(uint32_t spellId) const; const std::string& getSpellRank(uint32_t spellId) const; /// Returns the tooltip/description text from Spell.dbc (empty if unknown or has no text). const std::string& getSpellDescription(uint32_t spellId) const; const std::string& getSkillLineName(uint32_t spellId) const; /// Returns the DispelType for a spell (0=none,1=magic,2=curse,3=disease,4=poison,5+=other) uint8_t getSpellDispelType(uint32_t spellId) const; struct TrainerTab { std::string name; std::vector spells; }; const std::vector& getTrainerTabs() const { return trainerTabs_; } const ItemQueryResponseData* getItemInfo(uint32_t itemId) const { auto it = itemInfoCache_.find(itemId); return (it != itemInfoCache_.end()) ? &it->second : nullptr; } // Request item info from server if not already cached/pending void ensureItemInfo(uint32_t entry) { if (entry == 0 || itemInfoCache_.count(entry) || pendingItemQueries_.count(entry)) return; queryItemInfo(entry, 0); } uint64_t getBackpackItemGuid(int index) const { if (index < 0 || index >= static_cast(backpackSlotGuids_.size())) return 0; return backpackSlotGuids_[index]; } uint64_t getVendorGuid() const { return currentVendorItems.vendorGuid; } /** * Set callbacks */ void setOnSuccess(WorldConnectSuccessCallback callback) { onSuccess = callback; } void setOnFailure(WorldConnectFailureCallback callback) { onFailure = callback; } /** * Update - call regularly (e.g., each frame) * * @param deltaTime Time since last update in seconds */ void update(float deltaTime); /** * Reset DBC-backed caches so they reload from new expansion data. * Called by Application when the expansion profile changes. */ void resetDbcCaches(); private: void autoTargetAttacker(uint64_t attackerGuid); /** * Handle incoming packet from world server */ void handlePacket(network::Packet& packet); /** * Handle SMSG_AUTH_CHALLENGE from server */ void handleAuthChallenge(network::Packet& packet); /** * Handle SMSG_AUTH_RESPONSE from server */ void handleAuthResponse(network::Packet& packet); /** * Handle SMSG_CHAR_ENUM from server */ void handleCharEnum(network::Packet& packet); /** * Handle SMSG_CHARACTER_LOGIN_FAILED from server */ void handleCharLoginFailed(network::Packet& packet); /** * Handle SMSG_LOGIN_VERIFY_WORLD from server */ void handleLoginVerifyWorld(network::Packet& packet); /** * Handle SMSG_CLIENTCACHE_VERSION from server */ void handleClientCacheVersion(network::Packet& packet); /** * Handle SMSG_TUTORIAL_FLAGS from server */ void handleTutorialFlags(network::Packet& packet); /** * 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 */ void handleAccountDataTimes(network::Packet& packet); /** * Handle SMSG_MOTD from server */ void handleMotd(network::Packet& packet); /** Handle SMSG_NOTIFICATION (vanilla/classic server notification string) */ void handleNotification(network::Packet& packet); /** * Handle SMSG_PONG from server */ void handlePong(network::Packet& packet); /** * Handle SMSG_UPDATE_OBJECT from server */ void handleUpdateObject(network::Packet& packet); /** * Handle SMSG_COMPRESSED_UPDATE_OBJECT from server */ void handleCompressedUpdateObject(network::Packet& packet); /** * Handle SMSG_DESTROY_OBJECT from server */ 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); void handleGameObjectQueryResponse(network::Packet& packet); 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(); void updateOtherPlayerVisibleItems(uint64_t guid, const std::map& fields); void emitOtherPlayerEquipment(uint64_t guid); void emitAllOtherPlayerEquipment(); void detectInventorySlotBases(const std::map& fields); bool applyInventoryFields(const std::map& fields); void extractContainerFields(uint64_t containerGuid, const std::map& 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); // ---- Equipment set handler ---- void handleEquipmentSetList(network::Packet& packet); void handleUpdateAuraDuration(uint8_t slot, uint32_t durationMs); void handleSetForcedReactions(network::Packet& packet); // ---- 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 handlePetSpells(network::Packet& packet); void handleTurnInPetitionResults(network::Packet& packet); // ---- Character creation handler ---- void handleCharCreateResponse(network::Packet& packet); // ---- XP handler ---- 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; 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); bool shouldLogSpellstealAura(uint64_t casterGuid, uint64_t victimGuid, uint32_t spellId); void addSystemChatMessage(const std::string& message); /** * Send CMSG_PING to server (heartbeat) */ void sendPing(); /** * Send CMSG_AUTH_SESSION to server */ void sendAuthSession(); /** * Generate random client seed */ uint32_t generateClientSeed(); /** * Change state with logging */ void setState(WorldState newState); /** * Fail connection with reason */ void fail(const std::string& reason); void updateAttachedTransportChildren(float deltaTime); void setTransportAttachment(uint64_t childGuid, ObjectType type, uint64_t transportGuid, const glm::vec3& localOffset, bool hasLocalOrientation, float localOrientation); void clearTransportAttachment(uint64_t childGuid); // Opcode translation table (expansion-specific wire ↔ logical mapping) OpcodeTable opcodeTable_; // Update field table (expansion-specific field index mapping) UpdateFieldTable updateFieldTable_; // Packet parsers (expansion-specific binary format handling) std::unique_ptr packetParsers_; // Network std::unique_ptr socket; // State WorldState state = WorldState::DISCONNECTED; // Authentication data std::vector sessionKey; // 40-byte session key from auth server std::string accountName; // Account name uint32_t build = 12340; // Client build (3.3.5a) uint32_t realmId_ = 0; // Realm ID from auth REALM_LIST (used in WotLK AUTH_SESSION) uint32_t clientSeed = 0; // Random seed generated by client uint32_t serverSeed = 0; // Seed from SMSG_AUTH_CHALLENGE // Characters std::vector characters; // Character list from SMSG_CHAR_ENUM // Movement MovementInfo movementInfo; // Current player movement state uint32_t movementTime = 0; // Movement timestamp counter 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 for movement packet correctness. // fallTime must be the elapsed ms since the FALLING flag was set; the server // uses it for fall-damage calculations and anti-cheat validation. bool isFalling_ = false; uint32_t fallStartMs_ = 0; // movementInfo.time value when FALLING started // Inventory Inventory inventory; // Entity tracking EntityManager entityManager; // Manages all entities in view // Chat std::deque chatHistory; // Recent chat messages size_t maxChatHistory = 100; // Maximum chat messages to keep std::vector joinedChannels_; // Active channel memberships ChatBubbleCallback chatBubbleCallback_; EmoteAnimCallback emoteAnimCallback_; // Targeting uint64_t targetGuid = 0; uint64_t focusGuid = 0; // Focus target uint64_t lastTargetGuid = 0; // Previous target std::vector tabCycleList; int tabCycleIndex = -1; bool tabCycleStale = true; // Heartbeat uint32_t pingSequence = 0; // Ping sequence number (increments) float timeSinceLastPing = 0.0f; // Time since last ping sent (seconds) float pingInterval = 30.0f; // Ping interval (30 seconds) float timeSinceLastMoveHeartbeat_ = 0.0f; // Periodic movement heartbeat to keep server position synced float moveHeartbeatInterval_ = 0.5f; uint32_t lastLatency = 0; // Last measured latency (milliseconds) std::chrono::steady_clock::time_point pingTimestamp_; // Time CMSG_PING was sent // Player GUID and map uint64_t playerGuid = 0; uint32_t currentMapId_ = 0; bool hasHomeBind_ = false; uint32_t homeBindMapId_ = 0; uint32_t homeBindZoneId_ = 0; glm::vec3 homeBindPos_{0.0f}; // ---- Phase 1: Name caches ---- std::unordered_map playerNameCache; std::unordered_set pendingNameQueries; std::unordered_map creatureInfoCache; std::unordered_set pendingCreatureQueries; std::unordered_map gameObjectInfoCache_; std::unordered_set pendingGameObjectQueries_; // ---- Friend/contact list cache ---- std::unordered_map friendsCache; // name -> guid std::unordered_set friendGuids_; // all known friend GUIDs (for name backfill) uint32_t lastContactListMask_ = 0; uint32_t lastContactListCount_ = 0; std::vector contacts_; // structured contact list (friends + ignores) // ---- World state and faction initialization snapshots ---- uint32_t worldStateMapId_ = 0; uint32_t worldStateZoneId_ = 0; std::unordered_map worldStates_; std::vector initialFactions_; // ---- Ignore list cache ---- std::unordered_map ignoreCache; // name -> guid // ---- Logout state ---- bool loggingOut_ = false; float logoutCountdown_ = 0.0f; // seconds remaining before server logs us out (0 = instant/done) // ---- Display state ---- bool helmVisible_ = true; bool cloakVisible_ = true; uint8_t standState_ = 0; // 0=stand, 1=sit, ..., 7=dead, 8=kneel (server-confirmed) // ---- Follow state ---- uint64_t followTargetGuid_ = 0; // ---- AFK/DND status ---- bool afkStatus_ = false; bool dndStatus_ = false; std::string afkMessage_; std::string dndMessage_; std::string lastWhisperSender_; // ---- Online item tracking ---- struct OnlineItemInfo { uint32_t entry = 0; uint32_t stackCount = 1; uint32_t curDurability = 0; uint32_t maxDurability = 0; }; std::unordered_map onlineItems_; std::unordered_map itemInfoCache_; std::unordered_set pendingItemQueries_; std::array equipSlotGuids_{}; std::array backpackSlotGuids_{}; std::array keyringSlotGuids_{}; // Container (bag) contents: containerGuid -> array of item GUIDs per slot struct ContainerInfo { uint32_t numSlots = 0; std::array slotGuids{}; // max 36 slots }; std::unordered_map containerContents_; int invSlotBase_ = -1; int packSlotBase_ = -1; std::map lastPlayerFields_; bool onlineEquipDirty_ = false; std::array lastEquipDisplayIds_{}; // Visible equipment for other players: detect the update-field layout (base + stride) // using the local player's own equipped items, then decode other players by index. // Default to known WotLK 3.3.5a layout: UNIT_END(148) + 0x0088 = 284, stride 2. // The heuristic in maybeDetectVisibleItemLayout() can still override if needed. int visibleItemEntryBase_ = 284; int visibleItemStride_ = 2; bool visibleItemLayoutVerified_ = false; // true once heuristic confirms/overrides default std::unordered_map> otherPlayerVisibleItemEntries_; std::unordered_set otherPlayerVisibleDirty_; std::unordered_map otherPlayerMoveTimeMs_; std::unordered_map otherPlayerSmoothedIntervalMs_; // EMA of packet intervals // Inspect fallback (when visible item fields are missing/unreliable) std::unordered_map> inspectedPlayerItemEntries_; InspectResult inspectResult_; // most-recently received inspect response std::unordered_set pendingAutoInspect_; float inspectRateLimit_ = 0.0f; // ---- Phase 2: Combat ---- bool autoAttacking = false; bool autoAttackRequested_ = false; // local intent (CMSG_ATTACKSWING sent) 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 hostileAttackers_; std::vector 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 combatLog_; std::deque recentSpellstealLogs_; std::deque areaTriggerMsgs_; // unitGuid → sorted threat list (descending by threat value) std::unordered_map> threatLists_; // ---- Phase 3: Spells ---- WorldEntryCallback worldEntryCallback_; KnockBackCallback knockBackCallback_; CameraShakeCallback cameraShakeCallback_; UnstuckCallback unstuckCallback_; UnstuckCallback unstuckGyCallback_; UnstuckCallback unstuckHearthCallback_; BindPointCallback bindPointCallback_; HearthstonePreloadCallback hearthstonePreloadCallback_; CreatureSpawnCallback creatureSpawnCallback_; CreatureDespawnCallback creatureDespawnCallback_; PlayerSpawnCallback playerSpawnCallback_; PlayerDespawnCallback playerDespawnCallback_; PlayerEquipmentCallback playerEquipmentCallback_; CreatureMoveCallback creatureMoveCallback_; TransportMoveCallback transportMoveCallback_; TransportSpawnCallback transportSpawnCallback_; GameObjectSpawnCallback gameObjectSpawnCallback_; GameObjectMoveCallback gameObjectMoveCallback_; GameObjectDespawnCallback gameObjectDespawnCallback_; GameObjectCustomAnimCallback gameObjectCustomAnimCallback_; // Transport tracking struct TransportAttachment { ObjectType type = ObjectType::OBJECT; uint64_t transportGuid = 0; glm::vec3 localOffset{0.0f}; float localOrientation = 0.0f; bool hasLocalOrientation = false; }; std::unordered_map transportAttachments_; std::unordered_set transportGuids_; // GUIDs of known transport GameObjects std::unordered_set serverUpdatedTransportGuids_; uint64_t playerTransportGuid_ = 0; // Transport the player is riding (0 = none) glm::vec3 playerTransportOffset_ = glm::vec3(0.0f); // Player offset on transport uint64_t playerTransportStickyGuid_ = 0; // Last transport player was on (temporary retention) float playerTransportStickyTimer_ = 0.0f; // Seconds to keep sticky transport alive after transient clears std::unique_ptr transportManager_; // Transport movement manager std::unordered_set knownSpells; std::unordered_map spellCooldowns; // spellId -> remaining seconds uint32_t weaponProficiency_ = 0; // bitmask from SMSG_SET_PROFICIENCY itemClass=2 uint32_t armorProficiency_ = 0; // bitmask from SMSG_SET_PROFICIENCY itemClass=4 std::vector minimapPings_; uint8_t castCount = 0; bool casting = false; bool castIsChannel = false; uint32_t currentCastSpellId = 0; float castTimeRemaining = 0.0f; // Per-unit cast state (keyed by GUID, populated from SMSG_SPELL_START) std::unordered_map unitCastStates_; uint64_t pendingGameObjectInteractGuid_ = 0; // Talents (dual-spec support) uint8_t activeTalentSpec_ = 0; // Currently active spec (0 or 1) uint8_t unspentTalentPoints_[2] = {0, 0}; // Unspent points per spec std::unordered_map learnedTalents_[2]; // Learned talents per spec std::array, 2> learnedGlyphs_{}; // Glyphs per spec std::unordered_map talentCache_; // talentId -> entry std::unordered_map talentTabCache_; // tabId -> entry bool talentDbcLoaded_ = false; bool talentsInitialized_ = false; // Reset on world entry; guards first-spec selection // ---- Area trigger detection ---- struct AreaTriggerEntry { uint32_t id = 0; uint32_t mapId = 0; float x = 0, y = 0, z = 0; // canonical WoW coords (converted from DBC) float radius = 0; float boxLength = 0, boxWidth = 0, boxHeight = 0; float boxYaw = 0; }; bool areaTriggerDbcLoaded_ = false; std::vector areaTriggers_; std::unordered_set activeAreaTriggers_; // triggers player is currently inside float areaTriggerCheckTimer_ = 0.0f; bool areaTriggerSuppressFirst_ = false; // suppress first check after map transfer float castTimeTotal = 0.0f; std::array actionBar{}; std::vector playerAuras; std::vector targetAuras; std::unordered_map> unitAurasCache_; // per-unit aura cache uint64_t petGuid_ = 0; uint32_t petActionSlots_[10] = {}; // SMSG_PET_SPELLS action bar (10 slots) uint8_t petCommand_ = 1; // 0=stay,1=follow,2=attack,3=dismiss uint8_t petReact_ = 1; // 0=passive,1=defensive,2=aggressive std::vector petSpellList_; // known pet spells std::unordered_set petAutocastSpells_; // spells with autocast on // ---- Pet Stable ---- bool stableWindowOpen_ = false; uint64_t stableMasterGuid_ = 0; uint8_t stableNumSlots_ = 0; std::vector stabledPets_; void handleListStabledPets(network::Packet& packet); // ---- Battleground queue state ---- std::array bgQueues_{}; // ---- Available battleground list (SMSG_BATTLEFIELD_LIST) ---- std::vector availableBgs_; void handleBattlefieldList(network::Packet& packet); // Instance difficulty uint32_t instanceDifficulty_ = 0; bool instanceIsHeroic_ = false; // Raid target markers (icon 0-7 -> guid; 0 = empty slot) std::array raidTargetGuids_ = {}; // Mirror timers (0=fatigue, 1=breath, 2=feigndeath) MirrorTimer mirrorTimers_[3]; // Combo points (rogues/druids) uint8_t comboPoints_ = 0; uint64_t comboTarget_ = 0; // Instance / raid lockouts std::vector instanceLockouts_; // Arena team stats (indexed by team slot, updated by SMSG_ARENA_TEAM_STATS) std::vector arenaTeamStats_; // Arena team rosters (updated by SMSG_ARENA_TEAM_ROSTER) std::vector arenaTeamRosters_; // BG scoreboard (MSG_PVP_LOG_DATA) BgScoreboardData bgScoreboard_; // Instance encounter boss units (slots 0-4 from SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT) std::array encounterUnitGuids_ = {}; // 0 = empty slot // LFG / Dungeon Finder state LfgState lfgState_ = LfgState::None; uint32_t lfgDungeonId_ = 0; // current dungeon entry uint32_t lfgProposalId_ = 0; // pending proposal id (0 = none) int32_t lfgAvgWaitSec_ = -1; // estimated wait, -1=unknown uint32_t lfgTimeInQueueMs_= 0; // ms already in queue uint32_t lfgBootVotes_ = 0; // current boot-yes votes uint32_t lfgBootTotal_ = 0; // total votes cast uint32_t lfgBootTimeLeft_ = 0; // seconds remaining uint32_t lfgBootNeeded_ = 0; // votes needed to kick std::string lfgBootTargetName_; // name of player being voted on std::string lfgBootReason_; // reason given for kick // Ready check state bool pendingReadyCheck_ = false; uint32_t readyCheckReadyCount_ = 0; uint32_t readyCheckNotReadyCount_ = 0; std::string readyCheckInitiator_; std::vector readyCheckResults_; // per-player status live during check // Faction standings (factionId → absolute standing value) std::unordered_map factionStandings_; // Faction name cache (factionId → name), populated lazily from Faction.dbc std::unordered_map factionNameCache_; // repListId → factionId mapping (populated with factionNameCache) std::unordered_map factionRepListToId_; // factionId → repListId reverse mapping std::unordered_map factionIdToRepList_; bool factionNameCacheLoaded_ = false; void loadFactionNameCache(); std::string getFactionName(uint32_t factionId) const; // ---- Phase 4: Group ---- GroupListData partyData; bool pendingGroupInvite = false; std::string pendingInviterName; // Item text state bool itemTextOpen_ = false; std::string itemText_; // Shared quest state bool pendingSharedQuest_ = false; uint32_t sharedQuestId_ = 0; std::string sharedQuestTitle_; std::string sharedQuestSharerName_; uint64_t sharedQuestSharerGuid_ = 0; // Summon state bool pendingSummonRequest_ = false; uint64_t summonerGuid_ = 0; std::string summonerName_; float summonTimeoutSec_ = 0.0f; uint32_t totalTimePlayed_ = 0; uint32_t levelTimePlayed_ = 0; // Who results (last SMSG_WHO response) std::vector whoResults_; uint32_t whoOnlineCount_ = 0; // Trade state TradeStatus tradeStatus_ = TradeStatus::None; uint64_t tradePeerGuid_= 0; std::string tradePeerName_; std::array myTradeSlots_{}; std::array peerTradeSlots_{}; uint64_t myTradeGold_ = 0; uint64_t peerTradeGold_ = 0; // Shaman totem state TotemSlot activeTotemSlots_[NUM_TOTEM_SLOTS]; // Duel state bool pendingDuelRequest_ = false; uint64_t duelChallengerGuid_= 0; uint64_t duelFlagGuid_ = 0; std::string duelChallengerName_; uint32_t duelCountdownMs_ = 0; // 0 = no active countdown std::chrono::steady_clock::time_point duelCountdownStartedAt_{}; // ---- Guild state ---- std::string guildName_; std::vector guildRankNames_; GuildRosterData guildRoster_; GuildInfoData guildInfoData_; GuildQueryResponseData guildQueryData_; bool hasGuildRoster_ = false; bool pendingGuildInvite_ = false; std::string pendingGuildInviterName_; std::string pendingGuildInviteGuildName_; bool showPetitionDialog_ = false; uint32_t petitionCost_ = 0; uint64_t petitionNpcGuid_ = 0; uint64_t activeCharacterGuid_ = 0; Race playerRace_ = Race::HUMAN; // ---- Phase 5: Loot ---- bool lootWindowOpen = false; bool autoLoot_ = false; LootResponseData currentLoot; std::vector masterLootCandidates_; // from SMSG_LOOT_MASTER_LIST // Group loot roll state bool pendingLootRollActive_ = false; LootRollEntry pendingLootRoll_; struct LocalLootState { LootResponseData data; bool moneyTaken = false; bool itemAutoLootSent = false; }; std::unordered_map localLootState_; struct PendingLootRetry { uint64_t guid = 0; float timer = 0.0f; uint8_t remainingRetries = 0; bool sendLoot = false; }; std::vector pendingGameObjectLootRetries_; struct PendingLootOpen { uint64_t guid = 0; float timer = 0.0f; }; std::vector pendingGameObjectLootOpens_; // Tracks the last GO we sent CMSG_GAMEOBJ_USE to; used in handleSpellGo // to send CMSG_LOOT after a gather cast (mining/herbalism) completes. uint64_t lastInteractedGoGuid_ = 0; uint64_t pendingLootMoneyGuid_ = 0; uint32_t pendingLootMoneyAmount_ = 0; float pendingLootMoneyNotifyTimer_ = 0.0f; std::unordered_map recentLootMoneyAnnounceCooldowns_; uint64_t playerMoneyCopper_ = 0; int32_t playerArmorRating_ = 0; int32_t playerResistances_[6] = {}; // [0]=Holy,[1]=Fire,[2]=Nature,[3]=Frost,[4]=Shadow,[5]=Arcane // Server-authoritative primary stats: [0]=STR [1]=AGI [2]=STA [3]=INT [4]=SPI; -1 = not received yet int32_t playerStats_[5] = {-1, -1, -1, -1, -1}; // WotLK secondary combat stats (-1 = not yet received) int32_t playerMeleeAP_ = -1; int32_t playerRangedAP_ = -1; int32_t playerSpellDmgBonus_[7] = {-1,-1,-1,-1,-1,-1,-1}; // per school 0-6 int32_t playerHealBonus_ = -1; float playerDodgePct_ = -1.0f; float playerParryPct_ = -1.0f; float playerBlockPct_ = -1.0f; float playerCritPct_ = -1.0f; float playerRangedCritPct_ = -1.0f; float playerSpellCritPct_[7] = {-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f}; int32_t playerCombatRatings_[25] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; // Some servers/custom clients shift update field indices. We can auto-detect coinage by correlating // money-notify deltas with update-field diffs and then overriding UF::PLAYER_FIELD_COINAGE at runtime. uint32_t pendingMoneyDelta_ = 0; float pendingMoneyDeltaTimer_ = 0.0f; // Gossip bool gossipWindowOpen = false; GossipMessageData currentGossip; std::vector gossipPois_; void performGameObjectInteractionNow(uint64_t guid); // Quest details bool questDetailsOpen = false; std::chrono::steady_clock::time_point questDetailsOpenTime{}; // Delayed opening to allow item data to load QuestDetailsData currentQuestDetails; // Quest turn-in bool questRequestItemsOpen_ = false; QuestRequestItemsData currentQuestRequestItems_; uint32_t pendingTurnInQuestId_ = 0; uint64_t pendingTurnInNpcGuid_ = 0; bool pendingTurnInRewardRequest_ = false; std::unordered_map pendingQuestAcceptTimeouts_; std::unordered_map pendingQuestAcceptNpcGuids_; bool questOfferRewardOpen_ = false; QuestOfferRewardData currentQuestOfferReward_; // Quest log std::vector questLog_; std::unordered_set pendingQuestQueryIds_; std::unordered_set trackedQuestIds_; bool pendingLoginQuestResync_ = false; float pendingLoginQuestResyncTimeout_ = 0.0f; // Quest giver status per NPC std::unordered_map npcQuestStatus_; // Faction hostility lookup (populated from FactionTemplate.dbc) std::unordered_map factionHostileMap_; bool isHostileFaction(uint32_t factionTemplateId) const { auto it = factionHostileMap_.find(factionTemplateId); return it != factionHostileMap_.end() ? it->second : true; // default hostile if unknown } // Vehicle (WotLK): non-zero when player is seated in a vehicle uint32_t vehicleId_ = 0; // Taxi / Flight Paths std::unordered_map taxiNpcHasRoutes_; // guid -> has new/available routes std::unordered_map taxiNodes_; std::vector taxiPathEdges_; std::unordered_map> taxiPathNodes_; // pathId -> ordered waypoints 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; // Prevent re-entering taxi right after landing float taxiStartGrace_ = 0.0f; // Ignore transient landing/dismount checks right after takeoff size_t taxiClientIndex_ = 0; std::vector 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] = {}; // Track previously known nodes for discovery alerts bool taxiMaskInitialized_ = false; // First SMSG_SHOWTAXINODES seeds mask without alerts std::unordered_map taxiCostMap_; // destNodeId -> total cost in copper void buildTaxiCostMap(); void applyTaxiMountForCurrentNode(); uint32_t nextMovementTimestampMs(); void sanitizeMovementForTaxi(); void startClientTaxiPath(const std::vector& pathNodes); void updateClientTaxi(float deltaTime); // Mail bool mailboxOpen_ = false; uint64_t mailboxGuid_ = 0; std::vector mailInbox_; int selectedMailIndex_ = -1; bool showMailCompose_ = false; bool hasNewMail_ = false; std::array mailAttachments_{}; // Bank bool bankOpen_ = false; uint64_t bankerGuid_ = 0; std::array bankSlotGuids_{}; std::array bankBagSlotGuids_{}; int effectiveBankSlots_ = 28; // 24 for Classic, 28 for TBC/WotLK int effectiveBankBagSlots_ = 7; // 6 for Classic, 7 for TBC/WotLK // Guild Bank bool guildBankOpen_ = false; uint64_t guildBankerGuid_ = 0; GuildBankData guildBankData_; uint8_t guildBankActiveTab_ = 0; // Auction House bool auctionOpen_ = false; uint64_t auctioneerGuid_ = 0; uint32_t auctionHouseId_ = 0; AuctionListResult auctionBrowseResults_; AuctionListResult auctionOwnerResults_; AuctionListResult auctionBidderResults_; int auctionActiveTab_ = 0; // 0=Browse, 1=Bids, 2=Auctions float auctionSearchDelayTimer_ = 0.0f; // Last search params for re-query (pagination, auto-refresh after bid/buyout) 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_; // Routing: which result vector to populate from next SMSG_AUCTION_LIST_RESULT enum class AuctionResultTarget { BROWSE, OWNER, BIDDER }; AuctionResultTarget pendingAuctionTarget_ = AuctionResultTarget::BROWSE; // Vendor bool vendorWindowOpen = false; ListInventoryData currentVendorItems; std::deque buybackItems_; std::unordered_map pendingSellToBuyback_; int pendingBuybackSlot_ = -1; uint32_t pendingBuybackWireSlot_ = 0; uint32_t pendingBuyItemId_ = 0; uint32_t pendingBuyItemSlot_ = 0; // Trainer bool trainerWindowOpen_ = false; TrainerListData currentTrainerList_; struct SpellNameEntry { std::string name; std::string rank; std::string description; uint32_t schoolMask = 0; uint8_t dispelType = 0; }; std::unordered_map spellNameCache_; bool spellNameCacheLoaded_ = false; // Title cache: maps titleBit → title string (lazy-loaded from CharTitles.dbc) // The strings use "%s" as a player-name placeholder (e.g. "Commander %s", "%s the Explorer"). std::unordered_map titleNameCache_; bool titleNameCacheLoaded_ = false; void loadTitleNameCache(); // Set of title bit-indices known to the player (from SMSG_TITLE_EARNED). std::unordered_set knownTitleBits_; // Currently selected title bit, or -1 for no title. Updated from PLAYER_CHOSEN_TITLE. int32_t chosenTitleBit_ = -1; // Achievement caches (lazy-loaded from Achievement.dbc on first earned event) std::unordered_map achievementNameCache_; std::unordered_map achievementDescCache_; std::unordered_map achievementPointsCache_; bool achievementNameCacheLoaded_ = false; void loadAchievementNameCache(); // Set of achievement IDs earned by the player (populated from SMSG_ALL_ACHIEVEMENT_DATA) std::unordered_set earnedAchievements_; // Earn dates: achievementId → WoW PackedTime (from SMSG_ACHIEVEMENT_EARNED / SMSG_ALL_ACHIEVEMENT_DATA) std::unordered_map achievementDates_; // Criteria progress: criteriaId → current value (from SMSG_CRITERIA_UPDATE) std::unordered_map criteriaProgress_; void handleAllAchievementData(network::Packet& packet); // Per-player achievement data from SMSG_RESPOND_INSPECT_ACHIEVEMENTS // Key: inspected player's GUID; value: set of earned achievement IDs std::unordered_map> inspectedPlayerAchievements_; void handleRespondInspectAchievements(network::Packet& packet); // Area name cache (lazy-loaded from WorldMapArea.dbc; maps AreaTable ID → display name) std::unordered_map areaNameCache_; bool areaNameCacheLoaded_ = false; void loadAreaNameCache(); std::string getAreaName(uint32_t areaId) const; // Map name cache (lazy-loaded from Map.dbc; maps mapId → localized display name) std::unordered_map mapNameCache_; bool mapNameCacheLoaded_ = false; void loadMapNameCache(); // LFG dungeon name cache (lazy-loaded from LFGDungeons.dbc; WotLK only) std::unordered_map lfgDungeonNameCache_; bool lfgDungeonNameCacheLoaded_ = false; void loadLfgDungeonDbc(); std::string getLfgDungeonName(uint32_t dungeonId) const; std::vector trainerTabs_; void handleTrainerList(network::Packet& packet); void loadSpellNameCache(); void categorizeTrainerSpells(); // Callbacks WorldConnectSuccessCallback onSuccess; WorldConnectFailureCallback onFailure; CharCreateCallback charCreateCallback_; CharDeleteCallback charDeleteCallback_; CharLoginFailCallback charLoginFailCallback_; uint8_t lastCharDeleteResult_ = 0xFF; bool pendingCharCreateResult_ = false; bool pendingCharCreateSuccess_ = false; std::string pendingCharCreateMsg_; 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_; std::unique_ptr wardenMemory_; std::unique_ptr 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 wardenModuleHash_; // 16 bytes MD5 std::vector wardenModuleKey_; // 16 bytes RC4 uint32_t wardenModuleSize_ = 0; std::vector wardenModuleData_; // Downloaded module chunks std::vector wardenLoadedModuleImage_; // Parsed module image for key derivation std::shared_ptr 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 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); // ---- XP tracking ---- uint32_t playerXp_ = 0; uint32_t playerNextLevelXp_ = 0; uint32_t playerRestedXp_ = 0; bool isResting_ = false; uint32_t serverPlayerLevel_ = 1; static uint32_t xpForLevel(uint32_t level); // ---- Server time tracking (for deterministic celestial/sky systems) ---- float gameTime_ = 0.0f; // Server game time in seconds float timeSpeed_ = 0.0166f; // Time scale (default: 1 game day = 1 real hour) void handleLoginSetTimeSpeed(network::Packet& packet); // ---- Global Cooldown (GCD) ---- float gcdTotal_ = 0.0f; std::chrono::steady_clock::time_point gcdStartedAt_{}; // ---- Weather state (SMSG_WEATHER) ---- uint32_t weatherType_ = 0; // 0=clear, 1=rain, 2=snow, 3=storm float weatherIntensity_ = 0.0f; // 0.0 to 1.0 // ---- Light override (SMSG_OVERRIDE_LIGHT) ---- uint32_t overrideLightId_ = 0; // 0 = no override uint32_t overrideLightTransMs_ = 0; // ---- Player skills ---- std::map playerSkills_; std::unordered_map skillLineNames_; std::unordered_map skillLineCategories_; std::unordered_map spellToSkillLine_; // spellID -> skillLineID bool skillLineDbcLoaded_ = false; bool skillLineAbilityLoaded_ = false; static constexpr size_t PLAYER_EXPLORED_ZONES_COUNT = 128; std::vector playerExploredZones_ = std::vector(PLAYER_EXPLORED_ZONES_COUNT, 0u); bool hasPlayerExploredZones_ = false; void loadSkillLineDbc(); void loadSkillLineAbilityDbc(); void extractSkillFields(const std::map& fields); void extractExploredZoneFields(const std::map& fields); void applyQuestStateFromFields(const std::map& fields); // Apply packed kill counts from player update fields to a quest entry that has // already had its killObjectives populated from SMSG_QUEST_QUERY_RESPONSE. void applyPackedKillCountsFromFields(QuestLogEntry& quest); NpcDeathCallback npcDeathCallback_; NpcAggroCallback npcAggroCallback_; NpcRespawnCallback npcRespawnCallback_; StandStateCallback standStateCallback_; GhostStateCallback ghostStateCallback_; MeleeSwingCallback meleeSwingCallback_; uint64_t lastMeleeSwingMs_ = 0; // system_clock ms at last player auto-attack swing SpellCastAnimCallback spellCastAnimCallback_; UnitAnimHintCallback unitAnimHintCallback_; UnitMoveFlagsCallback unitMoveFlagsCallback_; NpcSwingCallback npcSwingCallback_; NpcGreetingCallback npcGreetingCallback_; NpcFarewellCallback npcFarewellCallback_; NpcVendorCallback npcVendorCallback_; ChargeCallback chargeCallback_; LevelUpCallback levelUpCallback_; LevelUpDeltas lastLevelUpDeltas_; std::vector tempEnchantTimers_; std::vector bookPages_; // pages collected for the current readable item OtherPlayerLevelUpCallback otherPlayerLevelUpCallback_; AchievementEarnedCallback achievementEarnedCallback_; AreaDiscoveryCallback areaDiscoveryCallback_; QuestProgressCallback questProgressCallback_; MountCallback mountCallback_; TaxiPrecacheCallback taxiPrecacheCallback_; TaxiOrientationCallback taxiOrientationCallback_; TaxiFlightStartCallback taxiFlightStartCallback_; OpenLfgCallback openLfgCallback_; uint32_t currentMountDisplayId_ = 0; uint32_t mountAuraSpellId_ = 0; // Spell ID of the aura that caused mounting (for CMSG_CANCEL_AURA fallback) 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; bool playerDead_ = false; bool releasedSpirit_ = false; uint32_t corpseMapId_ = 0; float corpseX_ = 0.0f, corpseY_ = 0.0f, corpseZ_ = 0.0f; uint64_t corpseGuid_ = 0; // Death Knight runes (class 6): slots 0-1=Blood, 2-3=Unholy, 4-5=Frost initially std::array playerRunes_ = [] { std::array r{}; r[0].type = r[1].type = RuneType::Blood; r[2].type = r[3].type = RuneType::Unholy; r[4].type = r[5].type = RuneType::Frost; return r; }(); uint64_t pendingSpiritHealerGuid_ = 0; bool resurrectPending_ = false; bool resurrectRequestPending_ = false; // ---- Talent wipe confirm dialog ---- bool talentWipePending_ = false; uint64_t talentWipeNpcGuid_ = 0; uint32_t talentWipeCost_ = 0; bool resurrectIsSpiritHealer_ = false; // true = SMSG_SPIRIT_HEALER_CONFIRM, false = SMSG_RESURRECT_REQUEST uint64_t resurrectCasterGuid_ = 0; std::string resurrectCasterName_; bool repopPending_ = false; uint64_t lastRepopRequestMs_ = 0; // ---- Completed quest IDs (SMSG_QUERY_QUESTS_COMPLETED_RESPONSE) ---- std::unordered_set completedQuests_; // ---- Equipment sets (SMSG_EQUIPMENT_SET_LIST) ---- struct EquipmentSet { uint64_t setGuid = 0; uint32_t setId = 0; std::string name; std::string iconName; uint32_t ignoreSlotMask = 0; std::array itemGuids{}; }; std::vector equipmentSets_; std::vector equipmentSetInfo_; // public-facing copy // ---- Forced faction reactions (SMSG_SET_FORCED_REACTIONS) ---- std::unordered_map forcedReactions_; // factionId -> reaction tier // ---- Server-triggered audio ---- PlayMusicCallback playMusicCallback_; PlaySoundCallback playSoundCallback_; PlayPositionalSoundCallback playPositionalSoundCallback_; // ---- UI error frame callback ---- UIErrorCallback uiErrorCallback_; // ---- Reputation change callback ---- RepChangeCallback repChangeCallback_; uint32_t watchedFactionId_ = 0; // auto-set to most recently changed faction // ---- PvP honor credit callback ---- PvpHonorCallback pvpHonorCallback_; // ---- Item loot callback ---- ItemLootCallback itemLootCallback_; // ---- Quest completion callback ---- QuestCompleteCallback questCompleteCallback_; // ---- GM Ticket state (SMSG_GMTICKET_GETTICKET / SMSG_GMTICKET_SYSTEMSTATUS) ---- bool gmTicketActive_ = false; ///< True when an open ticket exists on the server std::string gmTicketText_; ///< Text of the open ticket (from SMSG_GMTICKET_GETTICKET) float gmTicketWaitHours_ = 0.0f; ///< Server-estimated wait time in hours bool gmSupportAvailable_ = true; ///< GM support system online (SMSG_GMTICKET_SYSTEMSTATUS) // ---- Battlefield Manager state (WotLK Wintergrasp / outdoor battlefields) ---- bool bfMgrInvitePending_ = false; ///< True when an entry/queue invite is pending acceptance bool bfMgrActive_ = false; ///< True while the player is inside an outdoor battlefield uint32_t bfMgrZoneId_ = 0; ///< Zone ID of the pending/active battlefield // ---- WotLK Calendar: pending invite counter ---- uint32_t calendarPendingInvites_ = 0; ///< Unacknowledged calendar invites (SMSG_CALENDAR_SEND_NUM_PENDING) // ---- Spell modifiers (SMSG_SET_FLAT_SPELL_MODIFIER / SMSG_SET_PCT_SPELL_MODIFIER) ---- // Keyed by (SpellModOp, groupIndex); cleared on logout/character change. std::unordered_map spellFlatMods_; std::unordered_map spellPctMods_; }; } // namespace game } // namespace wowee