Files
2026-08-30 15:37:17 +02:00

155 lines
6.7 KiB
Lua

-- DopingControl core/demo.lua
-- Demo mode: the showcase view. It reuses test mode's simulated raid
-- (DC_Sim.buildStore -- no second roster exists) and lays its OWN
-- expectation table on top so ALL SIX school protection columns
-- (FR/FrR/NR/SR/HR/AR) are visible at once.
--
-- Two hard rules, both of them the reason this file exists at all:
--
-- 1. The demo layer NEVER writes into db.expectations. Live, five of the
-- six school columns ship with no seed entry (data/expectations.lua)
-- and are therefore hidden -- that is a deliberate default, and a
-- screenshot mode must not rewrite the user's saved matrix to get its
-- picture. BuildDemoExpectations deep-copies every line it starts
-- from, so no table is ever shared with the DB either.
-- 2. Demo state is TRANSIENT -- DC.demoMode / DC.demoExpectations live in
-- memory only and are gone after a reload. Deliberately unlike
-- db.testMode (which is persisted): the same treatment DC.simRoles
-- gets in core/roles.lua, and the BulwarkFrame /bf demo pattern.
--
-- Why the columns must be forced per (role, class) and not per role: the
-- model resolves a row through expect[role][class] and falls back to the
-- DC.DEFAULT_EXPECT ROLE line whenever that class line is missing
-- (core/model.lua, expectLine) -- a partially filled demo table would fall
-- back into the seed and silently lose the school columns again. The same
-- fallback drives the dynamic-column check (anyClassExpects), so an
-- incomplete layer would also drop the columns from the header.
--
-- FR is forced too, although the seed already ships it true for all five
-- roles: the user may have unchecked it, and "all six schools" is the
-- point of the mode.
--
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
DopingControl = DopingControl or {}
local DC = DopingControl
-- ------------------------------------------------------------------
-- Transient state (NEVER SavedVariables -- see rule 2 above).
-- ------------------------------------------------------------------
DC.demoMode = false
DC.demoExpectations = nil
-- DC.demoMode is the demo half of DC.SimOwnsStore (core/config.lua) -- the
-- predicate every scan gate asks. It deliberately lives in config.lua, not
-- here: config.lua loads second, ahead of every gate, so a broken demo
-- module can never take TEST mode's protection down with it.
-- The six school protection columns of the consumables slot table
-- (data/consumables.lua). FR is the only one with a seed entry.
DC.DEMO_SCHOOL_SLOTS = { "FR", "FrR", "NR", "SR", "HR", "AR" }
local function forceSchools(line)
local n = table.getn(DC.DEMO_SCHOOL_SLOTS)
for i = 1, n do
line[DC.DEMO_SCHOOL_SLOTS[i]] = true
end
end
-- ------------------------------------------------------------------
-- DC.BuildDemoExpectations(db) -> demo table (also cached in
-- DC.demoExpectations)
--
-- Shape is exactly the live one -- demo[role][class][slotId] -- built per
-- (role, performing class) over DC_Roles.ROLE_CLASSES, so it can be handed
-- to DC_Model.build in place of db.expectations without any special case
-- in the model.
--
-- Source per class line, in order: the user's saved line, else the
-- DC.DEFAULT_EXPECT seed line -- deep-copied either way, then the six
-- school slots forced on. A legacy old-shape role line (booleans directly
-- under [role], migrated away by DC.EnsureExpectations at ADDON_LOADED)
-- is not a table per class and therefore falls back to the seed; demo mode
-- showing seed defaults for one session is acceptable, writing into the DB
-- to avoid it is not.
-- ------------------------------------------------------------------
function DC.BuildDemoExpectations(db)
local src = db and db.expectations
local out = {}
local nRoles = table.getn(DC.ROLES)
for r = 1, nRoles do
local role = DC.ROLES[r]
local seedLine = (DC.DEFAULT_EXPECT and DC.DEFAULT_EXPECT[role]) or {}
local savedByClass = src and src[role]
local classes = (DC_Roles and DC_Roles.ROLE_CLASSES
and DC_Roles.ROLE_CLASSES[role]) or {}
local nClasses = table.getn(classes)
if nClasses > 0 then
local byClass = {}
for c = 1, nClasses do
local cls = classes[c]
local saved = savedByClass and savedByClass[cls]
local line
if type(saved) == "table" then
line = DC.DeepCopy(saved)
else
line = DC.DeepCopy(seedLine)
end
forceSchools(line)
byClass[cls] = line
end
out[role] = byClass
else
-- defensive: without DC_Roles there is no class domain to fill,
-- so emit the legacy role x slot line (the model falls back to
-- the seed for the cells, but the header tooltip and any
-- legacy reader still see the schools)
local line = DC.DeepCopy(seedLine)
forceSchools(line)
out[role] = line
end
end
DC.demoExpectations = out
return out
end
-- ------------------------------------------------------------------
-- DC.SetDemoMode(v) -- the switch behind /dc demo and the options button.
-- Mirrors DC.SetTestMode (core/init.lua) but touches no SavedVariables.
--
-- Store handling: entering demo mode swaps in the simulated raid. Leaving
-- it hands the store back to the REAL scan -- unless db.testMode is on,
-- which owns the simulated store on its own account and must not be kicked
-- out of it. DC.SetTestMode mirrors that in the other direction, so neither
-- mode can empty the other's showcase; both ask DC.SimOwnsStore
-- (core/config.lua), which is also what every scan gate now asks instead
-- of db.testMode.
-- ------------------------------------------------------------------
function DC.SetDemoMode(v)
local db = DC.db or DopingControlDB
if v then
DC.demoMode = true
DC.BuildDemoExpectations(db)
if DC_Sim and DC_Sim.buildStore then
-- buildStore() also refills the transient DC.simRoles -- do NOT
-- wipe them afterwards (see DC.SetTestMode)
DC.store = DC_Sim.buildStore()
end
else
DC.demoMode = false
DC.demoExpectations = nil
if not (db and db.testMode) then
DC.store = { players = {}, source = "scan" }
if DC_Scan and DC_Scan.Start then
DC_Scan.Start(true) -- leaving demo mode: immediate rescan
end
end
end
if DC_Matrix and DC_Matrix.Refresh then
DC_Matrix.Refresh()
end
if DC_Options and DC_Options.Refresh then
DC_Options.Refresh() -- relabels the options button when it is open
end
end