From 3a7ff71262cdd5f05a37efc628ba6fb8e7a0aa86 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 23:18:16 -0700 Subject: [PATCH] fix: use expansion-aware explored zone count to prevent fog-of-war corruption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Classic 1.12 and Turtle WoW have only 64 PLAYER_EXPLORED_ZONES uint32 fields (zone IDs pack into 2048 bits). TBC and WotLK use 128 (needed for Outland/Northrend zone IDs up to bit 4095). The hardcoded PLAYER_EXPLORED_ZONES_COUNT=128 caused extractExploredZoneFields to read 64 extra fields beyond the actual zone block in Classic/Turtle — consuming PLAYER_REST_STATE_EXPERIENCE, PLAYER_FIELD_COINAGE, and character- points fields as zone flags. On the world map, this could mark zones as explored based on random bit patterns in those unrelated fields. Add `exploredZonesCount()` virtual method to PacketParsers (default=128, Classic/Turtle override=64) and use it in extractExploredZoneFields to limit reads to the correct block and zero-fill remaining slots. --- include/game/packet_parsers.hpp | 8 ++++++++ src/game/game_handler.cpp | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/game/packet_parsers.hpp b/include/game/packet_parsers.hpp index d2556e7b..2cb17fdb 100644 --- a/include/game/packet_parsers.hpp +++ b/include/game/packet_parsers.hpp @@ -207,6 +207,11 @@ public: * WotLK: 5 fields per slot, Classic/Vanilla: 3. */ virtual uint8_t questLogStride() const { return 5; } + /** Number of PLAYER_EXPLORED_ZONES uint32 fields in update-object blocks. + * Classic/Vanilla/Turtle: 64 (bit-packs up to zone ID 2047). + * TBC/WotLK: 128 (covers Outland/Northrend zone IDs up to 4095). */ + virtual uint8_t exploredZonesCount() const { return 128; } + // --- Quest Giver Status --- /** Read quest giver status from packet. @@ -407,6 +412,9 @@ public: network::Packet buildAcceptQuestPacket(uint64_t npcGuid, uint32_t questId) override; // parseQuestDetails inherited from TbcPacketParsers (same format as TBC 2.4.3) uint8_t questLogStride() const override { return 3; } + // Classic 1.12 has 64 explored-zone uint32 fields (zone IDs fit in 2048 bits). + // TBC/WotLK use 128 (needed for Outland/Northrend zone IDs up to 4095). + uint8_t exploredZonesCount() const override { return 64; } bool parseMonsterMove(network::Packet& packet, MonsterMoveData& data) override { return MonsterMoveParser::parseVanilla(packet, data); } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index f007c947..0783c91a 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -17556,18 +17556,31 @@ void GameHandler::extractSkillFields(const std::map& fields) } void GameHandler::extractExploredZoneFields(const std::map& fields) { + // Number of explored-zone uint32 fields varies by expansion: + // Classic/Turtle = 64, TBC/WotLK = 128. Always allocate 128 for world-map + // bit lookups, but only read the expansion-specific count to avoid reading + // player money or rest-XP fields as zone flags. + const size_t zoneCount = packetParsers_ + ? static_cast(packetParsers_->exploredZonesCount()) + : PLAYER_EXPLORED_ZONES_COUNT; + if (playerExploredZones_.size() != PLAYER_EXPLORED_ZONES_COUNT) { playerExploredZones_.assign(PLAYER_EXPLORED_ZONES_COUNT, 0u); } bool foundAny = false; - for (size_t i = 0; i < PLAYER_EXPLORED_ZONES_COUNT; i++) { + for (size_t i = 0; i < zoneCount; i++) { const uint16_t fieldIdx = static_cast(fieldIndex(UF::PLAYER_EXPLORED_ZONES_START) + i); auto it = fields.find(fieldIdx); if (it == fields.end()) continue; playerExploredZones_[i] = it->second; foundAny = true; } + // Zero out slots beyond the expansion's zone count to prevent stale data + // from polluting the fog-of-war display. + for (size_t i = zoneCount; i < PLAYER_EXPLORED_ZONES_COUNT; i++) { + playerExploredZones_[i] = 0u; + } if (foundAny) { hasPlayerExploredZones_ = true;