annotate SkeletonKey.lua @ 72:c48913c5924c

- Dynamic bindings properly update between talent choices, and the hotkey text is also correctly reflected.
author Nenue
date Sat, 07 Jan 2017 12:47:41 -0500
parents 131d9190db6b
children 9824d524a661
rev   line source
Nenue@70 1 --------------------------------------------
Nenue@70 2 -- SkeletonKey
Nenue@70 3 -- Krakyn-Mal'Ganis
Nenue@70 4 -- @project-revision@ @project-hash@
Nenue@70 5 -- @file-revision@ @file-hash@
Nenue@70 6 -- Created: 6/16/2016 3:47 AM
Nenue@70 7 --------------------------------------------
Nenue@70 8 -- Header script
Nenue@70 9
Nenue@70 10 local addonName, kb = ...
Nenue@72 11 local print = DEVIAN_WORKSPACE and function(...) _G.print('SK',...) end or nop
Nenue@72 12 local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg',...) end or nop
Nenue@70 13 SkeletonKeyMixin = {
Nenue@70 14 scrollCache = {},
Nenue@70 15 tabButtons = {},
Nenue@70 16 keyButtons = {},
Nenue@70 17 panelButtons = {},
Nenue@70 18 numTabs = 0,
Nenue@70 19 }
Nenue@70 20 kb.L = setmetatable({}, {
Nenue@70 21 __call = function(t, k, ...) return format(t[k] or k, ...) end
Nenue@70 22 })
Nenue@70 23 local L = kb.L
Nenue@70 24
Nenue@70 25 --- Caps Lock literals
Nenue@70 26 L.UNSELECTED_TALENT_ASSIGNED = '|cFF00FF00%s|r added for |cFFFFFF00%s|r (%s).'
Nenue@70 27 L.BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).'
Nenue@70 28 L.BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.'
Nenue@70 29 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 30
Nenue@70 31
Nenue@70 32 local BINDING_TYPE_SPECIALIZATION = 3
Nenue@70 33 local BINDING_TYPE_CHARACTER = 2
Nenue@70 34 local BINDING_TYPE_GLOBAL = 1
Nenue@70 35 kb.configTitle = {
Nenue@70 36 [BINDING_TYPE_GLOBAL] = L('Global Binds'),
Nenue@70 37 [BINDING_TYPE_CHARACTER] = L('%%s'),
Nenue@70 38 [BINDING_TYPE_SPECIALIZATION] = L('%%s')
Nenue@70 39 }
Nenue@70 40 kb.configDescription = {
Nenue@70 41 [BINDING_TYPE_GLOBAL] = L('The bindings are applied globally.'),
Nenue@70 42 [BINDING_TYPE_CHARACTER] = L('Applied when you log onto this character.'),
Nenue@70 43 [BINDING_TYPE_SPECIALIZATION] = L('Applied when you select this specialization.'),
Nenue@70 44 }
Nenue@70 45
Nenue@70 46
Nenue@70 47 kb.ChangedBindings = {}
Nenue@70 48 kb.SystemBindings = {}
Nenue@70 49 kb.ActionTypes = {}
Nenue@70 50 kb.TalentBindings = {}
Nenue@70 51 kb.PetCache = {
Nenue@70 52 spell = {},
Nenue@70 53 spellslot = {},
Nenue@70 54 action = {},
Nenue@70 55 special = {},
Nenue@70 56 subtext = {}
Nenue@70 57 }
Nenue@70 58 kb.DynamicSpells = {
Nenue@70 59 profession = {},
Nenue@70 60 petaction = {},
Nenue@70 61 talent = {},
Nenue@70 62 }
Nenue@70 63 kb.TalentCache = {}
Nenue@70 64 kb.ProfessionCache = {}
Nenue@70 65 kb.pendingCalls = {}
Nenue@70 66 kb.pendingAttributes = {}
Nenue@70 67
Nenue@70 68 kb.configHeaders = {}
Nenue@70 69 kb.loadedProfiles = {}
Nenue@70 70 kb.orderedProfiles = {}
Nenue@70 71 kb.buttons = {}
Nenue@70 72 kb.macros = {}
Nenue@72 73 kb.bindings = setmetatable({}, {__newindex = function(t,k,v) rawset(t,k,v) cprint('adding', k, v) end})
Nenue@70 74 kb.petFrames = {} -- pet data is slightly delayed, their buttons are indexed here so they can be refreshed
Nenue@70 75 kb.talentFrames = {}
Nenue@70 76 kb.professionFrames = {}
Nenue@70 77
Nenue@70 78 -- these are sent to plugin
Nenue@70 79
Nenue@70 80
Nenue@70 81 local db
Nenue@70 82 local _G = _G
Nenue@70 83 local UnitName, SelectedRealmName, InCombatLockdown, UnitClass = UnitName, SelectedRealmName, InCombatLockdown, UnitClass
Nenue@70 84 local tostring, select, tinsert, pairs = tostring, select, tinsert, pairs
Nenue@70 85 local concat, wipe = table.concat, table.wipe
Nenue@70 86 local classHeader, className, classID = '', '', 0
Nenue@70 87
Nenue@70 88
Nenue@70 89 local CloseButton_OnClick = function()
Nenue@70 90
Nenue@70 91 kb.db.showUI = false
Nenue@70 92 print(kb.db.showUI)
Nenue@70 93 SkeletonKey:SetShown(kb.db.showUI)
Nenue@70 94 end
Nenue@70 95
Nenue@70 96 --- Returns conflicting assignment and binding profiles for use in displaying confirmations
Nenue@70 97 kb.IsCommandBound = function(self, command)
Nenue@70 98 local isAssigned, assignedBy = false, kb.db.bindMode
Nenue@70 99 local isBound, boundBy = false, kb.db.bindMode
Nenue@70 100 command = command or self.command
Nenue@70 101 for i = 1, #kb.orderedProfiles do
Nenue@70 102 local profile = kb.orderedProfiles[i]
Nenue@70 103 if i ~= kb.db.bindMode then
Nenue@70 104
Nenue@70 105 if profile.commands[command] then
Nenue@70 106 print(' command: ', i , kb.configHeaders[i], profile.commands[command])
Nenue@70 107 isAssigned = true
Nenue@70 108 assignedBy = i
Nenue@70 109 end
Nenue@70 110 if profile.bound[command] then
Nenue@70 111 print(' bound: ', i , kb.configHeaders[i], profile.bound[command])
Nenue@70 112 isBound = true
Nenue@70 113 boundBy = i
Nenue@70 114 end
Nenue@70 115
Nenue@70 116
Nenue@70 117
Nenue@70 118
Nenue@70 119 if isAssigned and isBound then
Nenue@70 120 print(' hit: ', i , kb.configHeaders[i], profile.commands[command], profile.bound[command])
Nenue@70 121 break
Nenue@70 122 end
Nenue@70 123 end
Nenue@70 124
Nenue@70 125 end
Nenue@70 126
Nenue@70 127 print('|cFFFFFF00IsCommandBound:|r', command,'|r [profile:', kb.db.bindMode .. ']', isAssigned, isBound, assignedBy, boundBy)
Nenue@70 128 return isAssigned, isBound, assignedBy, boundBy
Nenue@70 129 end
Nenue@70 130
Nenue@70 131 local talentSpellHardCodes = {
Nenue@70 132 [109248] = 'Binding Shot',
Nenue@70 133 }
Nenue@70 134
Nenue@70 135 --- Returns a value for use with Texture:SetDesaturated()
Nenue@70 136 kb.BindingIsLocked = function(key)
Nenue@70 137 local success = false
Nenue@70 138 for i = 1, db.bindMode-1 do
Nenue@70 139 local tier = kb.orderedProfiles[i]
Nenue@70 140 if tier.bindings[key] then
Nenue@70 141 success = true
Nenue@70 142 break
Nenue@70 143 end
Nenue@70 144 end
Nenue@70 145 return success
Nenue@70 146 end
Nenue@70 147
Nenue@70 148 --- Translates GetBindingKey() results into a printable string.
Nenue@70 149 kb.BindingString = function(...)
Nenue@70 150 local stack = {}
Nenue@70 151 for i = 1, select('#', ...) do
Nenue@70 152 local key = select(i, ...)
Nenue@70 153 if type(key) == 'string' then
Nenue@70 154 stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ')
Nenue@70 155 end
Nenue@70 156 end
Nenue@70 157
Nenue@70 158 if #stack >= 1 then
Nenue@70 159 return concat(stack, ',')
Nenue@70 160 else
Nenue@70 161 return nil
Nenue@70 162 end
Nenue@70 163 end
Nenue@70 164
Nenue@70 165
Nenue@70 166 function kb:print(...)
Nenue@70 167
Nenue@70 168 local msg = '|cFF0088FFSkeletonKey|r:'
Nenue@70 169 for i = 1, select('#', ...) do
Nenue@70 170 msg = msg .. ' ' .. tostring(select(i, ...))
Nenue@70 171 end
Nenue@70 172 DEFAULT_CHAT_FRAME:AddMessage(msg)
Nenue@70 173 end
Nenue@70 174
Nenue@70 175
Nenue@70 176 kb.Command = function(args, editor)
Nenue@70 177 if args:match("import") then
Nenue@70 178 kb.ImportCommmit(args)
Nenue@70 179 return
Nenue@70 180 elseif args:match("scan") then
Nenue@70 181 kb.ImportScan(args)
Nenue@70 182 SkeletonKey:Update()
Nenue@70 183 return
Nenue@70 184 elseif args:match("load") then
Nenue@70 185 kb:ApplyAllBindings()
Nenue@70 186 return
Nenue@70 187 end
Nenue@70 188
Nenue@70 189 if db.showUI then
Nenue@70 190 db.showUI = false
Nenue@70 191 else
Nenue@70 192 db.showUI = true
Nenue@70 193 if not InCombatLockdown() then
Nenue@70 194 kb:print(L('Config frame opened.'))
Nenue@70 195 else
Nenue@70 196 kb:print(L('Config frame will open upon exiting combat.'))
Nenue@70 197 end
Nenue@70 198 end
Nenue@70 199 SkeletonKey:SetShown(db.showUI)
Nenue@70 200 SkeletonKey:Update(true)
Nenue@70 201 end
Nenue@70 202
Nenue@70 203 kb.InitProfile = function(profile, prototype)
Nenue@70 204 print('|cFF00FFFFkb.InitProfile()', profile, prototype)
Nenue@70 205 if not profile then
Nenue@70 206 profile = {}
Nenue@70 207 end
Nenue@70 208 if prototype then
Nenue@70 209 for k,v in pairs(prototype) do
Nenue@70 210 if not profile[k] then
Nenue@70 211 profile[k] = v
Nenue@70 212 end
Nenue@70 213 end
Nenue@70 214 end
Nenue@70 215
Nenue@70 216 profile.bound = profile.bound or {}
Nenue@70 217 profile.buttons = profile.buttons or {}
Nenue@70 218 profile.commands = profile.commands or {}
Nenue@70 219 profile.bindings = profile.bindings or {}
Nenue@70 220 profile.macros = profile.macros or {}
Nenue@70 221 profile.talents = profile.talents or {}
Nenue@70 222 return profile
Nenue@70 223 end
Nenue@70 224
Nenue@70 225 kb.ResetProfile = function(profile, prototype)
Nenue@70 226 if profile == kb.currentProfile then
Nenue@70 227 for i, button in pairs(kb.buttons) do
Nenue@70 228 kb.ReleaseSlot(button)
Nenue@70 229 end
Nenue@70 230 end
Nenue@70 231 wipe(profile)
Nenue@70 232 kb.InitProfile(profile, prototype)
Nenue@70 233 end
Nenue@70 234
Nenue@70 235
Nenue@70 236
Nenue@70 237 --- Handles constructing spec profiles as they are selected
Nenue@70 238
Nenue@70 239
Nenue@70 240 --- Obtains profile data or creates the necessary tables
Nenue@70 241 kb.SelectProfileSet = function(name)
Nenue@70 242
Nenue@70 243 local defaultMode
Nenue@70 244 --- General info
Nenue@70 245 classHeader, className, classID = UnitClass('player')
Nenue@70 246 print('|cFF00FF00profile:|r', name)
Nenue@70 247 print('|cFF00FF00class:|r', UnitClass('player'))
Nenue@70 248
Nenue@70 249 defaultMode = BINDING_TYPE_GLOBAL
Nenue@70 250 if db[name] then
Nenue@70 251 defaultMode = BINDING_TYPE_CHARACTER
Nenue@70 252 if db[name][kb.specInfo.id] then
Nenue@70 253 defaultMode = BINDING_TYPE_SPECIALIZATION
Nenue@70 254 end
Nenue@70 255 end
Nenue@70 256
Nenue@70 257 db[name] = kb.InitProfile(db[name],
Nenue@70 258 {
Nenue@70 259 classHeader = classHeader,
Nenue@70 260 className = className,
Nenue@70 261 classID = classID
Nenue@70 262 })
Nenue@70 263 db[name][kb.specInfo.id] = kb.InitProfile(db[name][kb.specInfo.id],
Nenue@70 264 {
Nenue@70 265 specID = kb.specInfo.id,
Nenue@70 266 specName = kb.specInfo.name
Nenue@70 267 })
Nenue@70 268
Nenue@70 269 kb.loadedProfiles[BINDING_TYPE_GLOBAL] = db
Nenue@70 270 kb.loadedProfiles[BINDING_TYPE_CHARACTER] = db[name]
Nenue@70 271 kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] = db[name][kb.specInfo.id]
Nenue@70 272 kb.orderedProfiles = {db, db[name], db[name][kb.specInfo.id]}
Nenue@70 273
Nenue@70 274 if (not db.bindMode) or (not kb.configTitle[db.bindMode]) then
Nenue@70 275 print('fixing bad bindMode value, was', db.bindMode)
Nenue@70 276 db.bindMode = defaultMode
Nenue@70 277 end
Nenue@70 278
Nenue@70 279
Nenue@70 280 print(BINDING_TYPE_GLOBAL)
Nenue@70 281 kb.configHeaders[BINDING_TYPE_GLOBAL] = kb.configTitle[BINDING_TYPE_GLOBAL]
Nenue@70 282 kb.configHeaders[BINDING_TYPE_CHARACTER] = kb.configTitle[BINDING_TYPE_CHARACTER]:format(UnitName('player', true))
Nenue@70 283 kb.configHeaders[BINDING_TYPE_SPECIALIZATION] = kb.configTitle[BINDING_TYPE_SPECIALIZATION]:format(kb.specInfo.name or '')
Nenue@70 284
Nenue@70 285
Nenue@70 286 setmetatable(kb.loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return kb.configHeaders[BINDING_TYPE_GLOBAL] end})
Nenue@70 287 setmetatable(kb.loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return kb.configHeaders[BINDING_TYPE_CHARACTER] end})
Nenue@70 288 setmetatable(kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return kb.configHeaders[BINDING_TYPE_SPECIALIZATION] end})
Nenue@70 289
Nenue@70 290 print('|cFF00FF00bindMode:|r', db.bindMode)
Nenue@70 291 kb.currentProfile = kb.loadedProfiles[db.bindMode]
Nenue@70 292 kb.currentHeader = kb.configHeaders[db.bindMode]
Nenue@70 293 end
Nenue@70 294
Nenue@70 295
Nenue@70 296 function SkeletonKeyMixin:SetTab (id)
Nenue@70 297 self.scrollCache[db.bindMode] = kb.scrollOffset
Nenue@70 298 db.bindMode =id
Nenue@70 299 kb.currentProfile = kb.loadedProfiles[id]
Nenue@70 300 kb.currentHeader = kb.configHeaders[db.bindMode]
Nenue@70 301 kb.scrollOffset = self.scrollCache[db.bindMode] or 0
Nenue@70 302 self:Update(true)
Nenue@70 303 end
Nenue@70 304
Nenue@70 305 kb.ConfirmBindings = function()
Nenue@70 306 kb.ApplyAllBindings()
Nenue@70 307 if #kb.pendingAttributes == 0 then
Nenue@70 308 kb:print(L("Manual bindings update finished."))
Nenue@70 309 else
Nenue@70 310 kb:print(L("Manual update will complete upon exiting combat."))
Nenue@70 311 end
Nenue@70 312 SkeletonKey:Update()
Nenue@70 313 end
Nenue@70 314
Nenue@70 315 function SkeletonKeyMixin:OnLoad()
Nenue@70 316 kb.frame = self
Nenue@70 317 print('|cFF0088FF'..self:GetName()..':OnLoad()')
Nenue@70 318
Nenue@70 319 self.CloseButton:SetScript('OnClick', CloseButton_OnClick)
Nenue@72 320 self:RegisterEvent('PLAYER_LOGIN')
Nenue@70 321 self:RegisterEvent('PLAYER_ENTERING_WORLD')
Nenue@70 322 self:RegisterEvent('ADDON_LOADED')
Nenue@70 323 self:EnableKeyboard(false)
Nenue@70 324
Nenue@70 325 self.zoomScale = self:GetScale()
Nenue@70 326 self.backdrop = self:GetBackdrop()
Nenue@70 327 self.backdropColor = {self:GetBackdropColor() }
Nenue@70 328 self.backdropBorder = {self:GetBackdropBorderColor() }
Nenue@70 329
Nenue@70 330 end
Nenue@70 331
Nenue@70 332 function SkeletonKeyMixin:OnEvent(event, arg)
Nenue@70 333 if event == 'ADDON_LOADED' then
Nenue@70 334
Nenue@70 335 print('|cFF00FFFF'..event ..'|r', arg or '', IsLoggedIn())
Nenue@70 336 if IsLoggedIn() and not self.initialized then
Nenue@70 337 self:Setup()
Nenue@70 338 self.initialized = true
Nenue@70 339 self:Update()
Nenue@70 340 end
Nenue@70 341
Nenue@70 342
Nenue@70 343 elseif kb[event] then
Nenue@70 344 if self.initialized then
Nenue@70 345 print('|cFF0088FF'..event ..'|r', arg or '')
Nenue@70 346 kb[event](self, event, arg)
Nenue@70 347 else
Nenue@70 348
Nenue@70 349 print('|cFF004488'..event ..'|r', arg or '')
Nenue@70 350 end
Nenue@70 351 end
Nenue@70 352 end
Nenue@70 353
Nenue@70 354
Nenue@70 355 --- post ADDON_LOADED
Nenue@70 356 function SkeletonKeyMixin:Setup ()
Nenue@70 357 print('|cFF00FFFF'..self:GetName()..':Setup()')
Nenue@70 358 SkeletonKeyDB = kb.InitProfile(SkeletonKeyDB, {})
Nenue@70 359 kb.db = _G.SkeletonKeyDB
Nenue@70 360 kb.playerName = UnitName('player')
Nenue@70 361 kb.playerRealm = SelectedRealmName()
Nenue@70 362 kb.profileName = kb.playerRealm .. '_' .. kb.playerName
Nenue@70 363 db = kb.db
Nenue@70 364
Nenue@70 365 kb.UpdateSpecInfo()
Nenue@70 366 kb.UpdateTalentInfo()
Nenue@70 367 kb.SelectProfileSet(kb.profileName)
Nenue@70 368 -- todo: redo import checking
Nenue@70 369
Nenue@70 370 kb.UpdateSystemBinds()
Nenue@70 371 kb.ApplyAllBindings()
Nenue@70 372
Nenue@70 373 if not InCombatLockdown() then
Nenue@70 374 kb.CreateHooks()
Nenue@70 375 else
Nenue@70 376 kb:print('Some functionality will not load until breaking combat.')
Nenue@70 377 tinsert(kb.pendingCalls, kb.CreateHooks)
Nenue@70 378 end
Nenue@70 379 SLASH_SKB1 = "/skb"
Nenue@70 380 SLASH_SKB2 = "/skeletonkey"
Nenue@70 381 SlashCmdList.SKB = kb.Command
Nenue@70 382
Nenue@70 383 self:SetShown(kb.db.showUI)
Nenue@70 384 self:Update(true)
Nenue@70 385
Nenue@70 386 self:RegisterEvent('UPDATE_MACROS')
Nenue@70 387 self:RegisterEvent('UPDATE_BINDINGS')
Nenue@70 388 self:RegisterUnitEvent('UNIT_PORTRAIT_UPDATE', 'player', 'pet')
Nenue@70 389 self:RegisterUnitEvent('PLAYER_SPECIALIZATION_CHANGED', 'player', 'pet')
Nenue@70 390 self:RegisterUnitEvent('SPELLS_CHANGED')
Nenue@70 391 self:RegisterUnitEvent('TALENT_UPDATE', 'player', 'pet')
Nenue@70 392 self:RegisterEvent('PLAYER_REGEN_DISABLED')
Nenue@70 393 self:RegisterEvent('PLAYER_REGEN_ENABLED')
Nenue@70 394
Nenue@70 395 self:RegisterForDrag('LeftButton')
Nenue@70 396 self:SetMovable(true)
Nenue@70 397 for index, frame in ipairs(self.Plugins) do
Nenue@70 398 frame:Setup()
Nenue@70 399 end
Nenue@70 400 end
Nenue@70 401
Nenue@70 402
Nenue@70 403 -- Volatiles Access
Nenue@70 404 kb.FormatActionID = function(actionType, actionID) return tostring(actionType) .. '_' .. tostring(actionID) end
Nenue@70 405 kb.GetBindings = function() return kb.bindings end
Nenue@70 406 kb.GetButtons = function() return kb.buttons end
Nenue@70 407 kb.GetCharacterProfile = function () return kb.loadedProfiles[BINDING_TYPE_CHARACTER] end
Nenue@70 408 kb.GetGlobalProfile = function () return kb.loadedProfiles[BINDING_TYPE_GLOBAL] end
Nenue@70 409 kb.GetSpecProfile = function () return kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] end
Nenue@70 410
Nenue@70 411