fa6bbd0 - Add Cloying Shadows debuff
[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-40), -- Revival, already lowered by Artifact when lvl 75
52     },
53 };
54 local monkRelics = { -- Tendrils of Revival
55     ["151012"] = -10,
56     ["151010"] = -10,
57     ["147112"] = -10,
58     ["152291"] = -10,
59 };
60
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
64 local cdfixes = {};
65 local runningcds = {}; -- CDs currently active
66
67 local idToGuid = {};
68 local guidToId = {};
69 local guidToSpecid = {};
70 local monks = {};
71 local monksWithLegendary = {};
72 local dead = {};
73 local disconnected = {};
74
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);
80 end
81
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
88 end
89
90 local encounter = nil;
91 local function tick()
92     if not encounter and IsEncounterInProgress() then
93         encounter = true;
94     elseif encounter and not IsEncounterInProgress() then
95         encounter = nil;
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
100                 CdUp(guid, spellid);
101             end
102         end
103     end
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
109                 CdUp(guid, spellid);
110             else -- still on CD
111                 if remain >= 60 then
112                     frames[guid][spellid].text:SetFormattedText("%d:%02d", floor(remain/60), remain%60);
113                 else
114                     frames[guid][spellid].text:SetText(floor(remain));
115                 end
116             end
117         end
118     end
119     CTimerAfter(0.5, tick);
120 end
121
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);
127     end
128 end
129
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
142         frame.pos = i;
143     end
144     frame:ClearAllPoints();
145     frame:Hide();
146     refreshPositions();
147 end
148
149 local unique = 1;
150 local function getFrame()
151     local frame = tremove(unused);
152     if not frame then
153         frame = CreateFrame("Frame", format("OmaCD%i", unique), cdframe);
154         unique = unique + 1;
155         frame:SetWidth(75);
156         frame:SetHeight(16);
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");
168     end
169     return frame;
170 end
171 local function updateCD(guid, specid, spellid)
172     if not guidToId[guid] then return end
173     -- update cd value
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
179
180     -- no cd frame yet, create one
181     local frame = getFrame();
182     frame.name = ssub(UnitName(guidToId[guid]), 1, 8);
183     frame.guid = guid;
184     frame.spellid = spellid;
185     frame.base:SetVertexColor(unpack(upColor));
186     frame.icon:SetTexture(GetSpellTexture(spellid));
187     frame.text:SetText(frame.name);
188     frame:Show();
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);
195     return frame;
196 end
197
198 local function updateUnitCD(guid)
199     local specid = guidToSpecid[guid];
200     if specid then
201         for spellid, _ in pairs(trackedcds[specid]) do
202             updateCD(guid, specid, spellid);
203         end
204     end
205 end
206
207 local function updateMonk(guid, _, _, weapon, wrist)
208     local id = guidToId[guid];
209     if id then
210         local _, _, _, relic1, relic2, relic3 = strsplit(":", weapon);
211         local cdfix = 0;
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
215         if cdfix ~= 0 then
216             cdfixes[guid] = cdfix;
217         else
218             cdfixes[guid] = nil;
219         end
220
221         if wrist == 137096 then -- Petrichor Lagniappe
222             monksWithLegendary[guid] = true;
223         else
224             monksWithLegendary[guid] = nil;
225         end
226         monks[guid] = true;
227         updateUnitCD(guid);
228     end
229 end
230
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) end
237         end
238     end
239     CTimerAfter(10, monkTick);
240 end
241
242 local function updateDruid(guid, _, talent)
243     local id = guidToId[guid];
244     if id then
245         if talent then
246             cdfixes[guid] = -60;
247         else
248             cdfixes[guid] = nil;
249         end
250         updateUnitCD(guid);
251     end
252 end
253
254 local function updatePriest(guid, specid)
255     local id = guidToId[guid];
256     if id then
257         if guidToSpecid[guid] ~= specid and frames[guid] then
258             for _, frame in pairs(frames[guid]) do
259                 removeFrame(frame);
260             end
261         end
262         guidToSpecid[guid] = specid;
263         updateUnitCD(guid);
264     end
265 end
266
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);
280         else
281             updateUnitCD(guid);
282         end
283         idToGuid["player"] = guid;
284     else
285         idToGuid["player"] = nil;
286         if frames[guid] then
287             for _, frame in pairs(frames[guid]) do
288                 removeFrame(frame);
289             end
290         end
291     end
292 end
293
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
301             guidToId[guid] = id;
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;
307                 else
308                     guidToSpecid[guid] = 257; -- assume Holy for now to get something visible
309                 end
310                 updateUnitCD(guid);
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)
315                 updateUnitCD(guid);
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)
320                 updateUnitCD(guid);
321                 OmaInspect.Request(guid, id, updateDruid);
322             else
323                 updateUnitCD(guid);
324             end
325             idToGuid[id] = guid;
326         end
327     else
328         idToGuid[id] = nil;
329         if frames[guid] then
330             for _, frame in pairs(frames[guid]) do
331                 removeFrame(frame);
332             end
333         end
334     end
335 end
336
337 local prevGroupType = "solo";
338 local prevGroupSize = 0;
339 local updateQueued = nil;
340 local function updateUnitids()
341     local size = 0;
342     local prefix = "solo";
343     guidToId = {};
344     updateQueued = nil;
345
346     if IsInGroup() then
347         if IsInRaid() then
348             size = 40;
349             prefix = "raid";
350         else
351             size = 4;
352             prefix = "party";
353         end
354     end
355
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];
361             idToGuid[id] = nil;
362             if frames[guid] then
363                 print(guid, "removing frames in overall updateUnitids");
364                 for _, frame in pairs(frames[guid]) do
365                     removeFrame(frame);
366                 end
367             end
368         end
369         prevGroupType = prefix;
370         prevGroupSize = size;
371     end
372
373     updatePlayer();
374     for i = 1,size do
375         local id = format("%s%i", prefix, i);
376         if not UnitIsUnit(id, "player") then -- player is already done
377             updateUnitid(id);
378         end
379     end
380 end
381
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
387                 removeFrame(frame);
388             end
389         end
390     end
391 end
392
393 local events = {
394     ["UNIT_HEALTH"] = function(id)
395         local guid = idToGuid[id];
396         if guid and cds[guid] and UnitIsDeadOrGhost(id) then
397             dead[guid] = true;
398             for spellid, _ in pairs(cds[guid]) do
399                 CdDown(guid, spellid, nil);
400             end
401         elseif guid and dead[guid] and cds[guid] then
402             dead[guid] = nil;
403             for spellid, _ in pairs(cds[guid]) do
404                 CdUp(guid, spellid);
405             end
406         end
407     end,
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);
414             end
415         elseif guid and disconnected[guid] and cds[guid] then
416             disconnected[guid] = nil;
417             for spellid, _ in pairs(cds[guid]) do
418                 CdUp(guid, spellid);
419             end
420         end
421     end,
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];
426             if frame then
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;
432                 end
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
437                     removeFrame(frame);
438                 end
439                 updateUnitCD(guid);
440                 CdDown(guid, spellid, GetTime());
441             end
442         end
443     end,
444     ["PLAYER_SPECIALIZATION_CHANGED"] = function(id)
445         updateUnitid(id);
446     end,
447     ["GROUP_ROSTER_UPDATE"] = function()
448         fastUpdateUnitids();
449         if not updateQueued then
450             updateQueued = true
451             CTimerAfter(4, updateUnitids);
452         end
453     end,
454 };
455 events["PLAYER_ROLES_ASSIGNED"] = events["GROUP_ROSTER_UPDATE"];
456 events["PLAYER_ENTERING_WORLD"] = events["GROUP_ROSTER_UPDATE"];
457
458 local function cdtracker()
459     cdframe:SetFrameStrata("LOW");
460     cdframe:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 580, -400);
461     cdframe:SetWidth(1);
462     cdframe:SetHeight(1);
463
464     cdframe:UnregisterAllEvents();
465     cdframe:SetScript("OnEvent", function(self, event, ...)
466         events[event](...);
467     end);
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");
475     -- initial tick
476     CTimerAfter(0.5, tick);
477     CTimerAfter(10, monkTick);
478 end
479
480 cdframe:RegisterEvent("PLAYER_LOGIN");
481 cdframe:SetScript("OnEvent", function(self, event)
482     if event == "PLAYER_LOGIN" then
483         return cdtracker();
484     end
485 end);