diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index 2bbdd484..47e0cd1e 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -5073,10 +5073,9 @@ function BattleState:playerMonFainted() -- battles go straight to the party menu (the menu-phase guard). if self.kind ~= "wild" then return end local game = self.game - self:say(self.data.text._UseNextMonText or Strings("Use next POKéMON?")) - self:ui(function() - local ChoiceBox = require("src.ui.ChoiceBox") - return ChoiceBox.new(game, function(yes) + -- engine/battle/core.asm:1052-1078 + self:sayChoice(self.data.text._UseNextMonText or Strings("Use next POKéMON?"), + function(yes) if yes then return end -- the menu-phase guard opens the party menu local pSpd = (game.save.party[1].stats or { speed = 0 }).speed or 0 if self:runRoll(pSpd, TurnOrder.effectiveSpeed(self.enemy)) then @@ -5088,8 +5087,7 @@ function BattleState:playerMonFainted() else self:say(self:romText("_CantEscapeText", "Can't escape!")) end - end) - end) + end, { box = require("src.ui.Theme").useNextMonBox }) end -- ChooseNextMon (core.asm:1086-1128): the battle party menu; a fainted diff --git a/src/ui/Theme.lua b/src/ui/Theme.lua index 036dc9f1..d8497285 100644 --- a/src/ui/Theme.lua +++ b/src/ui/Theme.lua @@ -23,6 +23,8 @@ local Theme = { -- EnemySendOutFirstMon inlines its own TWO_OPTION_MENU at hlcoord 0, 7 -- instead of the shared right-hand one -- engine/battle/core.asm:1378-1384 trainerSwitchBox = { tx = 0, ty = 7, tw = 6, th = 5 }, + -- engine/battle/core.asm:1062 + useNextMonBox = { tx = 13, ty = 9, tw = 6, th = 5 }, -- SaveTheGame_YesOrNo pins its TWO_OPTION_MENU at hlcoord 0, 7 too -- -- engine/menus/save.asm:186-192 saveBox = { tx = 0, ty = 7, tw = 6, th = 5 }, diff --git a/tests/drivers/use_next_mon_2554.lua b/tests/drivers/use_next_mon_2554.lua new file mode 100644 index 00000000..06e75269 --- /dev/null +++ b/tests/drivers/use_next_mon_2554.lua @@ -0,0 +1,65 @@ +-- engine/battle/core.asm:1052 +return function(game) + local U = dofile("tests/drivers/util.lua") + local DIR = os.getenv("SHOT_DIR") or os.getenv("POKEPORT_SHOT_DIR") or "/tmp/shots" + local Pokemon = require("src.pokemon.Pokemon") + local BattleState = require("src.battle.BattleState") + + local function isChoice(top, battle) + return top ~= battle and top ~= nil and top.index ~= nil and top.onChoose ~= nil + end + + local lead = Pokemon.new(game.data, "CATERPIE", 3) + lead.hp = 1 + game.save.party = { lead, Pokemon.new(game.data, "SQUIRTLE", 10) } + U.teleport(game, "ROUTE_1", 5, 5, "down") + + local battle = BattleState.newWild(game, "RATTATA", 50) + battle.onFinish = function() end + game.overworld:pushBattle(battle) + + for _ = 1, 200 do + if battle.phase == "menu" then break end + U.tap(game, "a") + U.wait(4) + end + U.tap(game, "a"); U.wait(8) + U.tap(game, "a"); U.wait(8) + + local sawPrompt = false + local up = false + for _ = 1, 900 do + local top = game.stack:top() + local cur = battle.current + local txt = cur and cur.text and tostring(cur.text) or "" + if isChoice(top, battle) then up = true break end + if top ~= battle then break end + if txt:find("Use next") then + sawPrompt = true + else + U.tap(game, "a") + end + U.wait(2) + end + U.log("use-next page seen:", sawPrompt) + + local top = game.stack:top() + if not up then + for _ = 1, 120 do + U.wait(1) + if isChoice(game.stack:top(), battle) then up = true break end + end + end + U.log("PASS_USE_NEXT_YESNO_WITHOUT_A", up and sawPrompt) + local boxOk = false + if up then + top = game.stack:top() + boxOk = top.tx == 13 and top.ty == 9 + U.log("PASS_USE_NEXT_BOX_13_9", boxOk) + U.wait(20) + U.still(game, DIR .. "/2554_01_use_next_yesno_auto.png") + end + local ok = up and sawPrompt and boxOk + U.log(ok and "RESULT PASS" or "RESULT FAIL") + love.event.quit(ok and 0 or 1) +end diff --git a/tests/parity_double_faint.lua b/tests/parity_double_faint.lua index 01844892..f8943cd5 100644 --- a/tests/parity_double_faint.lua +++ b/tests/parity_double_faint.lua @@ -44,8 +44,16 @@ local function battleWith(partyHP, result) data = { text = { _UseNextMonText = "Use next POKéMON?" } }, game = { save = { party = party, player = { name = "RED" } } }, sayNext = function(self, m) self.said[#self.said + 1] = m end, - say = function(self, m) self.said[#self.said + 1] = m end, - ui = function() end, + rows = {}, + say = function(self, m) + self.said[#self.said + 1] = m + self.rows[#self.rows + 1] = { kind = "say", text = m } + end, + sayChoice = function(self, m, fn, opts) + self.said[#self.said + 1] = m + self.rows[#self.rows + 1] = { kind = "choice", text = m, fn = fn, opts = opts } + end, + ui = function(self) self.rows[#self.rows + 1] = { kind = "ui" } end, }, BattleState) end @@ -106,6 +114,11 @@ do BattleState.playerMonFainted(b) eq(b.result, nil, "a faint with reserves left does not decide the battle") check(not saidBlackout(b), "and does not black out") + eq(#b.rows, 1, "the prompt is a single queue row, no trailing ui row") + eq(b.rows[1].kind, "choice", "Use next POKéMON? is a done-tail choice row") + eq(b.rows[1].text, "Use next POKéMON?", "the choice row carries the prompt text") + eq(b.rows[1].opts.box.tx, 13, "YES/NO sits at hlcoord 13, 9 (x)") + eq(b.rows[1].opts.box.ty, 9, "YES/NO sits at hlcoord 13, 9 (y)") end -- enemyMonFainted is also a native authority path used by move effects. A