f69c4b0 - Add Delusions debuff
[wowui.git] / OmaAB / ActionBars.lua
1 -- ActionBars.lua
2 local _;
3 local pairs = pairs;
4 local GetActionTexture = GetActionTexture;
5 local GetActionLossOfControlCooldown = GetActionLossOfControlCooldown;
6 local GetActionCooldown, GetActionCharges = GetActionCooldown, GetActionCharges;
7 local IsConsumableAction, IsStackableAction = IsConsumableAction, IsStackableAction;
8 local IsItemAction, GetActionCount = IsItemAction, GetActionCount;
9 local HasAction, IsUsableAction = HasAction, IsUsableAction;
10 local IsCurrentAction, IsAutoRepeatAction = IsCurrentAction, IsAutoRepeatAction;
11 local RegisterStateDriver = RegisterStateDriver;
12 local CooldownFrame_Set, CooldownFrame_Clear = CooldownFrame_Set, CooldownFrame_Clear;
13 local CTimerAfter = C_Timer.After;
14 local GameTooltip = nil;
15 local COOLDOWN_TYPE_LOSS_OF_CONTROL = COOLDOWN_TYPE_LOSS_OF_CONTROL;
16 local COOLDOWN_TYPE_NORMAL = COOLDOWN_TYPE_NORMAL;
17 local CDTexture = "Interface\\Cooldown\\edge";
18 local locCDTexture = "Interface\\Cooldown\\edge-LoC";
19
20 local BUTTONLOCK = false; -- change to lock button dragging
21
22 local settings = {
23     ["OmaBT1"] = {
24         start = 1,
25         length = 12,
26         x = 1000,
27         y = 1000,
28     },
29     ["OmaBT2"] = {
30         start = 13,
31         length = 12,
32         x = 1000,
33         y = 960,
34     },
35     ["OmaBT3"] = {
36         start = 25,
37         length = 12,
38         x = 1000,
39         y = 920,
40     },
41     ["OmaBT4"] = {
42         start = 37,
43         length = 12,
44         x = 1000,
45         y = 880,
46     },
47     ["OmaBT5"] = {
48         start = 49,
49         length = 12,
50         x = 1000,
51         y = 840,
52     },
53     ["OmaBT6"] = {
54         start = 61,
55         length = 12,
56         x = 1000,
57         y = 800,
58     },
59     ["OmaBT7"] = {
60         start = 73,
61         length = 12,
62         x = 1000,
63         y = 760,
64     },
65     ["OmaBT8"] = {
66         start = 85,
67         length = 12,
68         x = 1000,
69         y = 720,
70     },
71     ["OmaBT9"] = {
72         start = 97,
73         length = 12,
74         x = 1000,
75         y = 680,
76     },
77     ["OmaBT10"] = {
78         start = 109,
79         length = 12,
80         x = 1000,
81         y = 640,
82     },
83 };
84
85 local buttons = {};
86 local activeButtons = {};
87
88 local ActionBars = CreateFrame("Frame", "OmaActionBars", UIParent);
89 local inheritedFrames = "SecureActionButtonTemplate,SecureHandlerDragTemplate,SecureHandlerStateTemplate";
90
91 local numChargeCDs = 0;
92 local function createChargeCD(parent)
93     numChargeCDs = numChargeCDs + 1;
94     local frame = CreateFrame("Cooldown", "OmaChargeCD"..numChargeCDs, parent, "CooldownFrameTemplate");
95     frame:SetHideCountdownNumbers(false);
96     frame:SetDrawSwipe(false);
97     frame:SetAllPoints(parent);
98     frame:SetFrameStrata("TOOLTIP");
99     return frame;
100 end
101
102 local function clearChargeCD(parent)
103     if parent.chargecd then CooldownFrame_Clear(parent.chargecd) end
104 end
105
106 local function startChargeCD(parent, start, duration, modrate)
107     if start == 0 then
108         return clearChargeCD(parent);
109     end
110     parent.chargecd = parent.chargecd or createChargeCD(parent);
111     CooldownFrame_Set(parent.chargecd, start, duration, true, true, modrate);
112 end
113
114 local redoCooldown;
115
116 local function updateCooldown(button, slot)
117     -- CD update from FrameXML/ActionButton.lua
118     local locstart, locduration = GetActionLossOfControlCooldown(slot);
119     local start, duration, enable, modrate = GetActionCooldown(slot);
120     local charges, maxcharges, chargestart, chargeduration, chargemodrate = GetActionCharges(slot);
121     if (locstart + locduration) > (start + duration) then
122         if button.cd.currentCooldownType ~= COOLDOWN_TYPE_LOSS_OF_CONTROL then
123             button.cd:SetEdgeTexture(locCDTexture);
124             button.cd:SetSwipeColor(0.17, 0, 0);
125             button.cd:SetHideCountdownNumbers(true);
126             button.cd.currentCooldownType = COOLDOWN_TYPE_LOSS_OF_CONTROL;
127         end
128
129         CooldownFrame_Set(button.cd, locstart, locduration, true, true, modrate);
130         clearChargeCD(button);
131     else
132         if button.cd.currentCooldownType ~= COOLDOWN_TYPE_NORMAL then
133             button.cd:SetEdgeTexture(CDTexture);
134             button.cd:SetSwipeColor(0, 0, 0);
135             button.cd:SetHideCountdownNumbers(false);
136             button.cd.currentCooldownType = COOLDOWN_TYPE_NORMAL;
137         end
138
139         if locstart > 0 then
140             button.cd:SetScript("OnCooldownDone", redoCooldown);
141         end
142         if charges and maxcharges and maxcharges > 1 and charges < maxcharges then
143             startChargeCD(button, chargestart, chargeduration, chargemodrate);
144         else
145             clearChargeCD(button);
146         end
147         CooldownFrame_Set(button.cd, start, duration, enable, false, modrate);
148     end
149 end
150
151 local function redoCooldown(cd)
152     local button = cd:GetParent();
153     cd:SetScript("OnCooldownDone", nil);
154     updateCooldown(button, button.slot);
155 end
156
157 local function updateCount(button, slot)
158     if IsConsumableAction(slot) or IsStackableAction(slot) or
159             (not IsItemAction(slot) and GetActionCount(slot) > 0) then
160         local count = GetActionCount(slot);
161         if count > 99 then
162             button.count:SetText("*");
163         else
164             button.count:SetText(count);
165         end
166     else
167         local charges, maxcharges = GetActionCharges(slot);
168         if maxcharges > 1 then
169             button.count:SetText(charges);
170         else
171             button.count:SetText("");
172         end
173     end
174 end
175
176 local function updateUsable(button, slot)
177     local isUsable, noMana = IsUsableAction(slot);
178     if isUsable then
179         button.icon:SetVertexColor(1, 1, 1);
180     elseif noMana then
181         button.icon:SetVertexColor(0, 0.5, 1);
182     else
183         button.icon:SetVertexColor(0.4, 0.4, 0.4);
184     end
185 end
186
187 local function updateState(button, slot)
188     if IsCurrentAction(slot) or IsAutoRepeatAction(slot) then
189         button:SetChecked(true);
190     else
191         button:SetChecked(false);
192     end
193 end
194
195 local function updateButton(button, slot)
196     if HasAction(slot) then
197         activeButtons[slot] = button;
198         button.base:Show();
199         button.icon:SetTexture(GetActionTexture(slot));
200         updateCooldown(button, slot);
201         updateUsable(button, slot);
202         updateState(button, slot);
203         updateCount(button, slot);
204     else
205         activeButtons[slot] = nil;
206         if not button.grid then button.base:Hide() end
207         button.icon:SetTexture(nil);
208         button.cd:Hide();
209         button.count:SetText("");
210     end
211 end
212
213 local function createActionBar(parent, name, config)
214     local prev;
215     for slot = config.start, config.start+config.length-1 do
216         local secure = CreateFrame("CheckButton", name..slot, parent, inheritedFrames);
217         secure.slot = slot;
218         if slot == config.start then
219             secure:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", config.x, config.y);
220         else
221             secure:SetPoint("TOPLEFT", prev, "TOPRIGHT");
222         end
223         secure:RegisterForClicks("AnyUp");
224         if not BUTTONLOCK then
225             secure:RegisterForDrag("LeftButton", "RightButton");
226         end
227         secure:SetWidth(32);
228         secure:SetHeight(32);
229         secure.base = secure:CreateTexture(nil, "BACKGROUND");
230         secure.base:SetAllPoints();
231         secure.base:SetColorTexture(0, 0, 0, 0.5);
232         secure.iconbase = secure:CreateTexture(nil, "BORDER");
233         secure.iconbase:SetPoint("TOPLEFT", secure.base, "TOPLEFT", 1, -1);
234         secure.iconbase:SetPoint("BOTTOMRIGHT", secure.base, "BOTTOMRIGHT", -1, 1);
235         secure.iconbase:SetColorTexture(0, 0, 0, 0.5);
236         secure.iconbase:Hide();
237         secure.icon = secure:CreateTexture(nil, "ARTWORK");
238         secure.icon:SetPoint("TOPLEFT", secure.iconbase, "TOPLEFT");
239         secure.icon:SetPoint("BOTTOMRIGHT", secure.iconbase, "BOTTOMRIGHT");
240         secure.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93);
241         secure:SetCheckedTexture("Interface\\Buttons\\CheckButtonHilight");
242         secure.hotkey = secure:CreateFontString(nil, "OVERLAY", "NumberFontNormalGray");
243         secure.hotkey:SetPoint("TOPRIGHT", secure, "TOPRIGHT", 2, -1);
244         secure.count = secure:CreateFontString(nil, "OVERLAY", "NumberFontNormal");
245         secure.count:SetPoint("BOTTOMRIGHT", secure, "BOTTOMRIGHT", 2, -1);
246         secure.cd = CreateFrame("Cooldown", name..slot.."CD", secure, "CooldownFrameTemplate");
247         secure.cd:SetAllPoints();
248         secure:SetAttribute("type", "action");
249         secure:SetAttribute("action", slot);
250         function secure:ActionChanged()
251             self.slot = self:GetAttribute("action");
252             return updateButton(self, self.slot);
253         end
254         secure:ActionChanged(); -- initial update
255         -- FrameXML/SecureHandlers.lua has arguments and return value
256         -- args: self, button, kind, value, ... (kind, value, ... from GetCursorInfo())
257         -- returns: kind, target, detail
258         -- or: "clear", kind, target, detail
259         -- used for Pickup* functions
260         -- some of these snippets based on LibActionButton-1.0
261         secure:SetAttribute("_ondragstart", [=[
262             return "action", self:GetAttribute("action");
263         ]=]);
264         secure:SetAttribute("_onreceivedrag", [=[
265             if not kind or not value then return nil end
266             return "action", self:GetAttribute("action");
267         ]=]);
268         -- pre-wrapper can pass a message to post-wrapper
269         secure:WrapScript(secure, "OnDragStart", [=[
270             local kind, value = GetActionInfo(self:GetAttribute("action"));
271             return "message", format("%s|%s", tostring(kind), tostring(value));
272         ]=], [=[
273             local kind, value = GetActionInfo(self:GetAttribute("action"));
274             if message ~= format("%s|%s", tostring(kind), tostring(value)) then
275                 self:CallMethod("ActionChanged");
276             end
277         ]=]);
278         secure:WrapScript(secure, "OnReceiveDrag", [=[
279             local kind, value = GetActionInfo(self:GetAttribute("action"));
280             return "message", format("%s|%s", tostring(kind), tostring(value));
281         ]=], [=[
282             local kind, value = GetActionInfo(self:GetAttribute("action"));
283             if message ~= format("%s|%s", tostring(kind), tostring(value)) then
284                 self:CallMethod("ActionChanged");
285             end
286         ]=]);
287         secure:WrapScript(secure, "OnClick", [=[
288             local kind, value = GetActionInfo(self:GetAttribute("action"));
289             return nil, format("%s|%s", tostring(kind), tostring(value));
290         ]=], [=[
291             local kind, value = GetActionInfo(self:GetAttribute("action"));
292             if message ~= format("%s|%s", tostring(kind), tostring(value)) then
293                 self:CallMethod("ActionChanged");
294             end
295         ]=]);
296         if slot < 13 then
297             -- first action bar has possible states based on vehicle/possess etc.
298             secure:SetAttribute("origaction", slot);
299             secure:SetAttribute("_onstate-possess", [=[
300                 if newstate == "possess" then
301                     local slot;
302                     if HasVehicleActionBar() then
303                         slot = (GetVehicleBarIndex()-1)*12+self:GetAttribute("origaction");
304                     elseif HasOverrideActionBar() then
305                         slot = (GetOverrideBarIndex()-1)*12+self:GetAttribute("origaction");
306                     elseif HasTempShapeshitftActionBar() then
307                         slot = (GetTempShapeshiftBarIndex()-1)*12+self:GetAttribute("origaction");
308                     else
309                         -- something wrong, just revert to normal
310                         print("Possess bar index not found");
311                         slot = self:GetAttribute("origaction");
312                     end
313                     self:SetAttribute("action", slot);
314                 else
315                     self:SetAttribute("action", self:GetAttribute("origaction"));
316                 end
317                 self:CallMethod("ActionChanged");
318             ]=]);
319             RegisterStateDriver(secure, "possess", "[overridebar][possessbar][shapeshift] possess; normal");
320         else
321             function secure:ShowButton() if HasAction(slot) then activeButtons[slot] = self end end
322             function secure:HideButton() activeButtons[slot] = nil end
323             -- all other action bar are hidden if with overridebar or vehicleui (not shapeshift, possessbar)
324             -- default Bartender4 options
325             secure:SetAttribute("_onstate-possess", [=[
326                 if newstate == "possess" then
327                     self:Hide();
328                     self:CallMethod("HideButton");
329                 else
330                     self:Show();
331                     self:CallMethod("ShowButton");
332                 end
333             ]=]);
334             RegisterStateDriver(secure, "possess", "[overridebar][vehicleui] possess; normal");
335         end
336         buttons[slot] = secure;
337         prev = secure;
338     end
339 end
340
341 local function initialize()
342     ActionBars:SetFrameStrata("LOW");
343     ActionBars:SetPoint("BOTTOMLEFT");
344     ActionBars:SetWidth(1);
345     ActionBars:SetHeight(1);
346     for name, config in pairs(settings) do
347         createActionBar(ActionBars, name, config);
348     end
349 end
350
351 local throttleCD = false;
352 local function throttleCDDone() throttleCD = false end
353
354 local events = {
355     ["ACTIONBAR_UPDATE_COOLDOWN"] = function()
356         if not throttleCD then -- only update at most once/frame
357             throttleCD = true;
358             for _, button in pairs(activeButtons) do
359                 updateCooldown(button, button.slot);
360             end
361             CTimerAfter(0.01, throttleCDDone); -- wait one frame
362         end
363     end,
364     ["SPELL_UPDATE_CHARGES"] = function()
365         for _, button in pairs(activeButtons) do
366             updateCount(button, button.slot);
367         end
368     end,
369     ["ACTIONBAR_SLOT_CHANGED"] = function(slot)
370         if buttons[slot] then buttons[slot]:ActionChanged() end
371     end,
372     ["ACTIONBAR_SHOWGRID"] = function()
373         for _, button in pairs(buttons) do
374             button.grid = true;
375             button.iconbase:Show();
376             if not activeButtons[button.slot] then button.base:Show() end
377         end
378     end,
379     ["ACTIONBAR_HIDEGRID"] = function()
380         for _, button in pairs(buttons) do
381             button.grid = nil;
382             button.iconbase:Hide();
383             if not activeButtons[button.slot] then button.base:Hide() end
384         end
385     end,
386     ["ACTIONBAR_UPDATE_STATE"] = function()
387         for _, button in pairs(activeButtons) do
388             updateState(button, button.slot);
389         end
390     end,
391     ["ACTIONBAR_UPDATE_USABLE"] = function()
392         for _, button in pairs(activeButtons) do
393             updateUsable(button, button.slot);
394         end
395     end,
396     ["UPDATE_OVERRIDE_ACTIONBAR"] = function()
397         if buttons[1] then -- called before PLAYER_LOGIN
398             for _, button in pairs(buttons) do
399                 updateButton(button, button.slot);
400             end
401         end
402     end,
403     ["UPDATE_BINDINGS"] = function()
404     end,
405     ["UPDATE_ALL_BUTTONS"] = function()
406         for _, button in pairs(buttons) do
407             updateButton(button, button.slot);
408         end
409     end,
410     ["PLAYER_LOGIN"] = function()
411         GameTooltip = _G["GameTooltip"];
412         initialize();
413     end,
414 };
415 events["LOSS_OF_CONTROL_ADDED"] = events["ACTIONBAR_UPDATE_COOLDOWN"];
416 events["LOSS_OF_CONTROL_UPDATE"] = events["ACTIONBAR_UPDATE_COOLDOWN"]; -- TODO might change once tooltips are in
417 events["PLAYER_MOUNT_DISPLAY_CHANGED"] = events["ACTIONBAR_UPDATE_USABLE"];
418 events["UPDATE_VEHICLE_ACTIONBAR"] = events["UPDATE_ALL_BUTTONS"];
419 events["SPELL_UPDATE_ICON"] = events["UPDATE_ALL_BUTTONS"];
420 events["PET_STABLE_UPDATE"] = events["UPDATE_ALL_BUTTONS"];
421 events["PET_STABLE_SHOW"] = events["UPDATE_ALL_BUTTONS"];
422 events["PLAYER_SPECIALIZATION_CHANGED"] = events["UPDATE_ALL_BUTTONS"];
423 events["UNIT_ENTERED_VEHICLE"] = function(unit)
424     if unit == "player" then events["ACTIONBAR_UPDATE_STATE"]() end
425 end
426 events["UNIT_EXITED_VEHICLE"] = events["UNIT_ENTERED_VEHICLE"];
427 -- TODO COMPANION_UPDATE needed?
428 -- TODO overlay glow ? don't exactly know what it does, proc highlight?
429 -- autorepeat, tooltips
430
431 ActionBars:RegisterEvent("PLAYER_LOGIN");
432 ActionBars:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN");
433 ActionBars:RegisterEvent("ACTIONBAR_UPDATE_USABLE");
434 ActionBars:RegisterEvent("ACTIONBAR_UPDATE_STATE");
435 ActionBars:RegisterEvent("ACTIONBAR_SLOT_CHANGED");
436 ActionBars:RegisterEvent("ACTIONBAR_SHOWGRID");
437 ActionBars:RegisterEvent("ACTIONBAR_HIDEGRID");
438 ActionBars:RegisterEvent("SPELL_UPDATE_ICON");
439 ActionBars:RegisterEvent("SPELL_UPDATE_CHARGES");
440 ActionBars:RegisterEvent("UPDATE_VEHICLE_ACTIONBAR");
441 ActionBars:RegisterEvent("UPDATE_OVERRIDE_ACTIONBAR");
442 ActionBars:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED");
443 ActionBars:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
444 ActionBars:RegisterEvent("UNIT_ENTERED_VEHICLE");
445 ActionBars:RegisterEvent("UNIT_EXITED_VEHICLE");
446 ActionBars:RegisterEvent("PET_STABLE_UPDATE");
447 ActionBars:RegisterEvent("PET_STABLE_SHOW");
448 ActionBars:SetScript("OnEvent", function(self, event, arg1)
449     events[event](arg1);
450 end);