mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
Add Gold save-editor support, rearrange the launcher, and ship a patch-notes modal.
The save editor now routes Gold vs RBY through a generation adapter so boxes, items, events, and maps write the right fields. The launcher layout is shuffled a bit, and patch notes can come from the updater, a packed file, or the iOS sidecar.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
-- Mods "Show for" chips collapse to a dropdown when they cannot all fit.
|
||||
-- Landscape / desktop keep the individual game chips. No pokered cite: the
|
||||
-- launcher is port-only chrome.
|
||||
-- luajit tests/engine/launcher_mod_scope_dropdown.lua
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check = T.check
|
||||
love = love or require("tests.love_stub")
|
||||
|
||||
love.graphics.setLineJoin = love.graphics.setLineJoin or function() end
|
||||
love.graphics.newShader = love.graphics.newShader or function() return {} end
|
||||
|
||||
local Kit = require("src.ui.kit.Kit")
|
||||
local RomImporter = require("src.import.RomImporter")
|
||||
local LauncherView = require("src.import.LauncherView")
|
||||
|
||||
local function window(w, h)
|
||||
love.graphics.getDimensions = function() return w, h end
|
||||
love.graphics.getPixelDimensions = function() return w, h end
|
||||
end
|
||||
|
||||
local function navIds()
|
||||
local ids = {}
|
||||
for i = 1, (Kit._navPrevN or 0) do
|
||||
local slot = Kit._nav[i]
|
||||
if slot and slot.id then ids[slot.id] = true end
|
||||
end
|
||||
return ids
|
||||
end
|
||||
|
||||
local function drawMods(W, H)
|
||||
window(W, H)
|
||||
local imp = RomImporter.new(function() end, { launcher = true })
|
||||
imp.tab = "mods"
|
||||
imp.ready = { red = true, blue = true, yellow = true, gold = true }
|
||||
local ok, err = pcall(LauncherView.draw, imp)
|
||||
check(ok, ("%dx%d mods draws: %s"):format(W, H, tostring(err)))
|
||||
return navIds()
|
||||
end
|
||||
|
||||
local portrait = drawMods(360, 780)
|
||||
check(portrait["mod-scope-menu"] == true,
|
||||
"portrait: Show-for collapses to a dropdown when the chips cannot all fit")
|
||||
check(portrait["mod-scope-gold"] == nil
|
||||
and portrait["mod-scope-red"] == nil
|
||||
and portrait["mod-scope-blue"] == nil
|
||||
and portrait["mod-scope-yellow"] == nil,
|
||||
"portrait: individual version chips are not drawn beside the dropdown")
|
||||
|
||||
local desktop = drawMods(1280, 720)
|
||||
check(desktop["mod-scope-menu"] == nil,
|
||||
"desktop: chips fit, so there is no dropdown")
|
||||
check(desktop["mod-scope-all"] == true
|
||||
and desktop["mod-scope-red"] == true
|
||||
and desktop["mod-scope-blue"] == true
|
||||
and desktop["mod-scope-yellow"] == true
|
||||
and desktop["mod-scope-gold"] == true,
|
||||
"desktop: every Show-for chip stays reachable")
|
||||
|
||||
print("ok launcher mod scope dropdown")
|
||||
@@ -23,7 +23,7 @@ local function importer(field)
|
||||
end
|
||||
|
||||
local modalFields = {
|
||||
"_modConfirm", "_modVersions", "_modReleaseNotes", "_findDetails",
|
||||
"_modConfirm", "_modVersions", "_modReleaseNotes", "_appPatchNotes", "_findDetails",
|
||||
}
|
||||
|
||||
for _, field in ipairs(modalFields) do
|
||||
@@ -77,6 +77,12 @@ do
|
||||
imp:keypressed("escape")
|
||||
eq(imp._modReleaseNotes, nil, "Escape closes release notes")
|
||||
end
|
||||
do
|
||||
resetFocus()
|
||||
local imp = importer("_appPatchNotes")
|
||||
imp:keypressed("escape")
|
||||
eq(imp._appPatchNotes, nil, "Escape closes patch notes")
|
||||
end
|
||||
do
|
||||
resetFocus()
|
||||
local imp = importer("_modVersions")
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
-- Launcher footer Patch notes control. The GitHub release body is already
|
||||
-- fetched by the updater; this is the in-app viewer for it.
|
||||
-- luajit tests/engine/launcher_patch_notes.lua
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
love = love or require("tests.love_stub")
|
||||
|
||||
love.graphics.setLineJoin = love.graphics.setLineJoin or function() end
|
||||
love.graphics.newShader = love.graphics.newShader or function() return {} end
|
||||
|
||||
local Kit = require("src.ui.kit.Kit")
|
||||
local RomImporter = require("src.import.RomImporter")
|
||||
local LauncherView = require("src.import.LauncherView")
|
||||
local PatchNotes = require("src.update.PatchNotes")
|
||||
|
||||
local function window(w, h)
|
||||
love.graphics.getDimensions = function() return w, h end
|
||||
love.graphics.getPixelDimensions = function() return w, h end
|
||||
end
|
||||
|
||||
local function freshLauncher()
|
||||
return RomImporter.new(function() end, { launcher = true })
|
||||
end
|
||||
|
||||
local realPrint = love.graphics.print
|
||||
local function drawAndCapture(imp)
|
||||
local seen = {}
|
||||
love.graphics.print = function(str, ...)
|
||||
seen[#seen + 1] = tostring(str)
|
||||
return realPrint(str, ...)
|
||||
end
|
||||
local ok, err = pcall(LauncherView.draw, imp)
|
||||
love.graphics.print = realPrint
|
||||
check(ok, "the frame draws: " .. tostring(err))
|
||||
return table.concat(seen, "\n")
|
||||
end
|
||||
|
||||
window(1280, 720)
|
||||
local imp = freshLauncher()
|
||||
local text = drawAndCapture(imp)
|
||||
check(text:find("Patch notes", 1, true) ~= nil,
|
||||
"desktop footer prints Patch notes")
|
||||
|
||||
window(360, 780)
|
||||
local phone = freshLauncher()
|
||||
check(drawAndCapture(phone):find("Patch notes", 1, true) ~= nil,
|
||||
"portrait footer still prints Patch notes")
|
||||
|
||||
do
|
||||
local body, ver = PatchNotes.body({
|
||||
state = function()
|
||||
return { notes = "## Issues closed\n\n- #12 cart padding", latest = "1.4.2" }
|
||||
end,
|
||||
})
|
||||
eq(body, "## Issues closed\n\n- #12 cart padding",
|
||||
"PatchNotes prefers the updater's GitHub body")
|
||||
eq(ver, "1.4.2", "PatchNotes carries the release version")
|
||||
end
|
||||
|
||||
do
|
||||
local body, ver = PatchNotes.body(nil)
|
||||
check(type(body) == "string" and body:find("Issues closed", 1, true),
|
||||
"without a check result PatchNotes uses the stashed iOS app-repo notes")
|
||||
check(type(ver) == "string" and ver:find("^%d+%.%d+%.%d+$") ~= nil,
|
||||
"stashed notes name a release version")
|
||||
end
|
||||
|
||||
do
|
||||
local f = assert(io.open("mobile/ios/app-repo.json", "rb"))
|
||||
local list = PatchNotes.parseRepo(f:read("*a"))
|
||||
f:close()
|
||||
check(#list >= 2, "app-repo.json stashes more than one release")
|
||||
local notes, ver = PatchNotes.fromRepo(list[2].version)
|
||||
eq(ver, list[2].version, "fromRepo can pick a specific stashed version")
|
||||
eq(notes, list[2].notes, "fromRepo returns that version's notes")
|
||||
end
|
||||
|
||||
imp._appPatchNotes = true
|
||||
local modal = drawAndCapture(imp)
|
||||
check(modal:find("Patch notes", 1, true) ~= nil, "the modal titles itself")
|
||||
check(modal:find("Close", 1, true) ~= nil, "the modal can be closed")
|
||||
|
||||
imp:keypressed("escape")
|
||||
eq(imp._appPatchNotes, nil, "Escape dismisses patch notes")
|
||||
|
||||
T.finish("launcher patch notes")
|
||||
@@ -32,6 +32,15 @@ eq(rel.payloadName, "gen1recomp-1.4.2.love", "payload name derived from version"
|
||||
eq(rel.payload.url, "http://x/love", "payload asset url picked")
|
||||
eq(rel.payload.size, 12345, "payload asset size picked")
|
||||
eq(rel.sums.url, "http://x/sums", "sums asset url picked")
|
||||
eq(rel.notes, "", "missing release body becomes empty notes")
|
||||
|
||||
local withNotes = Check.parseRelease(Json.encode({
|
||||
tag_name = "v1.4.2",
|
||||
body = "## Issues closed\n\n- #1 cart padding",
|
||||
assets = {},
|
||||
}))
|
||||
eq(withNotes.notes, "## Issues closed\n\n- #1 cart padding",
|
||||
"parseRelease keeps the GitHub release body")
|
||||
|
||||
-- a newer release that ships no .love yet: parses, but the payload/sums are nil
|
||||
-- so the worker will route to needs_full rather than an in-place update
|
||||
|
||||
@@ -287,6 +287,7 @@ eq(rel.payload.url, "http://x/love", "parseRelease picks the payload asset url")
|
||||
eq(rel.payload.size, 12345, "parseRelease picks the payload asset size")
|
||||
eq(rel.sums.url, "http://x/sums", "parseRelease picks the sums asset url")
|
||||
eq(rel.sums.size, 99, "parseRelease picks the sums asset size")
|
||||
eq(rel.notes, "", "parseRelease treats a missing body as empty notes")
|
||||
|
||||
-- a release with no .love yet still parses; payload/sums are nil so the worker
|
||||
-- routes to a full reinstall rather than an in-place update
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
-- tests/save_editor_task7_tests.lua
|
||||
-- tests/save_editor_task8_tests.lua
|
||||
-- tests/save_editor_mod_tests.lua
|
||||
-- tests/save_editor_gen2_tests.lua
|
||||
-- See tools/save-editor/README.md for the full list.
|
||||
--
|
||||
-- All of them drive tools/save-editor/Ops.lua rather than clicking pixel
|
||||
|
||||
@@ -3475,6 +3475,8 @@ do
|
||||
local lua = (arg and arg[-1]) or "luajit"
|
||||
local status = os.execute(("%q tests/save_editor_mod_tests.lua"):format(lua))
|
||||
check(status == 0 or status == true, "save_editor_mod_tests suite")
|
||||
status = os.execute(("%q tests/save_editor_gen2_tests.lua"):format(lua))
|
||||
check(status == 0 or status == true, "save_editor_gen2_tests suite")
|
||||
end
|
||||
|
||||
-- ---------------------------------------------- input hold regressions
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
-- Headless Gold save-editor rules. Run from repo root:
|
||||
-- luajit tests/save_editor_gen2_tests.lua
|
||||
package.path = package.path .. ";./?.lua;./?/init.lua;./tools/save-editor/?.lua"
|
||||
.. ";./tools/save-editor/panels/?.lua"
|
||||
|
||||
local love_stub = require("tests.love_stub")
|
||||
love = love_stub
|
||||
|
||||
local passed, failed = 0, 0
|
||||
|
||||
local function check(cond, msg)
|
||||
if cond then
|
||||
passed = passed + 1
|
||||
else
|
||||
failed = failed + 1
|
||||
print("FAIL: " .. msg)
|
||||
end
|
||||
end
|
||||
|
||||
local function eq(a, b, msg)
|
||||
check(a == b, msg .. string.format(" (got %s, want %s)", tostring(a), tostring(b)))
|
||||
end
|
||||
|
||||
print("== save editor gen2 tests ==")
|
||||
|
||||
local Gen = require("Gen")
|
||||
local Catalog = require("Catalog")
|
||||
local MonOps = require("MonOps")
|
||||
local Ops = require("Ops")
|
||||
local State = require("State")
|
||||
local Save2 = require("src.core.gen2.Save")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
|
||||
local data = {
|
||||
pokemon = {
|
||||
CYNDAQUIL = {
|
||||
id = "CYNDAQUIL", name = "CYNDAQUIL", dex = 155,
|
||||
types = { "FIRE" },
|
||||
baseStats = {
|
||||
hp = 39, attack = 52, defense = 43, speed = 65,
|
||||
specialAttack = 60, specialDefense = 50,
|
||||
},
|
||||
catchRate = 45, baseExp = 65,
|
||||
growthRate = "MEDIUM_FAST",
|
||||
levelMoves = { { level = 1, move = "TACKLE" } },
|
||||
genderRatio = 31,
|
||||
},
|
||||
TOTODILE = {
|
||||
id = "TOTODILE", name = "TOTODILE", dex = 158,
|
||||
types = { "WATER" },
|
||||
baseStats = {
|
||||
hp = 50, attack = 65, defense = 64, speed = 43,
|
||||
specialAttack = 44, specialDefense = 48,
|
||||
},
|
||||
catchRate = 45, baseExp = 66,
|
||||
growthRate = "MEDIUM_FAST",
|
||||
levelMoves = { { level = 1, move = "SCRATCH" } },
|
||||
genderRatio = 31,
|
||||
},
|
||||
},
|
||||
moves = {
|
||||
TACKLE = { pp = 35 },
|
||||
SCRATCH = { pp = 35 },
|
||||
},
|
||||
items = {
|
||||
POTION = { pocket = "ITEM" },
|
||||
MASTER_BALL = { pocket = "BALL" },
|
||||
FLOWER_MAIL = { pocket = "ITEM" },
|
||||
},
|
||||
maps = {},
|
||||
}
|
||||
|
||||
local function newState()
|
||||
local S = State.new()
|
||||
S.data = data
|
||||
S.cat = Catalog.build(data)
|
||||
S.save = Save2.newGame()
|
||||
S.version = "gold"
|
||||
Gen.ensureBoxes(S.save)
|
||||
return S
|
||||
end
|
||||
|
||||
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(SaveData.newGame()), 1, "Gen.of gen1 newGame")
|
||||
eq(Gen.of(Save2.newGame()), 2, "Gen.of gold newGame")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
check(Ops.speciesUsable(S, "CYNDAQUIL"), "spa/spd species is usable")
|
||||
local S1 = State.new()
|
||||
S1.data = {
|
||||
pokemon = {
|
||||
PIDGEY = { baseStats = { hp = 40, attack = 45, defense = 40, speed = 56, special = 35 } },
|
||||
BROKEN = { baseStats = { hp = 1 } },
|
||||
},
|
||||
}
|
||||
check(Ops.speciesUsable(S1, "PIDGEY"), "gen1 special species is usable")
|
||||
check(not Ops.speciesUsable(S1, "BROKEN"), "partial record is not usable")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
Ops.partyAdd(S)
|
||||
eq(#S.save.party, 1, "partyAdd on gold")
|
||||
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")
|
||||
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")
|
||||
|
||||
Ops.setLevel(S, mon, 20)
|
||||
eq(mon.level, 20, "setLevel 20")
|
||||
check(mon.experience > 0, "experience resynced")
|
||||
|
||||
Ops.setHappiness(S, mon, 200)
|
||||
eq(mon.happiness, 200, "happiness 200")
|
||||
Ops.setPokerus(S, mon, 15)
|
||||
eq(mon.pokerus, 15, "pokerus byte")
|
||||
Ops.setHeldItem(S, mon, "POTION")
|
||||
eq(mon.item, "POTION", "held item")
|
||||
|
||||
eq(mon.name, "CYNDAQUIL", "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")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
Ops.partyAdd(S)
|
||||
Ops.partyAdd(S)
|
||||
local Mail = require("src.core.gen2.Mail")
|
||||
Mail.set(S.save, 1, Mail.entry("FLOWER_MAIL", "hi", "GOLD", 1, "CYNDAQUIL"))
|
||||
Mail.set(S.save, 2, Mail.entry("SURF_MAIL", "bye", "GOLD", 1, "CYNDAQUIL"))
|
||||
S.selectedParty = 1
|
||||
Ops.partyMove(S, 1)
|
||||
eq(Mail.state(S.save).party[1].message, "bye", "partyMove carries mail with the mon")
|
||||
eq(Mail.state(S.save).party[2].message, "hi", "partyMove swaps the other letter")
|
||||
S.selectedParty = 1
|
||||
check(Ops.partyRemove(S) == false, "partyRemove arms")
|
||||
check(Ops.partyRemove(S) == true, "partyRemove commits")
|
||||
eq(Mail.state(S.save).party[1].message, "hi", "partyRemove shifts leftover mail up")
|
||||
check(Mail.state(S.save).party[2] == nil, "partyRemove clears the vacated slot")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
Ops.partyAdd(S)
|
||||
local mon = S.save.party[1]
|
||||
local Mail = require("src.core.gen2.Mail")
|
||||
Ops.setHeldItem(S, mon, "FLOWER_MAIL")
|
||||
eq(mon.item, "FLOWER_MAIL", "held mail item")
|
||||
local letter = Mail.state(S.save).party[1]
|
||||
check(letter ~= nil, "giving mail writes sPartyMail")
|
||||
eq(letter.species, "CYNDAQUIL", "new letter stamps current species")
|
||||
Ops.setSpecies(S, mon, "TOTODILE")
|
||||
eq(Mail.state(S.save).party[1].species, "TOTODILE",
|
||||
"setSpecies updates the letter's species copy")
|
||||
Ops.setHeldItem(S, mon, "POTION")
|
||||
check(Mail.state(S.save).party[1] == nil, "non-mail held item drops the letter")
|
||||
end
|
||||
|
||||
do
|
||||
local Mon = require("src.battle.gen2.Mon")
|
||||
local S = newState()
|
||||
local stale = Mon.new(data, "CYNDAQUIL", 5)
|
||||
stale.species = "TOTODILE"
|
||||
stale.name = "CYNDAQUIL"
|
||||
stale.types = { "FIRE" }
|
||||
S.save.dayCare = { man = { mon = stale }, lady = {} }
|
||||
Mon.syncSaveIdentity(S.save, data)
|
||||
eq(stale.name, "TOTODILE", "syncSaveIdentity rewrites Day-Care display name")
|
||||
eq(stale.types[1], "WATER", "syncSaveIdentity rewrites Day-Care types")
|
||||
eq(Mon.displayName({ nickname = nil, name = "ABRA", species = "RAYQUAZA" }),
|
||||
"ABRA", "displayName prefers the species copy over the id")
|
||||
eq(Mon.displayName({ nickname = "BOB", name = "ABRA", species = "RAYQUAZA" }),
|
||||
"BOB", "displayName prefers nickname")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
eq(Ops.boxCount(S), 14, "14 gold boxes")
|
||||
eq(Ops.boxCapacity(S), 20, "20 per box")
|
||||
Ops.boxAdd(S)
|
||||
eq(#Ops.boxes(S)[1], 1, "boxAdd into box 1")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
Ops.partyAdd(S)
|
||||
S.save.party[1].hp = 0
|
||||
Ops.partyAdd(S)
|
||||
S.selectedParty = 1
|
||||
S.selectedBox = 1
|
||||
local ok = Ops.deposit(S)
|
||||
check(ok, "deposit fainted mon while a healthy remains")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
Ops.partyAdd(S)
|
||||
Ops.partyAdd(S)
|
||||
S.selectedParty = 1
|
||||
S.selectedBox = 1
|
||||
local ok = Ops.deposit(S)
|
||||
check(ok, "deposit one of two healthy mons")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
Ops.partyAdd(S)
|
||||
S.selectedParty = 1
|
||||
S.selectedBox = 1
|
||||
local ok = Ops.deposit(S)
|
||||
check(not ok, "refuse depositing last healthy mon")
|
||||
check(S.status:lower():find("last", 1, true) or S.status:find("POKéMON")
|
||||
or S.status:find("POKEMON") or S.status:find("last"),
|
||||
"deposit refusal names the last-healthy rule: " .. tostring(S.status))
|
||||
eq(#S.save.party, 1, "party still has the mon")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
eq(Gen.money(S.save), 3000, "gold start money on player")
|
||||
Ops.addMoney(S, 1000)
|
||||
eq(S.save.player.money, 4000, "money writes player.money")
|
||||
check(S.save.money == nil or S.save.money ~= 4000, "does not write save.money")
|
||||
Ops.maxMoney(S)
|
||||
eq(S.save.player.money, 999999, "money cap")
|
||||
Ops.addCoins(S, 250)
|
||||
eq(S.save.player.coins, 250, "coins write player.coins")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
check(not Gen.hasBadge(S.save, "ZEPHYR"), "no zephyr yet")
|
||||
Ops.toggleBadge(S, "ZEPHYR")
|
||||
check(Gen.hasBadge(S.save, "ZEPHYR"), "zephyr earned")
|
||||
check(S.save.player.badges.ZEPHYR, "stored on player.badges")
|
||||
Ops.toggleBadge(S, "BOULDER")
|
||||
check(S.save.player.kantoBadges.BOULDER, "kanto badge store")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
Ops.dexOwned(S, "CYNDAQUIL", true)
|
||||
check(S.save.pokedex.caught.CYNDAQUIL, "dex writes caught")
|
||||
check(S.save.pokedex.owned == nil or S.save.pokedex.owned.CYNDAQUIL == nil,
|
||||
"does not write owned on gold")
|
||||
check(S.save.pokedex.seen.CYNDAQUIL, "owned implies seen")
|
||||
local _, owned = Ops.dexCounts(S)
|
||||
eq(owned, 1, "dexCounts reads caught")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
local name = "EVENT_BEAT_FALKNER"
|
||||
Ops.setFlag(S, name, true)
|
||||
check(Gen.getFlag(S.save, name), "gold EVENT_ sets bitfield")
|
||||
check(S.save.flags[name] == nil, "numeric flags are not string keys")
|
||||
Ops.setFlag(S, "MOD_EDITMON_GIFT", true)
|
||||
check(S.save.flags.MOD_EDITMON_GIFT, "mod flags stay named on gold")
|
||||
end
|
||||
|
||||
do
|
||||
local S = newState()
|
||||
S.mapId = "NEW_BARK_TOWN"
|
||||
S.mapClickCell = { cx = 4, cy = 5 }
|
||||
Ops.setPlayerHere(S)
|
||||
eq(S.save.position.map, "NEW_BARK_TOWN", "position.map")
|
||||
eq(S.save.position.x, 4, "position.x")
|
||||
eq(S.save.position.y, 5, "position.y")
|
||||
check(S.save.player.map == nil or S.save.player.map ~= "NEW_BARK_TOWN",
|
||||
"does not write player.map on gold")
|
||||
end
|
||||
|
||||
do
|
||||
local encoded = SaveData.encode(Save2.newGame())
|
||||
local back = SaveData.decode(encoded)
|
||||
eq(back.generation, 2, "round-trip keeps generation 2")
|
||||
end
|
||||
|
||||
do
|
||||
local names = Catalog.goldEventList()
|
||||
local hasFalkner = false
|
||||
for _, n in ipairs(names) do
|
||||
if n == "EVENT_BEAT_FALKNER" then hasFalkner = true break end
|
||||
end
|
||||
check(hasFalkner, "gold event list includes EVENT_BEAT_FALKNER")
|
||||
end
|
||||
|
||||
do
|
||||
local maps = Gen.maps({ gen2Maps = { AZALEA_GYM = true }, maps = { PALLET_TOWN = true } })
|
||||
check(maps.AZALEA_GYM, "Gen.maps includes gen2Maps")
|
||||
check(maps.PALLET_TOWN, "Gen.maps keeps Data:load maps beside gen2Maps")
|
||||
check(Gen.maps({ maps = { PALLET_TOWN = true } }).PALLET_TOWN,
|
||||
"Gen.maps falls back to maps")
|
||||
local mansion = Gen.maps({
|
||||
maps = {
|
||||
CELADON_MANSION_2F = { id = "CELADON_MANSION_2F", width = 4, height = 5 },
|
||||
},
|
||||
gen2Maps = {
|
||||
CELADON_MANSION_2F = { objects = { { name = "NPC" } } },
|
||||
BERRY_FARM = { id = "BERRY_FARM", width = 19, height = 12 },
|
||||
},
|
||||
})
|
||||
eq(mansion.CELADON_MANSION_2F.width, 4,
|
||||
"Gen.maps keeps extractor width under a gen2Maps objects patch")
|
||||
eq(mansion.CELADON_MANSION_2F.objects[1].name, "NPC",
|
||||
"Gen.maps still applies the gen2Maps patch fields")
|
||||
eq(mansion.BERRY_FARM.width, 19, "Gen.maps keeps mod maps only on gen2Maps")
|
||||
local bound = Gen.bindGoldData({ maps = { A = true }, tilesets = { T = true } })
|
||||
check(bound.gen2Maps == bound.maps, "bindGoldData aliases gen2Maps")
|
||||
check(bound.gen2Tilesets == bound.tilesets, "bindGoldData aliases gen2Tilesets")
|
||||
check(Gen.tilesets({ gen2Tilesets = { TILESET_GYM = true } }).TILESET_GYM,
|
||||
"Gen.tilesets prefers gen2Tilesets")
|
||||
end
|
||||
|
||||
do
|
||||
local Map2 = require("src.world.gen2.Map")
|
||||
local MapPreview = require("src.world.gen2.MapPreview")
|
||||
local def = {
|
||||
id = "AZALEA_GYM", tileset = "TILESET_GYM",
|
||||
width = 1, height = 1, blocks = { 1 }, borderBlock = 1,
|
||||
warps = {}, environment = "INDOOR",
|
||||
}
|
||||
local tileset = {
|
||||
id = "TILESET_GYM",
|
||||
image = "assets/generated/tilesets/gym.png",
|
||||
tilesPerRow = 16,
|
||||
blocks = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
|
||||
}
|
||||
local map = Map2.new(def, tileset)
|
||||
check(map.renderer == nil, "Map2 does not ship a renderer")
|
||||
local baker = MapPreview.baker({ tilesets = { TILESET_GYM = tileset } })
|
||||
local renderer = MapPreview.renderer(baker, map)
|
||||
check(renderer ~= nil and renderer.draw ~= nil,
|
||||
"MapPreview attaches a draw for a Gold map")
|
||||
end
|
||||
|
||||
print(string.format("save editor gen2 tests: %d passed, %d failed", passed, failed))
|
||||
if failed > 0 then os.exit(1) end
|
||||
@@ -142,6 +142,41 @@ local ok, err = pcall(function()
|
||||
local report = SaveData.validate(probe, Data)
|
||||
check(#report.lostMons == 0 and probe.party[1].species == "EDITMON",
|
||||
"validate keeps the modded mon while the mod is enabled")
|
||||
|
||||
-- Gold-targeted mods: spa/spd records are usable, and a Gen 1-only
|
||||
-- manifest stays out of Gold (no ROM cache / App.load("gold") required).
|
||||
local ModTargets = require("src.mods.ModTargets")
|
||||
local Ops = require("Ops")
|
||||
check(not ModTargets.supports({}, "gold"),
|
||||
"legacy gen1-only fixture does not support gold")
|
||||
check(not ModTargets.supports({ games = { "red" } }, "gold"),
|
||||
"explicit gen1 games list does not support gold")
|
||||
check(ModTargets.supports({ games = { "gold" } }, "gold"),
|
||||
"gold-targeted manifest supports gold")
|
||||
check(ModTargets.supports({ gen2compat = true }, "gold"),
|
||||
"gen2compat legacy still supports gold")
|
||||
|
||||
local goldS = {
|
||||
data = {
|
||||
pokemon = {
|
||||
EDITMON = {
|
||||
baseStats = {
|
||||
hp = 50, attack = 50, defense = 50, speed = 50,
|
||||
specialAttack = 50, specialDefense = 50,
|
||||
},
|
||||
},
|
||||
G1ONLY = {
|
||||
baseStats = {
|
||||
hp = 50, attack = 50, defense = 50, speed = 50, special = 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
check(Ops.speciesUsable(goldS, "EDITMON"),
|
||||
"spa/spd EDITMON is usable on gold")
|
||||
check(Ops.speciesUsable(goldS, "G1ONLY"),
|
||||
"gen1 special record remains usable (dual-key gate)")
|
||||
end)
|
||||
|
||||
os.remove(MOD_ROOT .. "/main.lua")
|
||||
@@ -154,7 +189,7 @@ os.remove(tmpPath)
|
||||
love.filesystem = savedFS
|
||||
-- leave shared singletons the way we found them (the fixture merged one
|
||||
-- record into Data.pokemon)
|
||||
Data.pokemon.EDITMON = nil
|
||||
if Data.pokemon then Data.pokemon.EDITMON = nil end
|
||||
Assets.loader = savedBridge
|
||||
Assets.invalidate()
|
||||
Runtime.install(savedEvents, savedHooks, savedErrors)
|
||||
|
||||
Reference in New Issue
Block a user