diff --git a/docs/mod-api-gen2-compat.md b/docs/mod-api-gen2-compat.md index fbb61d6e..754901a1 100644 --- a/docs/mod-api-gen2-compat.md +++ b/docs/mod-api-gen2-compat.md @@ -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 diff --git a/docs/modding.md b/docs/modding.md index e3a45fa2..3b799c00 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -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 diff --git a/src/ui/gen2/BattleState.lua b/src/ui/gen2/BattleState.lua index d3dfbd76..5c78e670 100644 --- a/src/ui/gen2/BattleState.lua +++ b/src/ui/gen2/BattleState.lua @@ -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 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. diff --git a/tests/engine/gate_gen2_mod_api.lua b/tests/engine/gate_gen2_mod_api.lua index 0cbcf0d7..cc5c2ca4 100644 --- a/tests/engine/gate_gen2_mod_api.lua +++ b/tests/engine/gate_gen2_mod_api.lua @@ -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 diff --git a/tests/mod_qol_hooks_tests.lua b/tests/mod_qol_hooks_tests.lua index d2d8efb0..d4b82643 100644 --- a/tests/mod_qol_hooks_tests.lua +++ b/tests/mod_qol_hooks_tests.lua @@ -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)