mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 15:01:11 +02:00
1905261c5b
CLOSES #1483, CLOSES #1610, CLOSES #1615, CLOSES #1646, CLOSES #1649, CLOSES #1651, CLOSES #1653, CLOSES #1656, CLOSES #1683, CLOSES #1685, CLOSES #1686, CLOSES #1687, CLOSES #1688, CLOSES #1689, CLOSES #1690, CLOSES #1693, CLOSES #1694, CLOSES #1695, CLOSES #1696, CLOSES #1702, CLOSES #1704, CLOSES #1705, CLOSES #1706, CLOSES #1707, CLOSES #1708, CLOSES #1710, CLOSES #1711, CLOSES #1712, CLOSES #1713, CLOSES #1716, CLOSES #1717, CLOSES #1718, CLOSES #1719, CLOSES #1720, CLOSES #1721, CLOSES #1725, CLOSES #1732, CLOSES #1745, CLOSES #1748, CLOSES #1749, CLOSES #1751, CLOSES #1754
214 lines
8.7 KiB
Lua
214 lines
8.7 KiB
Lua
-- #1716: a whirlpool tile force-turns the player instead of being swum through.
|
|
--
|
|
-- POKEPORT_IDENTITY=gold-dev POKEPORT_GAME=gold POKEPORT_TOUCH=0 \
|
|
-- POKEPORT_DRIVER=tests/drivers/gold_whirlpool_forceturn_bug1716_test.lua \
|
|
-- POKEPORT_SHOT_DIR=/tmp/gold-whirlpool \
|
|
-- perl -e 'alarm 240; exec @ARGV' \
|
|
-- python3 -c "import pty; pty.spawn(['love','.'])"
|
|
--
|
|
-- DoPlayerMovement's .CheckTile runs CheckWhirlpoolTile above the nybble ladder
|
|
-- and answers PLAYERMOVEMENT_FORCE_TURN (engine/overworld/player_movement.asm
|
|
-- :117-123), which is Script_ForcedMovement: step_dig 16, turn_in <back>,
|
|
-- step_dig 16, turn_head <back>, step_end (engine/events/forced_movement.asm
|
|
-- :25-51). turn_in `jp TurningStep`, i.e. InitStep under OBJECT_ACTION_SPIN --
|
|
-- a spinning one-cell step, not a facing change.
|
|
--
|
|
-- No POKEPORT_SPEED here on purpose: the spin, the step and the settle are what
|
|
-- is being watched, and fast-forward scales only the logic clock.
|
|
local U = require("tests.drivers.util")
|
|
|
|
local FieldMoves = require("src.world.gen2.FieldMoves")
|
|
local Movement = require("src.script.gen2.Movement")
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
local Permissions = require("src.world.gen2.Permissions")
|
|
|
|
-- data/generated/maps.lua, ROUTE_41: four whirlpools, the first at (22,12) with
|
|
-- plain water above and below it. Verified live below, and re-derived by
|
|
-- scanning the map if a re-import ever moves it.
|
|
local MAP = "ROUTE_41"
|
|
local WHIRL = { x = 22, y = 12 }
|
|
|
|
local DELTA = {
|
|
up = { 0, -1 }, down = { 0, 1 }, left = { -1, 0 }, right = { 1, 0 },
|
|
}
|
|
|
|
return function(game)
|
|
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-whirlpool"
|
|
local fails, lines = 0, {}
|
|
|
|
local function claim(ok, text)
|
|
if not ok then fails = fails + 1 end
|
|
lines[#lines + 1] = (ok and "PASS " or "FAIL ") .. text
|
|
return ok
|
|
end
|
|
|
|
U.wait(45)
|
|
local world = game.world
|
|
if not (world and world.map) then
|
|
U.log("FAIL the gold world never booted, nothing to look at")
|
|
while true do coroutine.yield() end
|
|
end
|
|
|
|
-- ---- the things a human's eyes cannot check ------------------------------
|
|
--
|
|
-- All of these fail silently and look exactly like "the fix did nothing".
|
|
claim(Movement.decodeByte(0x24).kind == "step",
|
|
"$24 turn_in decodes as a step, not a facing change")
|
|
claim(Movement.decodeByte(0x24).spin == true,
|
|
"and it carries OBJECT_ACTION_SPIN")
|
|
claim(Movement.STEP_DIG == 0x4f and Movement.STEP_DIG_FRAMES == 16,
|
|
"$4f step_dig is modelled, 16 frames")
|
|
claim(type(world.runForcedMovement) == "function",
|
|
"World:runForcedMovement exists for .CheckTile to call")
|
|
claim(type(world.player.scriptSpin) == "function",
|
|
"Player:scriptSpin exists for step_dig to drive")
|
|
|
|
-- SURF but deliberately NOT WHIRLPOOL, and no GLACIERBADGE: TryWhirlpoolOW
|
|
-- must never get a look in, or what we would be watching is the prompt.
|
|
local badges = game.save.player.badges or {}
|
|
game.save.player.badges = badges
|
|
for _, badge in pairs(FieldMoves.BADGE) do badges[badge] = nil end
|
|
badges[FieldMoves.BADGE.SURF] = true
|
|
local swimmer = Mon.new(game.data, "LAPRAS", 30,
|
|
{ moves = { { id = "SURF" } } })
|
|
claim(swimmer ~= nil, "a LAPRAS that knows SURF and nothing else")
|
|
game.save.party = { swimmer }
|
|
claim(not FieldMoves.hasBadge(game.save, FieldMoves.BADGE.WHIRLPOOL),
|
|
"no GLACIERBADGE, so no whirlpool prompt can fire")
|
|
|
|
-- ---- find the whirlpool --------------------------------------------------
|
|
world:applyPlayerState(FieldMoves.PLAYER_SURF)
|
|
world:setMap(MAP, WHIRL.x, WHIRL.y - 1, "down")
|
|
U.wait(10)
|
|
world.noWildEncounters = true
|
|
local map = world.map
|
|
|
|
local function isWhirl(x, y)
|
|
return map:inBounds(x, y) and Permissions.isWhirlpool(map:cellCollision(x, y))
|
|
end
|
|
local function isOpenWater(x, y)
|
|
return map:inBounds(x, y)
|
|
and Permissions.isWater(map:cellCollision(x, y))
|
|
and not Permissions.isWhirlpool(map:cellCollision(x, y))
|
|
end
|
|
|
|
local target, approach = nil, nil
|
|
if isWhirl(WHIRL.x, WHIRL.y) then target = { x = WHIRL.x, y = WHIRL.y } end
|
|
if not target then
|
|
-- A re-import moved it; take any whirlpool on the map instead of parking
|
|
-- the player on open sea with nothing to look at.
|
|
local def = world.maps[MAP]
|
|
for y = 0, (def.height or 0) * 2 - 1 do
|
|
for x = 0, (def.width or 0) * 2 - 1 do
|
|
if not target and isWhirl(x, y) then target = { x = x, y = y } end
|
|
end
|
|
end
|
|
end
|
|
if target then
|
|
for _, dir in ipairs({ "down", "up", "right", "left" }) do
|
|
local d = DELTA[dir]
|
|
if not approach and isOpenWater(target.x - d[1], target.y - d[2]) then
|
|
approach = { x = target.x - d[1], y = target.y - d[2], dir = dir }
|
|
end
|
|
end
|
|
end
|
|
claim(target ~= nil, ("%s still has a whirlpool tile on it"):format(MAP))
|
|
claim(approach ~= nil, "with open water beside it to swim in from")
|
|
if not (target and approach) then
|
|
for _, line in ipairs(lines) do U.log(line) end
|
|
U.log("nothing to drive; stopping here rather than faking the moment")
|
|
while true do coroutine.yield() end
|
|
end
|
|
if target.x ~= WHIRL.x or target.y ~= WHIRL.y then
|
|
U.log(("note: using the whirlpool at (%d,%d), not the (%d,%d) in the header")
|
|
:format(target.x, target.y, WHIRL.x, WHIRL.y))
|
|
end
|
|
|
|
-- ---- swim into it --------------------------------------------------------
|
|
world:setMap(MAP, approach.x, approach.y, approach.dir)
|
|
world:applyPlayerState(FieldMoves.PLAYER_SURF)
|
|
world.noWildEncounters = true
|
|
U.wait(20)
|
|
claim(FieldMoves.isSurfing(world.playerState), "on the water, on the Lapras")
|
|
U.shot(game, out .. "/01-before.png")
|
|
|
|
local p = world.player
|
|
local d = DELTA[approach.dir]
|
|
local beyond = { x = target.x + d[1], y = target.y + d[2] }
|
|
local seen = { onWhirl = false, spun = false, busy = false, past = false }
|
|
local shot = {}
|
|
|
|
local function press(dir)
|
|
table.insert(game.input.pressQueue, dir)
|
|
game.input.state[dir] = true
|
|
coroutine.yield()
|
|
end
|
|
|
|
for _ = 1, 300 do
|
|
press(approach.dir)
|
|
if p.cellX == target.x and p.cellY == target.y then
|
|
seen.onWhirl = true
|
|
end
|
|
if p.spinFrames then seen.spun = true end
|
|
if world:busy() then seen.busy = true end
|
|
if p.cellX == beyond.x and p.cellY == beyond.y then seen.past = true end
|
|
if not shot.spin and seen.onWhirl and p.spinFrames then
|
|
shot.spin = true
|
|
game.input.state[approach.dir] = false
|
|
U.shot(game, out .. "/02-whirling.png")
|
|
end
|
|
if shot.spin and not shot.back
|
|
and (p.cellX ~= target.x or p.cellY ~= target.y) then
|
|
shot.back = true
|
|
game.input.state[approach.dir] = false
|
|
U.shot(game, out .. "/03-thrown-back.png")
|
|
end
|
|
end
|
|
game.input.state[approach.dir] = false
|
|
-- Let whatever is still running settle before reading the resting facing.
|
|
for _ = 1, 180 do
|
|
if not world:busy() and not p.moving then break end
|
|
coroutine.yield()
|
|
end
|
|
U.shot(game, out .. "/04-settled.png")
|
|
|
|
local BACK = { up = "down", down = "up", left = "right", right = "left" }
|
|
claim(seen.onWhirl,
|
|
("the step onto the whirlpool at (%d,%d) happened -- the cart allows it")
|
|
:format(target.x, target.y))
|
|
claim(seen.busy, "the forced movement took the world off the d-pad")
|
|
claim(seen.spun, "step_dig put the player under OBJECT_ACTION_SPIN")
|
|
claim(not seen.past,
|
|
("300 frames of %s never reached (%d,%d) on the far side")
|
|
:format(approach.dir:upper(), beyond.x, beyond.y))
|
|
claim(p.cellX == approach.x and p.cellY == approach.y,
|
|
("the player was dragged back to (%d,%d), and is at (%d,%d)")
|
|
:format(approach.x, approach.y, p.cellX, p.cellY))
|
|
claim(p.facing == BACK[approach.dir],
|
|
("turn_head settled the facing to %s, and it is %s")
|
|
:format(BACK[approach.dir], tostring(p.facing)))
|
|
|
|
for _, line in ipairs(lines) do U.log(line) end
|
|
U.log(("%d checks, %d failed"):format(#lines, fails))
|
|
if fails > 0 then
|
|
U.log("something above is FAIL, so do not spend time watching the replay")
|
|
end
|
|
|
|
-- ---- the replay, live ----------------------------------------------------
|
|
U.log("shots are in " .. out .. "; 02 is the whirl, 03 the drag back out.")
|
|
U.log("the replay starts in three seconds and runs on its own.")
|
|
world:setMap(MAP, approach.x, approach.y, approach.dir)
|
|
world:applyPlayerState(FieldMoves.PLAYER_SURF)
|
|
world.noWildEncounters = true
|
|
U.wait(180)
|
|
for _ = 1, 200 do press(approach.dir) end
|
|
game.input.state[approach.dir] = false
|
|
|
|
U.log("the Lapras swims one cell into the whirlpool, whirls on the spot,")
|
|
U.log("is dragged back out still whirling, and stops facing away from it.")
|
|
U.log("a bounce with no whirl means scriptSpin never fired; a whirl that")
|
|
U.log("never leaves the cell means $24 is still decoding as a turn.")
|
|
U.log("the controls are yours -- the d-pad cannot cross that cell.")
|
|
while true do coroutine.yield() end
|
|
end
|