3 local pairs, ipairs, unpack = pairs, ipairs, unpack;
4 local tremove, format, ssub = table.remove, string.format, string.sub;
5 local floor, strsplit = math.floor, strsplit;
6 local GetTime, UnitGUID, UnitClass = GetTime, UnitGUID, UnitClass;
7 local UnitName, GetSpellTexture = UnitName, GetSpellTexture;
8 local IsInGroup, IsInRaid = IsInGroup, IsInRaid;
9 local CreateFrame, CTimerAfter = CreateFrame, C_Timer.After;
10 local UnitIsUnit, IsEncounterInProgress = UnitIsUnit, IsEncounterInProgress;
11 local UnitExists, UnitGroupRolesAssigned = UnitExists, UnitGroupRolesAssigned;
12 local UnitIsDeadOrGhost, UnitIsConnected = UnitIsDeadOrGhost, UnitIsConnected;
13 local GetTalentInfo, GetInspectSpecialization = GetTalentInfo, GetInspectSpecialization;
14 local GetSpecializationInfo, GetSpecialization = GetSpecializationInfo, GetSpecialization;
15 local GetInventoryItemLink, GetInventoryItemID = GetInventoryItemLink, GetInventoryItemID;
16 local INVSLOT_MAINHAND, INVSLOT_WRIST = INVSLOT_MAINHAND, INVSLOT_WRIST;
18 local upColor = {0, 0.7, 0.2, 0.7};
19 local downColor = {1, 0, 0, 0.5};
21 local cdframe = CreateFrame("Frame", "OmaCD", UIParent);
24 local shown = {}; -- simple array of frames in shown order
28 ["PRIEST"] = false, -- priest has 2 healing specs
34 [65] = { -- Holy Paladin
35 [31821] = 180, -- Aura Mastery
37 [105] = { -- Resto Druid
38 [740] = 180, -- Tranquility
40 [256] = { -- Discipline Priest
41 [62618] = 180, -- Power Word: Barrier
43 [257] = { -- Holy Priest
44 [64843] = 180, -- Divine Hymn
46 [264] = { -- Resto Shaman
47 [108280] = 180, -- Healing Tide Totem
48 [98008] = 180, -- Spirit Link Totem
50 [270] = { -- Mistweaver Monk
51 [115310] = (180-40), -- Revival, already lowered by Artifact when lvl 75
54 local monkRelics = { -- Tendrils of Revival
61 local cds = {}; -- CD durations currently available modified by passive cdfixes
62 -- CD fixes active e.g. cdfixes[guid] = -60, no need for spellid as
63 -- only classes with one CD have fixes
65 local runningcds = {}; -- CDs currently active
69 local guidToSpecid = {};
71 local monksWithLegendary = {};
73 local disconnected = {};
75 local function CdUp(guid, spellid)
76 local frame = frames[guid][spellid];
77 if runningcds[guid] then runningcds[guid][spellid] = nil end
78 frame.base:SetVertexColor(unpack(upColor));
79 frame.text:SetText(frame.name);
82 local function CdDown(guid, spellid, start)
83 local frame = frames[guid][spellid];
84 if not runningcds[guid] then runningcds[guid] = {} end
85 runningcds[guid][spellid] = start;
86 frame.base:SetVertexColor(unpack(downColor));
87 if start then frame.text:SetText(cds[guid][spellid]) end
90 local encounter = nil;
92 if not encounter and IsEncounterInProgress() then
94 elseif encounter and not IsEncounterInProgress() then
96 -- for some reason can't hook to ENCOUNTER_END event so have to track with timer
97 -- TODO support Argus, encounterID 1866
98 for guid, t in pairs(runningcds) do
99 for spellid, _ in pairs(t) do
104 local current = GetTime();
105 for guid, t in pairs(runningcds) do
106 for spellid, start in pairs(t) do
107 local remain = cds[guid][spellid] - (current - start);
108 if remain <= 0 then -- CD back up
112 frames[guid][spellid].text:SetFormattedText("%d:%02d", floor(remain/60), remain%60);
114 frames[guid][spellid].text:SetText(floor(remain));
119 CTimerAfter(0.5, tick);
122 local function refreshPositions()
123 for i, frame in ipairs(shown) do
124 local prev = shown[i-1] or cdframe;
125 frame:ClearAllPoints();
126 frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
130 local function removeFrame(frame)
131 unused[#unused+1] = frame;
132 frames[frame.guid][frame.spellid] = nil;
133 if not next(frames[frame.guid]) then frames[frame.guid] = nil end
134 cds[frame.guid][frame.spellid] = nil;
135 monks[frame.guid] = nil;
136 monksWithLegendary[frame.guid] = nil;
137 dead[frame.guid] = nil;
138 disconnected[frame.guid] = nil;
139 if runningcds[frame.guid] then runningcds[frame.guid][frame.spellid] = nil end
140 tremove(shown, frame.pos);
141 for i, frame in ipairs(shown) do
144 frame:ClearAllPoints();
150 local function getFrame()
151 local frame = tremove(unused);
153 frame = CreateFrame("Frame", format("OmaCD%i", unique), cdframe);
157 frame.base = frame:CreateTexture(nil, "BACKGROUND");
158 frame.base:SetAllPoints();
159 frame.base:SetColorTexture(1, 1, 1);
160 frame.icon = frame:CreateTexture(nil, "ARTWORK");
161 frame.icon:SetPoint("TOPLEFT");
162 frame.icon:SetPoint("BOTTOMLEFT");
163 frame.icon:SetWidth(16);
164 frame.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93);
165 frame.text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
166 frame.text:SetPoint("LEFT", frame, "LEFT", 20, 0);
167 frame.text:SetJustifyH("LEFT");
171 local function updateCD(guid, specid, spellid)
172 if not guidToId[guid] then return end
174 local cd = trackedcds[specid][spellid];
175 if cdfixes[guid] then cd = cd + cdfixes[guid] end
176 if not cds[guid] then cds[guid] = {} end
177 cds[guid][spellid] = cd;
178 if frames[guid] and frames[guid][spellid] then return end
180 -- no cd frame yet, create one
181 local frame = getFrame();
182 frame.name = ssub(UnitName(guidToId[guid]), 1, 8);
184 frame.spellid = spellid;
185 frame.base:SetVertexColor(unpack(upColor));
186 frame.icon:SetTexture(GetSpellTexture(spellid));
187 frame.text:SetText(frame.name);
189 if not frames[guid] then frames[guid] = {} end
190 frames[guid][spellid] = frame;
191 frame.pos = #shown+1;
192 local prev = shown[#shown] or cdframe;
193 shown[#shown+1] = frame;
194 frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
198 local function updateUnitCD(guid)
199 local specid = guidToSpecid[guid];
200 if specid and trackedcds[specid] then
201 for spellid, _ in pairs(trackedcds[specid]) do
202 updateCD(guid, specid, spellid);
207 local function updateMonk(guid, _, _, weapon, wrist)
208 local id = guidToId[guid];
210 local _, _, _, relic1, relic2, relic3 = strsplit(":", weapon);
212 if monkRelics[relic1] then cdfix = cdfix + monkRelics[relic1] end
213 if monkRelics[relic2] then cdfix = cdfix + monkRelics[relic2] end
214 if monkRelics[relic3] then cdfix = cdfix + monkRelics[relic3] end
216 cdfixes[guid] = cdfix;
221 if wrist == 137096 then -- Petrichor Lagniappe
222 monksWithLegendary[guid] = true;
224 monksWithLegendary[guid] = nil;
231 -- a secondary tick for monks inventory check out of combat
232 local function monkTick()
233 if not InCombatLockdown() then
234 for guid, _ in pairs(monks) do
235 local id = guidToId[guid];
236 if id then OmaInspect.Request(guid, id, updateMonk, true) end
239 CTimerAfter(10, monkTick);
242 local function updateDruid(guid, _, talent)
243 local id = guidToId[guid];
254 local function updatePriest(guid, specid)
255 local id = guidToId[guid];
257 if guidToSpecid[guid] ~= specid and frames[guid] then
258 for _, frame in pairs(frames[guid]) do
262 guidToSpecid[guid] = specid;
267 local function updatePlayer()
268 local specid = GetSpecializationInfo(GetSpecialization() or 1);
269 local guid = UnitGUID("player");
270 if trackedcds[specid] then
271 guidToId[guid] = "player";
272 guidToSpecid[guid] = specid;
273 if specid == 270 then -- Monk
274 local weapon = GetInventoryItemLink("player", INVSLOT_MAINHAND);
275 local wrist = GetInventoryItemID("player", INVSLOT_WRIST);
276 updateMonk(guid, nil, nil, weapon, wrist);
277 elseif specid == 105 then -- Druid
278 local _, _, _, talent = GetTalentInfo(6, 2, 1);
279 updateDruid(guid, nil, talent);
283 idToGuid["player"] = guid;
285 idToGuid["player"] = nil;
287 for _, frame in pairs(frames[guid]) do
294 local function updateUnitid(id)
295 local guid = UnitGUID(id);
296 if UnitIsUnit(id, "player") then -- player is special case
297 return updatePlayer();
298 elseif UnitExists(id) and UnitGroupRolesAssigned(id) == "HEALER" then
299 local _, class = UnitClass(id);
300 if specs[class] ~= nil then
302 local specid = guidToSpecid[guid];
303 guidToSpecid[guid] = specs[class];
304 if specs[class] == false then
305 if specid == 256 or specid == 257 then
306 guidToSpecid[guid] = specid;
308 guidToSpecid[guid] = 257; -- assume Holy for now to get something visible
311 OmaInspect.Request(guid, id, updatePriest);
312 elseif specs[class] == 270 then
313 -- Monk, have to check inventory for CD modifications
314 -- updateUnitCD (without having fixes yet)
316 OmaInspect.Request(guid, id, updateMonk, true);
317 elseif specs[class] == 105 then
318 -- Druid, have to inspect to get talents
319 -- updateUnitCD (without having fixes yet)
321 OmaInspect.Request(guid, id, updateDruid);
330 for _, frame in pairs(frames[guid]) do
337 local prevGroupType = "solo";
338 local prevGroupSize = 0;
339 local updateQueued = nil;
340 local function updateUnitids()
342 local prefix = "solo";
356 if prevGroupType ~= prefix then
357 -- clean up players from old group
358 for i = 1,prevGroupSize do
359 local id = format("%s%i", prevGroupType, i);
360 local guid = idToGuid[id];
363 print(guid, "removing frames in overall updateUnitids");
364 for _, frame in pairs(frames[guid]) do
369 prevGroupType = prefix;
370 prevGroupSize = size;
375 local id = format("%s%i", prefix, i);
376 if not UnitIsUnit(id, "player") then -- player is already done
382 -- just check removals, don't cause inspects
383 local function fastUpdateUnitids()
384 for guid, unitFrames in pairs(frames) do
385 if UnitGUID(guidToId[guid]) ~= guid then
386 for _, frame in pairs(unitFrames) do
394 ["UNIT_HEALTH"] = function(id)
395 local guid = idToGuid[id];
396 if guid and cds[guid] and UnitIsDeadOrGhost(id) then
398 for spellid, _ in pairs(cds[guid]) do
399 CdDown(guid, spellid, nil);
401 elseif guid and dead[guid] and cds[guid] then
403 for spellid, _ in pairs(cds[guid]) do
408 ["UNIT_CONNECTION"] = function(id)
409 local guid = idToGuid[id];
410 if guid and cds[guid] and not UnitIsConnected(id) then
411 disconnected[guid] = true;
412 for spellid, _ in pairs(cds[guid]) do
413 CdDown(guid, spellid, nil);
415 elseif guid and disconnected[guid] and cds[guid] then
416 disconnected[guid] = nil;
417 for spellid, _ in pairs(cds[guid]) do
422 ["UNIT_SPELLCAST_SUCCEEDED"] = function(id, _, _, _, spellid)
423 local guid = idToGuid[id];
424 if guid and frames[guid] then
425 local frame = frames[guid][spellid];
427 CdDown(guid, spellid, GetTime());
428 elseif monksWithLegendary[guid] and spellid == 115151 then
429 -- Renewing Mist with legendary affects Revival
430 if runningcds[guid] and runningcds[guid][115310] then
431 runningcds[guid][115310] = runningcds[guid][115310] - 2;
433 elseif guidToSpecid[guid] == 257 and spellid == 62618 then
434 -- assumed Holy priest cast Barrier, set to update to Disc
435 guidToSpecid[guid] = 256;
436 for _, frame in pairs(frames[guid]) do
440 CdDown(guid, spellid, GetTime());
444 ["PLAYER_SPECIALIZATION_CHANGED"] = function(id)
447 ["GROUP_ROSTER_UPDATE"] = function()
449 if not updateQueued then
451 CTimerAfter(4, updateUnitids);
455 events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"];
456 events["PLAYER_ENTERING_WORLD"] = events["GROUP_ROSTER_UPDATE"];
458 local function cdtracker()
459 cdframe:SetFrameStrata("LOW");
460 cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 580, -400);
462 cdframe:SetHeight(1);
464 cdframe:UnregisterAllEvents();
465 cdframe:SetScript("OnEvent", function(self, event, ...)
468 cdframe:RegisterEvent("UNIT_HEALTH");
469 cdframe:RegisterEvent("UNIT_CONNECTION");
470 cdframe:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED");
471 cdframe:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
472 cdframe:RegisterEvent("GROUP_ROSTER_UPDATE");
473 cdframe:RegisterEvent("PLAYER_ROLES_ASSIGNED");
474 cdframe:RegisterEvent("PLAYER_ENTERING_WORLD");
476 CTimerAfter(0.5, tick);
477 CTimerAfter(10, monkTick);
480 cdframe:RegisterEvent("PLAYER_LOGIN");
481 cdframe:SetScript("OnEvent", function(self, event)
482 if event == "PLAYER_LOGIN" then