mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Display quest rewards (money and items) in quest log details pane
Parses reward money, guaranteed items, and choice items from SMSG_QUEST_QUERY_RESPONSE fixed header for both Classic/TBC (40-field) and WotLK (55-field) layouts. Rewards are shown in the quest details pane below objective progress with icons, names, and counts.
This commit is contained in:
parent
1693abffd3
commit
c9c20ce433
3 changed files with 137 additions and 0 deletions
|
|
@ -1131,6 +1131,10 @@ public:
|
|||
uint32_t required = 0;
|
||||
};
|
||||
std::array<ItemObjective, 6> itemObjectives{}; // zeroed by default
|
||||
// Reward data parsed from SMSG_QUEST_QUERY_RESPONSE
|
||||
int32_t rewardMoney = 0; // copper; positive=reward, negative=cost
|
||||
std::array<QuestRewardItem, 4> rewardItems{}; // guaranteed reward items
|
||||
std::array<QuestRewardItem, 6> rewardChoiceItems{}; // player picks one of these
|
||||
};
|
||||
const std::vector<QuestLogEntry>& getQuestLog() const { return questLog_; }
|
||||
void abandonQuest(uint32_t questId);
|
||||
|
|
|
|||
|
|
@ -438,6 +438,49 @@ QuestQueryObjectives extractQuestQueryObjectives(const std::vector<uint8_t>& dat
|
|||
}
|
||||
}
|
||||
|
||||
// Parse quest reward fields from SMSG_QUEST_QUERY_RESPONSE fixed header.
|
||||
// Classic/TBC: 40 fixed fields; WotLK: 55 fixed fields.
|
||||
struct QuestQueryRewards {
|
||||
int32_t rewardMoney = 0;
|
||||
std::array<uint32_t, 4> itemId{};
|
||||
std::array<uint32_t, 4> itemCount{};
|
||||
std::array<uint32_t, 6> choiceItemId{};
|
||||
std::array<uint32_t, 6> choiceItemCount{};
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static QuestQueryRewards tryParseQuestRewards(const std::vector<uint8_t>& data,
|
||||
bool classicLayout) {
|
||||
const size_t base = 8; // after questId(4) + questMethod(4)
|
||||
const size_t fieldCount = classicLayout ? 40u : 55u;
|
||||
const size_t headerEnd = base + fieldCount * 4u;
|
||||
if (data.size() < headerEnd) return {};
|
||||
|
||||
// Field indices (0-based) for each expansion:
|
||||
// Classic/TBC: rewardMoney=[14], rewardItemId[4]=[20..23], rewardItemCount[4]=[24..27],
|
||||
// rewardChoiceItemId[6]=[28..33], rewardChoiceItemCount[6]=[34..39]
|
||||
// WotLK: rewardMoney=[17], rewardItemId[4]=[30..33], rewardItemCount[4]=[34..37],
|
||||
// rewardChoiceItemId[6]=[38..43], rewardChoiceItemCount[6]=[44..49]
|
||||
const size_t moneyField = classicLayout ? 14u : 17u;
|
||||
const size_t itemIdField = classicLayout ? 20u : 30u;
|
||||
const size_t itemCountField = classicLayout ? 24u : 34u;
|
||||
const size_t choiceIdField = classicLayout ? 28u : 38u;
|
||||
const size_t choiceCntField = classicLayout ? 34u : 44u;
|
||||
|
||||
QuestQueryRewards out;
|
||||
out.rewardMoney = static_cast<int32_t>(readU32At(data, base + moneyField * 4u));
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
out.itemId[i] = readU32At(data, base + (itemIdField + i) * 4u);
|
||||
out.itemCount[i] = readU32At(data, base + (itemCountField + i) * 4u);
|
||||
}
|
||||
for (size_t i = 0; i < 6; ++i) {
|
||||
out.choiceItemId[i] = readU32At(data, base + (choiceIdField + i) * 4u);
|
||||
out.choiceItemCount[i] = readU32At(data, base + (choiceCntField + i) * 4u);
|
||||
}
|
||||
out.valid = true;
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
|
@ -4529,6 +4572,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
const bool isClassicLayout = packetParsers_ && packetParsers_->questLogStride() <= 4;
|
||||
const QuestQueryTextCandidate parsed = pickBestQuestQueryTexts(packet.getData(), isClassicLayout);
|
||||
const QuestQueryObjectives objs = extractQuestQueryObjectives(packet.getData(), isClassicLayout);
|
||||
const QuestQueryRewards rwds = tryParseQuestRewards(packet.getData(), isClassicLayout);
|
||||
|
||||
for (auto& q : questLog_) {
|
||||
if (q.questId != questId) continue;
|
||||
|
|
@ -4584,6 +4628,21 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
objs.kills[2].npcOrGoId, "/", objs.kills[2].required, ", ",
|
||||
objs.kills[3].npcOrGoId, "/", objs.kills[3].required, "]");
|
||||
}
|
||||
|
||||
// Store reward data and pre-fetch item info for icons.
|
||||
if (rwds.valid) {
|
||||
q.rewardMoney = rwds.rewardMoney;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
q.rewardItems[i].itemId = rwds.itemId[i];
|
||||
q.rewardItems[i].count = (rwds.itemId[i] != 0) ? rwds.itemCount[i] : 0;
|
||||
if (rwds.itemId[i] != 0) queryItemInfo(rwds.itemId[i], 0);
|
||||
}
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
q.rewardChoiceItems[i].itemId = rwds.choiceItemId[i];
|
||||
q.rewardChoiceItems[i].count = (rwds.choiceItemId[i] != 0) ? rwds.choiceItemCount[i] : 0;
|
||||
if (rwds.choiceItemId[i] != 0) queryItemInfo(rwds.choiceItemId[i], 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -414,6 +414,80 @@ void QuestLogScreen::render(game::GameHandler& gameHandler, InventoryScreen& inv
|
|||
}
|
||||
}
|
||||
|
||||
// Reward summary
|
||||
bool hasAnyReward = (sel.rewardMoney != 0);
|
||||
for (const auto& ri : sel.rewardItems) if (ri.itemId) hasAnyReward = true;
|
||||
for (const auto& ri : sel.rewardChoiceItems) if (ri.itemId) hasAnyReward = true;
|
||||
if (hasAnyReward) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.9f, 0.5f, 1.0f), "Rewards");
|
||||
|
||||
// Money reward
|
||||
if (sel.rewardMoney > 0) {
|
||||
uint32_t rg = static_cast<uint32_t>(sel.rewardMoney) / 10000;
|
||||
uint32_t rs = static_cast<uint32_t>(sel.rewardMoney % 10000) / 100;
|
||||
uint32_t rc = static_cast<uint32_t>(sel.rewardMoney % 100);
|
||||
if (rg > 0)
|
||||
ImGui::Text("%ug %us %uc", rg, rs, rc);
|
||||
else if (rs > 0)
|
||||
ImGui::Text("%us %uc", rs, rc);
|
||||
else
|
||||
ImGui::Text("%uc", rc);
|
||||
}
|
||||
|
||||
// Guaranteed reward items
|
||||
bool anyFixed = false;
|
||||
for (const auto& ri : sel.rewardItems) if (ri.itemId) { anyFixed = true; break; }
|
||||
if (anyFixed) {
|
||||
ImGui::TextDisabled("You will receive:");
|
||||
for (const auto& ri : sel.rewardItems) {
|
||||
if (!ri.itemId) continue;
|
||||
std::string name = "Item " + std::to_string(ri.itemId);
|
||||
uint32_t dispId = 0;
|
||||
const auto* info = gameHandler.getItemInfo(ri.itemId);
|
||||
if (info && info->valid) {
|
||||
if (!info->name.empty()) name = info->name;
|
||||
dispId = info->displayInfoId;
|
||||
}
|
||||
VkDescriptorSet icon = dispId ? invScreen.getItemIcon(dispId) : VK_NULL_HANDLE;
|
||||
if (icon) {
|
||||
ImGui::Image((ImTextureID)(uintptr_t)icon, ImVec2(16, 16));
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (ri.count > 1)
|
||||
ImGui::Text("%s x%u", name.c_str(), ri.count);
|
||||
else
|
||||
ImGui::Text("%s", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Choice reward items
|
||||
bool anyChoice = false;
|
||||
for (const auto& ri : sel.rewardChoiceItems) if (ri.itemId) { anyChoice = true; break; }
|
||||
if (anyChoice) {
|
||||
ImGui::TextDisabled("Choose one of:");
|
||||
for (const auto& ri : sel.rewardChoiceItems) {
|
||||
if (!ri.itemId) continue;
|
||||
std::string name = "Item " + std::to_string(ri.itemId);
|
||||
uint32_t dispId = 0;
|
||||
const auto* info = gameHandler.getItemInfo(ri.itemId);
|
||||
if (info && info->valid) {
|
||||
if (!info->name.empty()) name = info->name;
|
||||
dispId = info->displayInfoId;
|
||||
}
|
||||
VkDescriptorSet icon = dispId ? invScreen.getItemIcon(dispId) : VK_NULL_HANDLE;
|
||||
if (icon) {
|
||||
ImGui::Image((ImTextureID)(uintptr_t)icon, ImVec2(16, 16));
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (ri.count > 1)
|
||||
ImGui::Text("%s x%u", name.c_str(), ri.count);
|
||||
else
|
||||
ImGui::Text("%s", name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track / Abandon buttons
|
||||
ImGui::Separator();
|
||||
bool isTracked = gameHandler.isQuestTracked(sel.questId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue