feat: color cast bars green/red by spell interruptibility from Spell.dbc

Load AttributesEx from Spell.dbc for all expansions (Classic/TBC/WotLK/
Turtle). Check SPELL_ATTR_EX_NOT_INTERRUPTIBLE (bit 4 = 0x10) to classify
each cast as interruptible or not when SMSG_SPELL_START arrives.

Target frame and nameplate cast bars now use:
- Green: spell can be interrupted by Kick/Counterspell/Pummel etc.
- Red: spell is immune to interrupt (boss abilities, instant-cast effects)
Both colors pulse faster at >80% completion to signal the closing window.

Adds GameHandler::isSpellInterruptible() and UnitCastState::interruptible.
This commit is contained in:
Kelsi 2026-03-17 19:43:19 -07:00
parent b8712f380d
commit 279b4de09a
7 changed files with 55 additions and 17 deletions

View file

@ -798,6 +798,7 @@ public:
uint32_t spellId = 0;
float timeRemaining = 0.0f;
float timeTotal = 0.0f;
bool interruptible = true; ///< false when SPELL_ATTR_EX_NOT_INTERRUPTIBLE is set
};
// Returns cast state for any unit by GUID (empty/non-casting if not found)
const UnitCastState* getUnitCastState(uint64_t guid) const {
@ -819,6 +820,10 @@ public:
auto* s = getUnitCastState(targetGuid);
return s ? s->timeRemaining : 0.0f;
}
bool isTargetCastInterruptible() const {
auto* s = getUnitCastState(targetGuid);
return s ? s->interruptible : true;
}
// Talents
uint8_t getActiveTalentSpec() const { return activeTalentSpec_; }
@ -2056,6 +2061,9 @@ public:
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;
/// Returns true if the spell can be interrupted by abilities like Kick/Counterspell.
/// False for spells with SPELL_ATTR_EX_NOT_INTERRUPTIBLE (attrEx bit 4 = 0x10).
bool isSpellInterruptible(uint32_t spellId) const;
struct TrainerTab {
std::string name;
@ -3059,7 +3067,7 @@ private:
// Trainer
bool trainerWindowOpen_ = false;
TrainerListData currentTrainerList_;
struct SpellNameEntry { std::string name; std::string rank; std::string description; uint32_t schoolMask = 0; uint8_t dispelType = 0; };
struct SpellNameEntry { std::string name; std::string rank; std::string description; uint32_t schoolMask = 0; uint8_t dispelType = 0; uint32_t attrEx = 0; };
std::unordered_map<uint32_t, SpellNameEntry> spellNameCache_;
bool spellNameCacheLoaded_ = false;