mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-21 05:00:43 +02:00
Pokemon Silver as a full launcher version, plus launcher mods-list and title-tempo fixes
Silver: derived import manifest (tools/make_silver_manifest.py re-resolves the Gold manifest's symbols from pokesilver.sym), silver GameVersion row, generation-keyed extractor routing, required-files override, edition save stamping (a Silver playthrough no longer writes into the Gold save), checkver-driven edition data, SILVER/KAMON/OSCAR/MAX presets, GOLD rival default, edition credits banner, Lugia title screen (OAM layouts, bob, trail, palettes as title.lua data keys with Gold defaults so old caches need no re-import), packaging for every build target, docs, and tests. Launcher: the installed-mods list is one continuous scroll (rows culled to the viewport) instead of a pager with an inner scroll viewport; the pad cursor's edge-scroll no longer runs it to the bottom. The game dropdown shows just the initial and caret. Find-tab behavior unchanged. Title tempo: a sprite-anim frame shows duration+1 ticks (engine/sprite_anims/core.asm GetSpriteAnimFrame), which locks both editions' 64-tick wing beat to the 64-tick sine bob; the title screens no longer run fast and out of phase.
This commit is contained in:
@@ -41,12 +41,21 @@ end
|
||||
-- Mock isReady
|
||||
RomImporter.isReady = function(v)
|
||||
return v == "red" or v == "gold" or v == "blue" or v == "yellow"
|
||||
or v == "silver"
|
||||
end
|
||||
|
||||
local ok = RomImporter.syncAndroidShortcuts("gold")
|
||||
check(ok == true, "syncAndroidShortcuts returns true on Android")
|
||||
check(#capturedShortcuts == 4, "syncAndroidShortcuts caps at 4 items")
|
||||
check(capturedShortcuts[1] == "gold", "activeVersion 'gold' is placed first")
|
||||
check(capturedShortcuts[2] == "red" and capturedShortcuts[3] == "blue"
|
||||
and capturedShortcuts[4] == "yellow",
|
||||
"the rest follow GameVersion.ORDER until the cap")
|
||||
|
||||
capturedShortcuts = nil
|
||||
RomImporter.syncAndroidShortcuts("silver")
|
||||
check(#capturedShortcuts == 4, "a fifth ready game does not widen the payload")
|
||||
check(capturedShortcuts[1] == "silver", "activeVersion 'silver' is placed first")
|
||||
|
||||
-- Test with subset of ready games (e.g. only Red and Gold)
|
||||
RomImporter.isReady = function(v)
|
||||
@@ -78,4 +87,4 @@ end
|
||||
love.system.getLaunchGame = savedGetLaunchGame
|
||||
love.system.updateShortcuts = nil
|
||||
|
||||
print("8/8 checks passed (android_shortcuts_payload_test)")
|
||||
T.finish("android_shortcuts_payload_test")
|
||||
|
||||
@@ -23,6 +23,7 @@ T.eq(GameVersion.generation("red"), 1, "Red is Gen 1")
|
||||
T.eq(GameVersion.generation("blue"), 1, "Blue is Gen 1")
|
||||
T.eq(GameVersion.generation("yellow"), 1, "Yellow is Gen 1")
|
||||
T.eq(GameVersion.generation("gold"), 2, "Gold is Gen 2")
|
||||
T.eq(GameVersion.generation("silver"), 2, "Silver is Gen 2")
|
||||
|
||||
-- ------- 2. manifest: gen2compat is opt-in and defaults off
|
||||
|
||||
|
||||
@@ -39,45 +39,54 @@ end
|
||||
local edited = 0
|
||||
local hooks = { editTouchControls = function() edited = edited + 1 end }
|
||||
|
||||
local gold = LauncherSettings.open(hooks, "gold")
|
||||
check(has(gold, "TOUCH PAD"), "Gold's gear offers TOUCH PAD")
|
||||
check(has(gold, "VIBRATION"), "and VIBRATION")
|
||||
check(has(gold, "TOUCH CONTROLS"), "and the layout editor")
|
||||
check(has(gold, "VOID FILL"), "and VOID FILL")
|
||||
for _, version in ipairs({ "gold", "silver" }) do
|
||||
local model = LauncherSettings.open(hooks, version)
|
||||
check(has(model, "TOUCH PAD"), version .. " gear offers TOUCH PAD")
|
||||
check(has(model, "VIBRATION"), version .. " and VIBRATION")
|
||||
check(has(model, "TOUCH CONTROLS"), version .. " and the layout editor")
|
||||
check(has(model, "VOID FILL"), version .. " and VOID FILL")
|
||||
|
||||
local voidFill = findRow(gold, "VOID FILL")
|
||||
eq(voidFill.value(), "FADE ", "VOID FILL defaults to FADE")
|
||||
voidFill.step(1)
|
||||
eq(gold.opts.gold.voidFill, "water", "right stores water in the gold block")
|
||||
eq(voidFill.value(), "WATER", "and the row reads WATER")
|
||||
voidFill.step(-1)
|
||||
eq(gold.opts.gold.voidFill, "fade", "left restores fade")
|
||||
local voidFill = findRow(model, "VOID FILL")
|
||||
eq(voidFill.value(), "FADE ", version .. " VOID FILL defaults to FADE")
|
||||
voidFill.step(1)
|
||||
eq(model.opts.gold.voidFill, "water",
|
||||
version .. " right stores water in the gold block")
|
||||
eq(voidFill.value(), "WATER", version .. " and the row reads WATER")
|
||||
voidFill.step(-1)
|
||||
eq(model.opts.gold.voidFill, "fade", version .. " left restores fade")
|
||||
|
||||
-- Every write has to land in the gold block: the flat keys beside it are
|
||||
-- Red's, and Gold's boot never reads them (src/core/gen2/Save.lua:299).
|
||||
-- loadOptions seeds the flat Gen 1 defaults, so the check is that the Gold
|
||||
-- rows leave them exactly as they found them.
|
||||
local flatPad = gold.opts.touchControls
|
||||
local flatBuzz = gold.opts.haptics
|
||||
-- Every write has to land in the gen2 block: the flat keys beside it are
|
||||
-- Red's, and no Gen 2 boot reads them (src/core/gen2/Save.lua:299).
|
||||
-- loadOptions seeds the flat Gen 1 defaults, so the check is that the Gen 2
|
||||
-- rows leave them exactly as they found them.
|
||||
local flatPad = model.opts.touchControls
|
||||
local flatBuzz = model.opts.haptics
|
||||
|
||||
local pad = findRow(gold, "TOUCH PAD")
|
||||
local before = pad.value()
|
||||
pad.step(1)
|
||||
check(pad.value() ~= before, "stepping TOUCH PAD flips it")
|
||||
check(type(gold.opts.gold) == "table", "into the gold block")
|
||||
eq(gold.opts.gold.touchControls.enabled, false, "which now carries enabled")
|
||||
eq(gold.opts.touchControls, flatPad, "leaving the flat Gen 1 key alone")
|
||||
local pad = findRow(model, "TOUCH PAD")
|
||||
local before = pad.value()
|
||||
pad.step(1)
|
||||
check(pad.value() ~= before, version .. " stepping TOUCH PAD flips it")
|
||||
check(type(model.opts.gold) == "table", version .. " into the gold block")
|
||||
eq(model.opts.gold.touchControls.enabled, false,
|
||||
version .. " which now carries enabled")
|
||||
eq(model.opts.touchControls, flatPad,
|
||||
version .. " leaving the flat Gen 1 key alone")
|
||||
eq(model.opts.silver, nil,
|
||||
version .. " and inventing no second Gen 2 block beside it")
|
||||
|
||||
local buzz = findRow(gold, "VIBRATION")
|
||||
local buzzBefore = buzz.value()
|
||||
buzz.step(1)
|
||||
check(buzz.value() ~= buzzBefore, "stepping VIBRATION moves the level")
|
||||
eq(gold.opts.gold.haptics, TouchControls.normalizeHaptics(gold.opts.gold.haptics),
|
||||
"VIBRATION stores a level the shared module knows")
|
||||
eq(gold.opts.haptics, flatBuzz, "also without touching Red's")
|
||||
local buzz = findRow(model, "VIBRATION")
|
||||
local buzzBefore = buzz.value()
|
||||
buzz.step(1)
|
||||
check(buzz.value() ~= buzzBefore, version .. " stepping VIBRATION moves the level")
|
||||
eq(model.opts.gold.haptics,
|
||||
TouchControls.normalizeHaptics(model.opts.gold.haptics),
|
||||
version .. " VIBRATION stores a level the shared module knows")
|
||||
eq(model.opts.haptics, flatBuzz, version .. " also without touching Red's")
|
||||
|
||||
findRow(gold, "TOUCH CONTROLS").action()
|
||||
eq(edited, 1, "the editor row reaches the host hook")
|
||||
edited = 0
|
||||
findRow(model, "TOUCH CONTROLS").action()
|
||||
eq(edited, 1, version .. " the editor row reaches the host hook")
|
||||
end
|
||||
|
||||
-- The Gen 1 gear is untouched by the extraction: same three rows, still on
|
||||
-- the flat table.
|
||||
@@ -94,6 +103,8 @@ eq(findRow(red, "TOUCH PAD").value() ~= nil, true, "and reading it back")
|
||||
-- rather than dead, on both sides.
|
||||
eq(has(LauncherSettings.open(nil, "gold"), "TOUCH CONTROLS"), false,
|
||||
"no hook, no editor row on Gold")
|
||||
eq(has(LauncherSettings.open(nil, "silver"), "TOUCH CONTROLS"), false,
|
||||
"nor on Silver")
|
||||
eq(has(LauncherSettings.open(nil, "red"), "TOUCH CONTROLS"), false,
|
||||
"nor on Red")
|
||||
-- The Edit row hands the screen to the host, and the host has to know WHICH
|
||||
|
||||
@@ -155,28 +155,34 @@ modImp.mods = mods
|
||||
modImp._ensureMods = function() return mods end
|
||||
LauncherView.draw(modImp)
|
||||
LauncherView.draw(modImp)
|
||||
check((modImp._modScrollMax or 0) > 0,
|
||||
"60 mods overflow the list viewport inside the panel")
|
||||
local list = modImp._modListRect
|
||||
check(list.x + list.w
|
||||
<= modImp._tabRegionRect.x + modImp._tabRegionRect.w - Kit.scrollBarW(),
|
||||
"the rows stop short of the region's scrollbar gutter")
|
||||
pointer(list.x + 10, list.y + 10)
|
||||
local reg = modImp._tabRegionRect
|
||||
check((modImp._tabScrollMax.mods or 0) > reg.h,
|
||||
"60 installed mods are ONE continuous list: the region's travel spans "
|
||||
.. "the whole list, not one page of it")
|
||||
eq(modImp._pages.mods, nil, "and no page state is ever minted for it")
|
||||
pointer(reg.x + 10, reg.y + 40)
|
||||
modImp._wheelY = -1
|
||||
LauncherView.draw(modImp)
|
||||
check((modImp.modScroll or 0) > 0, "a notch over the mod list scrolls the list")
|
||||
eq(modImp._tabScroll.mods or 0, 0, "not the panel region around it")
|
||||
eq(modImp._pageScroll, 0, "and not the page behind that")
|
||||
check((modImp._tabScroll.mods or 0) > 0,
|
||||
"a notch over the region scrolls the list like any other tab")
|
||||
eq(modImp._pageScroll, 0, "without reaching the page behind it")
|
||||
|
||||
modImp._tabScroll.mods = modImp._tabScrollMax.mods
|
||||
LauncherView.draw(modImp)
|
||||
pointer(reg.x + 10, reg.y + reg.h * 0.5)
|
||||
modImp._wheelY = -1
|
||||
LauncherView.draw(modImp)
|
||||
eq(modImp._pages.mods, nil,
|
||||
"bottoming the list out never auto-advances any pager")
|
||||
eq(modImp._tabScroll.mods, modImp._tabScrollMax.mods,
|
||||
"the list just rests at its end")
|
||||
|
||||
modImp._modActions = "mod1"
|
||||
local shielded = modImp.modScroll
|
||||
local shieldedPage = modImp._pageScroll
|
||||
pointer(list.x + 10, list.y + 10)
|
||||
local shieldedAt = modImp._tabScroll.mods
|
||||
modImp._wheelY = -1
|
||||
LauncherView.draw(modImp)
|
||||
eq(modImp.modScroll, shielded, "a shielded mod list ignores the notch")
|
||||
eq(modImp._tabScroll.mods or 0, 0, "and so does the region under the scrim")
|
||||
eq(modImp._pageScroll, shieldedPage, "and the page behind that")
|
||||
eq(modImp._tabScroll.mods, shieldedAt,
|
||||
"a shielded mod list ignores the notch under the scrim")
|
||||
modImp._modActions = nil
|
||||
modImp._wheelY = 0
|
||||
LauncherView.draw(modImp)
|
||||
@@ -225,32 +231,27 @@ dragMods.mods = mods
|
||||
dragMods._ensureMods = function() return mods end
|
||||
LauncherView.draw(dragMods)
|
||||
LauncherView.draw(dragMods)
|
||||
local dlist = dragMods._modListRect
|
||||
local dListMax = dragMods._modScrollMax
|
||||
local dreg = dragMods._tabRegionRect
|
||||
local dRegionMax = dragMods._tabScrollMax.mods
|
||||
check(dListMax > 0 and dRegionMax > 0,
|
||||
"the mods tab has both an inner list and a region to scroll")
|
||||
LauncherView.touchpressed(dragMods, 7, dlist.x + 20, dlist.y + 30)
|
||||
LauncherView.touchmoved(dragMods, 7, dlist.x + 20, dlist.y + 30 - 60)
|
||||
eq(dragMods.modScroll, math.min(60, dListMax),
|
||||
"the first pixels of the drag move the list")
|
||||
eq(dragMods._tabScroll.mods or 0, 0, "and nothing else")
|
||||
LauncherView.touchmoved(dragMods, 7, dlist.x + 20,
|
||||
dlist.y + 30 - 60 - dListMax - dRegionMax * 2)
|
||||
eq(dragMods.modScroll, dListMax, "carrying on saturates the list")
|
||||
eq(dragMods._tabScroll.mods, dRegionMax,
|
||||
"then the same gesture walks the region to its bottom")
|
||||
check(dRegionMax > 0, "the mods region scrolls its overscan like any tab")
|
||||
LauncherView.touchpressed(dragMods, 7, dreg.x + 20, dreg.y + 30)
|
||||
LauncherView.touchmoved(dragMods, 7, dreg.x + 20, dreg.y + 30 - 60)
|
||||
eq(dragMods._tabScroll.mods, math.min(60, dRegionMax),
|
||||
"a drag moves the region by the finger's travel")
|
||||
LauncherView.touchmoved(dragMods, 7, dreg.x + 20,
|
||||
dreg.y + 30 - 60 - dRegionMax * 2)
|
||||
eq(dragMods._tabScroll.mods, dRegionMax, "carrying on saturates the region")
|
||||
check((dragMods._pageScroll or 0) > 0, "and only then reaches the page")
|
||||
LauncherView.touchreleased(dragMods, 7, dlist.x + 20, dlist.y - 900)
|
||||
LauncherView.touchreleased(dragMods, 7, dreg.x + 20, dreg.y - 900)
|
||||
|
||||
dragMods._skins = { { id = "s1", source = "user", controls = 8, pages = 1 } }
|
||||
for i = 2, 12 do
|
||||
dragMods._skins[i] = { id = "s" .. i, source = "user", controls = 8, pages = 1 }
|
||||
end
|
||||
dragMods._ensureSkins = function() return dragMods._skins end
|
||||
dragMods.modScroll = 0
|
||||
local heldModScroll = dragMods.modScroll
|
||||
local overList = dlist.y + 30
|
||||
local heldModsPage = dragMods._pages.mods or 1
|
||||
local heldModsAt = dragMods._tabScroll.mods
|
||||
local overList = dreg.y + 60
|
||||
dragMods:_switchTab("skins")
|
||||
LauncherView.draw(dragMods)
|
||||
LauncherView.draw(dragMods)
|
||||
@@ -260,8 +261,10 @@ LauncherView.touchpressed(dragMods, 9, sreg.x + 20, overList)
|
||||
LauncherView.touchmoved(dragMods, 9, sreg.x + 20, overList - 200)
|
||||
check((dragMods._tabScroll.skins or 0) > 0,
|
||||
"a drag on the skins tab scrolls the skins tab")
|
||||
eq(dragMods.modScroll, heldModScroll,
|
||||
"and leaves the mod list where the player parked it")
|
||||
eq(dragMods._pages.mods or 1, heldModsPage,
|
||||
"and leaves the mod list on the page the player parked it")
|
||||
eq(dragMods._tabScroll.mods, heldModsAt,
|
||||
"with its region offset held for the return trip")
|
||||
LauncherView.touchreleased(dragMods, 9, sreg.x + 20, sreg.y - 400)
|
||||
|
||||
love.graphics.polygon = love.graphics.polygon or function() end
|
||||
@@ -308,8 +311,8 @@ local view = read("src/import/LauncherView.lua")
|
||||
check(view:find("Kit.scrollBegin(", 1, true) ~= nil,
|
||||
"the panel dispatch opens a scroll region")
|
||||
check(view:find("Kit.scrollEnd(", 1, true) ~= nil, "and closes it")
|
||||
check(view:find("modListWantsWheel", 1, true) ~= nil,
|
||||
"the nested mod list is asked before the region takes a notch")
|
||||
check(view:find("modListWantsWheel", 1, true) == nil,
|
||||
"no nested list steals the wheel from the region any more")
|
||||
check(view:find("start.region", 1, true) ~= nil,
|
||||
"a touch drag that began in the region scrolls the region")
|
||||
check(view:find("Kit.scrollGutter(", 1, true) ~= nil,
|
||||
|
||||
@@ -32,11 +32,13 @@ do
|
||||
"a version id names exactly that game")
|
||||
eq(table.concat(ModTargets.expand("GEN1"), ","), "red,blue,yellow",
|
||||
"gen1 is every Gen 1 game, case-insensitive")
|
||||
eq(table.concat(ModTargets.expand("gen2"), ","), "gold",
|
||||
eq(table.concat(ModTargets.expand("gen2"), ","), "gold,silver",
|
||||
"gen2 is every Gen 2 game")
|
||||
eq(table.concat(ModTargets.expand("silver"), ","), "silver",
|
||||
"and each of them names itself")
|
||||
eq(table.concat(ModTargets.expand("all"), ","),
|
||||
table.concat(GameVersion.ORDER, ","), "all is the launcher order itself")
|
||||
eq(ModTargets.expand("silver"), nil, "a game this engine has no cache for")
|
||||
eq(ModTargets.expand("crystal"), nil, "a game this engine has no cache for")
|
||||
eq(ModTargets.expand("gen9"), nil, "a generation with no games is unknown")
|
||||
eq(ModTargets.expand(7), nil, "a non-string token is not a game")
|
||||
end
|
||||
@@ -56,7 +58,7 @@ end
|
||||
do
|
||||
eq(list(mf({})), "red,blue,yellow",
|
||||
"a manifest with no games key is Gen 1, which is what it was tested as")
|
||||
eq(list(mf({ gen2compat = true })), "red,blue,yellow,gold",
|
||||
eq(list(mf({ gen2compat = true })), "red,blue,yellow,gold,silver",
|
||||
"gen2compat keeps Gen 1 and adds Gen 2")
|
||||
eq(mf({}).gen2compat, false, "and the derived flag agrees")
|
||||
eq(mf({ gen2compat = true }).gen2compat, true, "both ways")
|
||||
@@ -67,14 +69,14 @@ end
|
||||
|
||||
do
|
||||
local gen2 = mf({ games = { "gen2" } })
|
||||
eq(list(gen2), "gold", "games can name Gen 2 alone")
|
||||
eq(list(gen2), "gold,silver", "games can name Gen 2 alone")
|
||||
eq(gen2.gen2compat, true, "which IS the gen2compat claim the gate reads")
|
||||
local both = mf({ games = { "gen1", "gen2" } })
|
||||
eq(list(both), "red,blue,yellow,gold", "or both generations")
|
||||
eq(list(both), "red,blue,yellow,gold,silver", "or both generations")
|
||||
local one = mf({ games = { "blue" } })
|
||||
eq(list(one), "blue", "or one single game")
|
||||
eq(one.gen2compat, false, "a Gen 1 game is not a Gen 2 claim")
|
||||
eq(list(mf({ games = { "red" }, gen2compat = true })), "red,gold",
|
||||
eq(list(mf({ games = { "red" }, gen2compat = true })), "red,gold,silver",
|
||||
"an old gen2compat beside a new games list still adds its game")
|
||||
end
|
||||
|
||||
@@ -224,10 +226,12 @@ do
|
||||
local bad = ModProfile.decode(require("src.core.SaveSerializer").encode({
|
||||
format = "g1rmodlist", formatVersion = 1,
|
||||
profile = { name = "P", enabledByVersion = {
|
||||
gold = { a = true }, silver = { a = true }, red = "nope" } },
|
||||
gold = { a = true }, silver = { a = true }, crystal = { a = true },
|
||||
red = "nope" } },
|
||||
}))
|
||||
eq(bad.enabledByVersion.gold.a, true, "a shared file's known game is kept")
|
||||
eq(bad.enabledByVersion.silver, nil, "an unknown game is dropped on read")
|
||||
eq(bad.enabledByVersion.silver.a, true, "every one of them, not just the first")
|
||||
eq(bad.enabledByVersion.crystal, nil, "an unknown game is dropped on read")
|
||||
eq(bad.enabledByVersion.red, nil, "and so is a bucket that is not a table")
|
||||
end
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ package.loaded["src.import.RomImporter"] = nil
|
||||
RomImporter = require("src.import.RomImporter")
|
||||
|
||||
local function clearSavesInbox()
|
||||
for _, ver in ipairs({ "red", "blue", "yellow", "gold" }) do
|
||||
for _, ver in ipairs({ "red", "blue", "yellow", "gold", "silver" }) do
|
||||
local dir = "imports/saves/" .. ver
|
||||
for _, name in ipairs(love.filesystem.getDirectoryItems(dir) or {}) do
|
||||
love.filesystem.remove(dir .. "/" .. name)
|
||||
@@ -137,6 +137,8 @@ check(createdDirs["imports/saves/yellow"] == true,
|
||||
"RES-01: ensureSavesInboxDir creates imports/saves/yellow/")
|
||||
check(createdDirs["imports/saves/gold"] == true,
|
||||
"RES-01: ensureSavesInboxDir creates imports/saves/gold/")
|
||||
check(createdDirs["imports/saves/silver"] == true,
|
||||
"RES-01: ensureSavesInboxDir creates imports/saves/silver/")
|
||||
|
||||
-- NXSAV-02: notice/hint includes save dir + per-game imports/saves/<version>/ MTP path
|
||||
ri = freshImporter()
|
||||
|
||||
@@ -81,7 +81,8 @@ local function importer(ready)
|
||||
}, RomImporter)
|
||||
end
|
||||
|
||||
local allReady = importer({ red = true, blue = true, yellow = true, gold = true })
|
||||
local allReady = importer({ red = true, blue = true, yellow = true, gold = true,
|
||||
silver = true })
|
||||
allReady:_queueBaseRomScan()
|
||||
eq(allReady.baseRomScan.state, "done", "ready launcher skips discovery")
|
||||
eq(listings, 0, "ready launcher does not enumerate baseroms")
|
||||
@@ -123,7 +124,8 @@ eq(picks, 0, "missing detected ROM does not open the picker unexpectedly")
|
||||
missing:choose("red")
|
||||
eq(picks, 1, "the next import attempt falls back to the native picker")
|
||||
|
||||
local rescanned = importer({ red = true, blue = true, yellow = true, gold = true })
|
||||
local rescanned = importer({ red = true, blue = true, yellow = true, gold = true,
|
||||
silver = true })
|
||||
rescanned.baseRoms.red = { path = "baseroms/z-red.gb", name = "z-red.gb" }
|
||||
rescanned:reimport("red")
|
||||
check(rescanned.baseRoms.red == nil, "re-import clears the detected ROM")
|
||||
|
||||
@@ -31,10 +31,14 @@ require("src.core.Logger").warn = function() end
|
||||
|
||||
local Core = require("src.core.gen2.HallOfFame")
|
||||
local Credits = require("src.ui.gen2.Credits")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local HallOfFame = require("src.ui.gen2.HallOfFame")
|
||||
local Save = require("src.core.gen2.Save")
|
||||
local Screens = require("src.ui.Screens")
|
||||
|
||||
local priorVersion = GameVersion.get()
|
||||
GameVersion.set("gold")
|
||||
|
||||
-- ---- fixtures -------------------------------------------------------------
|
||||
|
||||
local POKEMON = {
|
||||
@@ -711,4 +715,6 @@ else
|
||||
eq(creditsData.palettes[1][1][1], 255, "and set 1 colour 0 is white")
|
||||
end
|
||||
|
||||
GameVersion.set(priorVersion)
|
||||
|
||||
S.finish()
|
||||
|
||||
@@ -76,6 +76,8 @@ local function makeWorld()
|
||||
local game = {
|
||||
data = { audio = {}, screens = registry },
|
||||
save = {
|
||||
version = "gold",
|
||||
generation = 2,
|
||||
player = { name = "GOLD" },
|
||||
party = { { species = "TYPHLOSION", level = 50, hp = 120,
|
||||
otId = 33333 } },
|
||||
|
||||
@@ -28,6 +28,9 @@ love.filesystem = {
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local Save = require("src.core.gen2.Save")
|
||||
|
||||
local priorVersion = GameVersion.get()
|
||||
GameVersion.set("gold")
|
||||
|
||||
local failures, checks = 0, 0
|
||||
local function check(name, got, want)
|
||||
checks = checks + 1
|
||||
@@ -48,6 +51,11 @@ check("main file", main, "save_gold.lua")
|
||||
check("backup file", backup, "save_gold.lua.bak")
|
||||
check("staged file", tmp, "save_gold.lua.tmp")
|
||||
check("gold suffix", GameVersion.saveSuffix("gold"), "_gold")
|
||||
local sMain, sBackup, sTmp = Save.filenames("silver")
|
||||
check("silver main file", sMain, "save_silver.lua")
|
||||
check("silver backup file", sBackup, "save_silver.lua.bak")
|
||||
check("silver staged file", sTmp, "save_silver.lua.tmp")
|
||||
check("silver suffix", GameVersion.saveSuffix("silver"), "_silver")
|
||||
-- Red keeps its historical un-suffixed name, so the two can never collide.
|
||||
check("red is unsuffixed", GameVersion.saveSuffix("red"), "")
|
||||
|
||||
@@ -104,7 +112,13 @@ check("defaults are copied", Save.defaultOptions().textSpeed, "MID")
|
||||
-- A sparse or hand-edited save must come back indexable rather than crashing
|
||||
-- the first screen that reads it.
|
||||
local sparse = Save.normalize({ player = {} })
|
||||
check("normalized version", sparse.version, "gold")
|
||||
check("normalized version", sparse.version, GameVersion.get())
|
||||
check("a Gen 2 version already on the table is kept",
|
||||
Save.normalize({ version = "silver", player = {} }).version, "silver")
|
||||
check("and a Gen 1 one is replaced by the running edition",
|
||||
Save.normalize({ version = "red", player = {} }).version, "gold")
|
||||
check("as is a version this engine does not have",
|
||||
Save.normalize({ version = "crystal", player = {} }).version, "gold")
|
||||
check("party exists", type(sparse.party), "table")
|
||||
check("inventory exists", type(sparse.inventory), "table")
|
||||
check("pokedex seen exists", type(sparse.pokedex.seen), "table")
|
||||
@@ -478,6 +492,46 @@ do
|
||||
files = {}
|
||||
end
|
||||
|
||||
-- ------- the same save, run as Silver
|
||||
--
|
||||
-- The blank name is the edition's own first PlayerNameArray row
|
||||
-- (data/player_names.asm:12-23).
|
||||
do
|
||||
GameVersion.set("silver")
|
||||
files = {}
|
||||
|
||||
local born = Save.newGame({})
|
||||
check("a silver boot stamps silver", born.version, "silver")
|
||||
check("still generation 2", born.generation, 2)
|
||||
check("with the edition's own preset name", born.player.name, "SILVER")
|
||||
check("gold's preset is unchanged", Save.defaultPlayerName("gold"), "GOLD")
|
||||
check("and silver's is its own", Save.defaultPlayerName("silver"), "SILVER")
|
||||
check("the running edition answers with no argument",
|
||||
Save.defaultPlayerName(), "SILVER")
|
||||
|
||||
check("a version-less save takes the running edition",
|
||||
Save.normalize({ player = {} }).version, "silver")
|
||||
check("and a gold save keeps gold on a silver boot",
|
||||
Save.normalize({ version = "gold", player = {} }).version, "gold")
|
||||
|
||||
check("no silver save yet", Save.exists("silver"), false)
|
||||
check("silver save wrote", Save.save(born), true)
|
||||
check("silver save exists now", Save.exists("silver"), true)
|
||||
check("gold is untouched by it", Save.exists("gold"), false)
|
||||
|
||||
local main = Save.filenames("silver")
|
||||
check("the silver file is silver's own", main, "saves/silver/slot1.lua")
|
||||
check("and it is the file on disk", files[main] ~= nil, true)
|
||||
local back = Save.load("silver")
|
||||
check("silver round-trips", back and back.player.name, "SILVER")
|
||||
check("keeping its stamp", back.version, "silver")
|
||||
|
||||
files = {}
|
||||
GameVersion.set("gold")
|
||||
end
|
||||
|
||||
GameVersion.set(priorVersion)
|
||||
|
||||
print(("gen2 save: %d checks, %d failures"):format(checks, failures))
|
||||
-- Raise rather than os.exit: tests/run_tests.lua dofiles this file, so an
|
||||
-- exit here takes the whole tier down with it and silently skips every
|
||||
|
||||
@@ -71,13 +71,17 @@ local data = {
|
||||
maps = {},
|
||||
}
|
||||
|
||||
local function newState()
|
||||
local function newState(version)
|
||||
version = version or "gold"
|
||||
local prior = GameVersion.get()
|
||||
GameVersion.set(version)
|
||||
local S = State.new()
|
||||
S.data = data
|
||||
S.cat = Catalog.build(data)
|
||||
S.save = Save2.newGame()
|
||||
S.version = "gold"
|
||||
S.version = version
|
||||
Gen.ensureBoxes(S.save)
|
||||
GameVersion.set(prior)
|
||||
return S
|
||||
end
|
||||
|
||||
@@ -85,10 +89,19 @@ do
|
||||
GameVersion.set("gold")
|
||||
eq(Gen.of({ generation = 2 }), 2, "Gen.of generation field")
|
||||
eq(Gen.of({ version = "gold" }), 2, "Gen.of version gold")
|
||||
eq(Gen.of({ version = "silver" }), 2, "Gen.of version silver")
|
||||
eq(Gen.of(SaveData.newGame()), 1, "Gen.of gen1 newGame")
|
||||
eq(Gen.of(Save2.newGame()), 2, "Gen.of gold newGame")
|
||||
end
|
||||
|
||||
do
|
||||
GameVersion.set("silver")
|
||||
eq(Save2.newGame().version, "silver", "a silver boot stamps silver")
|
||||
eq(Save2.newGame().player.name, "SILVER", "with silver's preset name")
|
||||
eq(Gen.of(Save2.newGame()), 2, "Gen.of silver newGame")
|
||||
GameVersion.set("gold")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
check(Ops.speciesUsable(S, "CYNDAQUIL"), "spa/spd species is usable")
|
||||
@@ -103,36 +116,37 @@ do
|
||||
check(not Ops.speciesUsable(S1, "BROKEN"), "partial record is not usable")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
for _, version in ipairs({ "gold", "silver" }) do
|
||||
local S = newState(version)
|
||||
Ops.partyAdd(S)
|
||||
eq(#S.save.party, 1, "partyAdd on gold")
|
||||
eq(#S.save.party, 1, "partyAdd on " .. version)
|
||||
local mon = S.save.party[1]
|
||||
eq(mon.species, "CYNDAQUIL", "first catalog species")
|
||||
check(mon.experience ~= nil, "gold mon has experience")
|
||||
check(mon.exp == nil or mon.experience ~= nil, "does not rely on gen1 exp")
|
||||
eq(mon.species, "CYNDAQUIL", version .. " first catalog species")
|
||||
check(mon.experience ~= nil, version .. " mon has experience")
|
||||
check(mon.exp == nil or mon.experience ~= nil,
|
||||
version .. " does not rely on gen1 exp")
|
||||
check(mon.stats.specialAttack and mon.stats.specialDefense,
|
||||
"gold stats have spa/spd")
|
||||
check(mon.happiness ~= nil, "gold mon has happiness")
|
||||
eq(mon.ot, S.save.player.name, "stampOT copies player name")
|
||||
version .. " stats have spa/spd")
|
||||
check(mon.happiness ~= nil, version .. " mon has happiness")
|
||||
eq(mon.ot, S.save.player.name, version .. " stampOT copies player name")
|
||||
|
||||
Ops.setLevel(S, mon, 20)
|
||||
eq(mon.level, 20, "setLevel 20")
|
||||
check(mon.experience > 0, "experience resynced")
|
||||
eq(mon.level, 20, version .. " setLevel 20")
|
||||
check(mon.experience > 0, version .. " experience resynced")
|
||||
|
||||
Ops.setHappiness(S, mon, 200)
|
||||
eq(mon.happiness, 200, "happiness 200")
|
||||
eq(mon.happiness, 200, version .. " happiness 200")
|
||||
Ops.setPokerus(S, mon, 15)
|
||||
eq(mon.pokerus, 15, "pokerus byte")
|
||||
eq(mon.pokerus, 15, version .. " pokerus byte")
|
||||
Ops.setHeldItem(S, mon, "POTION")
|
||||
eq(mon.item, "POTION", "held item")
|
||||
eq(mon.item, "POTION", version .. " held item")
|
||||
|
||||
eq(mon.name, "CYNDAQUIL", "new mon copies species display name")
|
||||
eq(mon.name, "CYNDAQUIL", version .. " new mon copies species display name")
|
||||
Ops.setSpecies(S, mon, "TOTODILE")
|
||||
eq(mon.species, "TOTODILE", "setSpecies id")
|
||||
eq(mon.name, "TOTODILE", "setSpecies rewrites the Gold display name")
|
||||
check(mon.nickname == nil, "setSpecies does not invent a nickname")
|
||||
eq(mon.types[1], "WATER", "setSpecies rewrites copied types")
|
||||
eq(mon.species, "TOTODILE", version .. " setSpecies id")
|
||||
eq(mon.name, "TOTODILE", version .. " setSpecies rewrites the display name")
|
||||
check(mon.nickname == nil, version .. " setSpecies does not invent a nickname")
|
||||
eq(mon.types[1], "WATER", version .. " setSpecies rewrites copied types")
|
||||
end
|
||||
|
||||
do
|
||||
|
||||
@@ -149,12 +149,22 @@ local ok, err = pcall(function()
|
||||
local Ops = require("Ops")
|
||||
check(not ModTargets.supports({}, "gold"),
|
||||
"legacy gen1-only fixture does not support gold")
|
||||
check(not ModTargets.supports({}, "silver"),
|
||||
"nor silver")
|
||||
check(not ModTargets.supports({ games = { "red" } }, "gold"),
|
||||
"explicit gen1 games list does not support gold")
|
||||
check(not ModTargets.supports({ games = { "red" } }, "silver"),
|
||||
"nor silver")
|
||||
check(ModTargets.supports({ games = { "gold" } }, "gold"),
|
||||
"gold-targeted manifest supports gold")
|
||||
check(not ModTargets.supports({ games = { "gold" } }, "silver"),
|
||||
"and names one edition, not the generation")
|
||||
check(ModTargets.supports({ games = { "gold", "silver" } }, "silver"),
|
||||
"a whole-Gen-2 games list supports silver")
|
||||
check(ModTargets.supports({ gen2compat = true }, "gold"),
|
||||
"gen2compat legacy still supports gold")
|
||||
check(ModTargets.supports({ gen2compat = true }, "silver"),
|
||||
"and silver with it")
|
||||
|
||||
local goldS = {
|
||||
data = {
|
||||
|
||||
Reference in New Issue
Block a user