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)
173 local cd = trackedcds[specid][spellid];
174 if cdfixes[guid] then cd = cd + cdfixes[guid] end
175 if not cds[guid] then cds[guid] = {} end
176 cds[guid][spellid] = cd;
177 if frames[guid] and frames[guid][spellid] then return end
179 -- no cd frame yet, create one
180 local frame = getFrame();
181 frame.name = ssub(UnitName(guidToId[guid]), 1, 8);
183 frame.spellid = spellid;
184 frame.base:SetVertexColor(unpack(upColor));
185 frame.icon:SetTexture(GetSpellTexture(spellid));
186 frame.text:SetText(frame.name);
188 if not frames[guid] then frames[guid] = {} end
189 frames[guid][spellid] = frame;
190 frame.pos = #shown+1;
191 local prev = shown[#shown] or cdframe;
192 shown[#shown+1] = frame;
193 frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
197 local function updateUnitCD(guid)
198 local specid = guidToSpecid[guid];
200 for spellid, _ in pairs(trackedcds[specid]) do
201 updateCD(guid, specid, spellid);
206 local function updateMonk(guid, _, _, weapon, wrist)
207 local id = guidToId[guid];
209 local _, _, _, relic1, relic2, relic3 = strsplit(":", weapon);
211 if monkRelics[relic1] then cdfix = cdfix + monkRelics[relic1] end
212 if monkRelics[relic2] then cdfix = cdfix + monkRelics[relic2] end
213 if monkRelics[relic3] then cdfix = cdfix + monkRelics[relic3] end
215 cdfixes[guid] = cdfix;
220 if wrist == 137096 then -- Petrichor Lagniappe
221 monksWithLegendary[guid] = true;
223 monksWithLegendary[guid] = nil;
230 -- a secondary tick for monks inventory check out of combat
231 local function monkTick()
232 if not InCombatLockdown() then
233 for guid, _ in pairs(monks) do
234 local id = guidToId[guid];
235 if id then OmaInspect.Request(guid, id, updateMonk) end
238 CTimerAfter(10, monkTick);
241 local function updateDruid(guid, _, talent)
242 local id = guidToId[guid];
253 local function updatePriest(guid, specid)
254 local id = guidToId[guid];
256 if guidToSpecid[guid] ~= specid then
257 for _, frame in pairs(frames[guid]) do
261 guidToSpecid[guid] = specid;
266 local function updatePlayer()
267 local specid = GetSpecializationInfo(GetSpecialization() or 1);
268 local guid = UnitGUID("player");
269 if trackedcds[specid] then
270 guidToId[guid] = "player";
271 guidToSpecid[guid] = specid;
272 if specid == 270 then -- Monk
273 local weapon = GetInventoryItemLink("player", INVSLOT_MAINHAND);
274 local wrist = GetInventoryItemID("player", INVSLOT_WRIST);
275 updateMonk(guid, nil, nil, weapon, wrist);
276 elseif specid == 105 then -- Druid
277 local _, _, _, talent = GetTalentInfo(6, 2, 1);
278 updateDruid(guid, nil, talent);
282 idToGuid["player"] = guid;
284 idToGuid["player"] = nil;
286 for _, frame in pairs(frames[guid]) do
293 local function updateUnitid(id)
294 local guid = UnitGUID(id);
295 if UnitIsUnit(id, "player") then -- player is special case
296 return updatePlayer();
297 elseif UnitExists(id) and UnitGroupRolesAssigned(id) == "HEALER" then
298 local _, class = UnitClass(id);
299 if specs[class] ~= nil then
301 local specid = guidToSpecid[guid];
302 guidToSpecid[guid] = specs[class];
303 if specs[class] == false then
304 if specid == 256 or specid == 257 then
305 guidToSpecid[guid] = specid;
307 guidToSpecid[guid] = 257; -- assume Holy for now to get something visible
310 OmaInspect.Request(guid, id, updatePriest);
311 elseif specs[class] == 270 then
312 -- Monk, have to check inventory for CD modifications
313 -- updateUnitCD (without having fixes yet)
315 OmaInspect.Request(guid, id, updateMonk, true);
316 elseif specs[class] == 105 then
317 -- Druid, have to inspect to get talents
318 -- updateUnitCD (without having fixes yet)
320 OmaInspect.Request(guid, id, updateDruid);
329 for _, frame in pairs(frames[guid]) do
336 local prevGroupType = "solo";
337 local prevGroupSize = 0;
338 local updateQueued = nil;
339 local function updateUnitids()
341 local prefix = "solo";
355 if prevGroupType ~= prefix then
356 -- clean up players from old group
357 for i = 1,prevGroupSize do
358 local id = format("%s%i", prevGroupType, i);
359 local guid = idToGuid[id];
362 print(guid, "removing frames in overall updateUnitids");
363 for _, frame in pairs(frames[guid]) do
368 prevGroupType = prefix;
369 prevGroupSize = size;
374 local id = format("%s%i", prefix, i);
375 if not UnitIsUnit(id, "player") then -- player is already done
381 -- just check removals, don't cause inspects
382 local function fastUpdateUnitids()
383 for guid, unitFrames in pairs(frames) do
384 if UnitGUID(guidToId[guid]) ~= guid then
385 for _, frame in pairs(unitFrames) do
393 ["UNIT_HEALTH"] = function(id)
394 local guid = idToGuid[id];
395 if guid and cds[guid] and UnitIsDeadOrGhost(id) then
397 for spellid, _ in pairs(cds[guid]) do
398 CdDown(guid, spellid, nil);
400 elseif guid and dead[guid] and cds[guid] then
402 for spellid, _ in pairs(cds[guid]) do
407 ["UNIT_CONNECTION"] = function(id)
408 local guid = idToGuid[id];
409 if guid and cds[guid] and not UnitIsConnected(id) then
410 disconnected[guid] = true;
411 for spellid, _ in pairs(cds[guid]) do
412 CdDown(guid, spellid, nil);
414 elseif guid and disconnected[guid] and cds[guid] then
415 disconnected[guid] = nil;
416 for spellid, _ in pairs(cds[guid]) do
421 ["UNIT_SPELLCAST_SUCCEEDED"] = function(id, _, _, _, spellid)
422 local guid = idToGuid[id];
423 if guid and frames[guid] then
424 local frame = frames[guid][spellid];
426 CdDown(guid, spellid, GetTime());
427 elseif monksWithLegendary[guid] and spellid == 115151 then
428 -- Renewing Mist with legendary affects Revival
429 if runningcds[guid] and runningcds[guid][115310] then
430 runningcds[guid][115310] = runningcds[guid][115310] - 2;
432 elseif guidToSpecid[guid] == 257 and spellid == 62618 then
433 -- assumed Holy priest cast Barrier, set to update to Disc
434 guidToSpecid[guid] = 256;
435 for _, frame in pairs(frames[guid]) do
439 CdDown(guid, spellid, GetTime());
443 ["PLAYER_SPECIALIZATION_CHANGED"] = function(id)
446 ["GROUP_ROSTER_UPDATE"] = function()
448 if not updateQueued then
450 CTimerAfter(4, updateUnitids);
454 events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"];
455 events["PLAYER_ENTERING_WORLD"] = events["GROUP_ROSTER_UPDATE"];
457 local function cdtracker()
458 cdframe:SetFrameStrata("LOW");
459 cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 580, -400);
461 cdframe:SetHeight(1);
463 cdframe:UnregisterAllEvents();
464 cdframe:SetScript("OnEvent", function(self, event, ...)
467 cdframe:RegisterEvent("UNIT_HEALTH");
468 cdframe:RegisterEvent("UNIT_CONNECTION");
469 cdframe:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED");
470 cdframe:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
471 cdframe:RegisterEvent("GROUP_ROSTER_UPDATE");
472 cdframe:RegisterEvent("PLAYER_ROLES_ASSIGNED");
473 cdframe:RegisterEvent("PLAYER_ENTERING_WORLD");
475 CTimerAfter(0.5, tick);
476 CTimerAfter(10, monkTick);
479 cdframe:RegisterEvent("PLAYER_LOGIN");
480 cdframe:SetScript("OnEvent", function(self, event)
481 if event == "PLAYER_LOGIN" then