mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
198 lines
8.1 KiB
Lua
198 lines
8.1 KiB
Lua
-- Manual check of the mod manager's PROFILES tab: seeded PROFILE 1, applying a
|
|
-- profile back over a staged toggle, and EXPORT.. writing a .g1rmodlist (#593).
|
|
-- The manager has no pokered analogue; the stand cell comes from pokered
|
|
-- data/maps/objects/PalletTown.asm (warp_event 5, 5 is Red's front door, so the
|
|
-- cell below it is the doorstep), with a walkable-neighbour fallback.
|
|
-- POKEPORT_DRIVER=tests/drivers/mod_profiles_bug593_test.lua POKEPORT_IDENTITY=bug593 POKEPORT_DEV=1 love .
|
|
-- Do not set POKEPORT_SPEED: fast-forward scales the logic clock only, so taps,
|
|
-- the notice timer and the confirm blip stop lining up.
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local Screens = require("src.ui.Screens")
|
|
local ModProfile = require("src.mods.ModProfile")
|
|
|
|
local MAP = "PALLET_TOWN"
|
|
local STAND = { x = 5, y = 6, facing = "up" }
|
|
|
|
local function check(label, ok)
|
|
U.log(ok and "PASS" or "FAIL", label)
|
|
return ok
|
|
end
|
|
|
|
-- park somewhere ordinary first: the manager is opened over the overworld in
|
|
-- real play (F10, src/core/Game.lua:446) and draws on top of it
|
|
U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing)
|
|
U.wait(10)
|
|
local ow = game.overworld
|
|
if ow and not ow.map:isWalkableCell(STAND.x, STAND.y) then
|
|
-- a map edit or a mod moved the doorstep: any free neighbour will do,
|
|
-- nothing here depends on the exact cell
|
|
for _, d in ipairs({ { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } }) do
|
|
local cx, cy = STAND.x + d[1], STAND.y + d[2]
|
|
if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then
|
|
U.log(("doorstep (%d, %d) is blocked, standing on"):format(STAND.x, STAND.y),
|
|
cx, cy)
|
|
U.teleport(game, MAP, cx, cy, "up")
|
|
U.wait(10)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local opts = (game.save and game.save.options) or {}
|
|
local status = (game.mods and game.mods.status and game.mods:status())
|
|
or game.modStatus or { available = {} }
|
|
-- two installed mods is the floor for this check: with one mod a profile
|
|
-- swap cannot be told apart from the mod simply staying on
|
|
check("at least two mods installed under mods/", #(status.available or {}) >= 2)
|
|
for _, m in ipairs(status.available or {}) do
|
|
U.log("installed mod:", m.id, m.enabled and "on" or "off")
|
|
end
|
|
check("sfxVol is not zero, so the A press blips", (opts.sfxVol or 0) > 0)
|
|
|
|
-- open the manager the way F10 does (Game:keypressed pushes the same id)
|
|
Screens.push(game, "ManagerState")
|
|
U.wait(10)
|
|
local mgr = game.stack:top()
|
|
check("the mod manager is on top of the stack", mgr and mgr.screenId == "ManagerState")
|
|
if not (mgr and mgr.screenId == "ManagerState") then
|
|
while true do coroutine.yield() end
|
|
end
|
|
|
|
-- right once: MODS -> PROFILES
|
|
U.tap(game, "right")
|
|
U.wait(6)
|
|
local rows = mgr:rowsForScreen()
|
|
check("right lands on the PROFILES tab", mgr.tab == 2)
|
|
local labels = {}
|
|
for i, r in ipairs(rows) do labels[i] = r.label end
|
|
U.log("profile rows:", table.concat(labels, " / "))
|
|
|
|
local first = rows[1]
|
|
check("PROFILE 1 was seeded from the setup that was already live",
|
|
first and first.profile ~= nil and first.label == "PROFILE 1")
|
|
check("it is marked active with the ! gutter glyph",
|
|
first and first.glyph == "!" and opts.activeProfile == "PROFILE 1")
|
|
-- the near-miss: profileRows grew by EXPORT.. and IMPORT.., so a snapCursor
|
|
-- or moveCursor still assuming the old three-row tail parks the cursor on the
|
|
-- wrong row and A fires the wrong action
|
|
check("the cursor sits on the first profile, not somewhere down the tail",
|
|
mgr.cursor == 1 and (mgr:focusedRow() or {}).label == "PROFILE 1")
|
|
local tail = {}
|
|
for i = #rows - 3, #rows do tail[#tail + 1] = rows[i] and rows[i].label end
|
|
check("the tail reads SAVE CURRENT AS.. / EXPORT.. / IMPORT.. / [AD-HOC]",
|
|
tail[1] == "SAVE CURRENT AS.." and tail[2] == "EXPORT.."
|
|
and tail[3] == "IMPORT.." and (tail[4] or ""):find("AD-HOC", 1, true) ~= nil)
|
|
|
|
-- stage a mod off on the MODS tab so applying PROFILE 1 has something to undo.
|
|
-- Pick one nothing else depends on and that is not experimental, so the toggle
|
|
-- commits straight away instead of opening the cascade or confirm overlay.
|
|
local target
|
|
for _, m in ipairs(status.available or {}) do
|
|
if m.enabled and not m.experimental then
|
|
local needed = false
|
|
for _, other in ipairs(status.available or {}) do
|
|
for _, spec in ipairs(other.dependencySpecs or {}) do
|
|
if spec.id == m.id then needed = true end
|
|
end
|
|
end
|
|
if not needed then target = m break end
|
|
end
|
|
end
|
|
check("found an enabled mod with no dependents to toggle", target ~= nil)
|
|
|
|
-- refresh() rebuilds status.available, so hold the id and re-look it up
|
|
-- through mgr.byId rather than keeping the manifest table from before
|
|
local targetId = target and target.id
|
|
local function live()
|
|
return targetId and mgr.byId[targetId] or nil
|
|
end
|
|
|
|
if targetId then
|
|
U.tap(game, "left")
|
|
U.wait(6)
|
|
for _ = 1, 40 do
|
|
local row = mgr:focusedRow()
|
|
if row and row.mod and row.mod.id == targetId then break end
|
|
U.tap(game, "down")
|
|
U.wait(2)
|
|
end
|
|
local row = mgr:focusedRow()
|
|
check("cursor reached " .. targetId .. " on the MODS tab",
|
|
row and row.mod and row.mod.id == targetId)
|
|
U.tap(game, "select")
|
|
U.wait(6)
|
|
check("SELECT staged " .. targetId .. " off",
|
|
mgr.overlay == nil and (live() or {}).enabled == false)
|
|
|
|
U.tap(game, "right")
|
|
U.wait(6)
|
|
check("back on PROFILES with the cursor on PROFILE 1",
|
|
mgr.tab == 2 and mgr.cursor == 1
|
|
and (mgr:focusedRow() or {}).label == "PROFILE 1")
|
|
U.tap(game, "a")
|
|
U.wait(10)
|
|
check("applying PROFILE 1 re-staged " .. targetId,
|
|
(live() or {}).enabled == true)
|
|
U.log("footer notice reads:", tostring(mgr.notice))
|
|
check("the footer says PROFILE STAGED", mgr.notice == "PROFILE STAGED")
|
|
end
|
|
|
|
-- walk down to EXPORT.. by taps, so a cursor that skips shows up here.
|
|
-- From the top of the list that is every profile row after the first plus
|
|
-- SAVE CURRENT AS.., i.e. #profiles + 1 presses.
|
|
local profileCount = 0
|
|
for _, r in ipairs(mgr:rowsForScreen()) do
|
|
if r.profile then profileCount = profileCount + 1 end
|
|
end
|
|
local wantSteps = profileCount + 1
|
|
local steps = 0
|
|
for _ = 1, 10 do
|
|
local row = mgr:focusedRow()
|
|
if row and row.exportProfile then break end
|
|
U.tap(game, "down")
|
|
U.wait(2)
|
|
steps = steps + 1
|
|
end
|
|
local exportRow = mgr:focusedRow()
|
|
check(("EXPORT.. is %d presses below PROFILE 1"):format(wantSteps),
|
|
exportRow ~= nil and exportRow.exportProfile == true and steps == wantSteps)
|
|
U.tap(game, "a")
|
|
U.wait(10)
|
|
U.log("footer notice reads:", tostring(mgr.notice))
|
|
check("the footer says SAVED PROFILE 1", mgr.notice == "SAVED PROFILE 1")
|
|
|
|
local rel = ModProfile.fileFor("PROFILE 1")
|
|
local info = love.filesystem.getInfo and love.filesystem.getInfo(rel)
|
|
check(rel .. " exists in the save dir", info ~= nil)
|
|
if info then
|
|
local body = love.filesystem.read(rel)
|
|
local back = ModProfile.decode(body)
|
|
check("the exported file decodes back to PROFILE 1",
|
|
back ~= nil and back.name == "PROFILE 1")
|
|
U.log("wrote", love.filesystem.getSaveDirectory() .. "/" .. rel,
|
|
info.size and (info.size .. " bytes") or "")
|
|
end
|
|
|
|
-- leave the cursor at the top of the list for whoever takes over
|
|
for _ = 1, steps do
|
|
U.tap(game, "up")
|
|
U.wait(2)
|
|
end
|
|
local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
U.shot(game, SHOT_DIR .. "/bug593_profiles.png")
|
|
U.log("captured", SHOT_DIR .. "/bug593_profiles.png")
|
|
|
|
U.log("The PROFILES tab is open with the cursor back on PROFILE 1, and the")
|
|
U.log("steps above already ran once: a mod was switched off, PROFILE 1 put it")
|
|
U.log("back, and EXPORT.. wrote the file. Redo it by hand: LEFT for MODS,")
|
|
U.log("SELECT to flip a mod, RIGHT, A on PROFILE 1. The row should carry the")
|
|
U.log("! glyph and the footer should flash PROFILE STAGED. The failure to")
|
|
U.log("watch for is the cursor landing a row off after a tab switch or a")
|
|
U.log("delete, so A on what looks like [AD-HOC] fires EXPORT.. instead.")
|
|
|
|
while true do
|
|
coroutine.yield()
|
|
end
|
|
end
|