mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix: correct quest offer reward parser and trade slot trail size
- QuestOfferRewardParser: replace 4-variant heuristic with 0..16 byte prefix scan × fixed/variable arrays (34 candidates total). AzerothCore WotLK 3.3.5a sends uint32 autoFinish + uint32 suggestedPlayers = 8 bytes before emoteCount; old uint8 read caused 3-byte misalignment, producing wrong item IDs and missing icons on quest reward windows. Scoring now strongly favours the 8-byte prefix and exact byte consumption. - Quest reward tooltip: delegate to InventoryScreen::renderItemTooltip() for full stats (armor, DPS, stats, bind type, etc.); show "Loading…" while item data is still fetching instead of showing nothing. - SMSG_TRADE_STATUS_EXTENDED: fix SLOT_TRAIL 49→52 bytes. AC 3.3.5a sends giftCreatorGuid(8) + 6 enchant slots(24) + randPropId(4) + suffixFactor(4) + durability(4) + maxDurability(4) + createPlayedTime(4) = 52 bytes after isWrapped; wrong skip misaligned all subsequent slots.
This commit is contained in:
parent
170ff1597c
commit
568c566e1a
3 changed files with 70 additions and 49 deletions
|
|
@ -19167,16 +19167,16 @@ void GameHandler::handleTradeStatusExtended(network::Packet& packet) {
|
|||
uint32_t displayId = packet.readUInt32();
|
||||
uint32_t stackCount = packet.readUInt32();
|
||||
|
||||
// isWrapped + giftCreatorGuid + several enchant fields — skip them all
|
||||
// We need at least 1+8+4*5 = 29 bytes for the rest of this slot entry
|
||||
bool isWrapped = false;
|
||||
if (packet.getSize() - packet.getReadPos() >= 1) {
|
||||
isWrapped = (packet.readUInt8() != 0);
|
||||
}
|
||||
// Skip giftCreatorGuid (8) + enchantId*5 (20) + suffixFactor (4) + randPropId (4) + lockId (4)
|
||||
// + maxDurability (4) + durability (4) = 49 bytes
|
||||
// Plus if wrapped: giftCreatorGuid already consumed; additional guid = 0
|
||||
constexpr size_t SLOT_TRAIL = 49;
|
||||
// AzerothCore 3.3.5a SendUpdateTrade() field order after isWrapped:
|
||||
// giftCreatorGuid (8) + PERM enchant (4) + SOCK enchants×3 (12)
|
||||
// + BONUS enchant (4) + TEMP enchant (4) [total enchants: 24]
|
||||
// + randomPropertyId (4) + suffixFactor (4)
|
||||
// + durability (4) + maxDurability (4) + createPlayedTime (4) = 52 bytes
|
||||
constexpr size_t SLOT_TRAIL = 52;
|
||||
if (packet.getSize() - packet.getReadPos() >= SLOT_TRAIL) {
|
||||
packet.setReadPos(packet.getReadPos() + SLOT_TRAIL);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3666,11 +3666,19 @@ bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData
|
|||
data.title = normalizeWowTextTokens(packet.readString());
|
||||
data.rewardText = normalizeWowTextTokens(packet.readString());
|
||||
|
||||
if (packet.getReadPos() + 10 > packet.getSize()) {
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) {
|
||||
LOG_DEBUG("Quest offer reward (short): id=", data.questId, " title='", data.title, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
// After the two strings the packet contains a variable prefix (autoFinish + optional fields)
|
||||
// before the emoteCount. Different expansions and server emulator versions differ:
|
||||
// Classic 1.12 : uint8 autoFinish + uint32 suggestedPlayers = 5 bytes
|
||||
// TBC 2.4.3 : uint32 autoFinish + uint32 suggestedPlayers = 8 bytes (variable arrays)
|
||||
// WotLK 3.3.5a : uint32 autoFinish + uint32 suggestedPlayers = 8 bytes (fixed 6/4 arrays)
|
||||
// Some vanilla-family servers omit autoFinish entirely (0 bytes of prefix).
|
||||
// We scan prefix sizes 0..16 bytes with both fixed and variable array layouts, scoring each.
|
||||
|
||||
struct ParsedTail {
|
||||
uint32_t rewardMoney = 0;
|
||||
uint32_t rewardXp = 0;
|
||||
|
|
@ -3678,28 +3686,27 @@ bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData
|
|||
std::vector<QuestRewardItem> fixedRewards;
|
||||
bool ok = false;
|
||||
int score = -1000;
|
||||
size_t prefixSkip = 0;
|
||||
bool fixedArrays = false;
|
||||
};
|
||||
|
||||
auto parseTail = [&](size_t startPos, bool hasFlags, bool fixedArrays) -> ParsedTail {
|
||||
auto parseTail = [&](size_t startPos, size_t prefixSkip, bool fixedArrays) -> ParsedTail {
|
||||
ParsedTail out;
|
||||
out.prefixSkip = prefixSkip;
|
||||
out.fixedArrays = fixedArrays;
|
||||
packet.setReadPos(startPos);
|
||||
|
||||
if (packet.getReadPos() + 1 > packet.getSize()) return out;
|
||||
/*autoFinish*/ packet.readUInt8();
|
||||
if (hasFlags) {
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
/*flags*/ packet.readUInt32();
|
||||
}
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
/*suggestedPlayers*/ packet.readUInt32();
|
||||
// Skip the prefix bytes (autoFinish + optional suggestedPlayers before emoteCount)
|
||||
if (packet.getReadPos() + prefixSkip > packet.getSize()) return out;
|
||||
packet.setReadPos(packet.getReadPos() + prefixSkip);
|
||||
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
uint32_t emoteCount = packet.readUInt32();
|
||||
if (emoteCount > 64) return out; // guard against misalignment
|
||||
if (emoteCount > 32) return out; // guard against misalignment
|
||||
for (uint32_t i = 0; i < emoteCount; ++i) {
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) return out;
|
||||
packet.readUInt32(); // delay
|
||||
packet.readUInt32(); // emote
|
||||
packet.readUInt32(); // emote type
|
||||
}
|
||||
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
|
|
@ -3717,7 +3724,7 @@ bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData
|
|||
item.choiceSlot = i;
|
||||
if (item.itemId > 0) {
|
||||
out.choiceRewards.push_back(item);
|
||||
nonZeroChoice++;
|
||||
++nonZeroChoice;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3735,7 +3742,7 @@ bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData
|
|||
item.displayInfoId = packet.readUInt32();
|
||||
if (item.itemId > 0) {
|
||||
out.fixedRewards.push_back(item);
|
||||
nonZeroFixed++;
|
||||
++nonZeroFixed;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3746,43 +3753,56 @@ bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData
|
|||
|
||||
out.ok = true;
|
||||
out.score = 0;
|
||||
if (hasFlags) out.score += 1;
|
||||
if (fixedArrays) out.score += 1;
|
||||
// Prefer the standard WotLK/TBC 8-byte prefix (uint32 autoFinish + uint32 suggestedPlayers)
|
||||
if (prefixSkip == 8) out.score += 3;
|
||||
else if (prefixSkip == 5) out.score += 1; // Classic uint8 autoFinish + uint32 suggestedPlayers
|
||||
// Prefer fixed arrays (WotLK/TBC servers always send 6+4 slots)
|
||||
if (fixedArrays) out.score += 2;
|
||||
// Valid counts
|
||||
if (choiceCount <= 6) out.score += 3;
|
||||
if (rewardCount <= 4) out.score += 3;
|
||||
if (fixedArrays) {
|
||||
if (nonZeroChoice <= choiceCount) out.score += 3;
|
||||
if (nonZeroFixed <= rewardCount) out.score += 3;
|
||||
} else {
|
||||
out.score += 3; // variable arrays align naturally with count
|
||||
}
|
||||
if (packet.getReadPos() <= packet.getSize()) out.score += 2;
|
||||
// All non-zero items are within declared counts
|
||||
if (nonZeroChoice <= choiceCount) out.score += 2;
|
||||
if (nonZeroFixed <= rewardCount) out.score += 2;
|
||||
// No bytes left over (or only a few)
|
||||
size_t remaining = packet.getSize() - packet.getReadPos();
|
||||
if (remaining <= 32) out.score += 2;
|
||||
if (remaining == 0) out.score += 5;
|
||||
else if (remaining <= 4) out.score += 3;
|
||||
else if (remaining <= 8) out.score += 2;
|
||||
else if (remaining <= 16) out.score += 1;
|
||||
else out.score -= static_cast<int>(remaining / 4);
|
||||
// Plausible money/XP values
|
||||
if (out.rewardMoney < 5000000u) out.score += 1; // < 500g
|
||||
if (out.rewardXp < 200000u) out.score += 1; // < 200k XP
|
||||
return out;
|
||||
};
|
||||
|
||||
size_t tailStart = packet.getReadPos();
|
||||
ParsedTail a = parseTail(tailStart, true, true); // WotLK-like (flags + fixed 6/4 arrays)
|
||||
ParsedTail b = parseTail(tailStart, false, true); // no flags + fixed 6/4 arrays
|
||||
ParsedTail c = parseTail(tailStart, true, false); // flags + variable arrays
|
||||
ParsedTail d = parseTail(tailStart, false, false); // classic-like variable arrays
|
||||
// Try prefix sizes 0..16 bytes with both fixed and variable array layouts
|
||||
std::vector<ParsedTail> candidates;
|
||||
candidates.reserve(34);
|
||||
for (size_t skip = 0; skip <= 16; ++skip) {
|
||||
candidates.push_back(parseTail(tailStart, skip, true)); // fixed arrays
|
||||
candidates.push_back(parseTail(tailStart, skip, false)); // variable arrays
|
||||
}
|
||||
|
||||
const ParsedTail* best = nullptr;
|
||||
for (const ParsedTail* cand : {&a, &b, &c, &d}) {
|
||||
if (!cand->ok) continue;
|
||||
if (!best || cand->score > best->score) best = cand;
|
||||
for (const auto& cand : candidates) {
|
||||
if (!cand.ok) continue;
|
||||
if (!best || cand.score > best->score) best = &cand;
|
||||
}
|
||||
|
||||
if (best) {
|
||||
data.choiceRewards = best->choiceRewards;
|
||||
data.fixedRewards = best->fixedRewards;
|
||||
data.rewardMoney = best->rewardMoney;
|
||||
data.rewardXp = best->rewardXp;
|
||||
data.fixedRewards = best->fixedRewards;
|
||||
data.rewardMoney = best->rewardMoney;
|
||||
data.rewardXp = best->rewardXp;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Quest offer reward: id=", data.questId, " title='", data.title,
|
||||
"' choices=", data.choiceRewards.size(), " fixed=", data.fixedRewards.size());
|
||||
"' choices=", data.choiceRewards.size(), " fixed=", data.fixedRewards.size(),
|
||||
" prefix=", (best ? best->prefixSkip : size_t(0)),
|
||||
(best && best->fixedArrays ? " fixed" : " var"));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7530,15 +7530,16 @@ void GameScreen::renderQuestOfferRewardWindow(game::GameHandler& gameHandler) {
|
|||
return {iconTex, col};
|
||||
};
|
||||
|
||||
// Helper: show item tooltip
|
||||
auto rewardItemTooltip = [&](const game::QuestRewardItem& ri, ImVec4 nameCol) {
|
||||
// Helper: show full item tooltip (reuses InventoryScreen's rich tooltip)
|
||||
auto rewardItemTooltip = [&](const game::QuestRewardItem& ri, ImVec4 /*nameCol*/) {
|
||||
auto* info = gameHandler.getItemInfo(ri.itemId);
|
||||
if (!info || !info->valid) return;
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::TextColored(nameCol, "%s", info->name.c_str());
|
||||
if (!info->description.empty())
|
||||
ImGui::TextWrapped("%s", info->description.c_str());
|
||||
ImGui::EndTooltip();
|
||||
if (!info || !info->valid) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::TextDisabled("Loading item data...");
|
||||
ImGui::EndTooltip();
|
||||
return;
|
||||
}
|
||||
inventoryScreen.renderItemTooltip(*info);
|
||||
};
|
||||
|
||||
if (!quest.choiceRewards.empty()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue