Files
gen1recomp/tests/parity_rival_walkoff.lua
bryanthaboi d920f7acdb clean up
2026-09-22 17:05:12 -04:00

261 lines
9.5 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- Parity: Route 22 + Cerulean rival post-battle exits (#154 / #168).
--
-- pokered Route22Rival{1,2}ExitMovementData* and CeruleanCityMovement3/4
-- plus CeruleanCityRivalText's Bill line after EVENT_BEAT_CERULEAN_RIVAL.
--
-- luajit tests/parity_rival_walkoff.lua
package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end
local S = require("tests.harness").suite("parity rival walkoff")
local check, eq = S.check, S.eq
-- restored at the bottom for the suites run after this file
local realMusic = package.loaded["src.core.Music"]
local realCommands = package.loaded["src.script.Commands"]
local realPicBox = package.loaded["src.ui.PicBox"]
package.loaded["src.core.Music"] = {
play = function() end, playOnce = function() return true end, stop = function() end,
playMap = function() end,
}
package.loaded["src.script.Commands"] = {
hide_object = function() end,
}
package.loaded["src.ui.PicBox"] = { new = function() return {} end }
local story5 = dofile("data/scripts/story5.lua")
local story = dofile("data/scripts/story.lua")
local function dirsEqual(a, b)
if type(a) ~= "table" or #a ~= #b then return false end
for i = 1, #b do if a[i] ~= b[i] then return false end end
return true
end
local function findWalk(rows)
for _, r in ipairs(rows) do
if r[1] == "walk_npc" then return r end
end
end
local function lastWalk(rows)
local found
for _, r in ipairs(rows) do
if r[1] == "walk_npc" then found = r end
end
return found
end
local function findRow(rows, name)
for i, r in ipairs(rows) do
if r[1] == name then return r, i end
end
end
local function texts(rows)
local out = {}
for _, r in ipairs(rows) do
if r[1] == "show_text" then out[#out + 1] = r[2] end
end
return out
end
local function capture(mapMod, game, x, y)
local rows
local ow = {
runner = {
isRunning = function() return false end,
run = function(_, r) rows = r end,
},
player = { facing = "down" },
npcByIndex = function() return { def = { name = "X" } } end,
}
check(mapMod.onStep(game, ow, x, y),
("onStep fires at (%d,%d)"):format(x, y))
check(rows ~= nil, "onStep queued rows")
return rows
end
-- Route22Rival1ExitMovementData1 / Data2, keyed on wSavedCoordIndex (which
-- Route22RivalBattleCoords entry matched, counted from 1) and NOT on the
-- rival's own row: index 1 is the (29,4) tile, where he stops BELOW the
-- player on (29,5) and leaves east; index 2 is the (29,5) tile, where he
-- stops LEFT of him on (28,5) and must step UP to row 4 to get around him.
-- This mapping was backwards until #236 (the y=4 walk began UP into the
-- cliff cell (28,3)), so these constants flipped with the fix.
local R1_Y4 = { "right", "right", "down", "down", "down", "down", "down" }
local R1_Y5 = { "up", "right", "right", "right",
"down", "down", "down", "down", "down", "down" }
-- Route22Rival2ExitMovementData1 falls through Data2, so index 1 (y=4) is
-- LEFT x4 from (29,5) and index 2 (y=5) LEFT x3 from (28,5)
local R2_Y4 = { "left", "left", "left", "left" }
local R2_Y5 = { "left", "left", "left" }
local CER_X20 = { "right", "down", "down", "down", "down", "down", "down" }
local CER_X21 = { "left", "down", "down", "down", "down", "down", "down" }
do
local game = { save = { flags = { EVENT_GOT_POKEDEX = true } }, data = {} }
local w5 = findWalk(capture(story5.ROUTE_22, game, 29, 5))
local w4 = findWalk(capture(story5.ROUTE_22, game, 29, 4))
check(dirsEqual(w5[3], R1_Y5), "Rival1 y=5 goes U,R×3,D×6 around the player")
check(dirsEqual(w4[3], R1_Y4), "Rival1 y=4 exits toward Viridian (R,R,D×5)")
local rows = capture(story5.ROUTE_22,
{ save = { flags = { EVENT_GOT_POKEDEX = true } }, data = {} }, 29, 5)
for _, r in ipairs(rows) do
check(not (r[1] == "move_npc_to" and r[3] == 25 and r[4] == 5),
"Rival1 must not retreat to spawn (25,5)")
end
end
do
local flags = {
EVENT_BEAT_BROCK = true,
EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE = true,
EVENT_BEAT_GIOVANNI = true,
}
local w5 = findWalk(capture(story5.ROUTE_22, { save = { flags = flags }, data = {} }, 29, 5))
local w4 = findWalk(capture(story5.ROUTE_22, { save = { flags = flags }, data = {} }, 29, 4))
check(dirsEqual(w5[3], R2_Y5), "Rival2 y=5 exits left×3 toward League")
check(dirsEqual(w4[3], R2_Y4), "Rival2 y=4 exits left×4 toward League")
end
do
local rows = capture(story5.CERULEAN_CITY, { save = { flags = {} }, data = {} }, 20, 6)
local t = texts(rows)
local hasBill = false
for _, id in ipairs(t) do
if id == "_CeruleanCityRivalIWentToBillsText" then hasBill = true end
end
check(hasBill, "Cerulean post-battle includes Bill dialogue")
local order = {
"_CeruleanCityRivalPreBattleText",
"_CeruleanCityRivalIWentToBillsText",
}
local pi = 1
for _, id in ipairs(t) do
if pi <= #order and id == order[pi] then pi = pi + 1 end
end
eq(pi, #order + 1, "Cerulean text order: pre, Bill")
check(dirsEqual(lastWalk(rows)[3], CER_X20),
"Cerulean x=20 exit: right then into town")
for _, r in ipairs(rows) do
check(not (r[1] == "move_npc_to" and r[4] == 2),
"Cerulean must not walk back north to (20,2)")
end
end
do
local rows = capture(story5.CERULEAN_CITY, { save = { flags = {} }, data = {} }, 21, 6)
check(dirsEqual(lastWalk(rows)[3], CER_X21),
"Cerulean x=21 exit: left then into town")
end
-- #2149 scripts/CeruleanCity.asm:84-99
for _, px in ipairs({ 20, 21 }) do
local rows = capture(story5.CERULEAN_CITY, { save = { flags = {} }, data = {} }, px, 6)
local place = findRow(rows, "place_npc")
check(place ~= nil, ("Cerulean x=%d places the rival before he walks"):format(px))
eq(place[2], 1, "place_npc addresses CERULEANCITY_RIVAL (object 1)")
eq(place[3], px, ("the rival spawns in the player's column (x=%d)"):format(px))
eq(place[4], 2, "the rival spawns on the object_event home row 2")
check(dirsEqual(findWalk(rows)[3], { "down", "down", "down" }),
("Cerulean x=%d approach is CeruleanCityMovement1"):format(px))
check(findRow(rows, "move_npc_to") == nil,
"the BFS walk is gone, so no fourth sideways step")
end
-- #2151 scripts/CeruleanCity.asm:141
local function assertEndBattleText(rows, what)
local save, si = findRow(rows, "save_end_battle_text")
check(save ~= nil, what .. " arms save_end_battle_text")
eq(save[2], "_CeruleanCityRivalDefeatedText", what .. " arms the loss line")
local _, bi = findRow(rows, "rival_battle")
eq(si, bi - 1, what .. " arms it on the row before rival_battle")
for _, r in ipairs(rows) do
check(not (r[1] == "show_text" and r[2] == "_CeruleanCityRivalDefeatedText"),
what .. " no longer prints the loss line from the script queue")
end
end
do
local rows = capture(story5.CERULEAN_CITY, { save = { flags = {} }, data = {} }, 21, 6)
assertEndBattleText(rows, "the Cerulean ambush")
assertEndBattleText(story.CERULEAN_CITY.talk.TEXT_CERULEANCITY_RIVAL,
"the Cerulean talk path")
end
do
local ScriptRunner = require("src.script.ScriptRunner")
local anyVerb = function() return true end
local scenes = {
["Cerulean ambush x=20"] =
capture(story5.CERULEAN_CITY, { save = { flags = {} }, data = {} }, 20, 6),
["Cerulean ambush x=21"] =
capture(story5.CERULEAN_CITY, { save = { flags = {} }, data = {} }, 21, 6),
["Cerulean talk"] = story.CERULEAN_CITY.talk.TEXT_CERULEANCITY_RIVAL,
["S.S. Anne 2F x=36"] =
capture(story5.SS_ANNE_2F, { save = { flags = {} }, data = {} }, 36, 8),
["S.S. Anne 2F x=37"] =
capture(story5.SS_ANNE_2F, { save = { flags = {} }, data = {} }, 37, 8),
}
for name, rows in pairs(scenes) do
local problems = ScriptRunner.validate(rows, anyVerb)
eq(#problems, 0, name .. " jump targets resolve: "
.. table.concat(problems, "; "))
end
end
-- #2164 scripts/SSAnne2F.asm:73
do
local game = { save = { flags = {} }, data = {} }
local kept
local function captureFacing(x)
local rows
local ow = {
runner = {
isRunning = function() return false end,
run = function(_, r) rows = r end,
},
player = { facing = "right" },
npcByIndex = function() return { def = { name = "X" } } end,
}
check(story5.SS_ANNE_2F.onStep(game, ow, x, 8),
("SS Anne onStep fires at (%d,8)"):format(x))
kept = ow.player.facing
return rows
end
local right = captureFacing(37)
eq(kept, "right", "the player keeps his walking facing while the rival comes down")
local turn, ti = findRow(right, "face_player_dir")
check(turn ~= nil, "x=37 turns the player as a scripted row")
eq(turn[2], "left", "x=37 turns the player left")
local _, mi = findRow(right, "move_npc_to")
local _, xi = findRow(right, "show_text")
check(ti > mi, "the turn happens after the rival's walk")
check(ti < xi, "the turn happens before the dialogue box")
local left = captureFacing(36)
eq(kept, "right", "x=36 leaves the player's facing alone")
check(findRow(left, "face_player_dir") == nil,
"x=36 never writes wPlayerMovingDirection")
end
do
local talk = story.CERULEAN_CITY.talk.TEXT_CERULEANCITY_RIVAL
check(talk ~= nil, "talk script for CERULEANCITY_RIVAL exists")
local hasBill = false
for _, r in ipairs(talk) do
if r[1] == "show_text" and r[2] == "_CeruleanCityRivalIWentToBillsText" then
hasBill = true
end
end
check(hasBill, "talk path also exposes Bill dialogue")
end
package.loaded["src.core.Music"] = realMusic
package.loaded["src.script.Commands"] = realCommands
package.loaded["src.ui.PicBox"] = realPicBox
S.finish()