249 lines
10 KiB
Lua
249 lines
10 KiB
Lua
-- DopingControl core/config.lua
|
|
-- SavedVariables defaults + fill-in: single DEFAULTS table, per-field fill-in
|
|
-- with nil-checks (NEVER 'or' for booleans -- would flip saved false),
|
|
-- copy-on-assign for table values so editing a live value can never mutate
|
|
-- the defaults.
|
|
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
|
|
|
DopingControl = DopingControl or {}
|
|
local DC = DopingControl
|
|
|
|
-- SavedVariables root (the client replaces this global at ADDON_LOADED;
|
|
-- core/init.lua then calls DC.EnsureDefaults(DopingControlDB)).
|
|
DopingControlDB = DopingControlDB or {}
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- Defaults
|
|
--
|
|
-- Two keys intentionally have NO entry here (a nil value cannot live in a
|
|
-- Lua table):
|
|
-- framePos -- default nil = "no saved position, use the anchor
|
|
-- default"; written by the UI on drag as
|
|
-- { point=..., relPoint=..., x=..., y=... }.
|
|
-- expectations -- default nil = "not initialized yet"; EnsureDefaults
|
|
-- special-cases it below: the saved shape
|
|
-- is PER CLASS (db.expectations[role][class][slotId]),
|
|
-- materialized/migrated from the DC.DEFAULT_EXPECT seed
|
|
-- by DC.EnsureExpectations.
|
|
-- ------------------------------------------------------------------
|
|
DC.DEFAULTS = {
|
|
testMode = false, -- /dc test: simulated raid
|
|
showMinimapButton = true,
|
|
minimapAngle = 215, -- degrees around the minimap rim
|
|
readyCheckScan = true, -- auto-scan on READY_CHECK
|
|
readyCheckOpen = true, -- auto-open window on READY_CHECK
|
|
collapse = {}, -- per-role collapsed flags, e.g. collapse.RANGED = true
|
|
filterGapsOnly = false, -- toolbar filter All | Gaps only
|
|
roles = {}, -- confirmed roles keyed by player NAME
|
|
-- Weapon skill books: db.skillBooks[playerName][weaponSkillName] =
|
|
-- true|false. A quest weapon-skill book (+3 skill) is INVISIBLE on
|
|
-- other players -- foreign weapon skill cannot be read at all (see
|
|
-- data/hit.lua) -- so it is an opt-in the user ticks per player and
|
|
-- per weapon skill; it only shifts that player's melee/ranged hit CAP.
|
|
-- Unchecking stores an explicit false (never nil), exactly like the
|
|
-- expectation grid, so "deliberately off" survives any later fill-in.
|
|
-- The own character ignores these flags: its weapon skill is measured
|
|
-- and a measured value already contains the book.
|
|
skillBooks = {},
|
|
}
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- DC.SimOwnsStore(db) -> bool
|
|
--
|
|
-- The ONE owner predicate for DC.store, and the question every scan gate
|
|
-- asks -- never db.testMode alone. Both simulated modes replace the store
|
|
-- with the simulated raid, and while either is on "real data never beats
|
|
-- the simulation":
|
|
-- * db.testMode -- /dc test, persisted (DEFAULTS above)
|
|
-- * DC.demoMode -- /dc demo, transient (core/demo.lua)
|
|
--
|
|
-- Why it exists: the gates named db.testMode only, so a finished scan
|
|
-- overwrote the sim store while demo mode was still armed. The demo
|
|
-- expectation layer then graded the REAL raid against all six school
|
|
-- protection columns (five of which ship with no seed entry at all): every
|
|
-- raider grew fabricated MISSING cells, the banner vanished with the "sim"
|
|
-- source that had been the only marker that this is a mode and not a
|
|
-- measurement, and each row's whisper button went live against a real
|
|
-- player.
|
|
--
|
|
-- Why HERE and not in core/demo.lua, where the mode lives: config.lua loads
|
|
-- second, ahead of every gate that calls this, so the gates need no
|
|
-- load-order guard -- and a demo module that fails to load can never take
|
|
-- test mode's protection down with it (DC.demoMode is then simply nil).
|
|
--
|
|
-- db is optional; the fallback is for callers that do not hold one.
|
|
-- Always returns a real boolean -- the gates invert it.
|
|
-- ------------------------------------------------------------------
|
|
function DC.SimOwnsStore(db)
|
|
db = db or DC.db or DopingControlDB
|
|
if db and db.testMode then
|
|
return true
|
|
end
|
|
if DC.demoMode then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Generic deep copy (tables only; keys are copied by reference, values
|
|
-- recursively). No cycle handling -- config/expectation shapes are trees.
|
|
function DC.DeepCopy(src)
|
|
if type(src) ~= "table" then
|
|
return src
|
|
end
|
|
local out = {}
|
|
for k, v in pairs(src) do
|
|
if type(v) == "table" then
|
|
out[k] = DC.DeepCopy(v)
|
|
else
|
|
out[k] = v
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
-- ------------------------------------------------------------------
|
|
-- Expectations: the saved shape is
|
|
-- PER CLASS --
|
|
-- db.expectations[role][class][slotId] = true|nil
|
|
-- with one class line for every class that can perform the role
|
|
-- (DC_Roles.ROLE_CLASSES, the ordered inversion of CLASS_ROLES).
|
|
-- DC.DEFAULT_EXPECT (data/expectations.lua) STAYS the role x slot SEED.
|
|
--
|
|
-- EnsureExpectations guarantees the full structure:
|
|
-- * first load (expectations == nil): every (role, performing class)
|
|
-- gets a deep copy of the seed's role line.
|
|
-- * legacy saved tables (one line per role) -- detected by BOOLEANS directly under
|
|
-- [role] (new-shape values are class tables) -- are MIGRATED: the
|
|
-- old role line is copied to every performing class, then the
|
|
-- structure is replaced. User edits survive on every class where
|
|
-- they are representable.
|
|
-- * new-shape tables: existing class lines are NEVER touched; missing
|
|
-- (role,class) combos are filled from the seed. The fill is
|
|
-- behavior-neutral -- the model resolves a missing combo to the
|
|
-- DC.DEFAULT_EXPECT role line anyway (runtime fallback) -- it only
|
|
-- hands the options grid a complete structure.
|
|
-- Known ambiguity (accepted): an old-shape role line with EVERY slot
|
|
-- unchecked is {} and indistinguishable from an untouched new-shape
|
|
-- role table (the boolean marker is gone); it is re-seeded per class.
|
|
--
|
|
-- Resolved at CALL time, not file-load time: data/expectations.lua and
|
|
-- core/roles.lua load after this file (TOC order const -> config ->
|
|
-- data/* -> model -> roles), but EnsureDefaults only runs at
|
|
-- ADDON_LOADED when everything is in. Offline callers without DC_Roles
|
|
-- loaded simply skip the expectations block.
|
|
-- ------------------------------------------------------------------
|
|
local function isOldShapeLine(line)
|
|
for _, v in pairs(line) do
|
|
if type(v) == "boolean" then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Top up ONE class line in place: slots the seed marks true and the line
|
|
-- has NEVER SEEN (nil) become true. Deliberate unchecks survive (the
|
|
-- options grid stores those as explicit false, never nil). Shared by both
|
|
-- EnsureExpectations branches below so a freshly migrated line is already
|
|
-- complete -- not just on the SECOND call (idempotency: a migration must
|
|
-- converge in one pass, not two).
|
|
local function topUpLine(cl, seedLine)
|
|
for slotId, v in pairs(seedLine) do
|
|
if v == true and cl[slotId] == nil then
|
|
cl[slotId] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
function DC.EnsureExpectations(db)
|
|
if DC.DEFAULT_EXPECT == nil or DC_Roles == nil
|
|
or DC_Roles.ROLE_CLASSES == nil then
|
|
return
|
|
end
|
|
if db.expectations == nil then
|
|
db.expectations = {}
|
|
end
|
|
local exp = db.expectations
|
|
local nRoles = table.getn(DC.ROLES)
|
|
for r = 1, nRoles do
|
|
local role = DC.ROLES[r]
|
|
local classes = DC_Roles.ROLE_CLASSES[role] or {}
|
|
local nClasses = table.getn(classes)
|
|
local line = exp[role]
|
|
local seedLine = DC.DEFAULT_EXPECT[role] or {}
|
|
if line ~= nil and isOldShapeLine(line) then
|
|
-- migration: copy the old role line to every performing
|
|
-- class, then top up each new class line from the CURRENT
|
|
-- seed (a slot added since the old line was saved must reach
|
|
-- it immediately, not only on a later call -- see topUpLine),
|
|
-- then replace the structure (never lose user edits where
|
|
-- representable)
|
|
local byClass = {}
|
|
for c = 1, nClasses do
|
|
local cl = DC.DeepCopy(line)
|
|
topUpLine(cl, seedLine)
|
|
byClass[classes[c]] = cl
|
|
end
|
|
exp[role] = byClass
|
|
else
|
|
if line == nil then
|
|
line = {}
|
|
exp[role] = line
|
|
end
|
|
-- fill missing (role,class) combos from the seed; existing
|
|
-- class lines keep their values, but slots the seed knows
|
|
-- and the line has NEVER SEEN (nil) are topped up -- without
|
|
-- this, a slot added in a later version never reaches saved
|
|
-- lines (field report: neck/ring columns arrived as "not
|
|
-- expected" for everyone). Deliberate unchecks survive: the
|
|
-- options grid stores them as explicit false, never nil.
|
|
for c = 1, nClasses do
|
|
local cls = classes[c]
|
|
if line[cls] == nil then
|
|
line[cls] = DC.DeepCopy(seedLine)
|
|
else
|
|
topUpLine(line[cls], seedLine)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fill missing fields in db from DC.DEFAULTS. Existing values -- including
|
|
-- saved booleans that are false -- are never touched. Table defaults are
|
|
-- deep-copied on assign.
|
|
function DC.EnsureDefaults(db)
|
|
for k, v in pairs(DC.DEFAULTS) do
|
|
if db[k] == nil then
|
|
if type(v) == "table" then
|
|
db[k] = DC.DeepCopy(v)
|
|
else
|
|
db[k] = v
|
|
end
|
|
end
|
|
end
|
|
-- expectations: per-(role,class) shape, materialized/migrated from
|
|
-- the DC.DEFAULT_EXPECT seed (see EnsureExpectations above)
|
|
DC.EnsureExpectations(db)
|
|
return db
|
|
end
|
|
|
|
-- Reset the whole config IN PLACE (keeps the table reference -- the client
|
|
-- saves the global DopingControlDB, so the reference must survive).
|
|
function DC.ResetConfig(db)
|
|
for k in pairs(db) do
|
|
db[k] = nil
|
|
end
|
|
return DC.EnsureDefaults(db)
|
|
end
|
|
|
|
-- Reset only the expectation matrix to the DC.DEFAULT_EXPECT seed,
|
|
-- materialized per (role, performing class) -- options button
|
|
-- "Reset expectations" (reseeds ALL classes).
|
|
function DC.ResetExpectations(db)
|
|
db.expectations = nil
|
|
DC.EnsureDefaults(db)
|
|
return db.expectations
|
|
end
|