mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
G2 support
This commit is contained in:
+144
-1
@@ -295,6 +295,15 @@ function SaveData.defaultOptions()
|
||||
-- Native mod enablement is an installation option, not save-slot data.
|
||||
-- Missing entries mean enabled so newly installed mods work by default.
|
||||
mods = {},
|
||||
-- Mods the player forced past the target gate (Loader:_gateGeneration).
|
||||
-- modsGen2[id][version] = true, one answer per game; a bare `true` is the
|
||||
-- pre-per-game shape and means the Gen 2 games only (see modForced).
|
||||
modsGen2 = {},
|
||||
-- Per-game enablement: modsByVersion[version][id] answers for that game
|
||||
-- only and falls through to the shared mods[id] above when absent, so an
|
||||
-- options.lua written before this key keeps its exact meaning. Read and
|
||||
-- written through SaveData.modEnabled / SaveData.setModEnabled.
|
||||
modsByVersion = {},
|
||||
-- Named setups the player can switch between (#593; src/mods/ModProfile.lua
|
||||
-- owns the shape, src/mods/ManagerState.lua the UI): each row is
|
||||
-- { name, enabled = {id=bool}, options = {id={k=v}}, slots = {version=slotId} }.
|
||||
@@ -541,6 +550,127 @@ function SaveData.loadOptions(fs)
|
||||
return SaveData.mergeOptions(data)
|
||||
end
|
||||
|
||||
-- ------- per-game mod enablement
|
||||
--
|
||||
-- One installed mod, one id, one enable flag per game that wants to differ.
|
||||
-- options.mods is the shared answer every version used to get; the overlay
|
||||
-- only holds the games the player actually chose for, so a mod set can differ
|
||||
-- between Red and Gold without either one owning the other's flags.
|
||||
|
||||
-- Whether a per-game answer is honoured at boot. The loader reads the enable
|
||||
-- flags once, before any entry chunk (src/mods/Loader.lua _loadState), so this
|
||||
-- flips on with that read and not before: until then every writer keeps to the
|
||||
-- shared flag and no surface promises what the boot does not do.
|
||||
SaveData.PER_VERSION_MODS = false
|
||||
|
||||
-- The version a write should be scoped to: the game asked for once per-game
|
||||
-- flags are live, nil (the shared flag) while they are only a preview.
|
||||
function SaveData.modScope(version)
|
||||
if SaveData.PER_VERSION_MODS then return version end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- true/false as chosen for `version`, else the shared flag, else nil -- the
|
||||
-- caller owns the default (the loader enables, the launcher keeps
|
||||
-- experimental mods off until asked).
|
||||
function SaveData.modEnabled(options, id, version)
|
||||
local byVersion = options and options.modsByVersion
|
||||
local bucket = version and type(byVersion) == "table" and byVersion[version]
|
||||
if type(bucket) == "table" and type(bucket[id]) == "boolean" then
|
||||
return bucket[id]
|
||||
end
|
||||
local shared = options and options.mods
|
||||
if type(shared) == "table" and type(shared[id]) == "boolean" then
|
||||
return shared[id]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Write the choice for one game, or the shared flag when version is nil. A
|
||||
-- per-game entry that agrees with the shared flag is dropped rather than
|
||||
-- stored, so the overlay stays the list of deliberate differences.
|
||||
function SaveData.setModEnabled(options, id, enabled, version)
|
||||
if type(options) ~= "table" or type(id) ~= "string" or id == "" then
|
||||
return options
|
||||
end
|
||||
enabled = enabled and true or false
|
||||
if not version then
|
||||
options.mods = options.mods or {}
|
||||
options.mods[id] = enabled
|
||||
return options
|
||||
end
|
||||
options.modsByVersion = options.modsByVersion or {}
|
||||
local bucket = options.modsByVersion[version] or {}
|
||||
options.modsByVersion[version] = bucket
|
||||
-- no shared flag reads as enabled, the same default the loader applies to a
|
||||
-- missing entry, so a fresh install never fills the overlay with agreement
|
||||
local shared = options.mods and options.mods[id]
|
||||
if type(shared) ~= "boolean" then shared = true end
|
||||
if shared == enabled then
|
||||
bucket[id] = nil
|
||||
else
|
||||
bucket[id] = enabled
|
||||
end
|
||||
return options
|
||||
end
|
||||
|
||||
-- ------- the player's target override
|
||||
--
|
||||
-- The manifest's `games` is the AUTHOR's claim and the loader enforces it
|
||||
-- (Loader:_gateGeneration); this is the player's per-game override of that
|
||||
-- claim. Scoped by version, because "run it on Gold anyway" is not an answer
|
||||
-- about Red: a version-blind flag forced a mod past a gate on a game its
|
||||
-- owner was never asked about.
|
||||
|
||||
-- A pre-per-game `true` could only ever take effect on a Gen 2 boot (the gate
|
||||
-- returned early on Gen 1), so that is exactly what it is read as here.
|
||||
local function forcedGenerations(entry)
|
||||
return entry == true and 2 or nil
|
||||
end
|
||||
|
||||
function SaveData.modForced(options, id, version, generation)
|
||||
local entry = type(options) == "table" and type(options.modsGen2) == "table"
|
||||
and options.modsGen2[id]
|
||||
if entry == nil or entry == false then return false end
|
||||
local gen = generation or (version and GameVersion.generation(version))
|
||||
local legacy = forcedGenerations(entry)
|
||||
if legacy then return legacy == gen end
|
||||
if type(entry) ~= "table" then return false end
|
||||
if version then return entry[version] == true end
|
||||
-- no version, only a generation: a harness seam, so ask whether ANY game of
|
||||
-- that generation was forced rather than inventing a game
|
||||
for id2, on in pairs(entry) do
|
||||
if on == true and GameVersion.generation(id2) == gen then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Write the override for one game. Without a version there is no game to
|
||||
-- answer for, so this writes nothing rather than guessing (the caller keeps
|
||||
-- the choice in memory for this boot and says so).
|
||||
function SaveData.setModForced(options, id, forced, version)
|
||||
if type(options) ~= "table" or type(id) ~= "string" or id == "" then
|
||||
return false
|
||||
end
|
||||
if not (version and GameVersion.VERSIONS[version]) then return false end
|
||||
options.modsGen2 = options.modsGen2 or {}
|
||||
local entry = options.modsGen2[id]
|
||||
if type(entry) ~= "table" then
|
||||
-- migrate the legacy flag in place, keeping the games it already covered
|
||||
local expanded = {}
|
||||
if forcedGenerations(entry) then
|
||||
for _, other in ipairs(GameVersion.ORDER) do
|
||||
if GameVersion.generation(other) == 2 then expanded[other] = true end
|
||||
end
|
||||
end
|
||||
entry = expanded
|
||||
options.modsGen2[id] = entry
|
||||
end
|
||||
entry[version] = forced and true or nil
|
||||
if next(entry) == nil then options.modsGen2[id] = nil end
|
||||
return true
|
||||
end
|
||||
|
||||
-- ------- save slots
|
||||
|
||||
-- A version's playthroughs live in numbered slots under saves/<version>/;
|
||||
@@ -677,7 +807,20 @@ function SaveData.slotSummary(save)
|
||||
for _ in pairs((save.pokedex and save.pokedex.owned) or {}) do
|
||||
dexCount = dexCount + 1
|
||||
end
|
||||
local t = math.floor(save.playTime or 0)
|
||||
-- playTime is a plain seconds count in a Gen 1 save but a
|
||||
-- { hours, minutes, seconds, frames } table in a Gen 2 (Gold) save, matching
|
||||
-- the cart's wGameTime* bytes. The launcher calls slotSummary on EVERY
|
||||
-- version's slot, so this has to read both shapes or the whole launcher
|
||||
-- crashes the moment a Gold save exists (math.floor on the table).
|
||||
local pt = save.playTime
|
||||
local t
|
||||
if type(pt) == "table" then
|
||||
t = (tonumber(pt.hours) or 0) * 3600
|
||||
+ (tonumber(pt.minutes) or 0) * 60
|
||||
+ (tonumber(pt.seconds) or 0)
|
||||
else
|
||||
t = math.floor(tonumber(pt) or 0)
|
||||
end
|
||||
local timeText = ("%d:%02d"):format(math.floor(t / 3600),
|
||||
math.floor(t / 60) % 60)
|
||||
return name, {
|
||||
|
||||
Reference in New Issue
Block a user