mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
Fix quest log not showing server-side quests on login
SMSG_QUEST_QUERY_RESPONSE skipped only 18 uint32s before reading the title string, but Classic layout has 40 fields before the title (16 header + 8 reward items + 12 choice items + 4 POI fields). Reading from the wrong position landed inside reward item data where empty slots contain 0-bytes, so readString() returned "", overwriting the "Quest #N" placeholder with an empty title — making quests invisible in the UI even though they were in the quest log. Fixes: - Expansion-aware skip count: 40 for Classic/Turtle, 55 for WotLK - Guard: only update title if parsed string is non-empty and printable - Also scan quest log fields in VALUES update path (not just CREATE_OBJECT2), so quests are detected even when the server sends partial updates
This commit is contained in:
parent
dd3149e3c1
commit
50f9651868
1 changed files with 82 additions and 27 deletions
|
|
@ -1583,37 +1583,54 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
|
||||
uint32_t questId = packet.readUInt32();
|
||||
uint32_t questMethod = packet.readUInt32(); // Quest method/type
|
||||
uint32_t questMethod = packet.readUInt32();
|
||||
|
||||
LOG_INFO(" questId=", questId, " questMethod=", questMethod);
|
||||
|
||||
// Parse quest title (after method comes level, flags, type, etc., then title string)
|
||||
// Skip intermediate fields to get to title
|
||||
if (packet.getReadPos() + 16 < packet.getSize()) {
|
||||
packet.readUInt32(); // quest level
|
||||
packet.readUInt32(); // min level
|
||||
packet.readUInt32(); // sort ID (zone)
|
||||
packet.readUInt32(); // quest type/info
|
||||
packet.readUInt32(); // suggested players
|
||||
packet.readUInt32(); // reputation objective faction
|
||||
packet.readUInt32(); // reputation objective value
|
||||
packet.readUInt32(); // required opposite faction
|
||||
packet.readUInt32(); // next quest in chain
|
||||
packet.readUInt32(); // XP ID
|
||||
packet.readUInt32(); // reward or required money
|
||||
packet.readUInt32(); // reward money max level
|
||||
packet.readUInt32(); // reward spell
|
||||
packet.readUInt32(); // reward spell cast
|
||||
packet.readUInt32(); // reward honor
|
||||
packet.readUInt32(); // reward honor multiplier
|
||||
packet.readUInt32(); // source item ID
|
||||
packet.readUInt32(); // quest flags
|
||||
// ... there are many more fields before title, let's try to read title string
|
||||
if (packet.getReadPos() + 1 < packet.getSize()) {
|
||||
std::string title = packet.readString();
|
||||
LOG_INFO(" Quest title: ", title);
|
||||
// SMSG_QUEST_QUERY_RESPONSE layout varies by expansion.
|
||||
//
|
||||
// Classic/Turtle (1.12.x) after questId+questMethod:
|
||||
// 16 header uint32s (questLevel, zoneOrSort, type, suggestedPlayers,
|
||||
// repFaction, repValue, nextChain, xpId,
|
||||
// rewMoney, rewMoneyMax, rewSpell, rewSpellCast,
|
||||
// rewHonor, rewHonorMult, srcItemId, questFlags)
|
||||
// 8 reward items (4 slots × 2: itemId + count)
|
||||
// 12 choice items (6 slots × 2: itemId + count)
|
||||
// 4 POI uint32s (mapId, x, y, opt)
|
||||
// = 40 uint32s before title string
|
||||
//
|
||||
// WotLK (3.3.5) after questId+questMethod:
|
||||
// 21 header uint32s (adds minLevel, questInfoId, 2nd repFaction/Value, questFlags2)
|
||||
// 12 reward items (4 slots × 3: itemId + count + displayId)
|
||||
// 18 choice items (6 slots × 3: itemId + count + displayId)
|
||||
// 4 POI uint32s
|
||||
// = 55 uint32s before title string
|
||||
//
|
||||
// Read all numeric fields, then look for the title string.
|
||||
// Using packetParsers_->questLogStride() as expansion discriminator:
|
||||
// stride==3 → Classic layout (40 skips)
|
||||
// stride==5 → WotLK layout (55 skips)
|
||||
const bool isClassicLayout = packetParsers_ && packetParsers_->questLogStride() == 3;
|
||||
const int skipCount = isClassicLayout ? 40 : 55;
|
||||
|
||||
// Update quest log entry with title
|
||||
for (int i = 0; i < skipCount; ++i) {
|
||||
packet.readUInt32();
|
||||
}
|
||||
|
||||
if (packet.getReadPos() < packet.getSize()) {
|
||||
std::string title = packet.readString();
|
||||
LOG_INFO(" Quest title: '", title, "'");
|
||||
|
||||
// Only update if we got a non-empty, printable title (guards against
|
||||
// landing in the middle of binary reward data on wrong layouts).
|
||||
bool validTitle = !title.empty();
|
||||
if (validTitle) {
|
||||
for (char c : title) {
|
||||
if ((unsigned char)c < 0x20 && c != '\t') { validTitle = false; break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (validTitle) {
|
||||
for (auto& q : questLog_) {
|
||||
if (q.questId == questId) {
|
||||
q.title = title;
|
||||
|
|
@ -1621,6 +1638,8 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_INFO(" Skipping non-printable title (wrong layout?) for quest ", questId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4187,6 +4206,42 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Scan quest log fields in VALUES updates too (server may re-send them
|
||||
// after quest accept, abandon, or same-map repositions).
|
||||
{
|
||||
const uint16_t ufQuestStart = fieldIndex(UF::PLAYER_QUEST_LOG_START);
|
||||
const uint8_t qStride = packetParsers_ ? packetParsers_->questLogStride() : 5;
|
||||
const uint16_t ufQuestEnd = ufQuestStart + 25 * qStride;
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
if (key >= ufQuestStart && key < ufQuestEnd &&
|
||||
(key - ufQuestStart) % qStride == 0) {
|
||||
uint32_t qId = val;
|
||||
if (qId != 0) {
|
||||
bool found = false;
|
||||
for (auto& q : questLog_) {
|
||||
if (q.questId == qId) { found = true; break; }
|
||||
}
|
||||
if (!found) {
|
||||
QuestLogEntry entry;
|
||||
entry.questId = qId;
|
||||
entry.complete = false;
|
||||
entry.title = "Quest #" + std::to_string(qId);
|
||||
questLog_.push_back(entry);
|
||||
LOG_INFO("Quest found in VALUES update: ", qId);
|
||||
if (socket) {
|
||||
network::Packet qPkt(wireOpcode(Opcode::CMSG_QUEST_QUERY));
|
||||
qPkt.writeUInt32(qId);
|
||||
socket->send(qPkt);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Quest slot cleared — remove from log if present
|
||||
uint16_t slot = (key - ufQuestStart) / qStride;
|
||||
(void)slot; // slot index available if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (applyInventoryFields(block.fields)) slotsChanged = true;
|
||||
if (slotsChanged) rebuildOnlineInventory();
|
||||
extractSkillFields(lastPlayerFields_);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue