mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
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:
parent
b8712f380d
commit
279b4de09a
7 changed files with 55 additions and 17 deletions
|
|
@ -18279,10 +18279,11 @@ void GameHandler::handleSpellStart(network::Packet& packet) {
|
|||
// Track cast bar for any non-player caster (target frame + boss frames)
|
||||
if (data.casterUnit != playerGuid && data.castTime > 0) {
|
||||
auto& s = unitCastStates_[data.casterUnit];
|
||||
s.casting = true;
|
||||
s.spellId = data.spellId;
|
||||
s.timeTotal = data.castTime / 1000.0f;
|
||||
s.timeRemaining = s.timeTotal;
|
||||
s.casting = true;
|
||||
s.spellId = data.spellId;
|
||||
s.timeTotal = data.castTime / 1000.0f;
|
||||
s.timeRemaining = s.timeTotal;
|
||||
s.interruptible = isSpellInterruptible(data.spellId);
|
||||
// Trigger cast animation on the casting unit
|
||||
if (spellCastAnimCallback_) {
|
||||
spellCastAnimCallback_(data.casterUnit, true, false);
|
||||
|
|
@ -21320,6 +21321,14 @@ void GameHandler::loadSpellNameCache() {
|
|||
if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { dispelField = f; hasDispelField = true; }
|
||||
}
|
||||
|
||||
// AttributesEx field (bit 4 = SPELL_ATTR_EX_NOT_INTERRUPTIBLE)
|
||||
uint32_t attrExField = 0xFFFFFFFF;
|
||||
bool hasAttrExField = false;
|
||||
if (spellL) {
|
||||
uint32_t f = spellL->field("AttributesEx");
|
||||
if (f != 0xFFFFFFFF && f < dbc->getFieldCount()) { attrExField = f; hasAttrExField = true; }
|
||||
}
|
||||
|
||||
// Tooltip/description field
|
||||
uint32_t tooltipField = 0xFFFFFFFF;
|
||||
if (spellL) {
|
||||
|
|
@ -21334,7 +21343,7 @@ void GameHandler::loadSpellNameCache() {
|
|||
std::string name = dbc->getString(i, spellL ? (*spellL)["Name"] : 136);
|
||||
std::string rank = dbc->getString(i, spellL ? (*spellL)["Rank"] : 153);
|
||||
if (!name.empty()) {
|
||||
SpellNameEntry entry{std::move(name), std::move(rank), {}, 0, 0};
|
||||
SpellNameEntry entry{std::move(name), std::move(rank), {}, 0, 0, 0};
|
||||
if (tooltipField != 0xFFFFFFFF) {
|
||||
entry.description = dbc->getString(i, tooltipField);
|
||||
}
|
||||
|
|
@ -21349,6 +21358,9 @@ void GameHandler::loadSpellNameCache() {
|
|||
if (hasDispelField) {
|
||||
entry.dispelType = static_cast<uint8_t>(dbc->getUInt32(i, dispelField));
|
||||
}
|
||||
if (hasAttrExField) {
|
||||
entry.attrEx = dbc->getUInt32(i, attrExField);
|
||||
}
|
||||
spellNameCache_[id] = std::move(entry);
|
||||
}
|
||||
}
|
||||
|
|
@ -21554,6 +21566,15 @@ uint8_t GameHandler::getSpellDispelType(uint32_t spellId) const {
|
|||
return (it != spellNameCache_.end()) ? it->second.dispelType : 0;
|
||||
}
|
||||
|
||||
bool GameHandler::isSpellInterruptible(uint32_t spellId) const {
|
||||
if (spellId == 0) return true;
|
||||
const_cast<GameHandler*>(this)->loadSpellNameCache();
|
||||
auto it = spellNameCache_.find(spellId);
|
||||
if (it == spellNameCache_.end()) return true; // assume interruptible if unknown
|
||||
// SPELL_ATTR_EX_NOT_INTERRUPTIBLE = bit 4 of AttributesEx (0x00000010)
|
||||
return (it->second.attrEx & 0x00000010u) == 0;
|
||||
}
|
||||
|
||||
const std::string& GameHandler::getSkillLineName(uint32_t spellId) const {
|
||||
auto slIt = spellToSkillLine_.find(spellId);
|
||||
if (slIt == spellToSkillLine_.end()) return EMPTY_STRING;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue