Mercurial > wow > skeletonkey
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 |