From f3ce4a0f98a5a200f1d96ddba9ff720640ac553a Mon Sep 17 00:00:00 2001 From: Crum Date: Sun, 3 Jun 2018 16:54:55 -0500 Subject: [PATCH 1/6] fix typo returning nil in auras --- ElvUI/Modules/UnitFrames/Elements/Auras.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ElvUI/Modules/UnitFrames/Elements/Auras.lua b/ElvUI/Modules/UnitFrames/Elements/Auras.lua index c2bfa9f..15cea48 100644 --- a/ElvUI/Modules/UnitFrames/Elements/Auras.lua +++ b/ElvUI/Modules/UnitFrames/Elements/Auras.lua @@ -487,10 +487,10 @@ function UF:UpdateDebuffsHeaderPosition() if numBuffs == 0 then debuffs:ClearAllPoints() - E:Point(debuff, sbuffs.point, buffs.attachTo, buffs.anchorPoint, buffs.xOffset, buffs.yOffset) + E:Point(debuffs, buffs.point, buffs.attachTo, buffs.anchorPoint, buffs.xOffset, buffs.yOffset) else debuffs:ClearAllPoints() - E:Point(debuff, sdebuffs.point, debuffs.attachTo, debuffs.anchorPoint, debuffs.xOffset, debuffs.yOffset) + E:Point(debuffs, debuffs.point, debuffs.attachTo, debuffs.anchorPoint, debuffs.xOffset, debuffs.yOffset) end end From 2222abe39c484860d4ecd2cd3aad54354f87a2d1 Mon Sep 17 00:00:00 2001 From: Logan Payton Date: Wed, 6 Jun 2018 17:27:16 -0400 Subject: [PATCH 2/6] Fix tooltip (debuff was showing buff tooltip) --- ElvUI/Libraries/oUF/elements/auras.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ElvUI/Libraries/oUF/elements/auras.lua b/ElvUI/Libraries/oUF/elements/auras.lua index 0eccaf8..9084dca 100644 --- a/ElvUI/Libraries/oUF/elements/auras.lua +++ b/ElvUI/Libraries/oUF/elements/auras.lua @@ -80,7 +80,8 @@ local HIDDEN = 0 local function UpdateTooltip(self) if self:GetParent().__owner.unit == "player" then - GameTooltip:SetPlayerBuff(self:GetID() - 1) + local index = GetPlayerBuff(self:GetID() - 1, self.filter) + GameTooltip:SetPlayerBuff(index) elseif self.filter == 'HELPFUL' then GameTooltip:SetUnitBuff(self:GetParent().__owner.unit, self:GetID(), self.filter) else From 265b0559f88a98defd4d291423fc44d8d4a779f7 Mon Sep 17 00:00:00 2001 From: Crum Date: Wed, 6 Jun 2018 23:42:24 -0500 Subject: [PATCH 3/6] move GetInventoryItemDurability function to global for future plugin use --- !Compatibility/api/wowAPI.lua | 22 ++++++++++++++++++++++ ElvUI/Modules/DataTexts/Durability.lua | 23 +---------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/!Compatibility/api/wowAPI.lua b/!Compatibility/api/wowAPI.lua index 2a853cf..27d8e84 100644 --- a/!Compatibility/api/wowAPI.lua +++ b/!Compatibility/api/wowAPI.lua @@ -10,6 +10,7 @@ local unpack = unpack local find, format, gsub, lower, match, upper = string.find, string.format, string.gsub, string.lower, string.match, string.upper local getn = table.getn --WoW API +local GetInventoryItemTexture = GetInventoryItemTexture local GetItemInfo = GetItemInfo local GetQuestGreenRange = GetQuestGreenRange local GetRealZoneText = GetRealZoneText @@ -21,6 +22,7 @@ local UnitLevel = UnitLevel local DUNGEON_DIFFICULTY1 = DUNGEON_DIFFICULTY1 local TIMEMANAGER_AM = gsub(TIME_TWELVEHOURAM, "^.-(%w+)$", "%1") local TIMEMANAGER_PM = gsub(TIME_TWELVEHOURPM, "^.-(%w+)$", "%1") +local DURABILITY_TEMPLATE = gsub(DURABILITY_TEMPLATE, "%%d / %%d", "(%%d+) / (%%d+)") --Libs local LBC = LibStub("LibBabble-Class-3.0"):GetLookupTable() local LBZ = LibStub("LibBabble-Zone-3.0"):GetLookupTable() @@ -472,4 +474,24 @@ function GetItemCount(itemName) end return count +end + +local scan +function GetInventoryItemDurability(slot) + if not GetInventoryItemTexture("player", slot) then return nil, nil end + + if not scan then + scan = CreateFrame("GameTooltip", "DurabilityScan", nil, "ShoppingTooltipTemplate") + scan:SetOwner(UIParent, "ANCHOR_NONE") + end + + scan:ClearLines() + scan:SetInventoryItem("player", slot) + + for i = 4, scan:NumLines() do + local text = _G[scan:GetName().."TextLeft"..i]:GetText() + for durability, max in string.gfind(text, DURABILITY_TEMPLATE) do + return tonumber(durability), tonumber(max) + end + end end \ No newline at end of file diff --git a/ElvUI/Modules/DataTexts/Durability.lua b/ElvUI/Modules/DataTexts/Durability.lua index c3e510d..9d3f4bd 100644 --- a/ElvUI/Modules/DataTexts/Durability.lua +++ b/ElvUI/Modules/DataTexts/Durability.lua @@ -11,9 +11,8 @@ local GetInventoryItemDurability = GetInventoryItemDurability local GetInventoryItemTexture = GetInventoryItemTexture local GetInventorySlotInfo = GetInventorySlotInfo local ToggleCharacter = ToggleCharacter -local DURABILITY_TEMPLATE = string.gsub(DURABILITY_TEMPLATE, "%%d / %%d", "(%%d+) / (%%d+)") -local DURABILITY = "Durability" -- Neel ElvUI locale +local DURABILITY = "Durability" local displayString = "" local tooltipString = "%d%%" @@ -34,26 +33,6 @@ local slots = { "HeadSlot" } -local scan -local function GetInventoryItemDurability(slot) - if not GetInventoryItemTexture("player", slot) then return nil, nil end - - if not scan then - scan = CreateFrame("GameTooltip", "DurabilityScan", nil, "ShoppingTooltipTemplate") - scan:SetOwner(UIParent, "ANCHOR_NONE") - end - - scan:ClearLines() - scan:SetInventoryItem("player", slot) - - for i = 4, scan:NumLines() do - local text = _G[scan:GetName().."TextLeft"..i]:GetText() - for durability, max in string.gfind(text, DURABILITY_TEMPLATE) do - return tonumber(durability), tonumber(max) - end - end -end - local function OnEvent(self, t) lastPanel = self totalDurability = 100 From cd7ea3556ecafeed90a4f049ea10d2d2d59216e4 Mon Sep 17 00:00:00 2001 From: Crum Date: Wed, 6 Jun 2018 23:43:10 -0500 Subject: [PATCH 4/6] cleanup nil error if account is GM --- ElvUI/Core/core.lua | 3 ++- ElvUI/Modules/Misc/AFK.lua | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ElvUI/Core/core.lua b/ElvUI/Core/core.lua index cd068c3..6fab1ca 100644 --- a/ElvUI/Core/core.lua +++ b/ElvUI/Core/core.lua @@ -15,7 +15,6 @@ local GetActiveTalentGroup = GetActiveTalentGroup local GetCVar = GetCVar local GetFunctionCPUUsage = GetFunctionCPUUsage local GetTalentTabInfo = GetTalentTabInfo -local InCombatLockdown = InCombatLockdown local IsAddOnLoaded = IsAddOnLoaded local IsInInstance, GetNumPartyMembers, GetNumRaidMembers = IsInInstance, GetNumPartyMembers, GetNumRaidMembers local RequestBattlefieldScoreData = RequestBattlefieldScoreData @@ -28,6 +27,8 @@ local RAID_CLASS_COLORS = RAID_CLASS_COLORS _, E.myclass = UnitClass("player") -- Constants _, E.myrace = UnitRace("player") _, E.myfaction = UnitFactionGroup("player") +-- The E.myfaction may error when in GM mode +E.myfaction = E.myfaction or "Others" E.myname = UnitName("player") E.version = GetAddOnMetadata("ElvUI", "Version") E.myrealm = GetRealmName() diff --git a/ElvUI/Modules/Misc/AFK.lua b/ElvUI/Modules/Misc/AFK.lua index fa395e6..4500015 100644 --- a/ElvUI/Modules/Misc/AFK.lua +++ b/ElvUI/Modules/Misc/AFK.lua @@ -5,7 +5,6 @@ local AFK = E:NewModule("AFK", "AceEvent-3.0", "AceTimer-3.0"); --Cache global variables --Lua functions local _G = _G -local GetTime = GetTime local floor = math.floor --WoW API / Variables local CinematicFrame = CinematicFrame @@ -14,12 +13,12 @@ local GetBattlefieldStatus = GetBattlefieldStatus local GetGuildInfo = GetGuildInfo local GetScreenHeight = GetScreenHeight local GetScreenWidth = GetScreenWidth +local GetTime = GetTime local UnitAffectingCombat = UnitAffectingCombat local IsInGuild = IsInGuild local IsShiftKeyDown = IsShiftKeyDown local Screenshot = Screenshot local SetCVar = SetCVar -local UnitFactionGroup = UnitFactionGroup local CUSTOM_CLASS_COLORS = CUSTOM_CLASS_COLORS local MAX_BATTLEFIELD_QUEUES = MAX_BATTLEFIELD_QUEUES @@ -284,11 +283,9 @@ function AFK:Initialize() E:Size(self.AFKMode.bottom.logo, 320, 150) E:Point(self.AFKMode.bottom.logo, "CENTER", self.AFKMode.bottom, "CENTER", 0, 50) self.AFKMode.bottom.logo:SetTexture("Interface\\AddOns\\ElvUI\\media\\textures\\logo") - - local factionGroup = UnitFactionGroup("player") self.AFKMode.bottom.faction = self.AFKMode.bottom:CreateTexture(nil, "OVERLAY") E:Point(self.AFKMode.bottom.faction, "BOTTOMLEFT", self.AFKMode.bottom, "BOTTOMLEFT", -20, -16) - self.AFKMode.bottom.faction:SetTexture("Interface\\AddOns\\ElvUI\\media\\textures\\"..factionGroup.."-Logo") + self.AFKMode.bottom.faction:SetTexture("Interface\\AddOns\\ElvUI\\media\\textures\\"..E.myfaction.."-Logo") E:Size(self.AFKMode.bottom.faction, 140) self.AFKMode.bottom.name = self.AFKMode.bottom:CreateFontString(nil, "OVERLAY") From 26ca83f608182a7d1f23c80508d495528c598b30 Mon Sep 17 00:00:00 2001 From: Crum Date: Thu, 7 Jun 2018 01:28:29 -0500 Subject: [PATCH 5/6] load StateDriver libraries & update files as needed (Needs more testing) --- !Compatibility/libs/Classy-1.0.lua | 26 + !Compatibility/libs/Parser.lua | 268 +++++++++ !Compatibility/libs/StateDriver-1.0.lua | 513 ++++++++++++++++++ !Compatibility/libs/libs.xml | 3 + .../LibUIDropDownMenu/LibUIDropDownMenu.lua | 12 +- ElvUI/Libraries/oUF/oUF.xml | 6 +- ElvUI/Libraries/oUF/ouf.lua | 44 +- .../Modules/UnitFrames/Config_Enviroment.lua | 140 +++-- ElvUI/Modules/UnitFrames/Load_UnitFrames.xml | 1 + ElvUI/Modules/UnitFrames/UnitFrames.lua | 6 +- 10 files changed, 910 insertions(+), 109 deletions(-) create mode 100644 !Compatibility/libs/Classy-1.0.lua create mode 100644 !Compatibility/libs/Parser.lua create mode 100644 !Compatibility/libs/StateDriver-1.0.lua diff --git a/!Compatibility/libs/Classy-1.0.lua b/!Compatibility/libs/Classy-1.0.lua new file mode 100644 index 0000000..237273e --- /dev/null +++ b/!Compatibility/libs/Classy-1.0.lua @@ -0,0 +1,26 @@ +--[[ + Classy.lua + A wrapper for defining classes that inherit from widgets +--]] + +local Classy = LibStub:NewLibrary('Classy-1.0', 0) +if not Classy then return end + +function Classy:New(frameType, parentClass) + local class = CreateFrame(frameType) + class.mt = {__index = class} + + if parentClass then + class = setmetatable(class, {__index = parentClass}) + + class.super = function(self, method, ...) + parentClass[method](self, unpack(arg)) + end + end + + class.Bind = function(self, obj) + return setmetatable(obj, self.mt) + end + + return class +end \ No newline at end of file diff --git a/!Compatibility/libs/Parser.lua b/!Compatibility/libs/Parser.lua new file mode 100644 index 0000000..34ffb15 --- /dev/null +++ b/!Compatibility/libs/Parser.lua @@ -0,0 +1,268 @@ +if not LibStub then return end + +local SD = LibStub:GetLibrary('StateDriver-1.0') +if not SD then return end + +local Parser = SD:New('Parser') + +local IsMounted = IsMounted +local IsStealthed = IsStealthed +local GetShapeshiftForm = GetShapeshiftForm + +local _G = getfenv(0) +local function IsInShapeshiftForm(index) + return GetShapeshiftForm() == tonumber(index) +end + + +function Parser:Initialize() + self.casting = nil + self.channel_spell = nil + self.channeling = nil + + self:RegisterEvent('PLAYER_ENTERING_WORLD') + self:RegisterEvent('SPELLCAST_START') + self:RegisterEvent('SPELLCAST_CHANNEL_START') + self:RegisterEvent('SPELLCAST_STOP') + self:RegisterEvent('SPELLCAST_CHANNEL_STOP') + -- self:SetScript('OnEvent', function() self[event](self) end) +end + +local _CastSpellByName +local Hooked_CastSpellByName = function(spell, unit) + if spell then + self.channel_spell = spell + _CastSpellByName(spell, unit) + end +end + +local HOOKED = false +function Parser:PLAYER_ENTERING_WORLD() + if not HOOKED then + _CastSpellByName = CastSpellByName + CastSpellByName = Hooked_CastSpellByName + HOOKED = true + end +end + +function Parser:SPELLCAST_START() + self.casting = arg1 +end + +function Parser:SPELLCAST_CHANNEL_START() + self.channeling = true +end + +function Parser:SPELLCAST_STOP() + self.casting = false +end + +function Parser:SPELLCAST_CHANNEL_STOP() + self.channel_spell = nil + self.channeling = false +end + +local conditions_map, casting, existence, hostility +do + local function IsChanneling(dependency) + return dependency and dependency == Parser.channeling or Parser.channeling + end + + local function IsCasting(dependency) + return dependency and dependency == Parser.casting or Parser.casting + end + + local function IsMouseOverUnit() + local is_mouseover = false + + is_mouseover = UnitName('mouseover') and 'mouseover' + + local frame = GetMouseFocus() + if frame and frame.GetAttribute then + is_mouseover = is_mouseover or frame:GetAttribute('unit') + end + is_mouseover = is_mouseover or frame and frame.unit + + return is_mouseover + end + + local function modifierCondition(dependency) + dependency = dependency == 'ctrl' and 'control' or dependency + local modifier = string.gsub(dependency, "^%l", string.upper) + return _G['Is' .. modifier .. 'KeyDown']() + end + + -- Rename dependent? + casting = { + ['channeling'] = IsChanneling, + ['nochanneling'] = function(dependency) + return not IsChanneling(dependency) + end, + ['casting'] = IsCasting, + ['nocasting'] = function(dependency) + return not IsCasting(dependency) + end, + ['group'] = function(dependency) + local in_raid = UnitInRaid('player') + -- Player is apparently always in a party... Party Rock Anthem, GO! + local in_party = UnitInParty('party1') + + if dependency then + if dependency == 'raid' then + return in_raid + else + return in_party + end + else + return in_raid or in_party + end + end, + ['nogroup'] = function(dependency) + local in_raid = UnitInRaid('player') + local in_party = UnitInParty('party1') + + if dependency then + if dependency == 'raid' then + return not in_raid + else + return not in_party + end + else + return (not in_raid) and (not in_party) + end + end, + ['mod'] = modifierCondition, + ['modifier'] = modifierCondition, + ['form'] = IsInShapeshiftForm, + ['stance'] = IsInShapeshiftForm + } + + existence = { + ['exists'] = UnitExists, + ['noexists'] = function(unit) + return not UnitExists(unit) + end, + ['dead'] = function(unit) + return UnitIsDead(unit) and not UnitAura(unit, 'Feign Death') + end, + ['nodead'] = function(unit) + return not (UnitIsDead(unit) and not UnitAura(unit, 'Feign Death')) + end, + } + + + hostility = { + ['harm'] = function(unit_one, unit_two) + return UnitExists(unit_two) and (not UnitIsFriend(unit_one, unit_two)) + end, + ['nohelp'] = function(unit_one, unit_two) + return UnitExists(unit_two) and (not UnitIsFriend(unit_one, unit_two)) + end, + ['help'] = UnitIsFriend, + ['noharm'] = UnitIsFriend + } + + + conditions_map = { + -- Targets + ['mouseover'] = IsMouseOverUnit, + + ['pet'] = HasPetUI, + ['nopet'] = function() return not HasPetUI() end, + + ['party'] = UnitInParty, + ['raid'] = UnitInRaid, + + ['combat'] = UnitAffectingCombat, + + ['mounted'] = IsMounted, + -- ['indoors'] = IsIndoors, + -- ['outdoors'] = IsOutdoors, + ['stealth'] = IsStealthed, + -- ['swimming'] = IsSwimming, + + ['bonusbar'] = function(bar_id) + return false + end, + } + + for k, v in next, casting do conditions_map[k] = v end + for k, v in next, existence do conditions_map[k] = v end + for k, v in next, hostility do conditions_map[k] = v end +end + +function CmdOptionParse(command) + -- /action [conditions] + local action, conditions + + -- Accumulating condition + local acc_condition + + -- Condition-mapped function and dependency argument + local func, dependency + + -- [@target] + local target, is_cond_target + + if not string.find(command, ';') then + command = command .. ' ;' + end + + -- Iterate condition cases + for _, cases in { string.split(';', command) } do + conditions, action = string.match(cases, '%[([^%]]+)%]%s*(.+)') + + -- Does the action have any conditions? + if conditions then + conditions = { string.split(',', conditions) } + acc_condition = true + target = nil + for _, cond in conditions do + cond = string.trim(cond) + cond, dependency = string.split(':', cond) + + -- Is the current condition a target command? Usually the first + -- condition is + is_cond_target = + string.match(cond, '@(.*)') or + string.match(cond, 'target=(.*)') + + -- Mouseover has a special condition, since unitframes don't + -- utilise the mouseover unit, in which case it's mapped to + -- player, target, partyN etc. + func = conditions_map[is_cond_target or cond] + + if is_cond_target then + target = func and func() or is_cond_target + else + local res + + if hostility[cond] then + res = func and func('player', target or 'target') + elseif existence[cond] then + res = func and func(target or 'target') + elseif cond == 'combat' then + res = func and func('player') + elseif casting[cond] then + res = func and func(dependency) + else + res = func and func() + end + + acc_condition = acc_condition and res + end + end + + -- Are all conditions met? Then perform that action + if acc_condition then + return string.trim(action), target + end + else + local cases = string.trim(cases) + return cases + end + end +end +SecureCmdOptionParse = CmdOptionParse + +Parser:Initialize() \ No newline at end of file diff --git a/!Compatibility/libs/StateDriver-1.0.lua b/!Compatibility/libs/StateDriver-1.0.lua new file mode 100644 index 0000000..cd759bf --- /dev/null +++ b/!Compatibility/libs/StateDriver-1.0.lua @@ -0,0 +1,513 @@ +--[[ +Name: StateDriver-1.0 +Revision: $Rev: 107 $ +Maintainers: martinjlowm +Website: https://github.com/martinjlowm/StateDriver/ +Dependencies: Classy-1.0 +License: None +]] + +if not LibStub then return end + +local SD = LibStub:NewLibrary('StateDriver-1.0', 0) +if not SD then return end + +local Classy = LibStub('Classy-1.0') + +local _G = getfenv(0) + +function SD:New(name, parent) + self[name] = Classy:New('Frame', parent) + + return self[name] +end + +local function GetAttribute(self, prefix, name, suffix) + local attributes = self.__state_driver.attributes + local value + + if not name and not suffix then + name = prefix + else + value = attributes[prefix .. name .. suffix] + value = value or attributes['*' .. name .. suffix] + value = value or attributes[prefix .. name .. '*'] + value = value or attributes['*' .. name .. '*'] + end + + return value or attributes[name] +end + +local function SetAttribute(self, attr, value) + local old_value = self.__state_driver.attributes[attr] + self.__state_driver.attributes[attr] = value + + local func = self.__state_driver.handlers['OnAttributeChanged'] + if func and type(func) == 'function' and value ~= old_value then + func(self, attr, value) + end +end + + +-- Figure out where to place this +local function initPlayerDrop() + UnitPopup_ShowMenu(PlayerFrameDropDown, "SELF", "player") + if not (UnitInRaid("player") or GetNumPartyMembers() > 0) or UnitIsPartyLeader("player") and PlayerFrameDropDown.init and not CanShowResetInstances() then + UIDropDownMenu_AddButton({text = RESET_INSTANCES, func = ResetInstances, notCheckable = 1}, 1) + PlayerFrameDropDown.init = nil + end +end + + +local ACTIONS = {} + +ACTIONS.target = function(self, unit, button) + if unit then + if unit == 'none' then + ClearTarget(); + elseif ( SpellIsTargeting() ) then + SpellTargetUnit(unit); + elseif ( CursorHasItem() ) then + DropItemOnUnit(unit); + else + TargetUnit(unit); + end + end +end + +ACTIONS.togglemenu = function(self, unit, button) + if UnitIsUnit(unit, 'player') then + UIDropDownMenu_Initialize(PlayerFrameDropDown, initPlayerDrop, 'MENU') + PlayerFrameDropDown.init = true + ToggleDropDownMenu(1, nil, PlayerFrameDropDown, 'cursor') + elseif unit == 'pet' then + ToggleDropDownMenu(1, nil, PetFrameDropDown, 'cursor') + elseif unit == 'target' then + ToggleDropDownMenu(1, nil, TargetFrameDropDown, 'cursor') + elseif string.sub(unit, 1, 5) == 'party' then + ToggleDropDownMenu(1, nil, _G['PartyMemberFrame' .. string.sub(unit,6) .. 'DropDown'], 'cursor') + elseif string.sub(unit, 1, 4) == 'raid' then + HideDropDownMenu(1) + + local menuFrame = FriendsDropDown + menuFrame.displayMode = 'MENU' + menuFrame.id = string.sub(this.unit,5) + menuFrame.unit = unit + menuFrame.name = UnitName(this.unit) + menuFrame.initialize = function() + UnitPopup_ShowMenu(getglobal(UIDROPDOWNMENU_OPEN_MENU), "PARTY", self.unit, self.name, self.id) + end + + ToggleDropDownMenu(1, nil, FriendsDropDown, 'cursor') + end +end + +ACTIONS.macro = function(self, unit, button) + local macro_text = self:GetAttribute('macrotext') + local spell, unit = SecureCmdOptionParse(macro_text) + CastSpellByName(spell, unit) +end + +ACTIONS.spell = function(self, unit, button) + local spell = self:GetAttribute('spell') + CastSpellByName(spell) +end + +ACTIONS.pet = function(self, unit, button) + local index = self:GetAttribute('pet') + CastPetAction(index, unit) +end + +ACTIONS.func = function(self, unit, button) + local func = SlashCmdList[self:GetAttribute('func')] + + if not func then + error(string.format('Slash command `%s` does not exist in SlashCmdList')) + return + end + + func() +end + +local link_pattern = '%[([^%]]+)%]' +local function CmdItemParse(item) + local slot = tonumber(item) + if slot then + return nil, nil, slot + end + + local link, name + for bag = 0, 4 do + for slot = 1, GetContainerNumSlots(bag) do + link = GetContainerItemLink(bag, slot) + + if link then + name = string.match(link, link_pattern) + if name and name == item then + return name, bag, slot + end + end + end + end +end + +ACTIONS.item = function(self, unit, button) + local item = self:GetAttribute('item') + + if item then + local name, bag, slot = CmdItemParse(item) + UseItem(name, bag, slot) + end +end + +function UseItem(name, bag, slot) + if bag then + UseContainerItem(bag, slot) + elseif slot then + UseInventoryItem(slot) + end +end + +ACTIONS.assign = function(self, unit, button) + local macro_text = self:GetAttribute('assign') + local symbol, unit = SecureCmdOptionParse(macro_text) + SetRaidTargetIcon(unit, symbol) +end + +function Button_GetModifierPrefix(frame) + local prefix = '' + prefix = IsShiftKeyDown() and 'shift-' .. prefix or prefix + prefix = IsControlKeyDown() and 'ctrl-' .. prefix or prefix + return IsAltKeyDown() and 'alt-' .. prefix or prefix +end + +function Button_GetButtonSuffix(button) + if button == 'LeftButton' then + return '1' + elseif button == 'RightButton' then + return '2' + elseif button == 'MiddleButton' then + return '3' + end + + return ''; +end + +function Button_GetModifiedAttribute(frame, name, button, prefix, suffix) + if not prefix then + prefix = Button_GetModifierPrefix(frame) + end + if not suffix then + suffix = Button_GetButtonSuffix(button) + end + + return frame:GetAttribute(prefix, name, suffix) +end + +local function OnClick(self, button) + if not self.GetAttribute then return end + + local unit = self:GetAttribute('unit') + + local action_type = Button_GetModifiedAttribute(self, 'type', button) + + if action_type then + local handler = ACTIONS[action_type] + if handler and type(handler) == 'function' then + handler(self, unit, button) + end + end + +end + +local function SetScript(self, handler, func) + if self.__state_driver.handlers[handler] then + if self:HasScript(handler) and handler == 'OnClick' then + self.__state_driver._SetScript(self, handler, function(...) + OnClick(this, arg1) + func() + end) + else + self.__state_driver.handlers[handler] = func + end + else + self.__state_driver._SetScript(self, handler, func) + end +end + +local _CreateFrame = CreateFrame +function CreateFrame(...) + local frame = _CreateFrame(unpack(arg)) + + -- State Driver environment + frame.__state_driver = {} + + frame.__state_driver._SetScript = frame.SetScript + frame.SetScript = SetScript + + frame.__state_driver.attributes = {} + frame.__state_driver.handlers = { + ['OnAttributeChanged'] = true, + ['OnClick'] = true + } + + frame.GetAttribute = GetAttribute + frame.SetAttribute = SetAttribute + + if frame:HasScript('OnClick') and not frame:GetScript('OnClick') then + frame:SetScript('OnClick', NOOP) + end + + return frame +end + +local function Execute(frame, func) + +end + +local function WrapScript(frame, func) + +end + +local function UnwrapScript(frame, func) + +end + +local function ChildUpdate(frame, attr_snippet, value) + local children = { frame:GetChildren() } + local childUpdate + + for _, child in next, children do + if child.GetAttribute then + childUpdate = child:GetAttribute('_childupdate-' .. attr_snippet) + if childUpdate then + childUpdate(child, value) + else + childUpdate = child:GetAttribute('_childupdate') + if childUpdate then + childUpdate(child, value) + end + end + ChildUpdate(child, attr_snippet, value) + end + end +end + +-- +-- SecureStateDriverManager +-- Automatically sets states based on macro options for state driver frames +-- Also handled showing/hiding frames based on unit existence (code originally by Tem) +-- + +-- Register a frame attribute to be set automatically with changes in game state +function RegisterAttributeDriver(frame, attribute, values) + frame.Execute = Execute + frame.WrapScript = WrapScript + frame.UnwrapScript = UnwrapScript + frame.ChildUpdate = ChildUpdate + if attribute and values and string.sub(attribute, 1, 1) ~= '_' then + Manager:SetAttribute('setframe', frame) + Manager:SetAttribute('setstate', attribute .. ' ' .. values) + end +end + +-- Unregister a frame from the state driver manager. +function UnregisterAttributeDriver(frame, attribute) + if attribute then + Manager:SetAttribute('setframe', frame) + Manager:SetAttribute('setstate', attribute) + else + Manager:SetAttribute('delframe', frame) + end +end + +-- Bridge functions for compatibility +function RegisterStateDriver(frame, state, values) + return RegisterAttributeDriver(frame, 'state-' .. state, values) +end + +function UnregisterStateDriver(frame, state) + return UnregisterAttributeDriver(frame, 'state-' .. state) +end + +-- Register a frame to be notified when a unit's existence changes, the +-- unit is obtained from the frame's attributes. If asState is true then +-- notification is via the 'state-unitexists' attribute with values +-- true and false. Otherwise it's via :Show() and :Hide() +function RegisterUnitWatch(frame, asState) + if asState then + Manager:SetAttribute('addwatchstate', frame) + else + Manager:SetAttribute('addwatch', frame) + end +end + +-- Unregister a frame from the unit existence monitor. +function UnregisterUnitWatch(frame) + SecureStateDriverManager:SetAttribute('removewatch', frame) +end + +-- +-- Private implementation +-- +local secureAttributeDrivers = {} +local unitExistsWatchers = {} +local unitExistsCache = setmetatable( + {}, + { __index = function(t,k) + local v = UnitExists(k) or false + t[k] = v + return v + end +}) +local STATE_DRIVER_UPDATE_THROTTLE = 0.1 +local timer = 0 + +local wipe = table.wipe + +-- Check to see if a frame is registered +function UnitWatchRegistered(frame) + return not (unitExistsWatchers[frame] == nil) +end + +local function SecureStateDriverManager_UpdateUnitWatch(frame, doState) + -- Not really so secure, eh? + local unit = frame:GetAttribute('unit') + local exists = (unit and unitExistsCache[unit]) + if doState then + local attr = exists or false + if frame:GetAttribute('state-unitexists') ~= attr then + frame:SetAttribute('state-unitexists', attr) + end + else + if exists then + frame:Show() + frame:SetAttribute('statehidden', nil) + else + frame:Hide() + frame:SetAttribute('statehidden', true) + end + end +end + +local pairs = pairs + +-- consolidate duplicated code for footprint and maintainability +local function resolveDriver(frame, attribute, values) + local newValue = SecureCmdOptionParse(values) + + if attribute == 'state-visibility' then + if newValue == 'show' then + frame:Show() + frame:SetAttribute('statehidden', nil) + elseif newValue == 'hide' then + frame:Hide() + frame:SetAttribute('statehidden', true) + end + elseif newValue then + if newValue == 'nil' then + newValue = nil + else + newValue = tonumber(newValue) or newValue + end + local oldValue = frame:GetAttribute(attribute) + if newValue ~= oldValue then + frame:SetAttribute(attribute, newValue) + local onState = frame:GetAttribute('_on' .. attribute) + if onState then + onState(frame, attribute, newValue) + end + end + end +end + +local function OnUpdate() + local self, elapsed = this, arg1 + + timer = timer - elapsed + if timer <= 0 then + timer = STATE_DRIVER_UPDATE_THROTTLE + + -- Handle state driver updates + for frame, drivers in next, secureAttributeDrivers do + for attribute, values in next, drivers do + resolveDriver(frame, attribute, values) + end + end + + -- Handle unit existence changes + wipe(unitExistsCache) + for k in next, unitExistsCache do + unitExistsCache[k] = nil + end + for frame, doState in next, unitExistsWatchers do + SecureStateDriverManager_UpdateUnitWatch(frame, doState) + end + end +end + +local function OnEvent() + local self, event = this, arg1 + + timer = 0 +end + +local function OnAttributeChanged(self, name, value) + if not value then + return + end + + if name == 'setframe' then + if not secureAttributeDrivers[value] then + secureAttributeDrivers[value] = {} + end + SecureStateDriverManager:Show() + elseif name == 'delframe' then + secureAttributeDrivers[value] = nil + elseif name == 'setstate' then + local frame = self:GetAttribute('setframe') + local attribute, values = string.match(value, '^(%S+)%s*(.*)$') + + if values == '' then + secureAttributeDrivers[frame][attribute] = nil + else + secureAttributeDrivers[frame][attribute] = values + resolveDriver(frame, attribute, values) + end + -- Two frames registering with identical setstate fails unless the value + -- is reset afterwards + self:SetAttribute('setstate', nil) + elseif name == 'addwatch' or name == 'addwatchstate' then + local doState = (name == 'addwatchstate') + unitExistsWatchers[value] = doState + SecureStateDriverManager:Show() + SecureStateDriverManager_UpdateUnitWatch(value, doState) + elseif name == 'removewatch' then + unitExistsWatchers[value] = nil + elseif name == 'updatetime' then + STATE_DRIVER_UPDATE_THROTTLE = value + end +end + + +Manager = SD:New('Manager') +Manager:SetScript('OnUpdate', OnUpdate) +Manager:SetScript('OnEvent', OnEvent) +Manager:SetScript('OnAttributeChanged', OnAttributeChanged) + +-- Events that trigger early rescans +Manager:RegisterEvent('MODIFIER_STATE_CHANGED') +Manager:RegisterEvent('ACTIONBAR_PAGE_CHANGED') +Manager:RegisterEvent('UPDATE_BONUS_ACTIONBAR') +Manager:RegisterEvent('PLAYER_ENTERING_WORLD') +Manager:RegisterEvent('UPDATE_SHAPESHIFT_FORM') +Manager:RegisterEvent('UPDATE_STEALTH') +Manager:RegisterEvent('PLAYER_TARGET_CHANGED') +Manager:RegisterEvent('PLAYER_FOCUS_CHANGED') +Manager:RegisterEvent('PLAYER_REGEN_DISABLED') +Manager:RegisterEvent('PLAYER_REGEN_ENABLED') +Manager:RegisterEvent('UNIT_PET') +Manager:RegisterEvent('GROUP_ROSTER_UPDATE') +-- Deliberately ignoring mouseover and others' target changes because they change so much + +_G['SecureStateDriverManager'] = Manager \ No newline at end of file diff --git a/!Compatibility/libs/libs.xml b/!Compatibility/libs/libs.xml index b37fb2c..6c654fe 100644 --- a/!Compatibility/libs/libs.xml +++ b/!Compatibility/libs/libs.xml @@ -3,4 +3,7 @@