comparison Turok/Modules/Timer/Timer.lua @ 6:a9b8b0866ece

clear out log jam
author Nenue
date Sun, 21 Feb 2016 08:32:53 -0500
parents
children 9400a0ff8540
comparison
equal deleted inserted replaced
5:8a9a6637f082 6:a9b8b0866ece
1 --- Turok - Timer/Timer.lua
2 -- @file-author@
3 -- @project-revision@ @project-hash@
4 -- @file-revision@ @file-hash@
5 --- Defines common elements for the various timer HUDs
6 local ADDON, _A = ...
7 local _G, CreateFrame, tconcat, GetInventoryItemsForSlot, GetInventoryItemID = _G, CreateFrame, table.concat, GetInventoryItemsForSlot, GetInventoryItemID
8 local T, F, tostring, type, max, tinsert, unpack, UIParent, loadstring = _A.Addon, _A.LibFog, tostring, type, max, table.insert, unpack, _G.UIParent, loadstring
9 local mod = T.modules.TimerControl
10 local P = mod.prototype
11 local db
12
13 local pairs, ipairs, gsub, sub, setmetatable = pairs, ipairs, string.gsub, string.sub, setmetatable
14 local INVTYPE_FINGER, INVSLOT_FINGER1, INVSLOT_FINGER2, INVTYPE_TRINKET, INVSLOT_TRINKET1, INVSLOT_TRINKET2 =
15 INVTYPE_FINGER, INVSLOT_FINGER1, INVSLOT_FINGER2, INVTYPE_TRINKET, INVSLOT_TRINKET1, INVSLOT_TRINKET2
16 --@debug@
17 local DEBUG = true
18 --@end-debug@
19 local cType, cText, cNum, cWord, cKey, cPink, cBool = cType, cText, cNum, cWord, cKey, cPink, cBool
20 local print = function(...)
21 if not DEBUG then return end
22 if _G.Devian and _G.DevianDB.workspace ~= 1 then
23 _G.print('Timer', ...)
24 end
25 end
26
27 local Timer_GetPrintHandler = function(self)
28 if self.trace then
29 return function(...)
30 print(...)
31 _G.print('TimerFocus', ...)
32 end else
33 return print
34 end
35 end
36 local pb_suppressed = {}
37
38 function mod:OnInitialize()
39
40 --@debug@
41 TurokData.spirit.timers = Turok.defaults.spirit.timers
42 --@end-debug@
43 self.db = TurokData.spirit
44 db = self.db
45 self.active_cooldowns = {}
46 self.cast_units = {}
47 self.buff_units = {}
48 self.loaded_types = {}
49 self.loaded_triggers = {}
50 self.equipped = {}
51 self.containers = {}
52 self.timers = {} -- active timers
53 self.empty_frames = {} -- foster table for frames released by talent change
54
55
56 T:RegisterChatCommand("tsp", self.Import_Open)
57 T:RegisterChatCommand("tka", self.Dialog_Command)
58 T:RegisterChatCommand("tki", self.CreateIndex)
59 --T:Print("/tsp to import spells. /tka to open create dialog")
60 -- suppress cacophony from all cooldowns activating at login
61 self.quiet = true
62 --self:ScheduleTimer(function() self:Dialog_Command() end, 4)
63
64 end
65
66 local mt_single = {
67 __mode = "v",
68 __newindex = function(t,k,v)
69 rawset(t,k,v)
70 _G.print('DB', 'TCMeta: adding leaf', k, '=', v)
71 end}
72 local mt_double = {
73 __index = function(t,k)
74 t[k] = setmetatable({}, mt_single)
75 _G.print('DB', 'TCMeta: add layer', k, '=', t[k])
76 return t[k]
77 end,
78 __newindex = function(t,k,v)
79 rawset(t,k,v)
80 _G.print('DB', 'TCMeta: adding to top layer', k, '=', v)
81 end
82 }
83 local mt_error = {
84 __call =function (t, txt)
85 t.disable = true
86 tinsert(t, txt)
87 end
88 }
89
90 --- Sets and cleans up index data used by event handlers
91 local Timer_UpdateIndex = function(self, key)
92
93 -- Is there already an entry for this key/value?
94 if self.frames[key] then
95 local lim = #mod.frames[key]
96 --[[
97 for i = self.frames[key]+1, lim, 1 do
98 mod.frames[key][i] = mod.frames[key+1]
99 end]]
100 --self.frames[key] = nil
101 print(' ', cText('mod.frames.')..cWord(key), '=', #mod.frames[key])
102 print(' ', cText('self.frames.')..cWord(key), '=', cNum(self.frames[key]))
103 end
104
105 if key then
106 local i = #mod.frames[key]+1
107 --mod.frames[key][i] = self
108 self.frames[key] = i
109 print(' ', cText('self.frames.')..cWord(key), '=', #mod.frames[key])
110 end
111 mod.loaded_types[key] = (#mod.frames[key] == 0) and nil or true
112 print(' ',cText(key..'_is_loaded'), '=', cBool(mod.loaded_types[key]))
113 end
114
115 --- Loading initators
116 function mod:OnEnable()
117 mod.LoadPresets()
118 mod.GetIndex()
119 -- setup indexes, use nested weak table for status since they implicitly have a key variable
120 mod.frames = {}
121 for class, p in pairs(mod.prototype.status) do
122 print('nested index table', class)
123 mod.frames[class] = setmetatable({}, mt_double)
124 end
125 mod.frames.spellName = setmetatable({}, mt_double)
126 for class, p in pairs(mod.prototype.display) do
127 mod.frames[class] = setmetatable({}, mt_single)
128 end
129 for class, p in pairs(mod.prototype.trigger) do
130 mod.frames[class] = setmetatable({}, mt_single)
131 end
132
133 local srcIndex = mod.timers
134 if T.playerClass and mod.index[T.playerClass] then
135 srcIndex = mod.index[T.playerClass]
136 print('*** Found index for '..tostring(T.playerClass)..', using that.')
137 else
138 print(cWord('*** Using global index.'))
139 end
140 mod.activeSpec = T.specID
141
142 --- go through that list
143 for id, timer in pairs(srcIndex) do
144 local result, message = mod:EnableTimer(id, timer)
145 end
146
147 mod.InitTimers()
148 --- Delay sound activations so there isn't a giant cacophony on load
149 mod:ScheduleTimer(function()
150 self.quiet = nil
151 end, db.audio_delay or 2)
152 end
153
154 function mod:EnableTimer(id, dvars)
155 local print = Timer_GetPrintHandler(dvars)
156 print('-{', cPink(dvars.name))
157 if not dvars then
158 if not mod.index.global[id] then
159 return false, "Unable to resolve dvars table."
160 end
161 dvars = mod.index.global[id]
162 end
163 if dvars.virtual then
164 return
165 end
166
167 local spirit, newFrame = mod:GetTimer(id, dvars)
168 if not spirit then return spirit, newFrame end
169
170 local cvars = spirit.cvars
171 local dvars = spirit.dvars
172 local trigger = P.trigger[cvars.type]
173 local display = P.display[cvars.display]
174 local cvars = spirit.cvars
175 local index = mod.frames
176 local print = Timer_GetPrintHandler(cvars)
177
178 if spirit.disable then
179 return false, "Manually disabled." -- nothing to do, nothing to say
180 end
181
182 --- Interpret STATUS vars
183 print(cText(' *** Merging Status Data'))
184 spirit.disable = dvars.disable
185 local pcount = 1
186 for k, handler in pairs(P.status) do
187 if cvars[k] then
188 if handler.Init then
189 print(cWord(' * Firing ')..cKey(k)..cWord('.Init'), cNum(cvars[k]))
190 handler.Init(spirit, cvars[k])
191 else
192 print(' ', cText('skipped'), cKey(k))
193 end
194 pcount = pcount + 1
195 end
196 end
197
198 spirit.Event = trigger.Event
199 spirit.Value = trigger.Value
200 spirit.SetText = mod.SetText
201 spirit.LoadText = mod.LoadText
202 spirit.Query = trigger.Query
203 spirit.Set = trigger.Set
204
205 --- Display handler init
206 if display.Init then
207 print(cText(' * Display Init:'), cKey(dvars.display))
208 display.Init(spirit)
209 end
210
211 --- Trigger handler and events Load()
212 print(cText(' * Trigger Init:'), cKey(dvars.type))
213 trigger.Init(spirit)
214
215
216 if C_PetBattles.IsInBattle() then
217 spirit.disable = true
218 spirit.debug_info("Hidden for pet battle")
219 pb_suppressed[id] = true
220 end
221
222
223 if spirit.disable then
224 spirit:UnregisterAllEvents()
225 spirit.displayState = nil
226 spirit.prevState = nil
227 spirit:Hide()
228 return false, tconcat(spirit.debug_info,"\n")
229 else
230 print('--', self.disable and cPink('DISABLED') or cNum('ENABLED'), #spirit.debug_info > 0 and tconcat(spirit.debug_info,"\n"), '}')
231 return true, tconcat(spirit.debug_info,"\n")
232 end
233 end
234
235 function mod:GetTimer(id, dvars)
236 local print = Timer_GetPrintHandler(dvars)
237 local newFrame
238 if not mod.timers[id] then
239 print(cKey(' [[CreateTimer'))
240 newFrame = true
241 --- Compile the cvar table from the various config layers:
242 -- Start with timer dvars, overwritten by any container settings, then a disable check, then merge in prototype values
243 local cvars = T.Config_Push({}, dvars, nil, cKey('['..id..']')..'.'..cWord('cvars'))
244 cvars.name = dvars.name -- push function ignores name keys
245
246 if dvars.container and db.containers[dvars.container] then
247 print(cText(' * Merging Container overrides'))
248 T.Config_Push(cvars, db.containers[dvars.container], cvars, cKey('['..id..']')..'.'..cWord('cvars'))
249 end
250
251 --- Stop here if disabled via SavedVars
252 if cvars.disable then
253 return false, "Manually disabled"
254 end
255
256 --- Localize the stuff we are going to loop over
257 local display = P.display[cvars.display]
258 local trigger = P.trigger[cvars.type]
259 local displayType = cvars.display
260 local triggerType = cvars.type
261 if not (display and trigger) then
262 return nil, "Missing prototype data. Summary: "..tostring(displayType).."="..(display and 'OK' or 'MISSING') ..
263 " "..tostring(triggerType).."="..(trigger and 'OK' or 'MISSING')
264 end
265
266 --- Establish the order in which values are merged
267 print(cText(' * Merging object CVars'))
268 local cvar_class = {cWord('db.'..displayType), cWord('db.'..triggerType), cWord('db.global')}
269 local cvar_array = {
270 db[displayType],
271 db[triggerType],
272 db.global,
273 }
274 local override_class = {cWord('trigger.'..cvars.type), cWord('display.'.. cvars.display)}
275 local override_array = {
276 display.cvars,
277 trigger.cvars }
278
279 --- Table merge user settings
280 for i, p in pairs(cvar_array) do
281 print(' '..cNum(i)..' merge ['..cvar_class[i]..']')
282 T.Config_Merge(cvars, p, cvars, cKey('['..id..']')..'.'..cWord('cvars'))
283 end
284
285 --- Overwrite with anything defined by the prototype structure because it's important
286 local _, odiff
287 for i, p in ipairs(override_array) do
288 _, odiff = T.Config_Push(cvars, p, cvars, cKey('['..id..']')..'.'..cWord('cvars'))
289 end
290 local print = Timer_GetPrintHandler(cvars)
291
292 --- Create the UI frame and seed it with the data we just composed
293 local spirit = CreateFrame('Frame', 'TurokTimerFrame'..gsub(dvars.name, "[^%a%d]", ''), UIParent, display.inherits)
294 spirit.trace = cvars.trace
295 spirit.timerID = id
296 spirit.timerName = dvars.name
297 spirit.container = dvars.container
298 spirit.cvars = cvars
299 spirit.dvars = dvars
300 spirit.Update = display.Update
301 spirit.SetState = display.SetState
302 spirit.Report = mod.Report
303 spirit.Stats = trigger.Stats
304
305 --- Set Layout Statics
306 T.SetFrameLayout(spirit, cvars)
307
308 --- Create troubleshooting collection
309 spirit.debug_info = setmetatable({}, mt_error)
310
311 --- Add the frame to corresponding prototype indexes
312 spirit.frames = {}
313 spirit.events = {}
314
315 if spirit.display ~= displayType then
316 spirit.display = displayType
317 Timer_UpdateIndex(spirit, displayType)
318 end
319 if spirit.type ~= triggerType then
320 spirit.type = triggerType
321 Timer_UpdateIndex(spirit, triggerType)
322 end
323 --- Add the frame to global index
324 mod.timers[id] = spirit
325 end
326
327 return mod.timers[id], newFrame
328 end
329
330 function mod.InitTimers()
331 local print = function(...) _G.print('TimerEvent', ...) end
332 print('INIT TIMERS ====================')
333 for id, spirit in pairs(mod.timers) do
334 if spirit.disable then
335 print(id, 'disabled:', tconcat(spirit.debug_info or {}, ', '))
336 else
337
338 print(cText('init'), cNum(id), cWord(spirit.name))
339 --- Throw a dry event to initialize values
340 print(cText(' *'), cWord('prototype.'..cKey(spirit.dvars.type)..'.'..cWord('Load')))
341 P.trigger[spirit.dvars.type].Event(spirit)
342
343 --- Set loose
344 print(cText(' *'), cWord('prototype')..'.'..cKey('events')..'.'..cWord('Load'))
345 mod.UpdateEvents(spirit, P.trigger[spirit.dvars.type].events)
346 end
347 end
348 print('INIT DONE =========================')
349 end
350
351 function mod:DisableTimer(name, timer)
352 local timer_frame = mod.db.timers[name]
353 if timer_frame and not timer_frame.disable then
354 timer_frame.disable = true
355 timer_frame:UnregisterAllEvents()
356 timer_frame:Hide()
357 end
358 end
359
360 function mod.UpdateEvents(self, events)
361 local print = Timer_GetPrintHandler(self)
362
363 self:SetScript('OnEvent', nil)
364 self:UnregisterAllEvents()
365
366 local proxy, listen = {}, {}
367 for event, handler in pairs(events) do
368 if mod[event] then
369 tinsert(proxy, cNum(event))
370 else
371 tinsert(listen, cWord(event))
372 self:RegisterEvent(event)
373 end
374 self.events[event] = handler
375 end
376
377 if #proxy > 0 then
378 print( ' -', cKey(self.name), cWord('receiving'), tconcat(proxy, ', '))
379 end
380 if #listen > 0 then
381 print( ' -', cKey(self.name), cText('listening'), tconcat(listen, ', '))
382 end
383
384 self:SetScript('OnEvent', self.Event)
385 end
386
387 local match_sub = {
388 {'%%c', "' .. tostring(t.caster).. '"},
389 {'%%h', "' .. tostring((t.valueFull >= 60) and (math.floor(t.valueFull/60)) or t.value) .. '"},
390 {'%%i', "' .. tostring((t.valueFull >= 60) and (t.value % 60) or ((t.valueFull < 6) and math.floor((t.ValueFull * 100) % 100) or '')) .. '"},
391 {'%%n', "' .. tostring(t.spellName) .. '"},
392 {'%%p', "' .. tostring(t.value) .. '"},
393 {'%%d', "' .. tostring(t.chargeDuration or t.duration) .. '"},
394 {'%%%.p', "' .. string.sub(tostring((t.valueFull %% 1) * 100),0,1) .. '"},
395 {"%%s", "' .. (t.stacks or t.charges or '') .. '"},
396 }
397
398 -- dot syntax implies use as embedded method
399 function mod.LoadText(self)
400 print(cKey('parsing textRegions for'), self.timerName, self.timerID)
401 self.textTypes = {}
402 self.textValues = {}
403 for name, region in pairs(self.textRegions) do
404 print(' ', cWord('textRegions')..'["'.. cType(self.timerName)..'"].'..cType(name))
405 if self.cvars[name..'Text'] then
406
407 -- todo: collect match counts and index the text fields by match types
408 local str = self.cvars[name..'Text']
409 for i, args in ipairs(match_sub) do
410 if str:match(args[1]) then
411 if not self.textTypes[args[1]] then
412 self.textTypes[args[1]] = {}
413 end
414 tinsert(self.textTypes[args[1]], region)
415 str = str:gsub(args[1], args[2])
416 end
417 end
418 str = "local t = _G.Turok.modules.TimerControl.timers["..self.timerID.."]\n"
419 .. "\n return '" .. str .. "'"
420 local func = assert(loadstring(str))
421 self.textValues[name] = func
422 end
423 end
424
425 --mod.SetText(self)
426 end
427
428 --- generic text setter
429 local HIDDEN, PASSIVE, ACTIVE = 0, 1, 2
430 mod.SetText = function(self)
431 if self.displayState ~= ACTIVE then
432 for name, region in pairs(self.textRegions) do
433 region:SetText(nil)
434 end
435 return
436 end
437
438 if not self.textValues then
439 self.textValues = {}
440 mod.LoadText(self, self.cvars)
441 end
442
443 -- hide when above a certain number
444
445 if self.spiral and self.spiral.subCounter then
446 if self.valueFull > 6 then
447 if self.textValues.subCounter then
448 --print('hiding milliseconds')
449 self.textRegions.subCounter:Hide()
450 self.textRegionsSub = self.textRegions.subCounter
451 self.textValuesSub = self.textValues.subCounter
452 self.textRegions.subCounter = nil
453 self.textValues.subCounter = nil
454 end
455 else
456 if not self.textValues.subCounter then
457 --print('showing milliseconds')
458 self.textValues.subCounter = self.textValuesSub
459 self.textRegions.subCounter = self.textRegionsSub
460 self.textRegions.subCounter:Show()
461 end
462 end
463 end
464
465 for name, region in pairs(self.textRegions) do
466 --print(name)
467 --print(name, self.timerName, self.textValues[name](self))
468 region:SetText(self.textValues[name](self))
469 end
470 end
471
472
473 -------------------------------------------------------------------------
474 --- Second-tier handlers to cut down on the number of Status:Event() polls
475
476 --- UNIT_SPELLCAST_*** use args to filter out the number of full handler runs
477 function mod:UNIT_SPELLCAST_SUCCEEDED (e, unit, spellName, rank, castID, spellID)
478 if not mod.frames.unit[unit] then
479 return
480 end
481
482 if #mod.frames.spellName[spellName] > 0 then
483 print('spellName-ID relation detected:', cWord(spellName), cNum(spellID))
484 for i, frame in pairs(mod.frames.spellName[spellName]) do
485 if not frame.frames.spellID then
486 frame.frames.spellID = {}
487 end
488 if not frame.frames.spellID[spellID] then
489
490 tinsert(mod.frames.spellID[spellID], frame)
491 frame.frames.spellID[spellID] = #mod.frames.spellID[spellID]
492 print(cText(' updating'), cKey(frame.timerName))
493 end
494 end
495 mod.frames.spellName[spellName] = nil
496 end
497
498
499
500 if mod.frames.spellID[spellID] then
501 for i, timer_frame in pairs(mod.frames.spellID[spellID]) do
502 print(cText('caught spell'), cWord(spellName), 'for', timer_frame:GetName())
503 timer_frame:Event(e, unit, spellName, rank, castID, spellID)
504 end
505 end
506 end
507 mod.UNIT_SPELLCAST_CHANNEL_START = mod.UNIT_SPELLCAST_SUCCEEDED
508
509 --- Fire a dry event to force status updates on units with changing GUID's
510 function mod:PLAYER_TARGET_CHANGED(e, unit)
511 print('doing a target swap thing')
512 for k, v in pairs( self.frames.unit.target) do
513 print(k, v)
514 v:Event(nil, 'target')
515 end
516 end
517
518 --- Same thing but for talent/spec-driven
519 function mod:PLAYER_TALENT_UPDATE(e, unit)
520 print('')
521 print('')
522 print(cText(e), T.specPage, T.specName)
523
524 local update_queue = {}
525 for _, k in ipairs({'talentID', 'talentRow', 'specPage'}) do
526 for value, frameSet in pairs(mod.frames.talentID) do
527 for id, frame in ipairs(frameSet) do
528 print(frame.timerID, frame.timerName)
529 update_queue[frame.timerID] = frame
530 end
531 end
532 end
533
534 for id, frame in pairs(update_queue) do
535 print('Refreshing spec-related frames', id, frame.timerName)
536 frame.disable = nil
537 table.wipe(frame.debug_info)
538 local res, msg = mod:EnableTimer(id, frame.dvars)
539 end
540
541 end
542
543 function mod:PLAYER_EQUIPMENT_CHANGED(e, slot, hasItem)
544 print(e, slot, hasItem)
545 local itemCheckList
546 if mod.frames.inventoryID and mod.frames.inventoryID[slot] then
547 print(' Inventory Frames:')
548 itemCheckList = GetInventoryItemsForSlot(slot, {}, false)
549 for id, slotFrame in pairs(mod.frames.inventoryID[slot]) do
550 print(' * Updating', cNum(id), cWord(slotFrame.timerName))
551 local res, msg = mod:EnableTimer(slotFrame.timerID, slotFrame.dvars)
552 print(' ', cBool(res), cText(msg))
553 end
554 end
555 if itemCheckList then
556 print(unpack(itemCheckList))
557 end
558 local itemID = GetInventoryItemID('player', slot)
559 if itemID and mod.frames.itemID[itemID] then
560 print(' Item ID Frames:')
561 for id, itemFrame in pairs(mod.frames.itemID[itemID]) do
562 print(' * Updating', cNum(id), cWord(itemFrame.timerName))
563
564 local res, msg = mod:EnableTimer(itemFrame.timerID, itemFrame.dvars)
565 print(' ', cBool(res), cText(msg))
566 end
567 end
568 end
569 function mod:PET_BATTLE_OPENING_START ()
570 for i, v in pairs(mod.timers) do
571 if not v.disable then
572 print('suppressing', v:GetName())
573 v.disable = true
574 v:Hide()
575 pb_suppressed[i] = true
576 end
577 end
578 end
579 function mod:PET_BATTLE_CLOSE()
580 for id, v in pairs(mod.timers) do
581 if pb_suppressed[id] then
582 print('restoring', v:GetName())
583 mod:EnableTimer(id)
584 pb_suppressed[id] = nil
585 end
586 end
587 end