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