f6d46bc - Add new TMW tracked buffs
[wowui.git] / OmaCD / Cooldowns.lua
1 -- Cooldowns.lua
2 local _;
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;
17
18 local upColor = {0, 0.7, 0.2, 0.7};
19 local downColor = {1, 0, 0, 0.5};
20
21 local cdframe = CreateFrame("Frame", "OmaCD", UIParent);
22 local frames = {};
23 local unused = {};
24 local shown = {}; -- simple array of frames in shown order
25
26 local specs = {
27     ["PALADIN"] = 65,
28     ["PRIEST"] = false, -- priest has 2 healing specs
29     ["SHAMAN"] = 264,
30     ["MONK"] = 270,
31     ["DRUID"] = 105,
32 };
33 local trackedcds = {
34     [65] = { -- Holy Paladin
35         [31821] = 180, -- Aura Mastery
36     },
37     [105] = { -- Resto Druid
38         [740] = 180, -- Tranquility
39     },
40     [256] = { -- Discipline Priest
41         [62618] = 180, -- Power Word: Barrier
42     },
43     [257] = { -- Holy Priest
44         [64843] = 180, -- Divine Hymn
45     },
46     [264] = { -- Resto Shaman
47         [108280] = 180, -- Healing Tide Totem
48         [98008] = 180, -- Spirit Link Totem
49     },
50     [270] = { -- Mistweaver Monk
51         [115310] = 180, -- Revival
52     },
53 };
54
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
58 local cdfixes = {};
59 local runningcds = {}; -- CDs currently active
60
61 local idToGuid = {};
62 local guidToId = {};
63 local guidToSpecid = {};
64 local dead = {};
65 local disconnected = {};
66
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);
72 end
73
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
80 end
81
82 local encounter = nil;
83 local function tick()
84     if not encounter and IsEncounterInProgress() then
85         encounter = true;
86     elseif encounter and not IsEncounterInProgress() then
87         encounter = nil;
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
92                 CdUp(guid, spellid);
93             end
94         end
95     end
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
101                 CdUp(guid, spellid);
102             else -- still on CD
103                 if remain >= 60 then
104                     frames[guid][spellid].text:SetFormattedText("%d:%02d", floor(remain/60), remain%60);
105                 else
106                     frames[guid][spellid].text:SetText(floor(remain));
107                 end
108             end
109         end
110     end
111     CTimerAfter(0.5, tick);
112 end
113
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);
119     end
120 end
121
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
132         frame.pos = i;
133     end
134     frame:ClearAllPoints();
135     frame:Hide();
136     refreshPositions();
137 end
138
139 local unique = 1;
140 local function getFrame()
141     local frame = tremove(unused);
142     if not frame then
143         frame = CreateFrame("Frame", format("OmaCD%i", unique), cdframe);
144         unique = unique + 1;
145         frame:SetWidth(75);
146         frame:SetHeight(16);
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");
158     end
159     return frame;
160 end
161 local function updateCD(guid, specid, spellid)
162     if not guidToId[guid] then return end
163     -- update cd value
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
169
170     -- no cd frame yet, create one
171     local frame = getFrame();
172     frame.name = ssub(UnitName(guidToId[guid]), 1, 8);
173     frame.guid = guid;
174     frame.spellid = spellid;
175     frame.base:SetVertexColor(unpack(upColor));
176     frame.icon:SetTexture(GetSpellTexture(spellid));
177     frame.text:SetText(frame.name);
178     frame:Show();
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);
185     return frame;
186 end
187
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);
193         end
194     end
195 end
196
197 local function updateDruid(guid, _, talent)
198     local id = guidToId[guid];
199     if id then
200         if talent then
201             cdfixes[guid] = -60;
202         else
203             cdfixes[guid] = nil;
204         end
205         updateUnitCD(guid);
206     end
207 end
208
209 local function updatePriest(guid, specid)
210     local id = guidToId[guid];
211     if id then
212         if guidToSpecid[guid] ~= specid and frames[guid] then
213             for _, frame in pairs(frames[guid]) do
214                 removeFrame(frame);
215             end
216         end
217         guidToSpecid[guid] = specid;
218         updateUnitCD(guid);
219     end
220 end
221
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, 1, 1);
230             updateDruid(guid, nil, talent);
231         else
232             updateUnitCD(guid);
233         end
234         idToGuid["player"] = guid;
235     else
236         idToGuid["player"] = nil;
237         if frames[guid] then
238             for _, frame in pairs(frames[guid]) do
239                 removeFrame(frame);
240             end
241         end
242     end
243 end
244
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
252             guidToId[guid] = id;
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;
258                 else
259                     guidToSpecid[guid] = 257; -- assume Holy for now to get something visible
260                 end
261                 updateUnitCD(guid);
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)
266                 updateUnitCD(guid);
267                 OmaInspect.Request(guid, id, updateDruid);
268             else
269                 updateUnitCD(guid);
270             end
271             idToGuid[id] = guid;
272         end
273     else
274         idToGuid[id] = nil;
275         if frames[guid] then
276             for _, frame in pairs(frames[guid]) do
277                 removeFrame(frame);
278             end
279         end
280     end
281 end
282
283 local prevGroupType = "solo";
284 local prevGroupSize = 0;
285 local updateQueued = nil;
286 local function updateUnitids()
287     local size = 0;
288     local prefix = "solo";
289     guidToId = {};
290     updateQueued = nil;
291
292     if IsInGroup() then
293         if IsInRaid() then
294             size = 40;
295             prefix = "raid";
296         else
297             size = 4;
298             prefix = "party";
299         end
300     end
301
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];
307             idToGuid[id] = nil;
308             if frames[guid] then
309                 print(guid, "removing frames in overall updateUnitids");
310                 for _, frame in pairs(frames[guid]) do
311                     removeFrame(frame);
312                 end
313             end
314         end
315         prevGroupType = prefix;
316         prevGroupSize = size;
317     end
318
319     updatePlayer();
320     for i = 1,size do
321         local id = format("%s%i", prefix, i);
322         if not UnitIsUnit(id, "player") then -- player is already done
323             updateUnitid(id);
324         end
325     end
326 end
327
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
333                 removeFrame(frame);
334             end
335         end
336     end
337 end
338
339 local events = {
340     ["UNIT_HEALTH"] = function(id)
341         local guid = idToGuid[id];
342         if guid and cds[guid] and UnitIsDeadOrGhost(id) then
343             dead[guid] = true;
344             for spellid, _ in pairs(cds[guid]) do
345                 CdDown(guid, spellid, nil);
346             end
347         elseif guid and dead[guid] and cds[guid] then
348             dead[guid] = nil;
349             for spellid, _ in pairs(cds[guid]) do
350                 CdUp(guid, spellid);
351             end
352         end
353     end,
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);
360             end
361         elseif guid and disconnected[guid] and cds[guid] then
362             disconnected[guid] = nil;
363             for spellid, _ in pairs(cds[guid]) do
364                 CdUp(guid, spellid);
365             end
366         end
367     end,
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];
372             if frame then
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
378                     removeFrame(frame);
379                 end
380                 updateUnitCD(guid);
381                 CdDown(guid, spellid, GetTime());
382             end
383         end
384     end,
385     ["PLAYER_SPECIALIZATION_CHANGED"] = function(id)
386         updateUnitid(id);
387     end,
388     ["GROUP_ROSTER_UPDATE"] = function()
389         fastUpdateUnitids();
390         if not updateQueued then
391             updateQueued = true
392             CTimerAfter(4, updateUnitids);
393         end
394     end,
395 };
396 events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"];
397 events["PLAYER_ENTERING_WORLD"] = events["GROUP_ROSTER_UPDATE"];
398
399 local function cdtracker()
400     cdframe:SetFrameStrata("LOW");
401     cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 580, -400);
402     cdframe:SetWidth(1);
403     cdframe:SetHeight(1);
404
405     cdframe:UnregisterAllEvents();
406     cdframe:SetScript("OnEvent", function(self, event, ...)
407         events[event](...);
408     end);
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");
416     -- initial tick
417     CTimerAfter(0.5, tick);
418 end
419
420 cdframe:SetScript("OnEvent", cdtracker);
421 cdframe:RegisterEvent("PLAYER_LOGIN");