1
2 #include "nx.h"
3
4 #include <cstdarg>
5 #include <cstdlib>
6 #if !defined(_WIN32)
7 #include <unistd.h>
8 #else
9 #include <direct.h>
10 #include <io.h>
11 #endif
12 //#include "main.h"
13 #include "game.h"
14 #include "graphics/Renderer.h"
15 #include "input.h"
16 #include "map.h"
17 #include "profile.h"
18 #include "settings.h"
19 #include "statusbar.h"
20 #include "trig.h"
21 #include "tsc.h"
22
23 #include <SDL_mixer.h>
24 #include <SDL_image.h>
25 using namespace NXE::Graphics;
26 #include "ResourceManager.h"
27 #include "caret.h"
28 #include "common/misc.h"
29 #include "console.h"
30 #include "screeneffect.h"
31 #include "sound/SoundManager.h"
32 #include "Utils/Logger.h"
33 using namespace NXE::Utils;
34
35 #if defined(__SWITCH__)
36 #include <switch.h>
37 #include <iostream>
38 #endif
39
40 #if defined(__VITA__)
41 // increase default allowed heap size on Vita
42 int _newlib_heap_size_user = 100 * 1024 * 1024;
43 #include <psp2/kernel/threadmgr.h>
44 extern "C"
45 {
sleep(unsigned int seconds)46 unsigned int sleep(unsigned int seconds)
47 {
48 sceKernelDelayThread(seconds*1000*1000);
49 return 0;
50 }
51
usleep(useconds_t usec)52 int usleep(useconds_t usec)
53 {
54 sceKernelDelayThread(usec);
55 return 0;
56 }
57 }
58 #endif
59
60 using namespace NXE::Sound;
61
62 int fps = 0;
63 static int fps_so_far = 0;
64 static uint32_t fpstimer = 0;
65
66 #define GAME_WAIT (1000 / GAME_FPS) // sets framerate
67 int framecount = 0;
68 bool freezeframe = false;
69 int flipacceltime = 0;
70
fatal(const char * str)71 static void fatal(const char *str)
72 {
73 LOG_CRITICAL("fatal: '{}'", str);
74
75 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", str, NULL);
76 }
77
78 /*static bool check_data_exists()
79 {
80 char fname[MAXPATHLEN];
81
82 sprintf(fname, "%s/npc.tbl", data_dir);
83 if (file_exists(fname)) return 0;
84
85 fatal("Missing \"data\" directory.\nPlease copy it over from a Doukutsu installation.");
86
87 return 1;
88 }*/
89
update_fps()90 void update_fps()
91 {
92 fps_so_far++;
93
94 if ((SDL_GetTicks() - fpstimer) >= 500)
95 {
96 fpstimer = SDL_GetTicks();
97 fps = (fps_so_far << 1);
98 fps_so_far = 0;
99 }
100
101 char fpstext[64];
102 sprintf(fpstext, "%d fps", fps);
103
104 int x = (Renderer::getInstance()->screenWidth - 4) - Renderer::getInstance()->font.getWidth(fpstext);
105 Renderer::getInstance()->font.draw(x, 4, fpstext, 0x00FF00, true);
106 }
107
run_tick()108 static inline void run_tick()
109 {
110 static bool can_tick = true;
111 #if defined(DEBUG)
112 static bool last_freezekey = false;
113 static bool last_framekey = false;
114 #endif
115 static int frameskip = 0;
116
117 input_poll();
118
119 // input handling for a few global things
120
121 if (justpushed(F9KEY))
122 {
123 Renderer::getInstance()->saveScreenshot();
124 }
125
126 // freeze frame
127 #if defined(DEBUG)
128 if (inputs[FREEZE_FRAME_KEY] && !last_freezekey)
129 {
130 can_tick = true;
131 freezeframe ^= 1;
132 framecount = 0;
133 }
134
135 if (inputs[FRAME_ADVANCE_KEY] && !last_framekey)
136 {
137 can_tick = true;
138 if (!freezeframe)
139 {
140 freezeframe = 1;
141 framecount = 0;
142 }
143 }
144
145 last_freezekey = inputs[FREEZE_FRAME_KEY];
146 last_framekey = inputs[FRAME_ADVANCE_KEY];
147
148 // fast-forward key (F5)
149
150 if (inputs[FFWDKEY])
151 {
152 game.ffwdtime = 2;
153 }
154
155 #endif
156
157 if (can_tick)
158 {
159 game.tick();
160
161 if (freezeframe)
162 {
163 char buf[1024];
164 sprintf(buf, "[] Tick %d", framecount++);
165 Renderer::getInstance()->font.draw(4, (Renderer::getInstance()->screenHeight - Renderer::getInstance()->font.getHeight() - 4), buf, 0x00FF00, true);
166 sprintf(buf, "Left: %d, Right: %d, JMP: %d, FR: %d, ST: %d", inputs[LEFTKEY], inputs[RIGHTKEY], inputs[JUMPKEY],
167 inputs[FIREKEY], inputs[STRAFEKEY]);
168 Renderer::getInstance()->font.draw(80, (Renderer::getInstance()->screenHeight - Renderer::getInstance()->font.getHeight() - 4), buf, 0x00FF00, true);
169 can_tick = false;
170 }
171
172 if (settings->show_fps)
173 {
174 update_fps();
175 }
176
177 if (!flipacceltime)
178 {
179 Renderer::getInstance()->flip();
180 }
181 else
182 {
183 flipacceltime--;
184 if (--frameskip < 0)
185 {
186 Renderer::getInstance()->flip();
187 frameskip = 256;
188 }
189 }
190
191 memcpy(lastinputs, inputs, sizeof(lastinputs));
192 }
193 else
194 { // frame is frozen; don't hog CPU
195 SDL_Delay(20);
196 }
197
198 SoundManager::getInstance()->runFade();
199 }
200
AppMinimized(void)201 void AppMinimized(void)
202 {
203 LOG_DEBUG("Game minimized or lost focus--pausing...");
204 NXE::Sound::SoundManager::getInstance()->pause();
205 for (;;)
206 {
207 if (Renderer::getInstance()->isWindowVisible())
208 {
209 break;
210 }
211
212 input_poll();
213 SDL_Delay(20);
214 }
215 NXE::Sound::SoundManager::getInstance()->resume();
216 LOG_DEBUG("Focus regained, resuming play...");
217 }
218
gameloop(void)219 void gameloop(void)
220 {
221 int32_t nexttick = 0;
222
223 game.switchstage.mapno = -1;
224
225 while (game.running && game.switchstage.mapno < 0)
226 {
227 // get time until next tick
228 int32_t curtime = SDL_GetTicks();
229 int32_t timeRemaining = nexttick - curtime;
230
231 if (timeRemaining <= 0 || game.ffwdtime)
232 {
233 run_tick();
234
235 // try to "catch up" if something else on the system bogs us down for a moment.
236 // but if we get really far behind, it's ok to start dropping frames
237 if (game.ffwdtime)
238 game.ffwdtime--;
239
240 nexttick = curtime + GAME_WAIT;
241
242 #if !defined(DEBUG)
243 // pause game if window minimized
244 if (!Renderer::getInstance()->isWindowVisible())
245 {
246 AppMinimized();
247 nexttick = 0;
248 }
249 #endif
250 }
251 else
252 {
253 // don't needlessly hog CPU, but don't sleep for entire
254 // time left, some CPU's/kernels will fall asleep for
255 // too long and cause us to run slower than we should
256 timeRemaining /= 2;
257 if (timeRemaining)
258 SDL_Delay(timeRemaining);
259 }
260 }
261 }
262
InitNewGame(bool with_intro)263 void InitNewGame(bool with_intro)
264 {
265 LOG_DEBUG("= Beginning new game =");
266
267 memset(game.flags, 0, sizeof(game.flags));
268 memset(game.skipflags, 0, sizeof(game.skipflags));
269 textbox.StageSelect.ClearSlots();
270
271 game.quaketime = game.megaquaketime = 0;
272 game.showmapnametime = 0;
273 game.debug.god = 0;
274 game.running = true;
275 game.frozen = false;
276
277 // fully re-init the player object
278 Objects::DestroyAll(true);
279 game.createplayer();
280
281 player->maxHealth = 3;
282 player->hp = player->maxHealth;
283
284 game.switchstage.mapno = STAGE_START_POINT;
285 game.switchstage.playerx = 10;
286 game.switchstage.playery = 8;
287 game.switchstage.eventonentry = (with_intro) ? 200 : 91;
288
289 fade.set_full(FADE_OUT);
290 }
291
main(int argc,char * argv[])292 int main(int argc, char *argv[])
293 {
294 bool error = false;
295 bool freshstart;
296
297 #if defined(UNIX_LIKE)
298 // On platforms where SDL may use Wayland (Linux and BSD), setting the icon from a surface doesn't work and
299 // the request will be ignored. Instead apps submit their app ID using the xdg-shell Wayland protocol and
300 // then the desktop looks up the icon based on this.
301 // SDL (as of 2.0.14) only exposes setting the app ID through the (missleadingly named) environment variable
302 // used below, so we call `setenv` here and hope it picks up the value during initialization of the Wayland
303 // backend. As its name implies this will also set window class on X11, but this will not cause any issues,
304 // and its recomended that that matches the app ID anyways. On other platforms, the env will be silently
305 // ignored.
306 setenv("SDL_VIDEO_X11_WMCLASS", "org.nxengine.nxengine_evo", 0);
307 #endif
308
309 #if defined(__SWITCH__)
310 if (romfsInit() != 0)
311 {
312 std::cerr << "romfsInit() failed" << std::endl;
313 return 1;
314 }
315 #endif
316
317 (void)ResourceManager::getInstance();
318
319 Logger::init(ResourceManager::getInstance()->getPrefPath("debug.log"));
320 // SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
321 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
322 {
323 LOG_CRITICAL("ack, sdl_init failed: {}.", SDL_GetError());
324 return 1;
325 }
326
327 int flags = IMG_INIT_PNG;
328 int initted = IMG_Init(flags);
329 if((initted & flags) != flags) {
330 LOG_CRITICAL("IMG_Init: Failed to init required png support: {}", IMG_GetError());
331 return 1;
332 }
333
334 // start up inputs first thing because settings_load may remap them
335 input_init();
336
337 // load settings, or at least get the defaults,
338 // so we know the initial screen resolution.
339 settings_load();
340
341 if (!Renderer::getInstance()->init(settings->resolution))
342 {
343 fatal("Failed to initialize graphics.");
344 return 1;
345 }
346 Renderer::getInstance()->setFullscreen(settings->fullscreen);
347
348 // if (check_data_exists())
349 // {
350 // return 1;
351 // }
352
353 Renderer::getInstance()->showLoadingScreen();
354
355 if (!SoundManager::getInstance()->init())
356 {
357 fatal("Failed to initialize sound.");
358 return 1;
359 }
360
361 if (trig_init())
362 {
363 fatal("Failed trig module init.");
364 return 1;
365 }
366
367 if (textbox.Init())
368 {
369 fatal("Failed to initialize textboxes.");
370 return 1;
371 }
372 if (Carets::init())
373 {
374 fatal("Failed to initialize carets.");
375 return 1;
376 }
377
378 if (game.init())
379 return 1;
380 if (!game.tsc->Init())
381 {
382 fatal("Failed to initialize script engine.");
383 return 1;
384 }
385 game.setmode(GM_NORMAL);
386 // set null stage just to have something to do while we go to intro
387 game.switchstage.mapno = 0;
388
389 char *profile_name = GetProfileName(settings->last_save_slot);
390 if (settings->skip_intro && file_exists(profile_name))
391 game.switchstage.mapno = LOAD_GAME;
392 else
393 game.setmode(GM_INTRO);
394
395 SDL_free(profile_name);
396
397 // for debug
398 if (game.paused)
399 {
400 game.switchstage.mapno = 0;
401 game.switchstage.eventonentry = 0;
402 }
403
404 game.running = true;
405 freshstart = true;
406
407 LOG_INFO("Entering main loop...");
408
409 while (game.running)
410 {
411 // SSS/SPS persists across stage transitions until explicitly
412 // stopped, or you die & reload. It seems a bit risky to me,
413 // but that's the spec.
414 if (game.switchstage.mapno >= MAPNO_SPECIALS)
415 {
416 NXE::Sound::SoundManager::getInstance()->stopLoopSfx();
417 // StopLoopSounds();
418 }
419
420 // enter next stage, whatever it may be
421 if (game.switchstage.mapno == LOAD_GAME || game.switchstage.mapno == LOAD_GAME_FROM_MENU)
422 {
423 if (game.switchstage.mapno == LOAD_GAME_FROM_MENU)
424 freshstart = true;
425
426 LOG_DEBUG("= Loading game =");
427 if (game_load(settings->last_save_slot))
428 {
429 fatal("savefile error");
430 goto ingame_error;
431 }
432 fade.set_full(FADE_IN);
433 }
434 else if (game.switchstage.mapno == TITLE_SCREEN)
435 {
436 LOG_DEBUG("= Title screen =");
437 game.curmap = TITLE_SCREEN;
438 }
439 else
440 {
441 if (game.switchstage.mapno == NEW_GAME || game.switchstage.mapno == NEW_GAME_FROM_MENU)
442 {
443 bool show_intro = (game.switchstage.mapno == NEW_GAME_FROM_MENU || ResourceManager::getInstance()->isMod());
444 InitNewGame(show_intro);
445 }
446
447 // slide weapon bar on first intro to Start Point
448 if (game.switchstage.mapno == STAGE_START_POINT && game.switchstage.eventonentry == 91)
449 {
450 freshstart = true;
451 }
452
453 // switch maps
454 if (load_stage(game.switchstage.mapno))
455 goto ingame_error;
456
457 player->x = (game.switchstage.playerx * TILE_W) * CSFI;
458 player->y = (game.switchstage.playery * TILE_H) * CSFI;
459 }
460
461 // start the level
462 if (game.initlevel())
463 return 1;
464
465 if (freshstart)
466 weapon_introslide();
467
468 gameloop();
469 game.stageboss.OnMapExit();
470 freshstart = false;
471 }
472
473 shutdown:;
474 game.tsc->Close();
475 game.close();
476 Carets::close();
477
478 input_close();
479 textbox.Deinit();
480 NXE::Sound::SoundManager::getInstance()->shutdown();
481 Renderer::getInstance()->close();
482 #if defined(__SWITCH__)
483 romfsExit();
484 #endif
485 SDL_Quit();
486 return error;
487
488 ingame_error:;
489 LOG_CRITICAL("");
490 LOG_CRITICAL(" ************************************************");
491 LOG_CRITICAL(" * An in-game error occurred. Game shutting down.");
492 LOG_CRITICAL(" ************************************************");
493 error = true;
494 goto shutdown;
495 }
496