comparison SkeletonKey/SkeletonKey.lua @ 14:82170735e67c

- move co-routine handler to general lib - slightly better handling of pet actions
author Nenue
date Thu, 28 Jul 2016 23:58:53 -0400
parents SkeletonKey/KeyBinds.lua@eeec4a600064
children 32d64e42ec9b
comparison
equal deleted inserted replaced
13:eeec4a600064 14:82170735e67c
1 --------------------------------------------
2 -- SkeletonKey
3 -- Krakyn-Mal'Ganis
4 -- @project-revision@ @project-hash@
5 -- @file-revision@ @file-hash@
6 -- Created: 6/16/2016 3:47 AM
7 --------------------------------------------
8 -- kb
9 -- .StoreBinding(button, key) bind current keystroke to command
10 -- .GetSlot(index) return display slot
11 -- .SetSlot(button, command, name, icon) assign display slot
12 -- .ReleaseSlot(button) clear button command
13 -- .UpdateSlot(button) update button contents
14 -- .SelectProfile(name) set profile character
15 -- .ApplyBindings(bindings) walk table with SetBinding()
16
17 local _
18 local kb, print = LibStub("LibKraken").register(KeyBinder)
19 local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg', ...) end or function() end
20 kb.L = setmetatable({}, {
21 __call = function(t, k, ...) return format(t[k] or '', ...) end
22 })
23 local L = kb.L
24
25 --- Caps Lock literals
26 local CLICK_KEYBINDER_MACRO = "CLICK KeyBinderMacro:"
27 local CLICK_KEYBINDER_KEY = "CLICK KeyBinderKey:"
28 local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES"
29 local FOOTER_OFFSET
30 local HEADER_OFFSET
31 L.BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).'
32 L.BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.'
33 L.BINDING_FAILED_PROTECTED = '|cFFFF4400Unable to use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400)|r'
34
35
36 local BINDING_TYPE_SPECIALIZATION = 3
37 local BINDING_TYPE_CHARACTER = 2
38 local BINDING_TYPE_GLOBAL = 1
39
40
41 --- Caps Lock derivatives
42 local ACTION_SCRIPT = {
43 ['mount'] = "/script C_MountJournal.SummonByID(%d)",
44 ['macro'] = "%s",
45 ['equipset'] = "/script UseEquipmentSet(%d)",
46 ['spell'] = "/cast %s",
47 ['petaction'] = "/cast %s",
48 ['battlepet'] = SLASH_SUMMON_BATTLE_PET1 .. " %s",
49 ['item'] = "/use %s"
50 }
51
52 local professionMappings = {
53 [5] = 3,
54 [7] = 4,
55 [9] = 5,
56 [10] = 6
57 }
58 kb.configTitle = {
59 [BINDING_TYPE_GLOBAL] = 'Global Binds',
60 [BINDING_TYPE_CHARACTER] = 'Character: %s',
61 [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s'
62 }
63 kb.configDescription = {
64 [BINDING_TYPE_GLOBAL] = 'The bindings are applied globally.',
65 [BINDING_TYPE_CHARACTER] = 'Applied when you log onto this character.',
66 [BINDING_TYPE_SPECIALIZATION] = 'Applied when you log onto this character and are that specialization.',
67 }
68
69
70
71 kb.inactiveTalentBindings = {}
72 kb.configHeaders = {}
73 kb.loadedProfiles = {}
74 kb.orderedProfiles = {}
75 kb.buttons = {}
76 kb.macros = {}
77
78 -- these are sent to plugin
79
80 local bindings = {}
81 local macros = {}
82 local talentBindings = {}
83
84 local protected = {
85 ['OPENCHATSLASH'] = true,
86 ['OPENCHAT'] = true,
87 }
88
89
90 local db
91 local bindHeader, currentHeader = '', ''
92 local specID, specGlobalID, specName, specDesc, specTexture, characterHeader = 0, 0, 'SPEC_NAME', 'SPEC_DESCRIPTION', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME'
93 local classHeader, className, classID = '', '', 0
94 local bindsCommitted = true
95 local forceButtonUpdate = false
96
97 --- Control handles
98 local saveButton, restoreButton, clearButton
99
100 --- Returns conflicting assignment and binding profiles for use in displaying confirmations
101 kb.IsCommandBound = function(self, command)
102 local isAssigned, assignedBy = false, db.bindMode
103 local isBound, boundBy = false, db.bindMode
104
105
106 command = command or self.command
107 for i = 1, #kb.orderedProfiles do
108 local tier = kb.orderedProfiles[i]
109 if i ~= db.bindMode then
110
111 if tier.commands[command] then
112 isAssigned = true
113 assignedBy = i
114 end
115 if tier.bound[command] then
116 isBound = true
117 boundBy = i
118 end
119
120
121 --print(' *', configHeaders[i], tier.commands[command], tier.bound[command])
122
123 if isAssigned and isBound then
124 break
125 end
126 end
127
128 end
129
130 print('|cFFFFFF00IsCommandBound:|r', command:gsub(CLICK_KEYBINDER_MACRO, ''),'|r [profile:', db.bindMode .. ']', isAssigned, isBound, assignedBy, boundBy)
131 return isAssigned, isBound, assignedBy, boundBy
132 end
133
134 local talentSpellHardCodes = {
135 [109248] = 'Binding Shot',
136 }
137
138 --- Returns a value for use with Texture:SetDesaturated()
139 kb.BindingIsLocked = function(key)
140 local success = false
141 for i = 1, db.bindMode-1 do
142 local tier = kb.orderedProfiles[i]
143 if tier.bindings[key] then
144 success = true
145 break
146 end
147 end
148 return success
149 end
150
151 --- Translates GetBindingKey() results into a printable string.
152 kb.BindingString = function(...)
153 local stack = {}
154 for i = 1, select('#', ...) do
155 local key = select(i, ...)
156 stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ')
157 end
158
159 if #stack >= 1 then
160 return table.concat(stack, ',')
161 else
162 return nil
163 end
164 end
165
166
167 --- Resolve the appropriate command and macroText for the given action parameters
168 kb.RegisterAction = function(type, id, name)
169 local macroText, macroName, command = '', '', ''
170
171 if type == 'spell' then
172 if kb.ProfessionCache[id] then
173 command = CLICK_KEYBINDER_KEY .. "profession_".. kb.ProfessionCache[id].profOffset .. '_' .. kb.ProfessionCache[id].spellNum
174 else
175 command = CLICK_KEYBINDER_KEY ..name
176 end
177 else
178 macroName = type .. ' ' .. name
179 macroText = ACTION_SCRIPT[type]:format(name)
180 local baseName, iterative = macroName, 1
181 while (macros[macroName] and macros[macroName][1] ~= macroText) do
182 print(' * cannot use|cFF00FF00', macroName, '|r"'.. (macros[macroName][1] or '') .. '"')
183 macroName = baseName .. '_' .. iterative
184 iterative = iterative + 1
185 end
186 if macroName ~= baseName then
187 print(' * Creating|cFF00FF00', macroName)
188 else
189 print(' * Re-using|cFF00FF00', macroName)
190 end
191 command = 'CLICK KeyBinderMacro:'.. macroName
192 macros[macroName] = {macroText, command }
193 end
194
195 print('RegisterAction', type, id, '->', command , macroText)
196 return macroName, macroText, command
197 end
198
199 --- Updates the current KeyBinding for the button's command
200 kb.StoreBinding = function(self, key)
201
202 if not self.command then
203 return
204 end
205
206 if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then
207 return
208 end
209 print('|cFFFFFF00received|cFFFFFF00', self:GetID(), '|cFF00FFFF', key)
210
211 if key == 'ESCAPE' then
212 local keys = {GetBindingKey(self.command) }
213 --print('detected', #keys, 'bindings')
214 for i, key in pairs(keys) do
215 --print('clearing', key)
216 SetBinding(key, nil)
217 SaveBindings(GetCurrentBindingSet())
218 if kb.currentProfile.bindings[key] then
219 kb:print(L('BINDING_REMOVED', self.actionName, kb.configHeaders[db.bindMode]))
220 kb.currentProfile.bindings[key] = nil
221 end
222 if kb.currentProfile.talents[self.actionName] then
223 kb.currentProfile.talents[self.actionName] = nil
224 end
225 bindings[self.actionType][self.actionID] = nil
226 end
227 if kb.currentProfile.bound[self.command] then
228 kb.currentProfile.bound[self.command] = nil
229 --kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[db.bindMode]))
230 end
231
232 bindsCommitted = false
233 self.active = false
234 else
235
236 local modifier = ''
237 if IsAltKeyDown() then
238 modifier = 'ALT-'
239 end
240 if IsControlKeyDown() then
241 modifier = modifier.. 'CTRL-'
242 end
243 if IsShiftKeyDown() then
244 modifier = modifier..'SHIFT-'
245 end
246
247
248 if self.command then
249 self.binding = modifier..key
250
251 local previousKeys
252 local previousAction = GetBindingAction(self.binding)
253 local binding1, binding2, new1, new2
254 print(type(previousAction), previousAction)
255 if previousAction ~= "" and previousAction ~= self.command then
256 if protected[previousAction] then
257 -- bounce out if trying to use a protected key
258 kb.statustext:SetText(L('BINDING_FAILED_PROTECTED', key, GetBindingAction(previousAction)))
259 kb.bindingstext:SetText(nil)
260 return
261 else
262 kb:print('Discarding keybind for', previousAction)
263 -- todo: sort out retcon'd talent spells
264 end
265 end
266
267 self.pending = true
268
269 bindsCommitted = false
270 SetBinding(self.binding, self.command)
271 SaveBindings(GetCurrentBindingSet())
272
273 local talentInfo
274 if self.actionType == 'spell' and kb.TalentCache[self.actionID] then
275 print('conditional binding (talent = "'..self.actionName..'")')
276 talentInfo = {self.macroName, self.actionName, self.actionType, self.actionID}
277 local bindings = {GetBindingKey(self.command) }
278 for i, key in ipairs(bindings) do
279 tinsert(talentInfo, key)
280 end
281 end
282
283 for level, profile in ipairs(kb.orderedProfiles) do
284 if (level == db.bindMode) then
285 profile.bound[self.command] = true
286 if talentInfo then
287 profile.bindings[self.binding] = nil
288 else
289 profile.bindings[self.binding] = self.command
290 end
291 profile.talents[self.actionName] = talentInfo
292 else
293 profile.bindings[self.binding] = nil
294 profile.bound[self.command] = nil
295 kb.currentProfile.talents[self.actionName] = nil
296 end
297 if kb.currentProfile.talents[self.actionID] then
298 kb.currentProfile.talents[self.actionID] = nil
299 end
300 end
301
302 kb:print(L('BINDING_ASSIGNED', self.binding, self.actionName, kb.configHeaders[db.bindMode]))
303 end
304 end
305 kb.UpdateSlot(self, true)
306 KeyBinderSaveButton:Enable()
307 end
308
309
310 kb.inactiveTalentBindings = {}
311 kb.ApplyTalentBinding = function(talentInfo, cache)
312 for i = 5, #talentInfo do
313 local command = CLICK_KEYBINDER_KEY.. talentInfo[2]
314 SetBinding(talentInfo[i], command)
315 cprint(' **', talentInfo[i], '->', command)
316 tinsert(cache, talentInfo[i])
317 end
318 end
319 kb.CacheTalentBinding = function(talentInfo, cache)
320
321 local spellID = talentInfo[4]
322 kb.inactiveTalentBindings[spellID] = kb.inactiveTalentBindings[spellID] or {}
323 kb.inactiveTalentBindings[spellID] = {select(5,unpack(talentInfo)) }
324 --cprint(spellID, unpack(kb.inactiveTalentBindings[spellID]))
325 end
326
327 kb.LoadBinding = function(command, name, icon, actionType, actionID, macroName, macroText )
328
329 if actionType == 'spell' then
330 KeyBinderKey:SetAttribute("*type-"..name, actionType)
331 KeyBinderKey:SetAttribute("*"..actionType.."-"..name, name)
332
333 elseif actionType == 'item' then
334 KeyBinderKey:SetAttribute("*type-"..name, actionType)
335 KeyBinderKey:SetAttribute("*"..actionType.."-"..name, name)
336 elseif actionType == 'macro' then
337 KeyBinderMacro:SetAttribute("*macro-"..macroName, actionID)
338 else
339 KeyBinderMacro:SetAttribute("*macrotext-"..macroName, macroText)
340 end
341
342 cprint('Loading binding', actionType, actionID)
343 bindings[actionType] = bindings[actionType] or {}
344 bindings[actionType][actionID] = bindings[actionType][actionID] or {}
345 bindings[command] = bindings[actionType][actionID]
346 return bindings[actionType], actionID
347 end
348
349 kb.ApplyBindings = function (profile)
350 cprint('binding profile', profile)
351 for slot, data in pairs(profile.buttons) do
352 kb.LoadBinding(unpack(data))
353 end
354
355 for key, command in pairs(profile.bindings) do
356
357 cprint(' *', key, '->', command)
358
359 --_G.print('HotKey','loading', key, command)
360 SetBinding(key, command)
361 if bindings[command] and not tContains(bindings[command], key) then
362 tinsert(bindings[command], key)
363 end
364 end
365
366 for spellName, talentInfo in pairs(profile.talents) do
367 local dummy = GetSpellInfo(spellName)
368 local func = kb.CacheTalentBinding
369 local dest = kb.inactiveTalentBindings
370 if dummy then
371 cprint('|cFFBBFF00Active:|r', dummy)
372 local macroName, spellName, actionType, actionID = unpack(talentInfo)
373 bindings[actionType] = bindings[actionType] or {}
374 bindings[actionType][actionID] = {}
375 func = kb.ApplyTalentBinding
376 dest = bindings[actionType][actionID]
377 else
378
379 cprint('|cFFFF4400Inactive:|r', talentInfo[2])
380 end
381 func(talentInfo, dest)
382 end
383
384 SaveBindings(GetCurrentBindingSet())
385 end
386
387 kb.ApplyAllBindings =function ()
388 table.wipe(kb.inactiveTalentBindings)
389
390 -- reflect action key settings
391 if GetCVarBool("ActionButtonUseKeyDown") then
392 KeyBinderMacro:RegisterForClicks("AnyDown")
393 KeyBinderKey:RegisterForClicks("AnyDown")
394 else
395 KeyBinderMacro:RegisterForClicks("AnyUp")
396 KeyBinderKey:RegisterForClicks("AnyUp")
397 end
398
399 for i, profile in ipairs(kb.orderedProfiles) do
400 kb.ApplyBindings(profile)
401 end
402 -- do this after to ensure that profession binds are properly overridden
403 kb.UpdateProfessionInfo()
404
405
406 end
407
408 kb.Command = function(args, editor)
409 if args:match("import") then
410 kb.ImportCommmit(args)
411 return
412 elseif args:match("scan") then
413 kb.ImportScan(args)
414 kb.ui()
415 return
416 elseif args:match("load") then
417 kb:ApplyAllBindings()
418 return
419 end
420
421 if db.showUI then
422 db.showUI = false
423 kb:print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.')
424 kb:Hide()
425 else
426 db.showUI = true
427 kb:print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.')
428 end
429 kb.ui(true)
430 end
431
432 kb.InitProfile = function(profile, prototype)
433 if not profile then
434 profile = {}
435 end
436 if prototype then
437 print('appplying prototype', prototype)
438 for k,v in pairs(prototype) do
439 if not profile[k] then
440 profile[k] = v
441 end
442 end
443 end
444
445 profile.bound = profile.bound or {}
446 profile.buttons = profile.buttons or {}
447 profile.commands = profile.commands or {}
448 profile.bindings = profile.bindings or {}
449 profile.macros = profile.macros or {}
450 profile.talents = profile.talents or {}
451 return profile
452 end
453
454 kb.ResetProfile = function(profile, prototype)
455 if profile == kb.currentProfile then
456 for i, button in pairs(buttons) do
457 kb.ReleaseSlot(button)
458 end
459 end
460 table.wipe(profile)
461 kb.InitProfile(profile, prototype)
462 end
463
464
465
466 --- Handles constructing spec profiles as they are selected
467
468
469 --- Obtains profile data or creates the necessary tables
470 kb.SelectProfileSet = function(name)
471
472 local defaultMode
473 --- General info
474 classHeader, className, classID = UnitClass('player')
475 print('|cFF00FF00profile:|r', name)
476 print('|cFF00FF00class:|r', UnitClass('player'))
477
478 --- Global
479 defaultMode = BINDING_TYPE_GLOBAL
480 kb.InitProfile(db)
481 kb.loadedProfiles[BINDING_TYPE_GLOBAL] = db
482
483 --- Character
484 if name then
485 db[name] = kb.InitProfile(db[name],
486 {classHeader = classHeader, className = className, classID = classID})
487 kb.loadedProfiles[BINDING_TYPE_CHARACTER] = db[name]
488 defaultMode = BINDING_TYPE_CHARACTER
489 end
490
491 --- Mutable skills data
492 kb.UpdateSpecInfo()
493 kb.UpdateTalentInfo()
494
495 kb.orderedProfiles = {kb.loadedProfiles[BINDING_TYPE_GLOBAL], kb.loadedProfiles[BINDING_TYPE_CHARACTER], kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION]}
496 if db.bindMode and (not kb.configTitle[db.bindMode]) then
497 print('fixing bad bindMode value, was', db.bindMode)
498 db.bindMode = defaultMode
499 end
500
501
502 print(BINDING_TYPE_GLOBAL)
503 kb.configHeaders[BINDING_TYPE_GLOBAL] = kb.configTitle[BINDING_TYPE_GLOBAL]
504 kb.configHeaders[BINDING_TYPE_CHARACTER] = kb.configTitle[BINDING_TYPE_CHARACTER]:format(UnitName('player', true))
505 kb.configHeaders[BINDING_TYPE_SPECIALIZATION] = kb.configTitle[BINDING_TYPE_SPECIALIZATION]:format(kb.specInfo.name)
506
507
508 setmetatable(kb.loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return kb.configHeaders[BINDING_TYPE_GLOBAL] end})
509 setmetatable(kb.loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return kb.configHeaders[BINDING_TYPE_CHARACTER] end})
510 setmetatable(kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return kb.configHeaders[BINDING_TYPE_SPECIALIZATION] end})
511
512 print('|cFF00FF00bindMode:|r', db.bindMode)
513 kb.currentProfile = kb.loadedProfiles[db.bindMode]
514 end
515
516 local scrollCache = {}
517 kb.SelectTab = function(self)
518 scrollCache[db.bindMode] = kb.scrollOffset
519 db.bindMode = self:GetID()
520 kb.currentProfile = kb.loadedProfiles[self:GetID()]
521 kb.scrollOffset = scrollCache[db.bindMode] or 0
522 kb.ui(true)
523 end
524
525 kb.RevertBindings = function()
526 -- todo: reversion code
527 end
528
529 kb.ConfirmBindings = function()
530 SaveBindings(GetCurrentBindingSet())
531 bindsCommitted = true
532 for i, button in ipairs(buttons) do
533 button.pending = false
534 end
535 kb.ApplyAllBindings()
536
537 kb.ui()
538 kb:print('Keybinds saved.')
539 end
540
541
542
543
544 --- post ADDON_LOADED
545 kb.variables = function()
546 SkeletonKeyDB = SkeletonKeyDB or {spec = {}}
547 kb.db = SkeletonKeyDB
548 kb.playerName = UnitName('player')
549 kb.playerRealm = SelectedRealmName()
550 kb.profileName = kb.playerRealm .. '_' .. kb.playerName
551 db = kb.db
552
553 kb.SelectProfileSet(kb.profileName)
554 -- todo: redo import checking
555
556
557
558 kb.ApplyAllBindings()
559
560 kb.ui(true)
561 end
562
563
564 kb.wrap = function(module)
565 kb.modules = kb.modules or {}
566 tinsert(kb.modules, module)
567 end
568
569 -- Volatiles Access
570 kb.GetBindings = function() return bindings end
571 kb.GetButtons = function() return buttons end
572 kb.GetCharacterProfile = function () return kb.loadedProfiles[BINDING_TYPE_CHARACTER] end
573 kb.GetGlobalProfile = function () return kb.loadedProfiles[BINDING_TYPE_GLOBAL] end
574 kb.GetLooseTalents = function() return talentBindings end
575 kb.GetReverts = function() return reverts end
576 kb.GetSpecProfile = function () return kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] end
577
578
579 SLASH_SKB1 = "/skb"
580 SLASH_SKB2 = "/skeletonkey"
581 SlashCmdList.SKB = kb.Command