mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
396 lines
17 KiB
Lua
396 lines
17 KiB
Lua
#!/usr/bin/env luajit
|
|
-- pokefirered/src/pokemon_summary_screen.c:1615
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
require("tests.fixture_data.game3_map_sections").install()
|
|
|
|
local T = require("tests.harness")
|
|
local check, eq = T.check, T.eq
|
|
require("tests.game3_cache").stubSpeciesNames()
|
|
|
|
require("src.core.GameVersion").set("firered")
|
|
|
|
package.loaded["src.core.game3.audio"] = {
|
|
playSe = function() end, playCry = function() end,
|
|
playSong = function() end, stopAll = function() end,
|
|
}
|
|
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 gfx = setmetatable({}, { __index = function() return function() end end })
|
|
gfx.newQuad = function() return {} end
|
|
gfx.newImage = function() return nil end
|
|
gfx.getWidth = function() return 240 end
|
|
gfx.getHeight = function() return 160 end
|
|
_G.love = { graphics = gfx, timer = { getTime = function() return 0 end } }
|
|
|
|
local FrlgFont = require("src.ui.game3.frlg_font")
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
local SummaryMenu = require("src.ui.game3.summary_menu")
|
|
local SummaryChrome = require("src.ui.game3.summary_chrome")
|
|
|
|
local calls
|
|
local function reset_calls() calls = {} end
|
|
reset_calls()
|
|
for _, name in ipairs({ "drawBg3", "drawProgress", "drawLayer", "drawPageBg", "drawShinyStar", "drawStatusIcon",
|
|
"drawTypeBadge", "drawHpBar", "drawExpBar", "drawPokerus", "drawMoveSelectionCursor",
|
|
"drawMarkings" }) do
|
|
SummaryChrome[name] = function(...)
|
|
calls[#calls + 1] = { name = name, args = { ... } }
|
|
end
|
|
end
|
|
local MANIFEST = nil
|
|
SummaryChrome.manifest = function() return MANIFEST end
|
|
local SESSION = nil
|
|
package.loaded["src.core.game3.runtime"] = { getSession = function() return SESSION end }
|
|
local SummaryData = require("src.core.game3.summary_data")
|
|
SummaryData.moveDescription = function() return "" end
|
|
SummaryData.abilityDescription = function() return "" end
|
|
local realMemo = SummaryData.formatTrainerMemo
|
|
SummaryData.formatTrainerMemo = function() return {} end
|
|
SummaryData.expProgress = function() return { totalExp = 0, expNeeded = 0, progressPercent = 0 } end
|
|
Pokemon.abilityName = function() return "STATIC" end
|
|
Pokemon.abilityId = function() return 9 end
|
|
Pokemon.monFrontPic = function() calls[#calls + 1] = { name = "pic", args = {} } return nil end
|
|
Pokemon.monIcon = function() calls[#calls + 1] = { name = "icon", args = {} } return nil end
|
|
local texts = {}
|
|
local NORMAL = FrlgFont.COLOR.NORMAL
|
|
FrlgFont.draw = function(text, x, y, opts)
|
|
texts[#texts + 1] = { text = tostring(text), x = x, y = y, colors = opts and opts.colors }
|
|
end
|
|
|
|
local function find(name, pred)
|
|
for _, c in ipairs(calls) do
|
|
if c.name == name and (not pred or pred(c.args)) then return c end
|
|
end
|
|
return nil
|
|
end
|
|
local function text_at(str)
|
|
for _, t in ipairs(texts) do if t.text == str then return t end end
|
|
return nil
|
|
end
|
|
|
|
local function mon()
|
|
return {
|
|
species = 25, level = 20, hp = 20, maxHp = 47, status = "PSN", isShiny = true, markings = 5,
|
|
attack = 29, defense = 7, spAtk = 125, spDef = 22, speed = 45,
|
|
moves = { "THUNDER WAVE", "GROWL" }, pp = { 4, 40 }, maxPp = { 20, 40 },
|
|
otName = "RED", otId = 1, personality = 1,
|
|
}
|
|
end
|
|
|
|
local pressed
|
|
local input = { wasPressed = function(_, b) return pressed == b end }
|
|
local function press(b)
|
|
pressed = b
|
|
SummaryMenu.handleInput(input)
|
|
pressed = nil
|
|
end
|
|
local function frames(n) for _ = 1, n do SummaryMenu.update(1 / 60) end end
|
|
local function settle()
|
|
for _ = 1, 60 do
|
|
if not SummaryMenu._slide.active then break end
|
|
frames(1)
|
|
end
|
|
SummaryMenu.handleInput(input)
|
|
end
|
|
local function draw()
|
|
reset_calls()
|
|
texts = {}
|
|
SummaryMenu.draw()
|
|
end
|
|
|
|
print("[test] 1. pret's sprite table: the picture pane stays on KNOWN MOVES")
|
|
check(type(SummaryMenu.leftPaneSprites) == "function", "SummaryMenu.leftPaneSprites exists")
|
|
if type(SummaryMenu.leftPaneSprites) == "function" then
|
|
local moves = SummaryMenu.leftPaneSprites(SummaryMenu.PAGE_MOVES, nil)
|
|
check(moves.pic and moves.ball and moves.markings and not moves.icon, "KNOWN MOVES keeps pic, ball, markings; no icon")
|
|
eq(moves.status and moves.status.x, 0, "KNOWN MOVES status x is the INFO top-left")
|
|
eq(moves.status and moves.status.y, 34, "KNOWN MOVES status y is the INFO top-left")
|
|
eq(moves.shiny and moves.shiny.x, 102, "KNOWN MOVES star x")
|
|
local detail = SummaryMenu.leftPaneSprites(SummaryMenu.PAGE_MOVES_INFO, nil)
|
|
check(detail.icon and not detail.pic and not detail.ball and not detail.markings, "move detail swaps the pic for the icon")
|
|
eq(detail.icon and detail.icon.y, 16, "move detail icon top-left y")
|
|
check(detail.status == nil, "move detail from A hides the status icon")
|
|
local learn = SummaryMenu.leftPaneSprites(SummaryMenu.PAGE_MOVES_INFO, "select_move")
|
|
eq(learn.status and learn.status.y, 41, "learn/forget screen shows status at y 41")
|
|
eq(detail.shiny and detail.shiny.x, 4, "move detail star x")
|
|
end
|
|
|
|
print("[test] 2. PP colours")
|
|
check(type(SummaryMenu.ppColorIndex) == "function", "SummaryMenu.ppColorIndex exists")
|
|
if type(SummaryMenu.ppColorIndex) == "function" then
|
|
eq(SummaryMenu.ppColorIndex(20, 20, true), 0, "full PP is normal")
|
|
eq(SummaryMenu.ppColorIndex(10, 20, true), 1, "half PP is yellow")
|
|
eq(SummaryMenu.ppColorIndex(5, 20, true), 2, "quarter PP is orange")
|
|
eq(SummaryMenu.ppColorIndex(0, 20, true), 3, "no PP is red")
|
|
eq(SummaryMenu.ppColorIndex(2, 3, true), 2, "2/3 is orange")
|
|
eq(SummaryMenu.ppColorIndex(1, 2, true), 1, "1/2 is yellow")
|
|
eq(SummaryMenu.ppColorIndex(0, 0, false), 0, "empty slot is normal")
|
|
end
|
|
|
|
print("[test] 3. move detail cursor")
|
|
check(type(SummaryMenu.detailCursorStep) == "function", "SummaryMenu.detailCursorStep exists")
|
|
if type(SummaryMenu.detailCursorStep) == "function" then
|
|
local two = { {}, {} }
|
|
eq(SummaryMenu.detailCursorStep(two, 1, 1, false), 4, "down past the last move lands on CANCEL")
|
|
eq(SummaryMenu.detailCursorStep(two, 4, 1, false), 0, "down from CANCEL wraps to the first move")
|
|
eq(SummaryMenu.detailCursorStep(two, 0, -1, false), 4, "up from the first move lands on CANCEL")
|
|
eq(SummaryMenu.detailCursorStep(two, 0, -1, true), 1, "swapping never lands on CANCEL")
|
|
eq(SummaryMenu.detailCursorStep(two, 1, 1, true), 0, "swapping wraps down to the first move")
|
|
end
|
|
|
|
print("[test] 4. page flips, A on INFO, slides")
|
|
local party = { mon() }
|
|
local closed = false
|
|
SummaryMenu.openMenu(party, 1, { onClose = function() closed = true end })
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_INFO, "opens on INFO")
|
|
press("left")
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_INFO, "left on INFO does not wrap to MOVES")
|
|
press("right")
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_SKILLS, "right flips to SKILLS")
|
|
check(SummaryMenu._slide.active, "the flip slides")
|
|
frames(14)
|
|
check(SummaryMenu._slide.active, "the flip task still runs 14 frames after the press")
|
|
frames(1)
|
|
check(not SummaryMenu._slide.active, "the flip task ends on frame 15 (hop + Task_PokeSum_FlipPages)")
|
|
press("right")
|
|
settle()
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_MOVES, "right flips to KNOWN MOVES")
|
|
press("right")
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_MOVES, "right on KNOWN MOVES does not wrap to INFO")
|
|
|
|
draw()
|
|
check(find("pic") ~= nil and find("icon") == nil, "KNOWN MOVES draws the front pic, not the icon")
|
|
check(find("drawBg3", function(a) return a[1] == "info" end) ~= nil, "KNOWN MOVES keeps the picture-frame bg3")
|
|
check(find("drawStatusIcon", function(a) return a[1] == 0 and a[2] == 34 end) ~= nil, "KNOWN MOVES status at 0,34")
|
|
check(find("drawShinyStar", function(a) return a[1] == 102 and a[2] == 36 end) ~= nil, "KNOWN MOVES shiny star at 102,36")
|
|
check(find("drawMarkings", function(a) return a[1] == 5 end) ~= nil, "KNOWN MOVES draws the markings")
|
|
local pp = text_at("gText_PokeSum_PP")
|
|
check(pp ~= nil and pp.x == 196, "the PP glyph prints at x 196")
|
|
local cur = text_at("4")
|
|
eq(cur and cur.x, 212, "a one-digit current PP right-aligns to end at 217")
|
|
local hy = text_at("gText_PokeSum_OneHyphen")
|
|
eq(hy and hy.x, 163, "an empty slot prints one hyphen at the name column")
|
|
local two = text_at("gText_PokeSum_TwoHyphens")
|
|
eq(two and two.x, 205, "an empty slot's PP prints two hyphens at x 205")
|
|
|
|
press("a")
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_MOVES_INFO, "A opens move detail")
|
|
check(SummaryMenu._slide.active and SummaryMenu._slide.kind == "detail", "move detail slides in")
|
|
settle()
|
|
draw()
|
|
check(find("icon") ~= nil and find("pic") == nil, "move detail draws the icon")
|
|
check(find("drawStatusIcon") == nil, "move detail hides the status icon")
|
|
check(find("drawLayer", function(a) return a[1] == "moves" end) ~= nil, "move detail keeps the move list panel")
|
|
check(find("drawTypeBadge", function(a) return a[2] == 48 and a[3] == 35 end) ~= nil, "move detail shows the mon's type")
|
|
press("b")
|
|
settle()
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_MOVES, "B returns to KNOWN MOVES")
|
|
|
|
press("left") settle()
|
|
press("left") settle()
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_INFO, "back on INFO")
|
|
draw()
|
|
check(find("drawShinyStar", function(a) return a[1] == 102 end) ~= nil, "INFO star at 102,36")
|
|
press("a")
|
|
check(closed and not SummaryMenu.isOpen(), "A on INFO closes the summary")
|
|
|
|
print("[test] 5. SKILLS numbers right-align, one colour")
|
|
SummaryMenu.openMenu({ mon() }, 1, { page = SummaryMenu.PAGE_SKILLS })
|
|
draw()
|
|
local hp = text_at("20/47")
|
|
eq(hp and hp.x, 174 + 63 - 5 * 6, "HP right-aligns in 63 px")
|
|
local spa = text_at("125")
|
|
eq(spa and spa.x, 210 + 27 - 3 * 6, "a stat right-aligns in 27 px")
|
|
local atk = text_at("29")
|
|
check(atk and atk.colors == NORMAL and spa and spa.colors == NORMAL, "every stat prints in the one skills colour")
|
|
local def = text_at("7")
|
|
eq(def and def.x, 210 + 27 - 6, "a one-digit stat right-aligns too")
|
|
SummaryMenu.close()
|
|
|
|
local function text_xy(x, y)
|
|
for _, t in ipairs(texts) do if t.x == x and t.y == y then return t end end
|
|
return nil
|
|
end
|
|
local function layer_at(name, x)
|
|
return find("drawLayer", function(a) return a[1] == name and a[2] == x end) ~= nil
|
|
end
|
|
|
|
print("[test] 6. flips wait for the cart's task frames before sliding")
|
|
SummaryMenu.openMenu({ mon() }, 1, {})
|
|
press("right")
|
|
frames(6) draw()
|
|
check(layer_at("info", 0), "six frames after the press INFO has not moved")
|
|
frames(1) draw()
|
|
check(layer_at("info", 60), "frame 7 slides INFO 60 px")
|
|
frames(3) draw()
|
|
check(layer_at("info", 240), "frame 10 has INFO fully off")
|
|
check(text_at("20/47") == nil, "the SKILLS right pane waits for ShowBg(0)")
|
|
frames(4) draw()
|
|
check(text_at("20/47") ~= nil, "frame 14 shows the SKILLS right pane")
|
|
settle()
|
|
press("right") settle()
|
|
press("a")
|
|
frames(8) draw()
|
|
check(find("drawLayer", function(a) return a[1] == "moves_info" end) == nil, "eight frames after A the detail layer has not entered")
|
|
frames(1) draw()
|
|
check(layer_at("moves_info", 180), "frame 9 slides the detail layer in 60 px")
|
|
settle()
|
|
press("b")
|
|
frames(7) draw()
|
|
check(layer_at("moves_info", 0), "seven frames after B the detail layer has not left")
|
|
frames(1) draw()
|
|
check(layer_at("moves_info", 60), "frame 8 slides the detail layer out 60 px")
|
|
settle()
|
|
SummaryMenu.close()
|
|
|
|
print("[test] 7. header and name strip ride the page layers")
|
|
SummaryMenu.openMenu({ mon() }, 1, { page = SummaryMenu.PAGE_MOVES })
|
|
press("left")
|
|
frames(5) draw()
|
|
check(text_xy(4, 1) and text_xy(4, 1).text == "gText_PokeSum_PageName_KnownMoves", "before case 4 the old title stays")
|
|
frames(3) draw()
|
|
local title = text_xy(124, 1)
|
|
check(title and title.text == "gText_PokeSum_PageName_PokemonSkills", "a left flip carries the new title on the incoming layer")
|
|
check(text_xy(160, 18) ~= nil, "the name strip rides the incoming layer too")
|
|
check(text_xy(40, 18) == nil, "nothing is left at the static name position")
|
|
settle()
|
|
press("right")
|
|
frames(8) draw()
|
|
check(text_xy(4, 1) and text_xy(4, 1).text == "gText_PokeSum_PageName_KnownMoves", "a right flip prints the new title on the layer underneath")
|
|
check(layer_at("skills", 120), "while SKILLS slides off")
|
|
settle()
|
|
press("a")
|
|
frames(10) draw()
|
|
check(text_xy(40, 18) == nil and text_xy(4, 18) == nil, "the name strip is blank while move detail slides in")
|
|
title = text_xy(124, 1)
|
|
check(title and title.text == "gText_PokeSum_PageName_KnownMoves", "the detail title rides the sliding layer")
|
|
frames(6) draw()
|
|
check(text_xy(40, 18) ~= nil, "FromInfo case 11 reprints the name strip")
|
|
settle()
|
|
press("b")
|
|
frames(9) draw()
|
|
check(text_xy(40, 18) == nil, "the name strip is blank while move detail slides out")
|
|
check(text_xy(4, 1) ~= nil, "the KNOWN MOVES title stays put on the back-out")
|
|
frames(4) draw()
|
|
check(text_xy(40, 18) ~= nil and text_xy(4, 18) ~= nil, "back-out case 9 reprints the name strip with the level")
|
|
settle()
|
|
SummaryMenu.close()
|
|
|
|
print("[test] 8. a same-direction press during a flip is queued")
|
|
SummaryMenu.openMenu({ mon() }, 1, {})
|
|
press("right") frames(3)
|
|
press("right")
|
|
eq(SummaryMenu._slide.queued, 1, "lastPageFlipDirection holds the second right")
|
|
settle()
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_MOVES, "right-right from INFO lands on KNOWN MOVES")
|
|
settle()
|
|
press("left") frames(3)
|
|
press("right")
|
|
eq(SummaryMenu._slide.queued, nil, "an opposite press during a flip is dropped")
|
|
settle()
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_SKILLS, "the dropped press does not flip back")
|
|
SummaryMenu.close()
|
|
|
|
print("[test] 9. L/R flip pages only in the LR button mode")
|
|
SESSION = { options = { buttonMode = 1 } }
|
|
SummaryMenu.openMenu({ mon() }, 1, {})
|
|
press("r")
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_SKILLS, "R flips right in LR mode")
|
|
settle()
|
|
press("l")
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_INFO, "L flips left in LR mode")
|
|
settle()
|
|
SummaryMenu.close()
|
|
for _, mode in ipairs({ 0, 2 }) do
|
|
SESSION = { options = { buttonMode = mode } }
|
|
SummaryMenu.openMenu({ mon() }, 1, {})
|
|
press("r")
|
|
eq(SummaryMenu._page, SummaryMenu.PAGE_INFO, "R does not flip in button mode " .. mode)
|
|
check(not SummaryMenu._slide.active, "no flip starts in button mode " .. mode)
|
|
SummaryMenu.close()
|
|
end
|
|
SESSION = nil
|
|
|
|
print("[test] 10. the move cursor survives detail and page flips")
|
|
SummaryMenu.openMenu({ mon() }, 1, { page = SummaryMenu.PAGE_MOVES })
|
|
press("a") settle()
|
|
press("down")
|
|
eq(SummaryMenu._moveCursor, 2, "cursor on the second move")
|
|
press("b") settle()
|
|
press("a") settle()
|
|
eq(SummaryMenu._moveCursor, 2, "A back into detail keeps the cursor (only :989 and :5055 reset it)")
|
|
press("b") settle()
|
|
press("left") settle()
|
|
press("right") settle()
|
|
press("a") settle()
|
|
eq(SummaryMenu._moveCursor, 2, "page flips keep the cursor")
|
|
press("down")
|
|
eq(SummaryMenu._moveCursor, 5, "down past the last move lands on CANCEL")
|
|
press("b") settle()
|
|
press("a") settle()
|
|
eq(SummaryMenu._moveCursor, 1, "B on CANCEL resets the cursor to the first move")
|
|
SummaryMenu.close()
|
|
|
|
print("[test] 11. egg STATE box and memo")
|
|
local egg = { species = 1, isEgg = true, friendship = 5, otName = "RED", otId = 1, metLocation = 1 }
|
|
local memo = realMemo(egg, nil)
|
|
eq(#memo, 1, "the egg memo holds only the origin text")
|
|
check(memo[1] and not tostring(memo[1]):find("sEggHatchTimeTexts", 1, true), "the hatch text is not in the memo")
|
|
SummaryMenu.openMenu({ egg }, 1, {})
|
|
draw()
|
|
local st = text_at("sEggHatchTimeTexts[3]")
|
|
eq(st and st.x, 127, "the hatch text prints at the STATE box x (right pane + 7)")
|
|
eq(st and st.y, 61, "the hatch text prints at the STATE box y (right pane + 45)")
|
|
local nm = text_at("gText_EggNickname")
|
|
check(nm and nm.x == 167 and nm.y == 35, "the egg's name prints in the right pane, not the name strip")
|
|
SummaryMenu.close()
|
|
|
|
print("[test] 12. egg pic shake")
|
|
MANIFEST = { eggPicShake = {
|
|
{ 1, 1, 0, -1, -1, 0, -1, -1, 0, 1, 1 },
|
|
{ 2, 1, 0, -1, -2, 0, -2, -1, 0, 1, 2 },
|
|
{ 2, 1, 1, 0, -1, -1, -2, 0, -2, -1, -1, 0, 1, 1, 2 },
|
|
} }
|
|
local eggImg = {}
|
|
local drawn
|
|
gfx.draw = function(img, a, b)
|
|
if img == eggImg then drawn = a end
|
|
end
|
|
local realFront = Pokemon.frontPic
|
|
Pokemon.frontPic = function() return { image = eggImg, w = 64, h = 64 } end
|
|
for _, c in ipairs({ { 5, 60, 2, 15 }, { 10, 90, 2, 11 }, { 40, 120, 1, 11 }, { 41, 120, 1, 11 } }) do
|
|
local cycles, wait, first, len = c[1], c[2], c[3], c[4]
|
|
SummaryMenu.openMenu({ { species = 1, isEgg = true, friendship = cycles } }, 1, {})
|
|
frames(wait)
|
|
eq(SummaryMenu._bounce.dx, 0, cycles .. " cycles: still for " .. wait .. " frames")
|
|
frames(1)
|
|
eq(SummaryMenu._bounce.dx, first, cycles .. " cycles: first jiggle step")
|
|
frames(len - 1)
|
|
eq(SummaryMenu._bounce.dx, 0, cycles .. " cycles: jiggle ends centred")
|
|
eq(SummaryMenu._bounce.count, 1, cycles .. " cycles: one jiggle done")
|
|
frames(wait + 1)
|
|
eq(SummaryMenu._bounce.dx, first, cycles .. " cycles: second jiggle after the same delay")
|
|
frames(len - 1 + 400)
|
|
eq(SummaryMenu._bounce.count, 2, cycles .. " cycles: two jiggles only")
|
|
eq(SummaryMenu._bounce.dx, 0, cycles .. " cycles: rests centred")
|
|
SummaryMenu.close()
|
|
end
|
|
SummaryMenu.openMenu({ { species = 1, isEgg = true, friendship = 5 } }, 1, {})
|
|
frames(61)
|
|
drawn = nil
|
|
draw()
|
|
eq(drawn, 60 - 32 + 2 + 64, "the egg pic draws h-flipped at its shaken x")
|
|
SummaryMenu.close()
|
|
Pokemon.frontPic = realFront
|
|
MANIFEST = nil
|
|
|
|
T.finish("game3_summary_layout_s3_test")
|