if Binder then return end Binder = {} local Binder = Binder -- Localize builtin functions we use local ipairs = ipairs local next = next local pairs = pairs local tinsert = table.insert -- Localize ESO API functions we use local d = d local strjoin = zo_strjoin local strsplit = zo_strsplit local GetNumActionLayers = GetNumActionLayers local GetActionLayerInfo = GetActionLayerInfo local GetActionLayerCategoryInfo = GetActionLayerCategoryInfo local GetActionInfo = GetActionInfo local GetActionIndicesFromName = GetActionIndicesFromName local function print(...) d(strjoin("", ...)) end Binder.defaults = { ["bindings"] = {}, ["auto"] = false, ["autoSets"] = {}, } Binder.debug = {} function Binder.BuildActionTables() local actionHierarchy = {} local actionNames = {} local layers = GetNumActionLayers() for layerIndex=1, layers do local layerName, categories = GetActionLayerInfo(layerIndex) local layer = {} for categoryIndex=1, categories do local category = {} local categoryName, actions = GetActionLayerCategoryInfo(layerIndex, categoryIndex) for actionIndex=1, actions do local actionName, isRebindable, isHidden = GetActionInfo(layerIndex, categoryIndex, actionIndex) if isRebindable then local action = { ["name"] = actionName, ["rebind"] = isRebindable, ["hidden"] = isHidden, } category[actionIndex] = action tinsert(actionNames, actionName) end end if next(category) ~= nil then category["name"] = categoryName layer[categoryIndex] = category end end if next(layer) ~= nil then layer["name"] = layerName actionHierarchy[layerIndex] = layer end end Binder.actionHierarchy = actionHierarchy Binder.actionNames = actionNames end function Binder.BuildBindingsTable() if not Binder.actionNames then Binder.BuildActionTables() end local bindings = {} local bindCount = 0 local maxBindings = GetMaxBindingsPerAction() for index, actionName in ipairs(Binder.actionNames) do local layerIndex, categoryIndex, actionIndex = GetActionIndicesFromName(actionName) local actionBindings = {} for bindIndex=1, maxBindings do local keyCode, mod1, mod2, mod3, mod4 = GetActionBindingInfo(layerIndex, categoryIndex, actionIndex, bindIndex) if keyCode ~= 0 then local bind = { ["keyCode"] = keyCode, ["mod1"] = mod1, ["mod2"] = mod2, ["mod3"] = mod3, ["mod4"] = mod4, } tinsert(actionBindings, bind) bindCount = bindCount + 1 end end bindings[actionName] = actionBindings end Binder.bindings = bindings Binder.bindCount = bindCount end function Binder.RestoreBindingsFromTable() local bindCount = 0 local attemptedBindCount = 0 local skippedBindCount = 0 local maxBindings = GetMaxBindingsPerAction() for actionName, actionBindings in pairs(Binder.bindings) do local layerIndex, categoryIndex, actionIndex = GetActionIndicesFromName(actionName) if layerIndex and categoryIndex and actionIndex then CallSecureProtected("UnbindAllKeysFromAction", layerIndex, categoryIndex, actionIndex) for bindingIndex, bind in ipairs(actionBindings) do if bindingIndex <= maxBindings then attemptedBindCount = attemptedBindCount + 1 CallSecureProtected("BindKeyToAction", layerIndex, categoryIndex, actionIndex, bindingIndex, bind["keyCode"], bind["mod1"], bind["mod2"], bind["mod3"], bind["mod4"]) bindCount = bindCount + 1 else skippedBindCount = skippedBindCount + 1 end end else skippedBindCount = skippedBindCount + 1 end end Binder.debug.bindCount = bindCount Binder.debug.attemptedBindCount = attemptedBindCount Binder.debug.skippedBindCount = skippedBindCount Binder.debug.maxBindingsPerAction = maxBindings end function Binder.SaveBindings(bindSetName, isSilent) if bindSetName == nil or bindSetName == "" then print("Usage: /binder save <set name>") return end Binder.debug.savingSet = bindSetName Binder.BuildBindingsTable() -- Update any existing bind set as a set union, or create new local bindSet = Binder.savedVariables.bindings[bindSetName] or {} for bindName, binding in pairs(Binder.bindings) do bindSet[bindName] = binding end Binder.savedVariables.bindings[bindSetName] = bindSet if not isSilent then print("Saved ", Binder.bindCount, " bindings to bind set '", bindSetName, "'.") end local character = GetUnitName("player") Binder.savedVariables.autoSets[character] = bindSetName Binder.debug.savedSet = bindSetName end function Binder.LoadBindings(bindSetName, isSilent) if bindSetName == nil or bindSetName == "" then print("Usage: /binder load <set name>") return end if Binder.savedVariables.bindings[bindSetName] == nil then print("Bind set '", bindSetName, "' does not exist.") return end if IsUnitInCombat("player") then print("Cannot load bind set - in combat. Please try again out of combat.") return end Binder.debug.loadingSet = bindSetName Binder.bindings = Binder.savedVariables.bindings[bindSetName] Binder.RestoreBindingsFromTable() if not isSilent then print("Loaded ", Binder.bindCount, " bindings from bind set '", bindSetName, "'.") end local character = GetUnitName("player") Binder.savedVariables.autoSets[character] = bindSetName Binder.debug.loadedSet = bindSetName end function Binder.ListBindings() local sets = {} for setName in pairs(Binder.savedVariables.bindings) do table.insert(sets, setName) end table.sort(sets) print("Bind sets saved:") for i,setName in ipairs(sets) do print("- ", setName) end end function Binder.DeleteBindings(bindSetName) if bindSetName == nil or bindSetName == "" then print("Usage: /binder delete <set name>") return end if Binder.savedVariables.bindings[bindSetName] == nil then print("Bind set '", bindSetName, "' does not exist.") return end Binder.savedVariables.bindings[bindSetName] = nil print("Deleted bind set '", bindSetName, "'.") end function Binder.SetAuto(newValue) if newValue == "on" then Binder.savedVariables.auto = true print("Enabled automatic bind set updates.") Binder.LoadAutomaticBindings() elseif newValue == "off" then Binder.savedVariables.auto = false print("Disabled automatic bind set updates.") else if Binder.savedVariables.auto then print("Automatic bind set updates are on (disable with /binder auto off).") else print("Automatic bind set updates are off (enable with /binder auto on).") end local character = GetUnitName("player") local setName = Binder.savedVariables.autoSets[character] if setName ~= nil then print("Currently active set: ", setName) end end end function Binder.SaveAutomaticBindings(isSilent) -- No-op if automatic mode is disabled. if not Binder.savedVariables.auto then return end local character = GetUnitName("player") local setName = Binder.savedVariables.autoSets[character] if setName == nil then setName = character:gsub(" ", "-") .. "-auto" Binder.savedVariables.autoSets[character] = setName end Binder.debug.autoSavingSet = setName Binder.SaveBindings(setName, isSilent) Binder.debug.autoSavedSet = setName end function Binder.LoadAutomaticBindings(isSilent) -- No-op if automatic mode is disabled. if not Binder.savedVariables.auto then return end local character = GetUnitName("player") local setName = Binder.savedVariables.autoSets[character] if setName ~= nil then Binder.debug.autoLoadingSet = setName Binder.LoadBindings(setName, isSilent) Binder.debug.autoLoadedSet = setName else -- If there isn't a set to load, but automatic mode is on, -- create a new set. Binder.SaveAutomaticBindings(isSilent) end end function Binder.OnKeybindingSetOrCleared() -- Silently update active bind set Binder.SaveAutomaticBindings(true) end function Binder.OnKeybindingsLoaded() -- Silently load active bind set Binder.LoadAutomaticBindings(true) end function Binder.RegisterEvents() EVENT_MANAGER:RegisterForEvent("Binder", EVENT_KEYBINDINGS_LOADED, Binder.OnKeybindingsLoaded) EVENT_MANAGER:RegisterForEvent("Binder", EVENT_KEYBINDING_SET, Binder.OnKeybindingSetOrCleared) EVENT_MANAGER:RegisterForEvent("Binder", EVENT_KEYBINDING_CLEARED, Binder.OnKeybindingSetOrCleared) end Binder.commands = { ["build"] = Binder.BuildBindingsTable, ["save"] = Binder.SaveBindings, ["load"] = Binder.LoadBindings, ["list"] = Binder.ListBindings, ["delete"] = Binder.DeleteBindings, ["auto"] = Binder.SetAuto, } function Binder.SlashCommandHelp() print("Binder usage:") print("- /binder save <set name> (saves current keybindings)") print("- /binder load <set name> (loads specified keybindings)") print("- /binder list (lists all saved bind sets)") print("- /binder delete <set name> (deletes specified keybindings)") print("- /binder auto [on||off] (lists or sets automatic mode)") end function Binder.SlashCommand(argtext) local args = {strsplit(" ", argtext)} if next(args) == nil then Binder.SlashCommandHelp() return end local command = Binder.commands[args[1]] if not command then print("Binder: unknown command '", args[1], "'.") Binder.SlashCommandHelp() return end -- Call the selected function with everything except the original command command(unpack(args, 2)) end function Binder.OnAddOnLoaded(event, addonName) if addonName ~= "Binder" then return end Binder.savedVariables = ZO_SavedVars:NewAccountWide("Binder_SavedVariables", 1, "default", Binder.defaults) -- Silently load the active bind set, if automatic mode is on. Binder.LoadAutomaticBindings(true) Binder.RegisterEvents() end EVENT_MANAGER:RegisterForEvent("Binder", EVENT_ADD_ON_LOADED, Binder.OnAddOnLoaded) if WF_SlashCommand ~= nil then -- Register via Wykkyd's framework for those who use it, to allow macroing WF_SlashCommand("binder", Binder.SlashCommand) else -- But don't require the framework. SLASH_COMMANDS["/binder"] = Binder.SlashCommand end