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, -- Revival
55 local cds = {}; -- CD durations currently available modified by passive cdfixes
56 -- CD fixes active e.g. cdfixes[guid] = -60, no need for spellid as
57 -- only classes with one CD have fixes
59 local runningcds = {}; -- CDs currently active
63 local guidToSpecid = {};
65 local disconnected = {};
67 local function CdUp(guid, spellid)
68 local frame = frames[guid][spellid];
69 if runningcds[guid] then runningcds[guid][spellid] = nil end
70 frame.base:SetVertexColor(unpack(upColor));
71 frame.text:SetText(frame.name);
74 local function CdDown(guid, spellid, start)
75 local frame = frames[guid][spellid];
76 if not runningcds[guid] then runningcds[guid] = {} end
77 runningcds[guid][spellid] = start;
78 frame.base:SetVertexColor(unpack(downColor));
79 if start then frame.text:SetText(cds[guid][spellid]) end
82 local encounter = nil;
84 if not encounter and IsEncounterInProgress() then
86 elseif encounter and not IsEncounterInProgress() then
88 -- for some reason can't hook to ENCOUNTER_END event so have to track with timer
89 -- TODO support Argus, encounterID 1866
90 for guid, t in pairs(runningcds) do
91 for spellid, _ in pairs(t) do
96 local current = GetTime();
97 for guid, t in pairs(runningcds) do
98 for spellid, start in pairs(t) do
99 local remain = cds[guid][spellid] - (current - start);
100 if remain <= 0 then -- CD back up
104 frames[guid][spellid].text:SetFormattedText("%d:%02d", floor(remain/60), remain%60);
106 frames[guid][spellid].text:SetText(floor(remain));
111 CTimerAfter(0.5, tick);
114 local function refreshPositions()
115 for i, frame in ipairs(shown) do
116 local prev = shown[i-1] or cdframe;
117 frame:ClearAllPoints();
118 frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
122 local function removeFrame(frame)
123 unused[#unused+1] = frame;
124 frames[frame.guid][frame.spellid] = nil;
125 if not next(frames[frame.guid]) then frames[frame.guid] = nil end
126 cds[frame.guid][frame.spellid] = nil;
127 dead[frame.guid] = nil;
128 disconnected[frame.guid] = nil;
129 if runningcds[frame.guid] then runningcds[frame.guid][frame.spellid] = nil end
130 tremove(shown, frame.pos);
131 for i, frame in ipairs(shown) do
134 frame:ClearAllPoints();
140 local function getFrame()
141 local frame = tremove(unused);
143 frame = CreateFrame("Frame", format("OmaCD%i", unique), cdframe);
147 frame.base = frame:CreateTexture(nil, "BACKGROUND");
148 frame.base:SetAllPoints();
149 frame.base:SetColorTexture(1, 1, 1);
150 frame.icon = frame:CreateTexture(nil, "ARTWORK");
151 frame.icon:SetPoint("TOPLEFT");
152 frame.icon:SetPoint("BOTTOMLEFT");
153 frame.icon:SetWidth(16);
154 frame.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93);
155 frame.text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
156 frame.text:SetPoint("LEFT", frame, "LEFT", 20, 0);
157 frame.text:SetJustifyH("LEFT");
161 local function updateCD(guid, specid, spellid)
162 if not guidToId[guid] then return end
164 local cd = trackedcds[specid][spellid];
165 if cdfixes[guid] then cd = cd + cdfixes[guid] end
166 if not cds[guid] then cds[guid] = {} end
167 cds[guid][spellid] = cd;
168 if frames[guid] and frames[guid][spellid] then return end
170 -- no cd frame yet, create one
171 local frame = getFrame();
172 frame.name = ssub(UnitName(guidToId[guid]), 1, 8);
174 frame.spellid = spellid;
175 frame.base:SetVertexColor(unpack(upColor));
176 frame.icon:SetTexture(GetSpellTexture(spellid));
177 frame.text:SetText(frame.name);
179 if not frames[guid] then frames[guid] = {} end
180 frames[guid][spellid] = frame;
181 frame.pos = #shown+1;
182 local prev = shown[#shown] or cdframe;
183 shown[#shown+1] = frame;
184 frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -1);
188 local function updateUnitCD(guid)
189 local specid = guidToSpecid[guid];
190 if specid and trackedcds[specid] then
191 for spellid, _ in pairs(trackedcds[specid]) do
192 updateCD(guid, specid, spellid);
197 local function updateDruid(guid, _, talent)
198 local id = guidToId[guid];
209 local function updatePriest(guid, specid)
210 local id = guidToId[guid];
212 if guidToSpecid[guid] ~= specid and frames[guid] then
213 for _, frame in pairs(frames[guid]) do
217 guidToSpecid[guid] = specid;
222 local function updatePlayer()
223 local specid = GetSpecializationInfo(GetSpecialization() or 1);
224 local guid = UnitGUID("player");
225 if trackedcds[specid] then
226 guidToId[guid] = "player";
227 guidToSpecid[guid] = specid;
228 if specid == 105 then -- Druid
229 local _, _, _, talent = GetTalentInfo(6, 2, 1);
230 updateDruid(guid, nil, talent);
234 idToGuid["player"] = guid;
236 idToGuid["player"] = nil;
238 for _, frame in pairs(frames[guid]) do
245 local function updateUnitid(id)
246 local guid = UnitGUID(id);
247 if UnitIsUnit(id, "player") then -- player is special case
248 return updatePlayer();
249 elseif UnitExists(id) and UnitGroupRolesAssigned(id) == "HEALER" then
250 local _, class = UnitClass(id);
251 if specs[class] ~= nil then
253 local specid = guidToSpecid[guid];
254 guidToSpecid[guid] = specs[class];
255 if specs[class] == false then
256 if specid == 256 or specid == 257 then
257 guidToSpecid[guid] = specid;
259 guidToSpecid[guid] = 257; -- assume Holy for now to get something visible
262 OmaInspect.Request(guid, id, updatePriest);
263 elseif specs[class] == 105 then
264 -- Druid, have to inspect to get talents
265 -- updateUnitCD (without having fixes yet)
267 OmaInspect.Request(guid, id, updateDruid);
276 for _, frame in pairs(frames[guid]) do
283 local prevGroupType = "solo";
284 local prevGroupSize = 0;
285 local updateQueued = nil;
286 local function updateUnitids()
288 local prefix = "solo";
302 if prevGroupType ~= prefix then
303 -- clean up players from old group
304 for i = 1,prevGroupSize do
305 local id = format("%s%i", prevGroupType, i);
306 local guid = idToGuid[id];
309 print(guid, "removing frames in overall updateUnitids");
310 for _, frame in pairs(frames[guid]) do
315 prevGroupType = prefix;
316 prevGroupSize = size;
321 local id = format("%s%i", prefix, i);
322 if not UnitIsUnit(id, "player") then -- player is already done
328 -- just check removals, don't cause inspects
329 local function fastUpdateUnitids()
330 for guid, unitFrames in pairs(frames) do
331 if UnitGUID(guidToId[guid]) ~= guid then
332 for _, frame in pairs(unitFrames) do
340 ["UNIT_HEALTH"] = function(id)
341 local guid = idToGuid[id];
342 if guid and cds[guid] and UnitIsDeadOrGhost(id) then
344 for spellid, _ in pairs(cds[guid]) do
345 CdDown(guid, spellid, nil);
347 elseif guid and dead[guid] and cds[guid] then
349 for spellid, _ in pairs(cds[guid]) do
354 ["UNIT_CONNECTION"] = function(id)
355 local guid = idToGuid[id];
356 if guid and cds[guid] and not UnitIsConnected(id) then
357 disconnected[guid] = true;
358 for spellid, _ in pairs(cds[guid]) do
359 CdDown(guid, spellid, nil);
361 elseif guid and disconnected[guid] and cds[guid] then
362 disconnected[guid] = nil;
363 for spellid, _ in pairs(cds[guid]) do
368 ["UNIT_SPELLCAST_SUCCEEDED"] = function(id, _, spellid)
369 local guid = idToGuid[id];
370 if guid and frames[guid] then
371 local frame = frames[guid][spellid];
373 CdDown(guid, spellid, GetTime());
374 elseif guidToSpecid[guid] == 257 and spellid == 62618 then
375 -- assumed Holy priest cast Barrier, set to update to Disc
376 guidToSpecid[guid] = 256;
377 for _, frame in pairs(frames[guid]) do
381 CdDown(guid, spellid, GetTime());
385 ["PLAYER_SPECIALIZATION_CHANGED"] = function(id)
388 ["GROUP_ROSTER_UPDATE"] = function()
390 if not updateQueued then
392 CTimerAfter(4, updateUnitids);
396 events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"];
397 events["PLAYER_ENTERING_WORLD"] = events["GROUP_ROSTER_UPDATE"];
399 local function cdtracker()
400 cdframe:SetFrameStrata("LOW");
401 cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 580, -400);
403 cdframe:SetHeight(1);
405 cdframe:UnregisterAllEvents();
406 cdframe:SetScript("OnEvent", function(self, event, ...)
409 cdframe:RegisterEvent("UNIT_HEALTH");
410 cdframe:RegisterEvent("UNIT_CONNECTION");
411 cdframe:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED");
412 cdframe:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
413 cdframe:RegisterEvent("GROUP_ROSTER_UPDATE");
414 cdframe:RegisterEvent("PLAYER_ROLES_ASSIGNED");
415 cdframe:RegisterEvent("PLAYER_ENTERING_WORLD");
417 CTimerAfter(0.5, tick);
420 cdframe:SetScript("OnEvent", cdtracker);
421 cdframe:RegisterEvent("PLAYER_LOGIN");