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 <cassert>
20 #include "async_handler.h"
21 #include "scene.h"
22 #include "graphics.h"
23 #include "input.h"
24 #include "player.h"
25 #include "output.h"
26 #include "audio.h"
27 #include "transition.h"
28 #include "game_interpreter.h"
29 #include "game_system.h"
30 #include "main_data.h"
31 
32 #ifndef NDEBUG
33 #define DEBUG_VALIDATE(x) Scene::DebugValidate(x)
34 #else
35 #define DEBUG_VALIDATE(x) do {} while(0)
36 #endif
37 
38 
39 constexpr int Scene::kStartGameDelayFrames;
40 constexpr int Scene::kReturnTitleDelayFrames;
41 std::shared_ptr<Scene> Scene::instance;
42 std::vector<std::shared_ptr<Scene> > Scene::old_instances;
43 std::vector<std::shared_ptr<Scene> > Scene::instances;
44 const char Scene::scene_names[SceneMax][12] =
45 {
46 	"Null",
47 	"Title",
48 	"Map",
49 	"Menu",
50 	"Item",
51 	"Skill",
52 	"Equip",
53 	"ActorTarget",
54 	"Status",
55 	"File",
56 	"Save",
57 	"Load",
58 	"End",
59 	"Battle",
60 	"Shop",
61 	"Name",
62 	"Gameover",
63 	"Debug",
64 	"Logo",
65 	"Order",
66 	"GameBrowser",
67 	"Teleport"
68 };
69 
70 enum PushPopOperation {
71 	ScenePushed = 1,
72 	ScenePopped
73 };
74 
75 int Scene::push_pop_operation = 0;
76 
rpgRtSceneFromSceneType(SceneType t)77 lcf::rpg::SaveSystem::Scene Scene::rpgRtSceneFromSceneType(SceneType t) {
78 	switch (t) {
79 		case Null:
80 		case GameBrowser:
81 		case SceneMax:
82 		case Logo:
83 			break;
84 		case Title:
85 			return lcf::rpg::SaveSystem::Scene_title;
86 		case Map:
87 			return lcf::rpg::SaveSystem::Scene_map;
88 		case Menu:
89 		case Item:
90 		case Skill:
91 		case Equip:
92 		case ActorTarget:
93 		case Status:
94 		case Teleport:
95 		case Order:
96 		case End:
97 			return lcf::rpg::SaveSystem::Scene_menu;
98 		case File:
99 		case Save:
100 		case Load:
101 			return lcf::rpg::SaveSystem::Scene_file;
102 		case Battle:
103 			return lcf::rpg::SaveSystem::Scene_battle;
104 		case Shop:
105 			return lcf::rpg::SaveSystem::Scene_shop;
106 		case Name:
107 			return lcf::rpg::SaveSystem::Scene_name;
108 		case Gameover:
109 			return lcf::rpg::SaveSystem::Scene_game_over;
110 		case Debug:
111 			return lcf::rpg::SaveSystem::Scene_debug;
112 	}
113 	return lcf::rpg::SaveSystem::Scene(-1);
114 }
115 
Scene()116 Scene::Scene() {
117 	type = Scene::Null;
118 }
119 
ScheduleTransitionIn(Scene::SceneType prev_scene_type)120 void Scene::ScheduleTransitionIn(Scene::SceneType prev_scene_type) {
121 	if (!Transition::instance().IsErasedNotActive()) {
122 		// Scene could have manually triggered transition earlier
123 		return;
124 	}
125 
126 	// If Start() or Continue() produced an async operation, defer TransitionIn() call until
127 	// after async completes
128 	if (async_continuation) {
129 		AsyncNext([this,fn=std::move(async_continuation),prev_scene_type]() {
130 					fn();
131 					ScheduleTransitionIn(prev_scene_type);
132 				});
133 	} else {
134 		AsyncNext([this,prev_scene_type]() { TransitionIn(prev_scene_type); });
135 	}
136 }
137 
MainFunction()138 void Scene::MainFunction() {
139 	static bool init = false;
140 
141 	if (IsAsyncPending()) {
142 		Player::Update(false);
143 		return;
144 	} else {
145 		// This is used to provide a hook for Scene_Map to finish
146 		// it's PreUpdate() and teleport logic after transition
147 		// or asynchronous file load.
148 		OnFinishAsync();
149 	}
150 
151 	// The continuation could have caused a new async wait condition, or
152 	// it could have changed the scene.
153 	if (!IsAsyncPending() && Scene::instance.get() == this) {
154 		if (!init) {
155 			auto prev_scene = Graphics::UpdateSceneCallback();
156 			auto prev_scene_type = prev_scene ? prev_scene->type : Null;
157 
158 			// Destroy the previous scene here, before any initialization logic / transition in occurs.
159 			prev_scene.reset();
160 
161 			// Initialization after scene switch
162 			switch (push_pop_operation) {
163 				case ScenePushed:
164 					Start();
165 					initialized = true;
166 					break;
167 				case ScenePopped:
168 					if (!initialized) {
169 						Start();
170 						initialized = true;
171 					} else {
172 						Continue(prev_scene_type);
173 					}
174 					break;
175 				default:;
176 			}
177 
178 			push_pop_operation = 0;
179 
180 			ScheduleTransitionIn(prev_scene_type);
181 
182 			init = true;
183 			return;
184 		} else {
185 			Player::Update();
186 		}
187 	}
188 
189 	if (Scene::instance.get() != this) {
190 		// Shutdown after scene switch
191 		assert(Scene::instance == instances.back() &&
192 			"Don't set Scene::instance directly, use Push instead!");
193 
194 		Graphics::Update();
195 
196 		auto next_scene = instance ? instance->type : Null;
197 
198 		// Scene could have manually triggered transition earlier
199 		if (!Transition::instance().IsActive()) {
200 			TransitionOut(next_scene);
201 		}
202 
203 		init = false;
204 	}
205 }
206 
Start()207 void Scene::Start() {
208 }
209 
Continue(SceneType)210 void Scene::Continue(SceneType /* prev_scene */) {
211 }
212 
TransitionIn(SceneType)213 void Scene::TransitionIn(SceneType) {
214 	Transition::instance().InitShow(Transition::TransitionFadeIn, this, 6);
215 }
216 
TransitionOut(SceneType)217 void Scene::TransitionOut(SceneType) {
218 	Transition::instance().InitErase(Transition::TransitionFadeOut, this, 6);
219 }
220 
Suspend(SceneType)221 void Scene::Suspend(SceneType /* next_scene */) {
222 }
223 
OnFinishAsync()224 void Scene::OnFinishAsync() {
225 	if (async_continuation) {
226 		// The continuation could set another continuation, so move this
227 		// one out of the way first before we call it.
228 		AsyncContinuation continuation;
229 		async_continuation.swap(continuation);
230 
231 		continuation();
232 	}
233 }
234 
IsAsyncPending()235 bool Scene::IsAsyncPending() {
236 	return Transition::instance().IsActive() || AsyncHandler::IsImportantFilePending()
237 		|| (instance != nullptr && instance->HasDelayFrames());
238 }
239 
Update()240 void Scene::Update() {
241 }
242 
Push(std::shared_ptr<Scene> const & new_scene,bool pop_stack_top)243 void Scene::Push(std::shared_ptr<Scene> const& new_scene, bool pop_stack_top) {
244 	if (pop_stack_top) {
245 		old_instances.push_back(instances.back());
246 		instances.pop_back();
247 	}
248 
249 	instances.push_back(new_scene);
250 	instance = new_scene;
251 
252 	push_pop_operation = ScenePushed;
253 
254 	DEBUG_VALIDATE("Push");
255 }
256 
Pop()257 void Scene::Pop() {
258 	old_instances.push_back(instances.back());
259 	instances.pop_back();
260 
261 	instance = instances.empty() ? nullptr : instances.back();
262 
263 	push_pop_operation = ScenePopped;
264 
265 	DEBUG_VALIDATE("Pop");
266 }
267 
PopUntil(SceneType type)268 void Scene::PopUntil(SceneType type) {
269 	int count = 0;
270 
271 	for (int i = (int)instances.size() - 1 ; i >= 0; --i) {
272 		if (instances[i]->type == type) {
273 			for (i = 0; i < count; ++i) {
274 				old_instances.push_back(instances.back());
275 				instances.pop_back();
276 			}
277 			instance = instances.back();
278 			push_pop_operation = ScenePopped;
279 			DEBUG_VALIDATE("PopUntil");
280 			return;
281 		}
282 		++count;
283 	}
284 
285 	Output::Warning("The requested scene {} was not on the stack", scene_names[type]);
286 	DEBUG_VALIDATE("PopUntil");
287 }
288 
Find(SceneType type)289 std::shared_ptr<Scene> Scene::Find(SceneType type) {
290 	std::vector<std::shared_ptr<Scene> >::const_reverse_iterator it;
291 	for (it = instances.rbegin() ; it != instances.rend(); ++it) {
292 		if ((*it)->type == type) {
293 			return *it;
294 		}
295 	}
296 
297 	return std::shared_ptr<Scene>();
298 }
299 
DrawBackground(Bitmap & dst)300 void Scene::DrawBackground(Bitmap& dst) {
301 	dst.Fill(Main_Data::game_system->GetBackgroundColor());
302 }
303 
CheckSceneExit(AsyncOp aop)304 bool Scene::CheckSceneExit(AsyncOp aop) {
305 	if (aop.GetType() == AsyncOp::eExitGame) {
306 		if (Scene::Find(Scene::GameBrowser)) {
307 			Scene::PopUntil(Scene::GameBrowser);
308 		} else {
309 			Player::exit_flag = true;
310 		}
311 		return true;
312 	}
313 
314 	if (aop.GetType() == AsyncOp::eToTitle) {
315 		Scene::ReturnToTitleScene();
316 		return true;
317 	}
318 
319 	return false;
320 }
321 
322 
323 
DebugValidate(const char * caller)324 inline void Scene::DebugValidate(const char* caller) {
325 	if (instances.size() <= 1) {
326 		// Scene of size 1 happens before graphics stack is up. Which can
327 		// cause the following logs to crash.
328 		return;
329 	}
330 	std::bitset<SceneMax> present;
331 	for (auto& scene: instances) {
332 		if (present[scene->type]) {
333 			Output::Debug("Scene Stack after {}:", caller);
334 			for (auto& s: instances) {
335 				auto fmt =  (s == scene) ? "--> {} <--" : "  {}";
336 				Output::Debug(fmt, scene_names[s->type]);
337 			}
338 			Output::Error("Multiple scenes of type={} in the Scene instances stack!", scene_names[scene->type]);
339 		}
340 		present[scene->type] = true;
341 	}
342 	if (instances[0]->type != Null) {
343 		Output::Error("Scene.instances[0] is of type={} in the Scene instances stack!", scene_names[instances[0]->type]);
344 	}
345 }
346 
ReturnToTitleScene()347 bool Scene::ReturnToTitleScene() {
348 	if (Scene::instance && Scene::instance->type == Scene::Title) {
349 		return false;
350 	}
351 
352 	auto title_scene = Scene::Find(Scene::Title);
353 	if (!title_scene) {
354 		return false;
355 	}
356 
357 	title_scene->SetDelayFrames(Scene::kReturnTitleDelayFrames);
358 	Scene::PopUntil(Scene::Title);
359 	return true;
360 }
361 
TransferDrawablesFrom(Scene & prev_scene)362 void Scene::TransferDrawablesFrom(Scene& prev_scene) {
363 	drawable_list.TakeFrom(prev_scene.GetDrawableList(),
364 			[this](auto* draw) { return draw->IsGlobal() || (uses_shared_drawables && draw->IsShared()); });
365 
366 	if (!UsesSharedDrawables() || prev_scene.UsesSharedDrawables()) {
367 		// Either we don't take shared, or we do and we got them from the previous scene.
368 		return;
369 	}
370 	// Previous scene did not use shared, that means the shared drawables are on the scene stack somewhere.
371 	// This can happen for example when you do Map -> Debug -> Battle.
372 	for (auto iter = instances.rbegin() + 1; iter != instances.rend(); ++iter) {
373 		auto& scene = *iter;
374 		if (scene->UsesSharedDrawables()) {
375 			drawable_list.TakeFrom(scene->GetDrawableList(), [](auto* draw) { return draw->IsShared(); });
376 			break;
377 		}
378 	}
379 }
380 
OnPartyChanged(Game_Actor *,bool)381 void Scene::OnPartyChanged(Game_Actor*, bool) {
382 }
383 
OnEventHpChanged(Game_Battler *,int)384 void Scene::OnEventHpChanged(Game_Battler*, int) {
385 }
386