feat: debuff dispel-type border coloring in buff bar

Read DispelType from Spell.dbc (new field in all expansion DBC layouts)
and use it to color debuff icon borders: magic=blue, curse=purple,
disease=brown, poison=green, other=red. Buffs remain green-bordered.
Adds getSpellDispelType() to GameHandler for lazy cache lookup.
This commit is contained in:
Kelsi 2026-03-12 06:55:16 -07:00
parent 9a21e19486
commit d817e4144c
7 changed files with 45 additions and 7 deletions

View file

@ -2,7 +2,8 @@
"Spell": { "Spell": {
"ID": 0, "Attributes": 5, "IconID": 117, "ID": 0, "Attributes": 5, "IconID": 117,
"Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1, "Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1,
"CastingTimeIndex": 15, "PowerType": 28, "ManaCost": 29, "RangeIndex": 33 "CastingTimeIndex": 15, "PowerType": 28, "ManaCost": 29, "RangeIndex": 33,
"DispelType": 4
}, },
"SpellRange": { "MaxRange": 2 }, "SpellRange": { "MaxRange": 2 },
"ItemDisplayInfo": { "ItemDisplayInfo": {

View file

@ -2,7 +2,8 @@
"Spell": { "Spell": {
"ID": 0, "Attributes": 5, "IconID": 124, "ID": 0, "Attributes": 5, "IconID": 124,
"Name": 127, "Tooltip": 154, "Rank": 136, "SchoolMask": 215, "Name": 127, "Tooltip": 154, "Rank": 136, "SchoolMask": 215,
"CastingTimeIndex": 22, "PowerType": 35, "ManaCost": 36, "RangeIndex": 40 "CastingTimeIndex": 22, "PowerType": 35, "ManaCost": 36, "RangeIndex": 40,
"DispelType": 3
}, },
"SpellRange": { "MaxRange": 4 }, "SpellRange": { "MaxRange": 4 },
"ItemDisplayInfo": { "ItemDisplayInfo": {

View file

@ -2,7 +2,8 @@
"Spell": { "Spell": {
"ID": 0, "Attributes": 5, "IconID": 117, "ID": 0, "Attributes": 5, "IconID": 117,
"Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1, "Name": 120, "Tooltip": 147, "Rank": 129, "SchoolEnum": 1,
"CastingTimeIndex": 15, "PowerType": 28, "ManaCost": 29, "RangeIndex": 33 "CastingTimeIndex": 15, "PowerType": 28, "ManaCost": 29, "RangeIndex": 33,
"DispelType": 4
}, },
"SpellRange": { "MaxRange": 2 }, "SpellRange": { "MaxRange": 2 },
"ItemDisplayInfo": { "ItemDisplayInfo": {

View file

@ -2,7 +2,8 @@
"Spell": { "Spell": {
"ID": 0, "Attributes": 4, "IconID": 133, "ID": 0, "Attributes": 4, "IconID": 133,
"Name": 136, "Tooltip": 139, "Rank": 153, "SchoolMask": 225, "Name": 136, "Tooltip": 139, "Rank": 153, "SchoolMask": 225,
"PowerType": 14, "ManaCost": 39, "CastingTimeIndex": 47, "RangeIndex": 49 "PowerType": 14, "ManaCost": 39, "CastingTimeIndex": 47, "RangeIndex": 49,
"DispelType": 2
}, },
"SpellRange": { "MaxRange": 4 }, "SpellRange": { "MaxRange": 4 },
"ItemDisplayInfo": { "ItemDisplayInfo": {

View file

@ -1586,6 +1586,8 @@ public:
const std::string& getSpellName(uint32_t spellId) const; const std::string& getSpellName(uint32_t spellId) const;
const std::string& getSpellRank(uint32_t spellId) const; const std::string& getSpellRank(uint32_t spellId) const;
const std::string& getSkillLineName(uint32_t spellId) const; const std::string& getSkillLineName(uint32_t spellId) const;
/// Returns the DispelType for a spell (0=none,1=magic,2=curse,3=disease,4=poison,5+=other)
uint8_t getSpellDispelType(uint32_t spellId) const;
struct TrainerTab { struct TrainerTab {
std::string name; std::string name;
@ -2486,7 +2488,7 @@ private:
// Trainer // Trainer
bool trainerWindowOpen_ = false; bool trainerWindowOpen_ = false;
TrainerListData currentTrainerList_; TrainerListData currentTrainerList_;
struct SpellNameEntry { std::string name; std::string rank; uint32_t schoolMask = 0; }; struct SpellNameEntry { std::string name; std::string rank; uint32_t schoolMask = 0; uint8_t dispelType = 0; };
std::unordered_map<uint32_t, SpellNameEntry> spellNameCache_; std::unordered_map<uint32_t, SpellNameEntry> spellNameCache_;
bool spellNameCacheLoaded_ = false; bool spellNameCacheLoaded_ = false;

View file

@ -16983,6 +16983,14 @@ void GameHandler::loadSpellNameCache() {
if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { schoolEnumField = f; hasSchoolEnum = true; } if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { schoolEnumField = f; hasSchoolEnum = true; }
} }
// DispelType field (0=none,1=magic,2=curse,3=disease,4=poison,5=stealth,…)
uint32_t dispelField = 0xFFFFFFFF;
bool hasDispelField = false;
if (spellL) {
uint32_t f = spellL->field("DispelType");
if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { dispelField = f; hasDispelField = true; }
}
uint32_t count = dbc->getRecordCount(); uint32_t count = dbc->getRecordCount();
for (uint32_t i = 0; i < count; ++i) { for (uint32_t i = 0; i < count; ++i) {
uint32_t id = dbc->getUInt32(i, spellL ? (*spellL)["ID"] : 0); uint32_t id = dbc->getUInt32(i, spellL ? (*spellL)["ID"] : 0);
@ -16990,7 +16998,7 @@ void GameHandler::loadSpellNameCache() {
std::string name = dbc->getString(i, spellL ? (*spellL)["Name"] : 136); std::string name = dbc->getString(i, spellL ? (*spellL)["Name"] : 136);
std::string rank = dbc->getString(i, spellL ? (*spellL)["Rank"] : 153); std::string rank = dbc->getString(i, spellL ? (*spellL)["Rank"] : 153);
if (!name.empty()) { if (!name.empty()) {
SpellNameEntry entry{std::move(name), std::move(rank), 0}; SpellNameEntry entry{std::move(name), std::move(rank), 0, 0};
if (hasSchoolMask) { if (hasSchoolMask) {
entry.schoolMask = dbc->getUInt32(i, schoolMaskField); entry.schoolMask = dbc->getUInt32(i, schoolMaskField);
} else if (hasSchoolEnum) { } else if (hasSchoolEnum) {
@ -16999,6 +17007,9 @@ void GameHandler::loadSpellNameCache() {
uint32_t e = dbc->getUInt32(i, schoolEnumField); uint32_t e = dbc->getUInt32(i, schoolEnumField);
entry.schoolMask = (e < 7) ? enumToBitmask[e] : 0; entry.schoolMask = (e < 7) ? enumToBitmask[e] : 0;
} }
if (hasDispelField) {
entry.dispelType = static_cast<uint8_t>(dbc->getUInt32(i, dispelField));
}
spellNameCache_[id] = std::move(entry); spellNameCache_[id] = std::move(entry);
} }
} }
@ -17192,6 +17203,12 @@ const std::string& GameHandler::getSpellRank(uint32_t spellId) const {
return (it != spellNameCache_.end()) ? it->second.rank : EMPTY_STRING; return (it != spellNameCache_.end()) ? it->second.rank : EMPTY_STRING;
} }
uint8_t GameHandler::getSpellDispelType(uint32_t spellId) const {
const_cast<GameHandler*>(this)->loadSpellNameCache();
auto it = spellNameCache_.find(spellId);
return (it != spellNameCache_.end()) ? it->second.dispelType : 0;
}
const std::string& GameHandler::getSkillLineName(uint32_t spellId) const { const std::string& GameHandler::getSkillLineName(uint32_t spellId) const {
auto slIt = spellToSkillLine_.find(spellId); auto slIt = spellToSkillLine_.find(spellId);
if (slIt == spellToSkillLine_.end()) return EMPTY_STRING; if (slIt == spellToSkillLine_.end()) return EMPTY_STRING;

View file

@ -9503,7 +9503,22 @@ void GameScreen::renderBuffBar(game::GameHandler& gameHandler) {
ImGui::PushID(static_cast<int>(i) + (pass * 256)); ImGui::PushID(static_cast<int>(i) + (pass * 256));
ImVec4 borderColor = isBuff ? ImVec4(0.2f, 0.8f, 0.2f, 0.9f) : ImVec4(0.8f, 0.2f, 0.2f, 0.9f); // Determine border color: buffs = green; debuffs use WoW dispel-type colors
ImVec4 borderColor;
if (isBuff) {
borderColor = ImVec4(0.2f, 0.8f, 0.2f, 0.9f); // green
} else {
// Debuff: color by dispel type (0=none/red, 1=magic/blue, 2=curse/purple,
// 3=disease/brown, 4=poison/green, other=dark-red)
uint8_t dt = gameHandler.getSpellDispelType(aura.spellId);
switch (dt) {
case 1: borderColor = ImVec4(0.15f, 0.50f, 1.00f, 0.9f); break; // magic: blue
case 2: borderColor = ImVec4(0.70f, 0.20f, 0.90f, 0.9f); break; // curse: purple
case 3: borderColor = ImVec4(0.55f, 0.30f, 0.10f, 0.9f); break; // disease: brown
case 4: borderColor = ImVec4(0.10f, 0.70f, 0.10f, 0.9f); break; // poison: green
default: borderColor = ImVec4(0.80f, 0.20f, 0.20f, 0.9f); break; // other: red
}
}
// Try to get spell icon // Try to get spell icon
VkDescriptorSet iconTex = VK_NULL_HANDLE; VkDescriptorSet iconTex = VK_NULL_HANDLE;