b13d011 - Re-add basic indicators
[wowui.git] / OmaRF / NewIndicators.lua
1 -- Indicators.lua
2 local unpack, ipairs, pairs, ceil, floor = unpack, ipairs, pairs, ceil, floor;
3 local UnitIsConnected, UnitIsDeadOrGhost = UnitIsConnected, UnitIsDeadOrGhost;
4 local UnitAura, UnitIsPlayer, GetTime = UnitAura, UnitIsPlayer, GetTime;
5 local UnitName, C_TimerAfter = UnitName, C_Timer.After;
6 local IsInGroup, IsInRaid, GetNumGroupMembers = IsInGroup, IsInRaid, GetNumGroupMembers;
7 local format = string.format;
8
9 local positions = OmaRF.positions;
10 local auraFilters = {"HELPFUL", "HARMFUL"};
11 local DEFAULT_ICON = "Interface\\AddOns\\OmaRF\\images\\rhomb";
12 local _;
13
14 local frameBase;
15 local frames = {};
16 local watchedAuras = {};
17 local majorAuras = {};
18 local majorMax;
19 local partyState = "solo";
20 local partyMembers = {"player", "party1", "party2", "party3", "party4"};
21
22 local function remaining(expires, current)
23     if expires == 0 then return "" end
24     local remain = expires - current;
25     if remain > 60 then
26         return format("%dm", ceil(remain/60));
27     end
28     return floor(remain+0.5);
29 end
30
31 local function showIndicator(ind, icon, caster, expires, current, config)
32     if not config.mine or UnitIsPlayer(caster) then
33         if config.showIcon then
34             if config.useDefaultIcon then
35                 ind.icon:SetTexture(DEFAULT_ICON);
36             else
37                 ind.icon:SetTexture(icon);
38             end
39             if not ind.icon:IsShown() then ind.icon:Show() end
40         end
41         ind.expires = expires;
42         if config.showText then
43             ind.text:SetText(remaining(expires, current));
44             if not ind.text:IsShown() then ind.text:Show() end
45         end
46     end
47 end
48
49 local function showMajorIndicator(ind, icon, count, expires, current)
50     ind.icon:SetTexture(icon);
51     ind.expires = expires;
52     ind.expireText:SetText(remaining(expires, current));
53     if count > 1 then
54         ind.stackText:SetText(count);
55         if not ind.stackText:IsShown() then ind.stackText:Show() end
56     else
57         if ind.stackText:IsShown() then ind.stackText:Hide() end
58     end
59     if not ind.icon:IsShown() then ind.icon:Show() end
60     if not ind.expireText:IsShown() then ind.expireText:Show() end
61 end
62
63 -- update current auras TODO add bar coloring back with extra options
64 local function updateAuras(frame, unit)
65     for _, ind in pairs(frame.inds) do ind.expires = nil end
66     for _, ind in ipairs(frame.majorInds) do ind.expires = nil end
67
68     local name, icon, count, expires, caster, id;
69     local majorPos = 1;
70     local current = GetTime();
71     for _, filter in ipairs(auraFilters) do
72         local i = 1;
73         while true do
74             name, _, icon, count, _, _, expires, caster, _, _, id = UnitAura(unit, i, filter);
75             if not id then break end
76             local pos = watchedAuras[id] or watchedAuras[name];
77             if pos then
78                 showIndicator(
79                     frame.inds[pos], icon, caster, expires, current,
80                     OmaRF.db.profile.indicators[pos]
81                 );
82             end
83             if (majorAuras[id] or majorAuras[name]) and majorPos <= majorMax then
84                 showMajorIndicator(frame.majorInds[majorPos], icon, count, expires, current);
85                 majorPos = majorPos + 1;
86             end
87             i = i + 1;
88         end
89     end
90 end
91
92 -- Check the indicators on a frame and update the times on them
93 local function updateIndicators(frame, i)
94     local unit = "player";
95     if partyState == "solo" then
96         if frame:IsShown() then frame:Hide() end
97         return;
98     elseif partyState == "raid" then
99         unit = partyState..i;
100     elseif i ~= 1 then
101         -- party frames have player separate
102         unit = partyState..(i-1);
103     end
104
105     if not UnitExists(unit) then
106         return;
107     elseif not UnitIsConnected(unit) or UnitIsDeadOrGhost(unit) then
108         if frame:IsShown() then frame:Hide() end
109         return;
110     elseif not frame:IsShown() then
111         frame:Show();
112     end
113
114     local current = GetTime();
115     for pos, ind in pairs(frame.inds) do
116         if ind.expires ~= nil then
117             if OmaRF.db.profile.indicators[pos].showText then
118                 ind.text:SetText(remaining(ind.expires, current));
119             end
120         else
121             if ind.icon:IsShown() then ind.icon:Hide() end
122             if ind.text:IsShown() then ind.text:Hide() end
123         end
124     end
125     for _, ind in ipairs(frame.majorInds) do
126         if ind.expires ~= nil then
127             ind.expireText:SetText(remaining(ind.expires, current));
128         else
129             if ind.icon:IsShown() then ind.icon:Hide() end
130             if ind.expireText:IsShown() then ind.expireText:Hide() end
131             if ind.stackText:IsShown() then ind.stackText:Hide() end
132         end
133     end
134 end
135
136 local function configureIndicators(frame)
137     local config = OmaRF.db.profile.indicators;
138     for pos, ind in pairs(frame.inds) do
139         ind.text:SetFont(STANDARD_TEXT_FONT, config[pos]["textSize"]);
140         ind.text:SetTextColor(unpack(config[pos]["textColor"]));
141         ind.icon:SetWidth(config[pos]["iconSize"]);
142         ind.icon:SetHeight(config[pos]["iconSize"]);
143         ind.icon:SetTexture(DEFAULT_ICON);
144         ind.icon:SetVertexColor(unpack(config[pos]["iconColor"]));
145     end
146
147     config = OmaRF.db.profile.majorAuras;
148     for i, ind in ipairs(frame.majorInds) do
149         if i == 1 then
150             ind.icon:ClearAllPoints();
151             ind.icon:SetPoint("CENTER", frame, "CENTER", -config["iconSize"], 0);
152         end
153         ind.icon:SetWidth(config["iconSize"]);
154         ind.icon:SetHeight(config["iconSize"]);
155         ind.expireText:SetFont(STANDARD_TEXT_FONT, config["textSize"], "OUTLINE");
156         ind.stackText:SetFont(STANDARD_TEXT_FONT, config["textSize"], "OUTLINE");
157     end
158 end
159
160 local function createIndicators(frame)
161     frame.inds = {};
162     for _, pos in ipairs(positions) do
163         frame.inds[pos] = {};
164         frame.inds[pos].text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
165         frame.inds[pos].text:SetPoint(pos, frame, pos);
166         frame.inds[pos].icon = frame:CreateTexture(nil, "OVERLAY");
167         frame.inds[pos].icon:SetPoint(pos, frame, pos);
168     end
169
170     frame.majorInds = {};
171     majorMax = OmaRF.db.profile.majorAuras.max;
172     for i = 1, majorMax do
173         frame.majorInds[i] = {};
174         local ind = frame.majorInds[i];
175         ind.icon = frame:CreateTexture(nil, "OVERLAY");
176         if i > 1 then
177             ind.icon:SetPoint("TOPLEFT", frame.majorInds[i-1].icon, "TOPRIGHT");
178         end
179         ind.expireText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
180         ind.expireText:SetPoint("BOTTOMRIGHT", ind.icon, "BOTTOMRIGHT");
181         ind.stackText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
182         ind.stackText:SetPoint("TOPLEFT", ind.icon, "TOPLEFT");
183     end
184
185     configureIndicators(frame);
186 end
187
188 -- Update all indicators
189 local function updateAllIndicators()
190     for i, frame in ipairs(frames) do
191         updateIndicators(frame, i);
192     end
193     if OmaRF.running then C_TimerAfter(0.15, updateAllIndicators) end
194 end
195
196 -- Used to update everything that is affected by the configuration
197 function OmaRF:RefreshConfig()
198     self:OnDisable(); -- clear everything
199     if self.db.profile.enabled then
200         for _, f in pairs(frames) do configureIndicators(f) end
201
202         watchedAuras = {};
203         for _, pos in ipairs(positions) do
204             for _, aura in ipairs(self.db.profile.indicators[pos]["auras"]) do
205                 watchedAuras[aura] = pos; -- TODO single aura only in one position
206             end
207         end
208         majorAuras = {};
209         for _, aura in ipairs(self.db.profile.majorAuras["auras"]) do
210             majorAuras[aura] = true;
211         end
212
213         if next(watchedAuras) ~= nil or next(majorAuras) ~= nil then
214             if not frameBase:IsShown() then frameBase:Show() end
215             self.running = true;
216             C_TimerAfter(0.15, updateAllIndicators);
217         end
218     end
219 end
220
221 local function updateStatusText(self, unit)
222     if not UnitIsConnected(unit) then
223         self.statusText:SetText("DC");
224     elseif UnitIsDeadOrGhost(unit) then
225         self.statusText:SetText("rip");
226     else
227         local healthLost = UnitHealthMax(unit) - UnitHealth(unit);
228         if healthLost <= 0 then 
229             self.statusText:SetText("");
230             return;
231         end
232
233         local prettyHealth;
234         if healthLost > 1200000000 then -- 1.2B
235             prettyHealth = format("-%.1fB", healthLost / 1000000000);
236         elseif healthLost > 1200000 then -- 1.2M
237             prettyHealth = format("-%.1fM", healthLost / 1000000);
238         elseif healthLost > 1000 then -- 1K
239             prettyHealth = format("-%dK", healthLost / 1000);
240         else
241             prettyHealth = format("-%d", healthLost)
242         end
243
244         self.statusText:SetText(prettyHealth);
245     end
246 end
247
248 local function frameEvent(self, event, ...)
249     local arg1, arg2, arg3, arg4 = ...;
250     if event == "UNIT_HEALTH" or event == "UNIT_HEALTH_FREQUENT" then
251         updateStatusText(self, arg1);
252     elseif event == "UNIT_AURA" then
253         updateAuras(self, arg1);
254     end
255 end
256
257 --[[ TODO powerBar display and bar coloring
258     if options.displayPowerBar and role ~= "HEALER" then
259         frame.healthBar:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -1, 1);
260         frame.powerBar:Hide();
261     end
262 ]]
263
264 --local update_ooc = false;
265 local function updateFrames()
266     if not IsInGroup() or GetNumGroupMembers() == 1 then
267         -- solo
268         partyState = "solo";
269         local frame = frames[1];
270         frame:UnregisterAllEvents();
271         frame:RegisterUnitEvent("UNIT_AURA", "player");
272         frame:RegisterUnitEvent("UNIT_HEALTH", "player");
273         frame:RegisterUnitEvent("UNIT_HEALTH_FREQUENT", "player");
274     else
275         if IsInRaid() then
276             partyState = "raid";
277             for i, frame in ipairs(frames) do
278                 local name = partyState..i;
279                 if not UnitExists(name) then break end
280                 frame:UnregisterAllEvents();
281                 frame:RegisterUnitEvent("UNIT_AURA", name);
282                 frame:RegisterUnitEvent("UNIT_HEALTH", name);
283                 frame:RegisterUnitEvent("UNIT_HEALTH_FREQUENT", name);
284             end
285         else
286             partyState = "party";
287             for i, name in ipairs(partyMembers) do
288                 local frame = frames[i];
289                 if not UnitExists(name) then break end
290                 frame:UnregisterAllEvents();
291                 frame:RegisterUnitEvent("UNIT_AURA", name);
292                 frame:RegisterUnitEvent("UNIT_HEALTH", name);
293                 frame:RegisterUnitEvent("UNIT_HEALTH_FREQUENT", name);
294             end
295         end
296     end
297     --[[for name, frame in pairs(frames) do
298         local unitFrame = _G[name];
299         if unitFrame then
300             if not frame.unitFrameSetup then
301                 if InCombatLockdown() then
302                     update_ooc = true;
303                 else
304                     unitFrame.name:SetFont(STANDARD_TEXT_FONT, 12, "");
305                     unitFrame.optionTable.displayBuffs = false;
306                     unitFrame.optionTable.displayDebuffs = false;
307                     unitFrame.optionTable.displayDispelDebuffs = false;
308                     frame.unitFrameSetup = true;
309                 end
310             end
311             local unit = unitFrame.unit;
312             if unit then
313                 if InCombatLockdown() then
314                     update_ooc = true;
315                 else
316                     local name = UnitName(unit);
317                     unitFrame.name:SetText(name);
318                 end
319             end
320         end
321     end]]
322 end
323
324 local function createStatusText(frame)
325     frame.statusText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
326     frame.statusText:SetPoint("CENTER", frame, "CENTER");
327     frame.statusText:SetFont(STANDARD_TEXT_FONT, 14, "");
328 end
329
330 local function initialize(frame)
331     frames[1] = CreateFrame("Frame", "OmaRF1", frameBase);
332     frames[1]:SetAllPoints("CompactRaidFrame1"); -- only connection to blizzard frames
333     frames[1]:SetScript("OnEvent", frameEvent);
334     frames[1]:Hide();
335     createStatusText(frames[1]);
336     createIndicators(frames[1]);
337     local i = 2;
338     -- TODO size could change
339     local width, height = frames[1]:GetWidth(), frames[1]:GetHeight();
340     for y = 2,5 do
341         frames[i] = CreateFrame("Frame", "OmaRF"..i, frameBase);
342         frames[i]:SetPoint("TOPLEFT", frames[i-1], "BOTTOMLEFT");
343         frames[i]:SetWidth(width);
344         frames[i]:SetHeight(height);
345         frames[i]:SetScript("OnEvent", frameEvent);
346         frames[i]:Hide();
347         createStatusText(frames[i]);
348         createIndicators(frames[i]);
349         i = i + 1;
350     end
351     for x = 0,6 do
352         for y = 1,5 do
353             frames[i] = CreateFrame("Frame", "OmaRF"..i, frameBase);
354             frames[i]:SetPoint("TOPLEFT", frames[x*5+y], "TOPRIGHT");
355             frames[i]:SetWidth(width);
356             frames[i]:SetHeight(height);
357             frames[i]:SetScript("OnEvent", frameEvent);
358             frames[i]:Hide();
359             createStatusText(frames[i]);
360             createIndicators(frames[i]);
361             i = i + 1;
362         end
363     end
364     C_TimerAfter(0.01, updateFrames);
365 end
366
367 local function baseEvent(self, event, ...)
368     if event == "GROUP_ROSTER_UPDATE" then
369         -- not sure if fired before LayoutFrames, wait a bit
370         C_TimerAfter(0.01, updateFrames);
371     --[[elseif event == "PLAYER_REGEN_ENABLED" then
372         if update_ooc then
373             updateFrames();
374             update_ooc = false;
375         end]]
376     elseif event == "PLAYER_LOGIN" then
377         initialize();
378     end
379 end
380
381 frameBase = CreateFrame("Frame", nil, UIParent);
382 frameBase:SetFrameStrata("HIGH");
383 frameBase:RegisterEvent("PLAYER_LOGIN");
384 frameBase:RegisterEvent("PLAYER_REGEN_ENABLED");
385 frameBase:RegisterEvent("GROUP_ROSTER_UPDATE");
386 frameBase:SetScript("OnEvent", baseEvent);
387 OmaRF.frameBase = frameBase;
388
389 -- hide buffs, debuffs from Blizzard frames
390 --DefaultCompactUnitFrameOptions.displayBuffs = false;
391 --DefaultCompactUnitFrameOptions.displayDebuffs = false;
392 --DefaultCompactUnitFrameOptions.displayDispelDebuffs = false;