1 
2 #include "title.h"
3 
4 #include "../TextBox/TextBox.h"
5 #include "../autogen/sprites.h"
6 #include "../Utils/Logger.h"
7 #include "../graphics/Renderer.h"
8 #include "../input.h"
9 #include "../map.h"
10 #include "../niku.h"
11 #include "../nx.h"
12 #include "../profile.h"
13 #include "../ResourceManager.h"
14 #include "../settings.h"
15 #include "../sound/SoundManager.h"
16 #include "../statusbar.h"
17 using namespace NXE::Graphics;
18 
19 // music and character selections for the different Counter times
20 static struct
21 {
22   uint32_t timetobeat;
23   int sprite;
24   uint8_t frames[4];
25   int songtrack;
26   int backdrop;
27 } titlescreens[] = {
28     {(3 * 3000), SPR_SUE, {2,3,4,5}, 2, 14},     // 3 mins	- Sue & Safety
29     {(4 * 3000), SPR_KING, {4,5,6,7}, 41, 13},   // 4 mins	- King & White
30     {(5 * 3000), SPR_TOROKO, {1,2,3,4}, 40, 12}, // 5 mins	- Toroko & Toroko's Theme
31     {(6 * 3000), SPR_CURLY, {0,1,2,3}, 36, 10},  // 6 mins	- Curly & Running Hell
32     {0xFFFFFFFF, SPR_MYCHAR, {0,1,0,2}, 24, 9}   // default
33 };
34 
35 // artifical fake "loading" delay between selecting an option and it being executed,
36 // because it actually doesn't look good if we respond instantly.
37 #define SELECT_DELAY 30
38 #define SELECT_LOAD_DELAY 20 // delay when leaving the multisave Load dialog
39 #define SELECT_MENU_DELAY 8  // delay from Load to load menu
40 
41 static struct
42 {
43   int sprite;
44   uint8_t* frames;
45   int cursel;
46   int selframe, seltimer;
47   int selchoice, seldelay;
48   int kc_pos;
49   bool in_multiload;
50 
51   uint32_t besttime; // Nikumaru display
52 } title;
53 
54 typedef struct
55 {
56   std::string text;
57   bool enabled;
58 } menuitem;
59 
60 std::vector<menuitem> _menuitems;
61 
draw_title()62 static void draw_title()
63 {
64   // background is dk grey, not pure black
65   Renderer::getInstance()->clearScreen(0x20, 0x20, 0x20);
66   map_draw_backdrop();
67   //	DrawFastLeftLayered();
68 
69   // top logo
70   int tx = (Renderer::getInstance()->screenWidth / 2) - (Renderer::getInstance()->sprites.sprites[SPR_TITLE].w / 2) - 2;
71   Renderer::getInstance()->sprites.drawSprite(tx, 40, SPR_TITLE);
72 
73   // draw menu
74 
75   int cx = (Renderer::getInstance()->screenWidth / 2) + (rtl() ? 32 : -32);
76   int cy = (Renderer::getInstance()->screenHeight / 2) - 8;
77 
78   TextBox::DrawFrame((Renderer::getInstance()->screenWidth / 2) - 64, cy - 16 , 128, 96);
79 
80   for (size_t i = 0; i < _menuitems.size(); i++)
81   {
82     if (_menuitems[i].enabled)
83     {
84       if (rtl())
85       {
86         Renderer::getInstance()->font.draw(cx - 10, cy, _(_menuitems[i].text));
87       }
88       else
89       {
90         Renderer::getInstance()->font.draw(cx + 10, cy, _(_menuitems[i].text));
91       }
92     }
93     else
94     {
95       if (rtl())
96       {
97         Renderer::getInstance()->font.draw(cx - 10, cy, _(_menuitems[i].text), 0x666666);
98       }
99       else
100       {
101         Renderer::getInstance()->font.draw(cx + 10, cy, _(_menuitems[i].text), 0x666666);
102       }
103     }
104 
105     if (i == (size_t)title.cursel)
106     {
107       if (rtl())
108       {
109         Renderer::getInstance()->sprites.drawSprite(cx, cy - 1, title.sprite, title.frames[title.selframe], LEFT);
110       }
111       else
112       {
113         Renderer::getInstance()->sprites.drawSprite(cx - 16, cy - 1, title.sprite, title.frames[title.selframe]);
114       }
115     }
116 
117     cy += 12;
118   }
119 
120   // animate character
121   if (++title.seltimer > 8)
122   {
123     title.seltimer = 0;
124     if (++title.selframe >= 4)
125       title.selframe = 0;
126   }
127 
128   // accreditation
129   cx        = (Renderer::getInstance()->screenWidth / 2) - (Renderer::getInstance()->sprites.sprites[SPR_PIXEL_FOREVER].w / 2);
130   int acc_y = Renderer::getInstance()->screenHeight - 48;
131   Renderer::getInstance()->sprites.drawSprite(cx, acc_y, SPR_PIXEL_FOREVER);
132 
133   // version
134   int wd = Renderer::getInstance()->font.getWidth(NXVERSION);
135   cx     = (Renderer::getInstance()->screenWidth / 2) + (rtl() ? (wd / 2) : -(wd / 2));
136   Renderer::getInstance()->font.draw(cx, acc_y + Renderer::getInstance()->sprites.sprites[SPR_PIXEL_FOREVER].h + 4, NXVERSION, 0xf3e298);
137 
138   // draw Nikumaru display
139   if (title.besttime != 0xffffffff)
140     niku_draw(title.besttime, true);
141 }
142 
143 static int kc_table[] = {UPKEY, UPKEY, DOWNKEY, DOWNKEY, LEFTKEY, RIGHTKEY, LEFTKEY, RIGHTKEY, -1};
144 
run_konami_code()145 void run_konami_code()
146 {
147   if (justpushed(UPKEY) || justpushed(DOWNKEY) || justpushed(LEFTKEY) || justpushed(RIGHTKEY))
148   {
149     if (justpushed(kc_table[title.kc_pos]))
150     {
151       title.kc_pos++;
152       if (kc_table[title.kc_pos] == -1)
153       {
154         NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_MENU_SELECT);
155         game.debug.god = 1;
156         title.kc_pos = 0;
157       }
158     }
159     else
160     {
161       title.kc_pos = 0;
162     }
163   }
164 }
165 
handle_input()166 static void handle_input()
167 {
168   if (justpushed(DOWNKEY))
169   {
170     NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_MENU_MOVE);
171     do
172     {
173       if (++title.cursel >= (int)_menuitems.size())
174         title.cursel = 0;
175     } while (!_menuitems.at(title.cursel).enabled);
176   }
177   else if (justpushed(UPKEY))
178   {
179     NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_MENU_MOVE);
180     do
181     {
182       if (--title.cursel < 0)
183         title.cursel = _menuitems.size()-1;
184     } while (!_menuitems.at(title.cursel).enabled);
185   }
186 
187   if (justpushed(ACCEPT_BUTTON) || justpushed(ENTERKEY))
188   {
189     NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_MENU_SELECT);
190     int choice = title.cursel;
191 
192     // handle case where user selects Load but there is no savefile,
193     // or the last_save_file is deleted.
194     if (title.cursel == 1)
195     {
196       if (!ProfileExists(settings->last_save_slot))
197       {
198         bool foundslot = false;
199         for (int i = 0; i < MAX_SAVE_SLOTS; i++)
200         {
201           if (ProfileExists(i))
202           {
203             LOG_WARN("Last save file {} missing. Defaulting to {} instead.", settings->last_save_slot, i);
204             settings->last_save_slot = i;
205             foundslot                = true;
206           }
207         }
208 
209         // there are no save files. Start a new game instead.
210         if (!foundslot)
211         {
212           LOG_WARN("No save files found. Starting new game instead.");
213           choice = 0;
214         }
215       }
216     }
217 
218     if (choice == 1)// && settings->multisave)
219     {
220       title.selchoice = 10;
221       title.seldelay  = SELECT_MENU_DELAY;
222     }
223     else
224     {
225       title.selchoice = choice;
226       if (choice == 0)
227         title.seldelay = SELECT_DELAY;
228       else
229         title.seldelay = 1;
230       //			music(0);
231     }
232   }
233 
234   run_konami_code();
235 }
236 
selectoption(int index)237 static void selectoption(int index)
238 {
239   switch (index)
240   {
241     case 0: // New
242     {
243       NXE::Sound::SoundManager::getInstance()->music(0);
244 
245       game.switchstage.mapno = NEW_GAME_FROM_MENU;
246       game.setmode(GM_NORMAL);
247     }
248     break;
249 
250     case 1: // Load
251     {
252       NXE::Sound::SoundManager::getInstance()->music(0);
253 
254       game.switchstage.mapno = LOAD_GAME_FROM_MENU;
255       game.setmode(GM_NORMAL);
256     }
257     break;
258 
259     case 2: // Options
260     {
261       //			music(0);
262       game.pause(GP_OPTIONS);
263     }
264     break;
265     case 3: // Mods
266     {
267       game.pause(GP_MODS);
268     }
269     break;
270     case 4: // Quit
271     {
272       NXE::Sound::SoundManager::getInstance()->music(0);
273       game.running = false;
274     }
275     break;
276 
277     case 10: // Load Menu (multisave)
278     {
279       textbox.SetVisible(true);
280       textbox.SaveSelect.SetVisible(true, SS_LOADING);
281       title.in_multiload = true;
282     }
283     break;
284   }
285 }
286 
title_init(int param)287 bool title_init(int param)
288 {
289   memset(&title, 0, sizeof(title));
290   //	game.switchstage.mapno = 0;
291   game.switchstage.mapno        = TITLE_SCREEN;
292   game.switchstage.eventonentry = 0;
293   game.showmapnametime          = 0;
294   textbox.SetVisible(false);
295 
296   title.besttime = niku_load();
297 
298   // select a title screen based on Nikumaru time
299   int t;
300   for (t = 0;; t++)
301   {
302     if (title.besttime < titlescreens[t].timetobeat || titlescreens[t].timetobeat == 0xffffffff)
303     {
304       break;
305     }
306   }
307 
308   title.sprite = titlescreens[t].sprite;
309   title.frames = titlescreens[t].frames;
310   NXE::Sound::SoundManager::getInstance()->music(titlescreens[t].songtrack);
311   map_set_backdrop(titlescreens[t].backdrop);
312   map.scrolltype = BK_FASTLEFT_LAYERS;
313   map.motionpos  = 0;
314 
315   if (AnyProfileExists())
316     title.cursel = 1; // Load Game
317   else
318     title.cursel = 0; // New Game
319 
320   _menuitems.clear();
321   _menuitems.push_back({"New game",true});
322 
323   if (AnyProfileExists())
324     _menuitems.push_back({"Load game",true});
325   else
326     _menuitems.push_back({"Load game",false});
327 
328   _menuitems.push_back({"Options",true});
329 
330   if (ResourceManager::getInstance()->mods().size() > 0 )
331     _menuitems.push_back({"Mods",true});
332   else
333     _menuitems.push_back({"Mods",false});
334 
335   _menuitems.push_back({"Quit",true});
336 
337   return 0;
338 }
339 
title_tick()340 void title_tick()
341 {
342   if (!title.in_multiload)
343   {
344     if (title.seldelay > 0)
345     {
346       Renderer::getInstance()->clearScreen(BLACK);
347 
348       title.seldelay--;
349       if (!title.seldelay)
350         selectoption(title.selchoice);
351 
352       return;
353     }
354 
355     handle_input();
356     draw_title();
357   }
358   else
359   {
360     Renderer::getInstance()->clearScreen(BLACK);
361 
362     if (!textbox.SaveSelect.IsVisible())
363     { // selection was made, and settings.last_save_slot is now set appropriately
364 
365       NXE::Sound::SoundManager::getInstance()->playSfx(NXE::Sound::SFX::SND_MENU_SELECT);
366 
367       textbox.SetVisible(false);
368       title.in_multiload = false;
369       if (!textbox.SaveSelect.Aborted())
370       {
371         title.selchoice = 1;
372         title.seldelay  = SELECT_LOAD_DELAY;
373       }
374     }
375     else
376     {
377       textbox.Tick();
378     }
379   }
380 }
381