mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-16 08:11:35 +02:00
feat(mods): support conditional option rows
This commit is contained in:
@@ -46,7 +46,11 @@ when the runtime schema is absent. The supported row types are `toggle`,
|
||||
`choice`, `number`, and `text`. Their optional fields retain the meanings
|
||||
established by the existing in-game option UI: choices are `[label, value]`
|
||||
pairs, numeric rows may provide `min`, `max`, and `step`, and text rows may
|
||||
provide `maxLen`.
|
||||
provide `maxLen`. A row may also use
|
||||
`visible_if = {key = "mode", equals = "compact"}` or replace `equals` with
|
||||
`not_equals`. This only hides the in-game menu row; the schema and stored value
|
||||
remain available, and consumers that do not implement conditions may ignore
|
||||
the field.
|
||||
|
||||
Only mods that are enabled and successfully loaded in the current boot are
|
||||
included. A disabled or failed mod must not contribute rows. If an older
|
||||
@@ -69,9 +73,10 @@ it may ignore an unknown row type or optional field.
|
||||
For compatibility with files produced by the original unversioned prototype,
|
||||
a missing `schema_version` means version 1. Consumers must ignore documents
|
||||
with a newer version rather than guessing at their shape. Producers must bump
|
||||
the version whenever they change the document shape or the meaning of an
|
||||
existing field. Version 1 is therefore the legacy unversioned format as well
|
||||
as the explicitly versioned format shown above.
|
||||
the version whenever they change the document envelope or the meaning of an
|
||||
existing field. New optional row fields that older consumers can safely ignore
|
||||
do not require a bump. Version 1 is therefore the legacy unversioned format as
|
||||
well as the explicitly versioned format shown above.
|
||||
|
||||
## Migration note
|
||||
|
||||
|
||||
@@ -986,12 +986,45 @@ end
|
||||
function ManagerState:buildOptionRows(m, schema)
|
||||
local rows = {}
|
||||
local modId = m.id
|
||||
local byKey, visibilityKeys = {}, {}
|
||||
for _, row in ipairs(schema) do
|
||||
if type(row) == "table" and type(row.key) == "string" then
|
||||
byKey[row.key] = row
|
||||
local condition = row.visible_if
|
||||
if type(condition) == "table" and type(condition.key) == "string" then
|
||||
visibilityKeys[condition.key] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
local function visible(row)
|
||||
local condition = row.visible_if
|
||||
if condition == nil then return true end
|
||||
if type(condition) ~= "table" or type(condition.key) ~= "string" then
|
||||
return false
|
||||
end
|
||||
local dependency = byKey[condition.key] or { key = condition.key }
|
||||
local value = self:optionValue(modId, dependency)
|
||||
if condition.equals ~= nil then return value == condition.equals end
|
||||
if condition.not_equals ~= nil then return value ~= condition.not_equals end
|
||||
return false
|
||||
end
|
||||
local function refresh(key)
|
||||
if not visibilityKeys[key] then return end
|
||||
local preferred = rows[self.cursor] and rows[self.cursor].id
|
||||
self.optionRows = self:buildOptionRows(m, schema)
|
||||
for index, candidate in ipairs(self.optionRows) do
|
||||
if candidate.id == preferred then self.cursor = index break end
|
||||
end
|
||||
self.cursor = clampIndex(self.cursor, #self.optionRows)
|
||||
end
|
||||
for _, row in ipairs(schema) do
|
||||
if type(row) ~= "table" or type(row.key) ~= "string" or row.key == ""
|
||||
or not OPTION_TYPES[row.type] then
|
||||
-- malformed rows are skipped, reported where the errors screen reads
|
||||
Runtime.reportError(modId, "options row skipped: "
|
||||
.. tostring(type(row) == "table" and (row.key or row.type) or row))
|
||||
elseif not visible(row) then
|
||||
-- Keep the row in the schema and stored options, only hide its menu row.
|
||||
elseif row.type == "toggle" then
|
||||
rows[#rows + 1] = { id = row.key, label = row.label or row.key,
|
||||
value = function()
|
||||
@@ -999,6 +1032,7 @@ function ManagerState:buildOptionRows(m, schema)
|
||||
end,
|
||||
step = function()
|
||||
self:setOption(modId, row.key, not self:optionValue(modId, row))
|
||||
refresh(row.key)
|
||||
return true
|
||||
end }
|
||||
elseif row.type == "choice" then
|
||||
@@ -1021,6 +1055,7 @@ function ManagerState:buildOptionRows(m, schema)
|
||||
end
|
||||
index = clampIndex(index + dir, #choices)
|
||||
self:setOption(modId, row.key, choices[index][2])
|
||||
refresh(row.key)
|
||||
return true
|
||||
end }
|
||||
elseif row.type == "number" then
|
||||
@@ -1036,6 +1071,7 @@ function ManagerState:buildOptionRows(m, schema)
|
||||
step = function(_, dir)
|
||||
local cur = tonumber(self:optionValue(modId, row)) or 0
|
||||
self:setOption(modId, row.key, clamp(cur + dir * (row.step or 1)))
|
||||
refresh(row.key)
|
||||
return true
|
||||
end,
|
||||
activate = function()
|
||||
@@ -1044,7 +1080,10 @@ function ManagerState:buildOptionRows(m, schema)
|
||||
max = row.max or 99,
|
||||
start = math.max(1, tonumber(self:optionValue(modId, row)) or 1),
|
||||
onDone = function(qty)
|
||||
if qty then self:setOption(modId, row.key, clamp(qty)) end
|
||||
if qty then
|
||||
self:setOption(modId, row.key, clamp(qty))
|
||||
refresh(row.key)
|
||||
end
|
||||
end,
|
||||
}))
|
||||
end }
|
||||
@@ -1061,6 +1100,7 @@ function ManagerState:buildOptionRows(m, schema)
|
||||
default = self:optionValue(modId, row),
|
||||
onDone = function(name)
|
||||
self:setOption(modId, row.key, name)
|
||||
refresh(row.key)
|
||||
end,
|
||||
}))
|
||||
end }
|
||||
@@ -1075,6 +1115,8 @@ function ManagerState:buildOptionRows(m, schema)
|
||||
self:setOption(modId, row.key, row.default)
|
||||
end
|
||||
end
|
||||
self.optionRows = self:buildOptionRows(m, schema)
|
||||
self.cursor = clampIndex(self.cursor, #self.optionRows)
|
||||
self:notify("DEFAULTS RESTORED")
|
||||
end }
|
||||
return rows
|
||||
|
||||
@@ -1106,6 +1106,27 @@ check(loader.modOptions.okmod.hardcore == false
|
||||
and loader.modOptions.okmod.startMoney == 3000
|
||||
and loader.modOptions.okmod.tag == "BLUE",
|
||||
"RESET DEFAULTS restores every schema default")
|
||||
|
||||
-- optional conditions keep mode-specific rows compact and refresh in place
|
||||
local conditionalSchema = {
|
||||
{ key = "mode", label = "MODE", type = "choice", default = "one",
|
||||
choices = { { "ONE", "one" }, { "TWO", "two" } } },
|
||||
{ key = "oneOnly", label = "ONE ONLY", type = "toggle", default = false,
|
||||
visible_if = { key = "mode", equals = "one" } },
|
||||
{ key = "twoOnly", label = "TWO ONLY", type = "toggle", default = false,
|
||||
visible_if = { key = "mode", equals = "two" } },
|
||||
{ key = "notOne", label = "NOT ONE", type = "toggle", default = false,
|
||||
visible_if = { key = "mode", not_equals = "one" } },
|
||||
}
|
||||
loader.modOptions.condmod = {}
|
||||
ms.cursor = 1
|
||||
ms.optionRows = ms:buildOptionRows({ id = "condmod" }, conditionalSchema)
|
||||
check(#ms.optionRows == 3 and ms.optionRows[2].id == "oneOnly",
|
||||
"visible_if uses the controlling row default")
|
||||
ms.optionRows[1].step(mgame, 1)
|
||||
check(#ms.optionRows == 4 and ms.optionRows[2].id == "twoOnly"
|
||||
and ms.optionRows[3].id == "notOne" and ms.cursor == 1,
|
||||
"editing a controller refreshes conditions without moving the cursor")
|
||||
press(ms, "b")
|
||||
check(ms.screen == "list", "B leaves the options screen")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user