--========================================================-- -- Scorpio Secure Action Button -- -- -- -- Author : kurapica125@outlook.com -- -- Create Date : 2020/11/16 -- --========================================================-- --========================================================-- Scorpio "Scorpio.Secure.SecureActionButton" "1.0.0" --========================================================-- export { GetProxyUI = UI.GetProxyUI } _ManagerFrame = SecureFrame("Scorpio_SecureActionButton_Manager", UIParent, "SecureHandlerStateTemplate") _ManagerFrame:Hide() _IFActionTypeHandler = {} _ActionTypeMap = {} _ActionTargetMap = {} _ActionTargetDetail = {} _ReceiveMap = {} _ActionButtonGroupList = {} _AutoAttackButtons = {} _AutoRepeatButtons = {} _RangeCheckButtons = {} _Spell4Buttons = {} local _GridCounter = 0 local _PetGridCounter = 0 local _OnTooltipButton local _KeyBindingMap = {} local _Locale = _Locale local _KeyBindingMode = false IsSpellOverlayed = _G.IsSpellOverlayed or Toolset.fakefunc ------------------------------------------------------ -- Module Event Handler -- ------------------------------------------------------ function OnLoad() _SVData.Char:SetDefault { SecureActionButtonNoDragGroup = {}, SecureActionButtonMouseDownGroup= {}, } _NoDragGroup = _SVData.Char.SecureActionButtonNoDragGroup _MouseDownGroup = _SVData.Char.SecureActionButtonMouseDownGroup for group, val in pairs(_NoDragGroup) do if val then DisableDrag(group) end end end __Service__(true) function RangeCheckerService() while true do for i = 1, 99999 do local button = _RangeCheckButtons[i] if not button then if i == 1 then NextEvent("SCORPIO_SAB_RANGE_CHECK") end break end _IFActionTypeHandler[button.ActionType]:RefreshRange(button) if i % 20 == 0 then Continue() end end Wait(0.2, "PLAYER_TARGET_CHANGED") end end __SystemEvent__() function ACTIONBAR_SHOWGRID() _GridCounter = _GridCounter + 1 if _GridCounter == 1 then for kind, handler in pairs(_IFActionTypeHandler) do if handler.IsPlayerAction and handler.ReceiveStyle ~= "Block" then handler:RefershGrid() end end end end __SystemEvent__() function ACTIONBAR_HIDEGRID() if _GridCounter > 0 then _GridCounter = _GridCounter - 1 if _GridCounter == 0 then for kind, handler in pairs(_IFActionTypeHandler) do if handler.IsPlayerAction and handler.ReceiveStyle ~= "Block" then handler:RefershGrid() end end end end end __SystemEvent__() function PET_BAR_SHOWGRID() _PetGridCounter = _PetGridCounter + 1 if _PetGridCounter == 1 then for kind, handler in pairs(_IFActionTypeHandler) do if handler.IsPetAction and handler.ReceiveStyle ~= "Block" then handler:RefershGrid() end end end end __SystemEvent__() function PET_BAR_HIDEGRID() if _PetGridCounter > 0 then _PetGridCounter = _PetGridCounter - 1 if _PetGridCounter == 0 then for kind, handler in pairs(_IFActionTypeHandler) do if handler.IsPetAction and handler.ReceiveStyle ~= "Block" then handler:RefershGrid() end end end end end __SystemEvent__() function PLAYER_ENTER_COMBAT() for button in pairs(_AutoAttackButtons) do button.IsAutoAttacking = true end end __SystemEvent__() function PLAYER_LEAVE_COMBAT() for button in pairs(_AutoAttackButtons) do button.IsAutoAttacking = false end end __SystemEvent__() function SPELL_ACTIVATION_OVERLAY_GLOW_SHOW(spellId) local buttons = _Spell4Buttons[spellId] if not buttons then return end if getmetatable(buttons) then buttons.OverlayGlow = true else for button in pairs(buttons) do button.OverlayGlow = true end end end __SystemEvent__() function SPELL_ACTIVATION_OVERLAY_GLOW_HIDE(spellId) local buttons = _Spell4Buttons[spellId] if not buttons then return end if getmetatable(buttons) then buttons.OverlayGlow = false else for button in pairs(buttons) do button.OverlayGlow = false end end end __SystemEvent__() function SPELL_UPDATE_CHARGES() for kind, handler in pairs(_IFActionTypeHandler) do handler:RefreshCount() end end __SystemEvent__() function START_AUTOREPEAT_SPELL() for button in pairs(_AutoRepeatButtons) do if not _AutoAttackButtons[button] then button.IsAutoAttacking = true end end end __SystemEvent__() function STOP_AUTOREPEAT_SPELL() for button in pairs(_AutoRepeatButtons) do if button.IsAutoAttacking and not _AutoAttackButtons[button] then button.IsAutoAttacking = false end end end __SystemEvent__ "ARCHAEOLOGY_CLOSED" "TRADE_SKILL_SHOW" "TRADE_SKILL_CLOSE" function TRADE_SKILL_SHOW() for kind, handler in pairs(_IFActionTypeHandler) do handler:RefreshButtonState() end end __SystemEvent__"UNIT_ENTERED_VEHICLE" "UNIT_EXITED_VEHICLE" function UNIT_ENTERED_VEHICLE(unit) if unit == "player" then for kind, handler in pairs(_IFActionTypeHandler) do handler:RefreshButtonState() end end end __SystemEvent__"UNIT_INVENTORY_CHANGED" "LEARNED_SPELL_IN_TAB" "ACTIONBAR_UPDATE_COOLDOWN" function UNIT_INVENTORY_CHANGED(unit) return (not unit or unit == "player") and _OnTooltipButton and _OnTooltipButton:UpdateTooltip() end __SystemEvent__() __Async__() function PET_BATTLE_OPENING_START() for i = 1, 6 do local key = tostring(i) local button = _KeyBindingMap[key] if button then ClearOverrideBindings(GetRawUI(button)) end end NextEvent("PET_BATTLE_CLOSE") NoCombat() for i = 1, 6 do local key = tostring(i) local button = _KeyBindingMap[key] if button then SetOverrideBindingClick(GetRawUI(button), false, key, button:GetName(), "LeftButton") end end end ------------------------------------------------------ -- Action Type Handler -- ------------------------------------------------------ __Sealed__() enum "ActionTypeHandleStyle" { "Keep", "Clear", "Block" } -- The handler for action types __Sealed__() __AnonymousClass__() interface "ActionTypeHandler" (function(_ENV) extend "IList" _RegisterSnippetTemplate = "%s[%q] = %q" _ActionButtonMap = Toolset.newtable(true, true) local function refreshButton(self, button) _AutoAttackButtons[button] = self.IsAttackAction(button) or nil _AutoRepeatButtons[button] = self.IsAutoRepeatAction(button) or nil button.HasAction = self.HasAction(button) button.IsAutoAttack = _AutoAttackButtons[button] or _AutoRepeatButtons[button] local spell = self.GetSpellId(button) local ospell = _Spell4Buttons[button] if ospell ~= spell then if ospell then local buttons = _Spell4Buttons[ospell] if getmetatable(buttons) == nil then buttons[button] = nil elseif buttons == button then _Spell4Buttons[ospell] = nil end end if spell then local buttons = _Spell4Buttons[spell] if buttons == nil then _Spell4Buttons[spell] = button elseif getmetatable(buttons) == nil then buttons[button] = true else buttons = { [buttons] = true } buttons[button] = true _Spell4Buttons[spell] = buttons end end _Spell4Buttons[button] = spell end if self.IsRangeSpell(button) then if not _RangeCheckButtons[button] then local index = #_RangeCheckButtons + 1 _RangeCheckButtons[button] = true _RangeCheckButtons[index] = button if index == 1 then FireSystemEvent("SCORPIO_SAB_RANGE_CHECK") end end else if _RangeCheckButtons[button] then for i = 1, #_RangeCheckButtons do if _RangeCheckButtons[i] == button then tremove(_RangeCheckButtons, i) break end end _RangeCheckButtons[button] = nil end end if self.ReceiveStyle ~= "Block" then self:RefershGrid(button) end self:RefreshButtonState(button) self:RefreshUsable(button) self:RefreshCooldown(button) self:RefreshFlyout(button) self:RefreshAutoCastable(button) self:RefreshAutoCasting(button) self:RefreshEquipItem(button) self:RefreshText(button) self:RefreshIcon(button) self:RefreshCount(button) self:RefreshOverlayGlow(button) self:RefreshShowSearchOverlay(button) self:RefreshIconLocked(button) self.Refresh(button) button:Refresh() if _OnTooltipButton == button then return button:UpdateTooltip() end end ------------------------------------------------------ -- Event ------------------------------------------------------ -- Fired when the handler is enabled or disabled event "OnEnableChanged" ------------------------------------------------------ -- Refresh Method ------------------------------------------------------ function RefershGrid(self, button) local force = (self.IsPlayerAction and _GridCounter or _PetGridCounter) > 0 local HasAction = self.HasAction if button then button.GridVisible = force or HasAction(button) else for _, button in self:GetIterator() do button.GridVisible = force or HasAction(button) end end end function RefreshButtonState(self, button) local IsActivedAction = self.IsActivedAction local IsAutoRepeatAction= self.IsAutoRepeatAction if button then button:SetChecked(IsActivedAction(button) or IsAutoRepeatAction(button)) else for _, button in self:GetIterator() do button:SetChecked(IsActivedAction(button) or IsAutoRepeatAction(button)) end end end function RefreshUsable(self, button) local IsUsableAction = self.IsUsableAction if button then button.IsUsable = IsUsableAction(button) else for _, button in self:GetIterator() do button.IsUsable= IsUsableAction(button) end end end function RefreshCount(self, button) local IsConsumableAction= self.IsConsumableAction local GetActionCount = self.GetActionCount local GetActionCharges = self.GetActionCharges if button then if IsConsumableAction(button) then button.Count = GetActionCount(button) else local cha, max = GetActionCharges(button) if max and max > 1 then button.Count= cha else button.Count= nil end end else for _, button in self:GetIterator() do if IsConsumableAction(button) then button.Count = GetActionCount(button) else local cha, max = GetActionCharges(button) if max and max > 1 then button.Count= cha else button.Count= nil end end end end end local shareCooldown = { start = 0, duration = 0 } function RefreshCooldown(self, button) local GetActionCooldown = self.GetActionCooldown if button then local s, d = GetActionCooldown(button) shareCooldown.start = s or 0 shareCooldown.duration = d or 0 button.Cooldown = shareCooldown else for _, button in self:GetIterator() do local s, d = GetActionCooldown(button) shareCooldown.start = s or 0 shareCooldown.duration = d or 0 button.Cooldown = shareCooldown end end end function RefreshFlash(self, button) local IsAttackAction = self.IsAttackAction local IsActivedAction = self.IsActivedAction local IsAutoRepeatAction= self.IsAutoRepeatAction if button then button.IsAutoAttacking = (IsAttackAction(button) and IsActivedAction(button)) or IsAutoRepeatAction(button) else for _, button in self:GetIterator() do button.IsAutoAttacking = (IsAttackAction(button) and IsActivedAction(button)) or IsAutoRepeatAction(button) end end end function RefreshOverlayGlow(self, button) local GetSpellId = self.GetSpellId if button then local spellId = GetSpellId(button) self.OverlayGlow = spellId and IsSpellOverlayed(spellId) else for _, button in self:GetIterator() do local spellId = GetSpellId(button) self.OverlayGlow= spellId and IsSpellOverlayed(spellId) end end end function RefreshRange(self, button) local IsInRange = self.IsInRange if button then button.InRange = IsInRange(button) else for _, button in self:GetIterator() do button.InRange = IsInRange(button) end end end function RefreshFlyout(self, button) local IsFlyout = self.IsFlyout if button then if button.IsCustomFlyout then return end button.IsFlyout = IsFlyout(button) else for _, button in self:GetIterator() do if not button.IsCustomFlyout then button.IsFlyout = IsFlyout(button) end end end end function RefreshAutoCastable(self, button) local IsAutoCastAction = self.IsAutoCastAction if button then button.IsAutoCastable= IsAutoCastAction(button) else for _, button in self:GetIterator() do button.IsAutoCastable = IsAutoCastAction(button) end end end function RefreshAutoCasting(self, button) local IsAutoCasting = self.IsAutoCasting if button then button.IsAutoCasting= IsAutoCasting(button) else for _, button in self:GetIterator() do button.IsAutoCasting = IsAutoCasting(button) end end end function RefreshIcon(self, button) local GetActionTexture = self.GetActionTexture if button then button.Icon = GetActionTexture(button) else for _, button in self:GetIterator() do button.Icon = GetActionTexture(button) end end end function RefreshEquipItem(self, button) local IsEquippedItem = self.IsEquippedItem if button then button.IsEquippedItem = IsEquippedItem(button) else for _, button in self:GetIterator() do button.IsEquippedItem = IsEquippedItem(button) end end end function RefreshText(self, button) local IsConsumableAction= self.IsConsumableAction local GetActionText = self.GetActionText if button then button.Text = IsConsumableAction(button) and "" or GetActionText(button) else for _, button in self:GetIterator() do button.Text = IsConsumableAction(button) and "" or GetActionText(button) end end end function RefreshShowSearchOverlay(self, button) local IsSearchOverlayShow = self.IsSearchOverlayShow if button then button.ShowSearchOverlay = IsSearchOverlayShow(button) else for _, button in self:GetIterator() do button.ShowSearchOverlay = IsSearchOverlayShow(button) end end end function RefreshIconLocked(self, button) local IsIconLocked = self.IsIconLocked if button then button.IconLocked = IsIconLocked(button) else for _, button in self:GetIterator() do button.IconLocked = IsIconLocked(button) end end end __Delegate__(Continue) function RefreshActionButtons(self, button) local refresh = refreshButton if button then -- The button may change its action type when waiting return button.ActionType == self.Type and refresh(self, button) else for _, button in self:GetIterator() do refresh(self, button) Continue() end end end ------------------------------------------------------ -- Method ------------------------------------------------------ GetIterator = ipairs function Insert(self, button) local oldHandler = _ActionButtonMap[button] if oldHandler then if oldHandler == self then return end oldHandler:Remove(button) end _ActionButtonMap[button]= self tinsert(self, button) self.Enabled = true end function Remove(self, button) if _ActionButtonMap[button] ~= self then return end _ActionButtonMap[button]= nil for i, v in ipairs(self) do if v == button then tremove(self, i) break end end self.Enabled = self[1] and true or false end -- Run the snippet in the global environment __NoCombat__() function RunSnippet(self, code) return self.Manager:Execute(code) end ------------------------------------------------------ -- Overridable Method For Action Buttons ------------------------------------------------------ -- Get the actions's kind, target, detail function GetActionDetail(self) local name = self:GetAttribute("actiontype") return self:GetAttribute(_ActionTargetMap[name]), _ActionTargetDetail[name] and self:GetAttribute(_ActionTargetDetail[name]) end -- Map the action function Map(self, ...) return ... end -- The refresh logic function Refresh(self) end -- Custom pick up action function PickupAction(self, target, detail) end -- Custom receive action function ReceiveAction(self, target, detail) end -- Whether the action button has an action function HasAction(self) return true end -- Get the action's text function GetActionText(self) return "" end -- Get the action's texture function GetActionTexture(self) end -- Get the action's charges function GetActionCharges(self) end -- Get the action's count function GetActionCount(self) return 0 end -- Get the action's cooldown function GetActionCooldown(self) return 0, 0, 0 end -- Whether the action is attackable function IsAttackAction(self) return false end -- Whether the action is an item and can be equipped function IsEquippedItem(self) return false end -- Whether the action is actived function IsActivedAction(self) return false end -- Whether the action is auto-repeat function IsAutoRepeatAction(self) return false end -- Whether the action is usable function IsUsableAction(self) return true end -- Whether the action is consumable function IsConsumableAction(self) return false end -- Whether the action is in range of the target function IsInRange(self) return end -- Whether the action is auto-castable function IsAutoCastAction(self) return false end -- Whether the action is auto-casting now function IsAutoCasting(self) return false end -- Whether need show the search overlay function IsSearchOverlayShow(self) return false end -- Whether the icon is locked function IsIconLocked(self) return false end -- Show the tooltip for the action function SetTooltip(self, tip) end -- Get the spell id of the action function GetSpellId(self) end -- Whether the action is a flyout spell function IsFlyout(self) return false end -- Whether the action has range spell function IsRangeSpell(self) return false end ------------------------------------------------------ -- Property ------------------------------------------------------ -- The manager of the action system property "Manager" { default = _ManagerFrame, set = false } -- Whether the handler is enabled(has buttons) property "Enabled" { type = Boolean, event = "OnEnableChanged" } -- The action's name property "Name" { type = String } -- The action type's type property "Type" { type = String } -- The target attribute name property "Target" { type = String } -- The detail attribute name property "Detail" { type = String } -- Whether the action is player action property "IsPlayerAction" { type = Boolean, default = true } -- Whether the action is pet action property "IsPetAction" { type = Boolean, default = false } -- The drag style of the action type property "DragStyle" { type = ActionTypeHandleStyle, default = ActionTypeHandleStyle.Clear } -- The receive style of the action type property "ReceiveStyle" { type = ActionTypeHandleStyle, default = ActionTypeHandleStyle.Clear } -- The receive map property "ReceiveMap" { type = String } -- The pickup map property "PickupMap" { type = String } -- The snippet to setup environment for the action type property "InitSnippet" { type = String } -- The snippet used when pick up action property "PickupSnippet" { type = String } -- The snippet used to update for new action settings property "UpdateSnippet" { type = String } -- The snippet used to receive action property "ReceiveSnippet" { type = String } -- The snippet used to clear action property "ClearSnippet" { type = String } -- The snippet used for pre click property "PreClickSnippet" { type = String } -- The snippet used for post click property "PostClickSnippet" { type = String } ------------------------------------------------------ -- Initialize ------------------------------------------------------ function __init(self) -- No repeat definition for action types if _IFActionTypeHandler[self.Name] then return end -- Register the action type handler _IFActionTypeHandler[self.Name] = self -- Default map if self.Type == nil then self.Type = self.Name end if self.Target == nil then self.Target = self.Type end if self.PickupMap == nil then self.PickupMap = self.Type end if self.ReceiveMap == nil and self.ReceiveStyle == "Clear" then self.ReceiveMap = self.Type end -- Register action type map _ActionTypeMap[self.Name] = self.Type _ActionTargetMap[self.Name] = self.Target _ActionTargetDetail[self.Name] = self.Detail self:RunSnippet( _RegisterSnippetTemplate:format("_ActionTypeMap", self.Name, self.Type) ) self:RunSnippet( _RegisterSnippetTemplate:format("_ActionTargetMap", self.Name, self.Target) ) if self.Detail then self:RunSnippet( _RegisterSnippetTemplate:format("_ActionTargetDetail", self.Name, self.Detail) ) end -- Init the environment if self.InitSnippet then self:RunSnippet( self.InitSnippet ) end -- Register PickupSnippet if self.PickupSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_PickupSnippet", self.Name, self.PickupSnippet) ) end -- Register UpdateSnippet if self.UpdateSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_UpdateSnippet", self.Name, self.UpdateSnippet) ) end -- Register ReceiveSnippet if self.ReceiveSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_ReceiveSnippet", self.Name, self.ReceiveSnippet) ) end -- Register ClearSnippet if self.ClearSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_ClearSnippet", self.Name, self.ClearSnippet) ) end -- Register DragStyle self:RunSnippet( _RegisterSnippetTemplate:format("_DragStyle", self.Name, self.DragStyle) ) -- Register ReceiveStyle self:RunSnippet( _RegisterSnippetTemplate:format("_ReceiveStyle", self.Name, self.ReceiveStyle) ) -- Register ReceiveMap if self.ReceiveMap then self:RunSnippet( _RegisterSnippetTemplate:format("_ReceiveMap", self.ReceiveMap, self.Name) ) _ReceiveMap[self.ReceiveMap] = self end -- Register PickupMap if self.PickupMap then self:RunSnippet( _RegisterSnippetTemplate:format("_PickupMap", self.Name, self.PickupMap) ) end -- Register PreClickMap if self.PreClickSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_PreClickSnippet", self.Name, self.PreClickSnippet) ) end -- Register PostClickMap if self.PostClickSnippet then self:RunSnippet( _RegisterSnippetTemplate:format("_PostClickSnippet", self.Name, self.PostClickSnippet) ) end -- Clear self.InitSnippet = nil self.PickupSnippet = nil self.UpdateSnippet = nil self.ReceiveSnippet = nil self.ClearSnippet = nil self.PreClickSnippet = nil self.PostClickSnippet = nil end end) ------------------------------------------------------ -- Action Button Manager -- ------------------------------------------------------ __SecureMethod__() function _ManagerFrame:OnPickUp(kind, target, detail) return not InCombatLockdown() and PickupAny("clear", kind, target, detail) end __SecureMethod__() function _ManagerFrame:OnReceive(kind, target, detail) return not InCombatLockdown() and _IFActionTypeHandler[kind] and _IFActionTypeHandler[kind]:ReceiveAction(target, detail) end __SecureMethod__() function _ManagerFrame:UpdateActionButton(name) self = GetProxyUI(_G[name]) local name = self:GetAttribute("actiontype") local handler = _IFActionTypeHandler[name] local target, detail = handler.GetActionDetail(self) if self.__IFActionHandler_Kind ~= name or self.__IFActionHandler_Target ~= target or self.__IFActionHandler_Detail ~= detail then if self.__IFActionHandler_Kind and self.__IFActionHandler_Kind ~= name then _IFActionTypeHandler[self.__IFActionHandler_Kind]:Remove(self) end self.__IFActionHandler_Kind = name self.__IFActionHandler_Target = target self.__IFActionHandler_Detail = detail handler:Insert(self) return handler:RefreshActionButtons(self) end end ------------------------------------------------------ -- Secure Snippet -- ------------------------------------------------------ do -- Init manger frame's enviroment _ManagerFrame:Execute[[ -- to fix blz error, use Manager not control Manager = self _NoDraggable = newtable() _ActionTypeMap = newtable() _ActionTargetMap = newtable() _ActionTargetDetail = newtable() _ReceiveMap = newtable() _PickupMap = newtable() _ClearSnippet = newtable() _UpdateSnippet = newtable() _PickupSnippet = newtable() _ReceiveSnippet = newtable() _PreClickSnippet = newtable() _PostClickSnippet = newtable() _DragStyle = newtable() _ReceiveStyle = newtable() UpdateAction = [=[ local name = self:GetAttribute("actiontype") -- Custom update if _UpdateSnippet[name] then Manager:RunFor( self, _UpdateSnippet[name], self:GetAttribute(_ActionTargetMap[name]), _ActionTargetDetail[name] and self:GetAttribute(_ActionTargetDetail[name]) ) end return Manager:CallMethod("UpdateActionButton", self:GetName()) ]=] ClearAction = [=[ local name = self:GetAttribute("actiontype") if name and name ~= "empty" then self:SetAttribute("actiontype", "empty") self:SetAttribute("type", nil) self:SetAttribute(_ActionTargetMap[name], nil) if _ActionTargetDetail[name] then self:SetAttribute(_ActionTargetDetail[name], nil) end -- Custom clear if _ClearSnippet[name] then Manager:RunFor(self, _ClearSnippet[name]) end end ]=] GetAction = [=[ return self:GetAttribute("actiontype"), self:GetAttribute(_ActionTargetMap[name]), _ActionTargetDetail[name] and self:GetAttribute(_ActionTargetDetail[name]) ]=] SetAction = [=[ local name, target, detail = ... Manager:RunFor(self, ClearAction) if name and _ActionTypeMap[name] and target then self:SetAttribute("actiontype", name) self:SetAttribute("type", _ActionTypeMap[name]) self:SetAttribute(_ActionTargetMap[name], target) if detail ~= nil and _ActionTargetDetail[name] then self:SetAttribute(_ActionTargetDetail[name], detail) end end return Manager:RunFor(self, UpdateAction) ]=] DragStart = [=[ local name = self:GetAttribute("actiontype") if _DragStyle[name] == "Block" then return false end local target = self:GetAttribute(_ActionTargetMap[name]) local detail = _ActionTargetDetail[name] and self:GetAttribute(_ActionTargetDetail[name]) -- Clear and refresh if _DragStyle[name] == "Clear" then Manager:RunFor(self, ClearAction) Manager:RunFor(self, UpdateAction) end -- Pickup the target if _PickupSnippet[name] == "Custom" then Manager:CallMethod("OnPickUp", name, target, detail) return false elseif _PickupSnippet[name] then return Manager:RunFor(self, _PickupSnippet[name], target, detail) else return "clear", _PickupMap[name], target, detail end ]=] ReceiveDrag = [=[ local kind, value, extra, extra2 = ... if not kind or not value then return false end local oldName = self:GetAttribute("actiontype") if _ReceiveStyle[oldName] == "Block" then return false end local oldTarget = oldName and self:GetAttribute(_ActionTargetMap[oldName]) local oldDetail = oldName and _ActionTargetDetail[oldName] and self:GetAttribute(_ActionTargetDetail[oldName]) if _ReceiveStyle[oldName] == "Clear" then Manager:RunFor(self, ClearAction) local name, target, detail = _ReceiveMap[kind] if name then if _ReceiveSnippet[name] and _ReceiveSnippet[name] ~= "Custom" then target, detail = Manager:RunFor(self, _ReceiveSnippet[name], value, extra, extra2) else target, detail = value, extra end if target then self:SetAttribute("actiontype", name) self:SetAttribute("type", _ActionTypeMap[name]) self:SetAttribute(_ActionTargetMap[name], target) if detail ~= nil and _ActionTargetDetail[name] then self:SetAttribute(_ActionTargetDetail[name], detail) end end end Manager:RunFor(self, UpdateAction) end if _ReceiveStyle[oldName] == "Keep" and _ReceiveSnippet[oldName] == "Custom" then Manager:CallMethod("OnReceive", oldName, oldTarget, oldDetail) return Manager:RunFor(self, UpdateAction) or false end -- Pickup the target if _PickupSnippet[oldName] == "Custom" then Manager:CallMethod("OnPickUp", oldName, oldTarget, oldDetail) return false elseif _PickupSnippet[oldName] then return Manager:RunFor(self, _PickupSnippet[oldName], oldTarget, oldDetail) else return "clear", _PickupMap[oldName], oldTarget, oldDetail end ]=] ]] _OnDragStartSnippet = [[ if (IsModifierKeyDown() or _NoDraggable[self:GetAttribute("IFActionHandlerGroup")]) and not IsModifiedClick("PICKUPACTION") then return false end return Manager:RunFor(self, DragStart) ]] _OnReceiveDragSnippet = [[ return Manager:RunFor(self, ReceiveDrag, kind, value, ...) ]] _PostReceiveSnippet = [[ return Manager:RunFor(Manager:GetFrameRef("UpdatingButton"), ReceiveDrag, %s, %s, %s, %s) ]] _SetActionSnippet = [[ return Manager:RunFor(Manager:GetFrameRef("UpdatingButton"), SetAction, %s, %s, %s) ]] _WrapClickPrev = [[ local name = self:GetAttribute("actiontype") if _PreClickSnippet[name] then return Manager:RunFor(self, _PreClickSnippet[name], button, down) end ]] _WrapClickPost = [[ local name = self:GetAttribute("actiontype") if _PostClickSnippet[name] then return Manager:RunFor(self, _PostClickSnippet[name], message, button, down) end ]] _WrapDragPrev = [[ return "message", "update" ]] _WrapDragPost = [[ Manager:RunFor(self, UpdateAction) ]] _OnShowSnippet = [[ if self:GetAttribute("autoKeyBinding") and self:GetAttribute("hotKey") then self:SetBindingClick(true, self:GetAttribute("hotKey"), self:GetName(), "LeftButton") end ]] _OnHideSnippet = [[ if self:GetAttribute("autoKeyBinding") then self:ClearBindings() end ]] end ------------------------------------------------------ -- Action Script Hanlder -- ------------------------------------------------------ do _GlobalGroup = "GLOBAL" function GetGroup(group) group = type(group) == "string" and strtrim(group) return group and group ~= "" and group:upper() or _GlobalGroup end function GetFormatString(param) return type(param) == "string" and ("%q"):format(param) or tostring(param) end function PickupAny(kind, target, detail, ...) if (kind == "clear") then ClearCursor() kind, target, detail= target, detail, ... end local handler = _IFActionTypeHandler[kind] return handler and handler:PickupAction(target, detail) end function PreClick(self) local oldKind = self:GetAttribute("actiontype") if InCombatLockdown() or (oldKind and _IFActionTypeHandler[oldKind].ReceiveStyle ~= "Clear") then return end local kind, value = GetCursorInfo() if not (kind and value) then return end self.__IFActionHandler_PreType = self:GetAttribute("type") self.__IFActionHandler_PreMsg = true -- Make sure no action used self:SetAttribute("type", nil) end function PostClick(self) _IFActionTypeHandler[self.ActionType]:RefreshButtonState(self) -- Restore the action if self.__IFActionHandler_PreMsg then if not InCombatLockdown() then if self.__IFActionHandler_PreType then self:SetAttribute("type", self.__IFActionHandler_PreType) end local kind, value, subtype, detail = GetCursorInfo() if kind and value and _ReceiveMap[kind] then local oldName = self.__IFActionHandler_Kind local oldTarget = self.__IFActionHandler_Target local oldDetail = self.__IFActionHandler_Detail _ManagerFrame:SetFrameRef("UpdatingButton", self) _ManagerFrame:Execute(_PostReceiveSnippet:format(GetFormatString(kind), GetFormatString(value), GetFormatString(subtype), GetFormatString(detail))) PickupAny("clear", oldName, oldTarget, oldDetail) end elseif self.__IFActionHandler_PreType then -- Keep safe NoCombat(self.SetAttribute, self, "type", self.__IFActionHandler_PreType) end self.__IFActionHandler_PreType = false self.__IFActionHandler_PreMsg = false end end function OnEnter(self) if _KeyBindingMode then return true end _OnTooltipButton = self return self:UpdateTooltip() end function OnLeave(self) _OnTooltipButton = nil GameTooltip:Hide() end function OnShow(self) _IFActionTypeHandler[self.ActionType]:RefershGrid(self) end __NoCombat__() function DisableDrag(group, value) group = GetGroup(group) _NoDragGroup[group] = value or nil _ManagerFrame:Execute( ("_NoDraggable[%q] = %s"):format(group, tostring(value or nil)) ) end function IsDragEnabled(group) return not _NoDragGroup[GetGroup(group)] end __NoCombat__() function EnableButtonDown(group, value) group = GetGroup(group) if not _MouseDownGroup[group] then _MouseDownGroup[group] = value or nil if _ActionButtonGroupList[group] then local reg = value and "AnyDown" or "AnyUp" for btn in pairs(_ActionButtonGroupList[group]) do btn:RegisterForClicks(reg) end end end end function IsButtonDownEnabled(group) return _MouseDownGroup[GetGroup(group)] end function SetActionButtonGroup(self, group, old) group = GetGroup(group) old = old and GetGroup(old) if old and _ActionButtonGroupList[old] then _ActionButtonGroupList[old][self] = nil end _ActionButtonGroupList[group] = _ActionButtonGroupList[group] or {} _ActionButtonGroupList[group][self] = true self:SetAttribute("IFActionHandlerGroup", group) self:RegisterForClicks(_MouseDownGroup[group] and "AnyDown" or "AnyUp") end function SetupActionButton(self) SetActionButtonGroup(self, self.ActionButtonGroup) self:RegisterForDrag("LeftButton", "RightButton") _ManagerFrame:WrapScript(self, "OnShow", _OnShowSnippet) _ManagerFrame:WrapScript(self, "OnHide", _OnHideSnippet) _ManagerFrame:WrapScript(self, "OnDragStart", _OnDragStartSnippet) _ManagerFrame:WrapScript(self, "OnReceiveDrag", _OnReceiveDragSnippet) _ManagerFrame:WrapScript(self, "OnClick", _WrapClickPrev, _WrapClickPost) _ManagerFrame:WrapScript(self, "OnDragStart", _WrapDragPrev, _WrapDragPost) _ManagerFrame:WrapScript(self, "OnReceiveDrag", _WrapDragPrev, _WrapDragPost) -- Register useful attribute snippets to be used in other addons self:SetFrameRef("_Manager", _ManagerFrame) self:SetAttribute("SetAction", [[ return self:GetFrameRef("_Manager"):RunFor(self, "Manager:RunFor(self, SetAction, ...)", ...) ]]) self:SetAttribute("ClearAction",[[ return self:GetFrameRef("_Manager"):RunFor(self, "Manager:RunFor(self, ClearAction)") ]]) self:SetAttribute("GetAction", [[ return self:GetFrameRef("_Manager"):RunFor(self, "return Manager:RunFor(self, GetAction)") ]]) if not self:GetAttribute("actiontype") then self:SetAttribute("actiontype", "empty") end self.PreClick = self.PreClick + PreClick self.PostClick = self.PostClick+ PostClick self.OnShow = self.OnShow + OnShow self.OnEnter = self.OnEnter + OnEnter self.OnLeave = self.OnLeave + OnLeave end function SaveAction(self, kind, target, detail) _ManagerFrame:SetFrameRef("UpdatingButton", self) _ManagerFrame:Execute(_SetActionSnippet:format(GetFormatString(kind), GetFormatString(target), GetFormatString(detail))) end end class "SecureActionButton" (function(_ENV) inherit "SecureCheckButton" import "System.Reactive" export { GetRawUI = UI.GetRawUI, IsObjectType = Class.IsObjectType, } local _KeyBindingMask = Mask("Scorpio_SecureActionButton_KeyBindingMask") _KeyBindingMask:Hide() _KeyBindingMask.EnableKeyBinding = true function _KeyBindingMask:OnKeySet(key, old) local parent = self:GetParent() if IsObjectType(parent, SecureActionButton) then parent.HotKey = key end end function _KeyBindingMask:OnKeyClear() local parent = self:GetParent() if IsObjectType(parent, SecureActionButton) then parent.HotKey = nil end end local function handleKeyBinding(self) if self.HotKey and (not self.AutoKeyBinding or self:IsVisible()) then SetOverrideBindingClick(GetRawUI(self), self.AutoKeyBinding, self.HotKey, self:GetName(), "LeftButton") else ClearOverrideBindings(GetRawUI(self)) end end ------------------------------------------------------ -- Static Property -- ------------------------------------------------------ --- Whether the action button group is draggable __Static__() __Indexer__(String) property "Draggable" { type = Boolean, get = function(self, group) return IsDragEnabled(group) end, set = function(self, group, value) DisableDrag(group, not value) end, } --- Whether the action button group use mouse down to trigger __Static__() __Indexer__(String) property "UseMouseDown" { type = Boolean, get = function(self, group) return IsButtonDownEnabled(group) end, set = function(self, group, value) EnableButtonDown(group, value) end, } ------------------------------------------------------ -- Property -- ------------------------------------------------------ --- The action button group property "ActionButtonGroup"{ default = _GlobalGroup, handler = SetActionButtonGroup } --- The action type property "ActionType" { set = false, field = "__IFActionHandler_Kind", default = "empty" } --- the action content property "ActionTarget" { set = false, field = "__IFActionHandler_Target" } --- The action detail property "ActionDetail" { set = false, field = "__IFActionHandler_Detail" } --- The gametool tip anchor property "GameTooltipAnchor"{ type = AnchorType } --- Whether use custom flyout logic property "IsCustomFlyout" { type = Boolean } ------------------------------------------------------ -- Observable Property -- ------------------------------------------------------ --- Whether show the button grid __Observable__() property "GridVisible" { type = Boolean } --- Whether always show the button grid __Observable__() property "GridAlwaysShow" { type = Boolean } --- Whether the button is usable __Observable__() property "IsUsable" { type = Boolean } --- Whether the button has action __Observable__() property "HasAction" { type = Boolean } --- The count/charge of the action __Observable__() property "Count" { type = Number } --- The cooldown of the action __Observable__() property "Cooldown" { type = CooldownStatus, set = Toolset.fakefunc } --- Whether the action is auto attack or auto repeat __Observable__() property "IsAutoAttack" { type = Boolean } --- Whether show the IsAutoAttacking __Observable__() property "IsAutoAttacking" { type = Boolean } --- Whether show the overlay glow __Observable__() property "OverlayGlow" { type = Boolean } --- Whether the target is in range __Observable__() property "InRange" { type = Any } --- The action button's flyout direction: UP, DOWN, LEFT, RIGHT __Observable__() property "FlyoutDirection" { type = FlyoutDirection, default = "UP", handler = function(self, val) self:SetAttribute("flyoutDirection", val) end } --- Whether the action is flyout __Observable__() property "IsFlyout" { type = Boolean } --- whether the flyout action bar is shown __Observable__() property "Flyouting" { type = Boolean } --- Whether the action is auto castable __Observable__() property "IsAutoCastable" { type = Boolean } --- Whether the action is auto casting __Observable__() property "IsAutoCasting" { type = Boolean } --- The icon of the action __Observable__() property "Icon" { type = Any } --- Whether the action is an equipped item __Observable__() property "IsEquippedItem" { type = Boolean } --- The action text __Observable__() property "Text" { type = String } --- Whether the icon should be locked __Observable__() property "IconLocked" { type = Boolean } __Observable__() property "ShowSearchOverlay"{ type = Boolean } --- The short key of the action __Observable__() property "HotKey" { type = String, handler = function(self, key, old) if old and _KeyBindingMap[old] == self then _KeyBindingMap[old] = nil end if key and key:upper() ~= key then self.HotKey = key:upper() return end self:SetAttribute("hotKey", key) if key and not self.AutoKeyBinding and _KeyBindingMap[key] ~= self then if _KeyBindingMap[key] then _KeyBindingMap[key].HotKey = nil end _KeyBindingMap[key] = self end return handleKeyBinding(self) end } ------------------------------------------------------ -- Property -- ------------------------------------------------------ --- Whether only bind keys when the button is shown property "AutoKeyBinding" { type = Boolean, handler = function(self, flag) self:SetAttribute("autoKeyBinding", flag and true or nil) if self.HotKey then if flag then if _KeyBindingMap[self.HotKey] == self then _KeyBindingMap[self.HotKey] = nil end elseif _KeyBindingMap[self.HotKey] and _KeyBindingMap[self.HotKey] ~= self then self.HotKey = nil return end end return handleKeyBinding(self) end } ------------------------------------------------------ -- Static Method -- ------------------------------------------------------ --- Start the key binding for all secure action buttons __Static__() __Async__() function StartKeyBinding() _KeyBindingMode = true Next(function() local current while _KeyBindingMode do local button = GetMouseFocus() if button then button = GetProxyUI(button) if IsObjectType(button, SecureActionButton) then _KeyBindingMask:SetParent(button) _KeyBindingMask:SetBindingKey(button.HotKey) _KeyBindingMask:Show() while button:IsMouseOver() and _KeyBindingMode do Next() end _KeyBindingMask:Hide() _KeyBindingMask:SetParent(_ManagerFrame) end end Next() end end) Alert(_Locale["Confirm when you finished the key binding"]) _KeyBindingMode = false _KeyBindingMask:Hide() _KeyBindingMask:SetParent(_ManagerFrame) end ------------------------------------------------------ -- Method -- ------------------------------------------------------ --- Set action for the actionbutton function SetAction(self, kind, target, detail) if kind and not _IFActionTypeHandler[kind] then error("SecureActionButton:SetAction(kind, target, detail) - no such action kind", 2) end if not kind or not target then kind, target, detail= nil, nil, nil else target, detail = _IFActionTypeHandler[kind].Map(self, target, detail) end NoCombat(SaveAction, self, kind, target, detail) end --- Get action for the actionbutton function GetAction(self) return self.ActionType, self.ActionTarget, self.ActionDetail end --- Be called when the action content is changed function Refresh(self) end __SecureMethod__() function UpdateTooltip(self) if not self.ActionType then return end local anchor = self.GameTooltipAnchor if anchor then GameTooltip:SetOwner(self, anchor) else if (GetCVar("UberTooltips") == "1") then GameTooltip_SetDefaultAnchor(GameTooltip, self) else GameTooltip:SetOwner(self, "ANCHOR_RIGHT") end end _IFActionTypeHandler[self.ActionType].SetTooltip(self, GameTooltip) GameTooltip:Show() end __Sealed__() ISecureActionButton = interface { __init = SetupActionButton } extend(ISecureActionButton) end)