mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
422 lines
15 KiB
Lua
422 lines
15 KiB
Lua
#!/usr/bin/env luajit
|
|
|
|
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 function eq(a, b, msg)
|
|
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
|
|
end
|
|
|
|
love = love or require("tests.love_stub")
|
|
|
|
local Boot = require("src.ui.game3.boot")
|
|
local Ui = require("src.ui.game3.mystery_gift")
|
|
local MysteryGift = require("src.core.game3.mystery_gift")
|
|
|
|
local FLAG_SYS_MYSTERY_GIFT_ENABLED = 0x839
|
|
|
|
print("[test] 1. The main menu rows")
|
|
do
|
|
local noSave = Boot.new()
|
|
Boot.setHasContinue(noSave, false)
|
|
local rows = Boot.menuItems(noSave)
|
|
eq(#rows, 2, "no save file gives two rows")
|
|
eq(rows[1], "NEW GAME", "row 1 is NEW GAME")
|
|
|
|
local plain = Boot.new()
|
|
Boot.setHasContinue(plain, true)
|
|
Boot.setContinueInfo(plain, { name = "RED", mysteryGift = false })
|
|
rows = Boot.menuItems(plain)
|
|
eq(#rows, 3, "a save without the Mystery Gift flag gives three rows")
|
|
eq(rows[1], "CONTINUE", "row 1 is CONTINUE")
|
|
eq(rows[2], "NEW GAME", "row 2 is NEW GAME")
|
|
eq(rows[3], "EXIT", "row 3 is EXIT")
|
|
check(not Boot.hasMysteryGift(plain), "no MYSTERY GIFT row without the flag")
|
|
|
|
local gift = Boot.new()
|
|
Boot.setHasContinue(gift, true)
|
|
Boot.setContinueInfo(gift, { name = "RED", mysteryGift = true })
|
|
rows = Boot.menuItems(gift)
|
|
-- pokefirered/src/main_menu.c:370 MAIN_MENU_MYSTERYGIFT
|
|
eq(#rows, 4, "the Mystery Gift layout keeps EXIT as a fourth row")
|
|
eq(rows[1], "CONTINUE", "row 1 is CONTINUE")
|
|
eq(rows[2], "NEW GAME", "row 2 is NEW GAME")
|
|
eq(rows[3], "MYSTERY GIFT", "MYSTERY GIFT takes the third window")
|
|
eq(rows[4], "EXIT", "EXIT is always on the menu")
|
|
check(Boot.hasMysteryGift(gift), "the flag selects the Mystery Gift layout")
|
|
|
|
-- pokefirered/src/main_menu.c:21 enum MainMenuType
|
|
local sawOption = false
|
|
for _, state in ipairs({ noSave, plain, gift }) do
|
|
for _, row in ipairs(Boot.menuItems(state)) do
|
|
if row == "OPTION" then sawOption = true end
|
|
end
|
|
end
|
|
check(not sawOption, "no layout carries an OPTION row")
|
|
end
|
|
|
|
print("[test] 2. The flag comes off the save file")
|
|
do
|
|
local off = Boot.continueInfoFromSave({ name = "RED", flags = {} })
|
|
check(off.mysteryGift == false, "a save without the flag reads false")
|
|
local on = Boot.continueInfoFromSave({
|
|
name = "RED",
|
|
flags = { [tostring(FLAG_SYS_MYSTERY_GIFT_ENABLED)] = true },
|
|
})
|
|
check(on.mysteryGift == true, "FLAG_SYS_MYSTERY_GIFT_ENABLED reads true")
|
|
end
|
|
|
|
print("[test] 3. Picking MYSTERY GIFT reaches the front end")
|
|
do
|
|
local state = Boot.new()
|
|
Boot.setHasContinue(state, true)
|
|
Boot.setContinueInfo(state, { name = "RED", mysteryGift = true })
|
|
state.phase = Boot.PHASE.MENU
|
|
state.menuIndex = 3
|
|
state.fadeT, state.fadeTarget = 0, 0
|
|
local key = nil
|
|
local input = { wasPressed = function(_, k) return k == key end }
|
|
key = "a"
|
|
Boot.update(state, input, 1 / 60)
|
|
eq(state.fadeThen, "mystery_gift", "the MYSTERY GIFT row starts the fade")
|
|
key = nil
|
|
for _ = 1, 400 do
|
|
if state.phase == Boot.PHASE.MYSTERY_GIFT then break end
|
|
Boot.update(state, input, 1 / 60)
|
|
end
|
|
eq(state.phase, Boot.PHASE.MYSTERY_GIFT, "the fade hands over to the Mystery Gift screen")
|
|
check(type(state.gift) == "table", "a front-end state was built")
|
|
eq(state.gift.state, Ui.STATE.MAIN_MENU, "the front end opens on its own menu")
|
|
local okDraw, err = pcall(Boot.draw, state)
|
|
check(okDraw, "the Mystery Gift screen draws: " .. tostring(err))
|
|
|
|
key = "b"
|
|
Boot.update(state, input, 1 / 60)
|
|
eq(state.phase, Boot.PHASE.MENU, "B leaves the front end for the main menu")
|
|
end
|
|
|
|
print("[test] 4. The front end receives a card and saves it")
|
|
local save, sess, st, saves
|
|
do
|
|
save = {}
|
|
sess = MysteryGift.sessionFromSave(save)
|
|
saves = 0
|
|
st = Ui.new({
|
|
session = sess,
|
|
onSave = function(s)
|
|
saves = saves + 1
|
|
return MysteryGift.applyToSave(s, save)
|
|
end,
|
|
})
|
|
|
|
local keys = {}
|
|
local function pressed(k) return keys[k] == true end
|
|
local function step(k)
|
|
keys = {}
|
|
if k then keys[k] = true end
|
|
Ui.update(st, pressed, 1 / 60)
|
|
end
|
|
local function finishMessage()
|
|
for _ = 1, 600 do
|
|
if not st.msg then break end
|
|
if st.msg.revealed >= st.msg.total then step("a") else step(nil) end
|
|
end
|
|
end
|
|
|
|
-- pokefirered/src/mystery_gift_menu.c:197 sListMenuItems_CardsOrNews
|
|
local rows = Ui.mainRows()
|
|
eq(rows[1], "WONDER CARDS", "the front end opens on WONDER CARDS")
|
|
eq(rows[2], "WONDER NEWS", "WONDER NEWS is the second row")
|
|
eq(rows[3], "EXIT", "EXIT is the third row")
|
|
|
|
step("a")
|
|
eq(st.state, Ui.STATE.DONT_HAVE_ANY, "no card saved takes the input branch")
|
|
check(st.msg ~= nil, "it says so first")
|
|
finishMessage()
|
|
eq(st.state, Ui.STATE.SOURCE_PROMPT, "then asks where to read one from")
|
|
step(nil)
|
|
eq(st.state, Ui.STATE.SOURCE_INPUT, "the source picker opens")
|
|
check(type(st.prompt) == "string" and st.prompt:find("WONDER CARD"),
|
|
"the prompt names the WONDER CARD")
|
|
|
|
local mystic = nil
|
|
for i, entry in ipairs(st.sources) do
|
|
if entry.key == "mystic_ticket" then mystic = i end
|
|
end
|
|
check(mystic ~= nil, "the MYSTIC TICKET is one of the sources")
|
|
eq(st.rows[#st.rows], "CANCEL", "the picker ends in CANCEL")
|
|
for _ = 2, mystic do step("down") end
|
|
eq(st.cursor, mystic, "the cursor reached the MYSTIC TICKET row")
|
|
step("a")
|
|
eq(st.state, Ui.STATE.RESULT_MSG, "the card arrives")
|
|
finishMessage()
|
|
eq(st.state, Ui.STATE.SAVE, "and the game saves")
|
|
step(nil)
|
|
eq(st.state, Ui.STATE.SAVE_DONE, "the save runs once")
|
|
eq(saves, 1, "onSave was called exactly once")
|
|
finishMessage()
|
|
eq(st.state, Ui.STATE.MAIN_MENU, "then back to the Mystery Gift menu")
|
|
|
|
check(MysteryGift.validateSavedCard(sess), "the card is on the session")
|
|
check(type(save.modData) == "table" and type(save.modData.mysteryGift) == "table",
|
|
"the record reached the save table")
|
|
check(save.flags[tostring(FLAG_SYS_MYSTERY_GIFT_ENABLED)] == true,
|
|
"FLAG_SYS_MYSTERY_GIFT_ENABLED reached the save table")
|
|
end
|
|
|
|
print("[test] 5. The card view and its menu")
|
|
do
|
|
local keys = {}
|
|
local function pressed(k) return keys[k] == true end
|
|
local function step(k)
|
|
keys = {}
|
|
if k then keys[k] = true end
|
|
Ui.update(st, pressed, 1 / 60)
|
|
end
|
|
local function confirmMessage()
|
|
for _ = 1, 600 do
|
|
if not st.msg then return end
|
|
if st.msg.revealed >= st.msg.total then
|
|
step("a")
|
|
return
|
|
end
|
|
step(nil)
|
|
end
|
|
end
|
|
|
|
step("a")
|
|
eq(st.state, Ui.STATE.GIFT_INPUT, "WONDER CARDS now opens the card")
|
|
local card = Ui.card(st)
|
|
eq(card.titleText, "MYSTIC TICKET", "the saved card is the MYSTIC TICKET")
|
|
eq(#card.bodyText, 4, "the card carries four body lines")
|
|
|
|
step("a")
|
|
eq(st.state, Ui.STATE.GIFT_SELECT, "A opens the card menu")
|
|
-- pokefirered/src/mystery_gift_menu.c:237 sListMenuItems_ReceiveToss
|
|
eq(#st.rows, 3, "a card that may not be sent shows three rows")
|
|
eq(st.rows[1], "RECEIVE", "row 1 is RECEIVE")
|
|
eq(st.rows[2], "TOSS", "row 2 is TOSS")
|
|
eq(st.rows[3], "CANCEL", "row 3 is CANCEL")
|
|
|
|
step("down")
|
|
step("a")
|
|
eq(st.state, Ui.STATE.ASK_TOSS, "TOSS asks first")
|
|
check(st.yesno ~= nil and st.yesno.text:find("event won't happen"),
|
|
"and warns that the event will not happen")
|
|
step("a")
|
|
eq(st.state, Ui.STATE.ASK_TOSS_UNRECEIVED, "an uncollected gift asks a second time")
|
|
step("a")
|
|
eq(st.state, Ui.STATE.SAVE, "yes tosses the card and saves")
|
|
confirmMessage()
|
|
step(nil)
|
|
eq(st.state, Ui.STATE.SAVE_DONE, "the toss is saved too")
|
|
confirmMessage()
|
|
eq(st.state, Ui.STATE.TOSSED, "the thrown-away message follows the save")
|
|
check(st.msg ~= nil and st.msg.text:find("thrown away"), "and it names the WONDER CARD")
|
|
confirmMessage()
|
|
eq(st.state, Ui.STATE.MAIN_MENU, "then back to the Mystery Gift menu")
|
|
check(not MysteryGift.validateSavedCard(sess), "the saved card is gone")
|
|
check(MysteryGift.applyToSave(sess, save), "the cleared record writes back")
|
|
check(save.modData.mysteryGift.card == nil, "the save table lost the card")
|
|
end
|
|
|
|
print("[test] 6. Wonder News paging")
|
|
do
|
|
local newsSave = {}
|
|
local newsSess = MysteryGift.sessionFromSave(newsSave)
|
|
local body = {}
|
|
for i = 1, 10 do body[i] = "LINE " .. i end
|
|
check(MysteryGift.receiveNews(newsSess, {
|
|
id = 7,
|
|
sendType = MysteryGift.SEND_TYPE_ALLOWED,
|
|
bgType = 1,
|
|
titleText = "WONDER NEWS",
|
|
bodyText = body,
|
|
}), "the news saves")
|
|
local nst = Ui.new({ session = newsSess, onSave = function() return true end })
|
|
local keys = {}
|
|
local function pressed(k) return keys[k] == true end
|
|
local function step(k)
|
|
keys = {}
|
|
if k then keys[k] = true end
|
|
Ui.update(nst, pressed, 1 / 60)
|
|
end
|
|
step("down")
|
|
step("a")
|
|
eq(nst.state, Ui.STATE.GIFT_INPUT, "WONDER NEWS opens the news")
|
|
check(nst.isNews, "the news branch is active")
|
|
eq(nst.newsScroll, 0, "the news starts unscrolled")
|
|
step("down")
|
|
eq(nst.newsScroll, 1, "down scrolls one line")
|
|
step("down")
|
|
eq(nst.newsScroll, 2, "down scrolls again")
|
|
step("down")
|
|
-- pokefirered/src/mystery_gift_show_news.c:346 scrollEnd counts lines past the eighth
|
|
eq(nst.newsScroll, 2, "ten lines over an eight-line window stop at two")
|
|
step("up")
|
|
eq(nst.newsScroll, 1, "up scrolls back")
|
|
local okDraw, err = pcall(Ui.draw, nst)
|
|
check(okDraw, "the news view draws: " .. tostring(err))
|
|
end
|
|
|
|
print("[test] 7. The source picker only offers sources for the mode it is in")
|
|
do
|
|
local realSources = MysteryGift.sources
|
|
local newsEntry = {
|
|
id = 11,
|
|
sendType = MysteryGift.SEND_TYPE_ALLOWED,
|
|
bgType = 2,
|
|
titleText = "WONDER NEWS",
|
|
bodyText = { "NEWS LINE" },
|
|
}
|
|
local cardEntry = MysteryGift.builtins()[1].card
|
|
MysteryGift.sources = function()
|
|
return {
|
|
{ key = "card_only", label = "CARD ONLY", origin = "builtin", card = cardEntry },
|
|
{ key = "news_only", label = "NEWS ONLY", origin = "file", news = newsEntry },
|
|
}
|
|
end
|
|
|
|
local function openPicker(isNews)
|
|
local st2 = Ui.new({ session = MysteryGift.sessionFromSave({}), onSave = function() return true end })
|
|
local keys = {}
|
|
local function pressed(k) return keys[k] == true end
|
|
local function step(k)
|
|
keys = {}
|
|
if k then keys[k] = true end
|
|
Ui.update(st2, pressed, 1 / 60)
|
|
end
|
|
if isNews then step("down") end
|
|
step("a")
|
|
for _ = 1, 600 do
|
|
if not st2.msg then break end
|
|
if st2.msg.revealed >= st2.msg.total then step("a") else step(nil) end
|
|
end
|
|
step(nil)
|
|
return st2, step
|
|
end
|
|
|
|
local cardSt = openPicker(false)
|
|
eq(cardSt.state, Ui.STATE.SOURCE_INPUT, "WONDER CARDS opens the picker")
|
|
eq(#cardSt.sources, 1, "it offers only the card source")
|
|
eq(cardSt.sources[1].key, "card_only", "and that source is the card one")
|
|
|
|
local newsSt, newsStep = openPicker(true)
|
|
eq(newsSt.state, Ui.STATE.SOURCE_INPUT, "WONDER NEWS opens the picker")
|
|
eq(#newsSt.sources, 1, "it offers only the news source")
|
|
eq(newsSt.sources[1].key, "news_only", "and that source is the news one")
|
|
newsStep("a")
|
|
eq(newsSt.state, Ui.STATE.RESULT_MSG, "picking it delivers rather than looping")
|
|
check(newsSt.msg ~= nil and tostring(newsSt.msg.text):find("received"),
|
|
"the news arrives: " .. tostring(newsSt.msg and newsSt.msg.text))
|
|
|
|
MysteryGift.sources = function()
|
|
return { { key = "card_only", label = "CARD ONLY", origin = "builtin", card = cardEntry } }
|
|
end
|
|
local emptySt, emptyStep = openPicker(true)
|
|
eq(emptySt.state, Ui.STATE.RESULT_MSG, "no news source opens no picker")
|
|
-- pokefirered/src/strings.c:1304 gText_NothingSentOver
|
|
check(emptySt.msg ~= nil and tostring(emptySt.msg.text):find("Nothing was sent over"),
|
|
"it says nothing was sent over instead")
|
|
for _ = 1, 600 do
|
|
if not emptySt.msg then break end
|
|
if emptySt.msg.revealed >= emptySt.msg.total then emptyStep("a") else emptyStep(nil) end
|
|
end
|
|
eq(emptySt.state, Ui.STATE.MAIN_MENU, "and returns to the Mystery Gift menu instead of looping")
|
|
|
|
MysteryGift.sources = realSources
|
|
end
|
|
|
|
print("[test] 8. The save message runs the save with no button press")
|
|
do
|
|
local realSources = MysteryGift.sources
|
|
local cardEntry = MysteryGift.builtins()[1].card
|
|
MysteryGift.sources = function()
|
|
return { { key = "card_only", label = "CARD ONLY", origin = "builtin", card = cardEntry } }
|
|
end
|
|
local saves = 0
|
|
local st2 = Ui.new({
|
|
session = MysteryGift.sessionFromSave({}),
|
|
onSave = function() saves = saves + 1 return true end,
|
|
})
|
|
local keys = {}
|
|
local function pressed(k) return keys[k] == true end
|
|
local function step(k)
|
|
keys = {}
|
|
if k then keys[k] = true end
|
|
Ui.update(st2, pressed, 1 / 60)
|
|
end
|
|
step("a")
|
|
for _ = 1, 600 do
|
|
if st2.state == Ui.STATE.SOURCE_INPUT then break end
|
|
if st2.msg and st2.msg.revealed >= st2.msg.total then step("a") else step(nil) end
|
|
end
|
|
step("a")
|
|
eq(st2.state, Ui.STATE.RESULT_MSG, "the card arrives")
|
|
for _ = 1, 600 do
|
|
if st2.state ~= Ui.STATE.RESULT_MSG then break end
|
|
if st2.msg and st2.msg.revealed >= st2.msg.total then step("a") else step(nil) end
|
|
end
|
|
eq(st2.state, Ui.STATE.SAVE, "the save message is up")
|
|
-- pokefirered/src/mystery_gift_menu.c:860 SaveOnMysteryGiftMenu
|
|
for _ = 1, 600 do
|
|
if saves > 0 then break end
|
|
step(nil)
|
|
end
|
|
eq(saves, 1, "the save ran with no button press")
|
|
eq(st2.state, Ui.STATE.SAVE_DONE, "and the completed message follows")
|
|
check(st2.msg ~= nil and tostring(st2.msg.text):find("press the A Button"),
|
|
"which is the one that waits for A")
|
|
MysteryGift.sources = realSources
|
|
end
|
|
|
|
print("[test] 9. EXIT stays on the menu under the MYSTERY GIFT row")
|
|
do
|
|
local gift = Boot.new()
|
|
Boot.setHasContinue(gift, true)
|
|
Boot.setContinueInfo(gift, { name = "RED", mysteryGift = true })
|
|
gift.phase = Boot.PHASE.MENU
|
|
gift.fadeT, gift.fadeTarget = 0, 0
|
|
local rows = Boot.menuItems(gift)
|
|
local sawExitRow = false
|
|
for _, row in ipairs(rows) do
|
|
if row == "EXIT" then sawExitRow = true end
|
|
end
|
|
check(sawExitRow, "the EXIT row is still there with MYSTERY GIFT on the menu")
|
|
local key = "select"
|
|
Boot.update(gift, { wasPressed = function(_, k) return k == key end }, 1 / 60)
|
|
check(gift.fadeThen ~= "exit", "SELECT is not a hidden exit")
|
|
for _ = 1, 3 do
|
|
key = "down"
|
|
Boot.update(gift, { wasPressed = function(_, k) return k == key end, isDown = function() return false end }, 1 / 60)
|
|
end
|
|
eq(gift.menuIndex, 4, "three downs reach the EXIT row")
|
|
check((gift.menuScroll or 0) > 0, "and the menu scrolled to show it")
|
|
key = "a"
|
|
Boot.update(gift, { wasPressed = function(_, k) return k == key end }, 1 / 60)
|
|
eq(gift.fadeThen, "exit", "A on EXIT starts the exit fade")
|
|
key = nil
|
|
local action = nil
|
|
for _ = 1, 400 do
|
|
action = Boot.update(gift, { wasPressed = function(_, k) return k == key end }, 1 / 60)
|
|
if action then break end
|
|
end
|
|
eq(type(action) == "table" and action.action or nil, "exit", "and it reaches the launcher exit action")
|
|
end
|
|
|
|
if failed == 0 then
|
|
print("PASS game3_gift_menu")
|
|
else
|
|
print("FAIL game3_gift_menu failures=" .. failed)
|
|
os.exit(1)
|
|
end
|