if not Cursive.superwow then return end local L = AceLibrary("AceLocale-2.2"):new("Cursive") local _, playerClassName = UnitClass("player") local curses = { trackedCurseIds = {}, trackedCurseNamesToTextures = {}, trackedCurseNameRanksToSpellSlots = {}, conflagrateSpellIds = { [17962] = true, [18930] = true, [18931] = true, [18932] = true, }, guids = {}, pendingCast = {}, resistSoundGuids = {}, expiringSoundGuids = {}, requestedExpiringSoundGuids = {} -- guid added on spellcast, moved to expiringSoundGuids once rendered by ui } -- combat events for curses local fades_test = L["(.+) fades from (.+)"] local resist_test = L["Your (.+) was resisted by (.+)"] local lastGuid = nil function curses:LoadCurses() -- reset dicts curses.trackedCurseIds = {} curses.trackedCurseNamesToTextures = {} curses.trackedCurseNameRanksToSpellSlots = {} -- curses to track if playerClassName == "WARLOCK" then curses.trackedCurseIds = getWarlockSpells() elseif playerClassName == "PRIEST" then curses.trackedCurseIds = getPriestSpells() elseif playerClassName == "MAGE" then curses.trackedCurseIds = getMageSpells() elseif playerClassName == "DRUID" then curses.trackedCurseIds = getDruidSpells() elseif playerClassName == "HUNTER" then curses.trackedCurseIds = getHunterSpells() elseif playerClassName == "ROGUE" then curses.trackedCurseIds = getRogueSpells() elseif playerClassName == "SHAMAN" then curses.trackedCurseIds = getShamanSpells() end -- go through spell slots and local i = 1 while true do local spellname, spellrank = GetSpellName(i, BOOKTYPE_SPELL) if not spellname then break end if spellrank == "" then spellrank = "Rank 1" end curses.trackedCurseNameRanksToSpellSlots[string.lower(spellname) .. spellrank] = i i = i + 1 end for id, data in pairs(curses.trackedCurseIds) do -- get the texture local name, rank, texture = SpellInfo(id) -- update trackedCurseNamesToTextures curses.trackedCurseNamesToTextures[data.name] = texture -- update trackedCurseIds curses.trackedCurseIds[id].texture = texture end end function curses:ScanTooltipForDuration(curseSpellID) -- scan spellbook for duration in case they have haste talent local nameRank = curses.trackedCurseIds[curseSpellID].name .. "Rank " .. curses.trackedCurseIds[curseSpellID].rank local spellSlot = curses.trackedCurseNameRanksToSpellSlots[nameRank] if spellSlot then Cursive.core.tooltipScan:SetOwner(Cursive.core.tooltipScan, "ANCHOR_NONE") Cursive.core.tooltipScan:ClearLines() Cursive.core.tooltipScan:SetSpell(spellSlot, BOOKTYPE_SPELL) local numLines = Cursive.core.tooltipScan:NumLines() if numLines and numLines > 0 then -- get the last line local text = getglobal("CursiveTooltipScan" .. "TextLeft" .. numLines):GetText() if text then local _, _, duration = string.find(text, L["curse_duration_format"]) if duration then return tonumber(duration) end end end end return curses.trackedCurseIds[curseSpellID].duration end function curses:GetCurseDuration(curseSpellID) if curses.trackedCurseIds[curseSpellID].variable_duration then return curses:ScanTooltipForDuration(curseSpellID) end return curses.trackedCurseIds[curseSpellID].duration end function curses:ScanGuidForCurse(guid, curseSpellID) for i = 1, 64 do local _, _, _, spellID = UnitDebuff(guid, i) if spellID then if spellID == curseSpellID then return true end else break end end for i = 1, 64 do local _, _, spellID = UnitBuff(guid, i) if spellID then if spellID == curseSpellID then return true end else break end end return nil end Cursive:RegisterEvent("LEARNED_SPELL_IN_TAB", function() -- reload curses in case spell slots changed curses:LoadCurses() end) Cursive:RegisterEvent("UNIT_CASTEVENT", function(casterGuid, targetGuid, event, spellID, castDuration) -- immolate will fire both start and cast if event == "CAST" then local _, guid = UnitExists("player") if casterGuid ~= guid then return end -- store pending cast curses.pendingCast = { spellID = spellID, targetGuid = targetGuid, castDuration = castDuration } if curses.trackedCurseIds[spellID] then lastGuid = targetGuid Cursive:ScheduleEvent("addCurse" .. targetGuid .. curses.trackedCurseIds[spellID].name, curses.ApplyCurse, 0.2, self, spellID, targetGuid, GetTime()) elseif curses.conflagrateSpellIds[spellID] then Cursive:ScheduleEvent("updateCurse" .. targetGuid .. L["conflagrate"], curses.UpdateCurse, 0.2, self, spellID, targetGuid, GetTime()) end elseif event == "START" then if curses.trackedCurseIds[spellID] then local _, guid = UnitExists("player") if casterGuid ~= guid then return end -- store pending cast curses.pendingCast = { spellID = spellID, targetGuid = targetGuid, castDuration = castDuration } end elseif event == "FAIL" then if curses.trackedCurseIds[spellID] then local _, guid = UnitExists("player") if casterGuid ~= guid then return end -- clear pending cast curses.pendingCast = {} end end end) Cursive:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE", function(message) -- check for resist local _, _, spellName, target = string.find(message, resist_test) if spellName and target then spellName = string.lower(spellName) -- clear pending cast curses.pendingCast = {} if curses.trackedCurseNamesToTextures[spellName] and lastGuid then Cursive:CancelScheduledEvent("addCurse" .. lastGuid .. spellName) -- check if sound should be played if curses:ShouldPlayResistSound(lastGuid) then PlaySoundFile("Interface\\AddOns\\Cursive\\Sounds\\resist.mp3") end elseif spellName == L["conflagrate"] and lastGuid then Cursive:CancelScheduledEvent("updateCurse" .. lastGuid .. spellName) end end end ) -- resists Cursive:RegisterEvent("CHAT_MSG_SPELL_AURA_GONE_OTHER", function(message) -- check if spell that faded is relevant local _, _, spellName, target = string.find(message, fades_test) if spellName and target then spellName = string.lower(spellName) if curses.trackedCurseNamesToTextures[spellName] then -- loop through targets with active curses for guid, data in pairs(curses.guids) do for curseName, curseData in pairs(data) do if curseName == spellName then -- see if target still has that curse if not curses:ScanGuidForCurse(guid, curseData.spellID) then -- remove curse curses:RemoveCurse(guid, curseName) end end end end end end end ) function curses:TimeRemaining(curseData) return math.ceil(curseData.duration - (GetTime() - curseData.start)) end function curses:EnableResistSound(guid) curses.resistSoundGuids[guid] = true end function curses:EnableExpiringSound(spellNameNoRank, guid) if curses.requestedExpiringSoundGuids[guid] and curses.requestedExpiringSoundGuids[guid][spellNameNoRank] then curses.requestedExpiringSoundGuids[guid][spellNameNoRank] = nil end if not curses.expiringSoundGuids[guid] then curses.expiringSoundGuids[guid] = {} end curses.expiringSoundGuids[guid][spellNameNoRank] = true end function curses:RequestExpiringSound(spellNameNoRank, guid) if not curses.requestedExpiringSoundGuids[guid] then curses.requestedExpiringSoundGuids[guid] = {} end curses.requestedExpiringSoundGuids[guid][spellNameNoRank] = true end function curses:HasRequestedExpiringSound(spellNameNoRank, guid) return curses.requestedExpiringSoundGuids[guid] and curses.requestedExpiringSoundGuids[guid][spellNameNoRank] end function curses:ShouldPlayExpiringSound(spellNameNoRank, guid) if curses.expiringSoundGuids[guid] and curses.expiringSoundGuids[guid][spellNameNoRank] then curses.expiringSoundGuids[guid][spellNameNoRank] = nil -- remove entry to avoid playing sound multiple times return true end return false end function curses:ShouldPlayResistSound(guid) if curses.resistSoundGuids[guid] then curses.resistSoundGuids[guid] = nil -- remove entry to avoid playing sound multiple times return true end return false end function curses:HasAnyCurse(guid) if curses.guids[guid] and next(curses.guids[guid]) then return true end return nil end function curses:HasCurse(spellName, targetGuid, minRemaining) if not minRemaining then minRemaining = 0 -- default to 0 end if curses.guids[targetGuid] and curses.guids[targetGuid][spellName] then local remaining = Cursive.curses:TimeRemaining(curses.guids[targetGuid][spellName]) if remaining > minRemaining then return true end end -- check pending cast if curses.pendingCast and curses.pendingCast.targetGuid == targetGuid and curses.pendingCast.spellID and curses.trackedCurseIds[curses.pendingCast.spellID] and curses.trackedCurseIds[curses.pendingCast.spellID].name == spellName then return true end return nil end function curses:ApplyCurse(spellID, targetGuid, startTime) -- clear pending cast curses.pendingCast = {} local name = curses.trackedCurseIds[spellID].name local rank = curses.trackedCurseIds[spellID].rank local duration = curses:GetCurseDuration(spellID) if not curses.guids[targetGuid] then curses.guids[targetGuid] = {} end curses.guids[targetGuid][name] = { rank = rank, duration = duration, start = startTime, spellID = spellID, } end function curses:UpdateCurse(spellID, targetGuid, startTime) -- clear pending cast curses.pendingCast = {} if curses.conflagrateSpellIds[spellID] then -- check if target has immolate if curses:HasCurse(L["immolate"], targetGuid) then -- reduce duration by 3 sec curses.guids[targetGuid][L["immolate"]].duration = curses.guids[targetGuid][L["immolate"]].duration - 3 end end end function curses:RemoveCurse(guid, curseName) if curses.guids[guid] and curses.guids[guid][curseName] then curses.guids[guid][curseName] = nil end if curses.expiringSoundGuids[guid] and curses.expiringSoundGuids[guid][curseName] then curses.expiringSoundGuids[guid][curseName] = nil end end function curses:RemoveGuid(guid) curses.guids[guid] = nil curses.resistSoundGuids[guid] = nil curses.expiringSoundGuids[guid] = nil curses.requestedExpiringSoundGuids[guid] = nil end Cursive.curses = curses