diff --git a/src/ui/OakSpeech.lua b/src/ui/OakSpeech.lua index bf4d2a4b..2dcbfd83 100644 --- a/src/ui/OakSpeech.lua +++ b/src/ui/OakSpeech.lua @@ -549,6 +549,10 @@ function OakSpeech:update(dt) elseif s.frame >= 79 and s.frame <= 102 then self.fadeLevel = math.floor((s.frame - 79) / 8) + 1 elseif s.frame > 102 then + -- clear before finish(): a finished-listener that pushes a state gets + -- ITS state popped in the speech's place, and a live shrink would call + -- finish() again next frame, re-firing the event every frame (#308) + self.shrink = nil self:finish() end end diff --git a/tests/mod_ui_tests.lua b/tests/mod_ui_tests.lua index 4199e79b..a0581ece 100644 --- a/tests/mod_ui_tests.lua +++ b/tests/mod_ui_tests.lua @@ -788,6 +788,27 @@ check(type("intro.oak_speech.started") == "string" "intro.oak_speech lifecycle event names are stable") events:removeOwner("fixture") +-- issue #308: finish runs once even when a finished-listener pushes a +-- screen (the pop in finish() then removes that screen instead of the +-- speech, and a live shrink re-fires the event every frame) +do + local fgame = { data = {}, stack = newStack(), input = newInput(), + save = { player = {} } } + local fspeech = OakSpeech.new(fgame, nil) + fspeech.shrink = { frame = 103 } -- past the shrink timeline's end + fgame.stack:push(fspeech) + fspeech.picReveal = nil -- skip the intro fade so update reaches shrink + local finishes = 0 + events:on("intro.oak_speech.finished", function() + finishes = finishes + 1 + fgame.stack:push({ pushedByListener = true }) -- the #308 trap + end, 0, "t308") + for _ = 1, 3 do fspeech:update(1 / 60) end + events:removeOwner("t308") + check(finishes == 1, + "finished fires once when a listener pushes a screen") +end + -- ModUI step helpers local tiny = { { id = "a" }, { id = "c" } } ModUI.insertStepAfter(tiny, "a", { id = "b" })