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
316 lines
12 KiB
Lua
316 lines
12 KiB
Lua
#!/usr/bin/env luajit
|
|
-- pokefirered/src/trade_scene.c:1337 DoTradeAnim_Cable
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
package.loaded["src.core.game3.rom_text"] = {
|
|
plain = function(key) return key end, box = function(key) return key end,
|
|
ascii = function(key) return key end, has = function() return true end,
|
|
key = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
|
|
at = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
|
|
count = function() return 0 end, list = function() return {} end,
|
|
lazy = function(map) return setmetatable({}, { __index = function(_, k) return map[k] end }) end,
|
|
}
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local function eq(a, b, msg)
|
|
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
|
|
end
|
|
|
|
local function finish()
|
|
if failed > 0 then
|
|
print("[test] FAILED " .. failed)
|
|
os.exit(1)
|
|
end
|
|
print("[test] all passed")
|
|
os.exit(0)
|
|
end
|
|
|
|
package.loaded["src.core.game3.audio"] = setmetatable({}, {
|
|
__index = function() return function() end end,
|
|
})
|
|
|
|
local store = { flags = {}, vars = {} }
|
|
local session = {
|
|
store = store, map = "FR_ROUTE2_HOUSE", party = {},
|
|
name = "RED", trainerId = 4242, vars = {}, flags = {},
|
|
dex = { seen = {}, owned = {} },
|
|
}
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return session end,
|
|
isActive = function() return true end,
|
|
}
|
|
|
|
local TradeScene = require("src.core.game3.trade_scene")
|
|
|
|
local function run(offer, received, opts, limit)
|
|
local order, counts = {}, {}
|
|
local frames = 0
|
|
TradeScene.play(offer, received, nil, opts)
|
|
local seen = TradeScene.phase()
|
|
order[#order + 1] = seen
|
|
counts[seen] = 0
|
|
while frames < (limit or 20000) do
|
|
frames = frames + 1
|
|
local over = TradeScene.step()
|
|
counts[seen] = (counts[seen] or 0) + 1
|
|
if over then break end
|
|
local now = TradeScene.phase()
|
|
if now ~= seen then
|
|
seen = now
|
|
order[#order + 1] = seen
|
|
counts[seen] = 0
|
|
end
|
|
end
|
|
return order, counts, frames
|
|
end
|
|
|
|
local OFFER = { species = 63, nickname = "ABRA", level = 12 }
|
|
local RECEIVED = { species = 122, nickname = "MIMIEN", otName = "REYLEY", otId = 1985, level = 12 }
|
|
|
|
print("[test] 1. every cable-trade phase runs in pret's order")
|
|
-- pokefirered/src/trade_scene.c:1266 the STATE_* enum, cable branch
|
|
local EXPECTED = {
|
|
{ "start", 1 },
|
|
{ "mon_slide_in", 61 },
|
|
{ "send_msg", 1 },
|
|
{ "bye_bye", 80 },
|
|
{ "pokeball_depart", 64 },
|
|
{ "pokeball_depart_wait", 87 },
|
|
{ "fade_out_to_gba_send", 1 },
|
|
{ "wait_fade_out_to_gba_send", 16 },
|
|
{ "fade_in_to_gba_send", 1 },
|
|
{ "wait_fade_in_to_gba_send", 16 },
|
|
{ "gba_zoom_out", 16 },
|
|
{ "gba_flash_send", 21 },
|
|
{ "gba_stop_flash_send", 126 },
|
|
{ "pan_away_gba", 32 },
|
|
{ "create_link_mon_leaving", 1 },
|
|
{ "link_mon_travel_out", 75 },
|
|
{ "link_mon_travel_offscreen", 45 },
|
|
{ "fade_out_to_crossing", 1 },
|
|
{ "wait_fade_out_to_crossing", 16 },
|
|
{ "fade_in_to_crossing", 1 },
|
|
{ "wait_fade_in_to_crossing", 16 },
|
|
{ "crossing_link_mons_enter", 14 },
|
|
{ "crossing_blend_white_1", 1 },
|
|
{ "crossing_blend_white_2", 1 },
|
|
{ "crossing_blend_white_3", 1 },
|
|
{ "crossing_create_mon_pics", 1 },
|
|
{ "crossing_mon_pics_move", 75 },
|
|
{ "crossing_link_mons_exit", 44 },
|
|
{ "create_link_mon_arriving", 16 },
|
|
{ "fade_out_to_gba_recv", 1 },
|
|
{ "wait_fade_out_to_gba_recv", 16 },
|
|
{ "link_mon_travel_in", 28 },
|
|
{ "pan_to_gba", 76 },
|
|
{ "destroy_link_mon", 1 },
|
|
{ "link_mon_arrived_delay", 10 },
|
|
{ "move_gba_to_center", 33 },
|
|
{ "gba_flash_recv", 1 },
|
|
{ "gba_stop_flash_recv", 126 },
|
|
{ "gba_zoom_in", 19 },
|
|
{ "fade_out_to_new_mon", 1 },
|
|
{ "wait_fade_out_to_new_mon", 16 },
|
|
{ "fade_in_to_new_mon", 1 },
|
|
{ "wait_fade_in_to_new_mon", 16 },
|
|
{ "pokeball_arrive", 1 },
|
|
{ "fade_pokeball_to_normal", 1 },
|
|
{ "pokeball_arrive_wait", 107 },
|
|
{ "show_new_mon", 1 },
|
|
{ "new_mon_msg", 1 },
|
|
{ "delay_for_mon_anim", 61 },
|
|
{ "wait_for_mon_cry", 1 },
|
|
{ "take_care_of_mon", 250 },
|
|
{ "after_new_mon_delay", 60 },
|
|
{ "check_ribbons", 1 },
|
|
{ "end_link_trade", 1 },
|
|
{ "try_evolution", 1 },
|
|
{ "wait_evolution", 1 },
|
|
{ "fade_out_end", 1 },
|
|
{ "wait_fade_out_end", 16 },
|
|
}
|
|
|
|
local order, counts, total = run(OFFER, RECEIVED, { art = { gba = true } })
|
|
eq(#order, #EXPECTED, "the cinema ran " .. #EXPECTED .. " phases")
|
|
for i, want in ipairs(EXPECTED) do
|
|
eq(order[i], want[1], "phase " .. i .. " is " .. want[1])
|
|
eq(counts[want[1]], want[2], want[1] .. " lasts " .. want[2] .. " frames")
|
|
end
|
|
|
|
local sum = 0
|
|
for _, want in ipairs(EXPECTED) do sum = sum + want[2] end
|
|
eq(total, sum, "the whole cinema is " .. sum .. " frames")
|
|
check(not TradeScene.isOpen(), "the scene closed itself when the last phase ended")
|
|
|
|
print("[test] 1b. pokefirered/src/trade_scene.c:1121 BG2 mon shadow under the sent and received mons")
|
|
do
|
|
local shadow = {}
|
|
TradeScene.play(OFFER, RECEIVED, nil, { art = { gba = true } })
|
|
for _ = 1, 20000 do
|
|
local ph = TradeScene.phase()
|
|
local st = TradeScene.state()
|
|
if ph and st and shadow[ph] == nil then
|
|
shadow[ph] = { on = st.monShadowBg == true, hofs = st.bg2hofs }
|
|
end
|
|
if TradeScene.phase() == "end_link_trade" then TradeScene.pressA() end
|
|
if TradeScene.step() then break end
|
|
end
|
|
check(shadow.bye_bye and shadow.bye_bye.on, "the shadow platform is up while Bye-bye shows")
|
|
eq(shadow.bye_bye and shadow.bye_bye.hofs, 0, "and it has slid in with the mon")
|
|
check(shadow.gba_flash_send and not shadow.gba_flash_send.on, "BG2 is the GBA screen during the send")
|
|
check(shadow.take_care_of_mon and shadow.take_care_of_mon.on, "the shadow is back under the received mon")
|
|
eq(shadow.take_care_of_mon and shadow.take_care_of_mon.hofs, 0, "at hofs 0 (trade_scene.c:1210)")
|
|
end
|
|
|
|
print("[test] 2. a nil art sheet degrades to the timed hold")
|
|
local holdOrder, holdCounts, holdTotal = run(OFFER, RECEIVED, nil)
|
|
local held = {}
|
|
for _, name in ipairs(holdOrder) do held[name] = true end
|
|
check(held.hold, "the no-art run holds on black instead of the transfer")
|
|
for _, name in ipairs({
|
|
"gba_zoom_out", "gba_flash_send", "pan_away_gba", "link_mon_travel_out",
|
|
"crossing_mon_pics_move", "gba_zoom_in", "link_mon_travel_in",
|
|
}) do
|
|
check(not held[name], "the no-art run skips " .. name)
|
|
end
|
|
check(held.fade_out_to_gba_send, "the no-art run keeps pret's fade to black")
|
|
eq(holdCounts.hold, TradeScene.HOLD_FRAMES, "the hold lasts HOLD_FRAMES")
|
|
eq(holdCounts.mon_slide_in, 61, "the art-free slide-in keeps pret's 61 frames")
|
|
eq(holdCounts.take_care_of_mon, 250, "the art-free reveal keeps pret's 250 frames")
|
|
check(holdTotal < total, "the held run is shorter than the full cinema")
|
|
|
|
print("[test] 3. the ball velocity table is pret's")
|
|
-- pokefirered/src/trade_scene.c:538
|
|
eq(TradeScene.BALL_VELOCITY[0], 0, "velocity[0]")
|
|
eq(TradeScene.BALL_VELOCITY[22], -4, "velocity[22]")
|
|
eq(TradeScene.BALL_VELOCITY[43], 0, "velocity[43]")
|
|
eq(TradeScene.BALL_VELOCITY[66], -4, "velocity[66]")
|
|
eq(TradeScene.BALL_VELOCITY[95], -1, "velocity[95]")
|
|
eq(TradeScene.BALL_VELOCITY[107], 3, "velocity[107]")
|
|
eq(TradeScene.BALL_VELOCITY[108], nil, "the table stops at 108 entries")
|
|
|
|
if not require("tests.game3_cache").mount() then
|
|
print("[skip] 4-5: the in-game trades are ROM data: " .. tostring(require("tests.game3_cache").reason))
|
|
else
|
|
print("[test] 4. the received mon lands in the party exactly once")
|
|
local Trade = require("src.core.game3.scripting.natives_trade")
|
|
local sent = { species = 63, nickname = "ABRA", level = 12, mail = nil }
|
|
local offered = {
|
|
species = 122, nickname = "MIMIEN", otName = "REYLEY", otId = 1985, level = 12,
|
|
}
|
|
session.party = { sent, { species = 25, nickname = "PIKA", level = 9 } }
|
|
Trade._offered = offered
|
|
local swaps = 0
|
|
local realTradeMons = Trade.tradeMons
|
|
Trade.tradeMons = function(...)
|
|
swaps = swaps + 1
|
|
return realTradeMons(...)
|
|
end
|
|
local task = Trade.sceneTask(nil, nil, 0, 0)
|
|
local guard = 0
|
|
while guard < 20000 do
|
|
guard = guard + 1
|
|
if task() then break end
|
|
end
|
|
Trade.tradeMons = realTradeMons
|
|
check(guard < 20000, "the scene task finished, frames=" .. guard)
|
|
eq(swaps, 1, "TradeMons ran exactly once")
|
|
eq(session.party[1], offered, "the received mon took the sent mon's slot")
|
|
eq(#session.party, 2, "the party did not grow")
|
|
eq(session.party[2].nickname, "PIKA", "the rest of the party is untouched")
|
|
eq(session.dex.owned[122], true, "the received species is registered as owned")
|
|
|
|
print("[test] 5. a second run of the same task does not swap again")
|
|
swaps = 0
|
|
Trade.tradeMons = function(...)
|
|
swaps = swaps + 1
|
|
return realTradeMons(...)
|
|
end
|
|
local done = task()
|
|
Trade.tradeMons = realTradeMons
|
|
check(done, "the finished task stays finished")
|
|
eq(swaps, 0, "no second swap")
|
|
eq(session.party[1], offered, "the party still holds the received mon once")
|
|
end
|
|
|
|
print("[test] 6. the map music is cached at the start and put back at the end")
|
|
-- pokefirered/src/trade_scene.c:1348
|
|
local MAP_SONG = 314
|
|
local realAudio = package.loaded["src.core.game3.audio"]
|
|
local realLove = _G.love
|
|
local songs = {}
|
|
package.loaded["src.core.game3.audio"] = {
|
|
_mapSong = MAP_SONG,
|
|
_currentSong = { id = MAP_SONG },
|
|
playSe = function() end,
|
|
playFanfare = function() end,
|
|
playCry = function() end,
|
|
isCryFinished = function() return true end,
|
|
playSong = function(id) songs[#songs + 1] = tonumber(id) end,
|
|
playMapSong = function(id) songs[#songs + 1] = tonumber(id) end,
|
|
}
|
|
_G.love = { graphics = {} }
|
|
local function runWithMusic(opts)
|
|
songs = {}
|
|
TradeScene.play(OFFER, RECEIVED, nil, opts)
|
|
TradeScene.step()
|
|
local cached = TradeScene.state() and TradeScene.state().cachedMapMusic
|
|
local guard = 0
|
|
while guard < 20000 do
|
|
guard = guard + 1
|
|
if TradeScene.phase() == "end_link_trade" then TradeScene.pressA() end
|
|
if TradeScene.step() then break end
|
|
end
|
|
return cached
|
|
end
|
|
local cachedInGame = runWithMusic({})
|
|
local inGameSongs = songs
|
|
local cachedLink = runWithMusic({ peer = { name = "TRIS", id = 31337 }, uiDriven = true })
|
|
local linkSongs = songs
|
|
_G.love = realLove
|
|
package.loaded["src.core.game3.audio"] = realAudio
|
|
eq(cachedInGame, MAP_SONG, "the in-game arm caches the map music at STATE_START")
|
|
eq(inGameSongs[1], 264, "STATE_START plays MUS_EVOLUTION")
|
|
-- pokefirered/src/trade_scene.c:1790
|
|
eq(inGameSongs[#inGameSongs], MAP_SONG, "the in-game arm ends on the cached map music")
|
|
eq(cachedLink, MAP_SONG, "the link arm caches the map music too")
|
|
-- pokefirered/src/trade_scene.c:2290
|
|
eq(linkSongs[#linkSongs], MAP_SONG, "the link arm ends on the cached map music")
|
|
check(not TradeScene.isOpen(), "both runs closed the scene")
|
|
|
|
if not require("tests.game3_cache").mount() then
|
|
print("[skip] 7: the in-game trades are ROM data: " .. tostring(require("tests.game3_cache").reason))
|
|
else
|
|
print("[test] 7. a trade with no mon to swap still fades the field back in")
|
|
local Trade = require("src.core.game3.scripting.natives_trade")
|
|
local realFade = package.loaded["src.ui.game3.fade"]
|
|
local fades = {}
|
|
package.loaded["src.ui.game3.fade"] = {
|
|
MODE = { TO_BLACK = "to_black", FROM_BLACK = "from_black" },
|
|
begin = function(mode) fades[#fades + 1] = mode end,
|
|
}
|
|
session.party = {}
|
|
Trade._offered = nil
|
|
local bail = Trade.sceneTask(nil, nil, 0, 3)
|
|
local bailFrames = 0
|
|
while bailFrames < 20000 do
|
|
bailFrames = bailFrames + 1
|
|
if bail() then break end
|
|
end
|
|
package.loaded["src.ui.game3.fade"] = realFade
|
|
check(bailFrames < 20000, "the bail-out task finished, frames=" .. bailFrames)
|
|
eq(fades[1], "to_black", "it still fades out first")
|
|
eq(fades[#fades], "from_black", "and the last fade brings the field back")
|
|
end
|
|
|
|
finish()
|