Nenue@0
|
1 --------------------------------------------
|
Nenue@5
|
2 -- SkeletonKey
|
Nenue@5
|
3 -- Krakyn-Mal'Ganis
|
Nenue@0
|
4 -- @project-revision@ @project-hash@
|
Nenue@0
|
5 -- @file-revision@ @file-hash@
|
Nenue@0
|
6 -- Created: 6/16/2016 3:47 AM
|
Nenue@0
|
7 --------------------------------------------
|
Nenue@0
|
8 -- kb
|
Nenue@5
|
9 -- .StoreBinding(button, key) bind current keystroke to command
|
Nenue@5
|
10 -- .GetSlot(index) return display slot
|
Nenue@5
|
11 -- .SetSlot(button, command, name, icon) assign display slot
|
Nenue@5
|
12 -- .ReleaseSlot(button) clear button command
|
Nenue@5
|
13 -- .UpdateSlot(button) update button contents
|
Nenue@5
|
14 -- .SelectProfile(name) set profile character
|
Nenue@5
|
15 -- .ApplyBindings(bindings) walk table with SetBinding()
|
Nenue@0
|
16
|
Nenue@5
|
17 local _
|
Nenue@5
|
18 local kb, print = LibStub("LibKraken").register(KeyBinder)
|
Nenue@14
|
19 kb.L = setmetatable({}, {
|
Nenue@17
|
20 __call = function(t, k, ...) return format(t[k] or k, ...) end
|
Nenue@14
|
21 })
|
Nenue@14
|
22 local L = kb.L
|
Nenue@7
|
23
|
Nenue@5
|
24 --- Caps Lock literals
|
Nenue@5
|
25 local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES"
|
Nenue@14
|
26 L.BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).'
|
Nenue@14
|
27 L.BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.'
|
Nenue@14
|
28 L.BINDING_FAILED_PROTECTED = '|cFFFF4400Unable to use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400)|r'
|
Nenue@14
|
29
|
Nenue@14
|
30
|
Nenue@5
|
31 local BINDING_TYPE_SPECIALIZATION = 3
|
Nenue@5
|
32 local BINDING_TYPE_CHARACTER = 2
|
Nenue@5
|
33 local BINDING_TYPE_GLOBAL = 1
|
Nenue@6
|
34 kb.configTitle = {
|
Nenue@17
|
35 [BINDING_TYPE_GLOBAL] = L('Global Binds'),
|
Nenue@17
|
36 [BINDING_TYPE_CHARACTER] = L('Character: %%s'),
|
Nenue@17
|
37 [BINDING_TYPE_SPECIALIZATION] = L('Specialization: %%s')
|
Nenue@0
|
38 }
|
Nenue@6
|
39 kb.configDescription = {
|
Nenue@17
|
40 [BINDING_TYPE_GLOBAL] = L('The bindings are applied globally.'),
|
Nenue@17
|
41 [BINDING_TYPE_CHARACTER] = L('Applied when you log onto this character.'),
|
Nenue@17
|
42 [BINDING_TYPE_SPECIALIZATION] = L('Applied when you select this specialization.'),
|
Nenue@5
|
43 }
|
Nenue@5
|
44
|
Nenue@27
|
45 kb.ChangedBindings = {}
|
Nenue@19
|
46 kb.SystemBindings = {}
|
Nenue@19
|
47 kb.ActionTypes = {}
|
Nenue@19
|
48 kb.TalentBindings = {}
|
Nenue@27
|
49 kb.PetCache = {
|
Nenue@27
|
50 spell = {},
|
Nenue@27
|
51 spellslot = {},
|
Nenue@27
|
52 action = {},
|
Nenue@27
|
53 special = {},
|
Nenue@27
|
54 subtext = {}
|
Nenue@27
|
55 }
|
Nenue@27
|
56 kb.TalentCache = {}
|
Nenue@27
|
57 kb.ProfessionCache = {}
|
Nenue@27
|
58 kb.pendingCalls = {}
|
Nenue@27
|
59 kb.pendingAttributes = {}
|
Nenue@0
|
60
|
Nenue@6
|
61 kb.configHeaders = {}
|
Nenue@6
|
62 kb.loadedProfiles = {}
|
Nenue@6
|
63 kb.orderedProfiles = {}
|
Nenue@6
|
64 kb.buttons = {}
|
Nenue@6
|
65 kb.macros = {}
|
Nenue@17
|
66 kb.bindings = {}
|
Nenue@15
|
67 kb.petFrames = {} -- pet data is slightly delayed, their buttons are indexed here so they can be refreshed
|
Nenue@15
|
68 kb.talentFrames = {}
|
Nenue@15
|
69 kb.professionFrames = {}
|
Nenue@0
|
70
|
Nenue@14
|
71 -- these are sent to plugin
|
Nenue@14
|
72
|
Nenue@14
|
73
|
Nenue@14
|
74 local db
|
Nenue@5
|
75 local bindHeader, currentHeader = '', ''
|
Nenue@5
|
76 local specID, specGlobalID, specName, specDesc, specTexture, characterHeader = 0, 0, 'SPEC_NAME', 'SPEC_DESCRIPTION', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME'
|
Nenue@5
|
77 local classHeader, className, classID = '', '', 0
|
Nenue@5
|
78 local bindsCommitted = true
|
Nenue@5
|
79 local forceButtonUpdate = false
|
Nenue@5
|
80
|
Nenue@5
|
81 --- Control handles
|
Nenue@0
|
82 local saveButton, restoreButton, clearButton
|
Nenue@0
|
83
|
Nenue@5
|
84 --- Returns conflicting assignment and binding profiles for use in displaying confirmations
|
Nenue@6
|
85 kb.IsCommandBound = function(self, command)
|
Nenue@6
|
86 local isAssigned, assignedBy = false, db.bindMode
|
Nenue@6
|
87 local isBound, boundBy = false, db.bindMode
|
Nenue@5
|
88 command = command or self.command
|
Nenue@6
|
89 for i = 1, #kb.orderedProfiles do
|
Nenue@6
|
90 local tier = kb.orderedProfiles[i]
|
Nenue@6
|
91 if i ~= db.bindMode then
|
Nenue@5
|
92
|
Nenue@5
|
93 if tier.commands[command] then
|
Nenue@5
|
94 isAssigned = true
|
Nenue@5
|
95 assignedBy = i
|
Nenue@5
|
96 end
|
Nenue@5
|
97 if tier.bound[command] then
|
Nenue@5
|
98 isBound = true
|
Nenue@5
|
99 boundBy = i
|
Nenue@5
|
100 end
|
Nenue@5
|
101
|
Nenue@5
|
102
|
Nenue@5
|
103 --print(' *', configHeaders[i], tier.commands[command], tier.bound[command])
|
Nenue@5
|
104
|
Nenue@5
|
105 if isAssigned and isBound then
|
Nenue@0
|
106 break
|
Nenue@0
|
107 end
|
Nenue@0
|
108 end
|
Nenue@5
|
109
|
Nenue@0
|
110 end
|
Nenue@5
|
111
|
Nenue@17
|
112 print('|cFFFFFF00IsCommandBound:|r', command,'|r [profile:', db.bindMode .. ']', isAssigned, isBound, assignedBy, boundBy)
|
Nenue@5
|
113 return isAssigned, isBound, assignedBy, boundBy
|
Nenue@0
|
114 end
|
Nenue@0
|
115
|
Nenue@5
|
116 local talentSpellHardCodes = {
|
Nenue@5
|
117 [109248] = 'Binding Shot',
|
Nenue@5
|
118 }
|
Nenue@5
|
119
|
Nenue@0
|
120 --- Returns a value for use with Texture:SetDesaturated()
|
Nenue@6
|
121 kb.BindingIsLocked = function(key)
|
Nenue@0
|
122 local success = false
|
Nenue@6
|
123 for i = 1, db.bindMode-1 do
|
Nenue@6
|
124 local tier = kb.orderedProfiles[i]
|
Nenue@0
|
125 if tier.bindings[key] then
|
Nenue@0
|
126 success = true
|
Nenue@0
|
127 break
|
Nenue@0
|
128 end
|
Nenue@0
|
129 end
|
Nenue@0
|
130 return success
|
Nenue@0
|
131 end
|
Nenue@0
|
132
|
Nenue@0
|
133 --- Translates GetBindingKey() results into a printable string.
|
Nenue@6
|
134 kb.BindingString = function(...)
|
Nenue@0
|
135 local stack = {}
|
Nenue@0
|
136 for i = 1, select('#', ...) do
|
Nenue@0
|
137 local key = select(i, ...)
|
Nenue@5
|
138 stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ')
|
Nenue@0
|
139 end
|
Nenue@0
|
140
|
Nenue@0
|
141 if #stack >= 1 then
|
Nenue@0
|
142 return table.concat(stack, ',')
|
Nenue@0
|
143 else
|
Nenue@0
|
144 return nil
|
Nenue@0
|
145 end
|
Nenue@0
|
146 end
|
Nenue@0
|
147
|
Nenue@5
|
148
|
Nenue@5
|
149
|
Nenue@5
|
150
|
Nenue@0
|
151
|
Nenue@5
|
152 kb.Command = function(args, editor)
|
Nenue@5
|
153 if args:match("import") then
|
Nenue@5
|
154 kb.ImportCommmit(args)
|
Nenue@5
|
155 return
|
Nenue@5
|
156 elseif args:match("scan") then
|
Nenue@5
|
157 kb.ImportScan(args)
|
Nenue@5
|
158 kb.ui()
|
Nenue@5
|
159 return
|
Nenue@5
|
160 elseif args:match("load") then
|
Nenue@5
|
161 kb:ApplyAllBindings()
|
Nenue@0
|
162 return
|
Nenue@0
|
163 end
|
Nenue@0
|
164
|
Nenue@5
|
165 if db.showUI then
|
Nenue@5
|
166 db.showUI = false
|
Nenue@5
|
167 kb:print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.')
|
Nenue@5
|
168 kb:Hide()
|
Nenue@5
|
169 else
|
Nenue@1
|
170 db.showUI = true
|
Nenue@5
|
171 kb:print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.')
|
Nenue@5
|
172 end
|
Nenue@5
|
173 kb.ui(true)
|
Nenue@5
|
174 end
|
Nenue@5
|
175
|
Nenue@5
|
176 kb.InitProfile = function(profile, prototype)
|
Nenue@5
|
177 if not profile then
|
Nenue@5
|
178 profile = {}
|
Nenue@5
|
179 end
|
Nenue@5
|
180 if prototype then
|
Nenue@5
|
181 print('appplying prototype', prototype)
|
Nenue@5
|
182 for k,v in pairs(prototype) do
|
Nenue@5
|
183 if not profile[k] then
|
Nenue@5
|
184 profile[k] = v
|
Nenue@5
|
185 end
|
Nenue@5
|
186 end
|
Nenue@0
|
187 end
|
Nenue@0
|
188
|
Nenue@5
|
189 profile.bound = profile.bound or {}
|
Nenue@5
|
190 profile.buttons = profile.buttons or {}
|
Nenue@5
|
191 profile.commands = profile.commands or {}
|
Nenue@5
|
192 profile.bindings = profile.bindings or {}
|
Nenue@5
|
193 profile.macros = profile.macros or {}
|
Nenue@5
|
194 profile.talents = profile.talents or {}
|
Nenue@5
|
195 return profile
|
Nenue@5
|
196 end
|
Nenue@5
|
197
|
Nenue@5
|
198 kb.ResetProfile = function(profile, prototype)
|
Nenue@6
|
199 if profile == kb.currentProfile then
|
Nenue@5
|
200 for i, button in pairs(buttons) do
|
Nenue@5
|
201 kb.ReleaseSlot(button)
|
Nenue@5
|
202 end
|
Nenue@5
|
203 end
|
Nenue@5
|
204 table.wipe(profile)
|
Nenue@5
|
205 kb.InitProfile(profile, prototype)
|
Nenue@5
|
206 end
|
Nenue@5
|
207
|
Nenue@5
|
208
|
Nenue@5
|
209
|
Nenue@5
|
210 --- Handles constructing spec profiles as they are selected
|
Nenue@5
|
211
|
Nenue@5
|
212
|
Nenue@5
|
213 --- Obtains profile data or creates the necessary tables
|
Nenue@5
|
214 kb.SelectProfileSet = function(name)
|
Nenue@5
|
215
|
Nenue@6
|
216 local defaultMode
|
Nenue@5
|
217 --- General info
|
Nenue@5
|
218 classHeader, className, classID = UnitClass('player')
|
Nenue@5
|
219 print('|cFF00FF00profile:|r', name)
|
Nenue@5
|
220 print('|cFF00FF00class:|r', UnitClass('player'))
|
Nenue@5
|
221
|
Nenue@6
|
222 defaultMode = BINDING_TYPE_GLOBAL
|
Nenue@17
|
223 if db[name] then
|
Nenue@6
|
224 defaultMode = BINDING_TYPE_CHARACTER
|
Nenue@17
|
225 if db[name][kb.specInfo.id] then
|
Nenue@17
|
226 defaultMode = BINDING_TYPE_SPECIALIZATION
|
Nenue@17
|
227 end
|
Nenue@5
|
228 end
|
Nenue@5
|
229
|
Nenue@17
|
230 db[name] = kb.InitProfile(db[name],
|
Nenue@17
|
231 {
|
Nenue@17
|
232 classHeader = classHeader,
|
Nenue@17
|
233 className = className,
|
Nenue@17
|
234 classID = classID
|
Nenue@17
|
235 })
|
Nenue@17
|
236 db[name][kb.specInfo.id] = kb.InitProfile(db[name][kb.specInfo.id],
|
Nenue@17
|
237 {
|
Nenue@17
|
238 specID = kb.specInfo.id,
|
Nenue@17
|
239 specName = kb.specInfo.name
|
Nenue@17
|
240 })
|
Nenue@5
|
241
|
Nenue@17
|
242 kb.loadedProfiles[BINDING_TYPE_GLOBAL] = db
|
Nenue@17
|
243 kb.loadedProfiles[BINDING_TYPE_CHARACTER] = db[name]
|
Nenue@17
|
244 kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] = db[name][kb.specInfo.id]
|
Nenue@17
|
245 kb.orderedProfiles = {db, db[name], db[name][kb.specInfo.id]}
|
Nenue@17
|
246
|
Nenue@15
|
247 if (not db.bindMode) or (not kb.configTitle[db.bindMode]) then
|
Nenue@6
|
248 print('fixing bad bindMode value, was', db.bindMode)
|
Nenue@6
|
249 db.bindMode = defaultMode
|
Nenue@5
|
250 end
|
Nenue@5
|
251
|
Nenue@5
|
252
|
Nenue@5
|
253 print(BINDING_TYPE_GLOBAL)
|
Nenue@6
|
254 kb.configHeaders[BINDING_TYPE_GLOBAL] = kb.configTitle[BINDING_TYPE_GLOBAL]
|
Nenue@6
|
255 kb.configHeaders[BINDING_TYPE_CHARACTER] = kb.configTitle[BINDING_TYPE_CHARACTER]:format(UnitName('player', true))
|
Nenue@10
|
256 kb.configHeaders[BINDING_TYPE_SPECIALIZATION] = kb.configTitle[BINDING_TYPE_SPECIALIZATION]:format(kb.specInfo.name)
|
Nenue@5
|
257
|
Nenue@5
|
258
|
Nenue@6
|
259 setmetatable(kb.loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return kb.configHeaders[BINDING_TYPE_GLOBAL] end})
|
Nenue@6
|
260 setmetatable(kb.loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return kb.configHeaders[BINDING_TYPE_CHARACTER] end})
|
Nenue@6
|
261 setmetatable(kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return kb.configHeaders[BINDING_TYPE_SPECIALIZATION] end})
|
Nenue@5
|
262
|
Nenue@6
|
263 print('|cFF00FF00bindMode:|r', db.bindMode)
|
Nenue@6
|
264 kb.currentProfile = kb.loadedProfiles[db.bindMode]
|
Nenue@17
|
265 kb.currentHeader = kb.configHeaders[db.bindMode]
|
Nenue@5
|
266 end
|
Nenue@5
|
267
|
Nenue@5
|
268 local scrollCache = {}
|
Nenue@5
|
269 kb.SelectTab = function(self)
|
Nenue@6
|
270 scrollCache[db.bindMode] = kb.scrollOffset
|
Nenue@5
|
271 db.bindMode = self:GetID()
|
Nenue@6
|
272 kb.currentProfile = kb.loadedProfiles[self:GetID()]
|
Nenue@17
|
273 kb.currentHeader = kb.configHeaders[db.bindMode]
|
Nenue@6
|
274 kb.scrollOffset = scrollCache[db.bindMode] or 0
|
Nenue@5
|
275 kb.ui(true)
|
Nenue@5
|
276 end
|
Nenue@5
|
277
|
Nenue@5
|
278 kb.RevertBindings = function()
|
Nenue@5
|
279 -- todo: reversion code
|
Nenue@5
|
280 end
|
Nenue@5
|
281
|
Nenue@5
|
282 kb.ConfirmBindings = function()
|
Nenue@5
|
283 kb.ApplyAllBindings()
|
Nenue@5
|
284 kb.ui()
|
Nenue@5
|
285 end
|
Nenue@5
|
286
|
Nenue@5
|
287
|
Nenue@5
|
288
|
Nenue@5
|
289
|
Nenue@5
|
290 --- post ADDON_LOADED
|
Nenue@5
|
291 kb.variables = function()
|
Nenue@17
|
292 SkeletonKeyDB = kb.InitProfile(SkeletonKeyDB, {})
|
Nenue@5
|
293 kb.db = SkeletonKeyDB
|
Nenue@5
|
294 kb.playerName = UnitName('player')
|
Nenue@5
|
295 kb.playerRealm = SelectedRealmName()
|
Nenue@5
|
296 kb.profileName = kb.playerRealm .. '_' .. kb.playerName
|
Nenue@5
|
297 db = kb.db
|
Nenue@5
|
298
|
Nenue@17
|
299 kb.UpdateSpecInfo()
|
Nenue@17
|
300 kb.UpdateTalentInfo()
|
Nenue@5
|
301 kb.SelectProfileSet(kb.profileName)
|
Nenue@6
|
302 -- todo: redo import checking
|
Nenue@6
|
303
|
Nenue@15
|
304 kb.UpdateSystemBinds()
|
Nenue@5
|
305 kb.ApplyAllBindings()
|
Nenue@5
|
306
|
Nenue@27
|
307 if not InCombatLockdown() then
|
Nenue@27
|
308 kb.CreateHooks()
|
Nenue@27
|
309 else
|
Nenue@27
|
310 kb:print('Some functionality will not load until breaking combat.')
|
Nenue@27
|
311 tinsert(kb.pendingCalls, kb.CreateHooks)
|
Nenue@27
|
312 end
|
Nenue@27
|
313
|
Nenue@27
|
314
|
Nenue@5
|
315 kb.ui(true)
|
Nenue@0
|
316 end
|
Nenue@0
|
317
|
Nenue@1
|
318
|
Nenue@5
|
319 kb.wrap = function(module)
|
Nenue@5
|
320 kb.modules = kb.modules or {}
|
Nenue@5
|
321 tinsert(kb.modules, module)
|
Nenue@0
|
322 end
|
Nenue@0
|
323
|
Nenue@5
|
324 -- Volatiles Access
|
Nenue@17
|
325 kb.GetBindings = function() return kb.bindings end
|
Nenue@17
|
326 kb.GetButtons = function() return kb.buttons end
|
Nenue@6
|
327 kb.GetCharacterProfile = function () return kb.loadedProfiles[BINDING_TYPE_CHARACTER] end
|
Nenue@6
|
328 kb.GetGlobalProfile = function () return kb.loadedProfiles[BINDING_TYPE_GLOBAL] end
|
Nenue@6
|
329 kb.GetSpecProfile = function () return kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] end
|
Nenue@0
|
330
|
Nenue@0
|
331
|
Nenue@5
|
332 SLASH_SKB1 = "/skb"
|
Nenue@5
|
333 SLASH_SKB2 = "/skeletonkey"
|
Nenue@5
|
334 SlashCmdList.SKB = kb.Command
|