diff --git a/src/game_message.cpp b/src/game_message.cpp index 9e2eed3ec7b..9557b07153f 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 3e770770e8a..b57eb683699 100644 --- a/src/scene_map.cpp +++ b/src/scene_map.cpp @@ -181,23 +181,22 @@ void Scene_Map::TransitionIn(SceneType prev_scene) { } void Scene_Map::TransitionOut(SceneType next_scene) { - auto& transition = Transition::instance(); - if (next_scene != Scene::Battle && next_scene != Scene::Debug) { screen_erased_by_event = false; } if (next_scene == Scene::Battle) { - transition.Init((Transition::TransitionType)Game_System::GetTransition(Game_System::Transition_BeginBattleErase), this, 32, true); - transition.AppendBefore(Color(255, 255, 255, 255), 12, 2); + InitTransitionOut((Transition::TransitionType)Game_System::GetTransition(Game_System::Transition_BeginBattleErase), 32); + Transition::instance().AppendBefore(Color(255, 255, 255, 255), 12, 2); return; } if (next_scene == Scene::Gameover) { - transition.Init(Transition::TransitionFadeOut, this, 32, true); + InitTransitionOut(Transition::TransitionFadeOut, 32); return; } - Scene::TransitionOut(next_scene); + + InitTransitionOut(Transition::TransitionFadeOut, 6); } void Scene_Map::DrawBackground(Bitmap& dst) { @@ -293,15 +292,14 @@ void Scene_Map::UpdateSceneCalling() { } void Scene_Map::StartPendingTeleport(TeleportParams tp) { - auto& transition = Transition::instance(); const auto& tt = Main_Data::game_player->GetTeleportTarget(); FileRequestAsync* request = Game_Map::RequestMap(tt.GetMapId()); request->SetImportantFile(true); request->Start(); - if (!transition.IsErased() && tt.GetType() != TeleportTarget::eVehicleHackTeleport && tp.erase_screen) { - transition.Init((Transition::TransitionType)Game_System::GetTransition(Game_System::Transition_TeleportErase), this, 32, true); + if (!Transition::instance().IsErased() && tt.GetType() != TeleportTarget::eVehicleHackTeleport && tp.erase_screen) { + InitTransitionOut((Transition::TransitionType)Game_System::GetTransition(Game_System::Transition_TeleportErase), 32); } AsyncNext([=]() { FinishPendingTeleport(tp); }); @@ -388,11 +386,9 @@ void Scene_Map::OnAsyncSuspend(F&& f, AsyncOp aop, bool is_preupdate) { return; } - auto& transition = Transition::instance(); - if (aop.GetType() == AsyncOp::eEraseScreen) { auto tt = static_cast(aop.GetTransitionType()); - transition.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; @@ -401,7 +397,7 @@ void Scene_Map::OnAsyncSuspend(F&& f, AsyncOp aop, bool is_preupdate) { if (aop.GetType() == AsyncOp::eShowScreen) { auto tt = static_cast(aop.GetTransitionType()); - transition.Init(tt, this, 32, false); + Transition::instance().Init(tt, this, 32, false); screen_erased_by_event = false; } @@ -413,7 +409,7 @@ void Scene_Map::OnAsyncSuspend(F&& f, AsyncOp aop, bool is_preupdate) { Game_System::BgmFade(800); // FIXME: Is 36 correct here? - transition.Init(Transition::TransitionFadeOut, Scene::instance.get(), 36, true); + InitTransitionOut(Transition::TransitionFadeOut, 36); AsyncNext([=]() { StartInn(); }); return; @@ -459,3 +455,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); + }; + Transition::instance().Init(tt, this, frames, true, cb); +} + diff --git a/src/scene_map.h b/src/scene_map.h index 714bfc6f2b6..952b66cfb28 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. @@ -82,6 +83,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 506a046dfba..1dcbfd24365 100644 --- a/src/transition.h +++ b/src/transition.h @@ -75,6 +75,8 @@ class Transition : public Drawable { static Transition& instance(); + using TransitionCallback = std::function; + /** * Defines a screen 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 9db119e694b..52e90985854 100644 --- a/src/window_message.cpp +++ b/src/window_message.cpp @@ -359,6 +359,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 c43afdd9f98..b8d07c316c2 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.