Nenue@0: -------------------------------------------- Nenue@5: -- SkeletonKey Nenue@5: -- Krakyn-Mal'Ganis Nenue@0: -- @project-revision@ @project-hash@ Nenue@0: -- @file-revision@ @file-hash@ Nenue@0: -- Created: 6/16/2016 3:47 AM Nenue@0: -------------------------------------------- Nenue@54: -- Header script Nenue@0: Nenue@5: local _ Nenue@5: local kb, print = LibStub("LibKraken").register(KeyBinder) Nenue@14: kb.L = setmetatable({}, { Nenue@17: __call = function(t, k, ...) return format(t[k] or k, ...) end Nenue@14: }) Nenue@14: local L = kb.L Nenue@7: Nenue@5: --- Caps Lock literals Nenue@14: L.BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).' Nenue@14: L.BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.' Nenue@52: L.BINDING_FAILED_PROTECTED = '|cFFFF4400Cannot use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400). Uncheck "Safety" to ignore this restraint.|r' Nenue@14: Nenue@14: Nenue@5: local BINDING_TYPE_SPECIALIZATION = 3 Nenue@5: local BINDING_TYPE_CHARACTER = 2 Nenue@5: local BINDING_TYPE_GLOBAL = 1 Nenue@6: kb.configTitle = { Nenue@17: [BINDING_TYPE_GLOBAL] = L('Global Binds'), Nenue@17: [BINDING_TYPE_CHARACTER] = L('Character: %%s'), Nenue@17: [BINDING_TYPE_SPECIALIZATION] = L('Specialization: %%s') Nenue@0: } Nenue@6: kb.configDescription = { Nenue@17: [BINDING_TYPE_GLOBAL] = L('The bindings are applied globally.'), Nenue@17: [BINDING_TYPE_CHARACTER] = L('Applied when you log onto this character.'), Nenue@17: [BINDING_TYPE_SPECIALIZATION] = L('Applied when you select this specialization.'), Nenue@5: } Nenue@5: Nenue@27: kb.ChangedBindings = {} Nenue@19: kb.SystemBindings = {} Nenue@19: kb.ActionTypes = {} Nenue@19: kb.TalentBindings = {} Nenue@27: kb.PetCache = { Nenue@27: spell = {}, Nenue@27: spellslot = {}, Nenue@27: action = {}, Nenue@27: special = {}, Nenue@27: subtext = {} Nenue@27: } Nenue@27: kb.TalentCache = {} Nenue@27: kb.ProfessionCache = {} Nenue@27: kb.pendingCalls = {} Nenue@27: kb.pendingAttributes = {} Nenue@0: Nenue@6: kb.configHeaders = {} Nenue@6: kb.loadedProfiles = {} Nenue@6: kb.orderedProfiles = {} Nenue@6: kb.buttons = {} Nenue@6: kb.macros = {} Nenue@17: kb.bindings = {} Nenue@15: kb.petFrames = {} -- pet data is slightly delayed, their buttons are indexed here so they can be refreshed Nenue@15: kb.talentFrames = {} Nenue@15: kb.professionFrames = {} Nenue@0: Nenue@14: -- these are sent to plugin Nenue@14: Nenue@14: Nenue@14: local db Nenue@50: local _G = _G Nenue@50: local UnitName, SelectedRealmName, InCombatLockdown, UnitClass = UnitName, SelectedRealmName, InCombatLockdown, UnitClass Nenue@50: local tostring, select, tinsert, pairs = tostring, select, tinsert, pairs Nenue@50: local concat, wipe = table.concat, table.wipe Nenue@5: local classHeader, className, classID = '', '', 0 Nenue@5: Nenue@5: --- Control handles Nenue@0: local saveButton, restoreButton, clearButton Nenue@0: Nenue@5: --- Returns conflicting assignment and binding profiles for use in displaying confirmations Nenue@6: kb.IsCommandBound = function(self, command) Nenue@6: local isAssigned, assignedBy = false, db.bindMode Nenue@6: local isBound, boundBy = false, db.bindMode Nenue@5: command = command or self.command Nenue@6: for i = 1, #kb.orderedProfiles do Nenue@6: local tier = kb.orderedProfiles[i] Nenue@6: if i ~= db.bindMode then Nenue@5: Nenue@5: if tier.commands[command] then Nenue@5: isAssigned = true Nenue@5: assignedBy = i Nenue@5: end Nenue@5: if tier.bound[command] then Nenue@5: isBound = true Nenue@5: boundBy = i Nenue@5: end Nenue@5: Nenue@5: Nenue@5: --print(' *', configHeaders[i], tier.commands[command], tier.bound[command]) Nenue@5: Nenue@5: if isAssigned and isBound then Nenue@0: break Nenue@0: end Nenue@0: end Nenue@5: Nenue@0: end Nenue@5: Nenue@17: print('|cFFFFFF00IsCommandBound:|r', command,'|r [profile:', db.bindMode .. ']', isAssigned, isBound, assignedBy, boundBy) Nenue@5: return isAssigned, isBound, assignedBy, boundBy Nenue@0: end Nenue@0: Nenue@5: local talentSpellHardCodes = { Nenue@5: [109248] = 'Binding Shot', Nenue@5: } Nenue@5: Nenue@0: --- Returns a value for use with Texture:SetDesaturated() Nenue@6: kb.BindingIsLocked = function(key) Nenue@0: local success = false Nenue@6: for i = 1, db.bindMode-1 do Nenue@6: local tier = kb.orderedProfiles[i] Nenue@0: if tier.bindings[key] then Nenue@0: success = true Nenue@0: break Nenue@0: end Nenue@0: end Nenue@0: return success Nenue@0: end Nenue@0: Nenue@0: --- Translates GetBindingKey() results into a printable string. Nenue@6: kb.BindingString = function(...) Nenue@0: local stack = {} Nenue@0: for i = 1, select('#', ...) do Nenue@0: local key = select(i, ...) Nenue@5: stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ') Nenue@0: end Nenue@0: Nenue@0: if #stack >= 1 then Nenue@50: return concat(stack, ',') Nenue@0: else Nenue@0: return nil Nenue@0: end Nenue@0: end Nenue@0: Nenue@5: Nenue@5: Nenue@5: Nenue@0: Nenue@5: kb.Command = function(args, editor) Nenue@5: if args:match("import") then Nenue@5: kb.ImportCommmit(args) Nenue@5: return Nenue@5: elseif args:match("scan") then Nenue@5: kb.ImportScan(args) Nenue@5: kb.ui() Nenue@5: return Nenue@5: elseif args:match("load") then Nenue@5: kb:ApplyAllBindings() Nenue@0: return Nenue@0: end Nenue@0: Nenue@5: if db.showUI then Nenue@5: db.showUI = false Nenue@5: kb:print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.') Nenue@5: kb:Hide() Nenue@5: else Nenue@1: db.showUI = true Nenue@5: kb:print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.') Nenue@5: end Nenue@5: kb.ui(true) Nenue@5: end Nenue@5: Nenue@5: kb.InitProfile = function(profile, prototype) Nenue@5: if not profile then Nenue@5: profile = {} Nenue@5: end Nenue@5: if prototype then Nenue@5: print('appplying prototype', prototype) Nenue@5: for k,v in pairs(prototype) do Nenue@5: if not profile[k] then Nenue@5: profile[k] = v Nenue@5: end Nenue@5: end Nenue@0: end Nenue@0: Nenue@5: profile.bound = profile.bound or {} Nenue@5: profile.buttons = profile.buttons or {} Nenue@5: profile.commands = profile.commands or {} Nenue@5: profile.bindings = profile.bindings or {} Nenue@5: profile.macros = profile.macros or {} Nenue@5: profile.talents = profile.talents or {} Nenue@5: return profile Nenue@5: end Nenue@5: Nenue@5: kb.ResetProfile = function(profile, prototype) Nenue@6: if profile == kb.currentProfile then Nenue@50: for i, button in pairs(kb.buttons) do Nenue@5: kb.ReleaseSlot(button) Nenue@5: end Nenue@5: end Nenue@50: wipe(profile) Nenue@5: kb.InitProfile(profile, prototype) Nenue@5: end Nenue@5: Nenue@5: Nenue@5: Nenue@5: --- Handles constructing spec profiles as they are selected Nenue@5: Nenue@5: Nenue@5: --- Obtains profile data or creates the necessary tables Nenue@5: kb.SelectProfileSet = function(name) Nenue@5: Nenue@6: local defaultMode Nenue@5: --- General info Nenue@5: classHeader, className, classID = UnitClass('player') Nenue@5: print('|cFF00FF00profile:|r', name) Nenue@5: print('|cFF00FF00class:|r', UnitClass('player')) Nenue@5: Nenue@6: defaultMode = BINDING_TYPE_GLOBAL Nenue@17: if db[name] then Nenue@6: defaultMode = BINDING_TYPE_CHARACTER Nenue@17: if db[name][kb.specInfo.id] then Nenue@17: defaultMode = BINDING_TYPE_SPECIALIZATION Nenue@17: end Nenue@5: end Nenue@5: Nenue@17: db[name] = kb.InitProfile(db[name], Nenue@17: { Nenue@17: classHeader = classHeader, Nenue@17: className = className, Nenue@17: classID = classID Nenue@17: }) Nenue@17: db[name][kb.specInfo.id] = kb.InitProfile(db[name][kb.specInfo.id], Nenue@17: { Nenue@17: specID = kb.specInfo.id, Nenue@17: specName = kb.specInfo.name Nenue@17: }) Nenue@5: Nenue@17: kb.loadedProfiles[BINDING_TYPE_GLOBAL] = db Nenue@17: kb.loadedProfiles[BINDING_TYPE_CHARACTER] = db[name] Nenue@17: kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] = db[name][kb.specInfo.id] Nenue@17: kb.orderedProfiles = {db, db[name], db[name][kb.specInfo.id]} Nenue@17: Nenue@15: if (not db.bindMode) or (not kb.configTitle[db.bindMode]) then Nenue@6: print('fixing bad bindMode value, was', db.bindMode) Nenue@6: db.bindMode = defaultMode Nenue@5: end Nenue@5: Nenue@5: Nenue@5: print(BINDING_TYPE_GLOBAL) Nenue@6: kb.configHeaders[BINDING_TYPE_GLOBAL] = kb.configTitle[BINDING_TYPE_GLOBAL] Nenue@6: kb.configHeaders[BINDING_TYPE_CHARACTER] = kb.configTitle[BINDING_TYPE_CHARACTER]:format(UnitName('player', true)) Nenue@10: kb.configHeaders[BINDING_TYPE_SPECIALIZATION] = kb.configTitle[BINDING_TYPE_SPECIALIZATION]:format(kb.specInfo.name) Nenue@5: Nenue@5: Nenue@6: setmetatable(kb.loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return kb.configHeaders[BINDING_TYPE_GLOBAL] end}) Nenue@6: setmetatable(kb.loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return kb.configHeaders[BINDING_TYPE_CHARACTER] end}) Nenue@6: setmetatable(kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return kb.configHeaders[BINDING_TYPE_SPECIALIZATION] end}) Nenue@5: Nenue@6: print('|cFF00FF00bindMode:|r', db.bindMode) Nenue@6: kb.currentProfile = kb.loadedProfiles[db.bindMode] Nenue@17: kb.currentHeader = kb.configHeaders[db.bindMode] Nenue@5: end Nenue@5: Nenue@5: local scrollCache = {} Nenue@5: kb.SelectTab = function(self) Nenue@6: scrollCache[db.bindMode] = kb.scrollOffset Nenue@5: db.bindMode = self:GetID() Nenue@6: kb.currentProfile = kb.loadedProfiles[self:GetID()] Nenue@17: kb.currentHeader = kb.configHeaders[db.bindMode] Nenue@6: kb.scrollOffset = scrollCache[db.bindMode] or 0 Nenue@5: kb.ui(true) Nenue@5: end Nenue@5: Nenue@5: kb.RevertBindings = function() Nenue@5: -- todo: reversion code Nenue@5: end Nenue@5: Nenue@5: kb.ConfirmBindings = function() Nenue@5: kb.ApplyAllBindings() Nenue@5: kb.ui() Nenue@5: end Nenue@5: Nenue@5: --- post ADDON_LOADED Nenue@5: kb.variables = function() Nenue@50: _G.SkeletonKeyDB = kb.InitProfile(_G.SkeletonKeyDB, {}) Nenue@50: kb.db = _G.SkeletonKeyDB Nenue@5: kb.playerName = UnitName('player') Nenue@5: kb.playerRealm = SelectedRealmName() Nenue@5: kb.profileName = kb.playerRealm .. '_' .. kb.playerName Nenue@5: db = kb.db Nenue@5: Nenue@17: kb.UpdateSpecInfo() Nenue@17: kb.UpdateTalentInfo() Nenue@5: kb.SelectProfileSet(kb.profileName) Nenue@6: -- todo: redo import checking Nenue@6: Nenue@15: kb.UpdateSystemBinds() Nenue@5: kb.ApplyAllBindings() Nenue@5: Nenue@27: if not InCombatLockdown() then Nenue@27: kb.CreateHooks() Nenue@27: else Nenue@27: kb:print('Some functionality will not load until breaking combat.') Nenue@27: tinsert(kb.pendingCalls, kb.CreateHooks) Nenue@27: end Nenue@27: Nenue@27: Nenue@5: kb.ui(true) Nenue@0: end Nenue@0: Nenue@1: Nenue@5: kb.wrap = function(module) Nenue@5: kb.modules = kb.modules or {} Nenue@5: tinsert(kb.modules, module) Nenue@0: end Nenue@0: Nenue@5: -- Volatiles Access Nenue@50: kb.FormatActionID = function(actionType, actionID) return tostring(actionType) .. '_' .. tostring(actionID) end Nenue@17: kb.GetBindings = function() return kb.bindings end Nenue@17: kb.GetButtons = function() return kb.buttons end Nenue@6: kb.GetCharacterProfile = function () return kb.loadedProfiles[BINDING_TYPE_CHARACTER] end Nenue@6: kb.GetGlobalProfile = function () return kb.loadedProfiles[BINDING_TYPE_GLOBAL] end Nenue@6: kb.GetSpecProfile = function () return kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] end Nenue@0: Nenue@0: Nenue@5: SLASH_SKB1 = "/skb" Nenue@5: SLASH_SKB2 = "/skeletonkey" Nenue@5: SlashCmdList.SKB = kb.Command