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