Files
gen1recomp/tests/drivers/gold_water_moves.lua
T
bryanthaboi ae6cac89e1 G2 support
2026-08-11 11:53:30 -04:00

169 lines
7.5 KiB
Lua

-- Assertion driver: SURF and WHIRLPOOL, done the way a player does them, on
-- real Gold maps. It PASSES or it errors.
--
-- POKEPORT_GAME=gold POKEPORT_IDENTITY=gold-dev \
-- POKEPORT_DRIVER=tests/drivers/gold_water_moves.lua love .
-- POKEPORT_SHOT_DIR=/tmp/gold-water (default)
--
-- tests/gen2_world_test.lua checks the pure halves (FieldMoves' badge gate,
-- the tile tests, the block replacement tables). What it cannot check is the
-- part that is all wiring: that walking into the sea puts you ON it, that the
-- player sprite becomes the Lapras, that a whirlpool block really leaves the
-- map's block buffer, and that the water behind it is passable afterwards.
--
-- Both routes IN are driven, because they are different code on the cart and
-- were different code here: TrySurfOW / TryWhirlpoolOW (walk into it, answer
-- the prompt) runs on the spot through CallScript, and the PACK / party-menu
-- route queues a script that only runs once the menus are gone.
local U = require("tests.drivers.util")
local FieldMoves = require("src.world.gen2.FieldMoves")
local Permissions = require("src.world.gen2.Permissions")
local Mon = require("src.battle.gen2.Mon")
-- Cells read out of the cache rather than remembered: Cherrygrove's beach is
-- the first stretch of sea a player can reach, and Route 41's whirlpools are
-- the ones between Olivine and Cianwood.
local BEACH = { map = "CHERRYGROVE_CITY", x = 10, y = 9 } -- faces water below
local WHIRL = { map = "ROUTE_41", x = 22, y = 11 } -- faces (22,12)
return function(game)
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-water"
U.wait(45)
local world = game.world
assert(world and world.map, "gold world did not boot")
local function tap(button, frames)
game.input.pressQueue[#game.input.pressQueue + 1] = button
game.input.state[button] = true
U.wait(2)
game.input.state[button] = false
U.wait(frames or 6)
end
-- Answer whatever text box or yes/no the field move puts up, until the world
-- is idle again. A field move is a script, so `busy` is the honest "is it
-- still going" and mashing A is what a player does.
local function settle(limit)
for _ = 1, (limit or 60) do
if not world:busy() and not world.fieldMove then return true end
tap("a", 4)
end
return not world:busy() and not world.fieldMove
end
-- SURF is FOGBADGE, WHIRLPOOL is GLACIERBADGE (FieldMoves.BADGE). Give
-- both, so what is under test is the move and not the gate -- the gate has
-- its own checks in gen2_world_test.
local badges = game.save.player.badges or {}
game.save.player.badges = badges
for _, badge in pairs(FieldMoves.BADGE) do badges[badge] = true end
assert(FieldMoves.hasBadge(game.save, FieldMoves.BADGE.SURF), "FOGBADGE")
assert(FieldMoves.hasBadge(game.save, FieldMoves.BADGE.WHIRLPOOL), "GLACIER")
local swimmer = Mon.new(game.data, "LAPRAS", 30,
{ moves = { { id = "SURF" }, { id = "WHIRLPOOL" } } })
assert(swimmer, "could not build a LAPRAS")
game.save.party = { swimmer }
-- ---- SURF, by walking into the sea -------------------------------------
do
world:setMap(BEACH.map, BEACH.x, BEACH.y, "down")
U.wait(10)
local ctx = world:fieldContext()
assert(Permissions.isWater(ctx.facingColl),
("%s (%d,%d) is not facing water any more -- re-import moved the beach")
:format(BEACH.map, BEACH.x, BEACH.y))
assert(not FieldMoves.isSurfing(world.playerState), "not surfing yet")
U.shot(game, out .. "/00-beach.png")
assert(world:trySurfOW(), "TrySurfOW refused a water tile with the badge")
assert(settle(), "the surf script never finished")
assert(FieldMoves.isSurfing(world.playerState),
"SURF ran and the player is still on foot")
assert(world.player.cellY > BEACH.y,
("the player did not step onto the water: still at (%d,%d)")
:format(world.player.cellX, world.player.cellY))
local coll = world.map:cellCollision(world.player.cellX, world.player.cellY)
assert(Permissions.isWater(coll),
"the player is surfing on something that is not water")
U.shot(game, out .. "/01-surfing.png")
-- And it is a real state, not a one-step animation: walk further out.
local fromY = world.player.cellY
U.hold(game, "down", 40)
U.wait(20)
assert(world.player.cellY > fromY,
"the player cannot swim once surfing")
assert(FieldMoves.isSurfing(world.playerState),
"the surf state did not survive a step")
U.log(("SURF: walked into the sea at (%d,%d) and swam to (%d,%d)")
:format(BEACH.x, BEACH.y, world.player.cellX, world.player.cellY))
end
-- ---- WHIRLPOOL, from the party menu ------------------------------------
do
-- Route 41 is open sea, so the player arrives already surfing -- which is
-- what the cart does too (wPlayerState survives the warp).
world:applyPlayerState(FieldMoves.PLAYER_SURF)
world:setMap(WHIRL.map, WHIRL.x, WHIRL.y, "down")
U.wait(10)
local def = world.maps[WHIRL.map]
local ctx = world:fieldContext()
assert(Permissions.isWhirlpool(ctx.facingColl),
("%s (%d,%d) is not facing a whirlpool"):format(
WHIRL.map, WHIRL.x, WHIRL.y))
local index = ctx.facingBlockIndex
local before = def.blocks[index]
local replacement = select(1, FieldMoves.blockReplacement(
FieldMoves.WHIRLPOOL_BLOCKS, ctx.tileset, ctx.facingBlock))
assert(replacement,
"no WhirlpoolBlockPointers row for this tileset/block pair")
assert(before ~= replacement, "the whirlpool is already cleared")
U.shot(game, out .. "/02-whirlpool.png")
-- The party-menu route: the result is QUEUED and only runs once the menus
-- are gone, which is the half that is easy to wire up wrong.
local result = world:useFieldMove("WHIRLPOOL", game.save.party[1])
assert(result and result.ok,
"the party menu refused WHIRLPOOL: " .. tostring(result and result.text))
assert(world.queuedFieldMove, "and it ran on the spot instead of queueing")
assert(world:runQueuedFieldMove(), "the queued move did not start")
assert(settle(), "the whirlpool script never finished")
assert(def.blocks[index] == replacement,
("the whirlpool block did not change: %s, want %s")
:format(tostring(def.blocks[index]), tostring(replacement)))
local after = world.map:cellCollision(ctx.facingX, ctx.facingY)
assert(not Permissions.isWhirlpool(after),
"the block changed but the cell is still a whirlpool")
assert(Permissions.isWalkable(after) or Permissions.isWater(after),
"the cleared whirlpool is not passable")
U.shot(game, out .. "/03-whirlpool-cleared.png")
-- Swim through it, which is the whole point of clearing one.
U.hold(game, "down", 40)
U.wait(20)
assert(world.player.cellY >= ctx.facingY,
("the player could not swim through the cleared whirlpool: (%d,%d)")
:format(world.player.cellX, world.player.cellY))
U.log(("WHIRLPOOL: block %d %s -> %s on %s, and the player swam through")
:format(index, tostring(before), tostring(replacement), WHIRL.map))
-- LoadMapAttributes refills the block buffer from ROM: a whirlpool is back
-- the next time you sail in, exactly like a cut tree.
world:setMap("NEW_BARK_TOWN", 13, 6, "down")
U.wait(5)
world:applyPlayerState(FieldMoves.PLAYER_SURF)
world:setMap(WHIRL.map, WHIRL.x, WHIRL.y, "down")
U.wait(5)
assert(def.blocks[index] == before,
"the whirlpool did not come back after a map load")
U.log("and it is back on the next map load, the way the cart refills it")
end
U.log("PASS gold_water_moves in " .. out)
love.event.quit()
end