mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
490 lines
20 KiB
Lua
490 lines
20 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.modkit")
|
|
local GameVersion = require("src.core.GameVersion")
|
|
local Manifest = require("src.mods.Manifest")
|
|
local ModTargets = require("src.mods.ModTargets")
|
|
local Schemas = require("src.mods.Schemas")
|
|
|
|
T.eq(GameVersion.generation("firered"), 3, "FireRed is Gen 3")
|
|
T.eq(GameVersion.generation("leafgreen"), 3, "LeafGreen is Gen 3")
|
|
T.eq(table.concat(ModTargets.expand("gen3"), ","), "firered,leafgreen",
|
|
"gen3 is every Gen 3 game")
|
|
|
|
local function manifest(extra)
|
|
local raw = { id = "fix", name = "Fixture", version = "1.0.0",
|
|
entry = "main.lua", api = 2 }
|
|
for key, value in pairs(extra or {}) do raw[key] = value end
|
|
return Manifest.validate(raw, "mods/fix")
|
|
end
|
|
|
|
T.check(ModTargets.supports(manifest({ games = { "gen3" } }), "firered"),
|
|
"games = [gen3] claims FireRed")
|
|
T.check(ModTargets.supports(manifest({ games = { "gen3" } }), "leafgreen"),
|
|
"games = [gen3] claims LeafGreen")
|
|
T.check(ModTargets.supports(manifest({ games = { "firered" } }), "firered"),
|
|
"games = [firered] claims FireRed")
|
|
T.check(not ModTargets.supports(manifest({ games = { "firered" } }), "leafgreen"),
|
|
"games = [firered] does not claim LeafGreen")
|
|
T.check(ModTargets.supports(manifest({ games = { "leafgreen" } }), "leafgreen"),
|
|
"games = [leafgreen] claims LeafGreen")
|
|
T.check(not ModTargets.supports(manifest({ games = { "leafgreen" } }), "firered"),
|
|
"games = [leafgreen] does not claim FireRed")
|
|
T.check(not ModTargets.supports(manifest(), "firered"),
|
|
"a manifest that says nothing is not a FireRed mod")
|
|
T.check(not ModTargets.supports(manifest({ gen2compat = true }), "firered"),
|
|
"and gen2compat is not a Gen 3 claim")
|
|
T.eq(manifest({ games = { "gen3" } }).gen2compat, false,
|
|
"a Gen 3 claim is not a Gen 2 claim either")
|
|
|
|
local ROUTED = {
|
|
pokemon = "gen3Pokemon", moves = "gen3Moves", items = "gen3Items",
|
|
encounters = "gen3Encounters", trainers = "gen3Trainers",
|
|
text = "gen3Text", map_scripts = "gen3Scripts",
|
|
}
|
|
|
|
for name, path in pairs(ROUTED) do
|
|
local spec = Schemas.REGISTRIES[name]
|
|
T.check(spec ~= nil, "catalog still has registry: " .. name)
|
|
T.eq(Schemas.targetFor(name, spec, 3), path,
|
|
"available under Gen 3 at its Gen 3 path: " .. name)
|
|
T.eq(Schemas.gatedFor(name, 3), false, "not gated under Gen 3: " .. name)
|
|
T.eq(Schemas.targetFor(name, spec, 1), spec.target,
|
|
"and Gen 1 is untouched by the routing: " .. name)
|
|
end
|
|
|
|
for _, name in ipairs({ "maps", "commands", "strings" }) do
|
|
local spec = Schemas.REGISTRIES[name]
|
|
T.eq(Schemas.targetFor(name, spec, 3), spec.target,
|
|
"available under Gen 3 at its shared path: " .. name)
|
|
end
|
|
|
|
for _, name in ipairs({ "tilesets", "sprites", "type_chart", "statuses",
|
|
"move_effects", "balls", "ai_classes", "growth_rates",
|
|
"evolution_methods", "transitions", "rulesets",
|
|
"field", "text_pointers", "link_fields", "screens",
|
|
"tokens", "music", "sfx", "cries", "map_songs",
|
|
"audio", "font", "palettes", "icons", "battle_anims",
|
|
"held_items", "phone_contacts", "decorations",
|
|
"apricorns", "landmarks", "radio_channels" }) do
|
|
local spec = Schemas.REGISTRIES[name]
|
|
T.check(spec ~= nil, "catalog still has registry: " .. name)
|
|
T.eq(Schemas.targetFor(name, spec, 3), nil,
|
|
"gated registry has no Gen 3 target: " .. name)
|
|
T.eq(Schemas.gatedFor(name, 3), true, "gated under Gen 3: " .. name)
|
|
end
|
|
|
|
T.eq(Schemas.targetFor("rom_text", Schemas.REGISTRIES.rom_text, 3), "gen3RomText",
|
|
"rom_text overrides FireRed text by its pret name")
|
|
|
|
for name in pairs(Schemas.GEN3) do
|
|
T.check(Schemas.REGISTRIES[name] ~= nil,
|
|
"Schemas.GEN3 names a real registry: " .. name)
|
|
end
|
|
|
|
do
|
|
local claimed = {}
|
|
for name, spec in pairs(Schemas.REGISTRIES) do
|
|
local path = Schemas.targetFor(name, spec, 3)
|
|
if path then
|
|
T.check(claimed[path] == nil or claimed[path] == name,
|
|
("two registries share one Gen 3 path (%s): %s and %s")
|
|
:format(path, tostring(claimed[path]), name))
|
|
claimed[path] = name
|
|
end
|
|
end
|
|
end
|
|
|
|
do
|
|
local spec = Schemas.REGISTRIES.pokemon
|
|
local gen3 = Schemas.shapeFor("pokemon", spec, 3)
|
|
T.check(gen3 ~= spec, "Gen 3 gets a derived spec")
|
|
T.eq(Schemas.shapeFor("pokemon", gen3, 3), gen3,
|
|
"resolving a derived spec again is a no-op")
|
|
T.check(gen3.fields.learnset ~= nil and gen3.fields.baseStats.fields.specialAttack ~= nil,
|
|
"the Gen 3 species shape is folded onto `fields`")
|
|
T.check(gen3.fields.eggMoves ~= nil,
|
|
"the Gen 3 species shape carries the gEggMoves field")
|
|
T.eq(gen3.gen3Fields, nil, "the gen3* keys are gone from the derived spec")
|
|
T.eq(gen3.gen2Fields, nil, "and so are the gen2* keys")
|
|
T.eq(gen3.target, "gen3Pokemon", "the derived spec carries the routed path")
|
|
T.check(gen3.baseAt ~= nil and gen3.baseIds ~= nil and gen3.write ~= nil,
|
|
"and reaches the numeric tables through baseAt/baseIds/write")
|
|
local gen2 = Schemas.shapeFor("pokemon", spec, 2)
|
|
T.eq(gen2.gen3Fields, nil, "a Gen 2 derived spec carries no gen3* keys")
|
|
T.eq(gen2.baseAt, Schemas.REGISTRIES.pokemon.gen2BaseAt,
|
|
"and keeps its own Gen 2 baseAt")
|
|
local scripts = Schemas.shapeFor("map_scripts", Schemas.REGISTRIES.map_scripts, 3)
|
|
T.eq(scripts.semantics, "record",
|
|
"Gen 3 scripts are whole VM row lists, not compose chains")
|
|
end
|
|
|
|
for name, spec in pairs(Schemas.REGISTRIES) do
|
|
if Schemas.hasGen3Shape(spec) then
|
|
T.eq(Schemas.gatedFor(name, 3), false,
|
|
"a registry with a Gen 3 shape is not gated: " .. name)
|
|
end
|
|
end
|
|
|
|
do
|
|
local G3 = Schemas.gen3View
|
|
T.eq(G3.idOf("NIDORAN\226\153\128"), "NIDORAN_F", "female glyph id")
|
|
T.eq(G3.idOf("NIDORAN\226\153\130"), "NIDORAN_M", "male glyph id")
|
|
T.eq(G3.idOf("FARFETCH'D"), "FARFETCHD", "apostrophes drop")
|
|
T.eq(G3.idOf("MR. MIME"), "MR_MIME", "punctuation folds to one underscore")
|
|
T.eq(G3.idOf("HO-OH"), "HO_OH", "hyphens fold")
|
|
T.eq(G3.idOf("POK\195\169 BALL"), "POKE_BALL", "the accented E folds")
|
|
T.eq(G3.idOf("??????????"), nil, "a placeholder name has no id")
|
|
local ir = G3.textIr("A\nB\n\nC")
|
|
local kinds = {}
|
|
for i, token in ipairs(ir) do kinds[i] = token.t end
|
|
T.eq(table.concat(kinds, ","), "text,nl,text,para,text,eos",
|
|
"a plain string becomes the VM's text IR")
|
|
end
|
|
|
|
local Catalog = T.catalog
|
|
|
|
local function isGen3Site(path)
|
|
return path:match("game3") ~= nil or path:match("Game3") ~= nil
|
|
or path:match("Gen3") ~= nil
|
|
end
|
|
|
|
local function isFacadeSite(path)
|
|
return path:match("Gen3Compat") ~= nil or path:match("WorldAPI") ~= nil
|
|
or path:match("BattleAPI") ~= nil
|
|
end
|
|
|
|
local GEN3_EVENTS = {
|
|
"game.ready", "save.created", "save.loaded", "save.loading", "save.writing",
|
|
"map.entered", "map.exited", "map.reloaded", "player.warped",
|
|
"world.stepped", "world.interacted", "world.npc_spawned",
|
|
"world.blacked_out", "world.block_replaced", "world.boulder_moved",
|
|
"world.object_toggled",
|
|
"flag.changed", "script.started", "script.ended",
|
|
"pokemon.before_give", "pokemon.evolved",
|
|
"battle.started", "battle.ended", "battle.turn_started", "battle.turn_ended",
|
|
"battle.move_used", "battle.damage_dealt", "battle.status_inflicted",
|
|
"battle.fainted", "battle.battler_switched", "battle.exp_gained",
|
|
"battle.ball_thrown", "pokemon.caught", "pokemon.level_up",
|
|
"pokemon.move_learned", "world.trainer_engaged",
|
|
"intro.oak_speech.started", "intro.oak_speech.step",
|
|
"intro.oak_speech.answered", "intro.oak_speech.finished",
|
|
}
|
|
|
|
local GEN3_HOOKS = {
|
|
"encounter.roll", "encounter.species", "encounter.table",
|
|
"movement.collision", "warp.destination", "world.talk", "item.use",
|
|
"world.follower.spawn",
|
|
"script.command", "save.write", "save.new_game",
|
|
"ui.start_menu.items", "pokemon.sprite",
|
|
"input.step", "input.key", "input.gamepad", "input.wheel", "render.hud",
|
|
"trainer.party", "catch.rate", "exp.gain", "evolution.check",
|
|
"battle.damage", "battle.crit", "battle.accuracy", "battle.charge_required",
|
|
"battle.run", "battle.turn_order", "battle.enemy_action",
|
|
}
|
|
|
|
local function assertShared(name, sites, kind)
|
|
local gen3, other = 0, 0
|
|
for _, path in ipairs(sites) do
|
|
if isGen3Site(path) then gen3 = gen3 + 1 else other = other + 1 end
|
|
end
|
|
T.check(gen3 > 0, ("FireRed raises the %s: %s"):format(kind, name))
|
|
T.check(other > 0,
|
|
("the %s %s is shared, not a Gen 3 invention (no Gen 1 or Gen 2 site)")
|
|
:format(kind, name))
|
|
end
|
|
|
|
for _, name in ipairs(GEN3_EVENTS) do
|
|
assertShared(name, Catalog.eventSites(name), "event")
|
|
end
|
|
for _, name in ipairs(GEN3_HOOKS) do
|
|
assertShared(name, Catalog.hookSites(name), "hook")
|
|
end
|
|
|
|
local function assertListed(names, catalogNames, sites, kind)
|
|
local listed = {}
|
|
for _, name in ipairs(names) do listed[name] = true end
|
|
for _, name in ipairs(catalogNames) do
|
|
if not Catalog.isModEvent(name) then
|
|
local gen3, other = false, false
|
|
for _, path in ipairs(sites(name)) do
|
|
if isGen3Site(path) then gen3 = true else other = true end
|
|
end
|
|
if gen3 then
|
|
T.check(other,
|
|
("%s %s is raised only from Gen 3 modules; Gen 3 reuses the "
|
|
.. "Gen 1 / Gen 2 names and invents none"):format(kind, name))
|
|
T.check(listed[name],
|
|
("%s %s has a Gen 3 site but is not in this gate's list; add it "
|
|
.. "here and to docs/mod-api-gen3-compat.md"):format(kind, name))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
assertListed(GEN3_EVENTS, Catalog.events(), Catalog.eventSites, "event")
|
|
assertListed(GEN3_HOOKS, Catalog.hooks(), Catalog.hookSites, "hook")
|
|
|
|
for _, name in ipairs(Catalog.events()) do
|
|
T.check(name:sub(1, 5) ~= "gen3.", "no generation-prefixed event: " .. name)
|
|
end
|
|
for _, name in ipairs(Catalog.hooks()) do
|
|
T.check(name:sub(1, 5) ~= "gen3.", "no generation-prefixed hook: " .. name)
|
|
end
|
|
|
|
do
|
|
local cache = {}
|
|
local function sourceOf(path)
|
|
if cache[path] == nil then
|
|
local handle = io.open(path, "r")
|
|
cache[path] = handle and handle:read("*a") or false
|
|
if handle then handle:close() end
|
|
end
|
|
return cache[path] or nil
|
|
end
|
|
for _, name in ipairs(Catalog.events()) do
|
|
for _, path in ipairs(Catalog.eventSites(name)) do
|
|
if isGen3Site(path) and not isFacadeSite(path) then
|
|
local body = sourceOf(path)
|
|
T.check(body and body:find(('Runtime.wants("%s")'):format(name), 1, true),
|
|
("the Gen 3 emit of %s in %s is guarded by Runtime.wants")
|
|
:format(name, path))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function files(...)
|
|
local out = {}
|
|
for _, set in ipairs({ ... }) do
|
|
for path, body in pairs(set) do out[path] = body end
|
|
end
|
|
return out
|
|
end
|
|
|
|
local function fixture(id, games, body)
|
|
local gamesJson = games and (',"games":["' .. table.concat(games, '","') .. '"]') or ""
|
|
return {
|
|
["mods/" .. id .. "/manifest.json"] = ([[{
|
|
"id": "%s", "name": "%s", "version": "1.0.0", "entry": "main.lua",
|
|
"api": 2%s
|
|
}]]):format(id, id, gamesJson),
|
|
["mods/" .. id .. "/main.lua"] = body,
|
|
}
|
|
end
|
|
|
|
local function statusOf(run, id)
|
|
for _, entry in ipairs(run.loader:status().available) do
|
|
if entry.id == id then return entry end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local GEN1_ONLY = fixture("fix_gen1_only", nil, [[
|
|
local mod = ...
|
|
mod.content.pokemon:patch("MEW", { catchRate = 1 })
|
|
]])
|
|
|
|
local GEN3_READY = fixture("fix_gen3_ready", { "gen1", "gen3" }, [[
|
|
local mod = ...
|
|
mod.exports.api = mod
|
|
local mew = mod.content.pokemon:get("MEW")
|
|
mod.exports.mewIndex = mew and mew.index
|
|
mod.exports.mewStats = mew and mew.baseStats.specialAttack
|
|
local pidgey = mod.content.pokemon:get("PIDGEY")
|
|
mod.exports.pidgeyEggMoves = pidgey and pidgey.eggMoves
|
|
mod.exports.mewEggMoves = mew and mew.eggMoves
|
|
local copy = {}
|
|
for key, value in pairs(mew) do copy[key] = value end
|
|
copy.spriteFront = mod.path .. "/front.png"
|
|
mod.content.pokemon:override("MEW", copy)
|
|
mod.content.pokemon:patch("CHARMANDER", {
|
|
catchRate = 3,
|
|
learnset = { { level = 1, move = "SURF" } },
|
|
eggMoves = { "EMBER" },
|
|
evolutions = { { method = "EVO_ITEM", item = "THUNDERSTONE",
|
|
species = "CHARMELEON" } },
|
|
})
|
|
mod.content.moves:patch("EMBER", { power = 50 })
|
|
mod.content.items:patch("POTION", { price = 1 })
|
|
mod.content.encounters:patch("FR_ROUTE_1", { land = { rate = 99 } })
|
|
mod.content.trainers:patch("326", {
|
|
party = { { species = "MEW", level = 7, moves = { "SURF" } } },
|
|
})
|
|
mod.content.text:override("Text_BootedUpPC", "HELLO")
|
|
mod.content.map_scripts:override("EventScript_Fixture", { { op = "end" } })
|
|
mod.content.transitions:register("FIXTURE_WIPE", { frames = 30 })
|
|
mod.exports.species = {}
|
|
for id in mod.content.pokemon:each() do mod.exports.species[#mod.exports.species + 1] = id end
|
|
]])
|
|
|
|
do
|
|
local data = T.sdk.gen3Data()
|
|
local run = T.sdk.loadMods({ "mods/fix_gen1_only", "mods/fix_gen3_ready" }, {
|
|
fs = T.sdk.memfs(files(GEN1_ONLY, GEN3_READY)),
|
|
data = data,
|
|
generation = 3,
|
|
})
|
|
|
|
local skipped = statusOf(run, "fix_gen1_only")
|
|
T.eq(skipped.state, "wrong_generation",
|
|
"Gen 3: a mod that never claimed FireRed is not loaded")
|
|
T.eq(skipped.error, nil, "Gen 3: and the skip is not a failure")
|
|
T.eq(statusOf(run, "fix_gen3_ready").state, "loaded",
|
|
"Gen 3: the declared mod loads (" .. tostring(run.errors[1]) .. ")")
|
|
|
|
local exports = run.loader.exports.fix_gen3_ready or {}
|
|
T.eq(exports.mewIndex, 151, "Gen 3: MEW resolves by name to species 151")
|
|
T.eq(exports.mewStats, 100, "Gen 3: with the split special stats")
|
|
T.eq(table.concat(exports.pidgeyEggMoves or {}, ","), "SCRATCH,TACKLE",
|
|
"Gen 3: the species record carries its egg moves as move ids")
|
|
T.eq(exports.mewEggMoves, nil,
|
|
"Gen 3: a species with no gEggMoves entry carries no eggMoves field")
|
|
local seen = {}
|
|
for _, id in ipairs(exports.species or {}) do seen[id] = true end
|
|
T.check(seen.NIDORAN_F and seen.CHARMANDER and not seen["?"],
|
|
"Gen 3: each() yields name ids and skips placeholder species")
|
|
|
|
local P = data.gen3Pokemon
|
|
T.eq(P._speciesMeta[4].catchRate, 3, "Gen 3: a patch lands in the numeric meta table")
|
|
T.eq(P._speciesMeta[151].catchRate, 45, "Gen 3: the skipped mod's patch left no trace")
|
|
T.eq(P._learnsets[4][1][2], 57, "Gen 3: a learnset move name writes back as its number")
|
|
T.eq(P._eggMoves[4][1], 52, "Gen 3: an egg move name writes back as its number")
|
|
T.eq(P._evolutions[4][1].method, 7, "Gen 3: an evolution method writes back as EVO_ITEM")
|
|
T.eq(P._evolutions[4][1].param, 96, "Gen 3: and its item as the item number")
|
|
T.eq(P._evolutions[4][1].target, 5, "Gen 3: and its species as the species number")
|
|
T.check(P.spriteOverrides and P.spriteOverrides[151]
|
|
and P.spriteOverrides[151].front == "mods/fix_gen3_ready/front.png",
|
|
"Gen 3: a sprite path override is recorded for the pic seeder")
|
|
T.eq(P.spriteOverrides[151].back, nil, "Gen 3: the untouched back pic stays vanilla")
|
|
|
|
T.eq(data.gen3Moves._rom[52].power, 50, "Gen 3: a move patch lands in the ROM row")
|
|
T.eq(data.gen3Items._byId[13].price, 1, "Gen 3: an item patch lands in the item row")
|
|
T.eq(data.gen3Encounters.FR_ROUTE_1.land.rate, 99, "Gen 3: an encounter patch lands")
|
|
T.eq(data.gen3Encounters["3:19"].land.rate, 99,
|
|
"Gen 3: and reaches the group:num alias of the same map")
|
|
T.eq(data.gen3Encounters.FR_ROUTE_1.land.slots[1].species, 16,
|
|
"Gen 3: encounter species stay numeric in the engine table")
|
|
local rival = data.gen3Trainers.trainers[326]
|
|
T.eq(rival.party[1].species, 151, "Gen 3: a trainer party species writes back as a number")
|
|
T.eq(rival.party[1].moves[1], 57, "Gen 3: and its moves as numbers")
|
|
T.eq(rival.partySize, 1, "Gen 3: partySize follows the party")
|
|
local text = data.gen3Text.Text_BootedUpPC
|
|
T.check(text[1].t == "text" and text[1].s == "HELLO" and text[2].t == "eos",
|
|
"Gen 3: a string text override becomes the VM's IR")
|
|
T.eq(data.gen3Scripts.EventScript_Fixture[1].op, "end",
|
|
"Gen 3: a script override replaces the VM row list")
|
|
|
|
T.check(data.transitions == nil or data.transitions.FIXTURE_WIPE == nil,
|
|
"Gen 3: a gated registry merges nothing")
|
|
local told = false
|
|
for _, message in ipairs(run.errors) do
|
|
if message:match("transitions") and message:match("Gen 3") then told = true end
|
|
end
|
|
T.check(told, "Gen 3: the dropped registration is reported in Gen 3's words")
|
|
|
|
local api = exports.api
|
|
T.check(api ~= nil, "Gen 3: the mod handed its api back for the facade check")
|
|
if api then
|
|
run.loader.game = {}
|
|
local world = api.world
|
|
T.eq(getmetatable(world), require("src.world.game3.WorldAPI"),
|
|
"Gen 3: mod.world resolves to src.world.game3.WorldAPI")
|
|
local battle = api.battle
|
|
T.eq(getmetatable(battle), require("src.battle.game3.BattleAPI"),
|
|
"Gen 3: mod.battle resolves to src.battle.game3.BattleAPI")
|
|
run.loader.game = nil
|
|
end
|
|
run.release()
|
|
end
|
|
|
|
do
|
|
local BAD = fixture("fix_refs", { "gen3" }, [[
|
|
local mod = ...
|
|
mod.content.pokemon:patch("PIDGEY", {
|
|
evolutions = { { method = "EVO_LEVEL", level = 18, species = "PIDGEOTTO_TYPO" } },
|
|
})
|
|
mod.content.pokemon:patch("MEW", { learnset = { { level = 1, move = "NOT_A_MOVE" } } })
|
|
]])
|
|
local run = T.sdk.loadMods({ "mods/fix_refs" }, {
|
|
fs = T.sdk.memfs(BAD), data = T.sdk.gen3Data(), generation = 3,
|
|
})
|
|
local pokemon, moves = false, false
|
|
for _, message in ipairs(run.errors) do
|
|
if message:match("unresolved reference to pokemon") then pokemon = true end
|
|
if message:match("unresolved reference to moves") then moves = true end
|
|
end
|
|
T.check(pokemon, "Gen 3: an evolution into an unknown species is reported")
|
|
T.check(moves, "Gen 3: a learnset move that does not exist is reported")
|
|
run.release()
|
|
end
|
|
|
|
do
|
|
local GOOD = fixture("fix_refs_ok", { "firered" }, [[
|
|
local mod = ...
|
|
mod.content.pokemon:patch("PIDGEY", {
|
|
evolutions = { { method = "EVO_LEVEL", level = 18, species = "CHARMELEON" } },
|
|
itemRare = "POTION",
|
|
})
|
|
]])
|
|
local run = T.sdk.loadMods({ "mods/fix_refs_ok" }, {
|
|
fs = T.sdk.memfs(GOOD), data = T.sdk.gen3Data(), generation = 3,
|
|
})
|
|
local dangling = {}
|
|
for _, message in ipairs(run.errors) do
|
|
if message:match("unresolved reference") then dangling[#dangling + 1] = message end
|
|
end
|
|
T.eq(#dangling, 0, "Gen 3: refs that resolve report nothing ("
|
|
.. table.concat(dangling, "; ") .. ")")
|
|
T.eq(statusOf(run, "fix_refs_ok").state, "loaded", "Gen 3: games = [firered] loads")
|
|
run.release()
|
|
end
|
|
|
|
do
|
|
local run = T.sdk.loadNone({ generation = 3, data = T.sdk.gen3Data() })
|
|
local content = run.loader.content
|
|
T.eq(content.balls:get("GREAT_BALL"), nil, "Gen 3 seeds none of Red's balls")
|
|
T.eq(content.statuses:get("BRN"), nil, "Gen 3 seeds none of Red's statuses")
|
|
T.eq(content.ai_classes:get("LAYER_1"), nil, "or Red's AI layers")
|
|
T.eq(content.commands:get("show_text"), nil, "or Gen 1's row-list verbs")
|
|
T.eq(run.data.statuses, nil, "a mod-free FireRed boot writes no Gen 1 namespace")
|
|
T.eq(run.data.commands, nil, "and leaves data.commands absent")
|
|
T.eq(#require("src.mods.Builtins").registries(3), 0,
|
|
"the Gen 3 registrant list is empty")
|
|
run.release()
|
|
end
|
|
|
|
do
|
|
local PROBE = [[
|
|
local mod = ...
|
|
local function attempt(name)
|
|
local ok, result = pcall(require, name)
|
|
if ok then return nil end
|
|
return tostring(result)
|
|
end
|
|
mod.exports.gen2Err = attempt("src.battle.gen2.Mon")
|
|
mod.exports.game2Err = attempt("src.core.Game2")
|
|
mod.exports.game3Err = attempt("src.core.game3.pokemon")
|
|
]]
|
|
local FILES = fixture("fix_probe", { "all" }, PROBE)
|
|
local onRed = T.sdk.loadMods({ "mods/fix_probe" },
|
|
{ fs = T.sdk.memfs(files(FILES)), data = {}, generation = 1 })
|
|
local red = onRed.loader.exports.fix_probe or {}
|
|
T.check(red.game3Err and red.game3Err:find("Gen 3 engine module", 1, true),
|
|
"Gen 1: a Gen 3 engine module is refused: " .. tostring(red.game3Err))
|
|
onRed.release()
|
|
|
|
local onFireRed = T.sdk.loadMods({ "mods/fix_probe" },
|
|
{ fs = T.sdk.memfs(files(FILES)), data = T.sdk.gen3Data(), generation = 3 })
|
|
local fr = onFireRed.loader.exports.fix_probe or {}
|
|
T.check(fr.gen2Err and fr.gen2Err:find("Gen 2 engine module", 1, true),
|
|
"Gen 3: a Gen 2 engine module is refused: " .. tostring(fr.gen2Err))
|
|
T.check(fr.game2Err and fr.game2Err:find("src.core.Game2", 1, true),
|
|
"Gen 3: and so is src.core.Game2")
|
|
T.check(not (fr.game3Err and fr.game3Err:find("engine module and this is", 1, true)),
|
|
"Gen 3: its own engine modules are not refused: " .. tostring(fr.game3Err))
|
|
onFireRed.release()
|
|
end
|
|
|
|
T.finish("gate_gen3_mod_api")
|