1 /*
2 * This file is part of EasyRPG Player.
3 *
4 * EasyRPG Player is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * EasyRPG Player is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // Headers
19 #include "scene_gameover.h"
20 #include "scene_map.h"
21 #include "scene_menu.h"
22 #include "scene_save.h"
23 #include "scene_debug.h"
24 #include "main_data.h"
25 #include "game_map.h"
26 #include "game_message.h"
27 #include "game_party.h"
28 #include "game_player.h"
29 #include "game_system.h"
30 #include "game_screen.h"
31 #include "game_pictures.h"
32 #include "game_variables.h"
33 #include <lcf/rpg/system.h>
34 #include <lcf/lsd/reader.h>
35 #include "player.h"
36 #include "transition.h"
37 #include "audio.h"
38 #include "input.h"
39 #include "screen.h"
40 #include "scene_load.h"
41 #include "output.h"
42 #include "dynrpg.h"
43
GetRunForegroundEvents(TeleportTarget::Type tt)44 static bool GetRunForegroundEvents(TeleportTarget::Type tt) {
45 switch (tt) {
46 case TeleportTarget::eForegroundTeleport:
47 return true;
48 case TeleportTarget::eParallelTeleport:
49 case TeleportTarget::eSkillTeleport:
50 case TeleportTarget::eAsyncQuickTeleport:
51 break;
52 }
53 return false;
54 }
55
Scene_Map(int from_save_id)56 Scene_Map::Scene_Map(int from_save_id)
57 : from_save_id(from_save_id)
58 {
59 type = Scene::Map;
60
61 SetUseSharedDrawables(true);
62
63 // New Game and Load Game always have a delay, so it set it by default in constructor.
64 SetDelayFrames(Scene::kStartGameDelayFrames);
65 }
66
~Scene_Map()67 Scene_Map::~Scene_Map() {
68 }
69
Start()70 void Scene_Map::Start() {
71 Scene_Debug::ResetPrevIndices();
72 spriteset.reset(new Spriteset_Map());
73 message_window.reset(new Window_Message(0, SCREEN_TARGET_HEIGHT - 80, SCREEN_TARGET_WIDTH, 80));
74
75 Game_Message::SetWindow(message_window.get());
76
77 // Called here instead of Scene Load, otherwise wrong graphic stack
78 // is used.
79 if (from_save_id > 0) {
80 auto current_music = Main_Data::game_system->GetCurrentBGM();
81 Main_Data::game_system->BgmStop();
82 Main_Data::game_system->BgmPlay(current_music);
83 DynRpg::Load(from_save_id);
84 } else {
85 Game_Map::PlayBgm();
86 }
87
88 Main_Data::game_screen->InitGraphics();
89 Main_Data::game_pictures->InitGraphics();
90 Game_Clock::ResetFrame(Game_Clock::now());
91
92 Start2(MapUpdateAsyncContext());
93 }
94
Start2(MapUpdateAsyncContext actx)95 void Scene_Map::Start2(MapUpdateAsyncContext actx) {
96 PreUpdate(actx);
97
98 if (actx.IsActive()) {
99 OnAsyncSuspend([this,actx]() { Start2(actx); }, actx.GetAsyncOp(), true);
100 return;
101 }
102
103 if (Main_Data::game_player->IsPendingTeleport()) {
104 TeleportParams tp;
105 auto tt = Main_Data::game_player->GetTeleportTarget().GetType();
106 tp.run_foreground_events = GetRunForegroundEvents(tt);
107 tp.erase_screen = false;
108 tp.use_default_transition_in = true;
109 tp.defer_recursive_teleports = false;
110 StartPendingTeleport(tp);
111 return;
112 }
113
114 // We do the start game fade in transition here instead of TransitionIn callback,
115 // in order to make async logic work properly.
116 auto& transition = Transition::instance();
117 if (transition.IsErasedNotActive()) {
118 transition.InitShow(Transition::TransitionFadeIn, this);
119 }
120
121 // Call any requested scenes when transition is done.
122 AsyncNext([this]() { UpdateSceneCalling(); });
123 }
124
Continue(SceneType prev_scene)125 void Scene_Map::Continue(SceneType prev_scene) {
126 Game_Message::SetWindow(message_window.get());
127
128 if (prev_scene == Scene::Battle) {
129 Game_Map::OnContinueFromBattle();
130 }
131
132 // Player cast Escape / Teleport from menu
133 if (Main_Data::game_player->IsPendingTeleport()) {
134 auto tt = Main_Data::game_player->GetTeleportTarget().GetType();
135 TeleportParams tp;
136 tp.run_foreground_events = GetRunForegroundEvents(tt);
137 tp.erase_screen = false;
138 tp.use_default_transition_in = true;
139 tp.defer_recursive_teleports = (tt == TeleportTarget::eSkillTeleport);
140 FinishPendingTeleport(tp);
141 return;
142 }
143
144 UpdateGraphics();
145 }
146
UpdateGraphics()147 void Scene_Map::UpdateGraphics() {
148 spriteset->Update();
149 }
150
TransitionIn(SceneType prev_scene)151 void Scene_Map::TransitionIn(SceneType prev_scene) {
152 auto& transition = Transition::instance();
153
154 // Teleport already setup a transition.
155 if (transition.IsActive()) {
156 return;
157 }
158
159 // If an event erased the screen, don't transition in.
160 if (screen_erased_by_event) {
161 return;
162 }
163
164 if (prev_scene == Scene::Battle) {
165 transition.InitShow(Main_Data::game_system->GetTransition(Main_Data::game_system->Transition_EndBattleShow), this);
166 return;
167 }
168
169 Scene::TransitionIn(prev_scene);
170 }
171
TransitionOut(SceneType next_scene)172 void Scene_Map::TransitionOut(SceneType next_scene) {
173 auto& transition = Transition::instance();
174
175 if (next_scene != Scene::Battle
176 && next_scene != Scene::Debug) {
177 screen_erased_by_event = false;
178 }
179
180 if (next_scene == Scene::Debug) {
181 transition.InitErase(Transition::TransitionCutOut, this);
182 return;
183 }
184
185 if (next_scene == Scene::Battle) {
186 if (!transition.IsErasedNotActive()) {
187 auto tt = Main_Data::game_system->GetTransition(Main_Data::game_system->Transition_BeginBattleErase);
188 if (tt == Transition::TransitionNone) {
189 // If transition type is none, RPG_RT flashes and then waits 40 frames before starting the battle.
190 transition.InitErase(Transition::TransitionCutOut, this, 40);
191 } else {
192 transition.InitErase(tt, this);
193 }
194 transition.PrependFlashes(31, 31, 31, 31, 10, 2);
195 } else {
196 // If screen is already erased, RPG_RT does nothing for 40 frames.
197 transition.InitErase(Transition::TransitionNone, this, 40);
198 }
199 return;
200 }
201
202 if (next_scene == Scene::Gameover) {
203 transition.InitErase(Transition::TransitionFadeOut, this);
204 return;
205 }
206
207 if (next_scene == Scene::Title) {
208 Main_Data::game_system->BgmStop();
209 }
210
211 Scene::TransitionOut(next_scene);
212 }
213
DrawBackground(Bitmap & dst)214 void Scene_Map::DrawBackground(Bitmap& dst) {
215 if (spriteset->RequireClear(GetDrawableList())) {
216 dst.Clear();
217 }
218 }
219
PreUpdate(MapUpdateAsyncContext & actx)220 void Scene_Map::PreUpdate(MapUpdateAsyncContext& actx) {
221 Game_Map::Update(actx, true);
222 UpdateGraphics();
223 }
224
PreUpdateForegroundEvents(MapUpdateAsyncContext & actx)225 void Scene_Map::PreUpdateForegroundEvents(MapUpdateAsyncContext& actx) {
226 Game_Map::UpdateForegroundEvents(actx);
227 UpdateGraphics();
228 }
229
Update()230 void Scene_Map::Update() {
231 if (activate_inn) {
232 UpdateInn();
233 return;
234 }
235 MapUpdateAsyncContext actx;
236 UpdateStage1(actx);
237 }
238
UpdateStage1(MapUpdateAsyncContext actx)239 void Scene_Map::UpdateStage1(MapUpdateAsyncContext actx) {
240 Game_Map::Update(actx);
241 UpdateGraphics();
242
243 // Waiting for async operation from map update.
244 if (actx.IsActive()) {
245 OnAsyncSuspend([this,actx]() { UpdateStage1(actx); }, actx.GetAsyncOp(), false);
246 return;
247 }
248
249 // On platforms with async loading (emscripten) graphical assets loaded this frame
250 // may require us to wait for them to download before we can start the transitions.
251 AsyncNext([this]() { UpdateStage2(); });
252 }
253
UpdateStage2()254 void Scene_Map::UpdateStage2() {
255 if (Main_Data::game_player->IsPendingTeleport()) {
256 const auto tt = Main_Data::game_player->GetTeleportTarget().GetType();
257 TeleportParams tp;
258 tp.run_foreground_events = GetRunForegroundEvents(tt);
259 tp.erase_screen = true;
260 tp.use_default_transition_in = false;
261 tp.defer_recursive_teleports = false;
262
263 StartPendingTeleport(tp);
264 return;
265 }
266 UpdateSceneCalling();
267 }
268
UpdateSceneCalling()269 void Scene_Map::UpdateSceneCalling() {
270
271 auto call = TakeRequestedScene();
272
273 if (call == nullptr
274 && Player::debug_flag
275 && !Game_Message::IsMessageActive())
276 {
277 // ESC-Menu calling can be force called when TestPlay mode is on and cancel is pressed 5 times while holding SHIFT
278 if (Input::IsPressed(Input::SHIFT)) {
279 if (Input::IsTriggered(Input::CANCEL)) {
280 debug_menuoverwrite_counter++;
281 if (debug_menuoverwrite_counter >= 5) {
282 call = std::make_shared<Scene_Menu>();
283 debug_menuoverwrite_counter = 0;
284 }
285 }
286 } else {
287 debug_menuoverwrite_counter = 0;
288 }
289
290 if (call == nullptr) {
291 if (Input::IsTriggered(Input::DEBUG_MENU)) {
292 call = std::make_shared<Scene_Debug>();
293 }
294 else if (Input::IsTriggered(Input::DEBUG_SAVE)) {
295 call = std::make_shared<Scene_Save>();
296 }
297 }
298 }
299
300 if (call != nullptr) {
301 Scene::Push(std::move(call));
302 }
303 }
304
StartPendingTeleport(TeleportParams tp)305 void Scene_Map::StartPendingTeleport(TeleportParams tp) {
306 auto& transition = Transition::instance();
307
308 if (!transition.IsErasedNotActive() && tp.erase_screen) {
309 transition.InitErase(Main_Data::game_system->GetTransition(Main_Data::game_system->Transition_TeleportErase), this);
310 }
311
312 AsyncNext([=]() { FinishPendingTeleport(tp); });
313 }
314
FinishPendingTeleport(TeleportParams tp)315 void Scene_Map::FinishPendingTeleport(TeleportParams tp) {
316 auto old_map_id = Game_Map::GetMapId();
317 Main_Data::game_player->PerformTeleport();
318
319 if (Game_Map::GetMapId() != old_map_id) {
320 spriteset.reset(new Spriteset_Map());
321 }
322 FinishPendingTeleport2(MapUpdateAsyncContext(), tp);
323 }
324
FinishPendingTeleport2(MapUpdateAsyncContext actx,TeleportParams tp)325 void Scene_Map::FinishPendingTeleport2(MapUpdateAsyncContext actx, TeleportParams tp) {
326 PreUpdate(actx);
327
328 if (actx.IsActive()) {
329 OnAsyncSuspend([=] { FinishPendingTeleport2(actx, tp); }, actx.GetAsyncOp(), true);
330 return;
331 }
332
333 if (!tp.defer_recursive_teleports) {
334 // RPG_RT behavior - Escape and Teleport skills silently ignore any teleport commands
335 // executed by events during pre-update frame and defer them until first frame.
336 if (Main_Data::game_player->IsPendingTeleport()) {
337 tp.erase_screen = false;
338 StartPendingTeleport(tp);
339 return;
340 }
341 }
342
343 auto& transition = Transition::instance();
344
345 // This logic was tested against RPG_RT and works this way ...
346 if (tp.use_default_transition_in && transition.IsErasedNotActive()) {
347 transition.InitShow(Transition::TransitionFadeIn, this);
348 } else if (!tp.use_default_transition_in && !screen_erased_by_event) {
349 transition.InitShow(Main_Data::game_system->GetTransition(Main_Data::game_system->Transition_TeleportShow), this);
350 }
351
352 // Call any requested scenes when transition is done.
353 AsyncNext([=]() { FinishPendingTeleport3(actx, tp); });
354 }
355
FinishPendingTeleport3(MapUpdateAsyncContext actx,TeleportParams tp)356 void Scene_Map::FinishPendingTeleport3(MapUpdateAsyncContext actx, TeleportParams tp) {
357 if (tp.run_foreground_events) {
358 PreUpdateForegroundEvents(actx);
359
360 if (actx.IsActive()) {
361 OnAsyncSuspend([=] { FinishPendingTeleport3(actx, tp); }, actx.GetAsyncOp(), true);
362 return;
363 }
364
365 if (!tp.defer_recursive_teleports) {
366 // See comments about defer_recursive_teleports in FinishPendingTeleport2
367 // Deferring in this block can actually never occur, since an Escape/Teleport won't ever
368 // also trigger foreground events to run in pre-update. We leave the check in here for symmetry.
369 if (Main_Data::game_player->IsPendingTeleport()) {
370 tp.erase_screen = false;
371 StartPendingTeleport(tp);
372 return;
373 }
374 }
375 }
376
377 // Call any requested scenes when transition is done.
378 AsyncNext([this]() { UpdateSceneCalling(); });
379 }
380
PerformAsyncTeleport(TeleportTarget original_tt)381 void Scene_Map::PerformAsyncTeleport(TeleportTarget original_tt) {
382 Main_Data::game_player->PerformTeleport();
383 Main_Data::game_player->ResetTeleportTarget(original_tt);
384
385 spriteset.reset(new Spriteset_Map());
386
387 AsyncNext(std::move(map_async_continuation));
388 }
389
390 template <typename F>
OnAsyncSuspend(F && f,AsyncOp aop,bool is_preupdate)391 void Scene_Map::OnAsyncSuspend(F&& f, AsyncOp aop, bool is_preupdate) {
392 if (CheckSceneExit(aop)) {
393 return;
394 }
395
396 if (aop.GetType() == AsyncOp::eSave) {
397 auto savefs = FileFinder::Save();
398 bool success = Scene_Save::Save(savefs, aop.GetSaveSlot());
399 if (aop.GetSaveResultVar() > 0) {
400 Main_Data::game_variables->Set(aop.GetSaveResultVar(), success ? 1 : 0);
401 Game_Map::SetNeedRefresh(true);
402 }
403 }
404
405 if (aop.GetType() == AsyncOp::eLoad) {
406 auto savefs = FileFinder::Save();
407 std::string save_name = Scene_Save::GetSaveFilename(savefs, aop.GetSaveSlot());
408 Player::LoadSavegame(save_name, aop.GetSaveSlot());
409 }
410
411 auto& transition = Transition::instance();
412
413 if (aop.GetType() == AsyncOp::eEraseScreen) {
414 auto tt = static_cast<Transition::Type>(aop.GetTransitionType());
415 if (tt == Transition::TransitionNone) {
416 // Emulates an RPG_RT bug where instantaneous transitions cause a
417 // 30 frame pause and then make the screen black.
418 transition.InitErase(Transition::TransitionCutOut, this, 30);
419 } else {
420 transition.InitErase(tt, this);
421 }
422
423 if (!is_preupdate) {
424 // RPG_RT behavior: EraseScreen commands performed during pre-update don't stick.
425 screen_erased_by_event = true;
426 }
427 }
428
429 if (aop.GetType() == AsyncOp::eShowScreen) {
430 auto tt = static_cast<Transition::Type>(aop.GetTransitionType());
431 transition.InitShow(tt, this);
432 screen_erased_by_event = false;
433 }
434
435 if (aop.GetType() == AsyncOp::eCallInn) {
436 activate_inn = true;
437 inn_started = false;
438 music_before_inn = Main_Data::game_system->GetCurrentBGM();
439 map_async_continuation = std::forward<F>(f);
440
441 Main_Data::game_system->BgmFade(800);
442
443 UpdateInn();
444 return;
445 }
446
447 if (aop.GetType() == AsyncOp::eQuickTeleport) {
448 map_async_continuation = std::forward<F>(f);
449
450 // If there is already a real teleport pending we need to make sure it gets executed after
451 // the async teleport.
452 auto orig_tt = Main_Data::game_player->GetTeleportTarget();
453
454 Main_Data::game_player->ReserveTeleport(aop.GetTeleportMapId(), aop.GetTeleportX(), aop.GetTeleportY(), -1, TeleportTarget::eAsyncQuickTeleport);
455
456 AsyncNext([=]() { PerformAsyncTeleport(orig_tt); });
457 return;
458 }
459
460 AsyncNext(std::forward<F>(f));
461 }
462
StartInn()463 void Scene_Map::StartInn() {
464 const lcf::rpg::Music& bgm_inn = Main_Data::game_system->GetSystemBGM(Main_Data::game_system->BGM_Inn);
465 if (Main_Data::game_system->IsStopMusicFilename(bgm_inn.name)) {
466 FinishInn();
467 return;
468 }
469
470 Main_Data::game_system->BgmPlay(bgm_inn);
471 }
472
FinishInn()473 void Scene_Map::FinishInn() {
474 // RPG_RT will always transition in, regardless of whether an EraseScreen command
475 // was issued previously.
476 screen_erased_by_event = false;
477
478 auto& transition = Transition::instance();
479
480 transition.InitShow(Transition::TransitionFadeIn, Scene::instance.get());
481 Main_Data::game_system->BgmPlay(music_before_inn);
482
483 // Full heal
484 std::vector<Game_Actor*> actors = Main_Data::game_party->GetActors();
485 for (Game_Actor* actor : actors) {
486 actor->FullHeal();
487 }
488
489 activate_inn = false;
490 inn_started = false;
491 AsyncNext(std::move(map_async_continuation));
492 }
493
UpdateInn()494 void Scene_Map::UpdateInn() {
495 // Allow message box to render during inn sequence.
496 if (Game_Message::IsMessageActive()) {
497 Game_Message::Update();
498 return;
499 }
500
501 if (!inn_started) {
502 Transition::instance().InitErase(Transition::TransitionFadeOut, Scene::instance.get());
503 inn_started = true;
504
505 AsyncNext([=]() { StartInn(); });
506 return;
507 }
508
509 if (Audio().BGM_IsPlaying() && !Audio().BGM_PlayedOnce()) {
510 return;
511 }
512
513 Main_Data::game_system->BgmStop();
514 FinishInn();
515 }
516