diff --git a/src/battle/BattleState.lua b/src/battle/BattleState.lua index a7f55aab..5eeb5aec 100644 --- a/src/battle/BattleState.lua +++ b/src/battle/BattleState.lua @@ -1476,12 +1476,21 @@ function BattleState:enter() table.insert(self.queue, { wait = 16 }) self:act(function() self.showEnemyTrainer = false + -- the slot is EMPTY from here until AnimateSendingOutMon runs below: + -- the pic has walked off and the mon is still in its ball, so nothing + -- stands in the enemy slot while TrainerSentOutText prints. Without + -- this the front sprite popped in full-size the instant the trainer + -- left, sat there through the whole text box, and the grow-in then + -- restarted it from nothing -- the mon appearing before it was sent + -- out. Mirrors the flag the mid-battle replacement already sets. + self.enemySendingOut = true self:slidePic("foe") end) self:say(Strings("%s sent\nout %s!", self.trainer.name, self.enemy.name)) self:act(function() -- EnemySendOutFirstMon (core.asm:1421-1434): after the text the -- pic grows out of the ball (AnimateSendingOutMon), then the cry + self.enemySendingOut = false self:startGrowIn(self.enemy) end) queueEnemyCry() diff --git a/tests/parity_battle_intro_chrome.lua b/tests/parity_battle_intro_chrome.lua index af1b12f8..37532314 100644 --- a/tests/parity_battle_intro_chrome.lua +++ b/tests/parity_battle_intro_chrome.lua @@ -19,6 +19,7 @@ local Pokemon = require("src.pokemon.Pokemon") local SaveData = require("src.core.SaveData") local Sound = require("src.core.Sound") local Music = require("src.core.Music") +local Timing = require("src.core.Timing") -- Silence audio: BattleState reaches both modules through require() at the -- call site, so patching the fields here is what the battle ends up calling. @@ -40,8 +41,11 @@ local function makeGame(party) function stack:push(state) self.states[#self.states + 1] = state end function stack:pop() return table.remove(self.states) end function stack:top() return self.states[#self.states] end + -- isDown as well as wasPressed: battle text collapses PrintLetterDelay + -- while A or B is held, and the typing path reads it every frame return { data = Data, save = save, stack = stack, - input = { wasPressed = function(_, b) return press[b] == true end } } + input = { wasPressed = function(_, b) return press[b] == true end, + isDown = function(_, b) return press[b] == true end } } end -- One fixed step with A held. updateQueue only reads the button once a page @@ -128,6 +132,12 @@ end check(prompted, "a typed-out intro page raises the prompt flag (blinking arrow)") eq(wild.introBalls, true, "the ball row is still up while the arrow blinks") +-- PromptText writes the arrow and then runs ProtectedDelay3 before +-- ManualTextScroll starts watching the joypad (home/text.asm:213-217), so the +-- page ignores the button for TEXT_PRE_ADVANCE frames. The loop above breaks +-- on the frame the arrow goes up, which is inside that hold. +for _ = 1, Timing.TEXT_PRE_ADVANCE do press.a = false wild:update(1 / 60) end + -- press A: ClearSprites + both ClearScreenAreas, then the enemy HUD step(wild) eq(wild.msgPrompt, nil, "the prompt flag clears on the A press") @@ -196,6 +206,26 @@ check(foeDone ~= nil and sentFrame ~= nil and foeDone < sentFrame, "the slide finishes BEFORE TrainerSentOutText (core.asm:1308-1310)") eq(tr.showEnemyTrainer, false, "only then is the trainer pic taken down") +-- ...and the mon is not standing in that slot yet. The pic walks off at +-- core.asm:1308-1310 but AnimateSendingOutMon does not run until :1421-1434, +-- so the slot is empty for the whole of TrainerSentOutText -- the mon is +-- still in its ball. It used to pop in full-size the instant the trainer +-- left and sit there through the text, so the grow-in played over a mon that +-- had already arrived; the mid-battle replacement always held it back. +check(tr.enemySendingOut, "the foe's mon stays in its ball while announced") +eq(tr:growInScale(tr.enemy), nil, "and nothing is growing into the slot yet") + +local firstGrowScale +for _ = 1, 400 do + step(tr) + if tr.growIn and tr.growIn.battler == tr.enemy then + firstGrowScale = tr:growInScale(tr.enemy) + break + end +end +eq(firstGrowScale, 0, "the send-out opens on the ball beat, not a finished pic") +eq(tr.enemySendingOut, false, "which is when the slot is handed over") + -- and the window never reopens: drive the rest of the intro out for _ = 1, 400 do step(tr) diff --git a/tests/parity_trainer_victory_text.lua b/tests/parity_trainer_victory_text.lua index 47a24a8e..45cf8446 100644 --- a/tests/parity_trainer_victory_text.lua +++ b/tests/parity_trainer_victory_text.lua @@ -36,8 +36,11 @@ local function makeGame(party) function stack:push(state) self.states[#self.states + 1] = state end function stack:pop() return table.remove(self.states) end function stack:top() return self.states[#self.states] end + -- isDown as well as wasPressed: battle text collapses PrintLetterDelay + -- while A or B is held, and the typing path reads it every frame return { data = Data, save = save, stack = stack, - input = { wasPressed = function(_, b) return press[b] == true end } } + input = { wasPressed = function(_, b) return press[b] == true end, + isDown = function(_, b) return press[b] == true end } } end -- A held: updateQueue only reads the button once a page is typed out, so an