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