Nenue@70: -------------------------------------------- Nenue@70: -- SkeletonKey Nenue@70: -- Krakyn-Mal'Ganis Nenue@70: -- @project-revision@ @project-hash@ Nenue@70: -- @file-revision@ @file-hash@ Nenue@70: -- Created: 6/16/2016 3:47 AM Nenue@70: -------------------------------------------- Nenue@70: -- Header script Nenue@70: Nenue@70: local addonName, kb = ... Nenue@72: local print = DEVIAN_WORKSPACE and function(...) _G.print('SK',...) end or nop Nenue@72: local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg',...) end or nop Nenue@70: SkeletonKeyMixin = { Nenue@70: scrollCache = {}, Nenue@70: tabButtons = {}, Nenue@70: keyButtons = {}, Nenue@70: panelButtons = {}, Nenue@70: numTabs = 0, Nenue@70: } Nenue@70: kb.L = setmetatable({}, { Nenue@70: __call = function(t, k, ...) return format(t[k] or k, ...) end Nenue@70: }) Nenue@70: local L = kb.L Nenue@70: Nenue@70: --- Caps Lock literals Nenue@70: L.UNSELECTED_TALENT_ASSIGNED = '|cFF00FF00%s|r added for |cFFFFFF00%s|r (%s).' Nenue@70: L.BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).' Nenue@70: L.BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.' Nenue@70: L.BINDING_FAILED_PROTECTED = '|cFFFF4400Cannot use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400). Uncheck "Safety" to ignore this restraint.|r' Nenue@70: Nenue@70: Nenue@70: local BINDING_TYPE_SPECIALIZATION = 3 Nenue@70: local BINDING_TYPE_CHARACTER = 2 Nenue@70: local BINDING_TYPE_GLOBAL = 1 Nenue@70: kb.configTitle = { Nenue@70: [BINDING_TYPE_GLOBAL] = L('Global Binds'), Nenue@70: [BINDING_TYPE_CHARACTER] = L('%%s'), Nenue@70: [BINDING_TYPE_SPECIALIZATION] = L('%%s') Nenue@70: } Nenue@70: kb.configDescription = { Nenue@70: [BINDING_TYPE_GLOBAL] = L('The bindings are applied globally.'), Nenue@70: [BINDING_TYPE_CHARACTER] = L('Applied when you log onto this character.'), Nenue@70: [BINDING_TYPE_SPECIALIZATION] = L('Applied when you select this specialization.'), Nenue@70: } Nenue@70: Nenue@70: Nenue@70: kb.ChangedBindings = {} Nenue@70: kb.SystemBindings = {} Nenue@70: kb.ActionTypes = {} Nenue@78: -- pet spell categories Nenue@70: kb.PetCache = { Nenue@70: spell = {}, Nenue@70: spellslot = {}, Nenue@70: action = {}, Nenue@70: special = {}, Nenue@70: subtext = {} Nenue@70: } Nenue@78: -- general spell resolution Nenue@70: kb.DynamicSpells = { Nenue@70: profession = {}, Nenue@70: petaction = {}, Nenue@70: talent = {}, Nenue@70: } Nenue@78: Nenue@70: kb.ProfessionCache = {} Nenue@78: Nenue@78: kb.specInfo = {} Nenue@70: kb.pendingCalls = {} Nenue@70: kb.pendingAttributes = {} Nenue@70: kb.configHeaders = {} Nenue@70: kb.loadedProfiles = {} Nenue@70: kb.orderedProfiles = {} Nenue@70: kb.buttons = {} Nenue@70: kb.macros = {} Nenue@72: kb.bindings = setmetatable({}, {__newindex = function(t,k,v) rawset(t,k,v) cprint('adding', k, v) end}) Nenue@70: kb.petFrames = {} -- pet data is slightly delayed, their buttons are indexed here so they can be refreshed Nenue@70: kb.talentFrames = {} Nenue@70: kb.professionFrames = {} Nenue@70: Nenue@70: -- these are sent to plugin Nenue@70: Nenue@70: Nenue@70: local db Nenue@70: local _G = _G Nenue@70: local UnitName, SelectedRealmName, InCombatLockdown, UnitClass = UnitName, SelectedRealmName, InCombatLockdown, UnitClass Nenue@70: local tostring, select, tinsert, pairs = tostring, select, tinsert, pairs Nenue@70: local concat, wipe = table.concat, table.wipe Nenue@70: local classHeader, className, classID = '', '', 0 Nenue@70: Nenue@70: Nenue@70: local CloseButton_OnClick = function() Nenue@70: Nenue@70: kb.db.showUI = false Nenue@70: print(kb.db.showUI) Nenue@70: SkeletonKey:SetShown(kb.db.showUI) Nenue@70: end Nenue@70: Nenue@70: --- Returns conflicting assignment and binding profiles for use in displaying confirmations Nenue@70: kb.IsCommandBound = function(self, command) Nenue@70: local isAssigned, assignedBy = false, kb.db.bindMode Nenue@70: local isBound, boundBy = false, kb.db.bindMode Nenue@70: command = command or self.command Nenue@70: for i = 1, #kb.orderedProfiles do Nenue@70: local profile = kb.orderedProfiles[i] Nenue@70: if i ~= kb.db.bindMode then Nenue@70: Nenue@70: if profile.commands[command] then Nenue@70: print(' command: ', i , kb.configHeaders[i], profile.commands[command]) Nenue@70: isAssigned = true Nenue@70: assignedBy = i Nenue@70: end Nenue@70: if profile.bound[command] then Nenue@70: print(' bound: ', i , kb.configHeaders[i], profile.bound[command]) Nenue@70: isBound = true Nenue@70: boundBy = i Nenue@70: end Nenue@70: Nenue@70: Nenue@70: Nenue@70: Nenue@70: if isAssigned and isBound then Nenue@70: print(' hit: ', i , kb.configHeaders[i], profile.commands[command], profile.bound[command]) Nenue@70: break Nenue@70: end Nenue@70: end Nenue@70: Nenue@70: end Nenue@70: Nenue@70: print('|cFFFFFF00IsCommandBound:|r', command,'|r [profile:', kb.db.bindMode .. ']', isAssigned, isBound, assignedBy, boundBy) Nenue@70: return isAssigned, isBound, assignedBy, boundBy Nenue@70: end Nenue@70: Nenue@70: local talentSpellHardCodes = { Nenue@70: [109248] = 'Binding Shot', Nenue@70: } Nenue@70: Nenue@70: --- Returns a value for use with Texture:SetDesaturated() Nenue@70: kb.BindingIsLocked = function(key) Nenue@70: local success = false Nenue@70: for i = 1, db.bindMode-1 do Nenue@70: local tier = kb.orderedProfiles[i] Nenue@70: if tier.bindings[key] then Nenue@70: success = true Nenue@70: break Nenue@70: end Nenue@70: end Nenue@70: return success Nenue@70: end Nenue@70: Nenue@70: --- Translates GetBindingKey() results into a printable string. Nenue@70: kb.BindingString = function(...) Nenue@70: local stack = {} Nenue@70: for i = 1, select('#', ...) do Nenue@70: local key = select(i, ...) Nenue@70: if type(key) == 'string' then Nenue@74: stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ') Nenue@74: end Nenue@70: end Nenue@70: Nenue@70: if #stack >= 1 then Nenue@70: return concat(stack, ',') Nenue@70: else Nenue@70: return nil Nenue@70: end Nenue@70: end Nenue@70: Nenue@70: Nenue@70: function kb:print(...) Nenue@70: Nenue@70: local msg = '|cFF0088FFSkeletonKey|r:' Nenue@70: for i = 1, select('#', ...) do Nenue@70: msg = msg .. ' ' .. tostring(select(i, ...)) Nenue@70: end Nenue@70: DEFAULT_CHAT_FRAME:AddMessage(msg) Nenue@70: end Nenue@70: Nenue@70: Nenue@70: kb.Command = function(args, editor) Nenue@70: if args:match("import") then Nenue@70: kb.ImportCommmit(args) Nenue@70: return Nenue@70: elseif args:match("scan") then Nenue@70: kb.ImportScan(args) Nenue@70: SkeletonKey:Update() Nenue@70: return Nenue@70: elseif args:match("load") then Nenue@70: kb:ApplyAllBindings() Nenue@70: return Nenue@70: end Nenue@70: Nenue@70: if db.showUI then Nenue@70: db.showUI = false Nenue@70: else Nenue@70: db.showUI = true Nenue@70: if not InCombatLockdown() then Nenue@70: kb:print(L('Config frame opened.')) Nenue@70: else Nenue@70: kb:print(L('Config frame will open upon exiting combat.')) Nenue@70: end Nenue@70: end Nenue@70: SkeletonKey:SetShown(db.showUI) Nenue@70: SkeletonKey:Update(true) Nenue@70: end Nenue@70: Nenue@70: kb.InitProfile = function(profile, prototype) Nenue@70: print('|cFF00FFFFkb.InitProfile()', profile, prototype) Nenue@70: if not profile then Nenue@70: profile = {} Nenue@70: end Nenue@70: if prototype then Nenue@70: for k,v in pairs(prototype) do Nenue@70: if not profile[k] then Nenue@70: profile[k] = v Nenue@70: end Nenue@70: end Nenue@70: end Nenue@70: Nenue@70: profile.bound = profile.bound or {} Nenue@70: profile.buttons = profile.buttons or {} Nenue@70: profile.commands = profile.commands or {} Nenue@70: profile.bindings = profile.bindings or {} Nenue@70: profile.macros = profile.macros or {} Nenue@70: profile.talents = profile.talents or {} Nenue@70: return profile Nenue@70: end Nenue@70: Nenue@70: kb.ResetProfile = function(profile, prototype) Nenue@70: if profile == kb.currentProfile then Nenue@70: for i, button in pairs(kb.buttons) do Nenue@70: kb.ReleaseSlot(button) Nenue@70: end Nenue@70: end Nenue@70: wipe(profile) Nenue@70: kb.InitProfile(profile, prototype) Nenue@70: end Nenue@70: Nenue@70: Nenue@70: Nenue@70: --- Handles constructing spec profiles as they are selected Nenue@70: Nenue@70: Nenue@70: --- Obtains profile data or creates the necessary tables Nenue@70: kb.SelectProfileSet = function(name) Nenue@70: Nenue@70: local defaultMode Nenue@70: --- General info Nenue@70: classHeader, className, classID = UnitClass('player') Nenue@74: --kb:print('|cFF00FF00profile:|r', name) Nenue@74: --kb:print('|cFF00FF00class:|r', UnitClass('player')) Nenue@70: Nenue@70: defaultMode = BINDING_TYPE_GLOBAL Nenue@70: if db[name] then Nenue@70: defaultMode = BINDING_TYPE_CHARACTER Nenue@70: if db[name][kb.specInfo.id] then Nenue@70: defaultMode = BINDING_TYPE_SPECIALIZATION Nenue@70: end Nenue@70: end Nenue@70: Nenue@70: db[name] = kb.InitProfile(db[name], Nenue@70: { Nenue@70: classHeader = classHeader, Nenue@70: className = className, Nenue@70: classID = classID Nenue@70: }) Nenue@70: db[name][kb.specInfo.id] = kb.InitProfile(db[name][kb.specInfo.id], Nenue@70: { Nenue@70: specID = kb.specInfo.id, Nenue@70: specName = kb.specInfo.name Nenue@70: }) Nenue@70: Nenue@70: kb.loadedProfiles[BINDING_TYPE_GLOBAL] = db Nenue@70: kb.loadedProfiles[BINDING_TYPE_CHARACTER] = db[name] Nenue@70: kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] = db[name][kb.specInfo.id] Nenue@70: kb.orderedProfiles = {db, db[name], db[name][kb.specInfo.id]} Nenue@70: Nenue@70: if (not db.bindMode) or (not kb.configTitle[db.bindMode]) then Nenue@70: print('fixing bad bindMode value, was', db.bindMode) Nenue@70: db.bindMode = defaultMode Nenue@70: end Nenue@70: Nenue@70: Nenue@70: print(BINDING_TYPE_GLOBAL) Nenue@70: kb.configHeaders[BINDING_TYPE_GLOBAL] = kb.configTitle[BINDING_TYPE_GLOBAL] Nenue@70: kb.configHeaders[BINDING_TYPE_CHARACTER] = kb.configTitle[BINDING_TYPE_CHARACTER]:format(UnitName('player', true)) Nenue@70: kb.configHeaders[BINDING_TYPE_SPECIALIZATION] = kb.configTitle[BINDING_TYPE_SPECIALIZATION]:format(kb.specInfo.name or '') Nenue@70: Nenue@70: Nenue@70: setmetatable(kb.loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return kb.configHeaders[BINDING_TYPE_GLOBAL] end}) Nenue@70: setmetatable(kb.loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return kb.configHeaders[BINDING_TYPE_CHARACTER] end}) Nenue@70: setmetatable(kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return kb.configHeaders[BINDING_TYPE_SPECIALIZATION] end}) Nenue@70: Nenue@70: kb.currentProfile = kb.loadedProfiles[db.bindMode] Nenue@70: kb.currentHeader = kb.configHeaders[db.bindMode] Nenue@74: Nenue@74: print('|cFF88FF00SelectProfile()|r', kb.profileName, classHeader, kb.specInfo.name) Nenue@70: end Nenue@70: Nenue@70: Nenue@70: function SkeletonKeyMixin:SetTab (id) Nenue@70: self.scrollCache[db.bindMode] = kb.scrollOffset Nenue@70: db.bindMode =id Nenue@70: kb.currentProfile = kb.loadedProfiles[id] Nenue@70: kb.currentHeader = kb.configHeaders[db.bindMode] Nenue@70: kb.scrollOffset = self.scrollCache[db.bindMode] or 0 Nenue@70: self:Update(true) Nenue@70: end Nenue@70: Nenue@70: kb.ConfirmBindings = function() Nenue@70: kb.ApplyAllBindings() Nenue@70: if #kb.pendingAttributes == 0 then Nenue@70: kb:print(L("Manual bindings update finished.")) Nenue@70: else Nenue@70: kb:print(L("Manual update will complete upon exiting combat.")) Nenue@70: end Nenue@70: SkeletonKey:Update() Nenue@70: end Nenue@70: Nenue@70: function SkeletonKeyMixin:OnLoad() Nenue@70: kb.frame = self Nenue@70: print('|cFF0088FF'..self:GetName()..':OnLoad()') Nenue@70: Nenue@70: self.CloseButton:SetScript('OnClick', CloseButton_OnClick) Nenue@86: --self:RegisterEvent('PLAYER_ENTERING_WORLD') Nenue@70: self:RegisterEvent('ADDON_LOADED') Nenue@74: self:RegisterEvent('PLAYER_LOGIN') Nenue@74: self:RegisterUnitEvent('PLAYER_SPECIALIZATION_CHANGED', 'player') Nenue@70: self:EnableKeyboard(false) Nenue@70: Nenue@70: self.zoomScale = self:GetScale() Nenue@70: self.backdrop = self:GetBackdrop() Nenue@70: self.backdropColor = {self:GetBackdropColor() } Nenue@70: self.backdropBorder = {self:GetBackdropBorderColor() } Nenue@70: Nenue@70: end Nenue@70: Nenue@70: function SkeletonKeyMixin:OnEvent(event, arg) Nenue@74: print('|cFFFF0088'.. event..'|r', unit) Nenue@74: if event == 'PLAYER_LOGIN' then Nenue@70: Nenue@70: print('|cFF00FFFF'..event ..'|r', arg or '', IsLoggedIn()) Nenue@74: if not self.initialized then Nenue@70: self:Setup() Nenue@70: self.initialized = true Nenue@74: kb.ApplyAllBindings() Nenue@74: self:Update(true) Nenue@70: end Nenue@70: Nenue@70: Nenue@70: elseif kb[event] then Nenue@70: if self.initialized then Nenue@70: print('|cFF0088FF'..event ..'|r', arg or '') Nenue@70: kb[event](self, event, arg) Nenue@70: else Nenue@70: Nenue@70: print('|cFF004488'..event ..'|r', arg or '') Nenue@70: end Nenue@70: end Nenue@70: end Nenue@70: Nenue@74: function SkeletonKeyMixin:RefreshSpells() Nenue@74: Nenue@74: kb.UpdateTalentInfo() Nenue@74: kb.UpdatePetInfo() Nenue@74: end Nenue@70: Nenue@70: --- post ADDON_LOADED Nenue@70: function SkeletonKeyMixin:Setup () Nenue@70: print('|cFF00FFFF'..self:GetName()..':Setup()') Nenue@70: SkeletonKeyDB = kb.InitProfile(SkeletonKeyDB, {}) Nenue@70: kb.db = _G.SkeletonKeyDB Nenue@70: kb.playerName = UnitName('player') Nenue@70: kb.playerRealm = SelectedRealmName() Nenue@70: kb.profileName = kb.playerRealm .. '_' .. kb.playerName Nenue@70: db = kb.db Nenue@70: Nenue@70: kb.UpdateSpecInfo() Nenue@70: kb.UpdateTalentInfo() Nenue@86: kb.UpdateProfessionInfo() Nenue@70: kb.SelectProfileSet(kb.profileName) Nenue@74: self:SetShown(kb.db.showUI) Nenue@70: -- todo: redo import checking Nenue@70: Nenue@70: kb.UpdateSystemBinds() Nenue@70: Nenue@70: if not InCombatLockdown() then Nenue@70: kb.CreateHooks() Nenue@70: else Nenue@70: kb:print('Some functionality will not load until breaking combat.') Nenue@70: tinsert(kb.pendingCalls, kb.CreateHooks) Nenue@70: end Nenue@70: SLASH_SKB1 = "/skb" Nenue@70: SLASH_SKB2 = "/skeletonkey" Nenue@70: SlashCmdList.SKB = kb.Command Nenue@70: Nenue@70: Nenue@70: self:RegisterEvent('UPDATE_MACROS') Nenue@74: --self:RegisterEvent('UPDATE_BINDINGS') Nenue@70: self:RegisterUnitEvent('UNIT_PORTRAIT_UPDATE', 'player', 'pet') Nenue@70: self:RegisterEvent('PLAYER_REGEN_DISABLED') Nenue@70: self:RegisterEvent('PLAYER_REGEN_ENABLED') Nenue@74: self:RegisterEvent('SPELLS_CHANGED') Nenue@74: self:RegisterEvent('PLAYER_TALENT_UPDATE') Nenue@76: self:RegisterEvent('PLAYER_PVP_TALENT_UPDATE') Nenue@70: Nenue@70: self:RegisterForDrag('LeftButton') Nenue@70: self:SetMovable(true) Nenue@70: for index, frame in ipairs(self.Plugins) do Nenue@70: frame:Setup() Nenue@70: end Nenue@70: end Nenue@70: Nenue@70: Nenue@70: -- Volatiles Access Nenue@70: kb.FormatActionID = function(actionType, actionID) return tostring(actionType) .. '_' .. tostring(actionID) end Nenue@70: kb.GetBindings = function() return kb.bindings end Nenue@70: kb.GetButtons = function() return kb.buttons end Nenue@70: kb.GetCharacterProfile = function () return kb.loadedProfiles[BINDING_TYPE_CHARACTER] end Nenue@70: kb.GetGlobalProfile = function () return kb.loadedProfiles[BINDING_TYPE_GLOBAL] end Nenue@70: kb.GetSpecProfile = function () return kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] end Nenue@70: Nenue@70: