9ab3f0e - Add Demolished debuff
[wowui.git] / OmaAB / ActionBars.lua
1 -- ActionBars.lua
2 local _;
3 local pairs = pairs;
4 local format = string.format;
5 local ssub = string.sub;
6 local GetActionInfo, GetActionTexture = GetActionInfo, GetActionTexture;
7 local GetActionLossOfControlCooldown = GetActionLossOfControlCooldown;
8 local GetActionCooldown, GetActionCharges = GetActionCooldown, GetActionCharges;
9 local GetActionText, GetBindingKey = GetActionText, GetBindingKey;
10 local IsConsumableAction, IsStackableAction = IsConsumableAction, IsStackableAction;
11 local IsItemAction, GetActionCount = IsItemAction, GetActionCount;
12 local IsSpellOverlayed, GetMacroSpell = IsSpellOverlayed, GetMacroSpell;
13 local IsMounted = IsMounted;
14 local HasAction, IsUsableAction = HasAction, IsUsableAction;
15 local IsCurrentAction, IsAutoRepeatAction = IsCurrentAction, IsAutoRepeatAction;
16 local CreateFrame = CreateFrame;
17 local RegisterStateDriver = RegisterStateDriver;
18 local CooldownFrame_Set, CooldownFrame_Clear = CooldownFrame_Set, CooldownFrame_Clear;
19 local GameTooltip = GameTooltip;
20 local GameTooltip_SetDefaultAnchor = GameTooltip_SetDefaultAnchor;
21 local COOLDOWN_TYPE_LOSS_OF_CONTROL = COOLDOWN_TYPE_LOSS_OF_CONTROL;
22 local COOLDOWN_TYPE_NORMAL = COOLDOWN_TYPE_NORMAL;
23 local CDTexture = "Interface\\Cooldown\\edge";
24 local locCDTexture = "Interface\\Cooldown\\edge-LoC";
25
26 local BUTTONLOCK = true; -- change to lock button dragging
27
28 local settings = {
29     ["Oma1"] = {
30         bar = 1,
31         start = 1,
32         length = 12,
33         columns = 4,
34         size = 40,
35         x = 580,
36         y = 300,
37         nomouse = true,
38     },
39     ["Oma2"] = {
40         bar = 2,
41         start = 13,
42         length = 12,
43         columns = 4,
44         size = 40,
45         x = 580,
46         y = 180,
47         nomouse = true,
48     },
49     ["Oma3"] = {
50         bar = 3,
51         start = 25,
52         length = 12,
53         columns = 3,
54         x = 1824,
55         y = 128,
56         flyout = "LEFT",
57     },
58     ["Oma4"] = {
59         bar = 4,
60         start = 37,
61         length = 12,
62         columns = 3,
63         x = 1824,
64         y = 256,
65     },
66     ["Oma5"] = {
67         bar = 5,
68         start = 49,
69         length = 12,
70         columns = 3,
71         x = 1000,
72         y = 840,
73     },
74     ["Oma6"] = {
75         bar = 6,
76         start = 61,
77         length = 12,
78         x = 1000,
79         y = 600,
80     },
81     -- used as bonus bars for some classes
82     ["Oma7"] = {
83         bar = 7,
84         start = 73,
85         length = 12,
86         x = 1000,
87         y = 760,
88     },
89     ["Oma8"] = {
90         bar = 8,
91         start = 85,
92         length = 12,
93         x = 1000,
94         y = 720,
95     },
96     ["Oma9"] = {
97         bar = 9,
98         start = 97,
99         length = 12,
100         x = 1000,
101         y = 680,
102     },
103     ["Oma10"] = {
104         bar = 10,
105         start = 109,
106         length = 12,
107         x = 1000,
108         y = 640,
109     },
110 };
111
112 local usingBonusbars = {
113     --["WARRIOR"] = {[7]=true, [8]=true, [9]=true}, -- not using stance separated actionbars
114     ["DRUID"] = {[7]=true, [8]=true, [9]=true}, -- moonkin form page is usable anyway
115     --["DRUID"] = {[7]=true, [8]=true, [9]=true,[10]=true},
116     ["ROGUE"] = {[7]=true},
117     --["PRIEST"] = {[7]=true}, -- shadowform doesn't change abilities
118 };
119
120 local chars = {
121     ["Stormreaver"] = {
122         ["Vildan"] = {1, 2, 3, 4,},
123         ["Gedren"] = {1, 2, 3, 4,},
124         ["Gazden"] = {1, 2, 3, 4,},
125         ["Gedran"] = {1, 2, 3, 4,},
126         ["Iled"] = {1, 2, 3, 4,},
127     },
128 };
129
130 local buttons = {};
131 local activeButtons = {};
132
133 local ActionBars = CreateFrame("Frame", "OmaActionBars", UIParent);
134 local inheritedFrames =
135 "SecureActionButtonTemplate,SecureHandlerDragTemplate,SecureHandlerStateTemplate";
136
137 local function showTooltip(secure)
138     GameTooltip_SetDefaultAnchor(GameTooltip, secure);
139     GameTooltip:SetAction(secure:GetAttribute("action"));
140 end
141
142 local function hideTooltip()
143     GameTooltip:Hide();
144 end
145
146 local numChargeCDs = 0;
147 local function createChargeCD(parent)
148     numChargeCDs = numChargeCDs + 1;
149     local frame = CreateFrame("Cooldown", "OmaChargeCD"..numChargeCDs, parent, "CooldownFrameTemplate");
150     frame:SetHideCountdownNumbers(false);
151     frame:SetDrawSwipe(false);
152     frame:SetAllPoints(parent);
153     frame:SetFrameStrata("TOOLTIP");
154     return frame;
155 end
156
157 local function clearChargeCD(parent)
158     if parent.chargecd then CooldownFrame_Clear(parent.chargecd) end
159 end
160
161 local function startChargeCD(parent, start, duration, modrate)
162     if start == 0 then
163         return clearChargeCD(parent);
164     end
165     parent.chargecd = parent.chargecd or createChargeCD(parent);
166     CooldownFrame_Set(parent.chargecd, start, duration, true, true, modrate);
167 end
168
169 local redoCooldown;
170
171 local function updateCooldown(button, slot)
172     -- CD update from FrameXML/ActionButton.lua
173     local locstart, locduration = GetActionLossOfControlCooldown(slot);
174     local start, duration, enable, modrate = GetActionCooldown(slot);
175     local charges, maxcharges, chargestart, chargeduration, chargemodrate = GetActionCharges(slot);
176     -- avoid as many updates as possible by checking if there's changes first
177     if button.prev and
178        button.prev[1] == locstart    and button.prev[2] == locduration and
179        button.prev[3] == start       and button.prev[4] == duration and
180        button.prev[5] == enable      and button.prev[6] == modrate and
181        button.prev[7] == charges     and button.prev[8] == maxcharges and
182        button.prev[9] == chargestart and button.prev[10] == chargeduration and
183        button.prev[11] == chargemodrate then
184         return;
185     end
186     button.prev = { locstart, locduration, start, duration, enable, modrate,
187         charges, maxcharges, chargestart, chargeduration, chargemodrate };
188     if (locstart + locduration) > (start + duration) then
189         if button.cd.currentCooldownType ~= COOLDOWN_TYPE_LOSS_OF_CONTROL then
190             button.cd:SetEdgeTexture(locCDTexture);
191             button.cd:SetSwipeColor(0.17, 0, 0);
192             button.cd:SetHideCountdownNumbers(true);
193             button.cd.currentCooldownType = COOLDOWN_TYPE_LOSS_OF_CONTROL;
194         end
195
196         CooldownFrame_Set(button.cd, locstart, locduration, true, true, modrate);
197         clearChargeCD(button);
198     else
199         if button.cd.currentCooldownType ~= COOLDOWN_TYPE_NORMAL then
200             button.cd:SetEdgeTexture(CDTexture);
201             button.cd:SetSwipeColor(0, 0, 0);
202             button.cd:SetHideCountdownNumbers(false);
203             button.cd.currentCooldownType = COOLDOWN_TYPE_NORMAL;
204         end
205
206         if locstart > 0 then
207             button.cd:SetScript("OnCooldownDone", redoCooldown);
208         end
209         if charges and maxcharges and maxcharges > 1 and charges < maxcharges then
210             startChargeCD(button, chargestart, chargeduration, chargemodrate);
211         else
212             clearChargeCD(button);
213         end
214         CooldownFrame_Set(button.cd, start, duration, enable, false, modrate);
215     end
216 end
217
218 local function redoCooldown(cd)
219     local button = cd:GetParent();
220     cd:SetScript("OnCooldownDone", nil);
221     updateCooldown(button, button.slot);
222 end
223
224 local function updateCount(button, slot)
225     if IsConsumableAction(slot) or IsStackableAction(slot) or
226             (not IsItemAction(slot) and GetActionCount(slot) > 0) then
227         local count = GetActionCount(slot);
228         if count > 99 then
229             button.count:SetText("*");
230         else
231             button.count:SetText(count);
232         end
233         button.count:Show();
234     else
235         local charges, maxcharges = GetActionCharges(slot);
236         if maxcharges > 1 then
237             button.count:SetText(charges);
238             button.count:Show();
239         else
240             button.count:Hide();
241         end
242     end
243 end
244
245 local function updateUsable(button, slot)
246     local isUsable, noMana = IsUsableAction(slot);
247     if isUsable then
248         button.icon:SetVertexColor(1, 1, 1);
249     elseif noMana then
250         button.icon:SetVertexColor(0, 0.5, 1);
251     else
252         button.icon:SetVertexColor(0.4, 0.4, 0.4);
253     end
254 end
255
256 local function updateState(button, slot)
257     button:SetChecked(IsCurrentAction(slot) or IsAutoRepeatAction(slot));
258 end
259
260 local function updateGlow(button, slot)
261     local stype, id, _ = GetActionInfo(slot);
262     if stype == "spell" and IsSpellOverlayed(id) then
263         button.glow:Show();
264     elseif stype == "macro" then
265         local _, _, macroid = GetMacroSpell(id);
266         if macroid and IsSpellOverlayed(macroid) then
267             button.glow:Show();
268         else
269             button.glow:Hide();
270         end
271     else -- TODO FlyoutHasSpell glow
272         button.glow:Hide();
273     end
274 end
275
276 local function startGlow(button, slot, spell)
277     local stype, id, _ = GetActionInfo(slot);
278     if stype == "spell" and id == spell then
279         button.glow:Show();
280     elseif stype == "macro" then
281         local _, _, macroid = GetMacroSpell(id);
282         if macroid and macroid == spell then
283             button.glow:Show();
284         end
285     end
286     -- TODO FlyoutHasSpell glow
287 end
288
289 local function stopGlow(button, slot, spell)
290     local stype, id, _ = GetActionInfo(slot);
291     if stype == "spell" and id == spell then
292         button.glow:Hide();
293     elseif stype == "macro" then
294         local _, _, macroid = GetMacroSpell(id);
295         if macroid and macroid == spell then
296             button.glow:Hide();
297         end
298     end
299     -- TODO FlyoutHasSpell glow
300 end
301
302 local function updateButton(button, slot)
303     if HasAction(slot) then
304         activeButtons[slot] = button;
305         button.base:Show();
306         button.icon:SetTexture(GetActionTexture(slot));
307         updateCooldown(button, slot);
308         updateUsable(button, slot);
309         updateState(button, slot);
310         updateCount(button, slot);
311         updateGlow(button, slot);
312         if not IsConsumableAction(slot) and not IsStackableAction(slot) then
313             button.text:SetText(ssub(GetActionText(slot) or "", 1, 4));
314             button.text:Show();
315         end
316         if button.hotkey.shown then button.hotkey:Show() end
317     else
318         activeButtons[slot] = nil;
319         if not button.grid then button.base:Hide() end
320         button.icon:SetTexture(nil);
321         button.cd:Hide();
322         button.count:Hide();
323         button.hotkey:Hide();
324         button.text:Hide();
325         button.glow:Hide();
326         button:SetChecked(false);
327     end
328 end
329
330 local function updateHotkeys(button)
331     local key = GetBindingKey(format("CLICK %s:LeftButton", button:GetName()));
332     if key and key ~= "" then
333         -- from LibKeyBound-1.0
334         key = key:upper();
335         key = key:gsub(" ", "");
336         key = key:gsub("ALT%-", "a");
337         key = key:gsub("CTRL%-", "c");
338         key = key:gsub("SHIFT%-", "s");
339         key = key:gsub("NUMPAD", "n");
340         button.hotkey:SetText(key);
341         button.hotkey.shown = true;
342         button.hotkey:Show();
343     else
344         button.hotkey.shown = nil;
345         button.hotkey:Hide();
346     end
347 end
348
349 local mainbartoggle = "[overridebar][possessbar][shapeshift]possess;";
350 mainbartoggle = mainbartoggle.."[bonusbar:1,stealth:1]bonusbar2;"; -- prowl
351 mainbartoggle = mainbartoggle.."[bonusbar:1]bonusbar1;[bonusbar:2]bonusbar2;"; -- cat form, unused
352 mainbartoggle = mainbartoggle.."[bonusbar:3]bonusbar3;[bonusbar:4]bonusbar4;"; -- bear form, moonkin form
353 mainbartoggle = mainbartoggle.."normal";
354 local function setupSnippets(secure, slot)
355     -- FrameXML/SecureHandlers.lua has arguments and return value
356     -- args: self, button, kind, value, ... (kind, value, ... from GetCursorInfo())
357     -- returns: kind, target, detail
358     -- or: "clear", kind, target, detail
359     -- used for Pickup* functions
360     -- some of these snippets based on LibActionButton-1.0
361     secure:SetAttribute("_ondragstart", [=[
362         return "action", self:GetAttribute("action");
363     ]=]);
364     secure:SetAttribute("_onreceivedrag", [=[
365         if not kind or not value then return nil end
366         return "action", self:GetAttribute("action");
367     ]=]);
368     -- pre-wrapper can pass a message to post-wrapper
369     secure:WrapScript(secure, "OnDragStart", [=[
370         local kind, value = GetActionInfo(self:GetAttribute("action"));
371         return "message", format("%s|%s", tostring(kind), tostring(value));
372     ]=], [=[
373         local kind, value = GetActionInfo(self:GetAttribute("action"));
374         if message ~= format("%s|%s", tostring(kind), tostring(value)) then
375             self:CallMethod("ActionChanged");
376         end
377     ]=]);
378     secure:WrapScript(secure, "OnReceiveDrag", [=[
379         local kind, value = GetActionInfo(self:GetAttribute("action"));
380         return "message", format("%s|%s", tostring(kind), tostring(value));
381     ]=], [=[
382         local kind, value = GetActionInfo(self:GetAttribute("action"));
383         if message ~= format("%s|%s", tostring(kind), tostring(value)) then
384             self:CallMethod("ActionChanged");
385         end
386     ]=]);
387     function secure:UpdateState()
388         return updateState(self, self.slot);
389     end
390     secure:WrapScript(secure, "OnClick", [=[
391         local kind, value = GetActionInfo(self:GetAttribute("action"));
392         return nil, format("%s|%s", tostring(kind), tostring(value));
393     ]=], [=[
394         local kind, value = GetActionInfo(self:GetAttribute("action"));
395         if message ~= format("%s|%s", tostring(kind), tostring(value)) then
396             self:CallMethod("ActionChanged");
397         else
398             self:CallMethod("UpdateState");
399         end
400     ]=]);
401     if slot < 13 then
402         -- first action bar has possible states based on vehicle/possess etc.
403         secure:SetAttribute("origaction", slot);
404         secure:SetAttribute("_onstate-possess", [=[
405             local oldslot = self:GetAttribute("action");
406             if newstate == "possess" then
407                 local slot;
408                 if HasVehicleActionBar() then
409                     slot = (GetVehicleBarIndex()-1)*12+self:GetAttribute("origaction");
410                 elseif HasOverrideActionBar() then
411                     slot = (GetOverrideBarIndex()-1)*12+self:GetAttribute("origaction");
412                 elseif HasTempShapeshiftActionBar() then
413                     slot = (GetTempShapeshiftBarIndex()-1)*12+self:GetAttribute("origaction");
414                 else
415                     -- something wrong, just revert to normal
416                     print("Possess bar index not found");
417                     slot = self:GetAttribute("origaction");
418                 end
419                 self:SetAttribute("action", slot);
420             elseif newstate == "bonusbar1" then
421                 self:SetAttribute("action", 72+self:GetAttribute("origaction"));
422             elseif newstate == "bonusbar2" then
423                 self:SetAttribute("action", 84+self:GetAttribute("origaction"));
424             elseif newstate == "bonusbar3" then
425                 self:SetAttribute("action", 96+self:GetAttribute("origaction"));
426             elseif newstate == "bonusbar4" then
427                 --self:SetAttribute("action", 108+self:GetAttribute("origaction"));
428                 -- moonkin form, don't change actionbar
429                 self:SetAttribute("action", self:GetAttribute("origaction"));
430             else
431                 self:SetAttribute("action", self:GetAttribute("origaction"));
432             end
433             self:CallMethod("ActionChanged", oldslot);
434         ]=]);
435         RegisterStateDriver(secure, "possess", mainbartoggle);
436     else
437         function secure:ShowButton() if HasAction(slot) then activeButtons[slot] = self end end
438         function secure:HideButton() activeButtons[slot] = nil end
439         -- all other action bar are hidden if with overridebar or vehicleui (not shapeshift, possessbar)
440         -- default Bartender4 options
441         secure:SetAttribute("_onstate-possess", [=[
442             if newstate == "possess" then
443                 self:Hide();
444                 self:CallMethod("HideButton");
445             else
446                 self:Show();
447                 self:CallMethod("ShowButton");
448             end
449         ]=]);
450         RegisterStateDriver(secure, "possess", "[overridebar][vehicleui] possess; normal");
451     end
452 end
453
454 local function createActionBar(parent, config)
455     local prev;
456     local i = 0;
457     local bar = CreateFrame("Frame", "OmaBTBar"..config.bar, parent, "SecureFrameTemplate");
458     bar:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", config.x, config.y);
459     bar:SetWidth(1);
460     bar:SetHeight(1);
461     if config.hidden then
462         bar:Hide();
463     end
464     for slot = config.start, config.start+config.length-1 do
465         local secure = CreateFrame("CheckButton", "OmaBT"..slot, bar, inheritedFrames);
466         secure.slot = slot;
467         if slot == config.start then
468             secure:SetPoint("TOPLEFT");
469         elseif config.columns and i % config.columns == 0 then
470             secure:SetPoint("TOPLEFT", _G["OmaBT"..(slot-config.columns)], "BOTTOMLEFT");
471         else
472             secure:SetPoint("TOPLEFT", prev, "TOPRIGHT");
473         end
474         secure:RegisterForClicks("AnyUp");
475         if not BUTTONLOCK then
476             secure:RegisterForDrag("LeftButton", "RightButton");
477         end
478         if config.nomouse then
479             secure:EnableMouse(false);
480         else
481             -- only show tooltips for bars with mouse interaction
482             secure:SetScript("OnEnter", showTooltip);
483             secure:SetScript("OnLeave", hideTooltip);
484         end
485         secure:SetWidth(config.size or 32);
486         secure:SetHeight(config.size or 32);
487         secure.base = secure:CreateTexture(nil, "BACKGROUND");
488         secure.base:SetAllPoints();
489         secure.base:SetColorTexture(0, 0, 0, 0.5);
490         secure.iconbase = secure:CreateTexture(nil, "BORDER");
491         secure.iconbase:SetPoint("TOPLEFT", secure.base, "TOPLEFT", 1, -1);
492         secure.iconbase:SetPoint("BOTTOMRIGHT", secure.base, "BOTTOMRIGHT", -1, 1);
493         secure.iconbase:SetColorTexture(0, 0, 0, 0.5);
494         secure.iconbase:Hide();
495         secure.icon = secure:CreateTexture(nil, "ARTWORK");
496         secure.icon:SetPoint("TOPLEFT", secure.iconbase, "TOPLEFT");
497         secure.icon:SetPoint("BOTTOMRIGHT", secure.iconbase, "BOTTOMRIGHT");
498         secure.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93);
499         secure:SetCheckedTexture("Interface\\Buttons\\CheckButtonHilight");
500         secure.autocastable = secure:CreateTexture(nil, "OVERLAY");
501         secure.autocastable:SetPoint("CENTER");
502         secure.autocastable:SetWidth(58);
503         secure.autocastable:SetHeight(58);
504         secure.autocastable:SetTexture("Interface\\Buttons\\UI-AutoCastableOverlay");
505         secure.autocastable:Hide();
506         secure.glow = secure:CreateTexture(nil, "OVERLAY", nil, 1);
507         secure.glow:SetPoint("CENTER");
508         secure.glow:SetWidth(config.size and config.size+26 or 53);
509         secure.glow:SetHeight(config.size and config.size+26 or 53);
510         secure.glow:SetTexture("Interface\\SpellActivationOverlay\\IconAlert");
511         secure.glow:SetTexCoord(0.00781250, 0.50781250, 0.27734375, 0.52634375);
512         secure.glow:Hide();
513         secure.hotkey = secure:CreateFontString(nil, "OVERLAY", "NumberFontNormalGray");
514         secure.hotkey:SetPoint("TOPRIGHT", secure, "TOPRIGHT", 2, -1);
515         secure.count = secure:CreateFontString(nil, "OVERLAY", "NumberFontNormal");
516         secure.count:SetPoint("BOTTOMRIGHT", secure, "BOTTOMRIGHT", 2, -1);
517         secure.text = secure:CreateFontString(nil, "OVERLAY", "NumberFontNormal");
518         secure.text:SetPoint("BOTTOMLEFT", secure, "BOTTOMLEFT", 2, -1);
519         secure.text:Hide();
520         secure.cd = CreateFrame("Cooldown", "OmaBTCD"..slot, secure, "CooldownFrameTemplate");
521         secure.cd:SetAllPoints();
522         secure:SetAttribute("type", "action");
523         secure:SetAttribute("action", slot);
524         if config.flyout then
525             secure:SetAttribute("flyoutDirection", config.flyout);
526         end
527         function secure:ActionChanged(oldslot)
528             if oldslot then
529                 activeButtons[oldslot] = nil;
530                 self.prev = nil; -- invalidate previous CD when slot changes
531             end
532             self.slot = self:GetAttribute("action");
533             return updateButton(self, self.slot);
534         end
535         secure:ActionChanged(); -- initial update
536         setupSnippets(secure, slot);
537         updateHotkeys(secure);
538         buttons[slot] = secure;
539         prev = secure;
540         i = i + 1;
541     end
542 end
543
544 local function initialize()
545     local _, class = UnitClass("player");
546     local name, realm = UnitFullName("player");
547     ActionBars:SetFrameStrata("LOW");
548     ActionBars:SetPoint("BOTTOMLEFT");
549     ActionBars:SetWidth(1);
550     ActionBars:SetHeight(1);
551     for _, config in pairs(settings) do
552         if (not usingBonusbars[class] or not usingBonusbars[class][config.bar]) and
553            (not chars[realm] or not chars[realm][name] or chars[realm][name][config.bar]) then
554             createActionBar(ActionBars, config);
555         end
556     end
557 end
558
559 local function setupBindings()
560     _G["BINDING_HEADER_OmaAB"] = "Oma Action Bar";
561     for i = 1,10 do
562         _G["BINDING_HEADER_OMAABBLANK"..i] = "Bar "..i;
563         for j = 1,12 do
564             _G[format("BINDING_NAME_CLICK OmaBT%d:LeftButton", (i-1)*12+j)] = format("Bar %d Button %d", i, j);
565         end
566     end
567 end
568
569 local mounted = false;
570
571 local events = {
572     ["ACTIONBAR_UPDATE_COOLDOWN"] = function()
573         for _, button in pairs(activeButtons) do
574             updateCooldown(button, button.slot);
575         end
576     end,
577     ["SPELL_UPDATE_CHARGES"] = function()
578         for _, button in pairs(activeButtons) do
579             updateCount(button, button.slot);
580         end
581     end,
582     ["ACTIONBAR_SLOT_CHANGED"] = function(slot)
583         if buttons[slot] then buttons[slot]:ActionChanged() end
584     end,
585     ["ACTIONBAR_SHOWGRID"] = function()
586         for _, button in pairs(buttons) do
587             button.grid = true;
588             button.iconbase:Show();
589             if not activeButtons[button.slot] then button.base:Show() end
590         end
591     end,
592     ["ACTIONBAR_HIDEGRID"] = function()
593         for _, button in pairs(buttons) do
594             button.grid = nil;
595             button.iconbase:Hide();
596             if not activeButtons[button.slot] then button.base:Hide() end
597         end
598     end,
599     ["ACTIONBAR_UPDATE_STATE"] = function()
600         for _, button in pairs(activeButtons) do
601             updateState(button, button.slot);
602         end
603     end,
604     ["ACTIONBAR_UPDATE_USABLE"] = function()
605         for _, button in pairs(activeButtons) do
606             updateUsable(button, button.slot);
607         end
608     end,
609     ["UPDATE_OVERRIDE_ACTIONBAR"] = function()
610         if buttons[1] then -- called before PLAYER_LOGIN
611             for _, button in pairs(buttons) do
612                 updateButton(button, button.slot);
613             end
614         end
615     end,
616     ["START_AUTOREPEAT_SPELL"] = function()
617         for _, button in pairs(activeButtons) do
618             if IsAutoRepeatAction(button.slot) then
619                 button.autorepeating = true;
620                 button.icon:SetVertexColor(0, 1, 0.5);
621             end
622         end
623     end,
624     ["STOP_AUTOREPEAT_SPELL"] = function()
625         for _, button in pairs(activeButtons) do
626             if button.autorepeating then
627                 button.autorepeating = nil;
628                 updateUsable(button, button.slot);
629             end
630         end
631     end,
632     ["SPELL_ACTIVATION_OVERLAY_GLOW_SHOW"] = function(spell)
633         -- TODO create mapping from spellIDs to buttons
634         for _, button in pairs(activeButtons) do
635             startGlow(button, button.slot, spell);
636         end
637     end,
638     ["SPELL_ACTIVATION_OVERLAY_GLOW_HIDE"] = function(spell)
639         -- TODO create mapping from spellIDs to buttons
640         for _, button in pairs(activeButtons) do
641             stopGlow(button, button.slot, spell);
642         end
643     end,
644     ["UPDATE_BINDINGS"] = function()
645         for _, button in pairs(buttons) do
646             updateHotkeys(button);
647         end
648     end,
649     ["UNIT_AURA"] = function(unit)
650         -- using UNIT_AURA instead of COMPANION_UPDATE to not update every time
651         -- someone mounts, tracking player mount status with COMPANION_UPDATE is
652         -- inconsistent
653         if (not mounted and IsMounted()) or (mounted and not IsMounted()) then
654             mounted = not mounted;
655             for _, button in pairs(activeButtons) do
656                 updateState(button, button.slot);
657             end
658         end
659     end,
660     ["UPDATE_ALL_BUTTONS"] = function()
661         for _, button in pairs(buttons) do
662             updateButton(button, button.slot);
663         end
664     end,
665     ["PLAYER_LOGIN"] = function()
666         initialize();
667     end,
668     ["ADDON_LOADED"] = function(addon)
669         if addon == "OmaAB" then
670             setupBindings();
671             ActionBars:UnregisterEvent("ADDON_LOADED");
672         end
673     end,
674 };
675 events["LOSS_OF_CONTROL_ADDED"] = events["ACTIONBAR_UPDATE_COOLDOWN"];
676 events["LOSS_OF_CONTROL_UPDATE"] = events["ACTIONBAR_UPDATE_COOLDOWN"]; -- TODO might change once tooltips are in
677 events["PLAYER_MOUNT_DISPLAY_CHANGED"] = events["ACTIONBAR_UPDATE_USABLE"];
678 events["TRADE_SKILL_SHOW"] = events["ACTIONBAR_UPDATE_STATE"];
679 events["TRADE_SKILL_CLOSE"] = events["ACTIONBAR_UPDATE_STATE"];
680 events["ARCHAEOLOGY_CLOSED"] = events["ACTIONBAR_UPDATE_STATE"];
681 events["PLAYER_ENTERING_WORLD"] = events["UPDATE_ALL_BUTTONS"];
682 events["UPDATE_VEHICLE_ACTIONBAR"] = events["UPDATE_ALL_BUTTONS"];
683 events["UPDATE_SHAPESHIFT_FORM"] = events["UPDATE_ALL_BUTTONS"];
684 events["SPELL_UPDATE_ICON"] = events["UPDATE_ALL_BUTTONS"];
685 events["PET_STABLE_UPDATE"] = events["UPDATE_ALL_BUTTONS"];
686 events["PET_STABLE_SHOW"] = events["UPDATE_ALL_BUTTONS"];
687 events["PLAYER_SPECIALIZATION_CHANGED"] = events["UPDATE_ALL_BUTTONS"];
688 events["UNIT_ENTERED_VEHICLE"] = function(unit)
689     if unit == "player" then events["ACTIONBAR_UPDATE_STATE"]() end
690 end
691 events["UNIT_EXITED_VEHICLE"] = events["UNIT_ENTERED_VEHICLE"];
692
693 ActionBars:RegisterEvent("ADDON_LOADED");
694 ActionBars:RegisterEvent("PLAYER_LOGIN");
695 ActionBars:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN");
696 ActionBars:RegisterEvent("ACTIONBAR_UPDATE_USABLE");
697 ActionBars:RegisterEvent("ACTIONBAR_UPDATE_STATE");
698 ActionBars:RegisterEvent("ACTIONBAR_SLOT_CHANGED");
699 ActionBars:RegisterEvent("ACTIONBAR_SHOWGRID");
700 ActionBars:RegisterEvent("ACTIONBAR_HIDEGRID");
701 ActionBars:RegisterEvent("SPELL_UPDATE_ICON");
702 ActionBars:RegisterEvent("SPELL_UPDATE_CHARGES");
703 ActionBars:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_SHOW");
704 ActionBars:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_HIDE");
705 ActionBars:RegisterEvent("UPDATE_VEHICLE_ACTIONBAR");
706 ActionBars:RegisterEvent("UPDATE_OVERRIDE_ACTIONBAR");
707 ActionBars:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED");
708 ActionBars:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
709 ActionBars:RegisterEvent("UNIT_ENTERED_VEHICLE");
710 ActionBars:RegisterEvent("UNIT_EXITED_VEHICLE");
711 ActionBars:RegisterEvent("PET_STABLE_UPDATE");
712 ActionBars:RegisterEvent("PET_STABLE_SHOW");
713 ActionBars:RegisterEvent("UPDATE_BINDINGS");
714 ActionBars:RegisterUnitEvent("UNIT_AURA", "player");
715 ActionBars:SetScript("OnEvent", function(self, event, arg1)
716     events[event](arg1);
717 end);