Mercurial > wow > turok
view Turok/Modules/Timer/Timer.lua @ 9:9400a0ff8540
Ugh
Timer:
- container update directionality
- talent update iterates over a non-volatile table to carry out updates
- index management steps organized
- talentRow status implemented, returns the spell associated with the talent chosen from that row
CombatLog:
- sort out font controls and unbork arguments
author | Nenue |
---|---|
date | Sun, 21 Feb 2016 13:08:30 -0500 |
parents | a9b8b0866ece |
children |
line wrap: on
line source
--- 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, rawset = _A.Addon, _A.LibFog, tostring, type, max, table.insert, unpack, _G.UIParent, loadstring, rawset local mod = T.modules.TimerControl local P = mod.prototype local db local pairs, ipairs, gsub, sub, setmetatable, wipe = pairs, ipairs, string.gsub, string.sub, setmetatable, wipe 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 teprint = function(...) if not DEBUG then return end if _G.Devian and _G.DevianDB.workspace ~= 1 then _G.print('TimerEvent',...) end end local tfprint = function(...) if not DEBUG then return end if _G.Devian and _G.DevianDB.workspace ~= 1 then _G.print('TimerFocus',...) end end local Timer_GetPrintHandler = function(self) if self.trace then return function(...) print(...) tfprint(...) 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() teprint('INIT TIMERS ====================') for id, spirit in pairs(mod.timers) do if spirit.disable then teprint(id, 'disabled:', tconcat(spirit.debug_info or {}, ', ')) else teprint(cText('init'), cNum(id), cWord(spirit.name)) --- Throw a dry event to initialize values teprint(cText(' *'), cWord('prototype.'..cKey(spirit.dvars.type)..'.'..cWord('Load'))) P.trigger[spirit.dvars.type].Event(spirit) --- Set loose teprint(cText(' *'), cWord('prototype')..'.'..cKey('events')..'.'..cWord('Load')) mod.UpdateEvents(spirit, P.trigger[spirit.dvars.type].events) end end teprint('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 (math.floor(t.valueFull/60) .. ':' .. ((t.value %% 60) < 10 and '0' or '').. (t.value %% 60)) or ((t.valueFull < 6) and (t.value .. '.' .. math.floor((t.valueFull * 10) %% 10)) or t.value)) .. '"}, {'%%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 local update_queue = {} function mod.ResetTimers(heading) print(cText('*** Flushing update queue for'), cWord(heading)) for id, frame in pairs(update_queue) do print(' ', cNum(id), cKey(frame.timerName)) frame.disable = nil wipe(frame.debug_info) local res, msg = mod:EnableTimer(id, frame.dvars) end wipe(update_queue) end function mod:PLAYER_TALENT_UPDATE(e, unit) print('') print('') print(cText(e), T.specPage, T.specName) 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 mod.resetTimers('Talent') end function mod:PLAYER_EQUIPMENT_CHANGED(e, slot, hasItem) print(e, slot, hasItem) if mod.frames.inventoryID and mod.frames.inventoryID[slot] then print(' Inventory slot:', cNum(slot)) for i, slotFrame in ipairs(mod.frames.inventoryID[slot]) do print(' ', cNum(i), cText(slotFrame.timerName)) update_queue[slotFrame.timerID] = slotFrame if mod.frames.itemID then local itemsForSlot = GetInventoryItemsForSlot(slot, {}, false) for _, itemID in pairs(itemsForSlot) do if mod.frames.itemID[itemID] then print(' Frames for equippable item:', cNum(itemID)) for j, itemFrame in ipairs(mod.frames.itemID[itemID]) do print(' ', cNum(j), cText(itemFrame.timerName)) update_queue[itemFrame.timerID] = itemFrame end end end end end end mod.ResetTimers('Equipment') 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