-- 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