Files
gen1recomp/tests/drivers/bills_pikachu_refix2_2165.lua

307 lines
11 KiB
Lua

-- #2165 refix 2: home/overworld.asm:1234-1252 (the B bypass and the
-- wPikachuCollisionCounter soft bump), engine/pikachu/pikachu_follow.asm:59
-- (the companion never starts the confused walk on the player's cell) and
-- engine/events/hidden_events/bills_house_pc.asm:14-40 (268 delay frames plus
-- five WaitForSoundToFinish drains). Self-terminating: it quits LOVE with 0
-- on all-PASS and 1 otherwise. No POKEPORT_SPEED -- it scales the logic
-- clock only and desyncs the cries against the walk.
--
-- SHOT_DIR=/tmp/shots2165b POKEPORT_SHOT_DIR=/tmp/shots2165b \
-- POKEPORT_DRIVER=tests/drivers/bills_pikachu_refix2_2165.lua \
-- POKEPORT_IDENTITY=yellow-sep04 POKEPORT_VERSION=yellow POKEPORT_TOUCH=0 love .
return function(game)
local U = dofile("tests/drivers/util.lua")
local GameVersion = require("src.core.GameVersion")
local Pokemon = require("src.pokemon.Pokemon")
local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots2165b"
local failures = 0
local function check(label, ok)
U.log(ok and "PASS" or "FAIL", label)
if not ok then failures = failures + 1 end
return ok
end
local function finish()
U.log(failures == 0 and "DONE all checks passed"
or ("DONE " .. failures .. " check(s) failed"))
U.log("shots in", SHOT_DIR)
love.event.quit(failures == 0 and 0 or 1)
while true do coroutine.yield() end
end
if not check("running the Yellow cache (POKEPORT_VERSION=yellow)",
GameVersion.isYellow()) then
finish()
end
local MAP = "BILLS_HOUSE"
local function resetSave()
game.save.party = { Pokemon.new(game.data, "PIKACHU", 12) }
game.save.player.name = "bryan"
game.save.options = game.save.options or {}
game.save.options.textSpeed = 1
game.save.flags = game.save.flags or {}
game.save.flags.EVENT_GOT_STARTER = true
game.save.flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = true
game.save.flags.EVENT_MET_BILL = nil
game.save.flags.EVENT_MET_BILL_2 = nil
game.save.flags.EVENT_BILL_SAID_USE_CELL_SEPARATOR = nil
game.save.flags.EVENT_USED_CELL_SEPARATOR_ON_BILL = nil
game.save.pikachuMapScriptActive = nil
end
local ow
local function follower()
for _, n in ipairs(ow and ow.npcs or {}) do
if n.pikachuFollower then return n end
end
return nil
end
local function objectAt(name)
for _, n in ipairs(ow and ow.npcs or {}) do
if n.def and n.def.name == name then return n end
end
return nil
end
local function press(dir, frames)
for _ = 1, frames do
table.insert(game.input.pressQueue, dir)
game.input.state[dir] = true
coroutine.yield()
end
end
local function release(...)
for _, b in ipairs({ ... }) do game.input.state[b] = false end
U.wait(1)
end
-- walk the player exactly one cell: hold until the cell index changes,
-- release, then let the in-flight step land
local function step(dir)
local p0 = ow.player
local sx, sy = p0.cellX, p0.cellY
for _ = 1, 60 do
table.insert(game.input.pressQueue, dir)
game.input.state[dir] = true
coroutine.yield()
if p0.cellX ~= sx or p0.cellY ~= sy then break end
end
game.input.state[dir] = false
for _ = 1, 40 do
if not p0.moving then break end
coroutine.yield()
end
U.wait(2)
end
-- =====================================================================
-- A. the soft bump and the B bypass, both of which only exist while
-- following is ENABLED -- home/overworld.asm:1240-1252
-- =====================================================================
resetSave()
game.save.flags.EVENT_MET_BILL_2 = true -- BillsHouseScript0 stays asleep
U.teleport(game, MAP, 3, 6, "up")
U.wait(30)
ow = game.overworld
if not check("Bill's House loaded", ow and ow.map and ow.map.id == MAP) then
finish()
end
check("the confused beat did not run with EVENT_MET_BILL_2 set",
ow.pikachuBillsScene ~= true)
-- take a step so the companion trails onto the cell behind the player
step("up")
U.wait(20)
local npc = follower()
if not check("the follower spawned and trailed", npc ~= nil) then finish() end
check("a following companion is walk-through when nothing is pressed",
npc.passable == true and (ow.pikachuCollisionCounter or 0) == 0)
local p = ow.player
local backDir = (npc.cellY > p.cellY and "down")
or (npc.cellY < p.cellY and "up")
or (npc.cellX > p.cellX and "right") or "left"
U.log(string.format("A player=(%d,%d)%s pika=(%d,%d) push=%s",
p.cellX, p.cellY, p.facing, npc.cellX, npc.cellY, backDir))
local startX, startY = p.cellX, p.cellY
local seeded, blockedTicks, movedAt = 0, 0, nil
for i = 1, 40 do
table.insert(game.input.pressQueue, backDir)
game.input.state[backDir] = true
coroutine.yield()
seeded = math.max(seeded, ow.pikachuCollisionCounter or 0)
if p.cellX ~= startX or p.cellY ~= startY or p.moving then
movedAt = i
break
end
if p.facing == backDir and npc.passable == false then
blockedTicks = blockedTicks + 1
end
end
release(backDir)
U.log("A seeded=" .. seeded .. " blockedTicks=" .. blockedTicks
.. " movedAt=" .. tostring(movedAt))
check("turning toward the companion seeds the 8-count bump", seeded == 8)
check("the bump holds the player for several frames, then yields",
blockedTicks >= 4 and movedAt ~= nil)
U.wait(20)
npc = follower()
p = ow.player
backDir = (npc.cellY > p.cellY and "down") or (npc.cellY < p.cellY and "up")
or (npc.cellX > p.cellX and "right") or "left"
startX, startY = p.cellX, p.cellY
local bWalkedThrough = false
game.input.state.b = true
for _ = 1, 40 do
table.insert(game.input.pressQueue, "b")
table.insert(game.input.pressQueue, backDir)
game.input.state[backDir] = true
coroutine.yield()
if p.cellX ~= startX or p.cellY ~= startY or p.moving then
bWalkedThrough = true
break
end
end
release(backDir, "b")
check("holding B walks straight through the companion", bWalkedThrough)
-- =====================================================================
-- B. the confused walk lands directly below Bill
-- (scripts/BillsHouse_2.asm:125 from the emerged cell)
-- =====================================================================
resetSave()
do
local wx, wy
for _, w in ipairs(game.data.maps.ROUTE_25.warps or {}) do
if w.destMap == MAP then wx, wy = w.x, w.y end
end
if not check("Route 25 has Bill's front door", wx ~= nil) then finish() end
U.teleport(game, "ROUTE_25", wx, wy + 1, "up")
U.wait(20)
for _ = 1, 300 do
ow = game.overworld
if ow and ow.map and ow.map.id == MAP then break end
table.insert(game.input.pressQueue, "up")
game.input.state.up = true
coroutine.yield()
end
game.input.state.up = false
for _ = 1, 240 do
ow = game.overworld
if ow and ow.map and ow.map.id == MAP and not ow.transitioning then
break
end
coroutine.yield()
end
end
U.wait(20)
ow = game.overworld
npc = follower()
if not check("walked in through Bill's front door",
ow and ow.map and ow.map.id == MAP) then
finish()
end
if not check("the follower spawned for the confused beat", npc ~= nil) then
finish()
end
check("the confused beat started", ow.pikachuBillsScene == true)
check("the scripted walk uses the 16-frame Pikachu step",
npc.stepFrames == 16)
for _ = 1, 600 do
if ow.emote and ow.emote.pikaPic then break end
U.wait(1)
end
for _ = 1, 900 do
if not ow.emote then break end
U.wait(1)
end
U.wait(10)
local bill = objectAt("BILLSHOUSE_BILL_POKEMON")
npc = follower()
U.log(string.format("B pika=(%d,%d) bill=(%s,%s)",
npc.cellX, npc.cellY,
bill and bill.cellX or "?", bill and bill.cellY or "?"))
check("the confused walk ends directly below Bill (6,6)",
npc.cellX == 6 and npc.cellY == 6)
check("which is Bill's own column, one row south",
bill ~= nil and npc.cellX == bill.cellX and npc.cellY == bill.cellY + 1)
U.shot(game, SHOT_DIR .. "/01_confused_walk_ends_below_bill.png")
-- =====================================================================
-- C. the parked companion is solid -- home/overworld.asm:1238-1240
-- =====================================================================
p = ow.player
for _ = 1, 4 do step("right") end
U.wait(10)
U.log(string.format("C player=(%d,%d)%s pika=(%d,%d) passable=%s",
p.cellX, p.cellY, p.facing, npc.cellX, npc.cellY,
tostring(npc.passable)))
check("the player reached the cell south of the companion",
p.cellX == 6 and p.cellY == 7)
check("and following is still disabled, so the companion is solid",
npc.passable == false)
startX, startY = p.cellX, p.cellY
press("up", 60)
release("up")
check("pushing north into the parked companion never moves the player",
p.cellX == startX and p.cellY == startY)
U.shot(game, SHOT_DIR .. "/02_solid_companion_blocks_player.png")
-- =====================================================================
-- D. the cell separator runs the cartridge's 268 delay frames plus five
-- WaitForSoundToFinish drains -- bills_house_pc.asm:14-40, :51-63
-- =====================================================================
game.save.flags.EVENT_BILL_SAID_USE_CELL_SEPARATOR = true
game.save.flags.EVENT_USED_CELL_SEPARATOR_ON_BILL = nil
U.teleport(game, MAP, 1, 5, "up")
U.wait(20)
ow = game.overworld
U.tap(game, "a")
U.wait(10)
for _ = 1, 200 do -- type + prompt-button the initiated text away
U.tap(game, "a")
U.wait(2)
if ow.runner and ow.runner:isRunning() then break end
if #(ow.pendingScripts or {}) > 0 then break end
end
local chainFrames, shot = 0, false
for _ = 1, 1200 do
if game.save.flags.EVENT_USED_CELL_SEPARATOR_ON_BILL then break end
chainFrames = chainFrames + 1
U.wait(1)
if not shot and chainFrames == 150 then
shot = true
U.shot(game, SHOT_DIR .. "/03_cell_separator_running.png")
end
end
-- 268 asm delay frames less the handful the text-box drain eats before
-- this loop starts; the pre-fix chain was 222 and measures ~218 here
U.log("D chainFrames=" .. chainFrames .. " (asm budget 268 + five SFX)")
check("the separator chain runs the cartridge's ~268 delay frames",
chainFrames >= 255)
check("and it sets EVENT_USED_CELL_SEPARATOR_ON_BILL only at the end",
game.save.flags.EVENT_USED_CELL_SEPARATOR_ON_BILL == true)
for _ = 1, 600 do
if objectAt("BILLSHOUSE_BILL1") then break end
U.wait(1)
end
U.wait(30)
check("Bill steps out of the machine when the chain ends",
objectAt("BILLSHOUSE_BILL1") ~= nil)
U.shot(game, SHOT_DIR .. "/04_bill_out_of_the_machine.png")
finish()
end