mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: parse and display item skill/reputation requirements in tooltips
- Store requiredSkill, requiredSkillRank, allowableClass, allowableRace, requiredReputationFaction, and requiredReputationRank from SMSG_ITEM_QUERY_SINGLE_RESPONSE in ItemQueryResponseData (was discarded) - Show "Requires <Skill> (<rank>)" in item tooltip, highlighted red when the player doesn't have sufficient skill level - Show "Requires <Rank> with <Faction>" for reputation-gated items - Skill names resolved from SkillLine.dbc; faction names from Faction.dbc - Also fix loot window tooltip suppressing items with names starting with 'I'
This commit is contained in:
parent
a6c4f6d2e9
commit
b0b47c354a
6 changed files with 96 additions and 20 deletions
|
|
@ -1619,6 +1619,13 @@ struct ItemQueryResponseData {
|
|||
std::array<uint32_t, 3> socketColor{};
|
||||
uint32_t socketBonus = 0; // enchantmentId of socket bonus; 0=none
|
||||
uint32_t itemSetId = 0; // ItemSet.dbc entry; 0=not part of a set
|
||||
// Requirement fields
|
||||
uint32_t requiredSkill = 0; // SkillLine.dbc ID (0 = no skill required)
|
||||
uint32_t requiredSkillRank = 0; // Minimum skill value
|
||||
uint32_t allowableClass = 0; // Class bitmask (0 = all classes)
|
||||
uint32_t allowableRace = 0; // Race bitmask (0 = all races)
|
||||
uint32_t requiredReputationFaction = 0; // Faction.dbc ID (0 = none)
|
||||
uint32_t requiredReputationRank = 0; // 0=Hated..8=Exalted
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1394,17 +1394,17 @@ bool ClassicPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQ
|
|||
return false;
|
||||
}
|
||||
|
||||
packet.readUInt32(); // AllowableClass
|
||||
packet.readUInt32(); // AllowableRace
|
||||
data.allowableClass = packet.readUInt32(); // AllowableClass
|
||||
data.allowableRace = packet.readUInt32(); // AllowableRace
|
||||
data.itemLevel = packet.readUInt32();
|
||||
data.requiredLevel = packet.readUInt32();
|
||||
packet.readUInt32(); // RequiredSkill
|
||||
packet.readUInt32(); // RequiredSkillRank
|
||||
data.requiredSkill = packet.readUInt32(); // RequiredSkill
|
||||
data.requiredSkillRank = packet.readUInt32(); // RequiredSkillRank
|
||||
packet.readUInt32(); // RequiredSpell
|
||||
packet.readUInt32(); // RequiredHonorRank
|
||||
packet.readUInt32(); // RequiredCityRank
|
||||
packet.readUInt32(); // RequiredReputationFaction
|
||||
packet.readUInt32(); // RequiredReputationRank
|
||||
data.requiredReputationFaction = packet.readUInt32(); // RequiredReputationFaction
|
||||
data.requiredReputationRank = packet.readUInt32(); // RequiredReputationRank
|
||||
packet.readUInt32(); // MaxCount
|
||||
data.maxStack = static_cast<int32_t>(packet.readUInt32()); // Stackable
|
||||
data.containerSlots = packet.readUInt32();
|
||||
|
|
|
|||
|
|
@ -1011,17 +1011,17 @@ bool TbcPacketParsers::parseItemQueryResponse(network::Packet& packet, ItemQuery
|
|||
return false;
|
||||
}
|
||||
|
||||
packet.readUInt32(); // AllowableClass
|
||||
packet.readUInt32(); // AllowableRace
|
||||
data.allowableClass = packet.readUInt32(); // AllowableClass
|
||||
data.allowableRace = packet.readUInt32(); // AllowableRace
|
||||
data.itemLevel = packet.readUInt32();
|
||||
data.requiredLevel = packet.readUInt32();
|
||||
packet.readUInt32(); // RequiredSkill
|
||||
packet.readUInt32(); // RequiredSkillRank
|
||||
data.requiredSkill = packet.readUInt32(); // RequiredSkill
|
||||
data.requiredSkillRank = packet.readUInt32(); // RequiredSkillRank
|
||||
packet.readUInt32(); // RequiredSpell
|
||||
packet.readUInt32(); // RequiredHonorRank
|
||||
packet.readUInt32(); // RequiredCityRank
|
||||
packet.readUInt32(); // RequiredReputationFaction
|
||||
packet.readUInt32(); // RequiredReputationRank
|
||||
data.requiredReputationFaction = packet.readUInt32(); // RequiredReputationFaction
|
||||
data.requiredReputationRank = packet.readUInt32(); // RequiredReputationRank
|
||||
packet.readUInt32(); // MaxCount
|
||||
data.maxStack = static_cast<int32_t>(packet.readUInt32()); // Stackable
|
||||
data.containerSlots = packet.readUInt32();
|
||||
|
|
|
|||
|
|
@ -2868,17 +2868,17 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
|||
LOG_ERROR("SMSG_ITEM_QUERY_SINGLE_RESPONSE: truncated before statsCount (entry=", data.entry, ")");
|
||||
return false;
|
||||
}
|
||||
packet.readUInt32(); // AllowableClass
|
||||
packet.readUInt32(); // AllowableRace
|
||||
data.allowableClass = packet.readUInt32(); // AllowableClass
|
||||
data.allowableRace = packet.readUInt32(); // AllowableRace
|
||||
data.itemLevel = packet.readUInt32();
|
||||
data.requiredLevel = packet.readUInt32();
|
||||
packet.readUInt32(); // RequiredSkill
|
||||
packet.readUInt32(); // RequiredSkillRank
|
||||
data.requiredSkill = packet.readUInt32(); // RequiredSkill
|
||||
data.requiredSkillRank = packet.readUInt32(); // RequiredSkillRank
|
||||
packet.readUInt32(); // RequiredSpell
|
||||
packet.readUInt32(); // RequiredHonorRank
|
||||
packet.readUInt32(); // RequiredCityRank
|
||||
packet.readUInt32(); // RequiredReputationFaction
|
||||
packet.readUInt32(); // RequiredReputationRank
|
||||
data.requiredReputationFaction = packet.readUInt32(); // RequiredReputationFaction
|
||||
data.requiredReputationRank = packet.readUInt32(); // RequiredReputationRank
|
||||
packet.readUInt32(); // MaxCount
|
||||
data.maxStack = static_cast<int32_t>(packet.readUInt32()); // Stackable
|
||||
data.containerSlots = packet.readUInt32();
|
||||
|
|
|
|||
|
|
@ -12462,8 +12462,9 @@ void GameScreen::renderLootWindow(game::GameHandler& gameHandler) {
|
|||
// Show item tooltip on hover
|
||||
if (hovered && info && info->valid) {
|
||||
inventoryScreen.renderItemTooltip(*info);
|
||||
} else if (hovered && !itemName.empty() && itemName[0] != 'I') {
|
||||
ImGui::SetTooltip("%s", itemName.c_str());
|
||||
} else if (hovered && info && !info->name.empty()) {
|
||||
// Item info received but not yet fully valid — show name at minimum
|
||||
ImGui::SetTooltip("%s", info->name.c_str());
|
||||
}
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
|
|
|
|||
|
|
@ -2795,6 +2795,74 @@ void InventoryScreen::renderItemTooltip(const game::ItemQueryResponseData& info,
|
|||
ImGui::TextColored(reqColor, "Requires Level %u", info.requiredLevel);
|
||||
}
|
||||
|
||||
// Required skill (e.g. "Requires Engineering (300)")
|
||||
if (info.requiredSkill != 0 && info.requiredSkillRank > 0) {
|
||||
// Lazy-load SkillLine.dbc names
|
||||
static std::unordered_map<uint32_t, std::string> s_skillNames;
|
||||
static bool s_skillNamesLoaded = false;
|
||||
if (!s_skillNamesLoaded && assetManager_) {
|
||||
s_skillNamesLoaded = true;
|
||||
auto dbc = assetManager_->loadDBC("SkillLine.dbc");
|
||||
if (dbc && dbc->isLoaded()) {
|
||||
const auto* layout = pipeline::getActiveDBCLayout()
|
||||
? pipeline::getActiveDBCLayout()->getLayout("SkillLine") : nullptr;
|
||||
uint32_t idF = layout ? (*layout)["ID"] : 0;
|
||||
uint32_t nameF = layout ? (*layout)["Name"] : 2;
|
||||
for (uint32_t r = 0; r < dbc->getRecordCount(); ++r) {
|
||||
uint32_t sid = dbc->getUInt32(r, idF);
|
||||
if (!sid) continue;
|
||||
std::string sname = dbc->getString(r, nameF);
|
||||
if (!sname.empty()) s_skillNames[sid] = std::move(sname);
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t playerSkillVal = 0;
|
||||
if (gameHandler_) {
|
||||
const auto& skills = gameHandler_->getPlayerSkills();
|
||||
auto skPit = skills.find(info.requiredSkill);
|
||||
if (skPit != skills.end()) playerSkillVal = skPit->second.effectiveValue();
|
||||
}
|
||||
bool meetsSkill = (playerSkillVal == 0 || playerSkillVal >= info.requiredSkillRank);
|
||||
ImVec4 skColor = meetsSkill ? ImVec4(1.0f, 1.0f, 1.0f, 0.75f) : ImVec4(1.0f, 0.5f, 0.5f, 1.0f);
|
||||
auto skIt = s_skillNames.find(info.requiredSkill);
|
||||
if (skIt != s_skillNames.end())
|
||||
ImGui::TextColored(skColor, "Requires %s (%u)", skIt->second.c_str(), info.requiredSkillRank);
|
||||
else
|
||||
ImGui::TextColored(skColor, "Requires Skill %u (%u)", info.requiredSkill, info.requiredSkillRank);
|
||||
}
|
||||
|
||||
// Required reputation (e.g. "Requires Exalted with Argent Dawn")
|
||||
if (info.requiredReputationFaction != 0 && info.requiredReputationRank > 0) {
|
||||
static std::unordered_map<uint32_t, std::string> s_factionNames;
|
||||
static bool s_factionNamesLoaded = false;
|
||||
if (!s_factionNamesLoaded && assetManager_) {
|
||||
s_factionNamesLoaded = true;
|
||||
auto dbc = assetManager_->loadDBC("Faction.dbc");
|
||||
if (dbc && dbc->isLoaded()) {
|
||||
const auto* layout = pipeline::getActiveDBCLayout()
|
||||
? pipeline::getActiveDBCLayout()->getLayout("Faction") : nullptr;
|
||||
uint32_t idF = layout ? (*layout)["ID"] : 0;
|
||||
uint32_t nameF = layout ? (*layout)["Name"] : 20;
|
||||
for (uint32_t r = 0; r < dbc->getRecordCount(); ++r) {
|
||||
uint32_t fid = dbc->getUInt32(r, idF);
|
||||
if (!fid) continue;
|
||||
std::string fname = dbc->getString(r, nameF);
|
||||
if (!fname.empty()) s_factionNames[fid] = std::move(fname);
|
||||
}
|
||||
}
|
||||
}
|
||||
static const char* kRepRankNames[] = {
|
||||
"Hated", "Hostile", "Unfriendly", "Neutral",
|
||||
"Friendly", "Honored", "Revered", "Exalted"
|
||||
};
|
||||
const char* rankName = (info.requiredReputationRank < 8)
|
||||
? kRepRankNames[info.requiredReputationRank] : "Unknown";
|
||||
auto fIt = s_factionNames.find(info.requiredReputationFaction);
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.75f), "Requires %s with %s",
|
||||
rankName,
|
||||
fIt != s_factionNames.end() ? fIt->second.c_str() : "Unknown Faction");
|
||||
}
|
||||
|
||||
// Spell effects
|
||||
for (const auto& sp : info.spells) {
|
||||
if (sp.spellId == 0) continue;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue