14d33e379590788d9b68706e1fc47e71503b5d6c
[wowui.git] / OmaRF / CFrame.lua
1 -- CFrame.lua
2 local _;
3 local unpack, pairs, ipairs = unpack, pairs, ipairs;
4 local ssub = string.sub;
5 local min = math.min;
6 local UnitName, UnitClass = UnitName, UnitClass;
7 local UnitInRange, UnitDebuff, UnitIsCharmed = UnitInRange, UnitDebuff, UnitIsCharmed;
8 local UnitPower, UnitPowerMax, UnitPowerType = UnitPower, UnitPowerMax, UnitPowerType;
9 local UnitHealth, UnitHealthMax = UnitHealth, UnitHealthMax;
10 local UnitInParty, UnitInRaid = UnitInParty, UnitInRaid;
11 local UnitGetIncomingHeals, UnitGetTotalAbsorbs = UnitGetIncomingHeals, UnitGetTotalAbsorbs;
12 local UnitThreatSituation, GetThreatStatusColor = UnitThreatSituation, GetThreatStatusColor;
13 local UnitIsDeadOrGhost, UnitIsConnected = UnitIsDeadOrGhost, UnitIsConnected;
14 local UnitHasIncomingResurrection = UnitHasIncomingResurrection;
15 local UnitGetTotalHealAbsorbs = UnitGetTotalHealAbsorbs;
16 local IsInGroup, IsInRaid = IsInGroup, IsInRaid;
17 local CTimerAfter = C_Timer.After;
18 local RAID_CLASS_COLORS = RAID_CLASS_COLORS;
19
20 local Frames = OmaFrames;
21 local Indicators = OmaIndicators;
22 local checkIndicators = Indicators.CheckIndicators;
23
24 local CFrame = CreateFrame("Frame", "OmaCFrame", UIParent);
25 local party = {};
26 local raid = {};
27 local updaters = {};
28
29 local width, height = Frames.Width, Frames.Height;
30 local anchorX, anchorY = Frames.AnchorX, Frames.AnchorY;
31 local baseColor = Frames.BaseColor;
32 local overlayColorDispel = Frames.OverlayColorDispel;
33 local overlayColorCharm = Frames.OverlayColorCharm;
34 local overlayColorAlert = Frames.OverlayColorAlert;
35 local powerColors = Frames.PowerColors;
36
37 local function updateHealth(frame, unit)
38     local current, max = UnitHealth(unit), frame.health.max;
39     local healthLost = max - current;
40     -- sanity check, occasionally UnitHealthMax gives zero
41     if max > 0 then
42         frame.health:SetWidth(current/frame.health.max*width);
43     else
44         frame.health:SetWidth(width);
45         frame.text:Hide();
46         return;
47     end
48
49     if UnitIsDeadOrGhost(unit) then
50         frame.health:SetWidth(1);
51         if UnitHasIncomingResurrection(unit) then
52             frame.text:SetText("Rez");
53         else
54             frame.text:SetText("Dead");
55         end
56     elseif not UnitIsConnected(unit) then
57         frame.health:SetWidth(1);
58         frame.text:SetText("DC");
59     elseif healthLost > 0 then
60         if healthLost > 1200000000 then -- 1.2B
61             frame.text:SetFormattedText("-%.1fB", healthLost / 1000000000);
62         elseif healthLost > 1200000 then -- 1.2M
63             frame.text:SetFormattedText("-%.1fM", healthLost / 1000000);
64         elseif healthLost > 1000 then -- 1K
65             frame.text:SetFormattedText("-%dK", healthLost / 1000);
66         else
67             frame.text:SetFormattedText("-%d", healthLost)
68         end
69         frame.text:Show();
70     else
71         frame.text:Hide();
72     end
73 end
74
75 local function updateMaxHealth(frame, unit)
76     frame.health.max = UnitHealthMax(unit);
77     updateHealth(frame, unit);
78 end
79
80 local function updatePower(frame, unit)
81     local max = frame.mana.max;
82     -- sanity check, occasionally UnitPowerMax gives zero
83     if max > 0 then
84         frame.mana:SetWidth(UnitPower(unit)/max*width);
85     else
86         frame.mana:SetWidth(width);
87     end
88 end
89
90 local function updateMaxPower(frame, unit)
91     frame.mana.max = UnitPowerMax(unit);
92     updatePower(frame, unit);
93 end
94
95 local function updatePowerColor(frame, unit)
96     frame.mana:SetColorTexture(unpack(powerColors[UnitPowerType(unit)]));
97 end
98
99 local function updateName(frame, unit)
100     local name = UnitName(unit);
101     if not name then return end
102     frame.name:SetText(ssub(name, 1, 6));
103
104     local _, class = UnitClass(unit);
105     local color = RAID_CLASS_COLORS[class];
106     if color then frame.name:SetVertexColor(color.r, color.g, color.b) end
107 end
108
109 local function updateHealPred(frame, unit)
110     local incoming = UnitGetIncomingHeals(unit) or 0;
111     if incoming > 0 then
112         local max = frame.health.max;
113         local space = width - frame.health:GetWidth() + 1;
114         local pred = (incoming / max) * width;
115         frame.healpred:SetWidth(min(space, pred));
116         frame.healpred:Show();
117     else
118         frame.healpred:Hide();
119     end
120 end
121
122 local function updateShield(frame, unit)
123     local shield = UnitGetTotalAbsorbs(unit) or 0;
124     if shield > 0 then
125         local max = frame.health.max;
126         local space = width - frame.health:GetWidth();
127         shield = (shield / max) * width;
128         if space < shield then
129             frame.shield:SetWidth(space);
130             frame.shieldhl:Show();
131         else
132             frame.shield:SetWidth(shield);
133             frame.shieldhl:Hide();
134         end
135         frame.shield:Show();
136     else
137         frame.shield:Hide();
138         frame.shieldhl:Hide();
139     end
140 end
141
142 local function updateHealAbsorb(frame, unit)
143     local absorb = UnitGetTotalHealAbsorbs(unit) or 0;
144     if absorb > 0 then
145         local max = frame.health.max;
146         local space = frame.health:GetWidth();
147         absorb = (absorb / max) * width;
148         frame.healabsorb:SetWidth(min(space, absorb));
149         frame.healabsorb:Show();
150     else
151         frame.healabsorb:Hide();
152     end
153 end
154
155 local function updateAuras(frame, unit)
156     checkIndicators(frame, unit);
157     if UnitDebuff(unit, 1, "RAID") ~= nil then
158         -- something dispellable
159         if frame.overlay.color ~= overlayColorDispel then
160             frame.overlay:SetColorTexture(unpack(overlayColorDispel));
161             frame.overlay:Show();
162             frame.overlay.color = overlayColorDispel;
163         end
164     elseif UnitIsCharmed(unit) then
165         if frame.overlay.color ~= overlayColorCharm then
166             frame.overlay:SetColorTexture(unpack(overlayColorCharm));
167             frame.overlay:Show();
168             frame.overlay.color = overlayColorCharm;
169         end
170     else
171         if frame.overlay.color ~= nil then
172             frame.overlay:Hide();
173             frame.overlay.color = nil;
174         end
175     end
176 end
177
178 local function updateAggro(frame, unit)
179     local status = UnitThreatSituation(unit);
180     if status and status > 0 then
181         frame.base:SetColorTexture(GetThreatStatusColor(status));
182     else
183         frame.base:SetColorTexture(unpack(baseColor));
184     end
185 end
186
187 local eventFuncs = {
188     ["UNIT_HEALTH"] = function(frame, unit)
189         updateHealth(frame, unit);
190         updateShield(frame, unit);
191         updateHealAbsorb(frame, unit);
192         -- no heal prediction update, that doesn't overflow too much
193     end,
194     ["UNIT_POWER"] = function(frame, unit)
195         updatePower(frame, unit);
196     end,
197     ["UNIT_AURA"] = function(frame, unit)
198         updateAuras(frame, unit);
199     end,
200     ["UNIT_HEAL_PREDICTION"] = function(frame, unit)
201         updateHealPred(frame, unit);
202     end,
203     ["UNIT_ABSORB_AMOUNT_CHANGED"] = function(frame, unit)
204         updateShield(frame, unit);
205     end,
206     ["UNIT_HEAL_ABSORB_AMOUNT_CHANGED"] = function(frame, unit)
207         updateHealAbsorb(frame, unit);
208     end,
209     ["UNIT_THREAT_SITUATION_UPDATE"] = function(frame, unit)
210         updateAggro(frame, unit);
211     end,
212     ["UNIT_MAXHEALTH"] = function(frame, unit)
213         updateMaxHealth(frame, unit);
214     end,
215     ["UNIT_MAXPOWER"] = function(frame, unit)
216         updateMaxPower(frame, unit);
217     end,
218     ["UNIT_DISPLAYPOWER"] = function(frame, unit)
219         updatePowerColor(frame, unit);
220     end,
221     ["UNIT_NAME_UPDATE"] = function(frame, unit)
222         updateName(frame, unit);
223     end,
224     ["UNIT_CONNECTION"] = function(frame, unit)
225         updateHealth(frame, unit);
226     end,
227     ["INCOMING_RESURRECT_CHANGED"] = function(frame, unit)
228         -- TODO have an icon
229         updateHealth(frame, unit);
230     end,
231     ["PARTY_MEMBER_ENABLE"] = function(frame)
232         -- new power info possibly (FrameXML/CompactUnitFrame.lua)
233         updateMaxPower(frame, frame.unit);
234         updatePowerColor(frame, frame.unit);
235     end,
236     ["UPDATE_ALL_BARS"] = function(frame, unit)
237         updateMaxHealth(frame, unit);
238         updateMaxPower(frame, unit);
239         --updateHealth(frame, unit); -- MaxHealth covers this
240         --updatePower(frame, unit); -- MaxPower covers this
241         updateAuras(frame, unit);
242         updateShield(frame, unit);
243         updateHealPred(frame, unit);
244         updateHealAbsorb(frame, unit);
245         updatePowerColor(frame, unit);
246         updateName(frame, unit);
247     end,
248 };
249 eventFuncs["UNIT_HEALTH_FREQUENT"] = eventFuncs["UNIT_HEALTH"];
250 eventFuncs["PARTY_MEMBER_DISABLE"] = eventFuncs["PARTY_MEMBER_ENABLE"];
251 local function unitEvent(self, event, ...)
252     local arg1, arg2, arg3, arg4 = ...;
253     eventFuncs[event](self, arg1);
254 end
255
256 local function unitUpdate(self, elapsed)
257     -- there's no in/out of range event, have to check each frame
258     -- from FrameXML/CompactUnitFrame.lua
259     local inRange, checked = UnitInRange(self.unit);
260     if checked and not inRange then
261         self:SetAlpha(0.55);
262     else
263         self:SetAlpha(1);
264     end
265 end
266
267 local function registerEvents(frame, unit)
268     -- events are taken from FrameXML/CompactUnitFrame.lua
269     -- TODO vehicle support, ready check support, raid marker support,
270     -- player flags support (/afk, /dnd)
271     frame:RegisterEvent("PARTY_MEMBER_ENABLE");
272     frame:RegisterEvent("PARTY_MEMBER_DISABLE");
273     frame:RegisterUnitEvent("UNIT_HEALTH", unit);
274     frame:RegisterUnitEvent("UNIT_HEALTH_FREQUENT", unit);
275     frame:RegisterUnitEvent("UNIT_MAXHEALTH", unit);
276     frame:RegisterUnitEvent("UNIT_POWER", unit);
277     frame:RegisterUnitEvent("UNIT_MAXPOWER", unit);
278     frame:RegisterUnitEvent("UNIT_DISPLAYPOWER", unit);
279     frame:RegisterUnitEvent("UNIT_NAME_UPDATE", unit);
280     frame:RegisterUnitEvent("UNIT_AURA", unit);
281     frame:RegisterUnitEvent("UNIT_HEAL_PREDICTION", unit);
282     frame:RegisterUnitEvent("UNIT_ABSORB_AMOUNT_CHANGED", unit);
283     frame:RegisterUnitEvent("UNIT_HEAL_ABSORB_AMOUNT_CHANGED", unit);
284     frame:RegisterUnitEvent("UNIT_THREAT_SITUATION_UPDATE", unit);
285     frame:RegisterUnitEvent("UNIT_CONNECTION", unit);
286     frame:RegisterUnitEvent("INCOMING_RESURRECT_CHANGED", unit);
287 end
288
289 local function vis(frame, vis)
290     if vis then
291         frame:Show();
292         frame:SetScript("OnUpdate", unitUpdate);
293         frame:UnregisterAllEvents();
294         registerEvents(frame, frame.unit);
295         -- wait one frame to update data
296         -- create function if needed to pass arguments to unitEvent
297         local func = updaters[frame];
298         if not func then
299             func = function() unitEvent(frame, "UPDATE_ALL_BARS", frame.unit) end;
300             updaters[frame] = func;
301         end
302         CTimerAfter(0.01, func);
303     else
304         frame:Hide();
305         frame:SetScript("OnUpdate", nil);
306         frame:UnregisterAllEvents();
307     end;
308 end
309
310 local function updateGroup()
311     if IsInGroup() then
312         if IsInRaid() then
313             -- show raid
314             for _, val in ipairs(raid) do
315                 if UnitInRaid(val.frame.unit) then
316                     vis(val.frame, true);
317                 elseif val.frame:IsShown() then
318                     vis(val.frame, false);
319                 end
320             end
321             -- hide player + party (use pairs, not ipairs)
322             for _, val in pairs(party) do
323                 if val.frame:IsShown() then vis(val.frame, false) end
324             end
325         else
326             -- show player + party
327             for _, val in pairs(party) do
328                 if UnitInParty(val.frame.unit) then
329                     vis(val.frame, true);
330                 elseif val.frame:IsShown() then
331                     vis(val.frame, false);
332                 end
333             end
334             -- hide raid
335             for _, val in ipairs(raid) do
336                 if val.frame:IsShown() then vis(val.frame, false) end
337             end
338         end
339     else
340         -- show player
341         vis(party[0].frame, true);
342         -- hide all other frames
343         for _, val in ipairs(party) do
344             if val.frame:IsShown() then vis(val.frame, false) end
345         end
346         for _, val in ipairs(raid) do
347             if val.frame:IsShown() then vis(val.frame, false) end
348         end
349     end
350 end
351
352 local function initialize()
353     CFrame:SetPoint("CENTER", nil, "CENTER", anchorX, anchorY);
354     CFrame:SetHeight((height+2)*8);
355     CFrame:SetWidth((width+2)*5+1); -- extra pixel for shield overflow
356     Frames.InitializeParty(CFrame, party, unitEvent);
357     Frames.InitializeRaid(CFrame, raid, unitEvent);
358 end
359
360 CFrame:RegisterEvent("PLAYER_LOGIN");
361 CFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
362 CFrame:RegisterEvent("GROUP_ROSTER_UPDATE");
363 CFrame:RegisterEvent("PLAYER_REGEN_ENABLED");
364 CFrame:SetScript("OnEvent", function(self, event, ...)
365     if event == "GROUP_ROSTER_UPDATE" or event == "PLAYER_REGEN_ENABLED" or
366        event == "PLAYER_ENTERING_WORLD" then
367         updateGroup();
368     elseif event == "PLAYER_LOGIN" then
369         initialize();
370     end
371 end);