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