From 4a50cb0728f0f581bf92156cebfade06e8f31fc1 Mon Sep 17 00:00:00 2001 From: bryanthaboi Date: Thu, 23 Jul 2026 11:29:58 -0400 Subject: [PATCH] hopefulyl a soltuion to broken end game saves --- data/scripts/story.lua | 34 +++++++++++++++++ tests/drivers/hof_pc_test.lua | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 tests/drivers/hof_pc_test.lua diff --git a/data/scripts/story.lua b/data/scripts/story.lua index 1ba8c796..9c7d3efc 100644 --- a/data/scripts/story.lua +++ b/data/scripts/story.lua @@ -712,6 +712,29 @@ M.HALL_OF_FAME = { local toggles = game.save.objectToggles and game.save.objectToggles.HALL_OF_FAME if toggles then toggles.HALLOFFAME_OAK = nil end + -- Make the Hall of Fame recording machine interactable. The console + -- juts from the north wall as the two solid cells (4,1) and (5,1); a + -- sign on each lets the player face the machine (from below, or from + -- either side) and press A to run the TEXT_HALLOFFAME_PC talk script. + -- maps.lua is generated from the ROM and gitignored, so the sign is + -- injected here (a tracked script) rather than baked into map data. + local def = ow.map.def + def.signs = def.signs or {} + local present = false + for _, s in ipairs(def.signs) do + if s.text == "TEXT_HALLOFFAME_PC" then present = true break end + end + if not present then + table.insert(def.signs, { text = "TEXT_HALLOFFAME_PC", x = 4, y = 1 }) + table.insert(def.signs, { text = "TEXT_HALLOFFAME_PC", x = 5, y = 1 }) + end + -- the live Map instance built its signAt lookup from def.signs before + -- this hook ran, so rebuild it (idempotent) to pick up the injection + ow.map.signAt = {} + for _, s in ipairs(def.signs) do + ow.map.signAt[s.y * ow.map.widthCells + s.x] = s + end + if not game.save.pendingHallOfFame then return end game.save.pendingHallOfFame = false ow:queueScript({ @@ -725,6 +748,17 @@ M.HALL_OF_FAME = { { "record_hall_of_fame" }, -- predef HallOfFamePC: induction + credits }) end, + talk = { + -- The recording machine doubles as a "warp home" PC: a YES/NO prompt + -- that teleports back to the new-game bedroom spawn (special_warps.asm + -- NewGameWarp: REDS_HOUSE_2F, 3, 6, facing down). Fabricated + -- convenience -- there is no such prompt in the original ROM. + TEXT_HALLOFFAME_PC = { + { "ask", "Return to\nPALLET TOWN?" }, -- 1 YES/NO -> lastCheck + { "jump_if_false", "end" }, -- 2 NO: back away + { "warp", "REDS_HOUSE_2F", 3, 6, "down" }, -- 3 YES: home to your room + }, + }, } -- ------------------------------------------------------------------- diff --git a/tests/drivers/hof_pc_test.lua b/tests/drivers/hof_pc_test.lua new file mode 100644 index 00000000..5a15b356 --- /dev/null +++ b/tests/drivers/hof_pc_test.lua @@ -0,0 +1,69 @@ +-- Driver: the Hall of Fame recording-machine "PC". Teleports into the +-- HALL_OF_FAME room, faces the console jutting from the north wall, and +-- exercises the injected TEXT_HALLOFFAME_PC sign both ways: NO leaves the +-- player in the room, YES warps home to the new-game bedroom spawn +-- (REDS_HOUSE_2F, 3, 6). Screenshots the prompt and the arrival. + +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or "/tmp/shots" + local TextBox = require("src.render.TextBox") + local ChoiceBox = require("src.ui.ChoiceBox") + + local pass = true + local function check(cond, msg) + if cond then U.log("PASS: " .. msg) else pass = false; U.log("FAIL: " .. msg) end + end + local function topIs(mt) return getmetatable(game.stack:top()) == mt end + + -- press A to advance the ask text until the YES/NO box is on top + local function openChoice() + for _ = 1, 120 do + if topIs(ChoiceBox) then return true end + if topIs(TextBox) then U.tap(game, "a") end + U.wait(3) + end + return false + end + + -- stand at (4,2) facing up: the console solid cell (4,1) is dead ahead + U.teleport(game, "HALL_OF_FAME", 4, 2, "up") + local ow = game.overworld + local fx, fy = ow.player:facingCell() + U.log(("at (%d,%d) facing %s -> facing cell (%d,%d)"):format( + ow.player.cellX, ow.player.cellY, ow.player.facing, fx, fy)) + check(ow.map:signAtCell(fx, fy) ~= nil, "a sign sits on the console tile") + + -- ---- NO: prompt appears, player stays in the Hall of Fame ---- + U.tap(game, "a") -- interact -> ask + U.wait(4) + check(openChoice(), "NO run: YES/NO prompt opened") + U.shot(game, DIR .. "/hof_pc_1_prompt.png") + U.tap(game, "down") -- cursor YES -> NO + U.wait(2) + U.tap(game, "a") -- confirm NO + U.wait(20) + check(ow.map.id == "HALL_OF_FAME", "NO keeps the player in HALL_OF_FAME") + + -- ---- YES: warp back to the bedroom spawn ---- + U.teleport(game, "HALL_OF_FAME", 4, 2, "up") + ow = game.overworld + U.tap(game, "a") + U.wait(4) + check(openChoice(), "YES run: YES/NO prompt opened") + U.tap(game, "a") -- YES (default cursor) + local warped = false + for _ = 1, 300 do + if ow.map.id == "REDS_HOUSE_2F" and not ow.transitioning then warped = true; break end + U.wait(2) + end + U.log(("after YES: map=%s pos=(%d,%d) facing=%s"):format( + ow.map.id, ow.player.cellX, ow.player.cellY, ow.player.facing)) + U.wait(10) + U.shot(game, DIR .. "/hof_pc_2_home.png") + check(warped, "YES warps to REDS_HOUSE_2F") + check(ow.player.cellX == 3 and ow.player.cellY == 6, + "landed on the bedroom spawn (3,6)") + + U.log(pass and "RESULT: ALL PASS" or "RESULT: SEE FAILURES ABOVE") +end