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 CanInspect, NotifyInspect, ClearInspectPlayer = CanInspect, NotifyInspect, ClearInspectPlayer;
17 local INVSLOT_MAINHAND, INVSLOT_WRIST = INVSLOT_MAINHAND, INVSLOT_WRIST;
19 local upColor = {0, 0.7, 0.2, 0.7};
20 local downColor = {1, 0, 0, 0.5};
22 local cdframe = CreateFrame("Frame", "OmaCD", UIParent);
25 local shown = {}; -- simple array of frames in shown order
29 ["PRIEST"] = false, -- priest has 2 healing specs
35 [65] = { -- Holy Paladin
36 [31821] = 180, -- Aura Mastery
38 [105] = { -- Resto Druid
39 [740] = 180, -- Tranquility
41 [256] = { -- Discipline Priest
42 [62618] = 180, -- Power Word: Barrier
44 [257] = { -- Holy Priest
45 [64843] = 180, -- Divine Hymn
47 [264] = { -- Resto Shaman
48 [108280] = 180, -- Healing Tide Totem
49 [98008] = 180, -- Spirit Link Totem
51 [270] = { -- Mistweaver Monk
52 [115310] = (180-40), -- Revival, already lowered by Artifact when lvl 75
55 local monkRelics = { -- Tendrils of Revival
62 local cds = {}; -- CD durations currently available modified by passive cdfixes
63 -- CD fixes active e.g. cdfixes[guid] = -60, no need for spellid as
64 -- only classes with one CD have fixes
66 local runningcds = {}; -- CDs currently active
70 local guidToSpecid = {};
71 local monksWithLegendary = {};
72 local inspectSent = {};
74 local disconnected = {};
76 local function CdUp(guid, spellid)
77 local frame = frames[guid][spellid];
78 if runningcds[guid] then runningcds[guid][spellid] = nil end
79 frame.base:SetVertexColor(unpack(upColor));
80 frame.text:SetText(frame.name);
83 local function CdDown(guid, spellid, start)
84 local frame = frames[guid][spellid];
85 if not runningcds[guid] then runningcds[guid] = {} end
86 runningcds[guid][spellid] = start;
87 frame.base:SetVertexColor(unpack(downColor));
88 if start then frame.text:SetText(cds[guid][spellid]) end
91 local function sendInspect(guid, id, func)
92 if not inspectSent[guid] and CanInspect(id) then
93 inspectSent[guid] = true;
94 local _, _, _, _, _, name = GetPlayerInfoByGUID(guid);
95 print(guid, name, "send inspect");
97 CTimerAfter(20, func); -- precaution for never receiving INSPECT_READY
101 local encounter = nil;
102 local function tick()
103 if not encounter and IsEncounterInProgress() then
105 elseif encounter and not IsEncounterInProgress() then
107 -- for some reason can't hook to ENCOUNTER_END event so have to track with timer
108 -- TODO support Argus, encounterID 1866
109 for guid, t in pairs(runningcds) do
110 for spellid, _ in pairs(t) do
115 local current = GetTime();
116 for guid, t in pairs(runningcds) do
117 for spellid, start in pairs(t) do
118 local remain = cds[guid][spellid] - (current - start);
119 if remain <= 0 then -- CD back up
123 frames[guid][spellid].text:SetFormattedText("%d:%02d", floor(remain/60), remain%60);
125 frames[guid][spellid].text:SetText(floor(remain));
130 CTimerAfter(0.5, tick);
133 local function refreshPositions()
134 for i, frame in ipairs(shown) do
135 local prev = shown[i-1] or cdframe;
136 frame:ClearAllPoints();
137 frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
141 local function removeFrame(frame)
142 unused[#unused+1] = frame;
143 frames[frame.guid][frame.spellid] = nil;
144 cds[frame.guid][frame.spellid] = nil;
145 if runningcds[frame.guid] then runningcds[frame.guid][frame.spellid] = nil end
146 tremove(shown, frame.pos);
147 for i, frame in ipairs(shown) do
150 frame:ClearAllPoints();
156 local function getFrame()
157 local frame = tremove(unused);
159 frame = CreateFrame("Frame", format("OmaCD%i", unique), cdframe);
163 frame.base = frame:CreateTexture(nil, "BACKGROUND");
164 frame.base:SetAllPoints();
165 frame.base:SetColorTexture(1, 1, 1);
166 frame.icon = frame:CreateTexture(nil, "ARTWORK");
167 frame.icon:SetPoint("TOPLEFT");
168 frame.icon:SetPoint("BOTTOMLEFT");
169 frame.icon:SetWidth(16);
170 frame.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93);
171 frame.text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
172 frame.text:SetPoint("LEFT", frame, "LEFT", 20, 0);
173 frame.text:SetJustifyH("LEFT");
177 local function updateCD(guid, specid, spellid)
179 local cd = trackedcds[specid][spellid];
180 if cdfixes[guid] then cd = cd + cdfixes[guid] end
181 if not cds[guid] then cds[guid] = {} end
182 cds[guid][spellid] = cd;
183 if frames[guid] and frames[guid][spellid] then return end
185 -- no cd frame yet, create one
186 local frame = getFrame();
187 frame.name = ssub(UnitName(guidToId[guid]), 1, 8);
189 frame.spellid = spellid;
190 frame.base:SetVertexColor(unpack(upColor));
191 frame.icon:SetTexture(GetSpellTexture(spellid));
192 frame.text:SetText(frame.name);
194 if not frames[guid] then frames[guid] = {} end
195 frames[guid][spellid] = frame;
196 frame.pos = #shown+1;
197 local prev = shown[#shown] or cdframe;
198 shown[#shown+1] = frame;
199 frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
203 local function updateUnitCD(guid)
204 local specid = guidToSpecid[guid];
206 for spellid, _ in pairs(trackedcds[specid]) do
207 updateCD(guid, specid, spellid);
212 local function updateMonk(guid)
213 local id = guidToId[guid];
214 if not id then return end -- called because another addon inspected
215 local weapon = GetInventoryItemLink(id, INVSLOT_MAINHAND);
217 -- try again directly
218 if CanInspect(id) then
219 print(guid, "send inspect");NotifyInspect(id);
220 CTimerAfter(20, updateMonk);
224 if inspectSent[guid] then inspectSent[guid] = nil end
225 print("Got monk", guid);
226 local _, _, _, relic1, relic2, relic3 = strsplit(":", weapon);
228 if monkRelics[relic1] then cdfix = cdfix + monkRelics[relic1] end
229 if monkRelics[relic2] then cdfix = cdfix + monkRelics[relic2] end
230 if monkRelics[relic3] then cdfix = cdfix + monkRelics[relic3] end
231 if cdfix ~= 0 then cdfixes[guid] = cdfix end
233 local wrist = GetInventoryItemID(guidToId[guid], INVSLOT_WRIST);
234 if wrist == 137096 then -- Petrichor Lagniappe
235 monksWithLegendary[guid] = true;
237 monksWithLegendary[guid] = nil;
242 local function updateDruid(guid, player)
243 local id = guidToId[guid];
245 -- Inner Peace talent
248 _, _, _, selected = GetTalentInfo(6, 2, 1);
250 _, _, _, selected = GetTalentInfo(6, 2, 1, true, id);
261 local function updatePriest(guid)
262 local id = guidToId[guid];
264 local specid = GetInspectSpecialization(id);
266 -- try again directly
267 if CanInspect(id) then
268 print(guid, "send inspect");NotifyInspect(id);
269 CTimerAfter(20, updatePriest);
272 if inspectSent[guid] then inspectSent[guid] = nil end
273 print("Got priest", guid);
274 guidToSpecid[guid] = specid;
279 local function updatePlayer()
280 local specid = GetSpecializationInfo(GetSpecialization() or 1);
281 local guid = UnitGUID("player");
282 if trackedcds[specid] then
283 guidToId[guid] = "player";
284 guidToSpecid[guid] = specid;
285 if specid == 270 then -- Monk
287 elseif specid == 105 then -- Druid
288 updateDruid(guid, true);
292 idToGuid["player"] = guid;
294 idToGuid["player"] = nil;
295 for _, frame in pairs(frames[guid]) do
301 local function updateUnitid(id)
302 local guid = UnitGUID(id);
303 if UnitIsUnit(id, "player") then -- player is special case
304 return updatePlayer();
305 elseif UnitExists(id) and UnitGroupRolesAssigned(id) == "HEALER" then
306 local _, class = UnitClass(id);
307 if specs[class] ~= nil then
309 if specs[class] == false then
310 -- Priest, have to inspect to get spec, try immediately, if not available, inspect
311 local specid = GetInspectSpecialization(id);
313 sendInspect(guid, id, updatePriest);
314 elseif specid == 256 or specid == 257 then
315 guidToSpecid[guid] = specid; -- only Holy and Discipline
318 elseif specs[class] == 270 then
319 -- Monk, have to check inventory for CD modifications
320 guidToSpecid[guid] = specs[class];
321 -- updateUnitCD (without having fixes yet)
323 sendInspect(guid, id, updateMonk);
324 elseif specs[class] == 105 then
325 -- Druid, have to inspect to get talents
326 guidToSpecid[guid] = specs[class];
327 -- updateUnitCD (without having fixes yet)
329 sendInspect(guid, id, updateDruid);
331 guidToSpecid[guid] = specs[class];
339 for _, frame in pairs(frames[guid]) do
346 local updateQueued = nil;
347 local function updateUnitids()
350 local newIdToGuid = {};
351 local prevIdToGuid = {};
355 for id, guid in pairs(idToGuid) do
356 prevIdToGuid[id] = guid;
370 if prevIdToGuid["player"] then prevIdToGuid["player"] = nil end
372 local id = format("%s%i", prefix, i);
373 if not UnitIsUnit(id, "player") then
376 if prevIdToGuid[id] then prevIdToGuid[id] = nil end
379 -- clean up players leaving
380 for id, guid in pairs(prevIdToGuid) do
382 for _, frame in pairs(frames[guid]) do
390 ["UNIT_HEALTH"] = function(id)
391 local guid = idToGuid[id];
392 if guid and cds[guid] and UnitIsDeadOrGhost(id) then
394 for spellid, _ in pairs(cds[guid]) do
395 CdDown(guid, spellid, nil);
397 elseif guid and dead[guid] and cds[guid] then
399 for spellid, _ in pairs(cds[guid]) do
404 ["UNIT_CONNECTION"] = function(id)
405 local guid = idToGuid[id];
406 if guid and cds[guid] and not UnitIsConnected(id) then
407 disconnected[guid] = true;
408 for spellid, _ in pairs(cds[guid]) do
409 CdDown(guid, spellid, nil);
411 elseif guid and disconnected[guid] and cds[guid] then
412 disconnected[guid] = nil;
413 for spellid, _ in pairs(cds[guid]) do
418 ["UNIT_SPELLCAST_SUCCEEDED"] = function(id, _, _, _, spellid)
419 local guid = idToGuid[id];
420 if guid and frames[guid] then
421 local frame = frames[guid][spellid];
423 CdDown(guid, spellid, GetTime());
424 elseif monksWithLegendary[guid] and spellid == 115151 then
425 -- Renewing Mist with legendary affects Revival
426 if runningcds[guid] and runningcds[guid][115310] then
427 runningcds[guid][115310] = runningcds[guid][115310] - 2;
432 ["PLAYER_SPECIALIZATION_CHANGED"] = function(id)
435 ["UNIT_INVENTORY_CHANGED"] = function(id)
436 local guid = idToGuid[id];
437 if guid and guidToSpecid[guid] == 270 then
438 print("OmaCD Inventory: Monk inventory update");
442 ["GROUP_ROSTER_UPDATE"] = function()
443 if not updateQueued then
445 CTimerAfter(2, updateUnitids);
448 ["INSPECT_READY"] = function(guid)
449 local specid = guidToSpecid[guid];
450 if inspectSent[guid] then inspectSent[guid] = nil end
451 if specid == 105 then -- Druid
452 print("OmaCD Inspect: druid,", guid);
454 ClearInspectPlayer();
455 elseif specid == 270 then -- Monk
456 print("OmaCD Inspect: monk,", guid);
457 -- don't clear inspect player, have to track inventory changes
458 elseif specid == false or specid == 256 or specid == 257 then -- Priest
459 print("OmaCD Inspect: priest,", guid);
461 ClearInspectPlayer();
465 events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"];
467 local function cdtracker()
468 cdframe:SetFrameStrata("LOW");
469 cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 200, -200);
471 cdframe:SetHeight(1);
473 cdframe:UnregisterAllEvents();
474 cdframe:SetScript("OnEvent", function(self, event, ...)
477 cdframe:RegisterEvent("UNIT_HEALTH");
478 cdframe:RegisterEvent("UNIT_CONNECTION");
479 cdframe:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED");
480 cdframe:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
481 cdframe:RegisterEvent("UNIT_INVENTORY_CHANGED");
482 cdframe:RegisterEvent("GROUP_ROSTER_UPDATE");
483 cdframe:RegisterEvent("INSPECT_READY");
484 cdframe:RegisterEvent("PLAYER_ROLES_ASSIGNED");
486 events["GROUP_ROSTER_UPDATE"]();
487 CTimerAfter(0.5, tick);
490 cdframe:RegisterEvent("PLAYER_LOGIN");
491 cdframe:SetScript("OnEvent", function(self, event)
492 if event == "PLAYER_LOGIN" then