-- 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 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, -- Revival }, }; 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 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 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; if not next(frames[frame.guid]) then frames[frame.guid] = nil end cds[frame.guid][frame.spellid] = nil; dead[frame.guid] = nil; disconnected[frame.guid] = 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) if not guidToId[guid] then return end -- 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 and trackedcds[specid] then for spellid, _ in pairs(trackedcds[specid]) do updateCD(guid, specid, spellid); end end end local function updateDruid(guid, _, talent) local id = guidToId[guid]; if id then if talent then cdfixes[guid] = -60; else cdfixes[guid] = nil; end updateUnitCD(guid); end end local function updatePriest(guid, specid) local id = guidToId[guid]; if id then if guidToSpecid[guid] ~= specid and frames[guid] then for _, frame in pairs(frames[guid]) do removeFrame(frame); end end 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 == 105 then -- Druid local _, _, _, talent = GetTalentInfo(6, 1, 1); updateDruid(guid, nil, talent); else updateUnitCD(guid); end idToGuid["player"] = guid; else idToGuid["player"] = nil; if frames[guid] then for _, frame in pairs(frames[guid]) do removeFrame(frame); end 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; local specid = guidToSpecid[guid]; guidToSpecid[guid] = specs[class]; if specs[class] == false then if specid == 256 or specid == 257 then guidToSpecid[guid] = specid; else guidToSpecid[guid] = 257; -- assume Holy for now to get something visible end updateUnitCD(guid); --OmaInspect.Request(guid, id, updatePriest); elseif specs[class] == 105 then -- Druid, have to inspect to get talents -- updateUnitCD (without having fixes yet) updateUnitCD(guid); OmaInspect.Request(guid, id, updateDruid); else 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 prevGroupType = "solo"; local prevGroupSize = 0; local updateQueued = nil; local function updateUnitids() local size = 0; local prefix = "solo"; guidToId = {}; updateQueued = nil; if IsInGroup() then if IsInRaid() then size = 40; prefix = "raid"; else size = 4; prefix = "party"; end end if prevGroupType ~= prefix then -- clean up players from old group for i = 1,prevGroupSize do local id = format("%s%i", prevGroupType, i); local guid = idToGuid[id]; idToGuid[id] = nil; if frames[guid] then print(guid, "removing frames in overall updateUnitids"); for _, frame in pairs(frames[guid]) do removeFrame(frame); end end end prevGroupType = prefix; prevGroupSize = size; end updatePlayer(); for i = 1,size do local id = format("%s%i", prefix, i); if not UnitIsUnit(id, "player") then -- player is already done updateUnitid(id); end end end -- just check removals, don't cause inspects local function fastUpdateUnitids() for guid, unitFrames in pairs(frames) do if UnitGUID(guidToId[guid]) ~= guid then for _, frame in pairs(unitFrames) 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 guidToSpecid[guid] == 257 and spellid == 62618 then -- assumed Holy priest cast Barrier, set to update to Disc guidToSpecid[guid] = 256; for _, frame in pairs(frames[guid]) do removeFrame(frame); end updateUnitCD(guid); CdDown(guid, spellid, GetTime()); end end end, ["PLAYER_SPECIALIZATION_CHANGED"] = function(id) updateUnitid(id); end, ["GROUP_ROSTER_UPDATE"] = function() fastUpdateUnitids(); if not updateQueued then updateQueued = true CTimerAfter(4, updateUnitids); end end, }; events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"]; events["PLAYER_ENTERING_WORLD"] = events["GROUP_ROSTER_UPDATE"]; local function cdtracker() cdframe:SetFrameStrata("LOW"); cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 580, -400); 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("GROUP_ROSTER_UPDATE"); cdframe:RegisterEvent("PLAYER_ROLES_ASSIGNED"); cdframe:RegisterEvent("PLAYER_ENTERING_WORLD"); -- initial tick CTimerAfter(0.5, tick); end cdframe:SetScript("OnEvent", cdtracker); cdframe:RegisterEvent("PLAYER_LOGIN");