mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 19:24:01 +02:00
187 lines
8.5 KiB
Lua
187 lines
8.5 KiB
Lua
-- BATTLE SIZE "fixed" + BATTLE BG "world": the battle draws as a fixed
|
|
-- letterbox over the map, stepped down with the survey zoom. The PKMN and
|
|
-- ITEM menus it opens have to stay that size, and its YES/NO prompt has to
|
|
-- stay inside it.
|
|
--
|
|
-- Both broke the same way -- by reading a fact about THIS FRAME instead of
|
|
-- about the battle:
|
|
--
|
|
-- * Renderer:uiScale follows the zoom only while a world is behind the UI,
|
|
-- gated on worldActive (beginWorldPass set it this frame). A "world"-bg
|
|
-- battle is non-opaque so the map keeps drawing under it -- but PartyMenu
|
|
-- and ListMenu ARE opaque, so pushing one makes StateStack:visibleBase
|
|
-- skip the map, the world pass never runs, and the menu loses the
|
|
-- step-down and blits a whole integer scale larger than the battle it
|
|
-- just covered. ("fill" hid this: it overrides the scale outright.)
|
|
--
|
|
-- * ChoiceBox bottom-anchored unconditionally, which docks it to the
|
|
-- WINDOW's bottom edge. That is right only when it is riding the
|
|
-- dialogue box below it, which is anchored there too. The battle draws
|
|
-- its own text inside the battle canvas, so the switch offer's YES/NO was
|
|
-- the only piece of that prompt flung to the window edge -- further off
|
|
-- the smaller the fixed battle is drawn.
|
|
-- luajit tests/engine/battle_fixed_menu_scale.lua
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.modkit")
|
|
local Renderer = require("src.render.Renderer")
|
|
local Zoom = require("src.render.Zoom")
|
|
local TextBox = require("src.render.TextBox")
|
|
local ChoiceBox = require("src.ui.ChoiceBox")
|
|
|
|
-- ------------------------------------------------- the UI scale holds its size
|
|
|
|
-- pin the window so the scales below are exact numbers rather than whatever
|
|
-- the runner happens to be sized at (this suite also runs under real LOVE)
|
|
local g = love.graphics
|
|
local realDims, realPixelDims = g.getDimensions, g.getPixelDimensions
|
|
g.getDimensions = function() return 640, 576 end
|
|
g.getPixelDimensions = function() return 640, 576 end
|
|
|
|
-- 640x576 fits the 160x144 classic surface at exactly 4x
|
|
T.eq(Renderer:fitScale(), 4, "the fixture window fits the classic surface at 4x")
|
|
|
|
local function uiScaleWith(offset, worldActive, hold)
|
|
local oldOffset, oldActive, oldHold =
|
|
Zoom.offset, Renderer.worldActive, Renderer.uiWorldHold
|
|
Zoom.offset, Renderer.worldActive, Renderer.uiWorldHold =
|
|
offset, worldActive, hold
|
|
local s = Renderer:uiScale()
|
|
Zoom.offset, Renderer.worldActive, Renderer.uiWorldHold =
|
|
oldOffset, oldActive, oldHold
|
|
return s
|
|
end
|
|
|
|
T.eq(uiScaleWith(0, true, false), 4, "unzoomed, the UI is the fit scale")
|
|
T.eq(uiScaleWith(-2, true, false), 2,
|
|
"zoomed out over a live world pass, the UI steps down with it")
|
|
T.eq(uiScaleWith(-2, false, false), 4,
|
|
"with no world behind it at all (title screen), the zoom is ignored")
|
|
|
|
-- the fix: the party menu / bag ended the world pass, but the battle under
|
|
-- them is still drawn over the map, so the surface must not grow
|
|
T.eq(uiScaleWith(-2, false, true), 2,
|
|
"an opaque menu over a world-bg battle keeps the battle's stepped-down scale")
|
|
T.eq(uiScaleWith(0, false, true), 4,
|
|
"and the hold changes nothing when the player never zoomed out")
|
|
|
|
g.getDimensions, g.getPixelDimensions = realDims, realPixelDims
|
|
|
|
-- the hold is the same whole-stack answer the dim already uses, so a menu
|
|
-- opened over the battle cannot drop it for a frame (Game:draw wires
|
|
-- uiWorldHold to worldBgBattleDim ~= nil; battle_fit_option covers the scan)
|
|
local Game = require("src.core.Game")
|
|
local BattleState = require("src.battle.BattleState")
|
|
-- isOpaque false is what "world" actually does to a live battle (BattleState
|
|
-- drops it per-instance at start, so the class default stays opaque for every
|
|
-- other battle) -- and it is the whole reason the map draws underneath
|
|
local worldBattle = setmetatable(
|
|
{ game = { save = { options = { battleBg = "world" } } }, isOpaque = false },
|
|
{ __index = BattleState })
|
|
T.check(Game.worldBgBattleDim({ states = { {}, worldBattle, {} } }) ~= nil,
|
|
"the stack scan the hold reads still finds the battle under an opaque menu")
|
|
|
|
-- --------------------------------------------- the backdrop holds under menus
|
|
|
|
-- The battle establishes the surround too, and the same opaque menu used to
|
|
-- take that over: it becomes visibleBase, the overworld stops drawing, and
|
|
-- the world the battle was composed over collapses to a flat black clear.
|
|
local overworld = { isOpaque = true }
|
|
local menu = { isOpaque = true } -- PartyMenu / ListMenu
|
|
local whiteBattle = setmetatable(
|
|
{ game = { save = { options = { battleBg = "white" } } } },
|
|
{ __index = BattleState })
|
|
local wideWhiteBattle = setmetatable(
|
|
{ game = { save = { options = {
|
|
battleBg = "white", battleLayout = "wide",
|
|
} } } },
|
|
{ __index = BattleState })
|
|
local function stack(...) return { states = { ... },
|
|
visibleBase = function(self)
|
|
for i = #self.states, 1, -1 do
|
|
if self.states[i].isOpaque then return i end
|
|
end
|
|
return 1
|
|
end } end
|
|
|
|
local s = stack(overworld, worldBattle, menu)
|
|
T.eq(s:visibleBase(), 3, "the menu is the topmost opaque state, as before")
|
|
T.eq(Game.drawBaseInStack(s, s:visibleBase()), 1,
|
|
"but the frame still starts at the overworld, so the map keeps drawing")
|
|
|
|
-- unchanged everywhere else
|
|
local s2 = stack(overworld, worldBattle)
|
|
T.eq(s2:visibleBase(), 1, "the battle alone already drew from the overworld")
|
|
T.eq(Game.drawBaseInStack(s2, s2:visibleBase()), 1, "and still does")
|
|
local s3 = stack(overworld, whiteBattle, menu)
|
|
T.eq(Game.drawBaseInStack(s3, s3:visibleBase()), 3,
|
|
"a classic white-bg battle has no presentation to hold, so nothing moves")
|
|
local s3wide = stack(overworld, wideWhiteBattle, menu)
|
|
T.eq(Game.drawBaseInStack(s3wide, s3wide:visibleBase()), 2,
|
|
"an opaque WIDE battle still draws beneath its classic menu")
|
|
local s4 = stack(overworld, menu)
|
|
T.eq(Game.drawBaseInStack(s4, s4:visibleBase()), 2,
|
|
"and a menu outside a battle is untouched")
|
|
T.eq(Game.drawBaseInStack(stack(overworld), 1), 1, "a lone base is safe")
|
|
T.eq(Game.drawBaseInStack(nil, 1), 1, "and so is no stack at all")
|
|
|
|
-- ------------------------------------------- the YES/NO stays with its screen
|
|
|
|
-- a bare choice box -- the battle's switch offer, a shop or PC confirm -- has
|
|
-- no anchored dialogue box under it to ride
|
|
T.eq(ChoiceBox.new({}, function() end).anchor, nil,
|
|
"a bare choice box does not anchor itself to the window edge")
|
|
T.eq(ChoiceBox.new({}, function() end, { defaultNo = true }).anchor, nil,
|
|
"and neither does one that only asked to start on NO")
|
|
|
|
-- ...but the one a dialogue box opens does, because that box is anchored too
|
|
local game = { save = { player = {} }, data = { text = {} } }
|
|
game.stack = {
|
|
states = {},
|
|
push = function(self, s) table.insert(self.states, s) end,
|
|
pop = function(self) return table.remove(self.states) end,
|
|
top = function(self) return self.states[#self.states] end,
|
|
}
|
|
game.input = {
|
|
wasPressed = function() return false end,
|
|
isDown = function() return false end,
|
|
}
|
|
|
|
local box = TextBox.new(game, "Shall we heal\nyour POKEMON?", nil,
|
|
{ choice = function() end })
|
|
game.stack:push(box)
|
|
for _ = 1, 600 do
|
|
if box.done then break end
|
|
box:update(1 / 60)
|
|
end
|
|
T.check(box.done, "the question finished typing")
|
|
box:update(1 / 60) -- the update after done is the one that pushes the choice
|
|
T.eq(#game.stack.states, 2, "the dialogue box opened its YES/NO")
|
|
T.eq(game.stack:top().anchor, "bottom",
|
|
"a choice box riding a dialogue box shares its bottom anchor")
|
|
|
|
-- ...and inside a battle even THAT one stays put, because the battle is the
|
|
-- screen: the caught-mon nickname prompt prints on the blanked battle field
|
|
-- (BattleState.blankForAskName), and docking it to the window edge drops it a
|
|
-- whole letterbox below what it is printed on.
|
|
T.eq(BattleState.holdsUIAnchors, true, "a battle composes its own screen")
|
|
T.eq(Game.uiAnchorsHeldInStack(stack(overworld, worldBattle)), true,
|
|
"so the anchors are held while it is up")
|
|
T.eq(Game.uiAnchorsHeldInStack(stack(overworld, worldBattle, {})), true,
|
|
"including for the text box and YES/NO it opens above itself")
|
|
T.eq(Game.uiAnchorsHeldInStack(stack(overworld)), false,
|
|
"the overworld's own dialogue box still docks to the screen edge")
|
|
T.eq(Game.uiAnchorsHeldInStack(nil), false, "and no stack is safe")
|
|
|
|
Renderer.uiAnchors = nil
|
|
Renderer.uiAnchorHold = true
|
|
Renderer:setUIAnchor(0, 96, 160, 48, "bottom")
|
|
T.eq(Renderer.uiAnchors, nil, "a held anchor never reaches the frame")
|
|
Renderer.uiAnchorHold = false
|
|
Renderer:setUIAnchor(0, 96, 160, 48, "bottom")
|
|
T.eq(#(Renderer.uiAnchors or {}), 1, "and an unheld one still does")
|
|
Renderer.uiAnchors = nil
|
|
|
|
T.finish("battle fixed menu scale")
|