mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
123feef3a1
CLOSES #2425, CLOSES #2428, CLOSES #2431, CLOSES #2432, CLOSES #2433, CLOSES #2435, CLOSES #2436, CLOSES #2437, CLOSES #2440
310 lines
11 KiB
Lua
310 lines
11 KiB
Lua
#!/usr/bin/env luajit
|
|
-- pokefirered/src/scrcmd.c:719
|
|
-- pokefirered/src/field_fadetransition.c:535
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local MB_WARP_DOOR, MB_CAVE_DOOR, MB_NORMAL = 0x69, 0x60, 0x00
|
|
|
|
local sounds, musicFades, loads = {}, {}, {}
|
|
local Audio = {
|
|
playSe = function(id) sounds[#sounds + 1] = id end,
|
|
fadeOutBgm = function(speed) musicFades[#musicFades + 1] = speed end,
|
|
}
|
|
package.loaded["src.core.game3.audio"] = Audio
|
|
|
|
local Player = {
|
|
facing = "up", cellX = 6, cellY = 6, visible = true, spriteYOffset = 0, steps = {},
|
|
}
|
|
function Player.setVisible(v) Player.visible = v and true or false end
|
|
function Player.forceStep(dir, cb)
|
|
Player.facing = dir
|
|
Player.steps[#Player.steps + 1] = { dir = dir, cb = cb, left = 16 }
|
|
return true
|
|
end
|
|
package.loaded["src.core.game3.player"] = Player
|
|
|
|
local Fade = require("src.ui.game3.fade")
|
|
local Map = { current = "A" }
|
|
function Map.load(_, _, id, opts)
|
|
loads[#loads + 1] = { id = id, x = opts.x, y = opts.y, facing = opts.facing,
|
|
fadeT = Fade.t, fadeActive = Fade.active, visible = Player.visible,
|
|
parked = package.loaded["src.core.game3.field"]._fieldCallback }
|
|
Map.current = id
|
|
Player.cellX, Player.cellY = opts.x, opts.y
|
|
Player.facing = opts.facing
|
|
end
|
|
package.loaded["src.core.game3.map"] = Map
|
|
|
|
package.loaded["src.ui.game3.map_preview_screen"] = {
|
|
has = function() return false end,
|
|
isForestActive = function() return false end,
|
|
dismiss = function() end,
|
|
}
|
|
package.loaded["src.import.gba.map_preview_extract"] = { TYPE_CAVE = 1 }
|
|
|
|
local doorCalls = {}
|
|
local Doors = {}
|
|
function Doors.open(mapId, x, y, _, cb)
|
|
doorCalls[#doorCalls + 1] = { "open", mapId, x, y }
|
|
if cb then cb() end
|
|
end
|
|
function Doors.close(mapId, x, y, _, cb)
|
|
doorCalls[#doorCalls + 1] = { "close", mapId, x, y }
|
|
if cb then cb() end
|
|
end
|
|
function Doors.holdOpen(mapId, x, y)
|
|
doorCalls[#doorCalls + 1] = { "hold", mapId, x, y }
|
|
end
|
|
function Doors.closeAfterDelay(mapId, x, y, _, _, cb)
|
|
doorCalls[#doorCalls + 1] = { "close", mapId, x, y }
|
|
if cb then cb() end
|
|
end
|
|
function Doors.getSoundForWarp() return 0 end
|
|
Doors.SOUND_EXIT = require("src.core.game3.se_ids").SE_EXIT
|
|
function Doors.reset() end
|
|
package.loaded["src.core.game3.doors"] = Doors
|
|
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
package.loaded["src.core.game3.scripting.space"] = { store = Flags.newStore() }
|
|
local Field = { _fieldCallback = false, locked = false }
|
|
function Field.lock() Field.locked = true end
|
|
function Field.unlock() Field.locked = false end
|
|
package.loaded["src.core.game3.field"] = Field
|
|
|
|
local Collision = require("src.core.game3.collision")
|
|
local destBehavior = MB_NORMAL
|
|
Collision.behavior = function() return destBehavior end
|
|
|
|
local Task = require("src.core.game3.task")
|
|
local SE = require("src.core.game3.se_ids")
|
|
local Warp = require("src.core.game3.warp")
|
|
|
|
local game = { data = { maps = {
|
|
A = { mapType = 3, regionMapSectionId = 1, music = 100 },
|
|
B = { mapType = 8, regionMapSectionId = 1, music = 200 },
|
|
C = { mapType = 3, regionMapSectionId = 1, music = 100 },
|
|
} } }
|
|
|
|
local function frame()
|
|
Fade.tick(1 / 60)
|
|
Task.update(1 / 60)
|
|
for i = #Player.steps, 1, -1 do
|
|
local s = Player.steps[i]
|
|
s.left = s.left - 1
|
|
if s.left <= 0 then
|
|
table.remove(Player.steps, i)
|
|
if s.cb then s.cb() end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function reset(from)
|
|
sounds, musicFades, loads, doorCalls = {}, {}, {}, {}
|
|
Fade.clear()
|
|
Task.clear()
|
|
Warp.clear()
|
|
Map.current = from or "A"
|
|
Player.steps = {}
|
|
Player.visible = true
|
|
Player.facing = "up"
|
|
destBehavior = MB_NORMAL
|
|
Audio._currentSong = { id = 100 }
|
|
Audio._fadeOut = nil
|
|
end
|
|
|
|
local function has(list, v)
|
|
for _, x in ipairs(list) do if x == v then return true end end
|
|
return false
|
|
end
|
|
|
|
local function run(kind, dest, maxFrames)
|
|
local done, doneAt, frames = false, nil, 0
|
|
local fadeAtDone
|
|
local peak = Fade.t or 0
|
|
Warp.scripted(nil, game, kind, dest, 6, 6, "up", function()
|
|
done = true
|
|
doneAt = frames
|
|
fadeAtDone = { t = Fade.t, active = Fade.active, parked = Field._fieldCallback }
|
|
end)
|
|
local startMode, startActive = Fade.mode, Fade.active
|
|
while not done and frames < (maxFrames or 400) do
|
|
frames = frames + 1
|
|
frame()
|
|
if (Fade.t or 0) > peak then peak = Fade.t end
|
|
end
|
|
return { done = done, doneAt = doneAt, fadeAtDone = fadeAtDone, peak = peak,
|
|
startMode = startMode, startActive = startActive }
|
|
end
|
|
|
|
print("[test] 1. warp fades to black with SE_EXIT, swaps the map under black, fades back in")
|
|
reset()
|
|
local r = run("warp", "B")
|
|
check(r.startActive and r.startMode == Fade.MODE.TO_BLACK, "a FADE_TO_BLACK starts on the warp frame")
|
|
check(has(sounds, SE.SE_EXIT), "SE_EXIT plays")
|
|
check(#musicFades == 1 and musicFades[1] == 2, "the old map music fades at the indoor speed 2")
|
|
check(#loads == 1 and loads[1].fadeT == 16 and not loads[1].fadeActive,
|
|
"the map loads only once the screen is fully black (t=" .. tostring(loads[1] and loads[1].fadeT) .. ")")
|
|
check(r.done and r.fadeAtDone.t == 0 and not r.fadeAtDone.active,
|
|
"done fires only after the fade-in finishes")
|
|
check(r.doneAt and r.doneAt >= 32, "the whole transition takes the fade out + in (frames=" .. tostring(r.doneAt) .. ")")
|
|
check(Warp.isBusy() == false, "Warp is released")
|
|
check(loads[1] and loads[1].parked == true and r.fadeAtDone.parked == false,
|
|
"ON_FRAME stays parked from the load until the fade-in ends")
|
|
|
|
print("[test] 2. warpsilent fades without SE_EXIT; same song keeps playing")
|
|
reset()
|
|
r = run("warpsilent", "C")
|
|
check(not has(sounds, SE.SE_EXIT), "no SE_EXIT")
|
|
check(#musicFades == 0, "same map song is not faded")
|
|
check(#loads == 1 and loads[1].fadeT == 16, "load under black")
|
|
check(r.done and r.fadeAtDone.t == 0, "fade-in done before done()")
|
|
|
|
print("[test] 3. FLAG_DONT_TRANSITION_MUSIC keeps the music")
|
|
reset()
|
|
local Space = package.loaded["src.core.game3.scripting.space"]
|
|
Flags.setFlag(Space.store, nil, "FLAG_DONT_TRANSITION_MUSIC", true)
|
|
r = run("warp", "B")
|
|
check(#musicFades == 0, "no music fade with the flag set")
|
|
Flags.setFlag(Space.store, nil, "FLAG_DONT_TRANSITION_MUSIC", false)
|
|
|
|
print("[test] 4. a covered screen is not faded out again")
|
|
reset()
|
|
Fade.t = 16
|
|
Fade.mode = Fade.MODE.TO_BLACK
|
|
Fade.active = false
|
|
r = run("seagallop", "C")
|
|
check(#loads == 1 and loads[1].fadeT == 16, "loaded under the existing black")
|
|
check(r.peak == 16 and r.done and r.fadeAtDone.t == 0, "faded in once")
|
|
check(not has(sounds, SE.SE_EXIT), "the ferry kind leaves SE_EXIT to the ferry")
|
|
|
|
print("[test] 5. a door destination runs the exit-door step")
|
|
reset()
|
|
destBehavior = MB_WARP_DOOR
|
|
r = run("warp", "C")
|
|
check(r.done, "done fired")
|
|
local opened, closed = false, false
|
|
for _, c in ipairs(doorCalls) do
|
|
if c[1] == "open" and c[2] == "C" and c[3] == 6 and c[4] == 6 then opened = true end
|
|
if c[1] == "close" and c[2] == "C" then closed = true end
|
|
end
|
|
check(opened and closed, "the destination door opened and closed")
|
|
check(Player.facing == "down" and Player.visible, "the player stepped out facing down")
|
|
check(r.fadeAtDone and r.fadeAtDone.t == 0, "the slow fade-in finished first")
|
|
|
|
print("[test] 6. a non-animated door destination steps the player out after the fade")
|
|
reset()
|
|
destBehavior = MB_CAVE_DOOR
|
|
r = run("warp", "C")
|
|
check(r.done and Player.visible and #Player.steps == 0, "stepped out and done")
|
|
|
|
print("[test] 7. warpspinenter loads at once and spins the player in with SE_WARP_OUT")
|
|
reset()
|
|
Player.facing = "right"
|
|
local doneFlag = false
|
|
Warp.scripted(nil, game, "warpspinenter", "C", 6, 6, nil, function() doneFlag = true end)
|
|
check(#loads == 1, "no fade-out before the load")
|
|
check(Fade.mode == Fade.MODE.FROM_BLACK and Fade.active, "fades in from black")
|
|
check(has(sounds, SE.SE_WARP_OUT), "SE_WARP_OUT plays")
|
|
check(Player.spriteYOffset < 0, "the player starts above the screen")
|
|
for _ = 1, 200 do
|
|
if doneFlag then break end
|
|
frame()
|
|
end
|
|
check(doneFlag and Player.spriteYOffset == 0 and Player.facing == "right",
|
|
"landed facing the saved direction (" .. tostring(Player.facing) .. ")")
|
|
|
|
print("[test] 8. warpteleport spins out with SE_WARP_IN before fading")
|
|
reset()
|
|
local tDone = false
|
|
Warp.scripted(nil, game, "warpteleport", "C", 6, 6, nil, function() tDone = true end)
|
|
check(has(sounds, SE.SE_WARP_IN), "SE_WARP_IN plays")
|
|
check(not Fade.active, "no fade while the player rises")
|
|
for _ = 1, 400 do
|
|
if tDone then break end
|
|
frame()
|
|
end
|
|
check(tDone and #loads == 1 and loads[1].fadeT == 16, "loaded under black after the spin-out")
|
|
|
|
print("[test] 10. the music check compares with the song actually playing")
|
|
reset()
|
|
Audio._currentSong = { id = 305 }
|
|
r = run("warp", "C")
|
|
check(#musicFades == 1, "surf music fades into a map whose default matches the old map's default")
|
|
reset()
|
|
Audio._currentSong = { id = 200 }
|
|
r = run("warp", "B")
|
|
check(#musicFades == 0, "the destination song already playing is not faded")
|
|
|
|
print("[test] 11. a scripted warp resets the player to face south")
|
|
reset()
|
|
Player.facing = "up"
|
|
r = run("warp", "C")
|
|
check(loads[1] and loads[1].facing == "down",
|
|
"loaded facing down, not the pre-warp facing (got " .. tostring(loads[1] and loads[1].facing) .. ")")
|
|
reset()
|
|
r = run("warpsilent", "B")
|
|
check(loads[1] and loads[1].facing == "down", "warpsilent also faces down")
|
|
|
|
print("[test] 12. an exit mat runs the same door exit as a scripted warp")
|
|
reset()
|
|
destBehavior = MB_WARP_DOOR
|
|
local exitDone = false
|
|
Warp.startDoorExit(nil, game, "C", 6, 6, 3, 7)
|
|
check(Field.locked, "the field is locked for the exit")
|
|
local exitOpenedAt, exitFrames = nil, 0
|
|
for _ = 1, 400 do
|
|
exitFrames = exitFrames + 1
|
|
frame()
|
|
for _, c in ipairs(doorCalls) do
|
|
if not exitOpenedAt and c[1] == "open" and c[2] == "C" and c[3] == 6 and c[4] == 6 then exitOpenedAt = exitFrames end
|
|
end
|
|
if not Warp.isBusy() then exitDone = true break end
|
|
end
|
|
local exitClosed = false
|
|
for _, c in ipairs(doorCalls) do
|
|
if c[1] == "close" and c[2] == "C" and c[3] == 6 and c[4] == 6 then exitClosed = true end
|
|
end
|
|
check(has(sounds, SE.SE_EXIT), "SE_EXIT plays on the exit mat")
|
|
check(exitOpenedAt ~= nil and exitOpenedAt >= 15 + 25, "the destination door opens 25 frames into the fade-in (frame "
|
|
.. tostring(exitOpenedAt) .. ")")
|
|
check(exitClosed and Player.facing == "down" and Player.visible, "stepped out facing down and the door closed")
|
|
check(exitDone and not Field.locked, "the exit finishes and releases the field")
|
|
|
|
print("[test] 9. the warp opcode hands its kind to the adapter")
|
|
local Vm = require("src.core.game3.scripting.vm")
|
|
for _, op in ipairs({ "warp", "warpsilent", "warpspinenter", "warpteleport" }) do
|
|
local gotKind
|
|
local vm = Vm.new({
|
|
store = Flags.newStore(),
|
|
scripts = { s = { { op = op, [1] = 1, [2] = 2, [3] = 255, [4] = 3, [5] = 4 }, { op = "waitstate" }, { op = "end" } } },
|
|
adapters = {
|
|
log = function() end,
|
|
warp = function(_, _, _, _, _, done, kind)
|
|
gotKind = kind
|
|
if done then done() end
|
|
end,
|
|
},
|
|
})
|
|
vm:start("s")
|
|
for _ = 1, 5 do vm:tick() end
|
|
check(gotKind == op, op .. " passes its kind (got " .. tostring(gotKind) .. ")")
|
|
end
|
|
|
|
if failed > 0 then
|
|
print("[test] FAILED " .. failed)
|
|
os.exit(1)
|
|
end
|
|
print("[test] all passed")
|
|
os.exit(0)
|