DopingControl v0.6.1
This commit is contained in:
+240
@@ -0,0 +1,240 @@
|
||||
-- DopingControl core/init.lua
|
||||
-- Bootstrap: ADDON_LOADED wiring (SavedVariables defaults), event
|
||||
-- registration (roster changes, READY_CHECK), test-mode switch, slash
|
||||
-- commands /dc + /doping. Loads LAST in the TOC -- every module global
|
||||
-- (DC_Scan, DC_Matrix, DC_Options, DC_Report, DC_Sim, DC_Probe) exists by
|
||||
-- the time any function here runs.
|
||||
--
|
||||
-- Pure-Lua 5.0 dofile-loadable: all WoW wiring behind "if CreateFrame then".
|
||||
-- The pure parts (DC.HandleSlash, DC.SetTestMode, DC.DumpUnknown) are
|
||||
-- offline-tested with stubbed modules.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
-- Store (the shared data shape every consumer reads). Empty until the
|
||||
-- first scan finishes or test mode injects the simulated store.
|
||||
DC.store = DC.store or { players = {}, source = "scan" }
|
||||
|
||||
-- Unknown-aura tally, filled by scan/aura.lua (DC.unknownSeen[name] = count);
|
||||
-- read here by /dc unknown (unknown buff names are never silently discarded).
|
||||
DC.unknownSeen = DC.unknownSeen or {}
|
||||
|
||||
local CHAT_PREFIX = "|cff3fae5cDopingControl|r: "
|
||||
|
||||
function DC.Print(msg)
|
||||
if DEFAULT_CHAT_FRAME then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(CHAT_PREFIX .. tostring(msg))
|
||||
else
|
||||
print(CHAT_PREFIX .. tostring(msg)) -- offline Lua 5.0
|
||||
end
|
||||
end
|
||||
|
||||
-- Called by scan/engine.lua whenever the store changed (scan finished).
|
||||
function DC.OnStoreUpdated()
|
||||
DC_Matrix.Refresh()
|
||||
end
|
||||
|
||||
-- Matrix visibility, used to decide whether a roster change should trigger
|
||||
-- an immediate rescan. DC_Matrix.IsShown is not pinned in the plan -- if
|
||||
-- ui/matrix.lua does not provide it, we conservatively treat the window as
|
||||
-- hidden (next manual scan / ready check rescans anyway).
|
||||
local function MatrixIsShown()
|
||||
if DC_Matrix and DC_Matrix.IsShown then
|
||||
return DC_Matrix.IsShown()
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Test mode: the simulator fully replaces the data source.
|
||||
-- ------------------------------------------------------------------
|
||||
function DC.SetTestMode(v)
|
||||
local db = DC.db or DopingControlDB
|
||||
db.testMode = v
|
||||
if v then
|
||||
-- buildStore() itself resets and refills DC.simRoles with the
|
||||
-- built-in confirmations (DC_Sim.ROLES, core/sim.lua) -- do NOT wipe
|
||||
-- DC.simRoles afterwards, that would erase them and turn every row
|
||||
-- into a suggestion badge (the sim roster deliberately contains
|
||||
-- exactly two suggestion-badge cases).
|
||||
DC.store = DC_Sim.buildStore()
|
||||
else
|
||||
DC.store = { players = {}, source = "scan" }
|
||||
DC_Scan.Start(true) -- leaving test mode: immediate real rescan
|
||||
end
|
||||
DC_Matrix.Refresh()
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- /dc unknown -- dump DC.unknownSeen via SuperWoW ExportFile.
|
||||
-- ------------------------------------------------------------------
|
||||
function DC.DumpUnknown()
|
||||
local names = {}
|
||||
local distinct = 0
|
||||
for name in pairs(DC.unknownSeen) do
|
||||
table.insert(names, name)
|
||||
distinct = distinct + 1
|
||||
end
|
||||
if distinct == 0 then
|
||||
DC.Print("no unknown buffs recorded")
|
||||
return
|
||||
end
|
||||
table.sort(names, function(a, b)
|
||||
local ca, cb = DC.unknownSeen[a], DC.unknownSeen[b]
|
||||
if ca ~= cb then return ca > cb end
|
||||
return a < b
|
||||
end)
|
||||
local lines = { "DopingControl unknown buffs (" .. distinct .. " names)" }
|
||||
for i = 1, table.getn(names) do
|
||||
table.insert(lines, string.format("%dx %s", DC.unknownSeen[names[i]], names[i]))
|
||||
end
|
||||
local text = table.concat(lines, "\n")
|
||||
if ExportFile then
|
||||
-- SuperWoW appends ".txt" itself -- pass the name WITHOUT extension.
|
||||
-- pcall: optional-DLL call -- an I/O error must not
|
||||
-- escape into the slash handler.
|
||||
local okE = pcall(ExportFile, "dc_unknown", text)
|
||||
if okE then
|
||||
DC.Print(distinct .. " unknown buff names dumped to dc_unknown.txt (WoW Imports folder)")
|
||||
else
|
||||
DC.Print("ExportFile failed - no dump written")
|
||||
end
|
||||
else
|
||||
DC.Print("ExportFile unavailable (SuperWoW required) - no dump written")
|
||||
end
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Slash commands: /dc, /doping
|
||||
-- ------------------------------------------------------------------
|
||||
local HELP_TEXT = "commands: /dc (window) | scan | options | report | test | probe [range <name>] | unknown | talents [on|off|clear]"
|
||||
|
||||
function DC.HandleSlash(msg)
|
||||
msg = msg or ""
|
||||
local _, _, word, rest = string.find(msg, "^%s*(%S*)%s*(.-)%s*$")
|
||||
local cmd = string.lower(word or "")
|
||||
local db = DC.db or DopingControlDB
|
||||
|
||||
if cmd == "" then
|
||||
DC_Matrix.Toggle()
|
||||
elseif cmd == "scan" then
|
||||
if db and db.testMode then
|
||||
-- real data never beats the simulation while test mode is on
|
||||
DC.Print("test mode is on - /dc test to switch back before scanning")
|
||||
else
|
||||
DC_Scan.Start()
|
||||
end
|
||||
elseif cmd == "options" then
|
||||
DC_Options.Toggle()
|
||||
elseif cmd == "report" then
|
||||
DC_Report.Open()
|
||||
elseif cmd == "test" then
|
||||
DC.SetTestMode(not (db and db.testMode))
|
||||
if db and db.testMode then
|
||||
DC.Print("test mode ON - simulated raid, chat output disabled")
|
||||
else
|
||||
DC.Print("test mode OFF - rescanning the real group")
|
||||
end
|
||||
elseif cmd == "probe" then
|
||||
-- core/probe.lua owns the probe subcommand grammar:
|
||||
-- DC_Probe.HandleSlash(rest) handles "" (battery),
|
||||
-- "range <name>" (1 Hz run) and the range-run stop toggle.
|
||||
DC_Probe.HandleSlash(rest)
|
||||
elseif cmd == "unknown" then
|
||||
DC.DumpUnknown()
|
||||
elseif cmd == "talents" then
|
||||
-- foreign talents come in over this server's inspect protocol
|
||||
-- (scan/talents.lua). "on"/"off" is a real switch, not cosmetic:
|
||||
-- asking makes OTHER players' clients send their tree back, so it
|
||||
-- must be possible to stop doing that.
|
||||
if not DC_Talents then
|
||||
DC.Print("talent module not loaded")
|
||||
elseif rest == "on" or rest == "off" then
|
||||
DopingControlDB.readTalents = (rest == "on")
|
||||
DC.Print("talent reading " .. rest)
|
||||
if rest == "off" and DC_Talents.Clear then
|
||||
DC_Talents.Clear()
|
||||
end
|
||||
elseif rest == "clear" then
|
||||
DC_Talents.Clear()
|
||||
DC.Print("talent cache cleared - they will be asked again")
|
||||
else
|
||||
local s = DC_Talents.Stats()
|
||||
DC.Print("talents: " .. s.known .. " known, " .. s.pending
|
||||
.. " mid-reply, " .. s.queued .. " queued, "
|
||||
.. (s.enabled and "ON" or "OFF")
|
||||
.. " (/dc talents on|off|clear)")
|
||||
end
|
||||
else
|
||||
DC.Print(HELP_TEXT)
|
||||
end
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- WoW wiring
|
||||
-- ------------------------------------------------------------------
|
||||
if CreateFrame then
|
||||
SLASH_DOPINGCONTROL1 = "/dc"
|
||||
SLASH_DOPINGCONTROL2 = "/doping"
|
||||
SlashCmdList["DOPINGCONTROL"] = DC.HandleSlash
|
||||
|
||||
-- Matrix toolbar hooks (ui/matrix.lua fires MX.hooks.*): the Options
|
||||
-- and Report buttons fall back to DC_Options/DC_Report directly, but
|
||||
-- the Scan button ONLY fires MX.hooks.scan -- wire it here through the
|
||||
-- slash path so it gets the same test-mode gate as "/dc scan".
|
||||
if DC_Matrix and DC_Matrix.hooks then
|
||||
DC_Matrix.hooks.scan = function()
|
||||
DC.HandleSlash("scan")
|
||||
end
|
||||
end
|
||||
|
||||
local ev = CreateFrame("Frame", "DopingControlInitEventFrame", UIParent)
|
||||
ev:RegisterEvent("ADDON_LOADED")
|
||||
ev:SetScript("OnEvent", function()
|
||||
if event == "ADDON_LOADED" then
|
||||
if arg1 ~= "DopingControl" then return end
|
||||
ev:UnregisterEvent("ADDON_LOADED")
|
||||
|
||||
-- SavedVariables are in place now.
|
||||
DopingControlDB = DopingControlDB or {}
|
||||
DC.EnsureDefaults(DopingControlDB) -- also deep-copies
|
||||
-- DC.DEFAULT_EXPECT into db.expectations on first load
|
||||
-- (core/config.lua handles that inside EnsureDefaults).
|
||||
DC.db = DopingControlDB
|
||||
|
||||
-- Roster changes invalidate the store; rescan only when the
|
||||
-- window is up (otherwise the next scan re-reads the roster
|
||||
-- anyway -- scan/engine.lua re-walks it on every Start()).
|
||||
ev:RegisterEvent("RAID_ROSTER_UPDATE")
|
||||
ev:RegisterEvent("PARTY_MEMBERS_CHANGED")
|
||||
|
||||
-- READY_CHECK: registered defensively -- it is unverified
|
||||
-- whether this event fires natively on 1.12/TurtleWoW, so a
|
||||
-- rejecting client must not break the bootstrap.
|
||||
pcall(ev.RegisterEvent, ev, "READY_CHECK")
|
||||
|
||||
-- Persisted test mode survives reloads: restore the simulated
|
||||
-- store so window content and db.testMode stay consistent.
|
||||
if DC.db.testMode then
|
||||
DC.store = DC_Sim.buildStore()
|
||||
end
|
||||
|
||||
elseif event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then
|
||||
local db = DC.db
|
||||
if db and not db.testMode and MatrixIsShown() then
|
||||
DC_Scan.Start()
|
||||
end
|
||||
|
||||
elseif event == "READY_CHECK" then
|
||||
-- The auto-SCAN on ready check is owned by scan/engine.lua
|
||||
-- (single owner) -- registering the Start
|
||||
-- here too fired a duplicate full scan per ready check. This
|
||||
-- handler only does the auto-OPEN half.
|
||||
local db = DC.db
|
||||
if db and db.readyCheckOpen then
|
||||
DC_Matrix.Show()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user