view ActionTemplates.lua @ 73:68365bda5ab5

oops macros broke again
author Nenue
date Sat, 07 Jan 2017 12:52:05 -0500
parents c48913c5924c
children 9824d524a661
line wrap: on
line source
-- 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
    local specialButtonType
    if actionType == 'spell' then
      cprint(GetSpellInfo(actionID))
      cprint(GetSpellInfo(name))
      if GetSpellInfo(name) then
        isAvailable = true
      end
      local dynamicInfo = kb.DynamicSpells[name]
      if dynamicInfo then
        configTable.assignedKeys = configTable.assignedKeys or {GetBindingKey(configTable.command)}
        cprint('|cFF00FFFFDynamicInfo:|r', dynamicInfo.dynamicType, table.concat(configTable.assignedKeys, ','))
        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'
      isAvailable = true
    end

    if isAvailable then

      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, isAvailable)


      kb.SecureAttribute(button, "*type-"..attributeSuffix, specialButtonType or actionType)
      kb.SecureAttribute(button, actionKey, attributeValue)

      cprint('|cFFFF4400add', name, isAvailable, indexKey, unpack(configTable.assignedKeys))
      kb.bindings[indexKey] = configTable.assignedKeys
      commandActions[command] = kb.bindings[indexKey]
      return command, kb.bindings[indexKey]
    else
      if kb.bindings[indexKey] then
        cprint('|cFFFF4400remove', name, isAvailable, indexKey, unpack(configTable.assignedKeys))
        kb.bindings[indexKey] = nil
      end

      return nil
    end
  end


  local usedSlots = {}
  kb.UpgradeProfile = function(profile)
    wipe(usedSlots)
    for slot, configTable in pairs(profile.buttons) do

      -- convert old style table
      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
        }

        local dynamic = kb.DynamicSpells[name]
        if dynamic then
          configTable.dynamicType = dynamic.dynamicType
          configTable.dynamicIndex = dynamic.dynamicIndex
          configTable.dynamicSubIndex = dynamic.dynamicSubIndex
          configTable.dynamicID = dynamic.dynamicID
          if configTable.dynamicType == 'talent' then
            profile.talents[name] = configTable
          end
        end


        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
    end


    -- clean up legacy data
    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.ApplyBindings = function (profile)
    cprint('|cFF0088FFApplyBindings()')
    --cprint('binding profile', profile)

    local version = profile.versionID or 0
    if version < 310 then
      kb.UpgradeProfile(profile)
    end

    -- do flat bindings to start
    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

    -- then buttons
    for slot, configTable in pairs(profile.buttons) do
      -- convert old style table
      if kb.LoadBinding(configTable) then
        local talent = profile.talents[configTable.actionName]
        if talent then
          configTable.assignedKeys = talent.assignedKeys
        end
        if not configTable.assignedKeys then
          configTable.assignedKeys = {GetBindingKey(configTable.command) }
        end
        if configTable.dynamicType then
          kb:print(table.concat(configTable.assignedKeys, ', ') .. ' bound to '.. configTable.actionName)
        end
      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.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.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