From b0868e75718785147bdaf56bac1f565f53f75442 Mon Sep 17 00:00:00 2001 From: johnjohto Date: Mon, 27 Jul 2026 22:31:56 -0400 Subject: [PATCH] Run the field-move white blink under its text, not after (#320) The vanilla surf/strength flow closed the menu, showed the overworld, then fired a solid-white blink on the empty map, and set the surfing sprite while the player was still on land. pokered's GBPalWhiteOutWithDelay3 runs while the text is still up, so the blink reads as a text flash. The blink now sits under the textbox on the stack (only the top state updates, so it holds its frames until the text closes), and surfing applies only when the step onto the water happens. Same reorder for the party-menu STRENGTH texts. The parity surf tests now dismiss the got-on text before asserting the mount, matching the new order. --- src/ui/PartyMenu.lua | 8 +++++--- src/world/OverworldController.lua | 24 ++++++++++------------- tests/mod_ui_tests.lua | 24 +++++++++++++++++++++++ tests/mod_world_tests.lua | 32 +++++++++++++++++++++++++++++++ tests/parity_I_M.lua | 4 ++++ 5 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/ui/PartyMenu.lua b/src/ui/PartyMenu.lua index 9bc478fb..6ee4212e 100644 --- a/src/ui/PartyMenu.lua +++ b/src/ui/PartyMenu.lua @@ -304,10 +304,12 @@ function PartyMenu:update(dt) or Strings("{RAM:wNameBuffer} used\nSTRENGTH.")):gsub("{RAM:wNameBuffer}", name) local t2 = (self.game.data.text._CanMoveBouldersText or Strings("{RAM:wNameBuffer} can\nmove boulders.")):gsub("{RAM:wNameBuffer}", name) + -- like surf (#320): the blink belongs UNDER the texts, not as a + -- flashbang on the empty map after them; the stack only updates + -- the top state, so the flash holds until the texts close + self.game.stack:push(Transition.whiteFlash(self.game)) self.game.stack:push(TextBox.new(self.game, t1, function() - self.game.stack:push(TextBox.new(self.game, t2, function() - self.game.stack:push(Transition.whiteFlash(self.game)) - end)) + self.game.stack:push(TextBox.new(self.game, t2)) end, { auto = { sound = function() return require("src.core.Sound").playCry(self.game.data, mon.species) end } })) diff --git a/src/world/OverworldController.lua b/src/world/OverworldController.lua index e2101b85..1fe9b3ef 100644 --- a/src/world/OverworldController.lua +++ b/src/world/OverworldController.lua @@ -2004,23 +2004,19 @@ function OverworldState:trySurf(fx, fy) if not mon then return end local name = mon.nickname or Game.data.pokemon[mon.species].name local p = self.player - p.surfing = true - require("src.core.Music").setSurfing(Game.data, true) local text = (Game.data.text._SurfingGotOnText or Strings("{PLAYER} got on\n{RAM:wNameBuffer}!")) :gsub("{RAM:wNameBuffer}", name) + -- GBPalWhiteOutWithDelay3 runs while the got-on text is still up + -- (start_sub_menus.asm .surf), so the blink reads as a text flash + -- instead of a flashbang on the empty map (#320). The flash sits + -- under the textbox on the stack: only the top state updates, so it + -- holds its frames until the text closes. surfing (and the sprite + -- swap) only applies when the step happens -- no paddling on land. + Game.stack:push(require("src.render.Transition").whiteFlash(Game)) Game.stack:push(TextBox.new(Game, text, function() - -- start_sub_menus.asm .surf: UseItem returns (mount + text done), - -- then GBPalWhiteOutWithDelay3 blinks before the simulated forward - -- press steps onto the water (or across a connection strip, like - -- Cinnabar's east coast onto Route 20) - local Transition = require("src.render.Transition") - if Transition.whiteFlash then - Game.stack:push(Transition.whiteFlash(Game, nil, function() - self:stepForwardOrCrossEdge(p.facing) - end)) - else - self:stepForwardOrCrossEdge(p.facing) - end + p.surfing = true + require("src.core.Music").setSurfing(Game.data, true) + self:stepForwardOrCrossEdge(p.facing) end)) end diff --git a/tests/mod_ui_tests.lua b/tests/mod_ui_tests.lua index a0581ece..0c387c34 100644 --- a/tests/mod_ui_tests.lua +++ b/tests/mod_ui_tests.lua @@ -532,6 +532,30 @@ press(fpm, "a") check(not fpm.submenu and forced == fgame.save.party[1], "forceSwitch still picks immediately (ChooseNextMon / SHIFT)") +-- ------- issue #320: the STRENGTH blink sits under its texts +do + local owStub = { strengthActive = false, + map = { def = { tileset = "OVERWORLD" } }, dark = false } + local sgame = partyGame() + sgame.overworld = owStub + sgame.data.text = {} -- the strength texts fall back to Strings sources + sgame.save.inventory.RAINBOWBADGE = 1 + sgame.save.party[1].moves = { { id = "STRENGTH" } } + local pm = PartyMenu.new(sgame) + pm.game = sgame + sgame.stack:push(pm) + press(pm, "a") -- open the submenu + check(pm.subItems[#pm.subItems].action == "strength", + "the strength row is listed with badge + move") + pm.subIndex = #pm.subItems + press(pm, "a") -- run STRENGTH + local states = sgame.stack.states + check(#states == 2 and states[1].frames ~= nil + and states[2].pages ~= nil, + "the blink sits under the strength texts") + check(owStub.strengthActive == true, "strength still activates") +end + -- ------- mod.ui helpers and theme defaults local items = { { label = "A" }, { label = "B" } } ModUI.insertAfter(items, "A", { label = "X" }) diff --git a/tests/mod_world_tests.lua b/tests/mod_world_tests.lua index caf7ae31..11114556 100644 --- a/tests/mod_world_tests.lua +++ b/tests/mod_world_tests.lua @@ -607,6 +607,38 @@ do end) end +do + -- issue #320: the surf blink sits UNDER the "got on" text (pokered's + -- GBPalWhiteOutWithDelay3 runs while the text is up), and the surfing + -- sprite swap waits for the actual step onto the water + require("src.render.Font").load(Data) + local save = { + flags = {}, + inventory = { SOULBADGE = 1 }, + party = { { species = "SQUIRTLE", moves = { { id = "SURF" } } } }, + player = { name = "RED" }, + } + local stack = { states = {} } + function stack:push(s) self.states[#self.states + 1] = s end + function stack:pop() return table.remove(self.states) end + function stack:top() return self.states[#self.states] end + local game = { data = Data, save = save, stack = stack } + check(bindGame(OW.trySurf, game), "trySurf binds a test Game") + check(bindGame(OW.partyKnows, game), "partyKnows binds the same Game") + local ow = setmetatable({ player = { facing = "down" } }, { __index = OW }) + ow.stepForwardOrCrossEdge = function(_, dir) ow.stepped = dir end + + ow:trySurf(10, 10) + local bottom, top = stack.states[1], stack.states[2] + check(bottom and bottom.frames ~= nil and top and top.pages ~= nil, + "the blink sits under the surf text on the stack") + check(not ow.player.surfing, "no surfing sprite while the text is up") + + top.onDone() + check(ow.player.surfing == true, "surfing applies with the step") + check(ow.stepped == "down", "the step onto the water fires") +end + do -- warp.destination reroutes one door without owning the warp table local warpDef = Data.maps.PALLET_TOWN.warps[1] diff --git a/tests/parity_I_M.lua b/tests/parity_I_M.lua index af47d595..40e70635 100644 --- a/tests/parity_I_M.lua +++ b/tests/parity_I_M.lua @@ -166,6 +166,9 @@ ow.player.facing = "down"; ow.player.surfing = false clearCaptured() local pmSurf = PartyMenu.new(Game) selectSubItem(pmSurf, 3) +-- the blink sits under the got-on text (#320); dismissing the text is +-- what mounts and steps +Game.stack:top().onDone() eq(ow.player.surfing, true, "SURF from the party menu sets player.surfing") check(not onStack(pmSurf), "party menu closes after a successful SURF") check(sawText("got on"), "_SurfingGotOnText shown on a successful SURF") @@ -498,6 +501,7 @@ clearCaptured() -- submenu order: STATS, SWITCH, FLY, CUT, STRENGTH, SURF (move order on mon) local pmFaintSurf = PartyMenu.new(Game) selectSubItem(pmFaintSurf, 6) +Game.stack:top().onDone() -- dismiss the text: mount + step (#320) eq(ow.player.surfing, true, "fainted mon can SURF from the party menu") check(not onStack(pmFaintSurf), "party menu closes after fainted SURF")