mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
153 lines
5.3 KiB
Lua
153 lines
5.3 KiB
Lua
-- The command queue, on the map that needs it.
|
|
--
|
|
-- `writecmdqueue` / `delcmdqueue` were no-ops because there was no wCmdQueue
|
|
-- engine (engine/overworld/cmd_queue.asm, polled once a frame). Ice Path B1F
|
|
-- is one of the two maps that use CMDQUEUE_STONETABLE, and it is the one that
|
|
-- matters: the queue is what makes a pushed boulder fall through the hole, Ice
|
|
-- Path gates Blackthorn, and Blackthorn is the eighth badge.
|
|
--
|
|
-- This pushes ICEPATHB1F_BOULDER1 north onto warp 3 at (11,2) with STRENGTH
|
|
-- active, and asserts the boulder falls and its twin one floor down appears.
|
|
--
|
|
-- POKEPORT_IDENTITY=gold-dev POKEPORT_GAME=gold \
|
|
-- POKEPORT_DRIVER=tests/drivers/gold_icepath_boulder.lua \
|
|
-- perl -e 'alarm 300; exec @ARGV' \
|
|
-- python3 -c "import pty; pty.spawn(['love','.'])"
|
|
local SHOT_DIR = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-icepath"
|
|
|
|
return function(game)
|
|
local w = game.world
|
|
local fails = 0
|
|
|
|
local function wait(n) for _ = 1, n do coroutine.yield() end end
|
|
|
|
local function ok(cond, msg)
|
|
if cond then print("[icepath] ok " .. msg)
|
|
else fails = fails + 1 print("[icepath] FAIL " .. msg) end
|
|
return cond
|
|
end
|
|
|
|
local function clearDirs()
|
|
game.input.pressQueue = {}
|
|
for _, d in ipairs({ "up", "down", "left", "right" }) do
|
|
game.input.state[d] = false
|
|
game.input.sources[d] = nil
|
|
end
|
|
end
|
|
|
|
local function hold(dir, frames)
|
|
clearDirs()
|
|
for _ = 1, frames do
|
|
table.insert(game.input.pressQueue, dir)
|
|
game.input.state[dir] = true
|
|
coroutine.yield()
|
|
end
|
|
clearDirs()
|
|
coroutine.yield()
|
|
end
|
|
|
|
local function boulder()
|
|
for _, npc in ipairs(w.npcs) do
|
|
if npc.def and npc.def.index == 1 then return npc end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
os.execute('mkdir -p "' .. SHOT_DIR .. '" 2>/dev/null')
|
|
wait(45)
|
|
|
|
-- ICEPATHB1F_BOULDER1's hole is warp 3 at (11,2), and (11,4) is wall, so the
|
|
-- boulder reaches it from the WEST: the last push of the puzzle is the player
|
|
-- at (9,2) walking right into a boulder on (10,2).
|
|
--
|
|
-- The boulder starts the map at (11,7) and the route between is most of the
|
|
-- floor's maze. This driver is about the queue, not about the maze, so it
|
|
-- parks the boulder on the cell the maze delivers it to and performs the LAST
|
|
-- push for real. Nothing else is faked: the push is a walk, the fall is the
|
|
-- queue noticing, and the script is the map's own.
|
|
w:setMap("ICE_PATH_B1F", 9, 2, "right")
|
|
wait(20)
|
|
ok(w.map.id == "ICE_PATH_B1F", "on Ice Path B1F")
|
|
|
|
local CmdQueue = require("src.world.gen2.CmdQueue")
|
|
ok(CmdQueue.count(w.cmdQueue) == 1,
|
|
"the map load wrote its MAPCALLBACK_CMDQUEUE entry ("
|
|
.. CmdQueue.count(w.cmdQueue) .. " slot(s) used)")
|
|
|
|
local b = boulder()
|
|
ok(b ~= nil, "boulder 1 is on the map")
|
|
ok(b and b.cellX == 11 and b.cellY == 7,
|
|
("at its spawn (11,7), got (%s,%s)"):format(tostring(b and b.cellX),
|
|
tostring(b and b.cellY)))
|
|
ok(w.map:cellCollision(11, 2) == 0x60,
|
|
"and the tile at its hole is COLL_PIT")
|
|
|
|
-- One cell west of the hole, which is where the maze push route ends.
|
|
b.cellX, b.cellY = 10, 2
|
|
b.homeX, b.homeY = 10, 2
|
|
b.px, b.py = 10 * 16, 2 * 16
|
|
ok(w.map:isWalkable(10, 2), "the cell it is pushed from is floor")
|
|
|
|
-- BIKEFLAGS_STRENGTH_ACTIVE, which .CheckStrengthBoulder reads. Getting it
|
|
-- the honest way needs a party with STRENGTH and the RISING BADGE; this
|
|
-- driver is about the queue, not about the field move.
|
|
w.strengthActive = true
|
|
|
|
game.capturePath = SHOT_DIR .. "/before.png"
|
|
wait(2)
|
|
|
|
-- The push: walking into an occupied cell with STRENGTH active is what
|
|
-- .CheckStrengthBoulder turns into a step for the boulder instead.
|
|
for i = 1, 3 do
|
|
hold("right", 40)
|
|
local bb = boulder()
|
|
print(("[icepath] push %d: player (%d,%d) boulder (%s,%s)"):format(
|
|
i, w.player.cellX, w.player.cellY,
|
|
tostring(bb and bb.cellX), tostring(bb and bb.cellY)))
|
|
if w:busy() or bb == nil then break end
|
|
end
|
|
|
|
-- The queue drops it on the first frame it is standing on the pit, and the
|
|
-- script that runs is pause 30 / SFX_STRENGTH / earthquake 80 / the line.
|
|
local fell = false
|
|
for _ = 1, 400 do
|
|
if w:busy() then fell = true break end
|
|
wait(2)
|
|
end
|
|
ok(fell, "something started once the boulder reached the hole")
|
|
game.capturePath = SHOT_DIR .. "/falling.png"
|
|
wait(4)
|
|
|
|
for _ = 1, 400 do
|
|
if not w:busy() then break end
|
|
table.insert(game.input.pressQueue, "a")
|
|
wait(3)
|
|
end
|
|
wait(20)
|
|
|
|
ok(boulder() == nil, "the boulder is gone from B1F")
|
|
-- EVENT_BOULDER_IN_ICE_PATH_1A (1805) hides the twin on the floor below; the
|
|
-- script CLEARS it, which is what puts the fallen boulder down there.
|
|
ok(w.events:get(1805) == false,
|
|
"and EVENT_BOULDER_IN_ICE_PATH_1A is clear, so it is on B2F now")
|
|
game.capturePath = SHOT_DIR .. "/after.png"
|
|
wait(4)
|
|
|
|
-- Follow it down and look.
|
|
w:setMap("ICE_PATH_B2F_MAHOGANY_SIDE", 11, 5, "up")
|
|
wait(30)
|
|
local below = nil
|
|
for _, npc in ipairs(w.npcs) do
|
|
if npc.def and npc.def.index == 1 then below = npc end
|
|
end
|
|
ok(below ~= nil and below.cellX == 11 and below.cellY == 3,
|
|
"the boulder is standing on B2F at (11,3)")
|
|
game.capturePath = SHOT_DIR .. "/below.png"
|
|
wait(30)
|
|
|
|
if fails > 0 then
|
|
error(("gold ice path: %d assertion(s) failed"):format(fails))
|
|
end
|
|
print("[driver] PASS gold command queue: the boulder fell through")
|
|
end
|