diff ActionTemplates.lua @ 70:131d9190db6b

Curseforge migration
author Nenue
date Wed, 28 Dec 2016 16:31:15 -0500
parents
children ca3118127e5e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ActionTemplates.lua	Wed Dec 28 16:31:15 2016 -0500
@@ -0,0 +1,635 @@
+-- SkeletonKey
+-- ActionTemplates.lua
+-- Created: 7/29/2016 9:14 PM
+-- %file-revision%
+-- Code dealing with the implementation of action hotkeys
+
+local tostring, tonumber, pairs, ipairs = tostring, tonumber, pairs, ipairs
+local unpack, SetBinding = unpack, SetBinding
+local tinsert, tContains, select, wipe =  tinsert, tContains, select, table.wipe
+local GetSpellBookItemInfo, GetSpellBookItemName, GetSpellInfo = GetSpellBookItemInfo, GetSpellBookItemName, GetSpellInfo
+local GetSpecialization, GetSpecializationInfo, IsPassiveSpell, IsTalentSpell = GetSpecialization, GetSpecializationInfo, IsPassiveSpell, IsTalentSpell
+local PetHasSpellbook, PetHasActionBar, GetPetActionInfo, HasPetSpells = PetHasSpellbook, PetHasActionBar, GetPetActionInfo, HasPetSpells
+local GetProfessions, GetProfessionInfo, GetTalentInfo = GetProfessions, GetProfessionInfo, GetTalentInfo
+local GetNumBindings, GetBinding = GetNumBindings, GetBinding
+
+local _, kb = ...
+local print = (DEVIAN_PNAME == 'SkeletonKey') and function(...) print('SK', ...) end or nop
+local cprint = (DEVIAN_PNAME == 'SkeletonKey') and function(...) _G.print('Cfg', ...) end or nop
+
+local CLICK_KEYBINDER_MACRO = "CLICK SkeletonKeyMacro:"
+local CLICK_KEYBINDER_KEY = "CLICK SkeletonKeyKey:"
+local PET_BASIC_SUBTEXT = 'Basic Attack'
+local PET_SPECIAL_SUBTEXT = 'Special Ability'
+local PETACTION_SCRIPT = {
+  [PET_ACTION_MOVE_TO] = {'pet_move_to', SLASH_PET_MOVE_TO1},
+  [PET_ACTION_ATTACK] = {'pet_attack', SLASH_PET_ATTACK1},
+  [PET_ACTION_FOLLOW] = {'pet_follow', SLASH_PET_FOLLOW1},
+  [PET_ACTION_WAIT] = {'pet_stay', SLASH_PET_STAY1 },
+  [PET_MODE_AGGRESSIVE] = {'pet_aggressive', SLASH_PET_AGGRESSIVE1 },
+  [PET_MODE_DEFENSIVE] = { 'pet_defensive', SLASH_PET_DEFENSIVE1},
+  [PET_MODE_PASSIVE] = { 'pet_passive', SLASH_PET_PASSIVE1},
+  [PET_MODE_ASSIST] = {'pet_assist', SLASH_PET_ASSIST1},
+}
+local SECONDARY_PROFESSIONS = {
+  [5] = 3,
+  [7] = 4,
+  [9] = 5,
+  [10] = 6
+}
+local petSpellCache,petSubtextCache
+local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544
+
+--kb.ChangedBindings = {}
+--kb.ActionTypes = {}
+
+local atype = kb.ActionTypes
+
+--- Caps Lock
+atype['mount'] = function(id, name)
+  if id == SUMMON_RANDOM_FAVORITE_MOUNT_SPELL then
+    return CLICK_KEYBINDER_MACRO, 'mount_random', "/script C_MountJournal.SummonByID(0)", SkeletonKeyMacro
+  else
+    return CLICK_KEYBINDER_MACRO, 'mount_'..id, "/script C_MountJournal.SummonByID("..id..")", SkeletonKeyMacro
+  end
+end
+
+atype['macro'] = function(id, name)
+  local _, _, text = GetMacroInfo(id)
+  return CLICK_KEYBINDER_MACRO, 'macro_' .. tostring(name), name, SkeletonKeyMacro
+end
+
+atype['equipset'] = function(id, name)
+    return CLICK_KEYBINDER_MACRO, 'equipset_'..tostring(name), "/script UseEquipmentSet("..tostring(id)..")", SkeletonKeyMacro
+end
+
+atype['spell'] = function(id, name)
+  local attributeName = name
+  if kb.ProfessionCache[id] then
+    attributeName = "profession_".. kb.ProfessionCache[id].dynamicIndex .. '_' .. kb.ProfessionCache[id].dynamicSubIndex
+  end
+  return CLICK_KEYBINDER_KEY, attributeName, name, SkeletonKeyKey
+end
+
+atype['petaction'] = function(_, name)
+  -- ID doesn't exist for basic commands, even though they can be picked up
+  local attributeName, attributeValue = "petaction_" .. tostring(name), "/cast "..tostring(name)
+
+  if not petSpellCache then
+    kb.UpdatePetInfo()
+  end
+  -- Compose a multi-macro for subtext abilities
+  if petSpellCache[name] then
+    attributeValue = ""
+    for spellName, enabled in pairs(petSubtextCache[petSpellCache[name]]) do
+      attributeValue = attributeValue .. "/cast " .. spellName .. "\n"
+    end
+  end
+
+  if PETACTION_SCRIPT[name] then
+    attributeName, attributeValue = unpack(PETACTION_SCRIPT[name])
+  elseif kb.PetCache.special[name] then
+    attributeName = "petaction_"..kb.PetCache.special[name][3].."_" .. tonumber(kb.PetCache.special[name][6])
+  end
+  return CLICK_KEYBINDER_MACRO, attributeName, attributeValue, SkeletonKeyMacro
+end
+
+atype['battlepet'] = function(id, name)
+  return CLICK_KEYBINDER_MACRO, 'battlepet_' .. tostring(name), SLASH_SUMMON_BATTLE_PET1 .. " " .. tostring(name), SkeletonKeyMacro
+end
+
+atype['item'] = function(id, name)
+  return CLICK_KEYBINDER_KEY, tostring(name), id, SkeletonKeyKey
+end
+
+
+--- Resolves the SecureActionButton attribute names used for the given action
+kb.RegisterAction = function(actionType, id, name)
+  assert(atype[actionType], 'Missing actionType handler for `'..tostring(actionType)..'`')
+  local target, attributeName, attributeValue, button  = atype[actionType](id, name)
+  local command = target .. attributeName
+
+  return attributeName, attributeValue, command, target, button
+end
+
+
+
+
+kb.ApplyTalentBinding = function(talentInfo, cache)
+  talentInfo.assignedKeys = talentInfo.assignedKeys or {}
+  for i , key in pairs(talentInfo.assignedKeys) do
+    local command = CLICK_KEYBINDER_KEY.. talentInfo.actionName
+    SetBinding(key, command)
+    cprint(' **', i, '->', command)
+    tinsert(cache, talentInfo)
+  end
+end
+kb.CacheTalentBinding = function(talentInfo, cache)
+
+  local spellID = talentInfo.actionID
+  cache[spellID] = cache[spellID] or {}
+  cache[spellID] = talentInfo
+  cprint(spellID, unpack(kb.TalentBindings[spellID]))
+end
+
+
+do
+  local commandActions = {}
+  local bindings = kb.bindings
+  local key, macro = SkeletonKeyKey, SkeletonKeyMacro
+  kb.LoadBinding = function( configTable)
+    if configTable.command then
+      configTable.command = configTable.command:gsub('KeyBinder', 'SkeletonKey')
+    end
+
+    local command, name, icon, actionType, actionID, macroName, macroText =
+      configTable.command, configTable.actionName, configTable.iconPath, configTable.actionType,
+      configTable.actionID, configTable.macroName, configTable.macroText
+
+
+    local indexKey = actionType..'_'..actionID
+    local actionPrefix = "*"..actionType.."-"
+    local button = SkeletonKeyKey
+    local isAvailable = true
+    local specialButtonType
+    if actionType == 'spell' then
+      if not GetSpellInfo(actionID) then
+        isAvailable = nil
+      end
+      local dynamicInfo = kb.TalentCache[name] or kb.ProfessionCache[name]
+      if dynamicInfo then
+        cprint('|cFF00FFFFDynamicInfo:|r', name)
+        for k, v in pairs(dynamicInfo) do
+          cprint(' --', k, v)
+          configTable[k] = v
+        end
+      end
+    else
+      if actionType ~= 'macro' then
+        actionPrefix = '*macrotext-'
+      end
+
+      specialButtonType = 'macro'
+    end
+
+    local attributeSuffix, attributeValue, command, target, button =  kb.RegisterAction(actionType, actionID, name)
+    local actionKey = actionPrefix .. attributeSuffix
+    cprint('|cFF00FF88LoadBinding()|r', button:GetName(), "*type-"..attributeSuffix, actionType,  '|cFFFFFF00'..actionKey, attributeValue)
+
+
+
+    if isAvailable then
+      kb.SecureAttribute(button, "*type-"..attributeSuffix, specialButtonType or actionType)
+      kb.SecureAttribute(button, actionKey, attributeValue)
+      kb.bindings[indexKey] = configTable.assignedKeys
+      commandActions[command] = kb.bindings[indexKey]
+      return command, kb.bindings[indexKey]
+    else
+      return nil
+    end
+  end
+
+  local usedSlots = {}
+  kb.ApplyBindings = function (profile)
+    cprint('|cFF0088FFApplyBindings()')
+    --cprint('binding profile', profile)
+
+    wipe(usedSlots)
+    for slot, configTable in pairs(profile.buttons) do
+
+
+      if #configTable >= 1 then
+        local command, name, icon, actionType, actionID, macroName, macroText, spellbookSlot, spellbookType = unpack(configTable)
+
+        cprint('|CFFFF4400Fixing pad entry', slot, command, name)
+        local assignedKeys = {GetBindingKey(command)}
+        for k,v in pairs(profile.bindings) do
+          if v == command then
+            tinsert(assignedKeys, k)
+          end
+        end
+
+        configTable = {
+          command = command,
+          actionType = actionType,
+          actionName = name,
+          actionID = actionID,
+          macroName = macroName,
+          macroText = macroText,
+          iconPath = icon,
+          spellbookSlot = spellbookSlot,
+          spellbookType = spellbookType,
+          assignedKeys = assignedKeys
+        }
+        kb.currentProfile.buttons[slot] = configTable
+      end
+      if not configTable.actionID then
+        configTable.actionID = configTable.actionName
+      end
+      if not configTable.iconPath then
+        print('corrected missing icon')
+        configTable.iconPath = GetSpellTexture(configTable.actionName)
+      end
+
+      usedSlots[configTable.actionName or configTable.actionID] = true
+      usedSlots[configTable.command] = true
+
+      if kb.LoadBinding(configTable) then
+        configTable.isAvailable = true
+      end
+    end
+
+    for key, command in pairs(profile.bindings) do
+      command = command:gsub('KeyBinder', 'SkeletonKey')
+      profile.bindings[key] = command
+      cprint('|cFF00FFFF'.. key .. '|r to|cFF00FF00', command)
+      SetBinding(key, command)
+
+      if commandActions[command] and not tContains(commandActions[command], key) then
+        tinsert(commandActions[command], key)
+      end
+    end
+
+    for spellName, talentInfo in pairs(profile.talents) do
+      if not usedSlots[spellName] then
+        cprint('|cFFFF4400Unslotted talent', spellName)
+        profile.talents[spellName] = nil
+      end
+    end
+    for command in pairs(profile.bound) do
+      if not usedSlots[command] then
+        cprint('|cFFFF4400Unslotted bound entry', command)
+        profile.bound[command] = nil
+        profile.commands[command] = nil
+      end
+    end
+    for command in pairs(profile.commands) do
+      if not usedSlots[command] then
+        cprint('|cFFFF4400Unslotted command entry', command)
+        profile.commands[command] = nil
+      end
+    end
+
+  end
+
+  kb.ApplyAllBindings =function ()
+    cprint('|cFF0088FFApplyAllBindings()')
+    wipe(kb.TalentBindings)
+    wipe(kb.bindings)
+    --kb:print('Loading binding profile', kb.profileName)
+
+    -- reflect action key settings
+    if GetCVarBool("ActionButtonUseKeyDown") then
+      SkeletonKeyMacro:RegisterForClicks("AnyDown")
+      SkeletonKeyKey:RegisterForClicks("AnyDown")
+    else
+      SkeletonKeyMacro:RegisterForClicks("AnyUp")
+      SkeletonKeyKey: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()
+
+    SaveBindings(GetCurrentBindingSet())
+  end
+end
+
+
+kb.specInfo = {}
+kb.UpdateSpecInfo = function()
+  kb.specInfo.id = GetSpecialization()
+  kb.specInfo.globalID, kb.specInfo.name, kb.specInfo.desc, kb.specInfo.texture = GetSpecializationInfo(kb.specInfo.id)
+end
+
+kb.UpdateMacroInfo =  function()
+  print('|cFFFFFF00kb.UpdateMacroInfo()|r')
+  for index = 1, GetNumMacros() do
+    local name = GetMacroInfo(index)
+    kb.SecureAttribute(SkeletonKeyMacro, "*type-macro_"..tostring(name), 'macro')
+    kb.SecureAttribute(SkeletonKeyMacro, "*macro-macro_"..tostring(name), index)
+  end
+end
+
+kb.UpdateTalentInfo = function()
+  print('|cFFFFFF00kb.UpdateTalentInfo()|r')
+  if kb.talentsPushed then
+    return
+  end
+  wipe(kb.TalentCache)
+  for row =1, MAX_TALENT_TIERS do
+    for col = 1, NUM_TALENT_COLUMNS do
+      local talentID, talentName, icon, selected, available, spellID = GetTalentInfo(row, col, 1)
+      local talentInfo = kb.TalentCache[spellID] or {}
+      if spellID then
+        talentInfo.actionType = 'spell'
+        talentInfo.actionName = talentName
+        talentInfo.dynamicType = 'talent'
+        talentInfo.dynamicID = talentID
+        talentInfo.dynamicIndex = row
+        talentInfo.dynamicSubIndex = col
+        talentInfo.actionID = spellID
+        talentInfo.isAvailable = selected
+        kb.TalentCache[spellID] = talentInfo
+        kb.TalentCache[talentName] = talentInfo
+        kb.DynamicSpells[spellID] = talentInfo
+        kb.DynamicSpells[talentName] = talentInfo
+      end
+
+      --print('Talent ', row, col, spellID, talentName)
+    end
+  end
+
+  for row = 1, MAX_PVP_TALENT_TIERS do
+    for col = 1, MAX_PVP_TALENT_COLUMNS do
+      local id, name, icon, selected, available, spellID, unlocked = GetPvpTalentInfo(row, col, 1)
+      if spellID then
+      local talentInfo = kb.TalentCache[spellID] or {}
+        talentInfo.actionType = 'spell'
+        talentInfo.actionName = name
+        talentInfo.dynamicType = 'talent'
+        talentInfo.dynamicID = id
+        talentInfo.actionID = spellID
+        talentInfo.isAvailable = selected
+        kb.TalentCache[spellID] = talentInfo
+        kb.TalentCache[name] = talentInfo
+        kb.DynamicSpells[spellID] = talentInfo
+        kb.DynamicSpells[name] = talentInfo
+      end
+    end
+  end
+
+  kb.talentsPushed = true
+
+  kb.UpdateDynamicButtons('talent')
+end
+
+kb.UpdateProfessionInfo = function()
+  wipe(kb.ProfessionCache)
+  local profs = {GetProfessions() }
+  --print(GetProfessions())
+  local primaryNum = 0
+  for i = 1, 6 do
+    if profs[i] then
+      local profID = profs[i]
+      local profName, texture, _, _, numSpells, spellOffset = GetProfessionInfo(profID)
+      cprint(i, profID, profName, numSpells, spellOffset)
+      if not SECONDARY_PROFESSIONS[profID] then
+        primaryNum = primaryNum + 1
+      end
+      local profNum = SECONDARY_PROFESSIONS[profID] or primaryNum
+      cprint(i, profNum)
+
+
+      kb.ProfessionCache[profNum] = kb.ProfessionCache[profNum] or {}
+
+      for j = 1, numSpells do
+        local spellName, _, icon, _, _, _, spellID = GetSpellInfo(spellOffset+j, BOOKTYPE_PROFESSION)
+        cprint(j, spellName)
+        local profInfo = {
+          actionType = 'spell',
+          actionName = spellName,
+          statusText = 'Profession ' .. i,
+          actionID = spellID,
+          iconPath = icon,
+          dynamicIndex = i,
+          dynamicSubIndex = j,
+          dynamicType = 'profession',
+          spellbookOffset = (spellOffset+j),
+          spellbookType = BOOKTYPE_PROFESSION,
+          isAvailable = true,
+          -- need to check if necessary
+          uniqueID = profID,
+        }
+
+        kb.SecureAttribute(SkeletonKeyKey, "*type-profession_"..i .. '_' ..j, "spell")
+        kb.SecureAttribute(SkeletonKeyKey, "*spell-profession_"..i .. '_' ..j, spellName)
+
+        kb.ProfessionCache[spellName] = profInfo
+        kb.ProfessionCache[spellID] = profInfo
+
+        kb.DynamicSpells[spellName] = profInfo
+        kb.DynamicSpells[spellID] = profInfo
+
+        kb.DynamicSpells.profession[i] = kb.DynamicSpells.profession[i] or {}
+        kb.DynamicSpells.profession[i][j] = profInfo
+        --print('  |cFF0088FF['..i..']|r|cFFFF44BB['..spellOffset+i..']|r', spellName, "profession_"..i .. '_' ..j)
+      end
+    end
+
+  end
+
+  kb.UpdateDynamicButtons('profession')
+end
+
+
+
+kb.UpdatePetInfo = function()
+  local hasPetSpells, petType = HasPetSpells()
+
+  -- reconcile saved data if it becomes available
+  if kb.db then
+    kb.db.petSpellsDB = kb.db.petSpellsDB or {}
+    kb.db.petSpellsDB.subtext = kb.db.petSpellsDB.subtext or {}
+    kb.db.petSpellsDB.spell = kb.db.petSpellsDB.spell or {}
+    local spellCache = kb.db.petSpellsDB.spell
+    local subtextCache = kb.db.petSpellsDB.subtext
+    if petSpellCache then
+      for k,v in pairs(petSpellCache) do
+        if not spellCache[k] then
+          spellCache[k] = v
+        end
+      end
+    end
+    petSpellCache = spellCache
+    if petSubtextCache then
+      for k,v in pairs(petSubtextCache) do
+        if not subtextCache[k] then
+          subtextCache[k] = v
+        end
+      end
+    end
+    petSubtextCache = subtextCache
+  else
+    petSpellCache = {}
+    petSubtextCache = {}
+  end
+
+  if PetHasSpellbook() then
+    --print('PET SPELLBOOK')
+    local spellbookOffset = 1
+    local specialNum = {}
+    local newSubtextItems = false
+
+    repeat
+
+      local spellType, spellID = GetSpellBookItemInfo(spellbookOffset, BOOKTYPE_PET)
+      local spellName, subText = GetSpellBookItemName(spellbookOffset, BOOKTYPE_PET)
+      local texture = GetSpellBookItemTexture(spellbookOffset, BOOKTYPE_PET)
+      if (spellType == 'SPELL') and (not IsPassiveSpell(spellbookOffset, BOOKTYPE_PET)) then
+        local info = kb.PetCache[spellName] or {}
+        kb.PetCache.spellslot[spellName] = {spellbookOffset, spellName, subText, spellID, texture}
+        --print('|cFF00FF88spellslot['..spellName..']|r', '=>', i, subText)
+
+        if subText then
+          kb.PetCache.subtext[subText] = kb.PetCache.subtext[subText] or {}
+          specialNum[subText] = (specialNum[subText] or 0) + 1
+
+          petSpellCache[spellName] = subText
+          petSubtextCache[subText] = petSubtextCache[subText] or {}
+
+          -- add to the list
+          if not petSubtextCache[subText][spellName] then
+            petSubtextCache[subText][spellName] = true
+            newSubtextItems = true
+            --print('|cFF00FFFFspecial['..spellName..']|r', '\n','|cFF00FFFFsubtext['..subText..']['..specialNum[subText]..']|r', '=>', i, spellName, subText, spellID,  texture, specialNum[subText])
+          end
+
+
+
+          local entry = {spellbookOffset, spellName, subText, spellID,  texture, specialNum[subText] }
+
+
+          info.spellbookOffset = spellbookOffset
+          info.spellbookType = BOOKTYPE_PET
+          info.actionName = spellName
+          info.spellID = spellID
+          info.dynamicType = 'petaction'
+          info.dynamicID = spellID
+          info.dynamicIndex = subText
+          info.dynamicSubIndex = specialNum[subText]
+          info.isAvailable = true
+
+          kb.PetCache.special[spellName] = info
+          kb.PetCache.subtext[subText][specialNum[subText]] = info
+          kb.DynamicSpells[spellName] = info
+          kb.DynamicSpells[spellID] = info
+
+        end
+
+        if spellID then
+          kb.PetCache.spell[spellbookOffset] = {spellID, spellName, subText}
+          --print('|cFF0088FFspell['..i..']|r', '=>', spellID, spellName, subText)
+        end
+
+        kb.PetCache[spellName] = info
+      end
+
+      spellbookOffset = spellbookOffset + 1
+    until spellType == nil
+
+    if newSubtextItems then
+
+      local macrotext = ""
+      for subText, spells in pairs(petSubtextCache) do
+        if specialNum[subText] then
+          for spellName, enabled in pairs(spells) do
+            macrotext = macrotext .. "/cast " .. spellName .. "\n"
+          end
+          kb.SecureAttribute(SkeletonKeyMacro, "*macrotext-petaction_"..subText.."_"..specialNum[subText], macrotext)
+        end
+      end
+    end
+
+
+  else
+    --print('NO PET SPELLBOOK')
+    wipe(kb.PetCache.spell)
+    wipe(kb.PetCache.spellslot)
+  end
+
+  if PetHasActionBar() then
+    --print('PET ACTION BAR')
+    for i = 1, 10 do
+
+
+      local name, subtext, texture, isToken, isActive = GetPetActionInfo(i)
+      if name then
+        kb.PetCache.action[i] = {name, subtext, texture, isToken, isActive }
+
+
+      end
+      --print('|cFFFFFF00action['..i..']|r', name, subtext, texture)
+    end
+  else
+    --print('NO PET ACTION BAR')
+    wipe(kb.PetCache.action)
+  end
+
+  kb.UpdateDynamicButtons('petaction')
+
+end
+
+kb.UpdateSystemBinds = function()
+  wipe(kb.SystemBindings)
+  local n = GetNumBindings()
+  for i=1, n do
+    local command, key1, key2 = GetBinding(i)
+    if not command:match('ACTION.*%d+') then
+      if key1 then
+        kb.SystemBindings[key1] = command
+      end
+      if key2 then
+        kb.SystemBindings[key2] = command
+      end
+    else
+      --print('ignoring action button binding', command)
+    end
+  end
+end
+
+kb.UpdateDynamicButtons = function(dynamicType)
+  for i, button in ipairs(kb.buttons) do
+    if button.isDynamic == dynamicType then
+      kb.UpdateSlot(button, true)
+    end
+  end
+end
+
+kb.pendingAttributes = {}
+kb.SecureAttribute = function(target, name, value)
+  if InCombatLockdown() then
+    if #kb.pendingAttributes == 0 then
+      kb:print(kb.L('Key bindings will be applied when you exit combat.'))
+    end
+
+    tinsert(kb.pendingAttributes, {target, name, value})
+    SkeletonKey:RegisterEvent('PLAYER_REGEN_ENABLED')
+
+  else
+
+    --cprint('|cFFFF4444' .. target:GetName()..'|r.|cFFFFFF00'.. tostring(name)..'|r = "'..tostring(value)..'"')
+    target:SetAttribute(name, value)
+  end
+end
+
+kb.PLAYER_REGEN_ENABLED = function()
+  if #kb.pendingAttributes >= 1 then
+    local args = tremove(kb.pendingAttributes)
+    while args do
+      local target, name, value = unpack(args)
+      --print(target:GetName(), 'attribute', '"'.. tostring(name)..'" = "'..tostring(value)..'"')
+      cprint('deferred', target:GetName(), 'attribute', '"'.. tostring(name)..'" = "'..tostring(value)..'"')
+      target:SetAttribute(name, value)
+      args = tremove(kb.pendingAttributes)
+    end
+  end
+
+  if #kb.pendingCalls >= 1 then
+
+    local func = tremove(kb.pendingCalls)
+    while func do
+      func()
+    end
+  end
+end
+
+kb.UpdateBindingsCache = function(actionType, actionID, bindings)
+  local indexKey = actionType .. '_' .. actionID
+  kb.bindings[indexKey] = bindings
+
+  cprint('|cFF00FF00'..indexKey..'|r = {'.. table.concat(bindings,', ').. '}')
+  tinsert(kb.ChangedBindings, {actionType, actionID})
+end
\ No newline at end of file