782e5bd835a306ae15a5c8e87071763e7796ec9a
[wowui.git] / RaidFrameIndicators.lua
1 -- ----------------------------------------------------------------------------
2 -- Raid Frame Indicators by Szandos, schyrio
3 -- ----------------------------------------------------------------------------
4 Indicators = LibStub( "AceAddon-3.0" ):NewAddon( "Indicators", "AceTimer-3.0");
5 local media = LibStub:GetLibrary("LibSharedMedia-3.0");
6 local f = {}; -- Indicator objects
7 local pad = 2;
8 local _;
9 local watchedAuras; -- all watched auras
10 local indicatorAuras; -- watched auras per indicator
11 local DEFAULT_ICON = "Interface\\AddOns\\RaidFrameCustomization\\images\\rhomb";
12
13 -- Hide buff/debuff icons
14 local function hideBlizzardBuffs(frame)
15     -- used in CompactUnitFrame_UpdateAuras (Buffs, Debuffs, DispellableDebuffs)
16     if frame.optionTable.displayBuffs then
17         frame.optionTable.displayBuffs = false
18         -- used in CompactUnitFrame_UpdateHealthColor, might not be set prior
19         frame.optionTable.healthBarColorOverride = CreateColor(0.4, 0.4, 0.4);
20     end
21     if frame.optionTable.displayDebuffs then frame.optionTable.displayDebuffs = false end
22     -- TODO
23     if frame.optionTable.displayDispelDebuffs then frame.optionTable.displayDispelDebuffs = true end
24
25 end
26 hooksecurefunc("CompactUnitFrame_SetOptionTable", hideBlizzardBuffs);
27
28 -- Set the appearance of the FontStrings
29 local function setupIndicatorAppearance(frame)
30     local frameName = frame:GetName();
31     if not f[frameName] then return end
32     local font = media and media:Fetch('font', Indicators.db.profile.indicatorFont) or STANDARD_TEXT_FONT;
33
34     local i;
35     for i = 1, 5 do
36         f[frameName][i].text:SetFont(font, Indicators.db.profile["textSize"..i], "");
37         f[frameName][i].icon:SetWidth(Indicators.db.profile["iconSize"..i]);
38         f[frameName][i].icon:SetHeight(Indicators.db.profile["iconSize"..i]);
39         if Indicators.db.profile["showIcon"..i] then
40             f[frameName][i].icon:Show();
41         else
42             f[frameName][i].icon:Hide();
43         end
44     end
45 end
46
47 -- Create the FontStrings used for indicators
48 local function setupCompactUnitFrame(frame)
49     local frameName = frame:GetName();
50     local i;
51     local positions = {
52         "TOPLEFT", "TOPRIGHT", "CENTER", "BOTTOMLEFT", "BOTTOMRIGHT"
53     };
54     local paddings = {
55         {pad, -pad}, {-pad, -pad}, {0, 0}, {pad, pad}, {-pad, pad}
56     };
57
58     -- Create indicators
59     f[frameName] = {};
60     for i = 1, 5 do
61         f[frameName][i] = {};
62         f[frameName][i].text = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall");
63         f[frameName][i].text:SetPoint(positions[i], frame, positions[i], paddings[i][1], paddings[i][2]);
64         f[frameName][i].icon = frame:CreateTexture(nil, "OVERLAY");
65         f[frameName][i].icon:SetPoint(positions[i], frame, positions[i], paddings[i][1], paddings[i][2]);
66     end
67     setupIndicatorAppearance(frame);
68 end
69
70 -- Get all unit auras
71 local function getAuras(unit)
72     local unitBuffs = {};
73     local unitDebuffs = {};
74     local auraName, icon, count, expires, caster, debuffType, spellId;
75     local i = 1;
76
77     -- Get all unit buffs
78     while true do
79         auraName, _, icon, count, _, _, expires, caster, _, _, spellId = UnitBuff(unit, i);
80         if not spellId then break end
81         if watchedAuras[auraName] or watchedAuras[spellId] then
82             -- possibly non-contiguous indexes, doesn't matter
83             unitBuffs[i] = {};
84             unitBuffs[i].auraName = auraName;
85             unitBuffs[i].spellId = spellId;
86             unitBuffs[i].count = count;
87             unitBuffs[i].expires = expires;
88             unitBuffs[i].mine = UnitIsUnit(caster, "player");
89             unitBuffs[i].icon = icon;
90         end
91         i = i + 1;
92     end
93
94     -- Get all unit debuffs
95     i = 1;
96     while true do
97         auraName, _, icon, count, debuffType, _, expires, caster, _, _, spellId  = UnitDebuff(unit, i);
98         if not spellId then break end
99         -- TODO debuffType check better
100         if watchedAuras[auraName] or watchedAuras[spellId] or watchedAuras[debuffType] then
101             unitDebuffs[i] = {};
102             unitDebuffs[i].auraName = auraName;
103             unitDebuffs[i].spellId = spellId;
104             unitDebuffs[i].count = count;
105             unitDebuffs[i].expires = expires;
106             unitDebuffs[i].mine = UnitIsUnit(caster, "player");
107             unitDebuffs[i].icon = icon;
108             unitDebuffs[i].debuffType = debuffType;
109         end
110         i = i + 1;
111     end
112
113     return unitBuffs, unitDebuffs;
114 end
115
116 -- Check the indicators on a frame and update the times on them
117 local function updateIndicator(frame)
118     if not frame.unit then return end
119     local unit = frame.unit;
120     local frameName = frame:GetName();
121     local currentTime = GetTime();
122     local i;
123
124     -- Check if the indicator object exists, else create it
125     if not f[frameName] then setupCompactUnitFrame(frame) end
126     -- Hide if unit is dead/disconnected
127     if (not UnitIsConnected(unit)) or UnitIsDeadOrGhost(frame.displayedUnit) then
128         for i = 1, 5 do
129             f[frameName][i].text:SetText("");
130             f[frameName][i].icon:SetTexture("");
131         end
132         return;
133     end
134
135     local unitBuffs, unitDebuffs = getAuras(unit);
136     for i = 1, 5 do
137         -- try to find matching aura
138         local found, aura;
139         for _, aura in pairs(unitBuffs) do
140             if indicatorAuras[i][aura.auraName] or indicatorAuras[i][aura.spellId] then
141                 found = aura;
142                 -- break on first matching buff cast by me
143                 -- otherwise continue through
144                 if aura.mine then break end
145             end
146         end
147         if not found then
148             -- search debuffs if buff was not found
149             for _, aura in pairs(unitDebuffs) do
150                 if indicatorAuras[i][aura.auraName] or indicatorAuras[i][aura.spellId] or
151                    indicatorAuras[i][aura.debuffType] then
152                     found = aura;
153                     -- break on first matching debuff cast by me
154                     -- otherwise continue through
155                     if aura.mine then break end
156                 end
157             end
158         end
159
160         if found then
161             if Indicators.db.profile["mine"..i] and not found.mine then
162                 -- don't show
163                 f[frameName][i].icon:SetTexture("");
164                 f[frameName][i].text:SetText("");
165             else
166                 if Indicators.db.profile["showIcon"..i] then
167                     -- show icon TODO coloring
168                     if Indicators.db.profile["useDefaultIcon"..i] then
169                         f[frameName][i].icon:SetTexture(DEFAULT_ICON);
170                     else
171                         f[frameName][i].icon:SetTexture(found.icon);
172                     end
173                 end
174                 -- TODO make show text into general setting
175                 -- under which you can select what text to show
176                 if Indicators.db.profile["showText"..i] then
177                     -- show text
178                     local text;
179                     local remaining = found.expires - currentTime;
180                     if remaining > 60 then
181                         text = string.format("%dm", ceil(remaining/60));
182                     else
183                         text = string.format("%d", floor(remaining+0.5));
184                     end
185
186                     if Indicators.db.profile["stack"..i] and found.count > 0 then
187                         if text then
188                             text = found.count.."-"..text;
189                         else
190                             text = found.count;
191                         end
192                     end
193
194                     -- colors
195                     f[frameName][i].text:SetTextColor(
196                         Indicators.db.profile["color"..i].r,
197                         Indicators.db.profile["color"..i].g,
198                         Indicators.db.profile["color"..i].b,
199                         Indicators.db.profile["color"..i].a
200                     );
201                     if Indicators.db.profile["debuffColor"..i] then
202                         if found.debuffType then
203                             if found.debuffType == "Curse" then
204                                 f[frameName][i].text:SetTextColor(0.6,0,1,1);
205                             elseif found.debuffType == "Disease" then
206                                 f[frameName][i].text:SetTextColor(0.6,0.4,0,1);
207                             elseif found.debuffType == "Magic" then
208                                 f[frameName][i].text:SetTextColor(0.2,0.6,1,1);
209                             elseif found.debuffType == "Poison" then
210                                 f[frameName][i].text:SetTextColor(0,0.6,0,1);
211                             end
212                         end
213                     end
214
215                     f[frameName][i].text:SetText(text);
216                 end
217             end
218         else
219             -- not found, show nothing
220             f[frameName][i].icon:SetTexture("");
221             f[frameName][i].text:SetText("");
222         end
223     end
224 end
225
226 -- Update all indicators
227 function Indicators:UpdateAllIndicators()
228     CompactRaidFrameContainer_ApplyToFrames(CompactRaidFrameContainer, "normal", updateIndicator);
229 end
230
231 -- Used to update everything that is affected by the configuration
232 function Indicators:RefreshConfig()
233     local i;
234     CompactRaidFrameContainer_ApplyToFrames(CompactRaidFrameContainer, "normal", setupIndicatorAppearance);
235
236     -- Format aura strings
237     watchedAuras = {};
238     indicatorAuras = {};
239     local auraName, i
240     for i = 1, 5 do
241         indicatorAuras[i] = {};
242         for auraName in string.gmatch(Indicators.db.profile["auras"..i], "[^\n]+") do -- Grab each line
243             auraName = string.gsub(auraName, "^%s*(.-)%s*$", "%1"); -- Strip any whitespaces
244             if tonumber(auraName) then
245                 watchedAuras[tonumber(auraName)] = true;
246                 indicatorAuras[i][tonumber(auraName)] = true;
247             else
248                 watchedAuras[auraName] = true;
249                 indicatorAuras[i][auraName] = true;
250             end
251         end
252     end
253
254     self:CancelAllTimers();
255     if next(watchedAuras) ~= nil then
256         self.updateTimer = self:ScheduleRepeatingTimer("UpdateAllIndicators", 0.11);
257     end
258 end
259
260 function Indicators:OnInitialize()
261     -- Set up config pane
262     self:SetupOptions();
263
264     -- Register callbacks for profile switching
265     self.db.RegisterCallback(self, "OnProfileChanged", "RefreshConfig");
266     self.db.RegisterCallback(self, "OnProfileCopied", "RefreshConfig");
267     self.db.RegisterCallback(self, "OnProfileReset", "RefreshConfig");
268 end
269
270 function Indicators:OnEnable()
271     if self.db.profile.enabled then
272         Indicators:RefreshConfig();
273     end
274 end
275
276 function Indicators:OnDisable()
277     local i;
278     -- Stop update
279     self:CancelAllTimers();
280     -- Hide all indicators
281     for frameName, _ in pairs(f) do
282         for i = 1, 5 do
283             f[frameName][i].text:SetText("");
284             f[frameName][i].icon:SetTexture("");
285         end
286     end
287 end
288
289 function dPrint(s)
290     DEFAULT_CHAT_FRAME:AddMessage("Indicators: ".. tostring(s));
291 end