mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
1018 lines
41 KiB
Lua
1018 lines
41 KiB
Lua
-- the OPTION row and its ladder, the launcher gear row, the mod-facing member
|
|
-- table -- plus the band geometry Game2 paints, asserted as rectangles rather
|
|
-- than as pixels.
|
|
--
|
|
-- What colour those bands actually come out is not a claim this file makes;
|
|
-- tests/drivers/gold_battle_bg_bug1709_test.lua is where a human judges that.
|
|
|
|
package.path = "./?.lua;" .. package.path
|
|
|
|
love = love or {}
|
|
love.graphics = love.graphics or {
|
|
getColor = function() return 1, 1, 1, 1 end,
|
|
setColor = function() end,
|
|
rectangle = function() end,
|
|
print = function() end,
|
|
printf = function() end,
|
|
draw = function() end,
|
|
newQuad = function() return {} end,
|
|
newImage = function() return nil end,
|
|
getShader = function() return nil end,
|
|
setShader = function() end,
|
|
newShader = function() error("no shaders in this harness") end,
|
|
getDimensions = function() return 160, 144 end,
|
|
push = function() end, pop = function() end,
|
|
translate = function() end, scale = function() end,
|
|
circle = function() end, clear = function() end,
|
|
}
|
|
love.math = love.math or { random = function(a, b) return b and a or 0.5 end }
|
|
love.image = love.image or {}
|
|
love.timer = love.timer or { getTime = function() return 0 end }
|
|
love.filesystem = love.filesystem or {
|
|
load = function() return nil end,
|
|
getInfo = function() return nil end,
|
|
read = function() return nil end,
|
|
write = function() return true end,
|
|
remove = function() return true end,
|
|
}
|
|
require("src.core.Logger").warn = function() end
|
|
|
|
local BattleState = require("src.ui.gen2.BattleState")
|
|
local Chrome = require("src.ui.gen2.Chrome")
|
|
local Game2 = require("src.core.Game2")
|
|
local Gen2Compat = require("src.mods.Gen2Compat")
|
|
local LauncherSettings = require("src.import.LauncherSettings")
|
|
local OptionsMenu = require("src.ui.gen2.OptionsMenu")
|
|
local Save = require("src.core.gen2.Save")
|
|
local ScreenPosition = require("src.core.ScreenPosition")
|
|
|
|
local checks, failures = 0, 0
|
|
local function check(label, got, want)
|
|
checks = checks + 1
|
|
if got ~= want then
|
|
failures = failures + 1
|
|
print(("FAIL %s: got %s want %s"):format(label, tostring(got),
|
|
tostring(want)))
|
|
end
|
|
end
|
|
|
|
-- --------------------------------------------------------------- the key
|
|
|
|
check("the default surround is the cart's paper white",
|
|
Save.DEFAULT_OPTIONS.battleBg, "white")
|
|
check("and a fresh options table carries it",
|
|
Save.defaultOptions().battleBg, "white")
|
|
|
|
check("the default battle size is the whole-pixel blit",
|
|
Save.DEFAULT_OPTIONS.battleFit, "fixed")
|
|
check("and a fresh options table carries that too",
|
|
Save.defaultOptions().battleFit, "fixed")
|
|
|
|
-- --------------------------------------------------------------- the row
|
|
|
|
local function rowNamed(label)
|
|
for i, row in ipairs(OptionsMenu.ROWS) do
|
|
if row.label == label then return i, row end
|
|
end
|
|
end
|
|
|
|
local bgIndex, bgRow = rowNamed("BATTLE BG")
|
|
check("OPTION has a BATTLE BG row", bgRow ~= nil, true)
|
|
if bgRow then
|
|
check("it edits battleBg", bgRow.key, "battleBg")
|
|
check("it is a port row, not one of the cart's seven", bgRow.port, true)
|
|
check("WHITE, BLACK and WORLD are the whole ladder", #bgRow.values, 3)
|
|
check("stored lowercase, shown WHITE", bgRow.display.white, "WHITE")
|
|
check("and BLACK", bgRow.display.black, "BLACK")
|
|
check("and WORLD", bgRow.display.world, "WORLD")
|
|
-- Appended at the tail, so nothing the other suites index by position moves.
|
|
check("it follows BATTLE SIZE",
|
|
OptionsMenu.ROWS[bgIndex - 1].label, "BATTLE SIZE")
|
|
check("and CANCEL still ends the list",
|
|
OptionsMenu.ROWS[bgIndex + 1].cancel, true)
|
|
check("CANCEL is last", bgIndex + 1, #OptionsMenu.ROWS)
|
|
end
|
|
|
|
local sizeIndex, sizeRow = rowNamed("BATTLE SIZE")
|
|
check("OPTION has a BATTLE SIZE row", sizeRow ~= nil, true)
|
|
if sizeRow then
|
|
check("it edits battleFit", sizeRow.key, "battleFit")
|
|
check("it is a port row, not one of the cart's seven", sizeRow.port, true)
|
|
check("FIXED and FILL are the whole ladder", #sizeRow.values, 2)
|
|
check("stored lowercase, shown FIXED", sizeRow.display.fixed, "FIXED")
|
|
check("and FILL, padded to the value column",
|
|
sizeRow.display.fill, "FILL ")
|
|
check("it sits in the port's tail, past the cart's seven",
|
|
sizeIndex > 7, true)
|
|
check("BATTLE BG comes straight after it", sizeIndex + 1, bgIndex)
|
|
end
|
|
check("the cart's rows are unmoved", OptionsMenu.ROWS[7].key, "frame")
|
|
check("the rebind screen is unmoved", OptionsMenu.ROWS[8].id, "controls")
|
|
check("the port's audio group is unmoved", OptionsMenu.ROWS[9].key, "musicVol")
|
|
|
|
-- The screen is built from ROWS, so the row has to survive buildRows too.
|
|
local menu = OptionsMenu.new({ options = Save.defaultOptions() },
|
|
{ options = Save.defaultOptions() })
|
|
local built
|
|
for _, row in ipairs(menu.rows) do
|
|
if row.key == "battleBg" then built = row end
|
|
end
|
|
check("buildRows keeps it", built ~= nil, true)
|
|
if built then
|
|
check("and gives it the mod-facing id battleBg", built.id, "battleBg")
|
|
check("the menu starts on WHITE", menu.options.battleBg, "white")
|
|
menu:cycle(built, 1)
|
|
check("right stores black, not the display string",
|
|
menu.options.battleBg, "black")
|
|
menu:cycle(built, 1)
|
|
check("right again reaches world", menu.options.battleBg, "world")
|
|
menu:cycle(built, 1)
|
|
check("and wraps to white", menu.options.battleBg, "white")
|
|
menu:cycle(built, -1)
|
|
check("left walks the ladder the other way", menu.options.battleBg, "world")
|
|
menu:cycle(built, -1)
|
|
check("still the other way", menu.options.battleBg, "black")
|
|
menu:cycle(built, -1)
|
|
check("and back", menu.options.battleBg, "white")
|
|
end
|
|
|
|
local builtFit
|
|
for _, row in ipairs(menu.rows) do
|
|
if row.key == "battleFit" then builtFit = row end
|
|
end
|
|
check("buildRows keeps BATTLE SIZE", builtFit ~= nil, true)
|
|
if builtFit then
|
|
check("and gives it the mod-facing id battleFit", builtFit.id, "battleFit")
|
|
check("the menu starts on FIXED", menu.options.battleFit, "fixed")
|
|
menu:cycle(builtFit, 1)
|
|
check("right stores fill, not the display string",
|
|
menu.options.battleFit, "fill")
|
|
menu:cycle(builtFit, 1)
|
|
check("right again wraps to fixed", menu.options.battleFit, "fixed")
|
|
menu:cycle(builtFit, -1)
|
|
check("left walks the ladder the other way", menu.options.battleFit, "fill")
|
|
menu:cycle(builtFit, -1)
|
|
check("and back", menu.options.battleFit, "fixed")
|
|
end
|
|
|
|
-- ------------------------------------------------------ the launcher gear
|
|
|
|
local model = LauncherSettings.open(nil, "gold")
|
|
local gearRow
|
|
for _, section in ipairs(model.sections) do
|
|
for _, row in ipairs(section.rows) do
|
|
if row.label == "BATTLE BG" then gearRow = row end
|
|
end
|
|
end
|
|
check("the gold gear offers BATTLE BG", gearRow ~= nil, true)
|
|
if gearRow then
|
|
model.opts.gold.battleBg = nil
|
|
check("an options.lua with no battleBg reads WHITE", gearRow.value(), "WHITE")
|
|
gearRow.step(1)
|
|
check("stepping writes the gold block, not the flat Gen 1 one",
|
|
model.opts.gold.battleBg, "black")
|
|
check("and the row reads BLACK", gearRow.value(), "BLACK")
|
|
gearRow.step(1)
|
|
check("stepping again reaches world", model.opts.gold.battleBg, "world")
|
|
check("and the row reads WORLD", gearRow.value(), "WORLD")
|
|
gearRow.step(1)
|
|
check("stepping again returns to white", model.opts.gold.battleBg, "white")
|
|
end
|
|
|
|
local gearSize
|
|
for _, section in ipairs(model.sections) do
|
|
for _, row in ipairs(section.rows) do
|
|
if row.label == "BATTLE SIZE" then gearSize = row end
|
|
end
|
|
end
|
|
check("the gold gear offers BATTLE SIZE", gearSize ~= nil, true)
|
|
if gearSize then
|
|
model.opts.gold.battleFit = nil
|
|
check("an options.lua with no battleFit reads FIXED", gearSize.value(),
|
|
"FIXED")
|
|
gearSize.step(1)
|
|
check("stepping writes the gold block, not the flat Gen 1 one",
|
|
model.opts.gold.battleFit, "fill")
|
|
check("and the row reads FILL", gearSize.value(), "FILL")
|
|
gearSize.step(1)
|
|
check("stepping again returns to fixed", model.opts.gold.battleFit, "fixed")
|
|
end
|
|
|
|
-- ---------------------------------------------------------- the battle end
|
|
|
|
local bgMode = BattleState.bgMode
|
|
check("the Gold battle screen answers bgMode", type(bgMode), "function")
|
|
if type(bgMode) == "function" then
|
|
check("black when the option says so",
|
|
bgMode({ game = { options = { battleBg = "black" } } }), "black")
|
|
check("white when it says white",
|
|
bgMode({ game = { options = { battleBg = "white" } } }), "white")
|
|
check("white on an options table that predates the key",
|
|
bgMode({ game = { options = {} } }), "white")
|
|
check("white with no game at all", bgMode({}), "white")
|
|
check("and WORLD is the third rung, not a stray value",
|
|
bgMode({ game = { options = { battleBg = "world" } } }), "world")
|
|
check("an unknown mode still falls back to white",
|
|
bgMode({ game = { options = { battleBg = "plaid" } } }), "white")
|
|
end
|
|
|
|
check("the battle screen is opaque in every mode", BattleState.isOpaque, true)
|
|
|
|
check("the dim the WORLD veil is painted at is published",
|
|
BattleState.BG_WORLD_DIM, 0.55)
|
|
|
|
check("and mods are told bgMode is backed on this side",
|
|
Gen2Compat.memberStatus("src.battle.BattleState", "bgMode"), "backed")
|
|
check("as is the dim they would read off it",
|
|
Gen2Compat.memberStatus("src.battle.BattleState", "BG_WORLD_DIM"), "backed")
|
|
|
|
-- ------------------------------------------------------------- the bands
|
|
--
|
|
-- Game2 paints the surround, not the battle screen: the widescreen layer
|
|
-- resolves to the TOP state, so a party menu or text box opened over the
|
|
-- battle would otherwise repaint the void white under itself.
|
|
|
|
local G = love.graphics
|
|
local realRect, realSetColor = G.rectangle, G.setColor
|
|
local painter = Game2.paintBattleSurround
|
|
check("Game2 owns the surround repaint", type(painter), "function")
|
|
|
|
local function paint(states, w, h)
|
|
if type(painter) ~= "function" then return {} end
|
|
local rects, pen = {}, { 1, 1, 1, 1 }
|
|
G.setColor = function(r, g, b, a)
|
|
pen = { r or 0, g or 0, b or 0, a or 1 }
|
|
end
|
|
G.rectangle = function(_, x, y, rw, rh)
|
|
rects[#rects + 1] = { x = x, y = y, w = rw, h = rh, pen = pen }
|
|
end
|
|
local stack = { states = states }
|
|
function stack:top() return self.states[#self.states] end
|
|
function stack:visibleBase()
|
|
for i = #self.states, 1, -1 do
|
|
if self.states[i].isOpaque then return i end
|
|
end
|
|
return 1
|
|
end
|
|
local ok, err = pcall(painter, { stack = stack }, w, h)
|
|
G.rectangle, G.setColor = realRect, realSetColor
|
|
if not ok then
|
|
check("paintBattleSurround ran: " .. tostring(err), false, true)
|
|
end
|
|
return rects
|
|
end
|
|
|
|
local blackBattle = { bgMode = function() return "black" end }
|
|
local whiteBattle = { bgMode = function() return "white" end }
|
|
local worldBattle = {
|
|
bgMode = function() return "world" end,
|
|
BG_WORLD_DIM = BattleState.BG_WORLD_DIM,
|
|
}
|
|
local plainMenu = {}
|
|
local overworld = {}
|
|
|
|
check("BLACK paints the four bands", #paint({ blackBattle }, 1024, 768), 4)
|
|
check("WHITE paints nothing at all", #paint({ whiteBattle }, 1024, 768), 0)
|
|
check("WORLD paints the same four bands, as a veil",
|
|
#paint({ worldBattle }, 1024, 768), 4)
|
|
check("the overworld alone paints nothing",
|
|
#paint({ overworld, plainMenu }, 1024, 768), 0)
|
|
|
|
-- A menu over the battle keeps the battle's void: the walk goes down the
|
|
-- stack past states that have no bgMode of their own.
|
|
check("a party menu over the battle still finds the battle's mode",
|
|
#paint({ overworld, blackBattle, plainMenu }, 1024, 768), 4)
|
|
check("and the same under WORLD",
|
|
#paint({ overworld, worldBattle, plainMenu }, 1024, 768), 4)
|
|
|
|
local blackPen = paint({ blackBattle }, 1024, 768)[1]
|
|
local worldPen = paint({ worldBattle }, 1024, 768)[1]
|
|
check("BLACK is opaque", blackPen and blackPen.pen[4], 1)
|
|
check("WORLD is the published dim", worldPen and worldPen.pen[4],
|
|
BattleState.BG_WORLD_DIM)
|
|
check("and a zero dim paints nothing", #paint({
|
|
{ bgMode = function() return "world" end, BG_WORLD_DIM = 0 } }, 1024, 768), 0)
|
|
|
|
local function panelSafe(states, w, h, label, scale)
|
|
scale = scale or Chrome.fitScale(w, h)
|
|
local ox, oy = Chrome.fitOrigin(w, h, scale)
|
|
local pw, ph = 160 * scale, 144 * scale
|
|
local rects = paint(states, w, h)
|
|
local covered, overlap, offColour = 0, 0, 0
|
|
for _, r in ipairs(rects) do
|
|
covered = covered + r.w * r.h
|
|
if r.pen[1] ~= 0 or r.pen[2] ~= 0 or r.pen[3] ~= 0 then
|
|
offColour = offColour + 1
|
|
end
|
|
local ix = math.max(0, math.min(r.x + r.w, ox + pw) - math.max(r.x, ox))
|
|
local iy = math.max(0, math.min(r.y + r.h, oy + ph) - math.max(r.y, oy))
|
|
overlap = overlap + ix * iy
|
|
end
|
|
-- The band maths has to tile the void exactly: any overlap with the panel is
|
|
-- a black frame eating the HUD or the message box, and any shortfall is a
|
|
-- strip of white left behind on one edge.
|
|
check(label .. ": no band touches the battle panel", overlap, 0)
|
|
check(label .. ": the void is covered edge to edge",
|
|
math.abs(covered - (w * h - pw * ph)) < 1, true)
|
|
check(label .. ": every band is black", offColour, 0)
|
|
end
|
|
|
|
for _, size in ipairs({ { 1024, 768 }, { 1280, 840 }, { 1920, 1080 },
|
|
{ 800, 600 }, { 1366, 768 } }) do
|
|
panelSafe({ blackBattle }, size[1], size[2],
|
|
("%dx%d"):format(size[1], size[2]))
|
|
panelSafe({ worldBattle }, size[1], size[2],
|
|
("world %dx%d"):format(size[1], size[2]))
|
|
end
|
|
|
|
-- SCREEN POS lifts the panel off centre, which is the other way the four
|
|
-- bands can stop agreeing with where the panel actually landed.
|
|
for _, mode in ipairs({ "center", "upper", "top" }) do
|
|
ScreenPosition.setMode(mode)
|
|
panelSafe({ blackBattle, plainMenu }, 1280, 840, "screen pos " .. mode)
|
|
end
|
|
ScreenPosition.setMode("center")
|
|
|
|
-- A window smaller than the GB screen has no void to paint; the bands must
|
|
-- not wrap around to the far edge on the negative origin.
|
|
check("nothing to paint under 160x144", #paint({ blackBattle }, 100, 90), 0)
|
|
|
|
local wantsFill = BattleState.wantsFillScale
|
|
check("the Gold battle screen answers wantsFillScale", type(wantsFill),
|
|
"function")
|
|
if type(wantsFill) == "function" then
|
|
check("FILL asks for the fill scale",
|
|
wantsFill({ game = { options = { battleFit = "fill" } } }), true)
|
|
check("FIXED does not",
|
|
wantsFill({ game = { options = { battleFit = "fixed" } } }), false)
|
|
check("nor does an options table that predates the key",
|
|
wantsFill({ game = { options = {} } }), false)
|
|
check("nor a screen with no game at all", wantsFill({}), false)
|
|
end
|
|
|
|
check("FIXED is the integer fit", BattleState.panelScale(1280, 840, false),
|
|
Chrome.fitScale(1280, 840))
|
|
check("FILL is min(w/160, h/144)", BattleState.fillScale(1280, 840), 840 / 144)
|
|
check("and FILL is not the integer fit here",
|
|
BattleState.fillScale(1280, 840) ~= Chrome.fitScale(1280, 840), true)
|
|
check("FILL never goes below one whole pixel",
|
|
BattleState.fillScale(100, 90), 1)
|
|
|
|
local fillBattle = {
|
|
bgMode = function() return "black" end,
|
|
battlePanelScale = function(_, w, h) return BattleState.fillScale(w, h) end,
|
|
}
|
|
for _, size in ipairs({ { 1280, 840 }, { 1366, 768 }, { 1024, 768 } }) do
|
|
panelSafe({ fillBattle }, size[1], size[2],
|
|
("fill %dx%d"):format(size[1], size[2]),
|
|
BattleState.fillScale(size[1], size[2]))
|
|
panelSafe({ fillBattle, plainMenu }, size[1], size[2],
|
|
("fill under a menu %dx%d"):format(size[1], size[2]),
|
|
BattleState.fillScale(size[1], size[2]))
|
|
end
|
|
|
|
local widescreenMenu = { drawsWidescreen = function() return true end }
|
|
panelSafe({ fillBattle, widescreenMenu }, 1280, 840,
|
|
"a widescreen menu over a fill battle", Chrome.fitScale(1280, 840))
|
|
|
|
-- ----------------------------------------------------- the call site
|
|
|
|
local function scene(mode, opts)
|
|
opts = opts or {}
|
|
local log = {}
|
|
local function note(what)
|
|
log[#log + 1] = { what = what, flag = Chrome.worldSurround }
|
|
end
|
|
local battle = {
|
|
isOpaque = true,
|
|
drawsWidescreen = function() return true end,
|
|
drawWidescreen = function() note("widescreen") end,
|
|
bgMode = function() return mode end,
|
|
BG_WORLD_DIM = BattleState.BG_WORLD_DIM,
|
|
}
|
|
if opts.fill then
|
|
battle.battlePanelScale = function(_, w, h)
|
|
return BattleState.fillScale(w, h)
|
|
end
|
|
end
|
|
local states = { battle }
|
|
if opts.menu then states[2] = plainMenu end
|
|
local game = {
|
|
stack = {
|
|
states = states,
|
|
top = function(self) return self.states[#self.states] end,
|
|
renderVisible = function(_, state) return state ~= nil end,
|
|
visibleBase = function() return 1 end,
|
|
draw = function() note("stack") end,
|
|
},
|
|
world = { map = {}, draw = function() note("world") end },
|
|
inFillBoot = function() return false end,
|
|
letterbox = function(_, _, _, worldActive)
|
|
note(worldActive and "letterbox(world)" or "letterbox")
|
|
end,
|
|
paintBattleSurround = function() note("surround") end,
|
|
}
|
|
local scales = {}
|
|
local realScale = G.scale
|
|
G.scale = function(sx, sy) scales[#scales + 1] = sx; realScale(sx, sy) end
|
|
Chrome.worldSurround = true -- a stale flag drawScene has to clear itself
|
|
local ok, err = pcall(Game2.drawScene, game, 1280, 840)
|
|
G.scale = realScale
|
|
Chrome.worldSurround = false
|
|
if not ok then check("drawScene ran: " .. tostring(err), false, true) end
|
|
local order, flags = {}, {}
|
|
for i, entry in ipairs(log) do
|
|
order[i], flags[entry.what] = entry.what, entry.flag
|
|
end
|
|
return table.concat(order, ","), flags, game, scales
|
|
end
|
|
|
|
do
|
|
local order, flags, game = scene("world")
|
|
check("a WORLD battle puts the map up before the widescreen layer",
|
|
order, "letterbox(world),world,widescreen,surround,letterbox")
|
|
check("the widescreen layer paints with the paper suppressed",
|
|
flags["widescreen"], true)
|
|
check("and so does the surround", flags["surround"], true)
|
|
check("the flag is down again by the closing letterbox",
|
|
flags["letterbox"], false)
|
|
check("and down when drawScene returns", Chrome.worldSurround, false)
|
|
check("the frame is marked as having drawn the world",
|
|
game.frameWorldActive, true)
|
|
end
|
|
|
|
do
|
|
local order, flags, game = scene("black")
|
|
check("BLACK never draws the map", order, "widescreen,surround,letterbox")
|
|
check("and never suppresses the paper", flags["widescreen"], false)
|
|
check("nor marks the frame", game.frameWorldActive, false)
|
|
end
|
|
|
|
do
|
|
local order, _, _, scales = scene("black", { fill = true, menu = true })
|
|
check("a menu over the battle blits over the widescreen layer",
|
|
order, "widescreen,surround,letterbox,stack")
|
|
check("at the battle's own panel scale", scales[1],
|
|
BattleState.fillScale(1280, 840))
|
|
end
|
|
|
|
do
|
|
local painted = 0
|
|
local rect, setColor = G.rectangle, G.setColor
|
|
G.rectangle = function() painted = painted + 1 end
|
|
G.setColor = function() end
|
|
local ok = pcall(Chrome.letterbox, 320, 288, 1, 1, 1)
|
|
local down = painted
|
|
Chrome.worldSurround = true
|
|
local okUp = pcall(Chrome.letterbox, 320, 288, 1, 1, 1)
|
|
Chrome.worldSurround = false
|
|
G.rectangle, G.setColor = rect, setColor
|
|
check("Chrome.letterbox paints the paper by default", ok and down, 1)
|
|
check("and stands down for a WORLD surround", okUp and painted, 1)
|
|
end
|
|
check("and the flag is down by default", Chrome.worldSurround, false)
|
|
|
|
|
|
local WideBattle = require("src.ui.gen2.WideBattle")
|
|
|
|
check("the default layout is the cart's panel",
|
|
Save.DEFAULT_OPTIONS.battleLayout, "og")
|
|
check("and a fresh options table carries it",
|
|
Save.defaultOptions().battleLayout, "og")
|
|
|
|
local layoutIndex, layoutRow = rowNamed("BATTLE LAYOUT")
|
|
check("OPTION has a BATTLE LAYOUT row", layoutRow ~= nil, true)
|
|
if layoutRow then
|
|
check("it edits battleLayout", layoutRow.key, "battleLayout")
|
|
check("it is a port row, not one of the cart's seven", layoutRow.port, true)
|
|
check("OG and WIDE are the whole ladder", #layoutRow.values, 2)
|
|
check("stored lowercase, shown OG, padded to the value column",
|
|
layoutRow.display.og, "OG ")
|
|
check("and WIDE", layoutRow.display.wide, "WIDE")
|
|
check("BATTLE HUD sits between it and BATTLE SIZE", layoutIndex + 2,
|
|
sizeIndex)
|
|
check("and that row is BATTLE HUD",
|
|
OptionsMenu.ROWS[layoutIndex + 1].label, "BATTLE HUD")
|
|
end
|
|
|
|
local builtLayout, viewedLayout
|
|
for _, row in ipairs(menu.rows) do
|
|
if row.key == "battleLayout" then builtLayout = row end
|
|
end
|
|
for _, row in ipairs(menu.view or {}) do
|
|
if row.id == "battleLayout" then viewedLayout = row end
|
|
end
|
|
check("buildRows keeps BATTLE LAYOUT", builtLayout ~= nil, true)
|
|
check("and files it under the BATTLE OPTIONS group", viewedLayout, nil)
|
|
if builtLayout then
|
|
check("and gives it the mod-facing id battleLayout", builtLayout.id,
|
|
"battleLayout")
|
|
menu.options.battleLayout = "og"
|
|
menu:cycle(builtLayout, 1)
|
|
check("right stores wide, not the display string",
|
|
menu.options.battleLayout, "wide")
|
|
menu:cycle(builtLayout, 1)
|
|
check("right again wraps to og", menu.options.battleLayout, "og")
|
|
menu:cycle(builtLayout, -1)
|
|
check("left walks the ladder the other way", menu.options.battleLayout,
|
|
"wide")
|
|
menu:cycle(builtLayout, -1)
|
|
check("and back", menu.options.battleLayout, "og")
|
|
end
|
|
|
|
local gearLayout
|
|
for _, section in ipairs(model.sections) do
|
|
for _, row in ipairs(section.rows) do
|
|
if row.label == "BATTLE LAYOUT" then gearLayout = row end
|
|
end
|
|
end
|
|
check("the gold gear offers BATTLE LAYOUT", gearLayout ~= nil, true)
|
|
if gearLayout then
|
|
model.opts.gold.battleLayout = nil
|
|
check("an options.lua with no battleLayout reads OG", gearLayout.value(),
|
|
"OG")
|
|
gearLayout.step(1)
|
|
check("stepping writes the gold block, not the flat Gen 1 one",
|
|
model.opts.gold.battleLayout, "wide")
|
|
check("and the row reads WIDE", gearLayout.value(), "WIDE")
|
|
gearLayout.step(1)
|
|
check("stepping again returns to og", model.opts.gold.battleLayout, "og")
|
|
end
|
|
|
|
local wideLayout = BattleState.wideLayout
|
|
check("the Gold battle screen answers wideLayout", type(wideLayout), "function")
|
|
check("under the same name Gen 1 uses",
|
|
BattleState.isWideBattleLayout, wideLayout)
|
|
if type(wideLayout) == "function" then
|
|
check("WIDE asks for the wide surface",
|
|
wideLayout({ game = { options = { battleLayout = "wide" } } }), true)
|
|
check("OG does not",
|
|
wideLayout({ game = { options = { battleLayout = "og" } } }), false)
|
|
check("nor does an options table that predates the key",
|
|
wideLayout({ game = { options = {} } }), false)
|
|
check("nor a screen with no game at all", wideLayout({}), false)
|
|
end
|
|
|
|
check("and mods are told wideLayout is backed on this side",
|
|
Gen2Compat.memberStatus("src.battle.BattleState", "wideLayout"), "backed")
|
|
check("as is isWideBattleLayout",
|
|
Gen2Compat.memberStatus("src.battle.BattleState", "isWideBattleLayout"),
|
|
"backed")
|
|
|
|
check("the wide surface is 304 pixels across", WideBattle.WIDTH, 304)
|
|
check("and the cart's 144 tall", WideBattle.HEIGHT, 144)
|
|
check("the classic field sits 9 tiles in", WideBattle.FIELD_X, 72)
|
|
check("which leaves 18 tiles of extra width", WideBattle.EXTRA_TILES, 18)
|
|
check("38 tiles across", WideBattle.TILES_W, 38)
|
|
|
|
local ogScreen = { game = { options = { battleLayout = "og" } } }
|
|
local wideScreen = { game = { options = { battleLayout = "wide" } } }
|
|
local pw, ph = BattleState.panelSize(ogScreen)
|
|
check("OG composes on the cart's panel", pw, 160)
|
|
check("and its height", ph, 144)
|
|
pw, ph = BattleState.panelSize(wideScreen)
|
|
check("WIDE composes on the wide one", pw, 304)
|
|
check("at the same height", ph, 144)
|
|
|
|
check("WIDE takes the fit its own width allows",
|
|
BattleState.battlePanelScale(wideScreen, 640, 480),
|
|
Chrome.fitScaleFor(640, 480, 38, 18))
|
|
check("which is a step under the classic fit here",
|
|
BattleState.battlePanelScale(wideScreen, 640, 480)
|
|
< Chrome.fitScale(640, 480), true)
|
|
check("OG is unchanged", BattleState.battlePanelScale(ogScreen, 640, 480),
|
|
Chrome.fitScale(640, 480))
|
|
check("Chrome.fitScale is still the 20x18 fit",
|
|
Chrome.fitScale(640, 480), Chrome.fitScaleFor(640, 480, 20, 18))
|
|
check("and fitOrigin still the 20x18 origin",
|
|
select(1, Chrome.fitOrigin(640, 480, 2)),
|
|
select(1, Chrome.fitOriginFor(640, 480, 2, 20, 18)))
|
|
|
|
local wideFill = {
|
|
game = { options = { battleLayout = "wide", battleFit = "fill" } },
|
|
}
|
|
check("WIDE + FILL measures against 304 too",
|
|
BattleState.battlePanelScale(wideFill, 1280, 840),
|
|
WideBattle.fillScale(1280, 840))
|
|
check("and that is not the classic fill",
|
|
WideBattle.fillScale(1280, 840) ~= BattleState.fillScale(1280, 840), true)
|
|
check("WIDE + FILL never goes below one whole pixel",
|
|
WideBattle.fillScale(100, 90), 1)
|
|
|
|
local function wideSafe(states, w, h, scale, label)
|
|
local ox, oy = Chrome.fitOriginFor(w, h, scale, 38, 18)
|
|
local pw, ph = 304 * scale, 144 * scale
|
|
local rects = paint(states, w, h)
|
|
local covered, overlap = 0, 0
|
|
for _, r in ipairs(rects) do
|
|
covered = covered + r.w * r.h
|
|
local ix = math.max(0, math.min(r.x + r.w, ox + pw) - math.max(r.x, ox))
|
|
local iy = math.max(0, math.min(r.y + r.h, oy + ph) - math.max(r.y, oy))
|
|
overlap = overlap + ix * iy
|
|
end
|
|
local want = 0
|
|
for _, on in ipairs({ oy > 0, oy + ph < h, ox > 0, ox + pw < w }) do
|
|
if on then want = want + 1 end
|
|
end
|
|
check(label .. ": every void edge gets a band", #rects, want)
|
|
check(label .. ": no band touches the wide panel", overlap, 0)
|
|
check(label .. ": the void is covered edge to edge",
|
|
math.abs(covered - (w * h - pw * ph)) < 1, true)
|
|
end
|
|
|
|
for _, size in ipairs({ { 1280, 840 }, { 1366, 768 }, { 1920, 1080 } }) do
|
|
local w, h = size[1], size[2]
|
|
local fit = Chrome.fitScaleFor(w, h, 38, 18)
|
|
local wideBattleState = {
|
|
bgMode = function() return "black" end,
|
|
panelSize = function() return 304, 144 end,
|
|
battlePanelScale = function() return fit end,
|
|
}
|
|
local wideFillBattle = {
|
|
bgMode = function() return "black" end,
|
|
panelSize = function() return 304, 144 end,
|
|
battlePanelScale = function() return WideBattle.fillScale(w, h) end,
|
|
}
|
|
local listOverBattle = {
|
|
drawsWidescreen = function() return true end,
|
|
drawWidescreen = function() end,
|
|
}
|
|
wideSafe({ wideBattleState }, w, h, fit, ("WIDE %dx%d"):format(w, h))
|
|
wideSafe({ wideBattleState, listOverBattle }, w, h, fit,
|
|
("WIDE under a party list %dx%d"):format(w, h))
|
|
wideSafe({ wideBattleState, plainMenu }, w, h, fit,
|
|
("WIDE under a plain menu %dx%d"):format(w, h))
|
|
wideSafe({ wideFillBattle, listOverBattle }, w, h,
|
|
WideBattle.fillScale(w, h),
|
|
("WIDE + FILL under a party list %dx%d"):format(w, h))
|
|
|
|
local opaqueList = {
|
|
isOpaque = true,
|
|
drawsWidescreen = function() return true end,
|
|
drawWidescreen = function() end,
|
|
}
|
|
local rects = paint({ wideBattleState, opaqueList }, w, h)
|
|
local fit = Chrome.fitScale(w, h)
|
|
local bx, by = Chrome.fitOrigin(w, h, fit)
|
|
local bw, bh = 160 * fit, 144 * fit
|
|
local covered, overlap = 0, 0
|
|
for _, r in ipairs(rects) do
|
|
covered = covered + r.w * r.h
|
|
local ix = math.max(0, math.min(r.x + r.w, bx + bw) - math.max(r.x, bx))
|
|
local iy = math.max(0, math.min(r.y + r.h, by + bh) - math.max(r.y, by))
|
|
overlap = overlap + ix * iy
|
|
end
|
|
check(("an opaque list over WIDE %dx%d: no band clips the list"):format(w, h),
|
|
overlap, 0)
|
|
check(("an opaque list over WIDE %dx%d: the void is covered"):format(w, h),
|
|
math.abs(covered - (w * h - bw * bh)) < 1, true)
|
|
end
|
|
|
|
do
|
|
local clips, scenes = 0, 0
|
|
local realClip = Chrome.withClip
|
|
Chrome.withClip = function(fn) clips = clips + 1; fn() end
|
|
local screen = {
|
|
wideLayout = function() return false end,
|
|
drawScene = function() scenes = scenes + 1 end,
|
|
}
|
|
BattleState.draw(screen)
|
|
check("OG still draws through Chrome.withClip", clips, 1)
|
|
check("and reaches the scene", scenes, 1)
|
|
screen.wideLayout = function() return true end
|
|
BattleState.draw(screen)
|
|
check("WIDE draws nothing a second time", clips, 1)
|
|
check("and never re-enters the scene", scenes, 1)
|
|
Chrome.withClip = realClip
|
|
end
|
|
|
|
do
|
|
local drawn = {}
|
|
local screen = {
|
|
wideLayout = function() return false end,
|
|
drawScene = function() drawn[#drawn + 1] = "scene" end,
|
|
battlePanelScale = function() return 2 end,
|
|
}
|
|
local realPanel = Chrome.withPanel
|
|
Chrome.withPanel = function(_, _, _, _, _, fn) drawn[#drawn + 1] = "panel"
|
|
fn() end
|
|
local realWide = WideBattle.draw
|
|
WideBattle.draw = function() drawn[#drawn + 1] = "wide" end
|
|
BattleState.drawWidescreen(screen, 1280, 840)
|
|
check("OG goes through Chrome.withPanel", table.concat(drawn, ","),
|
|
"panel,scene")
|
|
screen.wideLayout = function() return true end
|
|
BattleState.drawWidescreen(screen, 1280, 840)
|
|
check("WIDE hands the frame to WideBattle", table.concat(drawn, ","),
|
|
"panel,scene,wide")
|
|
Chrome.withPanel, WideBattle.draw = realPanel, realWide
|
|
end
|
|
|
|
do
|
|
local order = {}
|
|
local screen = {
|
|
drawEnemyHud = function() order[#order + 1] = "enemy" end,
|
|
drawPics = function() order[#order + 1] = "pics" end,
|
|
drawPlayerHud = function() order[#order + 1] = "player" end,
|
|
}
|
|
BattleState.drawHud(screen)
|
|
check("drawHud draws enemy HUD, pics, player HUD in that order",
|
|
table.concat(order, ","), "enemy,pics,player")
|
|
end
|
|
|
|
check("drawBottom takes the extra tiles as an argument",
|
|
type(BattleState.drawBottom), "function")
|
|
check("and WIDE hands it the 18 the wide surface adds",
|
|
WideBattle.EXTRA_TILES, 38 - Chrome.SCREEN_W)
|
|
|
|
|
|
check("the default HUD is the cart's placement",
|
|
Save.DEFAULT_OPTIONS.battleHud, "standard")
|
|
check("and a fresh options table carries it",
|
|
Save.defaultOptions().battleHud, "standard")
|
|
|
|
local hudIndex, hudRow = rowNamed("BATTLE HUD")
|
|
check("OPTION has a BATTLE HUD row", hudRow ~= nil, true)
|
|
if hudRow then
|
|
check("it edits battleHud", hudRow.key, "battleHud")
|
|
check("it is a port row", hudRow.port, true)
|
|
check("it follows BATTLE LAYOUT", hudIndex - 1, layoutIndex)
|
|
check("OG reads STANDARD even with extended stored",
|
|
hudRow.text({ battleLayout = "og", battleHud = "extended" }), "STANDARD")
|
|
check("WIDE + extended reads EXTENDED",
|
|
hudRow.text({ battleLayout = "wide", battleHud = "extended" }), "EXTENDED")
|
|
check("WIDE + standard reads STANDARD",
|
|
hudRow.text({ battleLayout = "wide", battleHud = "standard" }), "STANDARD")
|
|
end
|
|
|
|
local builtHud, viewedHud
|
|
for _, row in ipairs(menu.rows) do
|
|
if row.key == "battleHud" then builtHud = row end
|
|
end
|
|
for _, row in ipairs(menu.view or {}) do
|
|
if row.id == "battleHud" then viewedHud = row end
|
|
end
|
|
check("buildRows keeps BATTLE HUD", builtHud ~= nil, true)
|
|
check("and files it under the BATTLE OPTIONS group", viewedHud, nil)
|
|
if builtHud and builtLayout then
|
|
menu.options.battleLayout = "og"
|
|
menu.options.battleHud = "extended"
|
|
menu:cycle(builtHud, 1)
|
|
check("under OG the row resets to standard", menu.options.battleHud,
|
|
"standard")
|
|
menu:cycle(builtHud, 1)
|
|
check("and does not toggle", menu.options.battleHud, "standard")
|
|
menu.options.battleLayout = "wide"
|
|
menu:cycle(builtHud, 1)
|
|
check("under WIDE it toggles to extended", menu.options.battleHud,
|
|
"extended")
|
|
menu:cycle(builtHud, -1)
|
|
check("and back", menu.options.battleHud, "standard")
|
|
menu:cycle(builtHud, 1)
|
|
menu:cycle(builtLayout, 1)
|
|
check("stepping LAYOUT to og", menu.options.battleLayout, "og")
|
|
check("resets the HUD", menu.options.battleHud, "standard")
|
|
menu:cycle(builtLayout, 1)
|
|
check("LAYOUT still walks back to wide", menu.options.battleLayout, "wide")
|
|
check("without touching the HUD", menu.options.battleHud, "standard")
|
|
menu.options.battleLayout = "og"
|
|
end
|
|
|
|
local gearHud, gearBg
|
|
for _, section in ipairs(model.sections) do
|
|
for _, row in ipairs(section.rows) do
|
|
if row.label == "BATTLE HUD" then gearHud = row end
|
|
if row.label == "BATTLE BG" then gearBg = row end
|
|
end
|
|
end
|
|
check("the gold gear offers BATTLE HUD", gearHud ~= nil, true)
|
|
if gearHud and gearLayout and gearBg then
|
|
local gold = model.opts.gold
|
|
gold.battleLayout, gold.battleHud = "og", "extended"
|
|
check("the gear reads STANDARD under OG", gearHud.value(), "STANDARD")
|
|
check("stepping under OG changes nothing", gearHud.step(1), false)
|
|
check("but writes standard back", gold.battleHud, "standard")
|
|
gold.battleLayout = "wide"
|
|
gearHud.step(1)
|
|
check("under WIDE it toggles", gold.battleHud, "extended")
|
|
check("and reads EXTENDED", gearHud.value(), "EXTENDED")
|
|
gold.battleFit, gold.battleBg = "fill", "black"
|
|
check("FILL + EXTENDED locks BATTLE BG", gearBg.value(), "AUTO (FILL HUD)")
|
|
check("and its step is refused", gearBg.step(1), false)
|
|
check("so the stored bg is untouched", gold.battleBg, "black")
|
|
gold.battleFit = "fixed"
|
|
check("FIXED unlocks it again", gearBg.value(), "BLACK")
|
|
gearLayout.step(1)
|
|
check("stepping LAYOUT to og", gold.battleLayout, "og")
|
|
check("resets the HUD in the gear too", gold.battleHud, "standard")
|
|
gold.battleBg = "white"
|
|
end
|
|
|
|
local extendedHUD = BattleState.extendedHUD
|
|
check("the Gold battle screen answers extendedHUD", type(extendedHUD),
|
|
"function")
|
|
if type(extendedHUD) == "function" then
|
|
local function screen(layout, hud, fit, bg)
|
|
return { game = { options = { battleLayout = layout, battleHud = hud,
|
|
battleFit = fit, battleBg = bg } } }
|
|
end
|
|
check("OG never extends", extendedHUD(screen("og", "extended", "fixed",
|
|
"white")), false)
|
|
check("nor does WIDE + standard", extendedHUD(screen("wide", "standard",
|
|
"fixed", "white")), false)
|
|
check("FIXED + WHITE extends", extendedHUD(screen("wide", "extended",
|
|
"fixed", "white")), true)
|
|
check("FIXED + BLACK extends", extendedHUD(screen("wide", "extended",
|
|
"fixed", "black")), true)
|
|
check("FIXED + WORLD extends", extendedHUD(screen("wide", "extended",
|
|
"fixed", "world")), true)
|
|
check("FILL + WHITE extends", extendedHUD(screen("wide", "extended",
|
|
"fill", "white")), true)
|
|
check("FILL + BLACK extends, because the bg is forced white",
|
|
extendedHUD(screen("wide", "extended", "fill", "black")), true)
|
|
check("and bgMode reads white there",
|
|
BattleState.bgMode(screen("wide", "extended", "fill", "black")), "white")
|
|
check("FILL + standard keeps its black",
|
|
BattleState.bgMode(screen("wide", "standard", "fill", "black")), "black")
|
|
check("OG + FILL + extended keeps its black too",
|
|
BattleState.bgMode(screen("og", "extended", "fill", "black")), "black")
|
|
check("a screen with no game does not extend", extendedHUD({}), false)
|
|
local worldScreen = screen("wide", "extended", "fixed", "world")
|
|
local blackScreen = screen("wide", "extended", "fixed", "black")
|
|
check("extendedWorldHUD on FIXED + WORLD",
|
|
BattleState.extendedWorldHUD(worldScreen), true)
|
|
check("not extendedBlackHUD",
|
|
BattleState.extendedBlackHUD(worldScreen), false)
|
|
check("extendedBlackHUD on FIXED + BLACK",
|
|
BattleState.extendedBlackHUD(blackScreen), true)
|
|
check("not extendedWorldHUD",
|
|
BattleState.extendedWorldHUD(blackScreen), false)
|
|
check("FILL + WORLD is neither, it is white",
|
|
BattleState.extendedWorldHUD(screen("wide", "extended", "fill", "world")),
|
|
false)
|
|
end
|
|
|
|
for _, name in ipairs({ "extendedHUD", "extendedWorldHUD",
|
|
"extendedBlackHUD" }) do
|
|
check("mods are told " .. name .. " is backed",
|
|
Gen2Compat.memberStatus("src.battle.BattleState", name), "backed")
|
|
end
|
|
|
|
for _, size in ipairs({ { 1280, 840 }, { 1920, 1080 } }) do
|
|
local w, h = size[1], size[2]
|
|
local fit = Chrome.fitScaleFor(w, h, 38, 18)
|
|
local ox = Chrome.fitOriginFor(w, h, fit, 38, 18)
|
|
local pw = 304 * fit
|
|
local function extendedOwner(mode)
|
|
return {
|
|
bgMode = function() return mode end,
|
|
BG_WORLD_DIM = BattleState.BG_WORLD_DIM,
|
|
panelSize = function() return 304, 144 end,
|
|
battlePanelScale = function() return fit end,
|
|
extendedHUD = function() return true end,
|
|
}
|
|
end
|
|
local rects = paint({ extendedOwner("black") }, w, h)
|
|
local label = ("EXTENDED + BLACK %dx%d"):format(w, h)
|
|
check(label .. ": two side bars only", #rects, 2)
|
|
local covered, overlap, offColour = 0, 0, 0
|
|
for _, r in ipairs(rects) do
|
|
covered = covered + r.w * r.h
|
|
if r.pen[1] ~= 0 or r.pen[2] ~= 0 or r.pen[3] ~= 0 or r.pen[4] ~= 1 then
|
|
offColour = offColour + 1
|
|
end
|
|
local ix = math.max(0, math.min(r.x + r.w, ox + pw) - math.max(r.x, ox))
|
|
overlap = overlap + ix * math.max(0, math.min(r.y + r.h, h) - r.y)
|
|
check(label .. ": the bar runs the full height", r.h, h)
|
|
end
|
|
check(label .. ": no bar touches the paper column", overlap, 0)
|
|
check(label .. ": the sides are covered", covered, w * h - pw * h)
|
|
check(label .. ": every bar is opaque black", offColour, 0)
|
|
check(("EXTENDED + WORLD %dx%d paints nothing"):format(w, h),
|
|
#paint({ extendedOwner("world") }, w, h), 0)
|
|
wideSafe({ extendedOwner("black"), plainMenu }, w, h, fit,
|
|
("EXTENDED + BLACK under a plain menu %dx%d"):format(w, h))
|
|
wideSafe({ extendedOwner("world"), plainMenu }, w, h, fit,
|
|
("EXTENDED + WORLD under a plain menu %dx%d"):format(w, h))
|
|
end
|
|
|
|
do
|
|
local w, h = 1280, 840
|
|
local scale = Chrome.fitScaleFor(w, h, 38, 18)
|
|
local ox, oy = Chrome.fitOriginFor(w, h, scale, 38, 18)
|
|
local _, py, _, ph = Chrome.playfieldRect(w, h)
|
|
local tx, ty, ts = 0, 0, 1
|
|
local saved = {}
|
|
local realPush, realPop = G.push, G.pop
|
|
local realTranslate, realScale = G.translate, G.scale
|
|
G.push = function() saved[#saved + 1] = { tx, ty, ts } end
|
|
G.pop = function()
|
|
local s = table.remove(saved)
|
|
tx, ty, ts = s[1], s[2], s[3]
|
|
end
|
|
G.translate = function(dx, dy) tx, ty = tx + dx * ts, ty + dy * ts end
|
|
G.scale = function(s) ts = ts * s end
|
|
local realClip, realLetterbox, realFill = Chrome.clipTo, Chrome.letterbox,
|
|
Chrome.paletteFill
|
|
local columns = {}
|
|
Chrome.clipTo = function() end
|
|
Chrome.letterbox = function() end
|
|
Chrome.paletteFill = function(x, y, fw, fh)
|
|
if ts == 1 then columns[#columns + 1] = { x, y, fw, fh } end
|
|
end
|
|
|
|
local function fakeBattle(opts, onTop)
|
|
local placed = {}
|
|
local b = { game = { options = opts }, phase = "menu",
|
|
statsBoxMon = { level = 5 } }
|
|
b.game.stack = { top = function() return onTop and b or {} end }
|
|
b.battlePanelScale = function() return scale end
|
|
b.hasBattleSides = function() return true end
|
|
b.drawSceneBody = function() end
|
|
b.drawPics = function() end
|
|
b.drawScene = function(_, fn) fn() end
|
|
b.drawEnemyHud = function() placed.enemy = { tx, ty, ts } end
|
|
b.drawPlayerHud = function() placed.player = { tx, ty, ts } end
|
|
b.drawBottom = function(_, extra, noStats)
|
|
placed.bottom = { tx, ty, ts, extra, noStats }
|
|
end
|
|
b.extendedHUD = BattleState.extendedHUD
|
|
b.bgMode = BattleState.bgMode
|
|
return b, placed
|
|
end
|
|
|
|
local standard, placedStd = fakeBattle({ battleLayout = "wide",
|
|
battleHud = "standard", battleFit = "fixed", battleBg = "white" }, true)
|
|
WideBattle.draw(standard, w, h)
|
|
check("standard WIDE puts the enemy HUD on the surface origin",
|
|
placedStd.enemy and placedStd.enemy[1] .. "," .. placedStd.enemy[2],
|
|
ox .. "," .. oy)
|
|
check("and the player HUD 18 tiles in on the same row",
|
|
placedStd.player and placedStd.player[1] .. "," .. placedStd.player[2],
|
|
(ox + 144 * scale) .. "," .. oy)
|
|
check("and the bottom on the surface origin",
|
|
placedStd.bottom and placedStd.bottom[1] .. "," .. placedStd.bottom[2],
|
|
ox .. "," .. oy)
|
|
check("with its 18 extra tiles", placedStd.bottom and placedStd.bottom[4], 18)
|
|
check("and nothing more", placedStd.bottom and placedStd.bottom[5], nil)
|
|
check("standard WIDE paints no paper column", #columns, 0)
|
|
|
|
local extended, placedExt = fakeBattle({ battleLayout = "wide",
|
|
battleHud = "extended", battleFit = "fixed", battleBg = "black" }, true)
|
|
WideBattle.draw(extended, w, h)
|
|
local topY = oy + math.ceil((py - oy) / scale) * scale
|
|
local bottomY = oy + math.floor((py + ph - 144 * scale - oy) / scale) * scale
|
|
check("EXTENDED docks the enemy HUD at the playfield top",
|
|
placedExt.enemy and placedExt.enemy[1] .. "," .. placedExt.enemy[2],
|
|
ox .. "," .. topY)
|
|
check("which is the top edge itself at 1280x840", topY, py)
|
|
check("the bottom windows sit flush on the playfield bottom",
|
|
placedExt.bottom and placedExt.bottom[1] .. "," .. placedExt.bottom[2],
|
|
ox .. "," .. bottomY)
|
|
check("which is 144 scaled rows up from it", bottomY + 144 * scale, py + ph)
|
|
check("the player HUD rides with the bottom group",
|
|
placedExt.player and placedExt.player[1] .. "," .. placedExt.player[2],
|
|
(ox + 144 * scale) .. "," .. bottomY)
|
|
check("drawBottom keeps its one argument",
|
|
placedExt.bottom and placedExt.bottom[5], nil)
|
|
check("and the same scale", placedExt.enemy and placedExt.enemy[3], scale)
|
|
|
|
-- engine/battle/core.asm:7060
|
|
local levelUp, placedLevel = fakeBattle({ battleLayout = "wide",
|
|
battleHud = "extended", battleFit = "fixed", battleBg = "white" }, true)
|
|
levelUp.phase = "stats-box"
|
|
WideBattle.draw(levelUp, w, h)
|
|
check("the level-up stats box brings the foe HUD back to the surface",
|
|
placedLevel.enemy and placedLevel.enemy[1] .. "," .. placedLevel.enemy[2],
|
|
ox .. "," .. oy)
|
|
check("and the player HUD under the box, where the cart draws it",
|
|
placedLevel.player and placedLevel.player[1] .. ","
|
|
.. placedLevel.player[2], (ox + 144 * scale) .. "," .. oy)
|
|
check("bottom too", placedLevel.bottom and placedLevel.bottom[2], oy)
|
|
check("and no paper column while it is up", #columns, 1)
|
|
check("EXTENDED paints one paper column", #columns, 1)
|
|
check("the composition's width", columns[1] and columns[1][3], 304 * scale)
|
|
check("through the whole playfield", columns[1] and columns[1][4], ph)
|
|
check("from the letterbox x", columns[1] and columns[1][1], ox)
|
|
|
|
local covered, placedCov = fakeBattle({ battleLayout = "wide",
|
|
battleHud = "extended", battleFit = "fixed", battleBg = "white" }, false)
|
|
WideBattle.draw(covered, w, h)
|
|
check("a battle under a menu falls back to the surface",
|
|
placedCov.enemy and placedCov.enemy[1] .. "," .. placedCov.enemy[2],
|
|
ox .. "," .. oy)
|
|
check("bottom included",
|
|
placedCov.bottom and placedCov.bottom[2], oy)
|
|
check("and paints no column", #columns, 1)
|
|
|
|
G.push, G.pop, G.translate, G.scale = realPush, realPop, realTranslate,
|
|
realScale
|
|
Chrome.clipTo, Chrome.letterbox, Chrome.paletteFill = realClip,
|
|
realLetterbox, realFill
|
|
end
|
|
|
|
print(("gen2 battle options: %d checks, %d failures"):format(checks, failures))
|
|
if failures > 0 then
|
|
error(("%d assertion(s) failed"):format(failures), 0)
|
|
end
|