local MAJ, REV, COMPAT, ADDON, T, ORI = 4, 126, select(4,GetBuildInfo()), ... local MODERN, CF_WRATH = COMPAT >= 10e4, COMPAT < 10e4 and COMPAT >= 3e4 local EV, api, private = T.Evie, {ActionBook=T.ActionBook}, {} local OR_Rings, OR_LoadedState, OR_ModifierLockState = {}, 1, nil local defaultConfig = { InteractionMode=1, ClickPriority=true, CloseOnRelease=false, NoClose=false, NoCloseOnSlice=false, ReOpenAction=2, IndicationOffsetX=0, IndicationOffsetY=0, RingAtMouse=false, RingScale=1, CenterAction=false, MotionAction=false, QuickActionOnRelease=true, MouseBucket=1, UseDefaultBindings=true, PrimaryButton="BUTTON4", SecondaryButton="BUTTON5", SliceBinding=false, SliceBindingString="1 2 3 4 5 6 7 8 9 0", OpenNestedRingButton="BUTTON3", ScrollNestedRingUpButton="MOUSEWHEELUP", ScrollNestedRingDownButton="MOUSEWHEELDOWN", SelectedSliceBind="", OpenNestedRingButton2="", ScrollNestedRingUpButton2="", ScrollNestedRingDownButton2="", SelectedSliceBind2="", PadSupportMode="none", PSOpenSwitchMode=1, PSRestoreOnClose=true, PSThawHold=0.75, PSThawDuration=4, } local PADSUPPORT_MODE_MAP = {freelook1="freelook"} local configRoot, configInstance, activeProfile, PersistentStorageInfo, optionValidators, optionsMeta = {CharProfiles={}, ProfileStorage={}, PersistentStorage={}}, nil, nil, {}, {}, {__index=defaultConfig} local charId, internalFreeId = ("%s-%s"):format(GetRealmName(), UnitName("player")), 424 local svMigrationState = 0 -- DEPRECATED[2211/Y1] local TB_THRESH local L do local TL = T.L or {} T.L = newproxy(true) L, getmetatable(T.L).__call = T.L, function(_,k,t) return TL[k] or t or k end end local AB, KR, RW = T.ActionBook:compatible(2,42), T.ActionBook:compatible("Kindred", 1,11), T.ActionBook:compatible("Rewire",1,31) do local function createRingAction(name) local ringInfo = type(name) == "string" and OR_Rings[name] return ringInfo and ringInfo.action or nil end local function describeRingAction(name) return L"OPie Ring", OR_Rings[name] and OR_Rings[name].name or name, [[Interface\AddOns\OPie\gfx\opie_ring_icon]], nil, nil, nil, "collection" end AB:RegisterActionType("ring", createRingAction, describeRingAction, 1) AB:AugmentCategory(L"OPie rings", function(_, add) for i=1,#OR_Rings do add("ring", OR_Rings[i]) end end) end local function assert(condition, text, level, ...) return condition or error(tostring(text):format(...), 1 + (level or 1))((0)[0]) end local function copy(t, copies, into) copies, into = copies or {}, into or {} copies[t] = into for k,v in pairs(t) do k = type(k) == "table" and (copies[k] or copy(k, copies)) or k v = type(v) == "table" and (copies[v] or copy(v, copies)) or v into[k] = v end return into end local function tostringf(b) return b and "true" or "nil" end local function getSpecCharIdent(specIdx) local tg = specIdx or MODERN and GetSpecialization() or CF_WRATH and GetActiveTalentGroup() or 1 return (tg == 1 and "%s" or "%s-%d"):format(charId, tg) end local function getNumSpecs() return MODERN and GetNumSpecializations() or CF_WRATH and 2 or 1 end local function getProfile(k) k = configRoot.ProfileStorage[k] and k or "default" return k, configRoot.ProfileStorage[k] end local function getProfileForSpec(specIdx) return getProfile(configRoot.CharProfiles[getSpecCharIdent(specIdx)]) end local function normalizeStoredProfileIdent(ident) return ident ~= "default" and ident or nil end local safequote do local r = {u="\\117", ["{"]="\\123", ["}"]="\\125"} function safequote(s) return (("%q"):format(s):gsub("[{}u]", r)) end end local function getTimeBand(a,b, c,d) local t, p = GetServerTime() if t >= d then return 2 elseif DEV_EARLY_WARNING or t >= b and t <= c then return 1 elseif t <= a then return 0 end TB_THRESH = TB_THRESH or math.random(127)/128 p = t > c and (t-c)/(d-c) or ((t-a)/(b-a)) return (t > b and 1 or 0) + (p^3 > TB_THRESH and 1 or 0) end -- Here be (Secure) Dragons local OR_SecCore = CreateFrame("Button", ("OPieRT-%08x-%04x"):format(time() % 2^30, math.random(2^16)-1), UIParent, "SecureActionButtonTemplate,SecureHandlerAttributeTemplate,SecureHandlerMouseWheelTemplate") local OR_OpenProxy = CreateFrame("Button", "ORLOpen", nil, "SecureActionButtonTemplate") local OR_SecEnv, OR_ActiveRingName, OR_ActiveCollectionID, OR_ActiveSliceCount local OR_PadRestoreState = {} OR_SecCore:SetSize(2^15, 2^15) OR_SecCore:SetFrameStrata("FULLSCREEN_DIALOG") OR_SecCore:RegisterForClicks("AnyUp", "AnyDown") OR_SecCore:EnableMouseWheel(true) OR_SecCore:Hide() OR_SecCore:SetFrameRef("AB", AB:seclib()) OR_SecCore:SetFrameRef("KR", KR:seclib()) OR_SecCore:SetFrameRef("RW", RW:seclib()) local OR_DeferExecute do local queue, qc, qpos = {}, 0 function OR_DeferExecute(block, ...) if block then queue[qc+1], qc = block:format(...), qc + 1 end if InCombatLockdown() or qpos then return end qpos = 1 while qpos <= qc do OR_SecCore:Execute(queue[qpos]) qpos = qpos + 1 end qc, qpos = 0 end end do -- Click dispatcher local lowCH = CreateFrame("Button", ("OPieLBC-%08x-%04x"):format(time() % 2^30, math.random(2^16)-1), nil, "SecureActionButtonTemplate") lowCH:SetFrameStrata("BACKGROUND") lowCH:SetFrameLevel(1) lowCH:RegisterForClicks("AnyDown", "AnyUp") lowCH:SetAllPoints() lowCH:Hide() OR_SecCore:WrapScript(lowCH, "OnClick", "return owner:RunFor(self, ORL_OnClick, button, down)", "owner:RunFor(self, ORL_PostClick, message)") local bindProxy = CreateFrame("Frame", "ORL_BindProxy", nil, "SecureFrameTemplate") OR_SecCore:SetFrameRef("bindProxy", bindProxy) OR_SecCore:SetFrameRef("sliceBindProxy", CreateFrame("Frame", "ORL_BindProxySlice", nil, "SecureFrameTemplate")) OR_SecCore:SetFrameRef("overBindProxy", CreateFrame("Frame", "ORL_BindProxyOverride", nil, "SecureFrameTemplate")) OR_SecCore:SetFrameRef("lowCH", lowCH) OR_SecCore:SetFrameRef("screen", CreateFrame("Frame", nil, nil, "SecureFrameTemplate")) do -- Motion Trap local trap = CreateFrame("Frame", nil, nil, "SecureFrameTemplate") trap:SetFrameStrata("TOOLTIP") trap:SetAllPoints() trap:Hide() OR_SecCore:SetFrameRef("motion0", trap) OR_SecCore:WrapScript(trap, "OnEnter", "return owner:Run(ORL_SetMotionTrapArmed, false, true)") trap:SetMouseClickEnabled(false) for i=1,6 do local f = CreateFrame("Frame", nil, trap, "SecureFrameTemplate") if i < 3 then f:SetSize(1/8, 1/8) f:SetFrameLevel(9001) f:SetMouseMotionEnabled(true) else OR_SecCore:WrapScript(f, "OnEnter", "return owner:Run(ORL_SetMotionTrapArmed, false, true)") end f:SetMouseClickEnabled(false) OR_SecCore:SetFrameRef("motion" .. i, f) end end OR_SecCore:Execute([=[-- OR_SecCore_Init ORL_GlobalOptions, ORL_RingData, ORL_RingDataN = newtable(), newtable(), newtable() ORL_KnownCollections, ORL_StoredCA, ORL_OverSAB = newtable(), newtable(), nil POL_RUN_ONOPEN_ON_SWITCH, POL_RUN_ONOPEN_ON_REOPEN, POL_JUMP_COUNT_LIMIT = true, true, 50 collections, ctokens, rotation, rtokens, fcIgnore, rotationMode, emptyTable = newtable(), newtable(), newtable(), newtable(), newtable(), newtable(), newtable() modState, modLockState, sizeSq, bindProxy, sliceProxy, overProxy = "", nil, 16*9001^2, self:GetFrameRef("bindProxy"), self:GetFrameRef("sliceBindProxy"), self:GetFrameRef("overBindProxy") lowCH = self:GetFrameRef("lowCH") sliceBindState, visitedSlices, buttonBindings = newtable(), newtable(), newtable() BUTTON_NAMEKEY_MAP = newtable() BUTTON_NAMEKEY_MAP.LeftButton, BUTTON_NAMEKEY_MAP.RightButton, BUTTON_NAMEKEY_MAP.MiddleButton = "BUTTON1", "BUTTON2", "BUTTON3" BUTTON_NAMEKEY_MAP.Button4, BUTTON_NAMEKEY_MAP.Button5 = "BUTTON4", "BUTTON5" AIP_Binding, AIP_Key, AIP_Modifier, AIP_Handler, AIP_VirtualKey, AI_NoPointer = nil AIP_Action, AI_LeftAction, AI_RightAction, AIP_ActionFirstUp = nil AIP_OnDown, AI_LeftOnDown, AI_RightOnDown = nil AI_MotionArmed, AI_MotionArmedFC = false, false AI_ClickStack, AI_ClickStackIndex = newtable(), 0 AB, KR, RW = self:GetFrameRef("AB"), self:GetFrameRef("KR"), self:GetFrameRef("RW") SCREEN, CENTERED_CURSOR_YPOS, MOTION_TRAP = self:GetFrameRef("screen"), "0.6", newtable() SCREEN:SetAllPoints() SCREEN:Hide() for i=0,6 do MOTION_TRAP[i] = self:GetFrameRef("motion" .. i) end pmode_RotationModeMap = newtable() do local m = pmode_RotationModeMap m[20] = "cycle" m[36] = "shuffle" m[52] = "random" m[68] = "reset" m[84] = "jump" end ORL_SetMotionTrapArmed = [==[-- ORL_SetMotionTrapArmed local arm, armFC = ... if not arm then MOTION_TRAP[0]:Hide() AI_MotionArmed, AI_MotionArmedFC = false, false if armFC and AI_LeftAction then bindProxy:SetBindingClick(true, "BUTTON1", owner, "BUTTON1") lowCH:Hide() end return end local x, y = SCREEN:GetMousePosition() local m, w, h = 0.125, SCREEN:GetWidth(), SCREEN:GetHeight() x, y = x*w, y*h for i=1,6 do MOTION_TRAP[i]:ClearAllPoints() end MOTION_TRAP[1]:SetPoint("CENTER", SCREEN, "BOTTOM", 0, h * CENTERED_CURSOR_YPOS) MOTION_TRAP[2]:SetPoint("CENTER", SCREEN, "BOTTOMLEFT", x, y) MOTION_TRAP[3]:SetPoint("TOPLEFT") MOTION_TRAP[3]:SetPoint("BOTTOMRIGHT", SCREEN, "BOTTOMLEFT", x-m, 0) MOTION_TRAP[4]:SetPoint("TOPLEFT") MOTION_TRAP[4]:SetPoint("BOTTOMRIGHT", SCREEN, "BOTTOMRIGHT", 0, y+m) MOTION_TRAP[5]:SetPoint("TOPLEFT", SCREEN, "TOPLEFT", x+m, 0) MOTION_TRAP[5]:SetPoint("BOTTOMRIGHT") MOTION_TRAP[6]:SetPoint("TOPLEFT", SCREEN, "BOTTOMLEFT", 0, y-m) MOTION_TRAP[6]:SetPoint("BOTTOMRIGHT") MOTION_TRAP[0]:Show() AI_MotionArmed, AI_MotionArmedFC = true, armFC == true ]==] ORL_PrepareCollection = [==[-- ORL_PrepareCollection wipe(collections) wipe(ctokens) local firstFC, root, colData = nil, ..., AB:RunAttribute("GetCollectionContent", ...) for cid, i, aid, tok, pmode in (colData or ""):gmatch("\n(%d+) (%d+) (%d+) (%S+) (%d+)") do cid, i, aid, pmode = tonumber(cid), tonumber(i), tonumber(aid), tonumber(pmode) or 0 if not collections[cid] then collections[cid], ctokens[cid] = newtable(), newtable() end if pmode > 0 then local b01 = pmode % 4 fcIgnore[tok] = b01 >= 2 or nil if pmode % 8 >= 4 then rotationMode[tok] = pmode_RotationModeMap[pmode % 128 - b01] end end collections[cid][i], ctokens[cid][i], ctokens[cid][tok], firstFC = aid, tok, i, firstFC or (cid == root and not fcIgnore[tok] and tok) end collections[root], ctokens[root] = collections[root] or emptyTable, ctokens[root] or emptyTable for cid, list in pairs(collections) do for i, aid in pairs(list) do if collections[aid] then local tok = ctokens[cid][i] local rMode, rIdx, tIdx = rotationMode[tok] local isStoredRotation = rMode ~= "reset" and rMode ~= "jump" if isStoredRotation then rIdx = ctokens[aid][rtokens[tok]] if (rMode == "random" or rMode == "shuffle" and not rIdx) and #ctokens[aid] > 1 then tIdx = math.random(#ctokens[aid] - (rIdx and 1 or 0)) rIdx = rIdx and tIdx >= rIdx and (tIdx + 1) or tIdx end end rotation[tok], rtokens[tok] = rIdx or 1, isStoredRotation and ctokens[aid][rIdx] or rtokens[tok] or nil end end end return collections[root][0], firstFC ]==] ORL_CloseActiveRing = [[-- ORL_CloseActiveRing local old, shouldKeepOwner, selSliceIndex, selSliceAction = activeRing, ... if not shouldKeepOwner then owner:Hide() end bindProxy:ClearBindings() sliceProxy:ClearBindings() activeRing, openCollection, openCollectionID = nil AIP_Action, AIP_ActionFirstUp, AI_LeftAction, AI_RightAction = nil AIP_OnDown, AI_LeftOnDown, AI_RightOnDown = nil AIP_Binding, AIP_Key, AIP_Modifier, AIP_Handler, AIP_VirtualKey = nil self:Run(ORL_SetMotionTrapArmed, false) lowCH:Hide() owner:CallMethod("NotifyState", "close", old.name, old.action, selSliceIndex, selSliceAction) ]] ORL_RegisterVariations = [[-- ORL_RegisterVariations local binding, mapkey, downmix, skipKey = ... local bindKey = binding and binding:match("[^-]*.$") if skipKey and bindKey == skipKey or (binding or "") == "" then return end if bindKey:match("^BUTTON%d+$") then buttonBindings[binding] = mapkey end for alt=0,downmix:match("ALT") and 1 or 0 do for ctrl=0,downmix:match("CTRL") and 1 or 0 do for shift=0,downmix:match("SHIFT") and 1 or 0 do for meta=0,downmix:match("META") and 1 or 0 do self:SetBindingClick(true, (alt == 1 and "ALT-" or "") .. (ctrl == 1 and "CTRL-" or "") .. (shift == 1 and "SHIFT-" or "") .. (meta == 1 and "META-" or "") .. binding, owner, mapkey) end end end end ]] ORL_CheckSliceBindings = [==[-- ORL_CheckSliceBindings local usedKeys, uncombine, selectedSliceBindings = newtable(), false, newtable() selectedSliceBindings[0], selectedSliceBindings[-1] = ... for j=0, 1 do for i, b in pairs(j == 0 and selectedSliceBindings or j > 0 and activeRing.SliceBinding2 or emptyTable) do local bk = b and b:match("[^-]*.$") local uk = usedKeys[bk] if uk then uncombine = uncombine or newtable() uncombine[uk], uncombine[i] = 1, 1 else usedKeys[bk or 0] = i end end end activeRing.SliceBindingUncombine = uncombine return uncombine and true ]==] ORL_UpdateInteractionBindings = [==[-- ORL_UpdateInteractionBindings local setToVirtualKey = ... local interactKey = AIP_Binding == "-" and "-" or (AIP_Binding or ""):match("[^-]*.$") if setToVirtualKey then AIP_Binding, AIP_Key, AIP_Modifier, AIP_Handler, AIP_VirtualKey = AIP_Binding, interactKey, interactKey ~= AIP_Binding and AIP_Binding or nil, self, setToVirtualKey end bindProxy:ClearBindings() wipe(buttonBindings) wheelBucket = 0 local avoidPrimary = AIP_Action and AIP_Action ~= "close" and interactKey or nil local down, up, open = ORL_GlobalOptions.ScrollNestedRingDownBinding or "", ORL_GlobalOptions.ScrollNestedRingUpBinding or "", ORL_GlobalOptions.OpenNestedRingBinding or "" local down2, up2, open2 = ORL_GlobalOptions.ScrollNestedRingDownBinding2 or "", ORL_GlobalOptions.ScrollNestedRingUpBinding2 or "", ORL_GlobalOptions.OpenNestedRingBinding2 or "" owner:RunFor(bindProxy, ORL_RegisterVariations, down, down:match("MOUSEWHEEL") and "mwdownW" or "mwdownK", "ALT-CTRL-SHIFT", avoidPrimary) owner:RunFor(bindProxy, ORL_RegisterVariations, down2, down2:match("MOUSEWHEEL") and "mwdownW" or "mwdownK", "ALT-CTRL-SHIFT", avoidPrimary) owner:RunFor(bindProxy, ORL_RegisterVariations, up, up:match("MOUSEWHEEL") and "mwupW" or "mwupK", "ALT-CTRL-SHIFT", avoidPrimary) owner:RunFor(bindProxy, ORL_RegisterVariations, up2, up2:match("MOUSEWHEEL") and "mwupW" or "mwupK", "ALT-CTRL-SHIFT", avoidPrimary) owner:RunFor(bindProxy, ORL_RegisterVariations, open, "mwin", "ALT-CTRL-SHIFT", avoidPrimary) owner:RunFor(bindProxy, ORL_RegisterVariations, open2, "mwin", "ALT-CTRL-SHIFT", avoidPrimary) sliceProxy:ClearBindings() wipe(sliceBindState) local sliceBindings = activeRing.SliceBinding or false local selectedSliceBinding, selectedSliceBinding2 = activeRing.SelectedSliceBind or "", activeRing.SelectedSliceBind2 or "" if sliceBindings or selectedSliceBinding ~= "" or selectedSliceBinding2 ~= "" then local prefix = AIP_Action and AIP_Binding and AIP_Binding:match("^(.-)[^-]*%-?$") or "" local uncombine = prefix ~= "" and (sliceBindings or selectedSliceBinding ~= "" and selectedSliceBinding2 ~= "") and activeRing.SliceBindingUncombine uncombine = uncombine == nil and owner:Run(ORL_CheckSliceBindings, selectedSliceBinding, selectedSliceBinding2) and activeRing.SliceBindingUncombine or uncombine for i=1, sliceBindings and #openCollection or 0 do owner:RunFor(sliceProxy, ORL_RegisterVariations, sliceBindings[i], "slice" .. i, not (uncombine and uncombine[i]) and prefix or "") owner:RunFor(sliceProxy, ORL_RegisterVariations, sliceBindings[i+0.5], "slice" .. i, not (uncombine and uncombine[i+0.5]) and prefix or "") end owner:RunFor(sliceProxy, ORL_RegisterVariations, selectedSliceBinding, "usenow", not (uncombine and uncombine[0]) and prefix or "") owner:RunFor(sliceProxy, ORL_RegisterVariations, selectedSliceBinding2, "usenow", not (uncombine and uncombine[-1]) and prefix or "") end if AIP_Binding and AIP_Action then bindProxy:SetBindingClick(true, AIP_Binding, AIP_Handler, AIP_VirtualKey) end lowCH[AI_LeftAction and "Show" or "Hide"](lowCH) if AI_RightAction and AIP_Binding ~= "BUTTON2" and not AI_NoPointer then bindProxy:SetBindingClick(true, "BUTTON2", owner, "BUTTON2") end bindProxy:SetBindingClick(true, "ESCAPE", owner, "close") ]==] ORL_OpenRing = [==[-- ORL_OpenRing local ring, ringID, openCause, ocIndex = ORL_RingData[...], ... local fastSwitch, cid, setToVirtualKey = activeRing == ring and ring.ReOpenAction, ring.action if fastSwitch == 2 or fastSwitch == 1 and openCollectionID == cid then return false, owner:Run(ORL_CloseActiveRing) elseif activeRing and not fastSwitch then -- If the click-capturing overlay gets the binding DOWN event, only *it* will be notified of the corresponding UP. owner:Run(ORL_CloseActiveRing, self == owner) end AI_NoPointer = ring.NoPointer if openCause == "primary-binding" then AIP_Binding, setToVirtualKey = ring[ocIndex == 2 and "bind2" or "bind"], "r" .. ringID .. (ocIndex == 2 and "b" or "") AIP_Action, AIP_ActionFirstUp = ring.PrimaryAction, ring.PrimaryActionUp AI_LeftAction, AI_RightAction = ring.LeftAction, ring.RightAction AIP_OnDown, AI_LeftOnDown, AI_RightOnDown = AIP_Action == "close", false, true elseif openCause == "override-binding" then AIP_Binding, setToVirtualKey = bindOverrides[ringID], "ro" .. ringID AIP_Action, AIP_ActionFirstUp, AI_LeftAction, AI_RightAction = "use", nil, nil, "close" AIP_OnDown, AI_LeftOnDown, AI_RightOnDown, AI_NoPointer = false, false, true, false elseif openCause == "open-macro" then AIP_Binding, AIP_Key, AIP_Modifier, AIP_Handler, AIP_VirtualKey = nil AIP_Action, AIP_ActionFirstUp = nil, nil AI_LeftAction, AI_RightAction = ring.LeftAction2, ring.RightAction2 AIP_OnDown, AI_LeftOnDown, AI_RightOnDown = false, false, true end if AI_NoPointer then AI_LeftOnDown, AI_RightAction = nil end local stickyPrimaryMods = AIP_Action and AIP_Action ~= "close" modLockState, modState = KR:RunAttribute("ComputeConditionalLock", stickyPrimaryMods and AIP_Binding or "", "1", (not stickyPrimaryMods and "") or nil) local openAction, firstFC = owner:Run(ORL_PrepareCollection, cid) activeRing, openCollection, openCollectionID = ring, collections[cid], cid if ORL_StoredCA[ring.name] and not ring.fcToken then ring.fcToken, ORL_StoredCA[ring.name] = ORL_StoredCA[ring.name] end fastClick = (ring.CenterAction or ring.MotionAction) and ((not fcIgnore[ring.fcToken] and ctokens[cid][ring.fcToken]) or (ring.OpprotunisticCA and ctokens[cid][firstFC])) or nil fastClick = not AI_NoPointer and fastClick ~= 0 and fastClick or nil owner:SetScale(ring.scale) owner:SetPoint('CENTER', ring.ofs, ring.ofsx/owner:GetEffectiveScale(), ring.ofsy/owner:GetEffectiveScale()) owner:RunFor(self, ORL_UpdateInteractionBindings, setToVirtualKey) if ring.ClickPriority and not AI_NoPointer then owner:Show() lowCH:Hide() end local armMotionFC = fastClick and ring.MotionAction and true local needMotionTrap = armMotionFC or AI_LeftAction or AI_MotionArmed owner:CallMethod("NotifyState", "open", ring.name, ring.action, fastClick, fastSwitch and true, modLockState) owner:Run(ORL_SetMotionTrapArmed, needMotionTrap, armMotionFC) if openCollection[0] and (POL_RUN_ONOPEN_ON_REOPEN or not fastSwitch) then return owner:RunFor(self, ORL_PerformSliceAction, 0, false, true, "on-open") end return false ]==] ORL_SwitchRing = [==[-- ORL_SwitchRing local colID, switchCause = ... local ringID = ORL_KnownCollections[colID] local col, ring = collections[colID], ORL_RingData[ringID] if not col then owner:CallMethod("throw", "Cannot switch to unknown collection " .. tostring(colID)) return false end local ring2 = ring or activeRing -- non-ring collections inherit preferences if switchCause == "switch-binding" then elseif switchCause == "jump-slice-release" or switchCause == "jump-slice-used" or switchCause == "jump-slice-switch" then AI_NoPointer = ring2.NoPointer -- override binding could have deferred this AIP_Binding, AIP_Key, AIP_Modifier, AIP_Handler, AIP_VirtualKey = nil AIP_Action, AIP_ActionFirstUp = (AIP_Action or "noop") ~= "noop" and "close" or nil, nil AI_LeftAction, AI_RightAction = ring2.LeftAction2, ring2.RightAction2 if AI_NoPointer then AI_LeftAction, AI_RightAction = nil, nil end AIP_OnDown, AI_LeftOnDown, AI_RightOnDown = true, false, true modLockState, modState = nil, "" end activeRing = ORL_RingData[ringID] or activeRing openCollection, openCollectionID, fastClick = col, colID, nil owner:RunFor(self, ORL_UpdateInteractionBindings, nil) owner:CallMethod("NotifyState", "switch", ring and ring.name, openCollectionID, fastClick, true, modLockState) if openCollection[0] and POL_RUN_ONOPEN_ON_SWITCH and (ORL_JumpCount or 0) < POL_JUMP_COUNT_LIMIT then return owner:RunFor(self, ORL_PerformSliceAction, 0, false, true, "switch-onopen") end return false ]==] ORL_GetCursorSlice = [[-- ORL_GetCursorSlice if not openCollection[1] or AI_NoPointer then return nil end local psm = IsGamePadEnabled and IsGamePadEnabled() and ORL_GlobalOptions.PadSupportMode if psm == "freelook" and SCREEN:GetMousePosition() == 0.5 then local ms = GetGamePadState() local st = ms and ms.sticks st = st and st[ORL_GlobalOptions.PSStickIndex] if st then local radius, angle = 10000*st.len, math.deg(math.atan2(st.y, st.x)) if radius > 2500 then local segAngle = 360/#openCollection return floor(((90 - angle + segAngle/2 - activeRing.ofsDeg) % 360) / segAngle) + 1, false elseif radius < 100 then return fastClick, true else return nil, false end end end if AI_MotionArmedFC then return fastClick, true end local x, y = owner:GetMousePosition() x, y = x - 0.5, y - 0.5 local radius, segAngle = (x*x*sizeSq + y*y*sizeSq)^0.5, 360/#openCollection if radius >= 40 then return floor(((math.deg(math.atan2(x, y)) + segAngle/2 - activeRing.ofsDeg) % 360) / segAngle) + 1, false elseif radius <= 20 and activeRing.CenterAction then return fastClick, true end ]] ORL_ResolveNestedSlice = [==[-- ORL_ResolveNestedSlice local visitedSlices, col, index, willPerform = wipe(visitedSlices), ... while 1 do local aid, ct = collections[col] and collections[col][index], ctokens[col] and ctokens[col][index] if visitedSlices[ct] or not aid then return elseif not collections[aid] then if willPerform then for ict, icol in pairs(visitedSlices) do local ort = rtokens[ict] local irMode, ctk = rotationMode[ict], ctokens[icol] if #ctk <= 1 then elseif irMode == "cycle" then local nid = (rotation[ict] or 0) % #ctk + 1 rotation[ict], rtokens[ict] = nid, ctk[nid] elseif irMode == "shuffle" then local oid = rotation[ict] local nid = math.random(#ctk - (oid and 1 or 0)) nid = nid + (oid and nid >= oid and 1 or 0) rotation[ict], rtokens[ict] = nid, ctk[nid] end end end return aid, "act" elseif rotationMode[ct] == "jump" then return aid, "jump" end visitedSlices[ct], col, index = aid, aid, rotation[ct] or 1 end ]==] ORL_PerformSliceAction = [[-- ORL_PerformSliceAction local pureSlice, shouldUpdateFastClick, noClose, interactionSource = ... local pureToken = ctokens[openCollectionID][pureSlice] local action, at = owner:Run(ORL_ResolveNestedSlice, openCollectionID, pureSlice, true) activeRing.fcToken = shouldUpdateFastClick and (activeRing.CenterAction or activeRing.MotionAction) and not fcIgnore[pureToken] and pureToken or activeRing.fcToken if at == "jump" then ORL_JumpCount = (ORL_JumpCount or 0) + 1 return owner:RunFor(self, ORL_SwitchRing, action, interactionSource == "primary-binding" and "jump-slice-release" or "jump-slice-used") end if not noClose then owner:Run(ORL_CloseActiveRing, nil, pureSlice, action) end if action then local w, p, s = ORL_OverSAB or self, AI_ClickStackIndex + 1 s = AI_ClickStack[p] or newtable() w:SetAttribute("pressAndHoldAction", 1) w:SetAttribute("type", "macro") w:SetAttribute("typerelease", "macro") w:SetAttribute("macrotext", AB:RunAttribute("UseAction", action, modLockState)) s[1], s[2] = AB:GetAttribute("execID"), RW:GetAttribute("execPending") AI_ClickStack[p], AI_ClickStackIndex = s, p return action, AI_ClickStackIndex end return false ]] ORL_OnWheel = [==[-- ORL_OnWheel local slice = owner:Run(ORL_GetCursorSlice) local nestedCol = collections[openCollection[slice]] local rMode = rotationMode[(ctokens[openCollectionID] or emptyTable)[slice]] if not (slice and nestedCol and rMode ~= "jump") then return end if slice ~= wheelSlice then wheelSlice, wheelBucket = slice, 0 end wheelBucket = wheelBucket + (...) if abs(wheelBucket) >= activeRing.bucket then wheelBucket = 0 else return end local stoken, step, count, c = ctokens[openCollectionID][slice], (...) > 0 and 0 or -2, #nestedCol, 0 repeat rotation[stoken], c = (rotation[stoken] + step) % count + 1, c + 1 until owner:Run(ORL_ResolveNestedSlice, openCollectionID, slice, false) or c == count rtokens[stoken] = (ctokens[openCollection[slice]] or emptyTable)[rotation[stoken]] or rtokens[stoken] ]==] ORL_CheckButtonBindings = [==[-- ORL_CheckButtonBindings local lvalue, lkey, BUTTON, checkRingBindings = 0, nil, ... for bind, vbtn in pairs(buttonBindings) do local pm, btn = bind:match("()([^-]*%-?)$") if btn == BUTTON and #bind > lvalue and (pm == 1 or IsModifiedClick(bind)) then lkey, lvalue = vbtn, #bind end end if checkRingBindings and not lkey then for k, v in pairs(ORL_RingData) do if v.bindButton == BUTTON and (v.bindModifiedClick == nil or IsModifiedClick(v.bindModifiedClick)) and #v.bind > lvalue then lkey, lvalue = "r" .. k, #v.bind elseif v.bind2Button == BUTTON and (v.bind2ModifiedClick == nil or IsModifiedClick(v.bind2ModifiedClick)) and #v.bind2 > lvalue then lkey, lvalue = "r" .. k .. "b", #v.bind2 end end end return lkey ]==] ORL_OnClick = [==[-- ORL_OnClick local button, down = ... local BUTTON = BUTTON_NAMEKEY_MAP[button] or button and button:upper() local isActiveRingTriggerClick = (AIP_VirtualKey and AIP_VirtualKey == button) or (AIP_Key and AIP_Key == BUTTON and (AIP_Modifier == nil or IsModifiedClick(AIP_Modifier))) local openHotkeyOverride, openHotkeyId, openHotkeyV = button:match("^r(o?)(%d+)(b?)$") openHotkeyId = tonumber(openHotkeyId) if isActiveRingTriggerClick and (AIP_Action or AIP_ActionFirstUp and not down) then button = down == AIP_OnDown and AIP_Action or AIP_ActionFirstUp or "noop" -- If hovering over a nested ring/jump slice, allow global mwin/up/down bindings to override the ring binding (when down) local globalAction = down and owner:Run(ORL_CheckButtonBindings, (AIP_VirtualKey == ... and AIP_Key or BUTTON), false) local pointerAID = globalAction and openCollection[owner:Run(ORL_GetCursorSlice)] if collections[pointerAID] or ORL_KnownCollections[pointerAID] then -- Global override wins; ignore future (primary binding, up) button, AIP_ActionFirstUp = globalAction, nil end elseif BUTTON == "BUTTON1" and activeRing and AI_LeftAction then button = down == AI_LeftOnDown and AI_LeftAction or "noop" elseif BUTTON == "BUTTON2" and AI_RightAction then button = down == AI_RightOnDown and AI_RightAction or "noop" end if button == "noop" then return false elseif activeRing and (button == "use" or button == "useFC") then local slice, isFC = owner:Run(ORL_GetCursorSlice) if button == "useFC" and not (isFC and slice) then return false end return owner:RunFor(self, ORL_PerformSliceAction, slice, openCollectionID == activeRing.action, false, isActiveRingTriggerClick and "primary-binding") elseif activeRing and button == "usenow" then return owner:RunFor(self, ORL_PerformSliceAction, owner:Run(ORL_GetCursorSlice), false, true, "use-binding") elseif activeRing and button == "close" then return false, owner:Run(ORL_CloseActiveRing) elseif activeRing and button:match("^slice%d+") then local b = tonumber(button:match("slice(%d+)")) if openCollection and openCollection[b] then if down then sliceBindState[b] = true elseif sliceBindState[b] then return owner:RunFor(self, ORL_PerformSliceAction, b, true, activeRing.NoCloseOnSlice, "slice-binding") end end elseif activeRing and button == "mwin" and down then local slice = owner:Run(ORL_GetCursorSlice) local aid, rt = openCollection[slice], rotationMode[(ctokens[openCollectionID] or "")[slice]] if collections[aid] then return owner:RunFor(self, ORL_SwitchRing, aid, rt == "jump" and "jump-slice-switch" or "switch-binding") end elseif activeRing and button:match("^mw[ud]") then return false, down and owner:Run(ORL_OnWheel, (button:match("^mwup") and 1 or -1) * (button:match("K$") and activeRing.bucket or 1)) elseif BUTTON:match("^BUTTON%d+$") then -- The click-capturing overlay captures all mouse clicks, including bindings local lkey = owner:Run(ORL_CheckButtonBindings, BUTTON, true) if lkey then return owner:RunFor(self, ORL_OnClick, lkey, down) end elseif openHotkeyId and down then local isOverrideBinding = openHotkeyOverride == "o" and bindOverrides[openHotkeyId] return owner:RunFor(self, ORL_OpenRing, openHotkeyId, isOverrideBinding and "override-binding" or "primary-binding", openHotkeyV == "b" and 2 or 1) end return false ]==] ORL_PostClick = [[-- ORL_PostClick local message = ... local cs, oi, bad = AI_ClickStack[message], AI_ClickStackIndex self:SetAttribute('type', nil) self:SetAttribute('typerelease', nil) AI_ClickStackIndex, ORL_JumpCount = oi-1 bad = message ~= oi or cs == nil or AB:GetAttribute("execID") == cs[1] if bad or (cs[2] or 0) < (RW:GetAttribute("execPending") or 0) then owner:CallMethod("BadLizardError") end ]] ORL_OpenClick = [[-- ORL_OpenClick local rdata = ORL_RingDataN[...] if not rdata then return false, print('|cffff0000[OPie] Cannot open unknown ring "' .. tostring((...)) .. '".') end ORL_OverSAB = self -- Don't RunFor the /click handler button, as it won't fire an UP return owner:Run(ORL_ClearOverSAB, owner:Run(ORL_OpenRing, rdata.id, "open-macro")) ]] ORL_ClearOverSAB = [[-- ORL_ClearOverSAB ORL_OverSAB = nil return ... ]] ]=]) OR_SecCore:SetAttribute("_onmousewheel", [[-- OR_OnMouseWheel local wheelDirection = delta == 1 and "UP" or "DOWN" local bind = ORL_GlobalOptions.ScrollNestedRingDownBinding for bindDelta=-1,1,2 do if bind and bind:match("MOUSEWHEEL([UPDOWN]+)$") == wheelDirection and (bind:match("%-") == nil or IsModifiedClick(bind)) then delta = bindDelta break end bind = ORL_GlobalOptions.ScrollNestedRingUpBinding end return self:Run(ORL_OnWheel, delta) ]]) OR_SecCore:WrapScript(OR_SecCore, "OnClick", "return self:Run(ORL_OnClick, button, down)", "owner:RunFor(self, ORL_PostClick, message)") OR_SecCore:WrapScript(OR_OpenProxy, "OnClick", "return owner:RunFor(self, ORL_OpenClick, button)", "owner:RunFor(self, ORL_PostClick, message)") OR_SecCore:WrapScript(bindProxy, "OnAttributeChanged", [[-- ORL.BindProxy-OnAttributeChanged local rid, bv, data = (type(name) == "string" and name or ""):match("^binding%-r(%d+)(b?)$") data = ORL_RingData[rid and rid+0] if data then local bind, bindButton, bindModifiedClick if (value or "") ~= "" then local modifiedClick, modifiers, undash, button = value:match("^((.-)([^-]?)(BUTTON%d+))$") if undash ~= "" or modifiedClick == button then modifiedClick, modifiers = nil end bind, bindButton, bindModifiedClick = value, button, modifiedClick end if bv == "b" then data.bind2, data.bind2Button, data.bind2ModifiedClick = bind, bindButton, bindModifiedClick else data.bind, data.bindButton, data.bindModifiedClick = bind, bindButton, bindModifiedClick end end ]]) function OR_SecCore:throw(err) return error(err, 2) end OR_SecEnv = GetManagedEnvironment(OR_SecCore) end local function OR_GetRingOption(ringName, option) if not configInstance then return defaultConfig[option], nil, nil, nil, defaultConfig[option] end local ring, global, default, value, setting = configInstance.RingOptions[ringName and (ringName .. "#" .. option)], rawget(configInstance, option), defaultConfig[option] if ringName ~= nil then setting = ring else setting = global end if ringName ~= nil and ring ~= nil then value = ring elseif ring == nil and global ~= nil then value = global else value = default end return value, setting, ring, global, default end local OR_SyncRingBinding do -- Binding management local bindingEncodeChars = {["["]="OPEN", ["]"]="CLOSE", [";"]="SEMICOLON"} local encodedBindings = {OPEN="[", CLOSE="]", SEMICOLON=";"} local function RemoveConflictingBindings(bind) local rawBind = bind:gsub("%a+$", encodedBindings) return GetBindingAction(rawBind) ~= "" and ";" or nil end function OR_SyncRingBinding(name, props) if InCombatLockdown() then return end local hotkey, isSoftBinding, id = configInstance and configInstance.Bindings[name], false, props.internalID local hotkey2 = configInstance and configInstance.Bindings2[name] if hotkey == nil and type(props.hotkey) == "string" and OR_GetRingOption(name, "UseDefaultBindings") then hotkey, isSoftBinding = props.hotkey, true end if hotkey then if not hotkey:match("%[.*%]") then hotkey = "[] " .. hotkey:gsub("([^-]+)%s*$", bindingEncodeChars) end if isSoftBinding then hotkey = (hotkey .. ";"):gsub("([^%s%[%];]+)%s*;", RemoveConflictingBindings):sub(1,-2) end end if hotkey2 and not hotkey2:match("%[.*%]") then hotkey2 = "[] " .. hotkey2:gsub("([^-]+)%s*$", bindingEncodeChars) end if not OR_SecCore:GetAttribute("frameref-proxy" .. id) then local f = CreateFrame("Button", "ORL_RProxy" .. id, nil, "SecureActionButtonTemplate") f:RegisterForClicks("AnyUp", "AnyDown") OR_SecCore:WrapScript(f, "OnClick", "return owner:RunFor(self, ORL_OnClick, button, down)", 'owner:RunFor(self, ORL_PostClick, message)') OR_SecCore:SetFrameRef("proxy" .. id, f) local bk, lab = "BINDING_NAME_CLICK ".. f:GetName() .. ":r" .. id, (L"OPie ring: %s"):format(props.name or "?") _G[bk], _G[bk .. "b"] = lab, lab end if props.currentBindingConditional == hotkey and props.currentBindingConditional2 == hotkey2 and props.currentBindingSoft == isSoftBinding then return end props.currentBindingConditional, props.currentBindingConditional2, props.currentBindingSoft = hotkey, hotkey2, isSoftBinding OR_DeferExecute([=[-- OR_SyncRingBinding local ringName, internalId = %s, %d local clickProxy = self:GetFrameRef("proxy" .. internalId) KR:SetAttribute("frameref-RegisterBindingDriver-target", clickProxy) KR:SetAttribute("frameref-RegisterBindingDriver-notify", bindProxy) KR:RunAttribute("RegisterBindingDriver", "r" .. internalId, %s, %s) KR:SetAttribute("frameref-RegisterBindingDriver-target", clickProxy) KR:SetAttribute("frameref-RegisterBindingDriver-notify", bindProxy) KR:RunAttribute("RegisterBindingDriver", "r" .. internalId .. "b", %s, 10) ]=], safequote(name), id, safequote((hotkey or "") .. ";"), isSoftBinding and "-12" or "-10", safequote((hotkey2 or "") .. ";")) end OR_SecCore:Execute([=[-- BindingInit bindOverrides = newtable() RegisterStateDriver(self, "combat", "[combat] combat; nocombat") ORL_RegisterOverride = [[-- ORL_RegisterOverride local id, bind = ... if bindOverrides[id] then overProxy:ClearBinding(bindOverrides[id]) end if bindOverrides[bind] then bindOverrides[ bindOverrides[bind] ] = nil end if bind then overProxy:SetBindingClick(true, bind, self:GetFrameRef("proxy" .. id), "ro" .. id) bindOverrides[bind] = ring end bindOverrides[id] = bind ]] ]=]) OR_SecCore:SetAttribute("_onattributechanged", [=[-- ORL_UpdateBinding if name == "state-combat" and value == "combat" then overProxy:ClearBindings() wipe(bindOverrides) end ]=]) OR_SecCore.BadLizardError = GenerateClosure(error, "bad lizard", 2) end local function OR_SyncRing(name, actionId, newprops) local props = OR_Rings[name] or {} if not OR_Rings[name] then OR_Rings[name], OR_Rings[#OR_Rings+1], props.internalID, props.internalName, props.internalAP, internalFreeId = props, name, internalFreeId, name, #OR_Rings+1, internalFreeId+1 end if newprops then props.action, props.offset, props.name, props.hotkey, props.internal = actionId, newprops.offset or 0, newprops.name or name, newprops.hotkey, newprops.internal local fcBlock = "" for i=1,#newprops do -- DEPRECATED[2306/Y11]: Slice presentation mode hints in ActionBook's collections override these values. if newprops[i].sliceToken then local pMode, rMode = newprops[i].rotationMode, nil if pMode and (pMode == "random" or pMode == "shuffle" or pMode == "cycle" or pMode == "reset" or pMode == "jump") then rMode = pMode end fcBlock = fcBlock .. ("fcIgnore[%s], rotationMode[%1$s] = %s, %s "):format(safequote(newprops[i].sliceToken), tostringf(not newprops[i].fastClick), rMode and safequote(rMode) or "nil") end end props.fcBlock, props.opportunisticCA, props.noPersistentCA = fcBlock, not newprops.noOpportunisticCA, newprops.noPersistentCA props.sortScope = type(newprops.sortScope) == "number" and newprops.sortScope or 2 end local imode, noClose = OR_GetRingOption(name, "InteractionMode"), OR_GetRingOption(name, "NoClose") local sliceBindings = (imode == 3 or OR_GetRingOption(name, "SliceBinding")) and OR_GetRingOption(name, "SliceBindingString") local centerAction, motionAction = OR_GetRingOption(name, "CenterAction"), OR_GetRingOption(name, "MotionAction") local primaryAction = imode == 1 and "use" or imode == 2 and OR_GetRingOption(name, "CloseOnRelease") and "close" local primaryActionUp = imode == 2 and (centerAction or motionAction) and OR_GetRingOption(name, "QuickActionOnRelease") and "useFC" local leftAction = imode == 2 and (noClose and "usenow" or "use") local leftAction2 = imode ~= 3 and (noClose and "usenow" or "use") local rightAction = imode ~= 3 and "close" OR_DeferExecute([[-- SyncRing local ringName, internalId, actionId = %s, %d, %d local data = ORL_RingData[internalId] or newtable() ORL_KnownCollections[actionId], ORL_RingData[internalId], ORL_RingDataN[ringName], data.action, data.name, data.id = internalId, data, data, actionId, ringName, internalId data.name, data.ofs, data.ofsx, data.ofsy, data.ofsDeg = ringName, %q, %d, %d, %f data.CenterAction, data.MotionAction, data.ClickPriority, data.ReOpenAction = %s, %s, %s, %d data.NoClose, data.NoCloseOnSlice, data.NoPointer = %s, %s, %s data.PrimaryAction, data.PrimaryActionUp = %s, %s data.LeftAction, data.LeftAction2 = %s, %s data.RightAction, data.RightAction2 = %s, %s data.scale, data.bucket = %f, %d local sbs = %s local sb, bn = sbs and newtable() or false, 1 for s, s2 in (sbs or ""):gmatch("([^%%s\31]+)\31?(%%S*)") do -- NB: in string.format s = not (s == "false" or s:match("[Bb][Uu][Tt][Tt][Oo][Nn][123]$") or s:match("[Ee][Ss][Cc][Aa][Pp][Ee]$")) and s s2 = not (s2 == "false" or s2 == "" or s2:match("[Bb][Uu][Tt][Tt][Oo][Nn][123]$") or s2:match("[Ee][Ss][Cc][Aa][Pp][Ee]$")) and s2 sb[bn], sb[bn+0.5], bn = s, s2, bn + 1 end data.SliceBinding, data.SliceBindingUncombine, data.SelectedSliceBind, data.OpprotunisticCA = sb, nil, %s, %s %s ]], safequote(name), props.internalID, props.action, OR_GetRingOption(name, "RingAtMouse") and "$cursor" or "$screen", OR_GetRingOption(name, "IndicationOffsetX"), -OR_GetRingOption(name, "IndicationOffsetY"), props.offset, tostringf(centerAction), tostringf(motionAction), tostringf(OR_GetRingOption(name, "ClickPriority")), OR_GetRingOption(name, "ReOpenAction") % 1e6, tostringf(noClose), tostringf(OR_GetRingOption(name, "NoCloseOnSlice")), tostringf(imode == 3), primaryAction and safequote(primaryAction) or "nil", primaryActionUp and safequote(primaryActionUp) or "nil", leftAction and safequote(leftAction) or "nil", leftAction2 and safequote(leftAction2) or "nil", rightAction and safequote(rightAction) or "nil", rightAction and safequote(rightAction) or "nil", math.max(0.1, (OR_GetRingOption(name, "RingScale"))), (OR_GetRingOption(name, "MouseBucket")), (sliceBindings or "") ~= "" and safequote(sliceBindings) or "nil", safequote(OR_GetRingOption(name, "SelectedSliceBind") or ""), tostringf(props.opportunisticCA), props.fcBlock or "") OR_SyncRingBinding(name, props) if newprops and AB then AB:NotifyObservers("ring") end end local function OR_DeleteRing(name, data) OR_DeferExecute([[-- DeleteRing local ringName, internalId, actionId = %s, %d, %d self:SetAttribute("state-r" .. internalId, nil) ORL_KnownCollections[actionId], ORL_RingData[internalId], ORL_RingDataN[ringName] = nil KR:SetAttribute("frameref-UnregisterBindingDriver-target", self:GetFrameRef("proxy" .. internalId)) KR:RunAttribute("UnregisterBindingDriver", "r" .. internalId) ]], safequote(name), data.internalID, data.action or 0) local bind = configInstance and configInstance.Bindings[name] if configRoot and configRoot.ProfileStorage then local rnOpt = "^" .. name:gsub("[%]%[().+*%-?^$%%]", "%%%1") .. "#" for _,v in pairs(configRoot.ProfileStorage) do if v.Bindings then v.Bindings[name] = nil end if v.RingOptions then for k2, _v2 in pairs(v.RingOptions) do if type(k2) ~= "string" or k2:match(rnOpt) then v.RingOptions[k2] = nil end end end end end OR_Rings[data.internalAP], OR_Rings[OR_Rings[#OR_Rings]].internalAP = OR_Rings[#OR_Rings], data.internalAP OR_Rings[name], OR_Rings[#OR_Rings] = nil if bind then for k, v in pairs(OR_Rings) do if v.hotkey == bind then OR_SyncRing(k) end end end end local function OR_SecProfilePull() local pInstance = configRoot.ProfileStorage[OR_SecEnv.activeProfile] if not pInstance then return end for k, v in rtable.pairs(OR_SecEnv.rtokens) do pInstance.RotationTokens[k] = v end end local function OR_SecProfilePush() if InCombatLockdown() or (activeProfile == OR_SecEnv.activeProfile) then return end local e = ("-- SecProfilePush\n activeProfile = %s"):format(safequote(activeProfile)) for k,v in pairs(configInstance.RotationTokens) do e = e .. ("\n rtokens[%s] = %s"):format(safequote(k), safequote(v)) end OR_SecCore:Execute(e) end local function OR_PullCAs() local t = {} for k,v in rtable.pairs(OR_SecEnv.ORL_StoredCA) do t[k] = v end for _, k in ipairs(OR_Rings) do local rt = OR_SecEnv.ORL_RingDataN[k] and OR_SecEnv.ORL_RingDataN[k].fcToken or t[k] t[k] = not ((OR_Rings[k] and OR_Rings[k].noPersistentCA) or OR_SecEnv.fcIgnore[rt]) and rt or nil end return next(t) and t or nil end local OR_FindFinalAction do local seen, wipe = {}, table.wipe local secRotation, secCollections, secTokens, secRotationMode = OR_SecEnv.rotation, OR_SecEnv.collections, OR_SecEnv.ctokens, OR_SecEnv.rotationMode function OR_FindFinalAction(collection, id, from, rotationBonus, followJumps) wipe(seen) for k=1, 50 do local col = secCollections[collection] local act = col and col[id] if act then local nCol, tok, rot = secCollections[act], secTokens[collection][id] local isJump = secRotationMode[tok] == "jump" if nCol == nil then return act, tok, "act", k elseif isJump and tok ~= from and not followJumps then return act, tok, "jump", k elseif not seen[tok] then seen[tok], rot = true, secRotation[tok] or 1 if tok == from then rot = (rot + rotationBonus - 1) % #nCol + 1 end collection, id = act, rot else -- nCol and seen[tok] and (not isJump or tok == from or followJumps); arbitrarily break nesting loops return act, tok, isJump and "jump" or "act", k end end end end end local OR_PadStickCapture = {} do local hasStoredState, sinkStick, waitingStickLock, storedYaw, storedPitch = false, nil local isThawing, sinkFrame, ouFrame, thawEnd, thawL, thawH = false, CreateFrame("Frame"), CreateFrame("Frame") ouFrame:Hide() sinkFrame:Hide() sinkFrame:SetScript("OnGamePadStick", function() end) local function StickThaw_OnUpdate(s, e) local t = GetTime() if t >= thawEnd or not isThawing then if isThawing then if hasStoredState then SetCVar("GamePadCameraYawSpeed", storedYaw) SetCVar("GamePadCameraPitchSpeed", storedPitch) end isThawing, hasStoredState, sinkStick = false, false, nil sinkFrame:Hide() end s:Hide() return end local ms = C_GamePad.GetDeviceMappedState() local st = ms and ms.sticks st = st and st[hasStoredState and 2 or sinkStick] if st and st.len == 0 then thawEnd = t-1 return StickThaw_OnUpdate(s, e) elseif not hasStoredState then return end local r = (thawEnd-t)/thawL local p = r >= thawH and 0 or (1-r/(1-thawH)) p = p < 0 and 0 or p > 1 and 1 or p if p > 0 then local s = p*p SetCVar("GamePadCameraYawSpeed", storedYaw*s) SetCVar("GamePadCameraPitchSpeed", storedPitch*s) end end local function FreelookWait_OnUpdate(s) if not waitingStickLock then s:Hide() elseif IsGamePadFreelookEnabled() then OR_PadStickCapture:Lock(waitingStickLock) end end function OR_PadStickCapture:Lock(stick) if stick == 2 then if not hasStoredState then hasStoredState, storedYaw, storedPitch = true, GetCVar("GamePadCameraYawSpeed"), GetCVar("GamePadCameraPitchSpeed") end SetCVar("GamePadCameraYawSpeed", 0) SetCVar("GamePadCameraPitchSpeed", 0) elseif stick then sinkStick = stick sinkFrame:Show() end isThawing, waitingStickLock = false ouFrame:Hide() end function OR_PadStickCapture:WaitForFreelook(stick) local wasThawing = isThawing waitingStickLock, isThawing, sinkStick = stick, false, nil if wasThawing then StickThaw_OnUpdate(ouFrame, 0) end ouFrame:SetScript("OnUpdate", FreelookWait_OnUpdate) ouFrame:Show() end function OR_PadStickCapture:Release() if (hasStoredState or sinkStick) and not isThawing then isThawing, thawL, thawH = true, OR_GetRingOption(nil, "PSThawDuration"), OR_GetRingOption(nil, "PSThawHold") thawEnd = thawL + GetTime() ouFrame:SetScript("OnUpdate", StickThaw_OnUpdate) ouFrame:Show() end waitingStickLock = nil end function EV:GAME_PAD_ACTIVE_CHANGED(isActive) if sinkStick and not (isActive or isThawing) then sinkFrame:Hide() OR_PadStickCapture:WaitForFreelook(sinkStick) end end function EV:PLAYER_LOGOUT() if hasStoredState then SetCVar("GamePadCameraYawSpeed", storedYaw) SetCVar("GamePadCameraPitchSpeed", storedPitch) end end end function OR_PadRestoreState:SaveAndSet(freelook, cursor) if not self.saved then self.InFreeLook = IsGamePadFreelookEnabled() self.InCursorControl = IsGamePadCursorControlEnabled() self.saved = true end SetGamePadFreeLook(freelook) SetGamePadCursorControl(cursor) end function OR_SecCore:CheckCVars() local ccy = not InCombatLockdown() and GetCVar("CursorCenteredYPos") if tonumber(ccy) and ccy ~= OR_SecEnv.CENTERED_CURSOR_YPOS then OR_SecCore:Execute("CENTERED_CURSOR_YPOS = " .. safequote(ccy)) end end function OR_SecCore:NotifyState(state, _ringName, collection, ...) if state == "open" then local fastClick, fastOpen, ms = ... OR_ActiveCollectionID, OR_ActiveRingName, OR_ActiveSliceCount, OR_ModifierLockState = collection, OR_SecEnv.activeRing.name, #OR_SecEnv.openCollection, ms if ORI then securecall(ORI.Show, ORI, collection, fastClick, fastOpen, self) end if OR_SecEnv.AI_NoPointer then return end MouselookStop() local psm = C_GamePad.IsEnabled() and OR_SecEnv.ORL_GlobalOptions.PadSupportMode local switchOnOpen = psm and OR_GetRingOption(nil, "PSOpenSwitchMode") or 0 switchOnOpen = switchOnOpen == 2 or switchOnOpen ~= 0 and (OR_SecEnv.AIP_Key or ""):match("GAMEPAD") if psm == "freelook" then if switchOnOpen then OR_PadRestoreState:SaveAndSet(true, false) OR_PadStickCapture:Lock(OR_SecEnv.ORL_GlobalOptions.PSStickIndex) else OR_PadStickCapture:WaitForFreelook(OR_SecEnv.ORL_GlobalOptions.PSStickIndex) end elseif psm == "cursor" then if switchOnOpen then OR_PadRestoreState:SaveAndSet(false, true) end end self:CheckCVars() elseif state == "switch" then OR_ActiveCollectionID, OR_ActiveSliceCount = collection, #OR_SecEnv.openCollection if ORI then securecall(ORI.Show, ORI, collection, ..., "inplace-switch", self) end elseif state == "close" then if ORI then securecall(ORI.Hide, ORI, ...) end OR_ActiveSliceCount, OR_ActiveCollectionID, OR_ActiveRingName = 0 OR_PadStickCapture:Release() if OR_PadRestoreState.saved and OR_GetRingOption(nil, "PSRestoreOnClose") then SetGamePadFreeLook(OR_PadRestoreState.InFreeLook) SetGamePadCursorControl(OR_PadRestoreState.InCursorControl) end OR_PadRestoreState.saved = nil end end -- Responding to WoW Events local OR_NotifyPVars do local function callNotify(k, event) local v = PersistentStorageInfo[k] v.f(event, k, v.t) return true end function OR_NotifyPVars(event, filter, perProfile) local ok = true for k, v in pairs(PersistentStorageInfo) do if type(v.f) == "function" and v.t == (filter or v.t) and (perProfile == nil or perProfile == v.perProfile) then ok = securecall(callNotify, k, event) and ok end end return ok end end local function OR_ForceResync(filter) for _,v in ipairs(OR_Rings) do if (filter or v) == v then OR_SyncRing(v) end end if (filter or true) == true then local psm = OR_GetRingOption(nil, "PadSupportMode") local up1, up2 = OR_GetRingOption(nil, "ScrollNestedRingUpButton"), OR_GetRingOption(nil, "ScrollNestedRingUpButton2") local down1, down2 = OR_GetRingOption(nil, "ScrollNestedRingDownButton"), OR_GetRingOption(nil, "ScrollNestedRingDownButton2") local p = "[mM][oO][uU][sS][eE][wW][hH][eE][eE][lL]" local hasWheelScroll = (up1 or ""):match(p) or (up2 or ""):match(p) or (down1 or ""):match(p) or (down2 or ""):match(p) OR_DeferExecute([[-- SyncGlobalOptions ORL_GlobalOptions.OpenNestedRingBinding, ORL_GlobalOptions.ScrollNestedRingUpBinding, ORL_GlobalOptions.ScrollNestedRingDownBinding = %s, %s, %s ORL_GlobalOptions.OpenNestedRingBinding2, ORL_GlobalOptions.ScrollNestedRingUpBinding2, ORL_GlobalOptions.ScrollNestedRingDownBinding2 = %s, %s, %s ORL_GlobalOptions.PadSupportMode, ORL_GlobalOptions.PSStickIndex = %s, %d self:CallMethod("EnableMouseWheel", %s)]], safequote(OR_GetRingOption(nil, "OpenNestedRingButton")), safequote(up1), safequote(down1), safequote(OR_GetRingOption(nil, "OpenNestedRingButton2")), safequote(up2), safequote(down2), safequote(PADSUPPORT_MODE_MAP[psm] or psm or "none"), psm == "freelook1" and 1 or 2, hasWheelScroll and "true" or "false" ) end end local function OR_CheckBindings() if InCombatLockdown() then return end for i=1,#OR_Rings do local k = OR_Rings[i] OR_SyncRingBinding(k, OR_Rings[k]) end end function EV:PLAYER_REGEN_ENABLED() OR_CheckBindings() OR_SecProfilePush() OR_DeferExecute() end function EV:PLAYER_REGEN_DISABLED() OR_SecCore:CheckCVars() end local function OR_UnserializeConfigInstance(profile) local newCI activeProfile, newCI = getProfile(profile) for t in ("RingOptions Bindings Bindings2 RotationTokens"):gmatch("%S+") do if type(newCI[t]) ~= "table" then newCI[t] = {} end end for k,v in pairs(PersistentStorageInfo) do if v.perProfile then copy(newCI[k], nil, v.t) end end configInstance = setmetatable(newCI, optionsMeta) end local function OR_NotifyOptions() for option, func in pairs(optionValidators) do if func then securecall(func, option, configInstance[option]) end end end local function OR_UpgradeOptions(pv, isRingOptions) local nv = {} for k, v in pairs(pv) do local domain, opt = (isRingOptions and type(k) == "string" and k or ""):match("^(.*#)(.-)$") if not domain then domain, opt = "", k end if opt == "ClickActivation" and type(v) == "boolean" then nv[domain .. "InteractionMode"], pv[k] = pv[domain .. "InteractionMode"] or v and 2 or nil, nil -- DEPRECATED[2406/Z7] elseif opt == "PSSwitchOnOpen" and type(v) == "boolean" then nv[domain .. "PSOpenSwitchMode"], pv[k] = pv[domain .. "PSOpenSwitchMode"] or v and 1 or 0, nil -- DEPRECATED[2405/Z6] end end for k,v in pairs(nv) do pv[k], nv[k] = v, nil end end local function OR_UpgradeConfig() if not OR_UpgradeConfig then return end local tb = configRoot._TimeBand TB_THRESH = type(tb) == "number" and tb < 1 and tb >= 0 and tb or TB_THRESH or nil local gameVersion, opieVersion = GetBuildInfo(), ("%s (%d.%d)"):format(api:GetVersion()) if (configRoot._OPieVersion or opieVersion) ~= opieVersion then for _,pv in pairs(configRoot.ProfileStorage) do if type(pv) == "table" then OR_UpgradeOptions(pv, false) if type(pv.RingOptions) == "table" then OR_UpgradeOptions(pv.RingOptions, true) end end end end if configRoot._GameVersion ~= gameVersion then for _,v in pairs(configRoot.ProfileStorage) do if type(v) == "table" then v.RotationTokens = nil end end end configRoot._GameVersion, configRoot._OPieVersion, configRoot._GameLocale = gameVersion, opieVersion, GetLocale() OR_UpgradeConfig = nil return true end local function OR_InitConfigState() local from if type(OPie_SavedData) == "table" then svMigrationState, from = 2, OPie_SavedData elseif type(OneRing_Config) == "table" then svMigrationState, from = 1, OneRing_Config end if from then for k, v in pairs(from) do configRoot[k] = v end end for t in ("CharProfiles PersistentStorage ProfileStorage"):gmatch("%S+") do configRoot[t] = type(configRoot[t]) == "table" and copy(configRoot[t]) or {} end if type(configRoot.ProfileStorage.default) ~= "table" then configRoot.ProfileStorage.default = {Bindings={}, RingOptions={}} end local ok = securecall(OR_UpgradeConfig) OR_UnserializeConfigInstance(configRoot.CharProfiles[getSpecCharIdent()]) if type(configRoot.CenterActions) == "table" then local exec = "-- InitCA" for name, tok in pairs(configRoot.CenterActions) do exec = ("%s\nORL_StoredCA[%s] = %s"):format(exec, safequote(name), safequote(tok)) end OR_SecCore:Execute(exec) end -- Load variables into relevant tables, unlock core and fire notifications. for k, v in pairs(configRoot.PersistentStorage) do if PersistentStorageInfo[k] and not PersistentStorageInfo[k].perProfile then copy(v, nil, PersistentStorageInfo[k].t) end end ok = OR_NotifyPVars("LOADED") and ok OR_NotifyOptions() OR_LoadedState = OR_LoadedState == 2 and (ok and 4 or 3) or OR_LoadedState if ok then OPie_SavedData, OneRing_Config, configRoot.CenterActions = nil end end function EV:ADDON_LOADED(addon) if addon == ADDON then ORI, OR_LoadedState = T.OPieUI, OR_LoadedState == 1 and 2 or OR_LoadedState OR_InitConfigState() OR_SecProfilePush() OR_ForceResync() C_AddOns.DisableAddOn("OPie_Classic") return "remove" end end function EV:SAVED_VARIABLES_TOO_LARGE(addon) if addon == ADDON then OR_LoadedState = false end end function EV:PLAYER_LOGIN() OR_LoadedState = OR_LoadedState == 1 and -1 or OR_LoadedState OR_NotifyPVars("LOGIN") OR_NotifyPVars("POST-LOGIN") return "remove" end function EV:PLAYER_ENTERING_WORLD(_, isReload) if isReload and type(OPie_SavedDataPC) == "table" and type(OPie_SavedDataPC.FlagState) == "table" then local FM = AB and AB:compatible("FlagMast", 1) if FM then FM:RestoreState(OPie_SavedDataPC.FlagState) end end OPie_SavedDataPC = nil return "remove" end function EV:PLAYER_LOGOUT() OPie_SavedData = configRoot OneRing_Config = svMigrationState ~= 2 and configRoot or nil OR_NotifyPVars("LOGOUT") OR_SecProfilePull() configRoot._TimeBand = TB_THRESH configRoot.CenterActions = OR_PullCAs() for k, v in pairs(configInstance) do if v == defaultConfig[k] then configInstance[k] = nil end end for k, v in pairs(PersistentStorageInfo) do local store = v.perProfile and configInstance or configRoot.PersistentStorage store[k] = next(v.t) ~= nil and v.t or nil end for t in ("RingOptions Bindings Bindings2 RotationTokens"):gmatch("%S+") do for _, v in pairs(configRoot.ProfileStorage) do if type(v[t]) ~= "table" or next(v[t]) == nil then v[t] = nil end end end local FM = AB and AB:compatible("FlagMast", 1) local fs = FM and FM:GetState() OPie_SavedDataPC = fs and {FlagState=fs} or nil end local function OR_SaveCurrentProfile() OR_NotifyPVars("SAVE", nil, true) for k, v in pairs(PersistentStorageInfo) do if v.perProfile then configInstance[k] = next(v.t) and copy(v.t) end end OR_SecProfilePull() end local function OR_SwitchProfile(ident) if ident ~= activeProfile then OR_SaveCurrentProfile() end local prevProfile = activeProfile OR_UnserializeConfigInstance(ident) OR_NotifyPVars("UPDATE", nil, true) OR_NotifyOptions() OR_SecProfilePush() OR_ForceResync() if activeProfile ~= prevProfile then EV("OPIE_PROFILE_SWITCHED", activeProfile, prevProfile) end end function EV.ACTIVE_TALENT_GROUP_CHANGED() local newProfile = getProfileForSpec() if newProfile ~= activeProfile then OR_SwitchProfile(newProfile) end end EV.UPDATE_BINDINGS = OR_CheckBindings local function cmpRingProps(a, b) local ac, bc = b.sortScope or 0, a.sortScope or 0 if ac == bc then ac, bc = (a.name or a.internalName), (b.name or b.internalName) end return ac < bc end local getProfileIdentComparator do local pt local function cmpProfileIdent(a, b) local ac, bc = pt[a] or 0, pt[b] or 0 if ac ~= bc then return ac > bc end return strcmputf8i(a,b) < 0 end function getProfileIdentComparator() pt = {} for i=1, getNumSpecs() do pt[getProfileForSpec(i) or 0] = 1 end pt[activeProfile or 0] = 2 pt.default, pt[0] = 3, nil return cmpProfileIdent end end local function retProfiles(n, i) if i <= n then return getProfileForSpec(i), retProfiles(n, i+1) end end local function getRingBindings(ringName, skipDefault) local b1c, b1, isUser = configInstance.Bindings[ringName] if b1c == nil and not skipDefault then b1, isUser = OR_Rings[ringName].hotkey, false else b1, isUser = b1c, true end return b1, configInstance.Bindings2[ringName], isUser, b1c end function private:GetSVState() return OR_LoadedState end function private:RegisterOption(name, default, validator) assert(type(name) == "string" and default ~= nil and (validator == nil or type(validator) == "function"), 'Syntax: api:RegisterOption("name", defaultValue[, validatorFunc])', 2) assert(defaultConfig[name] == nil and PersistentStorageInfo[name] == nil, "Option %q has a conflicting name", 2, name) defaultConfig[name], optionValidators[name] = default, validator or false end function private:RegisterPVar(name, into, notifier, perProfile) assert(type(name) == "string" and (into == nil or type(into) == "table") and (notifier == nil or type(notifier) == "function"), 'Syntax: api:RegisterPVar("name"[, storageTable[, notifierFunc[, perProfile]]])', 2) assert(PersistentStorageInfo[name] == nil and defaultConfig[name] == nil, "Persistent variable %q already declared.", 2, name) assert(name:match("^%a"), "%q is not a valid persistent variable name", 2, name) local store, into = ((perProfile == true) and configInstance or configRoot.PersistentStorage), into or {} PersistentStorageInfo[name] = {t=into, f=notifier, perProfile=perProfile == true} if configInstance then if store and store[name] then copy(store[name], nil, into) end OR_NotifyPVars("LOADED", into) end return into end function private:GetOption(option, ringName) assert(type(option) == "string" and (ringName == nil or type(ringName) == "string"), 'Syntax: value, setting, ring, global, default = api:GetOption("option"[, "ringName"])', 2) if defaultConfig[option] == nil then return end return OR_GetRingOption(ringName, option) end function private:SetOption(option, value, ringName) assert(type(option) == "string" and (ringName == nil or type(ringName) == "string"), 'Syntax: api:SetOption("option", value[, "ringName"])', 2) assert(defaultConfig[option] ~= nil, "Option %q is undefined.", 2, option) assert(ringName == nil or OR_Rings[ringName], "Ring %q is undefined.", 2, ringName) assert(value == nil or type(defaultConfig[option]) == type(value), "Type mismatch: %q expected to be a %s (got %s).", 2, option, type(defaultConfig[option]), type(value)) assert(not optionValidators[option] or optionValidators[option](option, value, ringName) ~= false, "Value rejected by option validator.", 2) local scope, prefix = ringName and configInstance.RingOptions or configInstance, ringName and (ringName .. "#") or "" scope[prefix .. option] = value if optionValidators[option] == nil then OR_ForceResync(ringName) end end function private:GetRingBinding(ringName, bidx) assert(type(ringName) == "string" and (bidx == nil or bidx == 1 or bidx == 2), 'Syntax: binding, currentKey, isUserBinding, isActiveInternal, isActiveExternal = api:GetRingBinding("ringName"[, bindingIndex])', 2) local rprop = assert(OR_Rings[ringName], 'Ring %q is not defined', 2, ringName) local b1, b2, isUser = getRingBindings(ringName) local iid, secK, bindSuffix = rprop.internalID, "bind", "" if (b1 ~= nil) == (bidx == 2) then b1, isUser, secK, bindSuffix = b2, true, "bind2", "b" end local secRingData = OR_SecEnv.ORL_RingData[iid] local curKey = secRingData and secRingData[secK] local curAct = curKey and GetBindingAction(curKey, 1) curAct = (curKey == nil) or (curAct == ("CLICK ORL_RProxy" .. iid .. ":r" .. iid .. bindSuffix)) or curAct return b1, curKey, isUser, curKey ~= nil, curAct end function private:SetRingBinding(ringName, bidx, bind) assert(type(ringName) == "string" and (bidx == 1 or bidx == 2) and (type(bind) == "string" or bind == false or bind == nil), 'Syntax: api:SetRingBinding("ringName", bindingIndex, "binding" or false or nil)', 2) local rprop = assert(OR_Rings[ringName], "Ring %q is not defined", 2, ringName) local b1, b2, _b1u, b1c = getRingBindings(ringName) local niOffset = b2 and b1 == nil and 3 or -1 -- would GetRingBinding rewrite indices? local nb1, nb2 = select(niOffset + bidx + bidx, bind,b2, b1c,bind, bind,nil, b2,bind) if nb2 and not nb1 and (bidx ~= 2 or b1 == nil) then nb1, nb2 = nb2, nil elseif nb1 == nb2 then nb2 = nil end nb2 = nb2 or nil if (nb1 == b1c and nb2 == b2) or (nb1 == b2 and nb2 == b1c) then return end configInstance.Bindings[ringName], configInstance.Bindings2[ringName] = nb1, nb2 local obind = b1 ~= nb1 and b1 ~= nb2 and b1 or b2 ~= nb1 and b2 ~= nb2 and b2 for i=1, #OR_Rings do local ikey = OR_Rings[i] local ib1, ib2, ib1u = getRingBindings(ikey) if ikey ~= ringName and (bind and (ib1 == bind or ib2 == bind) or obind and (ib1 == obind or ib2 == obind)) then if bind then if ib2 == bind then configInstance.Bindings2[ikey], ib2 = nil, nil end if ib1 == bind and ib1u then configInstance.Bindings[ikey], configInstance.Bindings2[ikey] = ib2, nil end end OR_SyncRingBinding(ikey, OR_Rings[ikey]) end end OR_SyncRingBinding(ringName, rprop) end function private:ProfileExists(ident) return configRoot.ProfileStorage[ident] ~= nil end function private:GetAllProfiles() if not configInstance then return end local r = {} for k in pairs(configRoot.ProfileStorage) do r[#r+1] = k end table.sort(r, getProfileIdentComparator()) return r end function private:GetSpecProfiles() return retProfiles(getNumSpecs(), 1) end function private:SetSpecProfiles(...) assert(select("#", ...) == getNumSpecs(), 'SetSpecProfiles(...): improper argument count', 2) for i=1, getNumSpecs() do configRoot.CharProfiles[getSpecCharIdent(i)] = normalizeStoredProfileIdent(select(i, ...)) end local np = getProfileForSpec() if np ~= activeProfile then OR_SwitchProfile(np) end end function private:SwitchProfile(ident, inherit) assert(type(ident) == "string" and (inherit == nil or type(inherit) == "boolean" or type(inherit) == "table"), 'Syntax: api:SwitchProfile("profile"[, deriveFromCurrent or profileData])', 2) if type(inherit) == "table" then local data = copy(inherit) if data._usedBy then for _, charid in pairs(data._usedBy) do configRoot.CharProfiles[charid] = ident end data._usedBy = nil end configRoot.ProfileStorage[ident] = data elseif not configRoot.ProfileStorage[ident] then configRoot.ProfileStorage[ident] = inherit and copy(configInstance) or {} end OR_SwitchProfile(ident) configRoot.CharProfiles[getSpecCharIdent()] = normalizeStoredProfileIdent(activeProfile) end function private:ExportProfile(ident) assert(type(ident) == "string" or ident == nil, 'Syntax: profileData = api:ExportProfile(["profile"])', 2) assert(ident == nil or configRoot.ProfileStorage[ident], 'Profile %q does not exist.', 2, ident) if ident == nil then OR_SaveCurrentProfile() end local data = copy(ident == nil and configInstance or configRoot.ProfileStorage[ident]) if configRoot.CharProfiles then local id, ni, usedBy = ident or activeProfile, 1, {} for k,v in pairs(configRoot.CharProfiles) do if v == id then usedBy[ni], ni = k, ni + 1 end end data._usedBy = ni > 1 and usedBy or nil end return data end function private:DeleteProfile(ident) assert(type(ident) == "string", 'Syntax: api:DeleteProfile("profile")', 2) local oldP = configRoot.ProfileStorage[ident] if not oldP then return end if configRoot.CharProfiles then for k,v in pairs(configRoot.CharProfiles) do if v == ident then configRoot.CharProfiles[k] = nil end end end configRoot.ProfileStorage[ident] = nil if configInstance == oldP then private:SwitchProfile("default") end end function private:ResetRingBindings() wipe(configInstance.Bindings) OR_ForceResync() end function private:ResetOptions(includePerRing) assert(type(includePerRing) == "boolean" or includePerRing == nil, "Syntax: api:ResetOptions([includePerRing])", 2) for k, v in pairs(defaultConfig) do local iv = configInstance[k] configInstance[k] = nil if optionValidators[k] and (iv ~= nil and iv ~= v) then securecall(optionValidators[k], k, v) end end if includePerRing then configInstance.RingOptions = {} end OR_ForceResync() end function private:GetOpenRing(optTable) if type(optTable) == "table" then for k in pairs(defaultConfig) do optTable[k] = OR_GetRingOption(OR_ActiveRingName or "default", k) end end return OR_ActiveRingName, OR_ActiveSliceCount, OR_SecEnv.activeRing and OR_SecEnv.activeRing.ofsDeg or 0 end function private:GetOpenRingSlice(id) if type(id) ~= "number" or id < 1 or id > OR_ActiveSliceCount then return false end local sbt, act, tok, atype, nestLevel = OR_SecEnv.activeRing.SliceBinding, OR_FindFinalAction(OR_ActiveCollectionID, id) local nt = OR_SecEnv.collections[OR_SecEnv.collections[OR_ActiveCollectionID][id]] return act, tok, sbt and sbt[id], sbt and sbt[id+0.5], nt and #nt or 0, atype, nestLevel and nestLevel > 1 end function private:GetOpenRingSliceAction(id, id2) if id < 1 or id > OR_ActiveSliceCount then return end local s, tok, atype, nestLevel = OR_FindFinalAction(OR_ActiveCollectionID, id, id2 and OR_SecEnv.ctokens[OR_ActiveCollectionID][id] or nil, (id2 or 1)-1) if type(s) == "number" then if atype == "jump" then local icon, aid, tok2, _ = nil, OR_FindFinalAction(s, 1, nil, 0, true) if type(aid) == "number" then _, _, icon = AB:GetSlotInfo(aid, OR_ModifierLockState) end local rid = OR_SecEnv.ORL_RingData[OR_SecEnv.ORL_KnownCollections[s]] rid = OR_Rings[rid and rid.name] local rname = rid and rid.name return tok, true, nestLevel and nestLevel > (id2 and 2 or 1) and 4096+8192 or 4096, icon or [[Interface\AddOns\OPie\gfx\opie_ring_icon]], rname or L"Open nested ring", tok2, 0, 0 end return tok, AB:GetSlotInfo(s, OR_ModifierLockState) end return tok, false, 0, [[Interface\Icons\INV_Misc_QuestionMark]], "Unknown Slice", 0, 0, 0 end function private:GetCurrentInputs() if OR_SecEnv.AI_NoPointer then return "nopoint", nil, 0, false, 0 end local aframe, imode, cx, cy = OR_SecCore, "cursor", GetCursorPosition() local scale, l, b, w, h = aframe:GetEffectiveScale(), aframe:GetRect() local dx, dy = (cx / scale) - (l + w / 2), (cy / scale) - (b + h / 2) local radius2 = dx*dx+dy*dy local isActiveRadius, isCenterRadius = radius2 >= 1600, radius2 <= 400 local go, stl = OR_SecEnv.ORL_GlobalOptions, 0 if go.PadSupportMode == "freelook" and C_GamePad.IsEnabled() and IsGamePadFreelookEnabled() and not IsGamePadCursorControlEnabled() then local ms = C_GamePad.GetDeviceMappedState() local st = ms and ms.sticks st = st and st[go.PSStickIndex] if st then dx, dy, stl = st.x, st.y, st.len imode, isActiveRadius, isCenterRadius = "stick", stl > 0.25, stl < 0.01 end end local aidx, qidx = OR_SecEnv.fastClick, nil if aidx then local ar = OR_SecEnv.activeRing if ar.MotionAction and OR_SecEnv.AI_MotionArmedFC and imode ~= "stick" then qidx = aidx elseif ar.CenterAction and isCenterRadius then qidx = aidx end end return imode, qidx, atan2(dy, dx) % 360, isActiveRadius, stl end function private:FutureDeprecationError(msg, depth, a,b,c,d) local sev = getTimeBand(a,b,c,d) if sev == 2 then error(msg, 1 + (depth or 1))((0)[0]) elseif sev == 1 then securecall(error, msg, 2 + (depth or 1)) end end -- Public API function api:SetRing(name, actionId, props) assert(type(name) == "string" and (actionId == nil or (type(props) == "table" or type(actionId) == "number")), 'Syntax: OPie:SetRing("ringName"[, actionId, propsTable])', 2) if actionId then OR_SyncRing(name, actionId, props) elseif OR_Rings[name] then OR_DeleteRing(name, OR_Rings[name]) end end function api:GetNumRings() return #OR_Rings end function api:GetRingInfo(ring) assert(type(ring) == "number" or type(ring) == "string", 'Syntax: name, key, macro, flags = OPie:GetRingInfo(index or "ringName")', 2) local key = type(ring) == "string" and OR_Rings[ring] and ring or OR_Rings[ring] if not key then return end local props = OR_Rings[key] return props.name, key, "/click "..OR_OpenProxy:GetName().." "..key, (props.internal and 1 or 0) end function api:IterateRings(includeInternalRings) local ot = {} for i=1,#OR_Rings do local props = OR_Rings[OR_Rings[i]] if props and (includeInternalRings or not props.internal) then ot[#ot+1] = props end end table.sort(ot, cmpRingProps) local p, props = 1 return function() props, p = ot[p], p + 1 if props then return props.internalName, props.name or props.internalName, props.sortScope, props.internal and 1 or 0 end end end function api:IsKnownRingName(ringName) assert(type(ringName) == "string", 'Syntax: isKnown = OPie:IsKnownRingName("ringName")', 2) if OR_Rings[ringName] then return true end for _, v in pairs(configRoot.ProfileStorage) do if type(v.Bindings) == "table" and v.Bindings[ringName] then return true end end return false end function api:GetCurrentProfile() return activeProfile end function api:GetVersion() return C_AddOns.GetAddOnMetadata(ADDON, "Version") or "?", MAJ, REV end -- HIDDEN, UNSUPPORTED METHODS: May vanish at any time. local hum = {} setmetatable(api, {__index=hum}) hum.HUM = hum hum.NotGameTooltip = T.NotGameTooltip hum.GetOption = private.GetOption hum.GetRingBinding = private.GetRingBinding hum.GetOpenRing = private.GetOpenRing hum.GetOpenRingSlice = private.GetOpenRingSlice hum.GetOpenRingSliceAction = private.GetOpenRingSliceAction function hum:OverrideRingBinding(ringName, bind) assert(type(ringName) == "string" and (bind == nil or type(bind) == "string"), 'Syntax: api:OverrideRingBinding("ringName", "binding")', 2) assert(OR_Rings[ringName], 'Ring %q is not defined', 2, ringName) if OR_SecEnv.bindOverrides[OR_Rings[ringName].internalID] ~= bind then OR_SecCore:Execute(("owner:Run(ORL_RegisterOverride, %d, %s)"):format(OR_Rings[ringName].internalID, bind and safequote(bind) or "nil")) end end function hum:SetRingOpensAtMousePreference(ringName, pref) assert(type(ringName) == "string" and type(pref) == "boolean", 'Syntax: api:SetRingOpensAtMousePreference("ringName", preferAtMouse)') if select(3, OR_GetRingOption(ringName, "RingAtMouse")) == nil then private:SetOption("RingAtMouse", pref, ringName) end end hum.GetSVState = private.GetSVState for k,v in pairs(api) do if private[k] == nil then private[k] = v end end T.FutureDeprecationError = private.FutureDeprecationError _G.OPie, T.OPieCore = api, private