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