diff --git a/src/game_message.cpp b/src/game_message.cpp index 369cc322779..b74620cab13 100644 --- a/src/game_message.cpp +++ b/src/game_message.cpp @@ -196,6 +196,12 @@ int Game_Message::WordWrap(const std::string& line, const int limit, const std:: return line_count; } +void Game_Message::AdvanceAnimationFrames(int frames) { + if (window) { + window->AdvanceAnimationFrames(frames); + } +} + void Game_Message::Update() { if (window) { window->Update(); diff --git a/src/game_message.h b/src/game_message.h index 0e0ed7288be..69edacd5e9d 100644 --- a/src/game_message.h +++ b/src/game_message.h @@ -39,6 +39,9 @@ namespace Game_Message { void Update(); + /** Calls AdvanceAnimationFrames() on the Window_Message */ + void AdvanceAnimationFrames(int frames); + /** Reset the face graphic. */ void ClearFace(); diff --git a/src/scene_map.cpp b/src/scene_map.cpp index 045d344bc85..5808124b13b 100644 --- a/src/scene_map.cpp +++ b/src/scene_map.cpp @@ -187,15 +187,16 @@ void Scene_Map::TransitionOut(SceneType next_scene) { } if (next_scene == Scene::Battle) { - Graphics::GetTransition().Init((Transition::TransitionType)Game_System::GetTransition(Game_System::Transition_BeginBattleErase), this, 32, true); + InitTransitionOut((Transition::TransitionType)Game_System::GetTransition(Game_System::Transition_BeginBattleErase), 32); Graphics::GetTransition().AppendBefore(Color(255, 255, 255, 255), 12, 2); return; } if (next_scene == Scene::Gameover) { - Graphics::GetTransition().Init(Transition::TransitionFadeOut, this, 32, true); + InitTransitionOut(Transition::TransitionFadeOut, 32); return; } - Scene::TransitionOut(next_scene); + + InitTransitionOut(Transition::TransitionFadeOut, 6); } void Scene_Map::DrawBackground() { @@ -324,7 +325,7 @@ void Scene_Map::StartPendingTeleport(TeleportParams tp) { request->Start(); if (!Graphics::IsTransitionErased() && tt.GetType() != TeleportTarget::eVehicleHackTeleport && tp.erase_screen) { - Graphics::GetTransition().Init((Transition::TransitionType)Game_System::GetTransition(Game_System::Transition_TeleportErase), this, 32, true); + InitTransitionOut((Transition::TransitionType)Game_System::GetTransition(Game_System::Transition_TeleportErase), 32); } AsyncNext([=]() { FinishPendingTeleport(tp); }); @@ -450,7 +451,7 @@ void Scene_Map::OnAsyncSuspend(F&& f, AsyncOp aop, bool is_preupdate) { if (aop.GetType() == AsyncOp::eEraseScreen) { auto tt = static_cast(aop.GetTransitionType()); - Graphics::GetTransition().Init(tt, this, 32, true); + InitTransitionOut(tt, 32); if (!is_preupdate) { // RPG_RT behavior: EraseScreen commands performed during pre-update don't stick. screen_erased_by_event = true; @@ -471,7 +472,7 @@ void Scene_Map::OnAsyncSuspend(F&& f, AsyncOp aop, bool is_preupdate) { Game_System::BgmFade(800); // FIXME: Is 36 correct here? - Graphics::GetTransition().Init(Transition::TransitionFadeOut, Scene::instance.get(), 36, true); + InitTransitionOut(Transition::TransitionFadeOut, 36); AsyncNext([=]() { StartInn(); }); return; @@ -515,3 +516,15 @@ void Scene_Map::UpdateInn() { FinishInn(); } } + +void Scene_Map::InitTransitionOut(Transition::TransitionType tt, int frames) { + // In rare cases where a message and a transition are triggered on the same frame, RPG_RT + // will still animate the message box open/close and the transition effect will show + // the resulting message box state. This can affect interpreter timing so we try to emulate it. + // See #1706 Tests 17 and 18 + auto cb = [this](int frames) { + message_window->AdvanceAnimationFrames(frames); + }; + Graphics::GetTransition().Init(tt, this, frames, true, cb); +} + diff --git a/src/scene_map.h b/src/scene_map.h index 62af8c350d3..c4ff33463c8 100644 --- a/src/scene_map.h +++ b/src/scene_map.h @@ -24,6 +24,7 @@ #include "window_message.h" #include "window_varlist.h" #include "game_map.h" +#include "transition.h" /** * Scene Map class. @@ -91,6 +92,8 @@ class Scene_Map: public Scene { void UpdateInn(); void FinishInn(); + void InitTransitionOut(Transition::TransitionType tt, int frames); + template void AsyncNext(F&& f); template void OnAsyncSuspend(F&& f, AsyncOp aop, bool is_preupdate); diff --git a/src/transition.cpp b/src/transition.cpp index 7925a373581..512b72b80a7 100644 --- a/src/transition.cpp +++ b/src/transition.cpp @@ -29,6 +29,7 @@ #include "baseui.h" #include "drawable.h" #include "drawable_mgr.h" +#include "game_message.h" Transition::Transition() : Drawable(TypeTransition, Priority_Transition, true) { @@ -45,7 +46,7 @@ void Transition::AppendBefore(Color color, int duration, int iterations) { total_frames += flash_duration * flash_iterations; } -void Transition::Init(TransitionType type, Scene *linked_scene, int duration, bool erase) { +void Transition::Init(TransitionType type, Scene *linked_scene, int duration, bool erase, const TransitionCallback& cb) { // FIXME: Break this dependency on DisplayUI if (!black_screen && DisplayUi) { black_screen = Bitmap::Create(DisplayUi->GetWidth(), DisplayUi->GetHeight(), Color(0, 0, 0, 255)); @@ -61,6 +62,15 @@ void Transition::Init(TransitionType type, Scene *linked_scene, int duration, bo return; } + if (transition_type == TransitionErase) { + duration = 1; + } + + // Note: This must be called before we grab the screen! + if (cb) { + cb(duration); + } + frozen_screen = Graphics::SnapToBitmap(GetZ()); screen1 = erase ? frozen_screen : old_frozen_screen? old_frozen_screen : black_screen; screen2 = erase ? black_screen : frozen_screen; @@ -73,7 +83,7 @@ void Transition::Init(TransitionType type, Scene *linked_scene, int duration, bo current_frame = 0; flash_iterations = 0; flash_duration = 0; - total_frames = transition_type == TransitionErase ? 1 : duration; + total_frames = duration; SetAttributesTransitions(); } diff --git a/src/transition.h b/src/transition.h index 08be3f49609..a2b8e5dfd8c 100644 --- a/src/transition.h +++ b/src/transition.h @@ -73,6 +73,8 @@ class Transition : public Drawable { TransitionNone }; + using TransitionCallback = std::function; + Transition(); /** @@ -83,7 +85,7 @@ class Transition : public Drawable { * @param duration transition duration. * @param erase erase screen flag. */ - void Init(TransitionType type, Scene *linked_scene, int duration, bool erase = false); + void Init(TransitionType type, Scene *linked_scene, int duration, bool erase = false, const TransitionCallback& cb = {}); void AppendBefore(Color color, int duration, int iterations); diff --git a/src/window_message.cpp b/src/window_message.cpp index f8f57596735..b563717faf3 100644 --- a/src/window_message.cpp +++ b/src/window_message.cpp @@ -360,6 +360,21 @@ void Window_Message::ResetWindow() { } +void Window_Message::AdvanceAnimationFrames(int frames) { + if (frames == 0) { + return; + } + + close_started_this_frame = false; + + while (frames > 0) { + bool was_closing = IsClosing(); + Window::Update(); + close_finished_this_frame = was_closing && !IsClosing(); + --frames; + } +} + void Window_Message::Update() { if (IsOpening()) { DebugLog("%d: MSG OPENING"); } if (IsClosing()) { DebugLog("%d: MSG CLOSING"); } diff --git a/src/window_message.h b/src/window_message.h index 584884b5932..190556643c5 100644 --- a/src/window_message.h +++ b/src/window_message.h @@ -88,11 +88,19 @@ class Window_Message: public Window_Selectable { void Update() override; + /** + * Force the window animations to advance by a number of frames. + * This is exercised by some edge cases with transitions + * + * @params frames number of frames of the transition. + */ + void AdvanceAnimationFrames(int frames); + /** * Continues outputting more text. Also handles the * CommandCode parsing. */ - virtual void UpdateMessage(); + void UpdateMessage(); /** * Stub. For choice.