-- Lock down local environment. local PowerAuras = select(2, ...); setfenv(1, PowerAuras); --- Totem spell ID's by their slot in the bits. local TotemBitSlots = { -- Shaman totems. ["SHAMAN"] = { [1] = 120668, [2] = 98008, [3] = 2894, [4] = 108269, [5] = 2062, [6] = 16190, [7] = 8143, [8] = 8177, [9] = 8190, [10] = 5394, [11] = 2484, [12] = 3599, [13] = 51485, [14] = 108280, [15] = 108273, [16] = 108270, }, ["PALADIN"] = { }, ["DEATHKNIGHT"] = { }, ["DRUID"] = { [1] = 88747, [2] = 88747, [3] = 88747, }, ["MONK"] = { }, ["MAGE"] = { }, }; --- Copy of the above table, but mapping ID's to slots. local ReverseTotemBits = {}; for class, totems in pairs(TotemBitSlots) do -- Create reverse table. local new = {}; for slot, id in pairs(totems) do new[id] = bit.bor(new[id] or 0, 2^(slot - 1)); end -- Store. ReverseTotemBits[class] = new; end --- Table for the UI. Categorises totems by their ingame slot. local TotemsByUISlot = { ["SHAMAN"] = { [1] = { -- Fire totems. [1] = 3, [2] = 9, [3] = 12, }, [2] = { -- Earth totems. [1] = 5, [2] = 7, [3] = 11, [4] = 13, [5] = 16, }, [3] = { -- Water totems. [1] = 6, [2] = 10, [3] = 14, }, [4] = { -- Air totems. [1] = 1, [2] = 2, [3] = 4, [4] = 8, [5] = 15, }, -- [5] = { -- Heart totems. -- }, }, ["PALADIN"] = { }, ["DEATHKNIGHT"] = { }, ["DRUID"] = { [1] = { [1] = 1, }, [2] = { [1] = 2, }, [3] = { [1] = 3, }, }, ["MONK"] = { }, ["MAGE"] = { }, }; -- Sort the class totems out. for class, slots in pairs(TotemsByUISlot) do for _, indexes in pairs(slots) do table.sort(indexes, function(a, b) local name1 = GetSpellInfo(TotemBitSlots[class][a]); local name2 = GetSpellInfo(TotemBitSlots[class][b]); if(name1 and name2) then return name1 < name2; elseif(name1 and not name2) then return true; else return false; end end); end end --- Trigger class definition. local Totems = PowerAuras:RegisterTriggerClass("Totems", { --- Dictionary of default parameters this trigger uses. Parameters = { -- Slots are numbered. [1] = 0x00000000, [2] = 0x00000000, [3] = 0x00000000, [4] = 0x00000000, }, --- Dictionary of events this trigger responds to. Events = { PLAYER_TOTEM_UPDATE = "Totems", UPDATE_SHAPESHIFT_FORM = "Totems", -- Blizzard handles this for -- positioning, but it's included -- just in case. }, --- Dictionary of provider services required by this trigger type. Services = {}, --- Dictionary of supported trigger > service conversions. ServiceMirrors = { Stacks = "TriggerData", Text = "TriggerData", Texture = "TriggerData", Timer = "TriggerData", }, }); --- Constructs a new instance of the trigger and returns it. -- @param params The parameters to construct the trigger from. function Totems:New(params) -- Extract the parameters. Just do a raw copy and save us some work. local matches = PowerAuras:CopyTable(params); local _, class = UnitClass("player"); -- Generate the function. return function(self, buffer, action, store) -- Keep track of the one with the shortest duration. local earliest, time = 0, math.huge; -- Process the totems. for i = 1, MAX_TOTEMS do local match = matches[i]; -- Handle special matches. if(match ~= 0x20000000) then -- Must not be set to ignore this slot. if(match == 0x80000000 or match == 0x00000000) then -- Must have any totem in this slot. local state, _, start, duration = GetTotemInfo(i); if(not state and match ~= 0x00000000) then return false; elseif(state) then -- Will it finish sooner? if((start + duration) < time) then earliest = i; time = (start + duration); end end elseif(match == 0x40000000) then -- Must not have a totem in this slot. local state = GetTotemInfo(i); if(state) then return false; end else -- Handle normal matches. local state, name, start, duration, icon = GetTotemInfo(i); if(not state) then -- No totem in this slot. Expected one. return false; end -- Get the spell ID if possible. local id = PowerAuras.SpellIDLookup[name]; if(id == 0) then -- Manual scanning, this will be slow. for j = 1, #(TotemBitSlots[class]) do local slotID = TotemBitSlots[class][j]; -- Name check. local slotName = GetSpellInfo(slotID); if(slotName == name) then -- Match. id = slotID; break; end end -- Cache it manually. PowerAuras.SpellIDLookup[name] = id; end -- Got an ID now? if(id > 0) then -- Right, are we matching this totem? local offset = ReverseTotemBits[class][id] or 0; -- 2^0 = 0.5, which will never match anything. if(bit.band(match, offset) == 0) then -- We didn't hit a totem, and we required one. return false; else -- Will it finish sooner? if((start + duration) < time) then earliest = i; time = (start + duration); end end else -- Failed to figure out what the hell totem this is. return false; end end end end -- Congratulations, totems. Update source data. if(earliest > 0) then local _, name, start, duration, icon = GetTotemInfo(earliest); store.Text["name"] = name; store.Texture = icon; store.TimerStart = start; store.TimerEnd = (start + duration); else store.Text["name"] = ""; store.Texture = nil; store.TimerStart = nil; store.TimerEnd = nil; end return true; end; end --- Updates the match data for a totem slot. -- @param frame The frame that was updated. -- @param ... Arguments from the update callbacks. local function UpdateTotemSlot(frame, ...) -- Determine what we're updating. local _, aID, f1, tID, f2, slotID = PowerAuras:SplitNodeID(frame:GetID()); if(f1 == 0 and f2 == 0) then -- Totem dropdown. local key = ...; -- XOR the existing flags and update. local match = PowerAuras:GetParameter("Trigger", slotID, aID, tID); if(key > 0) then match = bit.bxor(match, 2^(key - 1)); match = bit.band(match, 0x0FFFFFFF); else -- Special key. if(key == -1) then match = 0x00000000; elseif(key == -2) then match = 0x80000000; elseif(key == -3) then match = 0x40000000; elseif(key == -4) then match = 0x20000000; end end PowerAuras:SetParameter("Trigger", slotID, match, aID, tID); end end --- Updates a UI frame with information about a totem slot. -- @param frame The frame to update. -- @param slot The slot index. local function UpdateSlotUI(frame, slot) -- Determine what we're updating. local _, aID, f1, tID, f2, slotID = PowerAuras:SplitNodeID(frame:GetID()); if(f1 == 0 and f2 == 0) then -- Totem dropdown. for i = 1, #(frame.ItemsByKey["__ROOT__"]) do local item = frame.ItemsByKey["__ROOT__"][i]; local key = item.Key; local checked = (bit.band(slot, 2^(key - 1)) > 0); frame:SetItemChecked(key, checked); if(checked) then frame:SetRawText(L["Multiple"]); end -- Is this a negative (special) key? if(key < 0) then if(key == -1 and slot == 0x00000000) then frame:SetItemChecked(key, true); frame:SetText(key); elseif(key == -2 and slot == 0x80000000) then frame:SetItemChecked(key, true); frame:SetText(key); elseif(key == -3 and slot == 0x40000000) then frame:SetItemChecked(key, true); frame:SetText(key); elseif(key == -4 and slot == 0x20000000) then frame:SetItemChecked(key, true); frame:SetText(key); end end end end end --- Creates the controls for the basic activation editor frame. -- @param frame The frame to apply controls to. -- @param ... ID's to use for Get/SetParameter calls. function Totems:CreateTriggerEditor(frame, ...) -- Does this class have totems or not? local _, class = UnitClass("player"); local slots = TotemsByUISlot[class]; if(not slots or #(slots) == 0) then -- Just add a label. local l = PowerAuras:Create("Label", frame); l:SetText(L["ClassNoTotems"]); l:SetRelativeWidth(1.0); l:SetHeight(36); l:SetJustifyH("CENTER"); l:SetJustifyV("MIDDLE"); frame:AddWidget(l); return; end -- Get the parameter ID's. local aID, tID = ...; -- Add editor controls for each row. for i = 1, MAX_TOTEMS do -- Add a dropdown for this slot. local totems = PowerAuras:Create("SimpleDropdown", frame); local isEven = ((i % 2) == 0); totems:SetUserTooltip("Totems_Totem"); totems:SetPadding((isEven and 2 or 4), 0, (isEven and 4 or 2), 0); totems:SetRelativeWidth(0.5); totems:SetID(PowerAuras:GetNodeID(nil, aID, 0, tID, 0, i)); totems:SetTitle(L("SlotID", i)); -- Add the totems for this class. local added = false; if(slots[i]) then added = true; -- Add the special options. totems:AddCheckItem(-1, L["AnyNone"]); totems:AddCheckItem(-2, L["Any"]); totems:AddCheckItem(-3, L["None"]); totems:AddCheckItem(-4, L["Ignore"]); -- Add totems for this class. local indexes = slots[i]; for _, index in ipairs(indexes) do local name = GetSpellInfo(TotemBitSlots[class][index]); totems:AddCheckItem(index, tostring(name), nil, true); end end -- Did we add totems? if(added) then -- Callbacks, add widget to frame. totems.OnValueUpdated:Connect(UpdateTotemSlot); totems:ConnectParameter("Trigger", i, UpdateSlotUI, ...); UpdateSlotUI(totems, PowerAuras:GetParameter("Trigger", i, ...)); frame:AddWidget(totems); -- Add separation row. if(isEven) then frame:AddRow(4); end else totems:Recycle(); break; end end end --- Initialises the per-trigger data store. This can be used to hold -- values which can then be exposed to other parts of the system. -- @param params The parameters of this trigger. -- @return An item to store, or nil. function Totems:InitialiseDataStore() return { TimerStart = 0, TimerEnd = 2^31 - 1, Texture = PowerAuras.DefaultIcon, Text = { ["name"] = "", }, }; end --- Upgrades the trigger from the specified version to the current version. -- @param version The version to upgrade from. -- @param params The trigger parameters to upgrade. function Totems:Upgrade(version, params) end