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