Merge remote-tracking branch 'origin/dev' into fix1038

# Conflicts:
#	docs/modding.md
This commit is contained in:
bryanthaboi
2026-08-10 14:14:39 -04:00
6 changed files with 70 additions and 5 deletions
+7
View File
@@ -343,6 +343,13 @@ lists fall back to their title; PC lists use stable, localization-independent
identifiers: `pc_box_withdraw`, `pc_box_deposit`, `pc_box_release`, identifiers: `pc_box_withdraw`, `pc_box_deposit`, `pc_box_release`,
`pc_box_change`, `pc_item_withdraw`, `pc_item_deposit`, and `pc_item_toss`. `pc_box_change`, `pc_item_withdraw`, `pc_item_deposit`, and `pc_item_toss`.
`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.
Pushed text boxes also pass through `battle.bottom_ui_visible`; a wrapper that
only owns battle presentation should return `false` only for its active battle
or text-box state.
`core.logic_speed` receives `(next, game)` once per `Game:logicSpeed()` call `core.logic_speed` receives `(next, game)` once per `Game:logicSpeed()` call
(once per frame). Vanilla behavior resolves the per-category GAME SPEED (once per frame). Vanilla behavior resolves the per-category GAME SPEED
option (`GameSpeed.CATEGORIES`: overworld/battle/menu) for whichever option (`GameSpeed.CATEGORIES`: overworld/battle/menu) for whichever
+18 -3
View File
@@ -133,6 +133,18 @@ function BattleState:sgbPalettes()
return nil return nil
end 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
local Rulesets = { local Rulesets = {
gen1_faithful = require("src.battle.rulesets.gen1_faithful"), gen1_faithful = require("src.battle.rulesets.gen1_faithful"),
modern_clean = require("src.battle.rulesets.modern_clean"), modern_clean = require("src.battle.rulesets.modern_clean"),
@@ -5358,7 +5370,7 @@ function BattleState:drawPicsLayer(slide, sx, sy, onlySide, skipMenuClip)
-- .mimicmenu) wipes rows 7+. The port draws pics above the menu -- .mimicmenu) wipes rows 7+. The port draws pics above the menu
-- layer in the colorized pipeline, so clip them to the visible rows. -- layer in the colorized pipeline, so clip them to the visible rows.
local g = love.graphics local g = love.graphics
local clipY = not skipMenuClip local clipY = not skipMenuClip and self:bottomUIVisible()
and (self.phase == "mimicSelect" and 56 and (self.phase == "mimicSelect" and 56
or self.phase == "moveSelect" and 64) or self.phase == "moveSelect" and 64)
or nil or nil
@@ -5470,6 +5482,7 @@ function BattleState:drawHUDs(slide)
-- per-pixel tint (grayFill) -- otherwise GREENBAR's red-channel-0 fill -- per-pixel tint (grayFill) -- otherwise GREENBAR's red-channel-0 fill
-- double-applies and the zone shade shader maps the whole bar to black (#229). -- double-applies and the zone shade shader maps the whole bar to black (#229).
local grayFill = self:colorMode() local grayFill = self:colorMode()
local showStatus = self:statusHUDVisible()
local barData = self.data local barData = self.data
local fx = self.fx local fx = self.fx
local hudShake = (fx and fx.hudShakeX) or 0 local hudShake = (fx and fx.hudShakeX) or 0
@@ -5479,7 +5492,8 @@ function BattleState:drawHUDs(slide)
-- DrawEnemyHUDAndHPBar is called from _InitBattleCommon (core.asm:6763) -- DrawEnemyHUDAndHPBar is called from _InitBattleCommon (core.asm:6763)
-- AFTER PrintBeginningBattleText returns, so "Wild X appeared!" shows the -- AFTER PrintBeginningBattleText returns, so "Wild X appeared!" shows the
-- player's ball row with no enemy HUD beside it (#317) -- player's ball row with no enemy HUD beside it (#317)
if self.enemy and not self.showEnemyTrainer and not self.enemySendingOut if showStatus and self.enemy and not self.showEnemyTrainer
and not self.enemySendingOut
and not self:growInScale(self.enemy) and slide == 0 and not self:growInScale(self.enemy) and slide == 0
and not self.introBalls and not self.enemy.fainted then and not self.introBalls and not self.enemy.fainted then
-- enemy HUD (DrawEnemyHUDAndHPBar): name row 0, <LV>+level (4,1), -- enemy HUD (DrawEnemyHUDAndHPBar): name row 0, <LV>+level (4,1),
@@ -5566,7 +5580,7 @@ function BattleState:drawHUDs(slide)
self:drawBallRow(self.playerParty or self.game.save.party, 88, 80, 8) self:drawBallRow(self.playerParty or self.game.save.party, 88, 80, 8)
end end
local hidePlayer = self.safari or self.demo local hidePlayer = self.safari or self.demo
if self.player and not hidePlayer and not self.showPlayerBack if showStatus and self.player and not hidePlayer and not self.showPlayerBack
and slide == 0 then and slide == 0 then
-- player HUD (DrawPlayerHUDAndHPBar): name (10,7), <LV>+level -- player HUD (DrawPlayerHUDAndHPBar): name (10,7), <LV>+level
-- (14,8), HP bar (10,9), HP numbers row 10, underline row 11 with -- (14,8), HP bar (10,9), HP numbers row 10, underline row 11 with
@@ -5591,6 +5605,7 @@ function BattleState:drawHUDs(slide)
end end
function BattleState:drawTextArea() function BattleState:drawTextArea()
if not self:bottomUIVisible() then return end
Font.drawBox(0, 12, 20, 6) Font.drawBox(0, 12, 20, 6)
love.graphics.setColor(0, 0, 0, 1) love.graphics.setColor(0, 0, 0, 1)
if self.phase == "messages" if self.phase == "messages"
+4 -2
View File
@@ -128,7 +128,8 @@ local function drawIntroBalls(battle)
end end
local function drawHUDs(battle, slide) local function drawHUDs(battle, slide)
if battle.enemy and not battle.showEnemyTrainer local showStatus = battle:statusHUDVisible()
if showStatus and battle.enemy and not battle.showEnemyTrainer
and not battle.enemySendingOut and not battle:growInScale(battle.enemy) and not battle.enemySendingOut and not battle:growInScale(battle.enemy)
and slide == 0 and not battle.introBalls and not battle.enemy.fainted then and slide == 0 and not battle.introBalls and not battle.enemy.fainted then
drawStatusPanel(battle, battle.enemy, 0, 0, false) drawStatusPanel(battle, battle.enemy, 0, 0, false)
@@ -139,7 +140,7 @@ local function drawHUDs(battle, slide)
-- item, not a HUD element (DisplayBattleMenu prints wNumSafariBalls inside -- item, not a HUD element (DisplayBattleMenu prints wNumSafariBalls inside
-- the battle menu box, engine/battle/core.asm:2074-2079), so it rides in -- the battle menu box, engine/battle/core.asm:2074-2079), so it rides in
-- drawCommandMenu below like the classic layout's (#540). -- drawCommandMenu below like the classic layout's (#540).
if not battle.safari and battle.player and not battle.demo if showStatus and not battle.safari and battle.player and not battle.demo
and not battle.showPlayerBack and slide == 0 then and not battle.showPlayerBack and slide == 0 then
drawStatusPanel(battle, battle.player, 184, 56, true) drawStatusPanel(battle, battle.player, 184, 56, true)
end end
@@ -255,6 +256,7 @@ local function drawMoveMenu(battle)
end end
local function drawTextArea(battle) local function drawTextArea(battle)
if not battle:bottomUIVisible() then return end
if battle.phase == "messages" and (battle.current or battle.animPlaying) then if battle.phase == "messages" and (battle.current or battle.animPlaying) then
drawMessageBox(battle) drawMessageBox(battle)
elseif battle.phase == "menu" then elseif battle.phase == "menu" then
+7
View File
@@ -7,11 +7,13 @@
-- the text is exhausted and A is pressed, then calls onDone. -- the text is exhausted and A is pressed, then calls onDone.
local Font = require("src.render.Font") local Font = require("src.render.Font")
local Runtime = require("src.mods.Runtime")
local Theme = require("src.ui.Theme") local Theme = require("src.ui.Theme")
local Timing = require("src.core.Timing") local Timing = require("src.core.Timing")
local TextBox = {} local TextBox = {}
TextBox.__index = TextBox TextBox.__index = TextBox
TextBox.isTextBox = true
-- theme-free fallbacks; geometry resolves against Theme.textBox at -- theme-free fallbacks; geometry resolves against Theme.textBox at
-- construction time, so an unthemed boot stays byte-identical -- construction time, so an unthemed boot stays byte-identical
@@ -344,6 +346,11 @@ function TextBox:update(dt)
end end
function TextBox:draw() function TextBox:draw()
if Runtime.wantsHook("battle.bottom_ui_visible")
and Runtime.call("battle.bottom_ui_visible", function() return true end,
self) == false then
return
end
-- The dialogue box belongs against the bottom of the screen, not floating -- The dialogue box belongs against the bottom of the screen, not floating
-- in the middle of a zoomed-out letterbox. Declared per frame; the -- in the middle of a zoomed-out letterbox. Declared per frame; the
-- renderer blits this region to the screen edge and the rest of the UI -- renderer blits this region to the screen edge and the rest of the UI
@@ -74,6 +74,9 @@ local function battleWith(fx, sprites)
growInScale = function() return nil end, growInScale = function() return nil end,
drawBallRow = function() end, drawBallRow = function() end,
statusLabel = function() return "" end, statusLabel = function() return "" end,
statusHUDVisible = function() return true end,
bottomUIVisible = function() return true end,
caughtMarkerVisible = function() return false end,
} }
end end
+31
View File
@@ -11,6 +11,7 @@ local Stats = require("src.pokemon.Stats")
local Zoom = require("src.render.Zoom") local Zoom = require("src.render.Zoom")
local ListMenu = require("src.ui.ListMenu") local ListMenu = require("src.ui.ListMenu")
local NamingScreen = require("src.ui.NamingScreen") local NamingScreen = require("src.ui.NamingScreen")
local TextBox = require("src.render.TextBox")
local Player = require("src.world.Player") local Player = require("src.world.Player")
local Music = require("src.core.Music") local Music = require("src.core.Music")
@@ -161,6 +162,36 @@ do
unsub() unsub()
end end
-- ------- battle UI visibility (companion / alternate renderers)
do
local BattleState = require("src.battle.BattleState")
check(BattleState.bottomUIVisible({ phase = "menu" }),
"battle bottom UI is visible without a mod")
local seen
local unsub = wrap("battle.bottom_ui_visible", function(_, state)
seen = state
return false
end)
check(not BattleState.bottomUIVisible({ phase = "messages" }),
"a mod can hide the battle text and menu layer")
local text = setmetatable({}, TextBox)
text:draw()
check(seen == text, "pushed text boxes use the same visibility hook")
unsub()
check(BattleState.bottomUIVisible({ phase = "moveSelect" }),
"battle bottom UI returns when the hook is removed")
check(BattleState.statusHUDVisible({}),
"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")
unsub()
check(BattleState.statusHUDVisible({}),
"battle status HUD returns when the hook is removed")
end
-- ------- music.volume (distance / indoor muffling) -- ------- music.volume (distance / indoor muffling)
do do