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