diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Turok/Modules/Timer/Timer.lua	Sun Feb 21 08:32:53 2016 -0500
@@ -0,0 +1,587 @@
+--- Turok - Timer/Timer.lua
+-- @file-author@
+-- @project-revision@ @project-hash@
+-- @file-revision@ @file-hash@
+--- Defines common elements for the various timer HUDs
+local ADDON, _A = ...
+local _G, CreateFrame, tconcat, GetInventoryItemsForSlot, GetInventoryItemID = _G, CreateFrame, table.concat, GetInventoryItemsForSlot, GetInventoryItemID
+local T, F, tostring, type, max, tinsert, unpack, UIParent, loadstring = _A.Addon, _A.LibFog, tostring, type, max, table.insert, unpack, _G.UIParent, loadstring
+local mod = T.modules.TimerControl
+local P = mod.prototype
+local db
+
+local pairs, ipairs, gsub, sub, setmetatable = pairs, ipairs, string.gsub, string.sub, setmetatable
+local INVTYPE_FINGER, INVSLOT_FINGER1, INVSLOT_FINGER2, INVTYPE_TRINKET, INVSLOT_TRINKET1, INVSLOT_TRINKET2 =
+INVTYPE_FINGER, INVSLOT_FINGER1, INVSLOT_FINGER2, INVTYPE_TRINKET, INVSLOT_TRINKET1, INVSLOT_TRINKET2
+--@debug@
+local DEBUG = true
+--@end-debug@
+local cType, cText, cNum, cWord, cKey, cPink, cBool = cType, cText, cNum, cWord, cKey, cPink, cBool
+local print = function(...)
+  if not DEBUG then return end
+  if _G.Devian and _G.DevianDB.workspace ~= 1 then
+    _G.print('Timer', ...)
+  end
+end
+
+local Timer_GetPrintHandler = function(self)
+  if self.trace then
+  return function(...)
+    print(...)
+    _G.print('TimerFocus', ...)
+  end else
+    return print
+  end
+end
+local pb_suppressed = {}
+
+function mod:OnInitialize()
+
+  --@debug@
+  TurokData.spirit.timers = Turok.defaults.spirit.timers
+  --@end-debug@
+  self.db = TurokData.spirit
+  db = self.db
+  self.active_cooldowns = {}
+  self.cast_units = {}
+  self.buff_units = {}
+  self.loaded_types = {}
+  self.loaded_triggers = {}
+  self.equipped = {}
+  self.containers = {}
+  self.timers = {} -- active timers
+  self.empty_frames = {} -- foster table for frames released by talent change
+
+
+  T:RegisterChatCommand("tsp", self.Import_Open)
+  T:RegisterChatCommand("tka", self.Dialog_Command)
+  T:RegisterChatCommand("tki", self.CreateIndex)
+  --T:Print("/tsp to import spells. /tka to open create dialog")
+  -- suppress cacophony from all cooldowns activating at login
+  self.quiet = true
+  --self:ScheduleTimer(function() self:Dialog_Command() end, 4)
+
+end
+
+local mt_single = {
+  __mode = "v",
+  __newindex = function(t,k,v)
+    rawset(t,k,v)
+    _G.print('DB', 'TCMeta: adding leaf', k, '=', v)
+  end}
+local mt_double = {
+  __index = function(t,k)
+    t[k] = setmetatable({}, mt_single)
+    _G.print('DB', 'TCMeta: add layer', k, '=', t[k])
+    return t[k]
+  end,
+  __newindex = function(t,k,v)
+    rawset(t,k,v)
+    _G.print('DB', 'TCMeta: adding to top layer', k, '=', v)
+  end
+}
+local mt_error = {
+  __call =function (t, txt)
+    t.disable = true
+    tinsert(t, txt)
+  end
+}
+
+--- Sets and cleans up index data used by event handlers
+local Timer_UpdateIndex = function(self, key)
+
+  -- Is there already an entry for this key/value?
+  if self.frames[key] then
+    local lim = #mod.frames[key]
+    --[[
+    for i = self.frames[key]+1, lim, 1 do
+      mod.frames[key][i] = mod.frames[key+1]
+    end]]
+    --self.frames[key] = nil
+    print('     ', cText('mod.frames.')..cWord(key), '=', #mod.frames[key])
+    print('     ', cText('self.frames.')..cWord(key), '=', cNum(self.frames[key]))
+  end
+
+  if key then
+    local i = #mod.frames[key]+1
+    --mod.frames[key][i] = self
+    self.frames[key] = i
+    print('     ', cText('self.frames.')..cWord(key), '=', #mod.frames[key])
+  end
+  mod.loaded_types[key] = (#mod.frames[key] == 0) and nil or true
+  print('     ',cText(key..'_is_loaded'), '=', cBool(mod.loaded_types[key]))
+end
+
+--- Loading initators
+function mod:OnEnable()
+  mod.LoadPresets()
+  mod.GetIndex()
+  -- setup indexes, use nested weak table for status since they implicitly have a key variable
+  mod.frames = {}
+  for class, p in pairs(mod.prototype.status) do
+    print('nested index table', class)
+    mod.frames[class] = setmetatable({}, mt_double)
+  end
+  mod.frames.spellName = setmetatable({}, mt_double)
+  for class, p in pairs(mod.prototype.display) do
+    mod.frames[class] = setmetatable({}, mt_single)
+  end
+  for class, p in pairs(mod.prototype.trigger) do
+    mod.frames[class] = setmetatable({}, mt_single)
+  end
+
+  local srcIndex = mod.timers
+  if T.playerClass and mod.index[T.playerClass] then
+    srcIndex = mod.index[T.playerClass]
+    print('*** Found index for '..tostring(T.playerClass)..', using that.')
+  else
+    print(cWord('*** Using global index.'))
+  end
+  mod.activeSpec = T.specID
+
+  --- go through that list
+  for id, timer in pairs(srcIndex) do
+    local result, message = mod:EnableTimer(id, timer)
+  end
+
+  mod.InitTimers()
+  --- Delay sound activations so there isn't a giant cacophony on load
+  mod:ScheduleTimer(function()
+    self.quiet = nil
+  end, db.audio_delay or 2)
+end
+
+function mod:EnableTimer(id, dvars)
+  local print = Timer_GetPrintHandler(dvars)
+  print('-{', cPink(dvars.name))
+  if not dvars then
+    if not mod.index.global[id] then
+      return false,  "Unable to resolve dvars table."
+    end
+    dvars = mod.index.global[id]
+  end
+  if dvars.virtual then
+    return
+  end
+
+  local spirit, newFrame = mod:GetTimer(id, dvars)
+  if not spirit then return spirit, newFrame end
+
+  local cvars = spirit.cvars
+  local dvars = spirit.dvars
+  local trigger = P.trigger[cvars.type]
+  local display = P.display[cvars.display]
+  local cvars = spirit.cvars
+  local index = mod.frames
+  local print = Timer_GetPrintHandler(cvars)
+
+  if spirit.disable then
+    return false, "Manually disabled." -- nothing to do, nothing to say
+  end
+
+  --- Interpret STATUS vars
+  print(cText('  *** Merging Status Data'))
+  spirit.disable = dvars.disable
+  local pcount = 1
+  for k, handler in pairs(P.status) do
+    if cvars[k] then
+      if handler.Init then
+        print(cWord('  * Firing ')..cKey(k)..cWord('.Init'), cNum(cvars[k]))
+        handler.Init(spirit, cvars[k])
+      else
+        print('   ', cText('skipped'), cKey(k))
+      end
+      pcount = pcount + 1
+    end
+  end
+
+  spirit.Event = trigger.Event
+  spirit.Value = trigger.Value
+  spirit.SetText = mod.SetText
+  spirit.LoadText = mod.LoadText
+  spirit.Query = trigger.Query
+  spirit.Set = trigger.Set
+
+  --- Display handler init
+  if display.Init then
+    print(cText('  * Display Init:'), cKey(dvars.display))
+    display.Init(spirit)
+  end
+
+  --- Trigger handler and events Load()
+  print(cText('  * Trigger Init:'), cKey(dvars.type))
+  trigger.Init(spirit)
+
+
+  if C_PetBattles.IsInBattle() then
+    spirit.disable = true
+    spirit.debug_info("Hidden for pet battle")
+    pb_suppressed[id] = true
+  end
+
+
+  if spirit.disable then
+    spirit:UnregisterAllEvents()
+    spirit.displayState = nil
+    spirit.prevState = nil
+    spirit:Hide()
+    return false, tconcat(spirit.debug_info,"\n")
+  else
+    print('--', self.disable and cPink('DISABLED') or cNum('ENABLED'), #spirit.debug_info > 0 and tconcat(spirit.debug_info,"\n"), '}')
+    return true, tconcat(spirit.debug_info,"\n")
+  end
+end
+
+function mod:GetTimer(id, dvars)
+  local print = Timer_GetPrintHandler(dvars)
+  local newFrame
+  if not mod.timers[id] then
+    print(cKey('  [[CreateTimer'))
+    newFrame = true
+    --- Compile the cvar table from the various config layers:
+     -- Start with timer dvars, overwritten by any container settings, then a disable check, then merge in prototype values
+    local cvars = T.Config_Push({}, dvars, nil, cKey('['..id..']')..'.'..cWord('cvars'))
+    cvars.name = dvars.name -- push function ignores name keys
+
+    if dvars.container and db.containers[dvars.container] then
+      print(cText('    * Merging Container overrides'))
+      T.Config_Push(cvars, db.containers[dvars.container], cvars, cKey('['..id..']')..'.'..cWord('cvars'))
+    end
+
+    --- Stop here if disabled via SavedVars
+    if cvars.disable then
+      return false, "Manually disabled"
+    end
+
+    --- Localize the stuff we are going to loop over
+    local display = P.display[cvars.display]
+    local trigger = P.trigger[cvars.type]
+    local displayType = cvars.display
+    local triggerType = cvars.type
+    if not (display and trigger) then
+      return nil, "Missing prototype data. Summary: "..tostring(displayType).."="..(display and 'OK' or 'MISSING') ..
+          " "..tostring(triggerType).."="..(trigger and 'OK' or 'MISSING')
+    end
+
+    --- Establish the order in which values are merged
+    print(cText('    * Merging object CVars'))
+    local cvar_class = {cWord('db.'..displayType), cWord('db.'..triggerType), cWord('db.global')}
+    local cvar_array = {
+      db[displayType],
+      db[triggerType],
+      db.global,
+    }
+    local override_class = {cWord('trigger.'..cvars.type), cWord('display.'.. cvars.display)}
+    local override_array = {
+      display.cvars,
+      trigger.cvars }
+
+    --- Table merge user settings
+    for i, p in pairs(cvar_array) do
+      print('    '..cNum(i)..' merge ['..cvar_class[i]..']')
+      T.Config_Merge(cvars, p, cvars, cKey('['..id..']')..'.'..cWord('cvars'))
+    end
+
+    --- Overwrite with anything defined by the prototype structure because it's important
+    local _, odiff
+    for i, p in ipairs(override_array) do
+      _, odiff = T.Config_Push(cvars, p, cvars, cKey('['..id..']')..'.'..cWord('cvars'))
+    end
+    local print = Timer_GetPrintHandler(cvars)
+
+    --- Create the UI frame and seed it with the data we just composed
+    local spirit =  CreateFrame('Frame', 'TurokTimerFrame'..gsub(dvars.name, "[^%a%d]", ''), UIParent, display.inherits)
+    spirit.trace = cvars.trace
+    spirit.timerID = id
+    spirit.timerName = dvars.name
+    spirit.container = dvars.container
+    spirit.cvars = cvars
+    spirit.dvars = dvars
+    spirit.Update = display.Update
+    spirit.SetState = display.SetState
+    spirit.Report = mod.Report
+    spirit.Stats = trigger.Stats
+
+    --- Set Layout Statics
+    T.SetFrameLayout(spirit, cvars)
+
+    --- Create troubleshooting collection
+    spirit.debug_info = setmetatable({}, mt_error)
+
+    --- Add the frame to corresponding prototype indexes
+    spirit.frames = {}
+    spirit.events = {}
+
+    if spirit.display ~= displayType then
+      spirit.display = displayType
+      Timer_UpdateIndex(spirit, displayType)
+    end
+    if spirit.type ~= triggerType then
+      spirit.type = triggerType
+      Timer_UpdateIndex(spirit, triggerType)
+    end
+    --- Add the frame to global index
+    mod.timers[id] = spirit
+  end
+
+  return mod.timers[id], newFrame
+end
+
+function mod.InitTimers()
+  local print = function(...) _G.print('TimerEvent', ...) end
+  print('INIT TIMERS ====================')
+  for id, spirit in pairs(mod.timers) do
+    if spirit.disable then
+      print(id, 'disabled:', tconcat(spirit.debug_info or {}, ', '))
+    else
+
+    print(cText('init'), cNum(id), cWord(spirit.name))
+    --- Throw a dry event to initialize values
+    print(cText(' *'), cWord('prototype.'..cKey(spirit.dvars.type)..'.'..cWord('Load')))
+    P.trigger[spirit.dvars.type].Event(spirit)
+
+    --- Set loose
+    print(cText(' *'), cWord('prototype')..'.'..cKey('events')..'.'..cWord('Load'))
+    mod.UpdateEvents(spirit, P.trigger[spirit.dvars.type].events)
+    end
+  end
+  print('INIT DONE =========================')
+end
+
+function mod:DisableTimer(name, timer)
+  local timer_frame = mod.db.timers[name]
+  if timer_frame and not timer_frame.disable then
+    timer_frame.disable = true
+    timer_frame:UnregisterAllEvents()
+    timer_frame:Hide()
+  end
+end
+
+function mod.UpdateEvents(self, events)
+  local print = Timer_GetPrintHandler(self)
+
+  self:SetScript('OnEvent', nil)
+  self:UnregisterAllEvents()
+
+  local proxy, listen = {}, {}
+  for event, handler in pairs(events) do
+    if mod[event] then
+      tinsert(proxy, cNum(event))
+    else
+      tinsert(listen, cWord(event))
+      self:RegisterEvent(event)
+    end
+    self.events[event] = handler
+  end
+
+  if #proxy > 0 then
+    print( '  -', cKey(self.name), cWord('receiving'), tconcat(proxy, ', '))
+  end
+  if #listen > 0 then
+    print( '  -', cKey(self.name), cText('listening'), tconcat(listen, ', '))
+  end
+
+  self:SetScript('OnEvent', self.Event)
+end
+
+local match_sub = {
+  {'%%c', "' .. tostring(t.caster).. '"},
+  {'%%h', "' .. tostring((t.valueFull >= 60) and (math.floor(t.valueFull/60)) or t.value) .. '"},
+  {'%%i', "' .. tostring((t.valueFull >= 60) and (t.value % 60) or ((t.valueFull < 6) and math.floor((t.ValueFull * 100) % 100) or '')) .. '"},
+  {'%%n', "' .. tostring(t.spellName) .. '"},
+  {'%%p', "' .. tostring(t.value) .. '"},
+  {'%%d', "' .. tostring(t.chargeDuration or t.duration) .. '"},
+  {'%%%.p', "' .. string.sub(tostring((t.valueFull %% 1) * 100),0,1) .. '"},
+  {"%%s", "' .. (t.stacks or t.charges or '') .. '"},
+}
+
+-- dot syntax implies use as embedded method
+function mod.LoadText(self)
+  print(cKey('parsing textRegions for'), self.timerName, self.timerID)
+  self.textTypes = {}
+  self.textValues = {}
+  for name, region in pairs(self.textRegions) do
+    print('  ', cWord('textRegions')..'["'.. cType(self.timerName)..'"].'..cType(name))
+    if self.cvars[name..'Text'] then
+
+      -- todo: collect match counts and index the text fields by match types
+      local str = self.cvars[name..'Text']
+      for i, args in ipairs(match_sub) do
+        if str:match(args[1]) then
+          if not self.textTypes[args[1]] then
+            self.textTypes[args[1]] = {}
+          end
+          tinsert(self.textTypes[args[1]], region)
+          str = str:gsub(args[1], args[2])
+        end
+      end
+      str = "local t = _G.Turok.modules.TimerControl.timers["..self.timerID.."]\n"
+          .. "\n return '" .. str .. "'"
+      local func = assert(loadstring(str))
+      self.textValues[name] = func
+    end
+  end
+
+  --mod.SetText(self)
+end
+
+--- generic text setter
+local HIDDEN, PASSIVE, ACTIVE = 0, 1, 2
+mod.SetText = function(self)
+  if self.displayState ~= ACTIVE then
+    for name, region in pairs(self.textRegions) do
+      region:SetText(nil)
+    end
+    return
+  end
+
+  if not self.textValues then
+    self.textValues = {}
+    mod.LoadText(self, self.cvars)
+  end
+
+  -- hide when above a certain number
+
+  if self.spiral and self.spiral.subCounter then
+    if self.valueFull > 6 then
+      if self.textValues.subCounter then
+        --print('hiding milliseconds')
+        self.textRegions.subCounter:Hide()
+        self.textRegionsSub = self.textRegions.subCounter
+        self.textValuesSub = self.textValues.subCounter
+        self.textRegions.subCounter = nil
+        self.textValues.subCounter = nil
+      end
+    else
+      if not self.textValues.subCounter then
+        --print('showing milliseconds')
+        self.textValues.subCounter = self.textValuesSub
+        self.textRegions.subCounter = self.textRegionsSub
+        self.textRegions.subCounter:Show()
+      end
+    end
+  end
+
+  for name, region in pairs(self.textRegions) do
+    --print(name)
+    --print(name, self.timerName, self.textValues[name](self))
+    region:SetText(self.textValues[name](self))
+  end
+end
+
+
+-------------------------------------------------------------------------
+--- Second-tier handlers to cut down on the number of Status:Event() polls
+
+--- UNIT_SPELLCAST_*** use args to filter out the number of full handler runs
+function mod:UNIT_SPELLCAST_SUCCEEDED (e, unit, spellName, rank, castID, spellID)
+  if not mod.frames.unit[unit] then
+    return
+  end
+
+  if #mod.frames.spellName[spellName] > 0 then
+    print('spellName-ID relation detected:', cWord(spellName), cNum(spellID))
+    for i, frame in pairs(mod.frames.spellName[spellName]) do
+      if not frame.frames.spellID then
+        frame.frames.spellID = {}
+      end
+      if not frame.frames.spellID[spellID] then
+
+        tinsert(mod.frames.spellID[spellID], frame)
+        frame.frames.spellID[spellID] = #mod.frames.spellID[spellID]
+        print(cText('  updating'), cKey(frame.timerName))
+      end
+    end
+    mod.frames.spellName[spellName] = nil
+  end
+
+
+
+  if mod.frames.spellID[spellID] then
+    for i, timer_frame in pairs(mod.frames.spellID[spellID]) do
+      print(cText('caught spell'), cWord(spellName), 'for', timer_frame:GetName())
+      timer_frame:Event(e, unit, spellName, rank, castID, spellID)
+    end
+  end
+end
+mod.UNIT_SPELLCAST_CHANNEL_START = mod.UNIT_SPELLCAST_SUCCEEDED
+
+--- Fire a dry event to force status updates on units with changing GUID's
+function mod:PLAYER_TARGET_CHANGED(e, unit)
+  print('doing a target swap thing')
+  for k, v in pairs( self.frames.unit.target) do
+    print(k, v)
+    v:Event(nil, 'target')
+  end
+end
+
+--- Same thing but for talent/spec-driven
+function mod:PLAYER_TALENT_UPDATE(e, unit)
+  print('')
+  print('')
+  print(cText(e), T.specPage, T.specName)
+
+  local update_queue = {}
+  for _, k in ipairs({'talentID', 'talentRow', 'specPage'}) do
+    for value, frameSet in pairs(mod.frames.talentID) do
+      for id, frame in ipairs(frameSet) do
+        print(frame.timerID, frame.timerName)
+        update_queue[frame.timerID] = frame
+      end
+    end
+  end
+
+  for id, frame in pairs(update_queue) do
+    print('Refreshing spec-related frames', id, frame.timerName)
+    frame.disable = nil
+    table.wipe(frame.debug_info)
+    local res, msg = mod:EnableTimer(id, frame.dvars)
+  end
+
+end
+
+function mod:PLAYER_EQUIPMENT_CHANGED(e, slot, hasItem)
+  print(e, slot, hasItem)
+  local itemCheckList
+  if mod.frames.inventoryID and  mod.frames.inventoryID[slot] then
+    print('  Inventory Frames:')
+      itemCheckList = GetInventoryItemsForSlot(slot, {}, false)
+       for id, slotFrame in pairs(mod.frames.inventoryID[slot]) do
+        print('   * Updating', cNum(id), cWord(slotFrame.timerName))
+        local res, msg = mod:EnableTimer(slotFrame.timerID, slotFrame.dvars)
+        print('     ', cBool(res), cText(msg))
+      end
+  end
+  if itemCheckList then
+    print(unpack(itemCheckList))
+  end
+  local itemID = GetInventoryItemID('player', slot)
+  if itemID and mod.frames.itemID[itemID] then
+    print('  Item ID Frames:')
+    for id, itemFrame in pairs(mod.frames.itemID[itemID]) do
+      print('   * Updating', cNum(id), cWord(itemFrame.timerName))
+
+      local res, msg = mod:EnableTimer(itemFrame.timerID, itemFrame.dvars)
+      print('     ', cBool(res), cText(msg))
+    end
+  end
+end
+function mod:PET_BATTLE_OPENING_START ()
+  for i, v in pairs(mod.timers) do
+    if not v.disable then
+      print('suppressing', v:GetName())
+      v.disable = true
+      v:Hide()
+      pb_suppressed[i] = true
+    end
+  end
+end
+function mod:PET_BATTLE_CLOSE()
+  for id, v in pairs(mod.timers) do
+    if pb_suppressed[id] then
+      print('restoring', v:GetName())
+      mod:EnableTimer(id)
+      pb_suppressed[id] = nil
+    end
+  end
+end
\ No newline at end of file