-- Cooldowns.lua local _; local pairs, ipairs, unpack = pairs, ipairs, unpack; local tremove, format, ssub = table.remove, string.format, string.sub; local floor, strsplit = math.floor, strsplit; local GetTime, UnitGUID, UnitClass = GetTime, UnitGUID, UnitClass; local UnitName, GetSpellTexture = UnitName, GetSpellTexture; local IsInGroup, IsInRaid = IsInGroup, IsInRaid; local CreateFrame, CTimerAfter = CreateFrame, C_Timer.After; local UnitIsUnit, IsEncounterInProgress = UnitIsUnit, IsEncounterInProgress; local UnitExists, UnitGroupRolesAssigned = UnitExists, UnitGroupRolesAssigned; local UnitIsDeadOrGhost, UnitIsConnected = UnitIsDeadOrGhost, UnitIsConnected; local GetTalentInfo, GetInspectSpecialization = GetTalentInfo, GetInspectSpecialization; local GetSpecializationInfo, GetSpecialization = GetSpecializationInfo, GetSpecialization; local GetInventoryItemLink, GetInventoryItemID = GetInventoryItemLink, GetInventoryItemID; local CanInspect, NotifyInspect, ClearInspectPlayer = CanInspect, NotifyInspect, ClearInspectPlayer; local INVSLOT_MAINHAND, INVSLOT_WRIST = INVSLOT_MAINHAND, INVSLOT_WRIST; local upColor = {0, 0.7, 0.2, 0.7}; local downColor = {1, 0, 0, 0.5}; local cdframe = CreateFrame("Frame", "OmaCD", UIParent); local frames = {}; local unused = {}; local shown = {}; -- simple array of frames in shown order local specs = { ["PALADIN"] = 65, ["PRIEST"] = false, -- priest has 2 healing specs ["SHAMAN"] = 264, ["MONK"] = 270, ["DRUID"] = 105, }; local trackedcds = { [65] = { -- Holy Paladin [31821] = 180, -- Aura Mastery }, [105] = { -- Resto Druid [740] = 180, -- Tranquility }, [256] = { -- Discipline Priest [62618] = 180, -- Power Word: Barrier }, [257] = { -- Holy Priest [64843] = 180, -- Divine Hymn }, [264] = { -- Resto Shaman [108280] = 180, -- Healing Tide Totem [98008] = 180, -- Spirit Link Totem }, [270] = { -- Mistweaver Monk [115310] = (180-40), -- Revival, already lowered by Artifact when lvl 75 }, }; local monkRelics = { -- Tendrils of Revival ["151012"] = -10, ["151010"] = -10, ["147112"] = -10, ["152291"] = -10, }; local cds = {}; -- CD durations currently available modified by passive cdfixes -- CD fixes active e.g. cdfixes[guid] = -60, no need for spellid as -- only classes with one CD have fixes local cdfixes = {}; local runningcds = {}; -- CDs currently active local idToGuid = {}; local guidToId = {}; local guidToSpecid = {}; local monksWithLegendary = {}; local inspectSent = {}; local dead = {}; local disconnected = {}; local function CdUp(guid, spellid) local frame = frames[guid][spellid]; if runningcds[guid] then runningcds[guid][spellid] = nil end frame.base:SetVertexColor(unpack(upColor)); frame.text:SetText(frame.name); end local function CdDown(guid, spellid, start) local frame = frames[guid][spellid]; if not runningcds[guid] then runningcds[guid] = {} end runningcds[guid][spellid] = start; frame.base:SetVertexColor(unpack(downColor)); if start then frame.text:SetText(cds[guid][spellid]) end end local function sendInspect(guid, id, func) if not inspectSent[guid] and CanInspect(id) then inspectSent[guid] = true; local _, _, _, _, _, name = GetPlayerInfoByGUID(guid); print(guid, name, "send inspect"); NotifyInspect(id); CTimerAfter(20, func); -- precaution for never receiving INSPECT_READY end end local encounter = nil; local function tick() if not encounter and IsEncounterInProgress() then encounter = true; elseif encounter and not IsEncounterInProgress() then encounter = nil; -- for some reason can't hook to ENCOUNTER_END event so have to track with timer -- TODO support Argus, encounterID 1866 for guid, t in pairs(runningcds) do for spellid, _ in pairs(t) do CdUp(guid, spellid); end end end local current = GetTime(); for guid, t in pairs(runningcds) do for spellid, start in pairs(t) do local remain = cds[guid][spellid] - (current - start); if remain <= 0 then -- CD back up CdUp(guid, spellid); else -- still on CD if remain >= 60 then frames[guid][spellid].text:SetFormattedText("%d:%02d", floor(remain/60), remain%60); else frames[guid][spellid].text:SetText(floor(remain)); end end end end CTimerAfter(0.5, tick); end local function refreshPositions() for i, frame in ipairs(shown) do local prev = shown[i-1] or cdframe; frame:ClearAllPoints(); frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1); end end local function removeFrame(frame) unused[#unused+1] = frame; frames[frame.guid][frame.spellid] = nil; cds[frame.guid][frame.spellid] = nil; if runningcds[frame.guid] then runningcds[frame.guid][frame.spellid] = nil end tremove(shown, frame.pos); for i, frame in ipairs(shown) do frame.pos = i; end frame:ClearAllPoints(); frame:Hide(); refreshPositions(); end local unique = 1; local function getFrame() local frame = tremove(unused); if not frame then frame = CreateFrame("Frame", format("OmaCD%i", unique), cdframe); unique = unique + 1; frame:SetWidth(75); frame:SetHeight(16); frame.base = frame:CreateTexture(nil, "BACKGROUND"); frame.base:SetAllPoints(); frame.base:SetColorTexture(1, 1, 1); frame.icon = frame:CreateTexture(nil, "ARTWORK"); frame.icon:SetPoint("TOPLEFT"); frame.icon:SetPoint("BOTTOMLEFT"); frame.icon:SetWidth(16); frame.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93); frame.text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); frame.text:SetPoint("LEFT", frame, "LEFT", 20, 0); frame.text:SetJustifyH("LEFT"); end return frame; end local function updateCD(guid, specid, spellid) -- update cd value local cd = trackedcds[specid][spellid]; if cdfixes[guid] then cd = cd + cdfixes[guid] end if not cds[guid] then cds[guid] = {} end cds[guid][spellid] = cd; if frames[guid] and frames[guid][spellid] then return end -- no cd frame yet, create one local frame = getFrame(); frame.name = ssub(UnitName(guidToId[guid]), 1, 8); frame.guid = guid; frame.spellid = spellid; frame.base:SetVertexColor(unpack(upColor)); frame.icon:SetTexture(GetSpellTexture(spellid)); frame.text:SetText(frame.name); frame:Show(); if not frames[guid] then frames[guid] = {} end frames[guid][spellid] = frame; frame.pos = #shown+1; local prev = shown[#shown] or cdframe; shown[#shown+1] = frame; frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1); return frame; end local function updateUnitCD(guid) local specid = guidToSpecid[guid]; if specid then for spellid, _ in pairs(trackedcds[specid]) do updateCD(guid, specid, spellid); end end end local function updateMonk(guid) local id = guidToId[guid]; if not id then return end -- called because another addon inspected local weapon = GetInventoryItemLink(id, INVSLOT_MAINHAND); if not weapon then -- try again directly if CanInspect(id) then print(guid, "send inspect");NotifyInspect(id); CTimerAfter(20, updateMonk); end return; end if inspectSent[guid] then inspectSent[guid] = nil end print("Got monk", guid); local _, _, _, relic1, relic2, relic3 = strsplit(":", weapon); local cdfix = 0; if monkRelics[relic1] then cdfix = cdfix + monkRelics[relic1] end if monkRelics[relic2] then cdfix = cdfix + monkRelics[relic2] end if monkRelics[relic3] then cdfix = cdfix + monkRelics[relic3] end if cdfix ~= 0 then cdfixes[guid] = cdfix end local wrist = GetInventoryItemID(guidToId[guid], INVSLOT_WRIST); if wrist == 137096 then -- Petrichor Lagniappe monksWithLegendary[guid] = true; else monksWithLegendary[guid] = nil; end updateUnitCD(guid); end local function updateDruid(guid, player) local id = guidToId[guid]; if id then -- Inner Peace talent local selected; if player then _, _, _, selected = GetTalentInfo(6, 2, 1); else _, _, _, selected = GetTalentInfo(6, 2, 1, true, id); end if selected then cdfixes[guid] = -60; else cdfixes[guid] = nil; end updateUnitCD(guid); end end local function updatePriest(guid) local id = guidToId[guid]; if id then local specid = GetInspectSpecialization(id); if specid == 0 then -- try again directly if CanInspect(id) then print(guid, "send inspect");NotifyInspect(id); CTimerAfter(20, updatePriest); end end if inspectSent[guid] then inspectSent[guid] = nil end print("Got priest", guid); guidToSpecid[guid] = specid; updateUnitCD(guid); end end local function updatePlayer() local specid = GetSpecializationInfo(GetSpecialization() or 1); local guid = UnitGUID("player"); if trackedcds[specid] then guidToId[guid] = "player"; guidToSpecid[guid] = specid; if specid == 270 then -- Monk updateMonk(guid); elseif specid == 105 then -- Druid updateDruid(guid, true); else updateUnitCD(guid); end idToGuid["player"] = guid; else idToGuid["player"] = nil; for _, frame in pairs(frames[guid]) do removeFrame(frame); end end end local function updateUnitid(id) local guid = UnitGUID(id); if UnitIsUnit(id, "player") then -- player is special case return updatePlayer(); elseif UnitExists(id) and UnitGroupRolesAssigned(id) == "HEALER" then local _, class = UnitClass(id); if specs[class] ~= nil then guidToId[guid] = id; if specs[class] == false then -- Priest, have to inspect to get spec, try immediately, if not available, inspect local specid = GetInspectSpecialization(id); if specid == 0 then sendInspect(guid, id, updatePriest); elseif specid == 256 or specid == 257 then guidToSpecid[guid] = specid; -- only Holy and Discipline updateUnitCD(guid); end elseif specs[class] == 270 then -- Monk, have to check inventory for CD modifications guidToSpecid[guid] = specs[class]; -- updateUnitCD (without having fixes yet) updateUnitCD(guid); sendInspect(guid, id, updateMonk); elseif specs[class] == 105 then -- Druid, have to inspect to get talents guidToSpecid[guid] = specs[class]; -- updateUnitCD (without having fixes yet) updateUnitCD(guid); sendInspect(guid, id, updateDruid); else guidToSpecid[guid] = specs[class]; updateUnitCD(guid); end idToGuid[id] = guid; end else idToGuid[id] = nil; if frames[guid] then for _, frame in pairs(frames[guid]) do removeFrame(frame); end end end end local updateQueued = nil; local function updateUnitids() local size = 0; local prefix; local newIdToGuid = {}; local prevIdToGuid = {}; guidToId = {}; updateQueued = nil; for id, guid in pairs(idToGuid) do prevIdToGuid[id] = guid; end if IsInGroup() then if IsInRaid() then size = 40; prefix = "raid"; else size = 4; prefix = "party"; end end updatePlayer(); if prevIdToGuid["player"] then prevIdToGuid["player"] = nil end for i = 1,size do local id = format("%s%i", prefix, i); if not UnitIsUnit(id, "player") then updateUnitid(id); end if prevIdToGuid[id] then prevIdToGuid[id] = nil end end -- clean up players leaving for id, guid in pairs(prevIdToGuid) do if frames[guid] then for _, frame in pairs(frames[guid]) do removeFrame(frame); end end end end local events = { ["UNIT_HEALTH"] = function(id) local guid = idToGuid[id]; if guid and cds[guid] and UnitIsDeadOrGhost(id) then dead[guid] = true; for spellid, _ in pairs(cds[guid]) do CdDown(guid, spellid, nil); end elseif guid and dead[guid] and cds[guid] then dead[guid] = nil; for spellid, _ in pairs(cds[guid]) do CdUp(guid, spellid); end end end, ["UNIT_CONNECTION"] = function(id) local guid = idToGuid[id]; if guid and cds[guid] and not UnitIsConnected(id) then disconnected[guid] = true; for spellid, _ in pairs(cds[guid]) do CdDown(guid, spellid, nil); end elseif guid and disconnected[guid] and cds[guid] then disconnected[guid] = nil; for spellid, _ in pairs(cds[guid]) do CdUp(guid, spellid); end end end, ["UNIT_SPELLCAST_SUCCEEDED"] = function(id, _, _, _, spellid) local guid = idToGuid[id]; if guid and frames[guid] then local frame = frames[guid][spellid]; if frame then CdDown(guid, spellid, GetTime()); elseif monksWithLegendary[guid] and spellid == 115151 then -- Renewing Mist with legendary affects Revival if runningcds[guid] and runningcds[guid][115310] then runningcds[guid][115310] = runningcds[guid][115310] - 2; end end end end, ["PLAYER_SPECIALIZATION_CHANGED"] = function(id) updateUnitid(id); end, ["UNIT_INVENTORY_CHANGED"] = function(id) local guid = idToGuid[id]; if guid and guidToSpecid[guid] == 270 then print("OmaCD Inventory: Monk inventory update"); updateMonk(guid); end end, ["GROUP_ROSTER_UPDATE"] = function() if not updateQueued then updateQueued = true CTimerAfter(2, updateUnitids); end end, ["INSPECT_READY"] = function(guid) local specid = guidToSpecid[guid]; if inspectSent[guid] then inspectSent[guid] = nil end if specid == 105 then -- Druid print("OmaCD Inspect: druid,", guid); updateDruid(guid); ClearInspectPlayer(); elseif specid == 270 then -- Monk print("OmaCD Inspect: monk,", guid); -- don't clear inspect player, have to track inventory changes elseif specid == false or specid == 256 or specid == 257 then -- Priest print("OmaCD Inspect: priest,", guid); updatePriest(guid); ClearInspectPlayer(); end end, }; events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"]; local function cdtracker() cdframe:SetFrameStrata("LOW"); cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 200, -200); cdframe:SetWidth(1); cdframe:SetHeight(1); cdframe:UnregisterAllEvents(); cdframe:SetScript("OnEvent", function(self, event, ...) events[event](...); end); cdframe:RegisterEvent("UNIT_HEALTH"); cdframe:RegisterEvent("UNIT_CONNECTION"); cdframe:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED"); cdframe:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED"); cdframe:RegisterEvent("UNIT_INVENTORY_CHANGED"); cdframe:RegisterEvent("GROUP_ROSTER_UPDATE"); cdframe:RegisterEvent("INSPECT_READY"); cdframe:RegisterEvent("PLAYER_ROLES_ASSIGNED"); -- initial update events["GROUP_ROSTER_UPDATE"](); CTimerAfter(0.5, tick); end cdframe:RegisterEvent("PLAYER_LOGIN"); cdframe:SetScript("OnEvent", function(self, event) if event == "PLAYER_LOGIN" then return cdtracker(); end end);