From e0e030003bec082f14c591e656e44ff10b555427 Mon Sep 17 00:00:00 2001 From: AverageConsumer <35539970+AverageConsumer@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:33:07 +0200 Subject: [PATCH] fix(gen2): honor mod move-grid navigation --- docs/mod-api-gen2-compat.md | 5 +++-- src/ui/gen2/BattleState.lua | 23 ++++++++++++++++++- tests/engine/gate_gen2_mod_api.lua | 1 + tests/mod_qol_hooks_tests.lua | 36 ++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/docs/mod-api-gen2-compat.md b/docs/mod-api-gen2-compat.md index e412b4d2..af5cf6c6 100644 --- a/docs/mod-api-gen2-compat.md +++ b/docs/mod-api-gen2-compat.md @@ -540,8 +540,9 @@ gains a field instead of the name gaining a prefix. `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`, - `battle.catch_exp`, `battle.bottom_ui_visible` and - `battle.status_hud_visible`. One payload difference: Gen 1's vanilla + `battle.catch_exp`, `battle.bottom_ui_visible`, + `battle.status_hud_visible` and `battle.move_grid_navigation`. 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/src/ui/gen2/BattleState.lua b/src/ui/gen2/BattleState.lua index cd1b7d15..5531edbb 100644 --- a/src/ui/gen2/BattleState.lua +++ b/src/ui/gen2/BattleState.lua @@ -44,6 +44,12 @@ local BattleState = {} BattleState.__index = BattleState BattleState.isOpaque = true +function BattleState:moveGridNavigation() + if not Runtime.wantsHook("battle.move_grid_navigation") then return false end + return Runtime.call("battle.move_grid_navigation", function() return false end, + self) == true +end + -- Armed while a battle line waits for PromptButton (home/text.asm). Any -- positive value means "hold until A/B"; the cart never times these out, so -- the victory jingle can keep looping through the post-win prompts. @@ -2043,7 +2049,22 @@ function BattleState:update(_dt) if self.phase == "moves" then local moves = self:playerMoves() - if input:wasPressed("up") then + local grid + if self:moveGridNavigation() then + local index, count = self.moveIndex, #moves + if input:wasPressed("left") or input:wasPressed("right") then + local other = math.floor((index - 1) / 2) * 2 + + (1 - (index - 1) % 2) + 1 + grid = other <= count and other or index + elseif input:wasPressed("up") or input:wasPressed("down") then + local other = (1 - math.floor((index - 1) / 2)) * 2 + + (index - 1) % 2 + 1 + grid = other <= count and other or index + end + end + if grid then + self.moveIndex = grid + elseif input:wasPressed("up") then self.moveIndex = self.moveIndex > 1 and self.moveIndex - 1 or #moves elseif input:wasPressed("down") then self.moveIndex = self.moveIndex < #moves and self.moveIndex + 1 or 1 diff --git a/tests/engine/gate_gen2_mod_api.lua b/tests/engine/gate_gen2_mod_api.lua index 68cff2e8..d8862deb 100644 --- a/tests/engine/gate_gen2_mod_api.lua +++ b/tests/engine/gate_gen2_mod_api.lua @@ -421,6 +421,7 @@ local GEN2_HOOKS = { -- nextFn gets nil there). "battle.catch_exp", "battle.low_health_alarm", "battle.overlay", "battle.bottom_ui_visible", "battle.status_hud_visible", + "battle.move_grid_navigation", -- 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 d4b82643..58f6454e 100644 --- a/tests/mod_qol_hooks_tests.lua +++ b/tests/mod_qol_hooks_tests.lua @@ -266,13 +266,49 @@ end do local BattleState = require("src.battle.BattleState") + local Gen2BattleState = require("src.ui.gen2.BattleState") local battle = { wideLayout = function() return false end } check(not BattleState.moveGridNavigation(battle), "classic move navigation stays a list without a mod") + check(not Gen2BattleState.moveGridNavigation({}), + "Gold move navigation stays a list without a mod") local unsub = wrap("battle.move_grid_navigation", function() return true end) check(BattleState.moveGridNavigation(battle), "a mod can opt the classic move menu into grid navigation") + check(Gen2BattleState.moveGridNavigation({}), + "the same hook opts Gold's move menu into grid navigation") + + local pressed, moveCount = "right", 4 + local gold = setmetatable({ + phase = "moves", moveIndex = 1, + slideFrame = math.huge, + game = { input = { + wasPressed = function(_, key) return key == pressed end, + } }, + updateAlarm = function() end, + stepHpAnim = function() return false end, + playerMoves = function() + local moves = {} + for i = 1, moveCount do moves[i] = {} end + return moves + end, + }, { __index = Gen2BattleState }) + gold:update(0) + check(gold.moveIndex == 2, + "Gold grid navigation moves right across a companion move row") + pressed, gold.moveIndex = "down", 1 + gold:update(0) + check(gold.moveIndex == 3, + "Gold grid navigation moves down the companion move column") + pressed, moveCount, gold.moveIndex = "down", 3, 2 + gold:update(0) + check(gold.moveIndex == 2, + "Gold grid navigation does not select an empty fourth move slot") unsub() + pressed, gold.moveIndex = "right", 1 + gold:update(0) + check(gold.moveIndex == 1, + "removing the hook restores Gold's native vertical move list") battle.wideLayout = function() return true end check(BattleState.moveGridNavigation(battle), "the native wide move grid remains enabled without a mod")