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