Mercurial > wow > skeletonkey
diff SkeletonKey/SkeletonKey.lua @ 14:82170735e67c
- move co-routine handler to general lib
- slightly better handling of pet actions
author | Nenue |
---|---|
date | Thu, 28 Jul 2016 23:58:53 -0400 |
parents | SkeletonKey/KeyBinds.lua@eeec4a600064 |
children | 32d64e42ec9b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SkeletonKey/SkeletonKey.lua Thu Jul 28 23:58:53 2016 -0400 @@ -0,0 +1,581 @@ +-------------------------------------------- +-- SkeletonKey +-- Krakyn-Mal'Ganis +-- @project-revision@ @project-hash@ +-- @file-revision@ @file-hash@ +-- Created: 6/16/2016 3:47 AM +-------------------------------------------- +-- kb +-- .StoreBinding(button, key) bind current keystroke to command +-- .GetSlot(index) return display slot +-- .SetSlot(button, command, name, icon) assign display slot +-- .ReleaseSlot(button) clear button command +-- .UpdateSlot(button) update button contents +-- .SelectProfile(name) set profile character +-- .ApplyBindings(bindings) walk table with SetBinding() + +local _ +local kb, print = LibStub("LibKraken").register(KeyBinder) +local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg', ...) end or function() end +kb.L = setmetatable({}, { + __call = function(t, k, ...) return format(t[k] or '', ...) end +}) +local L = kb.L + +--- Caps Lock literals +local CLICK_KEYBINDER_MACRO = "CLICK KeyBinderMacro:" +local CLICK_KEYBINDER_KEY = "CLICK KeyBinderKey:" +local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES" +local FOOTER_OFFSET +local HEADER_OFFSET +L.BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).' +L.BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.' +L.BINDING_FAILED_PROTECTED = '|cFFFF4400Unable to use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400)|r' + + +local BINDING_TYPE_SPECIALIZATION = 3 +local BINDING_TYPE_CHARACTER = 2 +local BINDING_TYPE_GLOBAL = 1 + + +--- Caps Lock derivatives +local ACTION_SCRIPT = { + ['mount'] = "/script C_MountJournal.SummonByID(%d)", + ['macro'] = "%s", + ['equipset'] = "/script UseEquipmentSet(%d)", + ['spell'] = "/cast %s", + ['petaction'] = "/cast %s", + ['battlepet'] = SLASH_SUMMON_BATTLE_PET1 .. " %s", + ['item'] = "/use %s" +} + +local professionMappings = { + [5] = 3, + [7] = 4, + [9] = 5, + [10] = 6 +} +kb.configTitle = { + [BINDING_TYPE_GLOBAL] = 'Global Binds', + [BINDING_TYPE_CHARACTER] = 'Character: %s', + [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s' +} +kb.configDescription = { + [BINDING_TYPE_GLOBAL] = 'The bindings are applied globally.', + [BINDING_TYPE_CHARACTER] = 'Applied when you log onto this character.', + [BINDING_TYPE_SPECIALIZATION] = 'Applied when you log onto this character and are that specialization.', +} + + + +kb.inactiveTalentBindings = {} +kb.configHeaders = {} +kb.loadedProfiles = {} +kb.orderedProfiles = {} +kb.buttons = {} +kb.macros = {} + +-- these are sent to plugin + +local bindings = {} +local macros = {} +local talentBindings = {} + +local protected = { + ['OPENCHATSLASH'] = true, + ['OPENCHAT'] = true, +} + + +local db +local bindHeader, currentHeader = '', '' +local specID, specGlobalID, specName, specDesc, specTexture, characterHeader = 0, 0, 'SPEC_NAME', 'SPEC_DESCRIPTION', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME' +local classHeader, className, classID = '', '', 0 +local bindsCommitted = true +local forceButtonUpdate = false + +--- Control handles +local saveButton, restoreButton, clearButton + +--- Returns conflicting assignment and binding profiles for use in displaying confirmations +kb.IsCommandBound = function(self, command) + local isAssigned, assignedBy = false, db.bindMode + local isBound, boundBy = false, db.bindMode + + + command = command or self.command + for i = 1, #kb.orderedProfiles do + local tier = kb.orderedProfiles[i] + if i ~= db.bindMode then + + if tier.commands[command] then + isAssigned = true + assignedBy = i + end + if tier.bound[command] then + isBound = true + boundBy = i + end + + + --print(' *', configHeaders[i], tier.commands[command], tier.bound[command]) + + if isAssigned and isBound then + break + end + end + + end + + print('|cFFFFFF00IsCommandBound:|r', command:gsub(CLICK_KEYBINDER_MACRO, ''),'|r [profile:', db.bindMode .. ']', isAssigned, isBound, assignedBy, boundBy) + return isAssigned, isBound, assignedBy, boundBy +end + +local talentSpellHardCodes = { + [109248] = 'Binding Shot', +} + +--- Returns a value for use with Texture:SetDesaturated() +kb.BindingIsLocked = function(key) + local success = false + for i = 1, db.bindMode-1 do + local tier = kb.orderedProfiles[i] + if tier.bindings[key] then + success = true + break + end + end + return success +end + +--- Translates GetBindingKey() results into a printable string. +kb.BindingString = function(...) + local stack = {} + for i = 1, select('#', ...) do + local key = select(i, ...) + stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ') + end + + if #stack >= 1 then + return table.concat(stack, ',') + else + return nil + end +end + + +--- Resolve the appropriate command and macroText for the given action parameters +kb.RegisterAction = function(type, id, name) + local macroText, macroName, command = '', '', '' + + if type == 'spell' then + if kb.ProfessionCache[id] then + command = CLICK_KEYBINDER_KEY .. "profession_".. kb.ProfessionCache[id].profOffset .. '_' .. kb.ProfessionCache[id].spellNum + else + command = CLICK_KEYBINDER_KEY ..name + end + else + macroName = type .. ' ' .. name + macroText = ACTION_SCRIPT[type]:format(name) + local baseName, iterative = macroName, 1 + while (macros[macroName] and macros[macroName][1] ~= macroText) do + print(' * cannot use|cFF00FF00', macroName, '|r"'.. (macros[macroName][1] or '') .. '"') + macroName = baseName .. '_' .. iterative + iterative = iterative + 1 + end + if macroName ~= baseName then + print(' * Creating|cFF00FF00', macroName) + else + print(' * Re-using|cFF00FF00', macroName) + end + command = 'CLICK KeyBinderMacro:'.. macroName + macros[macroName] = {macroText, command } + end + + print('RegisterAction', type, id, '->', command , macroText) + return macroName, macroText, command +end + +--- Updates the current KeyBinding for the button's command +kb.StoreBinding = function(self, key) + + if not self.command then + return + end + + if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then + return + end + print('|cFFFFFF00received|cFFFFFF00', self:GetID(), '|cFF00FFFF', key) + + if key == 'ESCAPE' then + local keys = {GetBindingKey(self.command) } + --print('detected', #keys, 'bindings') + for i, key in pairs(keys) do + --print('clearing', key) + SetBinding(key, nil) + SaveBindings(GetCurrentBindingSet()) + if kb.currentProfile.bindings[key] then + kb:print(L('BINDING_REMOVED', self.actionName, kb.configHeaders[db.bindMode])) + kb.currentProfile.bindings[key] = nil + end + if kb.currentProfile.talents[self.actionName] then + kb.currentProfile.talents[self.actionName] = nil + end + bindings[self.actionType][self.actionID] = nil + end + if kb.currentProfile.bound[self.command] then + kb.currentProfile.bound[self.command] = nil + --kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[db.bindMode])) + end + + bindsCommitted = false + self.active = false + else + + local modifier = '' + if IsAltKeyDown() then + modifier = 'ALT-' + end + if IsControlKeyDown() then + modifier = modifier.. 'CTRL-' + end + if IsShiftKeyDown() then + modifier = modifier..'SHIFT-' + end + + + if self.command then + self.binding = modifier..key + + local previousKeys + local previousAction = GetBindingAction(self.binding) + local binding1, binding2, new1, new2 + print(type(previousAction), previousAction) + if previousAction ~= "" and previousAction ~= self.command then + if protected[previousAction] then + -- bounce out if trying to use a protected key + kb.statustext:SetText(L('BINDING_FAILED_PROTECTED', key, GetBindingAction(previousAction))) + kb.bindingstext:SetText(nil) + return + else + kb:print('Discarding keybind for', previousAction) + -- todo: sort out retcon'd talent spells + end + end + + self.pending = true + + bindsCommitted = false + SetBinding(self.binding, self.command) + SaveBindings(GetCurrentBindingSet()) + + local talentInfo + if self.actionType == 'spell' and kb.TalentCache[self.actionID] then + print('conditional binding (talent = "'..self.actionName..'")') + talentInfo = {self.macroName, self.actionName, self.actionType, self.actionID} + local bindings = {GetBindingKey(self.command) } + for i, key in ipairs(bindings) do + tinsert(talentInfo, key) + end + end + + for level, profile in ipairs(kb.orderedProfiles) do + if (level == db.bindMode) then + profile.bound[self.command] = true + if talentInfo then + profile.bindings[self.binding] = nil + else + profile.bindings[self.binding] = self.command + end + profile.talents[self.actionName] = talentInfo + else + profile.bindings[self.binding] = nil + profile.bound[self.command] = nil + kb.currentProfile.talents[self.actionName] = nil + end + if kb.currentProfile.talents[self.actionID] then + kb.currentProfile.talents[self.actionID] = nil + end + end + + kb:print(L('BINDING_ASSIGNED', self.binding, self.actionName, kb.configHeaders[db.bindMode])) + end + end + kb.UpdateSlot(self, true) + KeyBinderSaveButton:Enable() +end + + +kb.inactiveTalentBindings = {} +kb.ApplyTalentBinding = function(talentInfo, cache) + for i = 5, #talentInfo do + local command = CLICK_KEYBINDER_KEY.. talentInfo[2] + SetBinding(talentInfo[i], command) + cprint(' **', talentInfo[i], '->', command) + tinsert(cache, talentInfo[i]) + end +end +kb.CacheTalentBinding = function(talentInfo, cache) + + local spellID = talentInfo[4] + kb.inactiveTalentBindings[spellID] = kb.inactiveTalentBindings[spellID] or {} + kb.inactiveTalentBindings[spellID] = {select(5,unpack(talentInfo)) } + --cprint(spellID, unpack(kb.inactiveTalentBindings[spellID])) +end + +kb.LoadBinding = function(command, name, icon, actionType, actionID, macroName, macroText ) + + if actionType == 'spell' then + KeyBinderKey:SetAttribute("*type-"..name, actionType) + KeyBinderKey:SetAttribute("*"..actionType.."-"..name, name) + + elseif actionType == 'item' then + KeyBinderKey:SetAttribute("*type-"..name, actionType) + KeyBinderKey:SetAttribute("*"..actionType.."-"..name, name) + elseif actionType == 'macro' then + KeyBinderMacro:SetAttribute("*macro-"..macroName, actionID) + else + KeyBinderMacro:SetAttribute("*macrotext-"..macroName, macroText) + end + + cprint('Loading binding', actionType, actionID) + bindings[actionType] = bindings[actionType] or {} + bindings[actionType][actionID] = bindings[actionType][actionID] or {} + bindings[command] = bindings[actionType][actionID] + return bindings[actionType], actionID +end + +kb.ApplyBindings = function (profile) + cprint('binding profile', profile) + for slot, data in pairs(profile.buttons) do + kb.LoadBinding(unpack(data)) + end + + for key, command in pairs(profile.bindings) do + + cprint(' *', key, '->', command) + + --_G.print('HotKey','loading', key, command) + SetBinding(key, command) + if bindings[command] and not tContains(bindings[command], key) then + tinsert(bindings[command], key) + end + end + + for spellName, talentInfo in pairs(profile.talents) do + local dummy = GetSpellInfo(spellName) + local func = kb.CacheTalentBinding + local dest = kb.inactiveTalentBindings + if dummy then + cprint('|cFFBBFF00Active:|r', dummy) + local macroName, spellName, actionType, actionID = unpack(talentInfo) + bindings[actionType] = bindings[actionType] or {} + bindings[actionType][actionID] = {} + func = kb.ApplyTalentBinding + dest = bindings[actionType][actionID] + else + + cprint('|cFFFF4400Inactive:|r', talentInfo[2]) + end + func(talentInfo, dest) + end + + SaveBindings(GetCurrentBindingSet()) +end + +kb.ApplyAllBindings =function () + table.wipe(kb.inactiveTalentBindings) + + -- reflect action key settings + if GetCVarBool("ActionButtonUseKeyDown") then + KeyBinderMacro:RegisterForClicks("AnyDown") + KeyBinderKey:RegisterForClicks("AnyDown") + else + KeyBinderMacro:RegisterForClicks("AnyUp") + KeyBinderKey:RegisterForClicks("AnyUp") + end + + for i, profile in ipairs(kb.orderedProfiles) do + kb.ApplyBindings(profile) + end + -- do this after to ensure that profession binds are properly overridden + kb.UpdateProfessionInfo() + + +end + +kb.Command = function(args, editor) + if args:match("import") then + kb.ImportCommmit(args) + return + elseif args:match("scan") then + kb.ImportScan(args) + kb.ui() + return + elseif args:match("load") then + kb:ApplyAllBindings() + return + end + + if db.showUI then + db.showUI = false + kb:print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.') + kb:Hide() + else + db.showUI = true + kb:print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.') + end + kb.ui(true) +end + +kb.InitProfile = function(profile, prototype) + if not profile then + profile = {} + end + if prototype then + print('appplying prototype', prototype) + for k,v in pairs(prototype) do + if not profile[k] then + profile[k] = v + end + end + end + + profile.bound = profile.bound or {} + profile.buttons = profile.buttons or {} + profile.commands = profile.commands or {} + profile.bindings = profile.bindings or {} + profile.macros = profile.macros or {} + profile.talents = profile.talents or {} + return profile +end + +kb.ResetProfile = function(profile, prototype) + if profile == kb.currentProfile then + for i, button in pairs(buttons) do + kb.ReleaseSlot(button) + end + end + table.wipe(profile) + kb.InitProfile(profile, prototype) +end + + + +--- Handles constructing spec profiles as they are selected + + +--- Obtains profile data or creates the necessary tables +kb.SelectProfileSet = function(name) + + local defaultMode + --- General info + classHeader, className, classID = UnitClass('player') + print('|cFF00FF00profile:|r', name) + print('|cFF00FF00class:|r', UnitClass('player')) + + --- Global + defaultMode = BINDING_TYPE_GLOBAL + kb.InitProfile(db) + kb.loadedProfiles[BINDING_TYPE_GLOBAL] = db + + --- Character + if name then + db[name] = kb.InitProfile(db[name], + {classHeader = classHeader, className = className, classID = classID}) + kb.loadedProfiles[BINDING_TYPE_CHARACTER] = db[name] + defaultMode = BINDING_TYPE_CHARACTER + end + + --- Mutable skills data + kb.UpdateSpecInfo() + kb.UpdateTalentInfo() + + kb.orderedProfiles = {kb.loadedProfiles[BINDING_TYPE_GLOBAL], kb.loadedProfiles[BINDING_TYPE_CHARACTER], kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION]} + if db.bindMode and (not kb.configTitle[db.bindMode]) then + print('fixing bad bindMode value, was', db.bindMode) + db.bindMode = defaultMode + end + + + print(BINDING_TYPE_GLOBAL) + kb.configHeaders[BINDING_TYPE_GLOBAL] = kb.configTitle[BINDING_TYPE_GLOBAL] + kb.configHeaders[BINDING_TYPE_CHARACTER] = kb.configTitle[BINDING_TYPE_CHARACTER]:format(UnitName('player', true)) + kb.configHeaders[BINDING_TYPE_SPECIALIZATION] = kb.configTitle[BINDING_TYPE_SPECIALIZATION]:format(kb.specInfo.name) + + + setmetatable(kb.loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return kb.configHeaders[BINDING_TYPE_GLOBAL] end}) + setmetatable(kb.loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return kb.configHeaders[BINDING_TYPE_CHARACTER] end}) + setmetatable(kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return kb.configHeaders[BINDING_TYPE_SPECIALIZATION] end}) + + print('|cFF00FF00bindMode:|r', db.bindMode) + kb.currentProfile = kb.loadedProfiles[db.bindMode] +end + +local scrollCache = {} +kb.SelectTab = function(self) + scrollCache[db.bindMode] = kb.scrollOffset + db.bindMode = self:GetID() + kb.currentProfile = kb.loadedProfiles[self:GetID()] + kb.scrollOffset = scrollCache[db.bindMode] or 0 + kb.ui(true) +end + +kb.RevertBindings = function() + -- todo: reversion code +end + +kb.ConfirmBindings = function() + SaveBindings(GetCurrentBindingSet()) + bindsCommitted = true + for i, button in ipairs(buttons) do + button.pending = false + end + kb.ApplyAllBindings() + + kb.ui() + kb:print('Keybinds saved.') +end + + + + +--- post ADDON_LOADED +kb.variables = function() + SkeletonKeyDB = SkeletonKeyDB or {spec = {}} + kb.db = SkeletonKeyDB + kb.playerName = UnitName('player') + kb.playerRealm = SelectedRealmName() + kb.profileName = kb.playerRealm .. '_' .. kb.playerName + db = kb.db + + kb.SelectProfileSet(kb.profileName) + -- todo: redo import checking + + + + kb.ApplyAllBindings() + + kb.ui(true) +end + + +kb.wrap = function(module) + kb.modules = kb.modules or {} + tinsert(kb.modules, module) +end + +-- Volatiles Access +kb.GetBindings = function() return bindings end +kb.GetButtons = function() return buttons end +kb.GetCharacterProfile = function () return kb.loadedProfiles[BINDING_TYPE_CHARACTER] end +kb.GetGlobalProfile = function () return kb.loadedProfiles[BINDING_TYPE_GLOBAL] end +kb.GetLooseTalents = function() return talentBindings end +kb.GetReverts = function() return reverts end +kb.GetSpecProfile = function () return kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] end + + +SLASH_SKB1 = "/skb" +SLASH_SKB2 = "/skeletonkey" +SlashCmdList.SKB = kb.Command