Mercurial > wow > skeletonkey
view ActionTemplates.lua @ 78:d4c100b0fd01 v7.1.5-80-release
- Fixed an issue with talent bindings not loading between specialization changes.
- Fixed action button text corruption that occurred after changing assignments on an inactive spell.
author | Nenue |
---|---|
date | Thu, 26 Jan 2017 20:25:04 -0500 |
parents | 6623b7f2c1ca |
children | b9a53385462c |
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 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 local profInfo = kb.DynamicSpells.profession[id] if profInfo then attributeName = "profession_".. profInfo.dynamicIndex .. '_' .. profInfo.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), tostring(name), 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 prefix, attributeName, attributeValue, button = atype[actionType](id, name) local command = prefix .. attributeName return attributeName, attributeValue, command, prefix, button end local spells = {} local SkeletonKey_GetGenericSpell = function(spellName, spellID, icon) if not spells[spellID] then spells[spellID] = {} spells[spellID].actionType = 'spell' spells[spellID].actionID = spellID spells[spellID].actionName = spellName spells[spellID].iconPath = icon spells[spellID].statusText = '|cFFBBBBBBSpell|r' spells[spellID].dynamicType = nil end return spells[spellID] end -- tries to resolve spells from talent overrides/profession book/etc local dynamicTypes = {['profession'] = 'ProfessionCache', ['talent'] = 'TalentCache', ['petaction'] = 'PetInfoCache'} kb.ResolveSpellSlot = function(self) local spellName, spellID, command, icon = self.actionName, self.actionID, self.command, self.iconPath --print(' In:', spellName, spellID, command) --print(GetSpellInfo(spellName or spellID)) local internalName, _, internalIcon, _, _, _, _ = GetSpellInfo(spellName or spellID) local isAvailable = internalName and true if internalName and (internalName ~= spellName) then -- it's a binding for the originating spell, leave it as is print(' |cFFFF4400spell is an override(', internalName, '~=', spellName,') leave the name info alone') self.statusText = '|cFFFFFF00Spell|r' self.isAvailable = true return end -- let's us match spells replaced by talents local info = kb.DynamicSpells[internalName or spellName] if not info then local dynamicType, dynamicIndex, dynamicSubIndex = command:match("(%a+)_(%S+)_(%S+)") if kb.DynamicSpells[dynamicType] then print('|cFFFF4400resolving dynamic type index:', internalName, spellName, command) dynamicIndex = tonumber(dynamicIndex) dynamicSubIndex = tonumber(dynamicSubIndex) local cache = kb.DynamicSpells[dynamicType] print('type:', dynamicType) if dynamicIndex and cache[dynamicIndex] then info = kb.DynamicSpells[dynamicType][dynamicIndex] print('index:', dynamicIndex) if dynamicSubIndex and info[dynamicSubIndex] then info = info[dynamicSubIndex] print('sub-index:', dynamicSubIndex) end isAvailable = true end end if not info then info = SkeletonKey_GetGenericSpell(spellName, spellID, internalIcon or icon) end end info.isAvailable = isAvailable print('|cFF00FF88Slot Details:|r', info.actionName, info.actionID, info.dynamicType, info.isAvailable) for k,v in pairs(info) do --cprint(' ',k,v) self[k] = v end return info end do local PROFILE_VERSION = 320 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)) 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 isAvailable = configTable.isAvailable elseif GetSpellInfo(actionID) then isAvailable = true end elseif actionType == 'item' then actionID = configTable.actionName isAvailable = true 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 if profile.talents then profile.talents = nil end profile.versionID = PROFILE_VERSION end kb.ApplyBindings = function (profile) cprint('|cFF0088FFApplyBindings()') --cprint('binding profile', profile) local version = profile.versionID or 0 if version < PROFILE_VERSION then kb.UpgradeProfile(profile) end -- then buttons for slot, configTable in pairs(profile.buttons) do -- convert old style table if kb.LoadBinding(configTable) then if not configTable.assignedKeys then configTable.assignedKeys = {GetBindingKey(configTable.command)} end --if configTable.dynamicType == 'talent' then -- kb:print(table.concat(configTable.assignedKeys, ', ') .. ' bound to '.. configTable.actionName) --end for _, key in pairs(configTable.assignedKeys) do local command = configTable.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 end end end kb.ApplyAllBindings =function () print('|cFFFFFF00ApplyAllBindings()') 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 local AddSpellInfo = function(id, name, icon, dynamicType, dynamicID, dynamicIndex, dynamicSubIndex, isAvailable) local spell = kb.DynamicSpells[id] or {} spell.actionName = name spell.dynamicType = dynamicType spell.dynamicID = dynamicID spell.iconPath = icon spell.dynamicIndex = dynamicIndex spell.dynamicSubIndex = dynamicSubIndex spell.isAvailable = isAvailable spell.actionType = 'spell' spell.spellbookType = BOOKTYPE_SPELL kb.DynamicSpells[name] = spell local spellList = dynamicType and kb.DynamicSpells[dynamicType] if spellList then spellList[dynamicIndex] = spellList[dynamicIndex] or {} spellList[dynamicIndex][dynamicSubIndex] = spell end cprint('|cFF00FFFFSpellInfo:|r', name, isAvailable, dynamicType or 'spell') end 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.UpdateSpells()|r') cprint('|cFFFFFF00kb.UpdateSpells()|r') if kb.talentsPushed then return end 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.DynamicSpells[spellID] or {} if spellID then AddSpellInfo(spellID, talentName, icon, 'talent', talentID, row, col, selected) end end end for row = 1, MAX_PVP_TALENT_TIERS do for col = 1, MAX_PVP_TALENT_COLUMNS do local talentID, talentName, icon, selected, available, spellID, unlocked = GetPvpTalentInfo(row, col, 1) if spellID then AddSpellInfo(spellID, talentName, icon, 'talent', talentID, row, col, selected) end end end local numTabs = GetNumSpellTabs() for i = 1, numTabs do local name, texture, offset, numSpells = GetSpellTabInfo(i) for spellLine = offset+1, offset+numSpells do local skillType, spellID = GetSpellBookItemInfo(spellLine) if skillType == 'SPELL' then local name, _, icon = GetSpellInfo(spellID) AddSpellInfo(spellID, name, icon, nil, nil, nil, nil, true) elseif skillType == 'FLYOUT' then local flyoutID = GetFlyoutID(spellLine) local _, _, numSlots = GetFlyoutInfo(flyoutID) if numSlots then for slot = 1, numSlots do local spellID, isKnown = GetFlyoutSlotInfo(flyoutID, slot) local name, rank, icon = GetSpellInfo(spellID) AddSpellInfo(spellID, name, icon, nil, nil, nil, nil, true) end end end end end kb.talentsPushed = true kb.UpdateDynamicButtons('talent') end kb.UpdateSpells = kb.UpdateTalentInfo 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) for j = 1, numSpells do local spellName, _, icon, _, _, _, spellID = GetSpellInfo(spellOffset+j, BOOKTYPE_PROFESSION) cprint(j, spellName) local profInfo = { actionType = 'spell', actionName = spellName, actionID = spellID, iconPath = icon, dynamicIndex = i, dynamicSubIndex = j, dynamicType = 'profession', spellbookOffset = (spellOffset+j), spellbookType = BOOKTYPE_PROFESSION, isAvailable = true, dynamicID = profID, } kb.SecureAttribute(SkeletonKeyKey, "*type-profession_"..i .. '_' ..j, "spell") kb.SecureAttribute(SkeletonKeyKey, "*spell-profession_"..i .. '_' ..j, spellName) kb.DynamicSpells[spellName] = profInfo kb.DynamicSpells[spellID] = profInfo kb.DynamicSpells.profession[spellName] = profInfo kb.DynamicSpells.profession[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