diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index 6cee785a..4d809bc1 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -1871,9 +1871,18 @@ function BattleState:update(dt) -- its own has no slide to wait for. if (self.introSlide or 0) > 0 then return end if not self:updateQueue() then - if self.afterQueue == "menu" then + local destination = self.afterQueue + -- These fields are queue/presentation cursors, not durable battle + -- state. Once the queue has drained, keeping their terminal values + -- makes the real command menu look busy to BattleSafety even though + -- every message, wait and intro animation has settled. + self.afterQueue = nil + self.nextInsert = nil + self.waitFrames = nil + if destination == "menu" then + self.introSlide = nil self.phase = "menu" - elseif self.afterQueue == "finish" then + elseif destination == "finish" then self:finish() end end diff --git a/tests/engine/battle_checkpoint_boundary.lua b/tests/engine/battle_checkpoint_boundary.lua index e8d1bf5f..33c01e0d 100644 --- a/tests/engine/battle_checkpoint_boundary.lua +++ b/tests/engine/battle_checkpoint_boundary.lua @@ -15,7 +15,8 @@ local StateStack = require("src.core.StateStack") local Data = Fixtures.fresh() -local function makeGame() +local function makeGame(kind) + kind = kind or "wild" local save = SaveData.newGame() save.meta.playthroughId = "battle-playthrough" save.party = { Pokemon.new(Data, "FIXMON_A", 20) } @@ -29,12 +30,24 @@ local function makeGame() runner = { isRunning = function() return false end }, parallelRunners = {}, pendingScripts = {}, parallelQueue = {}, scriptMoves = {}, } + function overworld:captureSave(progress) + progress.player.map = self.map.id + progress.player.x = self.player.cellX + progress.player.y = self.player.cellY + progress.player.facing = self.player.facing + end local game = { data = Data, save = save, stack = stack, overworld = overworld } stack.states[1] = overworld - local battle = BattleState.newWild(game, "FIXMON_B", 12) + local battle = kind == "trainer" + and BattleState.newTrainer(game, "OPP_FIX_YOUNGSTER", 1) + or BattleState.newWild(game, "FIXMON_B", 12) battle.phase = "menu" battle.queue = {} - battle.checkpointOrigin = { kind = "wild_encounter" } + battle.checkpointOrigin = kind == "trainer" + and { kind = "trainer_encounter", map = save.player.map, + npcId = "TRAINER_1", trainerClass = "OPP_FIX_YOUNGSTER", partyIndex = 1, + event = "EVENT_BEAT_TRAINER_1" } + or { kind = "wild_encounter" } battle.onFinish = function() end stack.states[2] = battle return game, overworld, battle @@ -45,6 +58,42 @@ T.same(Checkpoint.inspect(game), { canCapture = true, canRestore = true, kind = "battle", }, "settled standard wild battle is a checkpoint boundary") +local function settleRealBattle(kind) + local liveGame, _, liveBattle = makeGame(kind) + liveBattle.phase, liveBattle.queue = nil, {} + liveGame.input = { + wasPressed = function(_, button) return button == "a" end, + isDown = function(_, button) return button == "a" end, + } + liveBattle:enter() + local frames = 0 + while liveBattle.phase ~= "menu" and frames < 10000 do + frames = frames + 1 + liveBattle:update(1 / 60) + end + T.eq(liveBattle.phase, "menu", "the real battle intro reaches its command menu") + return liveGame +end + +local realGame = settleRealBattle("wild") +T.same(Checkpoint.inspect(realGame), { + canCapture = true, canRestore = true, kind = "battle", +}, "the completed real battle intro is a checkpoint boundary") +local oldGetRandomState, oldSetRandomState = + love.math.getRandomState, love.math.setRandomState +love.math.getRandomState = function() return "real-boundary-rng" end +love.math.setRandomState = function() end +local realSnapshot, realCaptureCode = Checkpoint.capture(realGame) +T.check(type(realSnapshot) == "table" and realSnapshot.kind == "battle", + "the first real command decision captures for deferred tools: " + .. tostring(realCaptureCode)) +love.math.getRandomState, love.math.setRandomState = + oldGetRandomState, oldSetRandomState +local realTrainerGame = settleRealBattle("trainer") +T.same(Checkpoint.inspect(realTrainerGame), { + canCapture = true, canRestore = true, kind = "battle", +}, "the completed real trainer intro is a checkpoint boundary") + local function refused(mutator, code, label) local game2, ow2, battle2 = makeGame() mutator(game2, ow2, battle2)