Files
gen1recomp/src/mods/ModTargets.lua
T
1jamie dfeacc36b0 - adding ability to specify gen specific deps in manifest system.
- fixed issue with wayland users drag and drop causing CTD
2026-08-13 18:18:59 -05:00

207 lines
6.8 KiB
Lua

-- Which games a mod is for. One derivation for the manifest's `games` key,
-- the legacy `gen2compat` flag, and the labels both mod surfaces draw:
-- src/mods/LauncherMods.lua and src/mods/ManagerState.lua read this rather
-- than each keeping its own copy of the rule.
local GameVersion = require("src.core.GameVersion")
local ModTargets = {}
-- every version of one generation, in launcher order (GameVersion.ORDER)
function ModTargets.generationVersions(gen)
local out = {}
for _, id in ipairs(GameVersion.ORDER) do
if GameVersion.generation(id) == gen then out[#out + 1] = id end
end
return out
end
-- the generations this engine has games for, ascending
local function generations()
local seen, out = {}, {}
for _, id in ipairs(GameVersion.ORDER) do
local gen = GameVersion.generation(id)
if not seen[gen] then
seen[gen] = true
out[#out + 1] = gen
end
end
table.sort(out)
return out
end
-- one manifest token -> the version ids it covers, or nil when it names no
-- game this engine knows: "red" | "gen1" | "all"
function ModTargets.expand(token)
if type(token) ~= "string" then return nil end
local key = token:lower():match("^%s*(.-)%s*$")
if key == "all" then return GameVersion.ORDER end
if GameVersion.VERSIONS[key] then return { key } end
local gen = key:match("^gen%s*(%d+)$")
if gen then
local list = ModTargets.generationVersions(tonumber(gen))
if #list > 0 then return list end
end
return nil
end
-- version-id lists are always ORDER-sorted and deduped, so two manifests that
-- say the same thing different ways encode and compare the same
local function fromSet(set)
local out = {}
for _, id in ipairs(GameVersion.ORDER) do
if set[id] then out[#out + 1] = id end
end
return out
end
-- normalize a manifest `games` array; second return is the tokens that named
-- no game, which Manifest reports at its own api level
function ModTargets.normalize(list)
local set, unknown = {}, {}
for _, token in ipairs(type(list) == "table" and list or {}) do
local ids = ModTargets.expand(token)
if ids then
for _, id in ipairs(ids) do set[id] = true end
else
unknown[#unknown + 1] = tostring(token)
end
end
return fromSet(set), unknown
end
function ModTargets.union(a, b)
local set = {}
for _, list in ipairs({ a or {}, b or {} }) do
for _, id in ipairs(list) do set[id] = true end
end
return fromSet(set)
end
-- The pre-`games` reading of a manifest: Gen 1 always, Gen 2 only where the
-- author claimed gen2compat (src/mods/Manifest.lua).
function ModTargets.legacy(gen2compat)
local out = ModTargets.generationVersions(1)
if gen2compat then
return ModTargets.union(out, ModTargets.generationVersions(2))
end
return out
end
-- does a version-id list hold any game of that generation
function ModTargets.covers(versions, gen)
for _, id in ipairs(versions or {}) do
if GameVersion.generation(id) == gen then return true end
end
return false
end
-- the version ids a validated manifest targets; Manifest.validate resolves
-- `games` at load, so this is the same answer everywhere
function ModTargets.versions(manifest)
local games = manifest and manifest.games
if type(games) == "table" and #games > 0 then return games end
return ModTargets.legacy(manifest and manifest.gen2compat)
end
-- Does the mod target this game? `version` is a version id; pass nil with a
-- generation to ask about a whole generation (the loader's injected seam).
function ModTargets.supports(manifest, version, generation)
local versions = ModTargets.versions(manifest)
if version and GameVersion.VERSIONS[version] then
for _, id in ipairs(versions) do
if id == version then return true end
end
return false
end
return ModTargets.covers(versions, generation or GameVersion.generation())
end
-- What will actually happen here: the loader gates on this same answer, per
-- version (Loader:_gateGeneration), and the player's override forces past it,
-- so the two UIs report a run, not a claim.
function ModTargets.runsHere(manifest, version, generation, forced)
if forced then return true end
return ModTargets.supports(manifest, version, generation)
end
-- "Gen 1" / "Gen 1+2" while a mod takes whole generations, the version names
-- ("Red/Gold") once it takes only some of one
function ModTargets.label(manifest)
local versions = ModTargets.versions(manifest)
local set = {}
for _, id in ipairs(versions) do set[id] = true end
local whole, partial = {}, false
for _, gen in ipairs(generations()) do
local all, any = true, false
for _, id in ipairs(ModTargets.generationVersions(gen)) do
if set[id] then any = true else all = false end
end
if any and all then whole[#whole + 1] = tostring(gen)
elseif any then partial = true end
end
if #whole > 0 and not partial then
return "Gen " .. table.concat(whole, "+")
end
local names = {}
for _, id in ipairs(versions) do
names[#names + 1] = GameVersion.info(id).label or id
end
if #names == 0 then return "No game" end
return table.concat(names, "/")
end
-- the same label as an all-caps chip, for the launcher tag and the GB font
function ModTargets.chip(manifest)
return ModTargets.label(manifest):upper()
end
-- one game's own name, for a line that has to say which one this is
function ModTargets.gameLabel(version)
local info = version and GameVersion.info(version)
return (info and info.label) or tostring(version)
end
-- One launcher-voice line for a mod that does not target `version`
-- (src/mods/LauncherMods.lua statusFor).
function ModTargets.detail(manifest, version)
return ("For %s, not %s"):format(ModTargets.label(manifest),
ModTargets.gameLabel(version))
end
-- Does a dependency spec apply to this game / version?
-- If spec.games is provided, it must match version / generation.
-- If spec.game_version is provided, the engine version must satisfy it.
function ModTargets.specApplies(spec, version, generation)
if not spec then return false end
if type(spec.games) == "table" and #spec.games > 0 then
if version or generation then
local match = false
for _, id in ipairs(spec.games) do
if version and id == version then
match = true
break
end
if generation and GameVersion.generation(id) == generation then
match = true
break
end
end
if not match then return false end
end
end
if spec.game_version then
local ok = pcall(function()
local Semver = require("src.mods.Semver")
local Version = require("src.core.Version")
if Version and Version.engine and Version.engine:match("^0%.0%.0%-") == nil then
return Semver.satisfies(Version.engine, spec.game_version)
end
return true
end)
if not ok then return false end
end
return true
end
return ModTargets