feat(gen2): share battle UI visibility hooks

This commit is contained in:
AverageConsumer
2026-08-13 22:41:26 +02:00
parent 26e9e1d597
commit 7b8cb127a8
5 changed files with 57 additions and 4 deletions
+3 -2
View File
@@ -533,8 +533,9 @@ gains a field instead of the name gaining a prefix.
`pokemon.level_up`, `pokemon.move_learned`; hooks `battle.damage`,
`battle.crit`, `battle.accuracy`, `battle.turn_order`,
`battle.enemy_action`, `battle.run`, `battle.exp_award`, `exp.gain`,
`catch.rate`, `trainer.party`, `battle.overlay`, `battle.low_health_alarm`
and `battle.catch_exp`. One payload difference: Gen 1's vanilla
`catch.rate`, `trainer.party`, `battle.overlay`, `battle.low_health_alarm`,
`battle.catch_exp`, `battle.bottom_ui_visible` and
`battle.status_hud_visible`. One payload difference: Gen 1's vanilla
`battle.low_health_alarm` link reads `ctx.battle.data`, and Gold's battle
screen has no `.data` field, so the Gen 2 site **adds** `ctx.data` beside the
Gen 1 keys. A mod that calls `nextFn` is unaffected; one that reaches through
+1
View File
@@ -455,6 +455,7 @@ identifiers: `pc_box_withdraw`, `pc_box_deposit`, `pc_box_release`,
`battle.bottom_ui_visible` and `battle.status_hud_visible` independently
control the battle text/menu layer and the HP/status panels. Both receive
`(next, state)` and default to `true`, so vanilla rendering is unchanged.
Both hooks apply to Gen 1 and Gen 2 battles.
Text boxes and YES/NO prompts pushed above a battle inherit a `false` result
for that battle, so hiding the bottom layer cannot leave their white backing
behind under another overlay. Text boxes also pass through the hook as their
+21 -2
View File
@@ -173,6 +173,18 @@ end
function BattleState:wantsFillScale() return true end
function BattleState:drawsWidescreen() return true end
function BattleState:bottomUIVisible()
if not Runtime.wantsHook("battle.bottom_ui_visible") then return true end
return Runtime.call("battle.bottom_ui_visible", function() return true end,
self) ~= false
end
function BattleState:statusHUDVisible()
if not Runtime.wantsHook("battle.status_hud_visible") then return true end
return Runtime.call("battle.status_hud_visible", function() return true end,
self) ~= false
end
-- opts: battle (a Battle), onDone(outcome), save
function BattleState.new(game, opts)
opts = opts or {}
@@ -3079,6 +3091,7 @@ end
function BattleState:drawHud()
local wasBattle = Font.useBattleExtra(true)
local enemy, player = self:activeMon("enemy"), self:activeMon("player")
local showStatus = self:statusHUDVisible()
-- Enemy HUD (DrawEnemyHUD clears (1,0) 4 rows x 11 cols):
-- name at (1,0); PrintLevel at (6,1) with the gender symbol at (9,1);
@@ -3087,7 +3100,7 @@ function BattleState:drawHud()
-- runs, so a shake or a slide does not drag the HP bar with it.
-- And nothing at all before UpdateEnemyHUD has ever run: the intro bands
-- slide in over a blanked tilemap (core.asm:8554/8564).
if self.showEnemyHud and not self:hudCleared("enemy") then
if showStatus and self.showEnemyHud and not self:hudCleared("enemy") then
Chrome.print(self:name(enemy), 1, 0)
-- PrintLevel writes <LV> at the coordinate it is given and then LEFT-aligns
-- the digits after it, so the glyph is pinned to column 6 whether the level
@@ -3127,7 +3140,8 @@ function BattleState:drawHud()
-- BattleMenu's own tutorial arm skips UpdateBattleHuds as well. The DUDE's
-- half of the screen is his back-pic and nothing more.
-- Nor before SendOutPlayerMon's own UpdatePlayerHUD (core.asm:3838).
if not player or not self.showPlayerHud or self:hudCleared("player") then
if not showStatus or not player or not self.showPlayerHud
or self:hudCleared("player") then
Font.useBattleExtra(wasBattle)
return
end
@@ -3226,6 +3240,11 @@ function BattleState:drawPanel()
end
self:drawHud()
if not self:bottomUIVisible() then
love.graphics.setColor(1, 1, 1, 1)
return
end
-- Message box across the bottom, with the menu window over its right half --
-- the cart draws the prompt into the full-width box and then opens the menu
-- on top, so the tail of a long name is simply covered.
+1
View File
@@ -420,6 +420,7 @@ local GEN2_HOOKS = {
-- that a Gen 1 mod reaching through ctx.battle.data instead of calling
-- nextFn gets nil there).
"battle.catch_exp", "battle.low_health_alarm", "battle.overlay",
"battle.bottom_ui_visible", "battle.status_hud_visible",
-- One pic path resolver for both games: the Gen 1 site is the SHARED
-- src/pokemon/Sprites.lua and Gold's own battle screen calls the same hook
-- with the Gen 1 ctx keys plus `letter` and `shiny`, which Red has no
+31
View File
@@ -168,8 +168,11 @@ end
do
local BattleState = require("src.battle.BattleState")
local Gen2BattleState = require("src.ui.gen2.BattleState")
check(BattleState.bottomUIVisible({ phase = "menu" }),
"battle bottom UI is visible without a mod")
check(Gen2BattleState.bottomUIVisible({ phase = "menu" }),
"Gold battle bottom UI is visible without a mod")
local seen
local unsub = wrap("battle.bottom_ui_visible", function(_, state)
seen = state
@@ -177,6 +180,8 @@ do
end)
check(not BattleState.bottomUIVisible({ phase = "messages" }),
"a mod can hide the battle text and menu layer")
check(not Gen2BattleState.bottomUIVisible({ phase = "menu" }),
"the same hook hides Gold's battle text and menu layer")
local text = setmetatable({}, TextBox)
text:draw()
check(seen == text, "pushed text boxes use the same visibility hook")
@@ -200,15 +205,41 @@ do
check(BattleState.bottomUIVisible({ phase = "moveSelect" }),
"battle bottom UI returns when the hook is removed")
check(Gen2BattleState.bottomUIVisible({ phase = "moves" }),
"Gold battle bottom UI returns when the hook is removed")
local Chrome = require("src.ui.gen2.Chrome")
local clear, box, print = Chrome.clear, Chrome.box, Chrome.print
local boxes = 0
Chrome.clear = function() end
Chrome.box = function() boxes = boxes + 1 end
Chrome.print = function() end
local panel = setmetatable({
battle = { player = {}, enemy = {} }, phase = "resolving",
drawHud = function() end,
}, { __index = Gen2BattleState })
unsub = wrap("battle.bottom_ui_visible", function() return false end)
panel:drawPanel()
check(boxes == 0, "Gold skips its in-state battle box when a mod owns it")
unsub()
panel:drawPanel()
check(boxes == 1, "Gold draws its battle box again without the hook")
Chrome.clear, Chrome.box, Chrome.print = clear, box, print
check(BattleState.statusHUDVisible({}),
"battle status HUD is visible without a mod")
check(Gen2BattleState.statusHUDVisible({}),
"Gold battle status HUD is visible without a mod")
unsub = wrap("battle.status_hud_visible", function() return false end)
check(not BattleState.statusHUDVisible({}),
"a mod can hide the battle status HUD")
check(not Gen2BattleState.statusHUDVisible({}),
"the same hook hides Gold's battle status HUD")
unsub()
check(BattleState.statusHUDVisible({}),
"battle status HUD returns when the hook is removed")
check(Gen2BattleState.statusHUDVisible({}),
"Gold battle status HUD returns when the hook is removed")
end
-- ------- battle.caught_marker_visible (caught wild marker)