mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
276 lines
11 KiB
Lua
276 lines
11 KiB
Lua
-- Driver: watch the Route 22 rival leave without clipping the cliff (#236).
|
|
-- scripts/Route22.asm keys the ambush on which coord you stepped on: (29,4)
|
|
-- parks him at (29,5) with ExitMovementData1, (29,5) at (28,5) with ...Data2.
|
|
-- POKEPORT_DRIVER=tests/drivers/route22_rival_exit_bug236_test.lua \
|
|
-- POKEPORT_IDENTITY=bug236 POKEPORT_TOUCH=0 POKEPORT_VERSION=red \
|
|
-- SHOT_DIR=/tmp/shots love . (BUG236_TILE=5 runs the other tile)
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
local Pokemon = require("src.pokemon.Pokemon")
|
|
local Flags = require("src.script.Flags")
|
|
local TextBox = require("src.render.TextBox")
|
|
local mapScripts = require("data.scripts.init")
|
|
|
|
local MAP = "ROUTE_22"
|
|
local TILE_Y = tonumber(os.getenv("BUG236_TILE") or "4")
|
|
if TILE_Y ~= 4 and TILE_Y ~= 5 then TILE_Y = 4 end
|
|
local TRIG_X = 29
|
|
-- what pokered does on this tile
|
|
local WANT = (TILE_Y == 4)
|
|
and { rx = 29, ry = 5, rivalFacing = "up", playerFacing = "down",
|
|
-- Route22Rival1ExitMovementData1
|
|
dirs = { "right", "right", "down", "down", "down", "down", "down" },
|
|
endX = 31, endY = 10 }
|
|
or { rx = 28, ry = 5, rivalFacing = "right", playerFacing = "left",
|
|
-- Route22Rival1ExitMovementData2
|
|
dirs = { "up", "right", "right", "right",
|
|
"down", "down", "down", "down", "down", "down" },
|
|
endX = 31, endY = 10 }
|
|
|
|
local function check(label, ok)
|
|
U.log(ok and "PASS" or "FAIL", label)
|
|
return ok
|
|
end
|
|
|
|
-- ---- a party that ends OPP_RIVAL1 in one turn --------------------------
|
|
-- the walk-off only exists after a win, and the fight is not under test
|
|
local tank = Pokemon.new(game.data, "MEWTWO", 100)
|
|
tank.moves = {
|
|
{ id = "PSYCHIC_M", pp = 99 },
|
|
{ id = "THUNDERBOLT", pp = 99 },
|
|
{ id = "ICE_BEAM", pp = 99 },
|
|
{ id = "RECOVER", pp = 99 },
|
|
}
|
|
game.save.party = { tank }
|
|
game.save.player = game.save.player or {}
|
|
game.save.player.name = game.save.player.name or "RED"
|
|
game.save.player.rival = game.save.player.rival or "BLUE"
|
|
game.save.defeatedTrainers = {}
|
|
game.save.objectToggles = game.save.objectToggles or {}
|
|
game.save.objectToggles[MAP] = nil
|
|
-- the window Route22DefaultScript arms: Pokedex in hand, Brock not yet
|
|
-- beaten, this rival not yet fought
|
|
Flags.set(game.save, "EVENT_CHOSE_SQUIRTLE")
|
|
Flags.set(game.save, "EVENT_GOT_POKEDEX")
|
|
Flags.clear(game.save, "EVENT_BEAT_BROCK")
|
|
Flags.clear(game.save, "EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE")
|
|
Flags.clear(game.save, "EVENT_BEAT_GIOVANNI")
|
|
Flags.clear(game.save, "EVENT_BEAT_ROUTE22_RIVAL_2ND_BATTLE")
|
|
|
|
-- ---- park one cell west of the trigger ---------------------------------
|
|
-- rows 4 and 5 are open path from x=24 to x=33; row 3 above them is the
|
|
-- cliff the buggy exit walked into
|
|
U.teleport(game, MAP, TRIG_X - 1, TILE_Y, "right")
|
|
U.wait(10)
|
|
local ow = game.overworld
|
|
|
|
-- ---- preconditions -----------------------------------------------------
|
|
-- a missing text entry, a moved object and an onStep that never fires all
|
|
-- look like a movement bug on screen: nothing happens
|
|
local t = game.data.text
|
|
for _, key in ipairs({ "_Route22RivalBeforeBattleText1",
|
|
"_Route22Rival1DefeatedText",
|
|
"_Route22RivalAfterBattleText1" }) do
|
|
check(key .. " resolves to a string",
|
|
type(t[key]) == "string" and t[key] ~= "")
|
|
end
|
|
|
|
local spawn
|
|
for _, o in ipairs(game.data.maps[MAP].objects or {}) do
|
|
if o.name == "ROUTE22_RIVAL1" then spawn = o end
|
|
end
|
|
check("ROUTE22_RIVAL1 has an object_event", spawn ~= nil)
|
|
check("it spawns on (25,5) as in data/maps/objects/Route22.asm",
|
|
spawn ~= nil and spawn.x == 25 and spawn.y == 5)
|
|
check("ROUTE_22 (28,3) is solid cliff (the cell #236 walked into)",
|
|
not ow.map:isWalkableCell(28, 3))
|
|
|
|
-- Dry-run the scene on a throwaway overworld so the exit list can be walked
|
|
-- against real collision before anything happens on screen. Music.play is
|
|
-- silenced or the rival sting fires the scene early.
|
|
local Music = require("src.core.Music")
|
|
local realPlay = Music.play
|
|
Music.play = function() end
|
|
local rows, probeFacing
|
|
do
|
|
local probe = {
|
|
runner = { isRunning = function() return false end,
|
|
run = function(_, r) rows = r end },
|
|
player = { facing = "down" },
|
|
npcByIndex = function() return { def = { name = "X" } } end,
|
|
}
|
|
local script = mapScripts.get(MAP)
|
|
check("ROUTE_22 has an onStep hook", script ~= nil and script.onStep ~= nil)
|
|
if script and script.onStep then
|
|
check(("onStep fires on the ambush tile (%d,%d)"):format(TRIG_X, TILE_Y),
|
|
script.onStep(game, probe, TRIG_X, TILE_Y) == true)
|
|
end
|
|
probeFacing = probe.player.facing
|
|
end
|
|
Music.play = realPlay
|
|
|
|
local moveTo, face, walk
|
|
for _, r in ipairs(rows or {}) do
|
|
if r[1] == "move_npc_to" then moveTo = r end
|
|
if r[1] == "face_object" then face = r end
|
|
if r[1] == "walk_npc" then walk = r end
|
|
end
|
|
check("the scene carries move/face/walk rows",
|
|
moveTo ~= nil and face ~= nil and walk ~= nil)
|
|
if moveTo and face and walk then
|
|
U.log(("plan: rival to (%d,%d) facing %s, player turned %s, exit %s")
|
|
:format(moveTo[3], moveTo[4], tostring(face[3]),
|
|
tostring(probeFacing), table.concat(walk[3], ", ")))
|
|
check(("rival parks on (%d,%d), where Route22MoveRivalRightScript leaves him")
|
|
:format(WANT.rx, WANT.ry),
|
|
moveTo[3] == WANT.rx and moveTo[4] == WANT.ry)
|
|
check("rival faces " .. WANT.rivalFacing .. " at the player",
|
|
face[3] == WANT.rivalFacing)
|
|
check("player is turned " .. WANT.playerFacing .. " at the rival",
|
|
probeFacing == WANT.playerFacing)
|
|
local same = #walk[3] == #WANT.dirs
|
|
for i = 1, #WANT.dirs do
|
|
if walk[3][i] ~= WANT.dirs[i] then same = false end
|
|
end
|
|
check("exit list is " .. table.concat(WANT.dirs, ", "), same)
|
|
-- replay it cell by cell against the real map: this is the assertion the
|
|
-- cliff clip failed
|
|
local D = { up = { 0, -1 }, down = { 0, 1 },
|
|
left = { -1, 0 }, right = { 1, 0 } }
|
|
local x, y, clean = moveTo[3], moveTo[4], true
|
|
for i, d in ipairs(walk[3]) do
|
|
x, y = x + D[d][1], y + D[d][2]
|
|
if not ow.map:isWalkableCell(x, y) then
|
|
clean = false
|
|
U.log((" exit step %d (%s) walks into solid ground at (%d,%d)")
|
|
:format(i, d, x, y))
|
|
end
|
|
if x == TRIG_X and y == TILE_Y then
|
|
clean = false
|
|
U.log((" exit step %d (%s) walks through the player on (%d,%d)")
|
|
:format(i, d, x, y))
|
|
end
|
|
end
|
|
check("every exit cell is walkable and none is the player's", clean)
|
|
check(("the exit ends on (%d,%d), off the bottom of the screen")
|
|
:format(WANT.endX, WANT.endY),
|
|
x == WANT.endX and y == WANT.endY)
|
|
end
|
|
|
|
if U.shot(game, DIR .. "/bug236_0_before.png") then
|
|
U.log("captured", DIR .. "/bug236_0_before.png")
|
|
end
|
|
|
|
-- ---- step onto the ambush tile -----------------------------------------
|
|
U.hold(game, "right", 24)
|
|
|
|
local function boxText()
|
|
local top = game.stack:top()
|
|
if getmetatable(top) ~= TextBox then return "" end
|
|
local parts = {}
|
|
for _, page in ipairs(top.pages or {}) do
|
|
if type(page) == "table" then
|
|
for _, line in ipairs(page) do parts[#parts + 1] = tostring(line) end
|
|
end
|
|
end
|
|
return table.concat(parts, " ")
|
|
end
|
|
|
|
local function rivalNpc()
|
|
for _, n in ipairs(game.overworld.npcs or {}) do
|
|
if n.def and n.def.name and n.def.name:find("RIVAL") then return n end
|
|
end
|
|
end
|
|
|
|
-- wait for the approach walk to finish: he is on his mark when the script
|
|
-- moves drain and the pre-battle box is up
|
|
local parked
|
|
for _ = 1, 400 do
|
|
local r = rivalNpc()
|
|
if r and #ow.scriptMoves == 0 and not r.moving and boxText() ~= "" then
|
|
parked = r
|
|
break
|
|
end
|
|
U.wait(2)
|
|
end
|
|
check("the rival showed up and stopped walking", parked ~= nil)
|
|
if parked then
|
|
U.log(("rival parked on (%d,%d) facing %s; player on (%d,%d) facing %s")
|
|
:format(parked.cellX, parked.cellY, tostring(parked.facing),
|
|
ow.player.cellX, ow.player.cellY, tostring(ow.player.facing)))
|
|
check(("he is on (%d,%d) live, not just on paper"):format(WANT.rx, WANT.ry),
|
|
parked.cellX == WANT.rx and parked.cellY == WANT.ry)
|
|
check("he is not standing on top of the player",
|
|
not (parked.cellX == ow.player.cellX and parked.cellY == ow.player.cellY))
|
|
end
|
|
if U.shot(game, DIR .. "/bug236_1_ambush.png") then
|
|
U.log("captured", DIR .. "/bug236_1_ambush.png")
|
|
end
|
|
|
|
-- ---- fight it for the human --------------------------------------------
|
|
-- FIGHT + first move every prompt; PSYCHIC one-shots the level 5-9 party.
|
|
local sawBattle = false
|
|
for f = 1, 4000 do
|
|
local top = game.stack:top()
|
|
if top and top.phase then
|
|
sawBattle = true
|
|
if top.phase == "menu" then top.menuIndex = 1
|
|
elseif top.phase == "moveSelect" then top.moveIndex = 1 end
|
|
U.tap(game, "a")
|
|
if f > 2400 and top.onFinish then
|
|
-- safety valve: never leave the run wedged in a stalled battle
|
|
U.log("force-finishing a stalled battle")
|
|
top.onFinish("win")
|
|
if game.stack:top() == top then game.stack:pop() end
|
|
end
|
|
elseif sawBattle then
|
|
break
|
|
elseif top ~= ow then
|
|
U.tap(game, "a") -- pre-battle dialogue
|
|
end
|
|
U.wait(2)
|
|
end
|
|
check("the battle ran and ended", sawBattle)
|
|
check("EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE is set",
|
|
game.save.flags.EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE == true)
|
|
|
|
-- ---- stop on the LAST post-battle box ----------------------------------
|
|
-- Rows 8 and 9 are DefeatedText then AfterBattleText; the walk (row 10)
|
|
-- starts when row 9's box closes, so hand over with row 9 on screen.
|
|
local after = t._Route22RivalAfterBattleText1 or ""
|
|
local needle = after:match("dawdling") and "dawdling" or "LEAGUE"
|
|
local onLastBox = false
|
|
for _ = 1, 600 do
|
|
if boxText():find(needle, 1, true) then onLastBox = true break end
|
|
if game.stack:top() ~= ow then U.tap(game, "a") end
|
|
U.wait(3)
|
|
end
|
|
check("the last post-battle box is on screen", onLastBox)
|
|
U.wait(20)
|
|
if U.shot(game, DIR .. "/bug236_2_lastbox.png") then
|
|
U.log("captured", DIR .. "/bug236_2_lastbox.png")
|
|
end
|
|
|
|
-- ---- hand off ----------------------------------------------------------
|
|
local r = rivalNpc()
|
|
U.log(("BLUE is beaten on the (%d,%d) ambush tile and the box on screen is")
|
|
:format(TRIG_X, TILE_Y))
|
|
U.log("his last line. Press A and watch him leave: that walk is #236. He")
|
|
U.log("must not clip the cliff at the top, step on your cell, or vanish")
|
|
U.log("before he is off the bottom of the screen.")
|
|
if r then
|
|
U.log(("He is standing on (%d,%d); you are on (%d,%d)."):format(
|
|
r.cellX, r.cellY, ow.player.cellX, ow.player.cellY))
|
|
end
|
|
if TILE_Y == 4 then
|
|
U.log("Expect two steps right, then five straight down.")
|
|
else
|
|
U.log("Expect one step up around you, three right, then six down.")
|
|
end
|
|
|
|
while true do
|
|
coroutine.yield()
|
|
end
|
|
end
|