1 // Hyperbolic Rogue -- menus
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3
4 /** \file menus.cpp
5 * \brief generic menus (start menu, main menu, Overview, etc.)
6 */
7
8 // -- overview --
9
10 #include "hyper.h"
11 #define BLACKISH 0x404040
12 #define REDDISH 0x400000
13
14 namespace hr {
15 EX ld whatever[16];
16 EX int whateveri[16];
17
PREC(ld x)18 int PREC(ld x) {
19 ld sh = shiftmul;
20 if(sh > -.2 && sh < .2) x = 10.01;
21 return int(shiftmul * x);
22 }
23
showOverview()24 EX void showOverview() {
25 cmode = sm::ZOOMABLE | sm::OVERVIEW;
26 DEBBI(DF_GRAPH, ("show overview"));
27
28 if(dialog::infix != "")
29 mouseovers = dialog::infix;
30 else {
31 mouseovers = XLAT("world overview");
32 mouseovers += " ";
33 mouseovers += XLAT(" kills: %1/%2", its(tkills()), its(killtypes()));
34 mouseovers += XLAT(" $$$: %1", its(gold()));
35 if(randomPatternsMode) ;
36 else if(landUnlocked(laHell)) {
37 int i1, i2; countHyperstoneQuest(i1, i2);
38 mouseovers += XLAT(" Hyperstone: %1/%2", its(i1), its(i2));
39 }
40 else
41 mouseovers += XLAT(" Hell: %1/%2", its(orbsUnlocked()), its(lands_for_hell()));
42 }
43
44 bool pages = false;
45
46 {
47 dynamicval<int> ds(dual::state, dual::state ? 2 : 0);
48 generateLandList(isLandIngame);
49 }
50
51 bool not_in_game = false;
52
53 if(dialog::infix != "") {
54 auto land_matches = [] (eLand l) {
55 string s = dnameof(l);
56 s += "@";
57 s += dnameof(treasureType(l));
58 s += "@";
59 s += dnameof(nativeOrbType(l));
60 return dialog::hasInfix(s);
61 };
62
63 vector<eLand> filtered;
64 for(eLand l: landlist) if(land_matches(l)) filtered.push_back(l);
65 if(filtered.size())
66 landlist = filtered;
67 else {
68 for(int i=0; i<landtypes; i++) if(land_matches(eLand(i))) filtered.push_back(eLand(i));
69 if(filtered.size()) landlist = filtered, not_in_game = true;
70 }
71 }
72
73 int nl = isize(landlist), nlm;
74
75 int lstart = 0;
76
77 if(nl > 30) {
78 pages = true;
79 lstart += dialog::handlePage(nl, nlm, (nl+1)/2);
80 }
81 else nlm = nl;
82
83 int vf = min((vid.yres-64-vid.fsize*2) / nlm + (not_in_game ? 1 : 0), vid.xres/40);
84
85 eLand curland = getLandForList(cwt.at);
86
87 getcstat = '0';
88
89 if(not_in_game) {
90 int i0 = 56 + vid.fsize + nl * vf;
91 displayfrZ(64, i0, 1, vf-4, "(these lands are not in game)", forecolor, 0);
92 }
93
94 for(int i=0; i<nl; i++) {
95 eLand l = landlist[lstart + i];
96 int xr = vid.xres / 64;
97 int i0 = 56 + vid.fsize + i * vf;
98 color_t col;
99 if(landUnlocked(l)) col = linf[l].color; else col = 0x404040;
100 if(l == curland)
101 displayfrZ(1, i0, 1, vf-4, "*", forecolor, 0);
102 if(displayfrZH(xr*1, i0, 1, vf-4, XLAT1(linf[l].name), col, 0))
103 getcstat = 1000 + l;
104 eItem it = treasureType(l);
105 int lv = items[it] * landMultiplier(l);
106 if(lv >= 25) col = 0xFFD500;
107 else if(lv && it == itSavedPrincess) col = 0xFFD500;
108 else if(lv >= 10) col = 0x00D500;
109 else if(items[it]) col = 0xC0C0C0;
110 else col = BLACKISH;
111 int c8 = (vf+2)/3;
112 if(displayfrZH(xr*24-c8*6, i0, 1, vf-4, (required_for_hyperstones(it) ? "" : "*") + its(items[it]), col, 16))
113 getcstat = 2000+it;
114 if(!cheater)
115 if(displayfrZH(xr*24, i0, 1, vf-4, its(hiitems[modecode()][it]), col, 16))
116 getcstat = 2000+it;
117 if(items[it]) col = iinf[it].color; else col = BLACKISH;
118 if(displayfrZH(xr*24+c8*4, i0, 1, vf-4, s0 + iinf[it].glyph, col, 16))
119 getcstat = 2000+it;
120 if(displayfrZH(xr*24+c8*5, i0, 1, vf-4, XLAT1(iinf[it].name), col, 0))
121 getcstat = 2000+it;
122 eItem io = nativeOrbType(l);
123 if(io == itShard) {
124 if(items[it] >= 10) col = winf[waMirror].color; else col = BLACKISH;
125 if(displayfrZH(xr*46, i0, 1, vf-4, XLAT1(winf[waMirror].name), col, 0))
126 getcstat = 3000+waMirror;
127 if(getcstat == 3000+waMirror) {
128 string olrdesc = olrDescriptions[getOLR(io, cwt.at->land)];
129 mouseovers = XLAT(olrdesc, cwt.at->land, it, treasureTypeUnlock(curland, io));
130 }
131 }
132 else if(io) {
133 if(lv >= 25) col = 0xFFD500;
134 else if(lv >= 10) col = 0xC0C0C0;
135 else col = BLACKISH;
136 if(displayfrZH(xr*46-c8*4, i0, 1, vf-4, its(items[io]), col, 16))
137 getcstat = 2000+io;
138 if(lv >= 10) col = iinf[io].color; else col = BLACKISH;
139 if(displayfrZH(xr*46-c8, i0, 1, vf-4, s0 + iinf[io].glyph, col, 16))
140 getcstat = 2000+io;
141 if(displayfrZH(xr*46, i0, 1, vf-4, XLAT1(iinf[io].name), col, 0))
142 getcstat = 2000+io;
143 if(getcstat == 2000+io) {
144 string olrdesc = olrDescriptions[getOLR(io, curland)];
145 mouseovers = XLAT(olrdesc, curland, it, treasureTypeUnlock(curland, io));
146 }
147 }
148 }
149
150 dialog::displayPageButtons(3, pages);
151
152 keyhandler = [] (int sym, int uni) {
153 int umod = uni % 1000;
154 int udiv = uni / 1000;
155 if(udiv == 1 && umod < landtypes) {
156 const eLand l = eLand(umod);
157 gotoHelp(""); gotoHelpFor(l);
158 if(cheater) {
159 help_extension hex;
160 hex.key = 't';
161 hex.text = XLAT("teleport");
162 hex.action = [l] () {
163 cheater++;
164 bool princ = (l == laPrincessQuest);
165 if(princ) {
166 if(kills[moVizier] == 0) kills[moVizier] = 1;
167 princess::forceMouse = true;
168 princess::gotoPrincess = true;
169 cheatMoveTo(laPalace);
170 }
171 else cheatMoveTo(l);
172 canmove = true;
173 if(princ) fullcenter();
174 popScreen();
175 popScreen();
176 };
177 help_extensions.push_back(hex);
178 }
179 }
180 else if(udiv == 2 && umod < ittypes) {
181 gotoHelp(generateHelpForItem(eItem(umod)));
182 if(cheater) {
183 dialog::helpToEdit(items[umod], 0, 200, 10, 10);
184 dialog::reaction = [] () {
185 if(hardcore) canmove = true;
186 else checkmove();
187 cheater++;
188 };
189 dialog::bound_low(0);
190 }
191 }
192 else if(udiv == 3 && umod < walltypes) gotoHelp(generateHelpForWall(eWall(umod)));
193 else if(uni == SDLK_F1) gotoHelp(
194 "This displays all lands available in the game. "
195 "Bonus lands (available only in separate challenges) "
196 "are not included. Lands written in dark have to be "
197 "unlocked, and lands in dark red are unavailable "
198 "because of using special options. Click on any "
199 "land or item to get information about it. Hover over "
200 "an Orb to know its relation to the current land. "
201 "Cheaters can click to move between lands, and use the "
202 "mousewheel to gain or lose treasures and orbs quickly (Ctrl = precise, Shift = reverse)."
203 );
204 else if(dialog::handlePageButtons(uni)) ;
205 else if(dialog::editInfix(uni)) ;
206 else if(doexiton(sym, uni)) popScreen();
207 };
208 }
209
210 // -- main menu --
211
showMainMenu()212 EX void showMainMenu() {
213 cancel(); cancel = noaction;
214 gamescreen(2);
215
216 getcstat = ' ';
217
218 dialog::init(XLAT("HyperRogue %1", VER), 0xC00000, 200, 100);
219
220 dialog::addItem(XLAT("settings"), 's');
221 dialog::add_action_push(showSettings);
222 dialog::addItem(XLAT("special modes"), 'm');
223
224 #if CAP_SAVE
225 dialog::addItem(XLAT("local highscores"), 't');
226 #endif
227 dialog::addHelp();
228 if(cheater)
229 dialog::addItem(XLAT("cheats"), 'c');
230 else dialog::addBreak(100);
231 dialog::addItem(XLAT("restart game"), 'r');
232
233 dialog::addItem(inSpecialMode() ? XLAT("reset special modes") : XLAT("back to the start menu"), 'R');
234
235 string q;
236 #if ISMOBILE
237 dialog::addItem(XLAT("visit the website"), 'q');
238 #else
239 q = quitsaves() ? XLAT("save the game") : XLAT("quit the game");
240 dialog::addItem(q, 'q');
241 #endif
242
243 if(canmove)
244 q = XLAT("review your quest");
245 else
246 q = XLAT("game over screen");
247 dialog::addItem(q, SDLK_ESCAPE);
248 dialog::addItem(get_o_key().first, 'o');
249
250 if(inv::on)
251 dialog::addItem(XLAT("inventory"), 'i');
252
253 #if ISMOBILE
254 #if CAP_ACHIEVE
255 dialog::addItem(XLAT("leaderboards/achievements"), '3');
256 #endif
257 #endif
258
259 #if CAP_ANDROIDSHARE
260 dialog::addItem("SHARE", 's'-96);
261 #endif
262
263 if(!canmove) q = XLAT("review the scene");
264 else if(turncount > 0) q = XLAT("continue game");
265 else q = XLAT("play the game!");
266
267 dialog::addItem(q, ' ');
268 dialog::display();
269
270 keyhandler = [] (int sym, int uni) {
271 dialog::handleNavigation(sym, uni);
272 if(sym == SDLK_F1 || uni == 'h') gotoHelp("@");
273 else if(uni == 'c' && cheater) pushScreen(showCheatMenu);
274 else if(uni == 'm') pushScreen(showChangeMode);
275 else if(uni == 'R') dialog::do_if_confirmed([] {
276 #if CAP_STARTANIM
277 startanims::pick();
278 #endif
279 popScreenAll(), pushScreen(showStartMenu);
280 });
281 #if CAP_SAVE
282 else if(uni == 't') scores::load();
283 #endif
284 else if(uni == 'r' || sym == SDLK_F5) dialog::do_if_confirmed([] {
285 restart_game();
286 });
287 else if(uni == 'q' || sym == SDLK_F10) {
288 if(needConfirmation()) dialog::do_if_confirmed([] {
289 #if ISMOBILE
290 extern void openURL();
291 openURL();
292 #else
293 quitmainloop = true;
294 #endif
295 });
296 else quitmainloop = true;
297 }
298 else if(uni == 'o') {
299 clearMessages();
300 get_o_key().second();
301 }
302 #if CAP_INV
303 else if(uni == 'i') {
304 clearMessages();
305 pushScreen(inv::show);
306 }
307 #endif
308 else if(sym == SDLK_ESCAPE)
309 showMissionScreen();
310 #if ISMOBILE
311 #ifdef HAVE_ACHIEVEMENTS
312 else if(NUMBERKEY == '3') {
313 achievement_final(false);
314 pushScreen(leader::showMenu);
315 }
316 #endif
317 #endif
318 else if(doexiton(sym, uni)) {
319 popScreenAll();
320 msgs.clear();
321 }
322 };
323 }
324
325 // -- display modes --
326
editScale()327 EX void editScale() {
328 dialog::editNumber(vpconf.scale, .001, 1000, .1, 1, XLAT("scale factor"),
329 XLAT("Scale the displayed model."));
330 dialog::scaleSinh();
331 }
332
333 EX const char *wdmodes[7] = {"ASCII", "black", "plain", "Escher", "plain/3D", "Escher/3D", "ASCII/3D"};
334 EX const char *mdmodes[6] = {"ASCII", "items only", "items and monsters", "3D", "?", "?"};
335 EX const char *hlmodes[3] = {"press Alt", "highlight", "super-highlight"};
336
showGraphQuickKeys()337 EX void showGraphQuickKeys() {
338 cmode = sm::SIDE | sm::MAYDARK;
339 gamescreen(0);
340
341 dialog::init(XLAT("quick options"));
342
343 dialog::addItem("quick projection", '1');
344
345 dialog::addSelItem(XLAT("wall display mode"), XLAT(wdmodes[vid.wallmode]), '5');
346
347 dialog::addSelItem(XLAT("monster display mode"), XLAT(mdmodes[vid.monmode]), '8');
348
349 dialog::addSelItem(XLAT("highlight stuff"), XLAT(hlmodes[vid.highlightmode]), 508);
350
351 dialog::addBoolItem(XLAT("draw the grid"), (vid.grid), '6');
352 dialog::addBoolItem(XLAT("mark heptagons"), (vid.darkhepta), '7');
353
354 dialog::addBreak(50);
355 dialog::addInfo(XLAT("Hint: these keys usually work during the game"));
356 dialog::addInfo(XLAT("also hold Alt during the game to toggle high contrast"));
357
358 dialog::addBreak(50);
359 dialog::addBack();
360 dialog::display();
361
362 keyhandler = [] (int sym, int uni) {
363 dialog::handleNavigation(sym, uni);
364
365 if(gmodekeys(sym, uni)) ;
366 else if(doexiton(sym, uni)) popScreen();
367 };
368 }
369
enable_cheat()370 EX void enable_cheat() {
371 if(tactic::on && gold()) {
372 addMessage(XLAT("Not available in the pure tactics mode!"));
373 }
374 else if(daily::on) {
375 addMessage(XLAT("Not available in the daily challenge!"));
376 }
377 else if(!cheater) dialog::cheat_if_confirmed([] {
378 cheater++;
379 addMessage(XLAT("You activate your demonic powers!"));
380 #if !ISMOBILE
381 addMessage(XLAT("Shift+F, Shift+O, Shift+T, Shift+L, Shift+U, etc."));
382 #endif
383 popScreen();
384 });
385 else {
386 popScreen();
387 specialland = firstland = princess::challenge ? laPalace : laIce;
388 restart_game();
389 }
390 }
391
392 // -- game modes --
393
switchHardcore()394 EX void switchHardcore() {
395 if(hardcore && !canmove) {
396 restart_game();
397 hardcore = false;
398 }
399 else if(hardcore && canmove) { hardcore = false; }
400 else { hardcore = true; canmove = true; hardcoreAt = turncount; }
401 if(hardcore)
402 addMessage(XLAT("One wrong move and it is game over!"));
403 else
404 addMessage(XLAT("Not so hardcore?"));
405 if(pureHardcore()) popScreenAll();
406 }
407
switch_casual()408 EX void switch_casual() {
409 if(savecount > 0) {
410 dialog::push_confirm_dialog([] {
411 restart_game();
412 casual = !casual;
413 }, XLAT("Switching casual allowed only before saving the game. Do you want to restart?"));
414 return;
415 }
416 else
417 casual = !casual;
418 if(casual) {
419 addMessage(XLAT("You are in the casual mode! Achievements are disabled."));
420 addMessage(XLAT("Collect an Orb of Safety to save a checkpoint."));
421 }
422 popScreenAll();
423 }
424
showCreative()425 EX void showCreative() {
426 cmode = sm::SIDE | sm::MAYDARK;
427 gamescreen(3);
428 dialog::init(XLAT("creative mode"));
429
430 #if CAP_EDIT
431 dialog::addItem(XLAT("map editor"), 'm');
432 dialog::add_action([] {
433 dialog::cheat_if_confirmed([] {
434 cheater++;
435 pushScreen(mapeditor::showMapEditor);
436 lastexplore = turncount;
437 addMessage(XLAT("You activate your terraforming powers!"));
438 });
439 });
440 #endif
441
442 #if CAP_EDIT
443 dialog::addItem(XLAT("shape editor"), 'g');
444 dialog::add_action([] {
445 mapeditor::drawing_tool = false;
446 mapeditor::intexture = false;
447 pushScreen(mapeditor::showDrawEditor);
448 mapeditor::initdraw(cwt.at);
449 });
450 #endif
451
452 #if CAP_EDIT
453 dialog::addItem(XLAT("drawing tool"), 'd');
454 dialog::add_action([] {
455 dialog::cheat_if_confirmed([] {
456 mapeditor::drawing_tool = true;
457 mapeditor::intexture = false;
458 pushScreen(mapeditor::showDrawEditor);
459 mapeditor::initdraw(cwt.at);
460 });
461 });
462 #endif
463
464 // display modes
465 #if CAP_MODEL
466 if(GDIM == 2) {
467 dialog::addItem(XLAT("paper model creator"), 'n');
468 dialog::add_action([] { netgen::run(); });
469 }
470 #endif
471
472 #if CAP_SHOT
473 dialog::addItem(XLAT("screenshots"), 's');
474 dialog::add_action_push(shot::menu);
475 #endif
476
477 #if CAP_ANIMATIONS
478 dialog::addBoolItem(XLAT("animations"), anims::any_on(), 'a');
479 dialog::add_action_push(anims::show);
480 #endif
481
482 dialog::addBoolItem(XLAT("history mode"), history::on || history::includeHistory, 'h');
483 dialog::add_action_push(history::history_menu);
484
485 #if CAP_TEXTURE
486 if(GDIM == 2) {
487 dialog::addBoolItem(XLAT("texture mode"), texture::config.tstate == texture::tsActive, 't');
488 dialog::add_action_push(texture::showMenu);
489 }
490 #endif
491
492 dialog::addBoolItem(XLAT("cheat mode"), (cheater), 'c');
493 dialog::add_action(enable_cheat);
494
495 // dialog::addBoolItem(XLAT("expansion"), viewdists, 'x');
496
497 dialog::addBreak(50);
498 dialog::addBack();
499 dialog::display();
500 }
501
show_chaos()502 EX void show_chaos() {
503 gamescreen(3);
504 dialog::init(XLAT("land structure"));
505 chaosUnlocked = chaosUnlocked || autocheat;
506
507 dialog::addHelp(
508 "In the Chaos mode, lands change very often, and "
509 "there are no walls between them. "
510 "Some lands are incompatible with this."
511 "\n\nYou need to reach Crossroads IV to unlock the Chaos mode."
512 );
513
514 dialog::addBreak(100);
515
516 char key = 'a';
517 for(int i=0; i<lsGUARD; i++) {
518 dynamicval<eLandStructure> dls(land_structure);
519 auto li = eLandStructure(i);
520 land_structure = li;
521 fix_land_structure_choice();
522 if(ls::any_chaos() && !chaosUnlocked) continue;
523 if(li == lsNoWalls && geometry == gNormal && !chaosUnlocked) continue;
524 if(land_structure == i) {
525 dialog::addBoolItem(land_structure_name(false), land_structure == dls.backup, key + i);
526 dialog::add_action(dual::mayboth([li] {
527 dialog::do_if_confirmed([li] {
528 stop_game();
529 land_structure = li;
530 start_game();
531 });
532 }));
533 }
534 }
535
536 dialog::addBreak(100);
537 dialog::addSelItem(XLAT("land"), XLAT1(linf[specialland].name), 'l');
538 dialog::add_action(activate_ge_land_selection);
539
540 dialog::addBreak(100);
541 if(ineligible_starting_land)
542 dialog::addInfo("this starting land is not eligible for achievements");
543 else if(land_structure == lsNiceWalls)
544 dialog::addInfo("eligible for most achievements");
545 else if(land_structure == lsChaos)
546 dialog::addInfo("eligible for Chaos mode achievements");
547 else if(land_structure == lsSingle)
548 dialog::addInfo("eligible for special achievements");
549 else
550 dialog::addInfo("not eligible for achievements");
551 if(cheater) dialog::addInfo("(but the cheat mode is on)");
552 if(casual) dialog::addInfo("(but the casual mode is on)");
553
554 dialog::addBreak(100);
555 dialog::addBack();
556 dialog::display();
557 }
558
mode_higlights()559 EX void mode_higlights() {
560 gamescreen(3);
561 dialog::init(XLAT("highlights & achievements"));
562
563 dialog::addBigItem(XLATN("Space Rocks"), 'r');
564 dialog::add_action(dialog::add_confirmation([] {
565 popScreenAll();
566 stop_game();
567 resetModes();
568 specialland = laAsteroids;
569 set_geometry(gKleinQuartic);
570 set_variation(eVariation::bitruncated);
571 land_structure = lsSingle;
572 shmup::on = true;
573 start_game();
574 }));
575 dialog::addInfo(XLAT("classic game except hyperbolic"));
576 dialog::extend();
577
578 #if CAP_RACING && MAXMDIM >= 4
579 dialog::addBigItem(XLAT("Racing in Thurston geometries"), 't'-96);
580 dialog::add_action(dialog::add_confirmation(racing::start_thurston));
581 dialog::addInfo(XLAT("race through a maze in exotic 3D geometry!"));
582 dialog::extend();
583 #endif
584
585 dialog::addBigItem(XLAT1("Halloween"), 'Z');
586 dialog::add_action(dialog::add_confirmation(halloween::start_all));
587 dialog::addInfo(XLAT("Halloween mini-game"));
588 dialog::extend();
589
590 dialog::addBigItem(XLAT1("heptagonal mode"), 'H');
591 dialog::add_action(dialog::add_confirmation([] {
592 popScreenAll();
593 resetModes('7');
594 land_structure = lsNiceWalls;
595 start_game();
596 clearMessages();
597 welcomeMessage();
598 }));
599 dialog::addInfo(XLAT("can you find the Heptagonal Grail?"));
600 dialog::extend();
601
602 dialog::addBreak(100);
603 dialog::addBigItem(XLAT1("other achievements:"), 0);
604 dialog::addItem(XLAT("General Euclid"), 'e');
605 dialog::add_action(dialog::add_confirmation([] {
606 popScreenAll();
607 resetModes();
608 set_geometry(gEuclid);
609 firstland = specialland = laMirrorOld;
610 land_structure = lsSingle;
611 start_game();
612 clearMessages();
613 welcomeMessage();
614 }));
615
616 dialog::addItem(XLAT("Worm of the World"), 'w');
617 dialog::add_action(dialog::add_confirmation([] {
618 popScreenAll();
619 resetModes();
620 set_geometry(gZebraQuotient);
621 firstland = specialland = laDesert;
622 land_structure = lsSingle;
623 start_game();
624 clearMessages();
625 welcomeMessage();
626 }));
627
628 dialog::addItem(XLAT("Lovász Conjecture"), 'L');
629 dialog::add_action(dialog::add_confirmation([] {
630 popScreenAll();
631 resetModes();
632 set_geometry(gKleinQuartic);
633 gp::param = gp::loc(1, 1);
634 set_variation(eVariation::untruncated);
635
636 firstland = specialland = laMotion;
637 land_structure = lsSingle;
638 start_game();
639 clearMessages();
640 welcomeMessage();
641 }));
642
643 #if CAP_CRYSTAL
644 if(hiitemsMax(itHolyGrail) || cheater || autocheat) {
645 dialog::addItem(XLAT("Knight of the 16-Cell Table"), '1');
646 dialog::add_action(dialog::add_confirmation([] {
647 popScreenAll();
648 resetModes();
649 crystal::set_crystal(8);
650 firstland = specialland = laCamelot;
651 land_structure = lsSingle;
652 start_game();
653 clearMessages();
654 welcomeMessage();
655 pushScreen(crystal::crystal_knight_help);
656 }));
657
658 dialog::addItem(XLAT("Knight of the 3-Spherical Table"), '3');
659 dialog::add_action(dialog::add_confirmation([] {
660 popScreenAll();
661 resetModes();
662 crystal::set_crystal(8);
663 set_variation(eVariation::bitruncated);
664 set_variation(eVariation::bitruncated);
665 land_structure = lsSingle;
666 firstland = specialland = laCamelot;
667 start_game();
668 clearMessages();
669 welcomeMessage();
670 pushScreen(crystal::crystal_knight_help);
671 }));
672 }
673 else {
674 dialog::addItem("(locked until you find a Holy Grail)", 0);
675 }
676 #endif
677
678 #if MAXMDIM >= 4
679 dialog::addBreak(100);
680 dialog::addBigItem(XLAT1("some cool visualizations"), 0);
681 dialog::addItem(XLAT("Emerald Mine in {5,3,4}"), '5');
682 dialog::add_action(dialog::add_confirmation([] {
683 popScreenAll();
684 resetModes();
685 specialland = laEmerald;
686 land_structure = lsSingle;
687 set_geometry(gSpace534);
688 check_cgi();
689 cgi.require_basics();
690 cgi.require_shapes();
691 fieldpattern::field_from_current();
692 set_geometry(gFieldQuotient);
693 int p = 2;
694 for(;; p++) { currfp.Prime = p; currfp.force_hash = 0x72414D0C; if(!currfp.solve()) break; }
695 start_game();
696 clearMessages();
697 welcomeMessage();
698 }));
699 #endif
700
701 dialog::addBreak(100);
702 dialog::addBack();
703 dialog::display();
704 }
705
default_land_structure()706 EX eLandStructure default_land_structure() {
707 if(bounded) return lsSingle;
708 if(tactic::on || princess::challenge) return lsSingle;
709 if(yendor::on) return yendor::get_land_structure();
710 if(specialland == laCanvas) return lsSingle;
711 if(nice_walls_available()) return lsNiceWalls;
712 return lsNoWalls;
713 }
714
menuitem_land_structure(char key)715 EX void menuitem_land_structure(char key) {
716
717 if(default_land_structure() == land_structure && !ineligible_starting_land)
718 dialog::addBoolItem(XLAT("land structure"), false, key);
719 else
720 dialog::addSelItem(XLAT("land structure"), land_structure_name(true), key);
721 dialog::add_action_push(show_chaos);
722 }
723
showChangeMode()724 EX void showChangeMode() {
725 gamescreen(3);
726 dialog::init(XLAT("special modes"));
727 // gameplay modes
728
729 #if CAP_TOUR
730 dialog::addBoolItem(XLAT("guided tour"), tour::on, 'T');
731 dialog::add_action_confirmed(tour::start);
732 #endif
733
734 dialog::addBoolItem(XLAT("creative mode"), (false), 'c');
735 dialog::add_action_push(showCreative);
736
737 dialog::addBoolItem(XLAT("experiment with geometry"), geometry || CHANGED_VARIATION || viewdists, 'e');
738 dialog::add_action(runGeometryExperiments);
739
740 dialog::addBreak(100);
741
742 dialog::addBoolItem(XLAT(SHMUPTITLE), shmup::on, 's');
743 dialog::add_action_confirmed(shmup::switch_shmup);
744
745 dialog::addBoolItem(XLAT("multiplayer"), multi::players > 1, 'm');
746 dialog::add_action_push(multi::showConfigureMultiplayer);
747
748 #if CAP_SAVE
749 dialog::addSelItem(XLAT("casual mode"), ONOFF(casual), 'C');
750 dialog::add_action(switch_casual);
751 #endif
752
753 if(!shmup::on) {
754 dialog::addSelItem(XLAT("hardcore mode"),
755 hardcore && !pureHardcore() ? XLAT("PARTIAL") : ONOFF(hardcore), 'h');
756 dialog::add_action(switchHardcore);
757 }
758 if(getcstat == 'h')
759 mouseovers = XLAT("One wrong move and it is game over!");
760
761 multi::cpid = 0;
762 menuitem_land_structure('l');
763
764 dialog::addBoolItem(XLAT("puzzle/exploration mode"), peace::on, 'p');
765 dialog::add_action_push(peace::showMenu);
766
767 dialog::addBoolItem(XLAT("Orb Strategy mode"), (inv::on), 'i');
768 dialog::add_action_confirmed([] { restart_game(rg::inv); });
769
770 dialog::addBoolItem(XLAT("pure tactics mode"), (tactic::on), 't');
771 dialog::add_action(tactic::start);
772
773 dialog::addBoolItem(XLAT("Yendor Challenge"), (yendor::on), 'y');
774 dialog::add_action([] {
775 clearMessages();
776 if(yendor::everwon || autocheat)
777 pushScreen(yendor::showMenu);
778 else gotoHelp(yendor::chelp);
779 });
780
781 dialog::addBoolItem(XLAT("%1 Challenge", moPrincess), (princess::challenge), 'P');
782 dialog::add_action_confirmed([] {
783 if(!princess::everSaved)
784 addMessage(XLAT("Save %the1 first to unlock this challenge!", moPrincess));
785 else restart_game(rg::princess);
786 });
787
788 dialog::addBoolItem(XLAT("random pattern mode"), (randomPatternsMode), 'r');
789 dialog::add_action_confirmed([] {
790 stop_game();
791 firstland = laIce;
792 restart_game(rg::randpattern);
793 });
794
795 #if CAP_RACING
796 dialog::addBoolItem(XLAT("racing mode"), racing::on, 'R');
797 dialog::add_action(racing::configure_race);
798 #endif
799 #if CAP_ARCM && !ISWEB
800 if(multi::players == 1) {
801 dialog::addBoolItem(XLAT("dual geometry mode"), dual::state, 'D');
802 dialog::add_action_confirmed([] { restart_game(rg::dualmode); });
803 }
804 if(dual::state) {
805 dialog::addBoolItem(XLAT("dual geometry puzzle"), dpgen::in, 'G');
806 dialog::add_action_confirmed([] { pushScreen(dpgen::show_menu); });
807 }
808 #endif
809 #if CAP_DAILY
810 dialog::addBoolItem(XLAT("Strange Challenge"), daily::on, 'z');
811 dialog::add_action_push(daily::showMenu);
812 #endif
813
814 dialog::addBreak(50);
815
816 dialog::addItem(XLAT("highlights & achievements"), 'h');
817 dialog::add_action_push(mode_higlights);
818
819 dialog::addBack();
820 dialog::display();
821 }
822
823 EX bool showstartmenu;
824
showHalloween()825 EX bool showHalloween() {
826 time_t t = time(NULL);
827 struct tm tm = *localtime(&t);
828 int month = tm.tm_mon + 1;
829 int day = tm.tm_mday;
830 if(month == 10 && day >= 24) return true;
831 if(month == 11 && day <= 7) return true;
832 return false;
833 }
834
835 int daily_mode;
836
announce_random()837 void announce_random() {
838 dialog::addBreak(100);
839 dialog::addTitle("(random option)", 0x808080, 50);
840 }
841
announce_nothing()842 void announce_nothing() {
843 dialog::addBreak(100);
844 dialog::addTitle("", 0x808080, 50);
845 }
846
announce_seasonal()847 void announce_seasonal() {
848 dialog::addBreak(100);
849 dialog::addTitle("(seasonal option)", 0x808080, 50);
850 }
851
showStartMenu()852 EX void showStartMenu() {
853 if(!daily_mode) {
854 daily_mode = hrand(10) + 1;
855 if(showHalloween())
856 daily_mode = 20;
857 }
858
859 getcstat = ' ';
860
861 #if CAP_STARTANIM
862 startanims::display();
863 #endif
864
865 dialog::init();
866
867 dialog::addInfo(XLAT("Welcome to HyperRogue!"));
868 dialog::addBreak(100);
869
870 dialog::addBigItem(XLAT("HyperRogue classic"), 'c');
871 dialog::addInfo(XLAT("explore the world, collect treasures"));
872 dialog::addInfo(XLAT("do not get checkmated"));
873
874 #if CAP_INV
875 dialog::addBreak(100);
876 dialog::addBigItem(XLAT("Orb Strategy mode"), 'i');
877 dialog::addInfo(XLAT("use your Orbs in tough situations"));
878 #endif
879
880 #if CAP_TOUR
881 dialog::addBreak(100);
882 dialog::addBigItem(XLAT("guided tour"), 't');
883 dialog::addInfo(XLAT("learn about hyperbolic geometry!"));
884 #endif
885
886 if(have_current_settings()) {
887 dialog::addBreak(100);
888 dialog::addBigItem(XLAT1("use current/saved settings"), SDLK_ESCAPE);
889 }
890
891 if(have_current_graph_settings()) {
892 dialog::addBreak(100);
893 dialog::addBigItem(XLAT1("reset the graphics settings"), 'r');
894 dialog::add_action([] () { reset_graph_settings(); });
895 }
896
897 dialog::addBreak(100);
898 dialog::addBigItem(XLAT("main menu"), 'm');
899 dialog::addInfo(XLAT("more options"));
900
901 switch(daily_mode) {
902 case 1:
903 #if CAP_SHMUP_GOOD
904 announce_random();
905 dialog::addBigItem(XLAT("shoot'em up mode"), 's');
906 dialog::addInfo(XLAT("continuous spacetime"));
907 #if CAP_ACHIEVE
908 dialog::addInfo(XLAT("(most achievements are not available)"));
909 #endif
910 #endif
911 break;
912
913 case 2:
914 announce_random();
915 dialog::addBigItem(XLAT("heptagonal mode"), '7');
916 dialog::addInfo(XLAT("more curvature"));
917 dialog::addInfo(XLAT("(most achievements are not available)"));
918 break;
919
920 case 3:
921 announce_random();
922 dialog::addBigItem(XLAT("experiment with geometry"), 'g');
923 dialog::addInfo(XLAT("(most achievements are not available)"));
924 break;
925
926 case 4:
927 if(chaosUnlocked) {
928 announce_random();
929 dialog::addBigItem(XLAT("Chaos mode"), 'C');
930 dialog::addInfo(XLAT("(most achievements are not available)"));
931 }
932 break;
933
934 #if CAP_RUG
935 case 5:
936 announce_random();
937 dialog::addBigItem(XLAT("hypersian rug mode"), 'M');
938 dialog::addInfo(XLAT("see the true form"));
939 break;
940 #endif
941
942 #if CAP_TEXTURE && CAP_EDIT
943 case 6:
944 announce_random();
945 dialog::addBigItem(XLAT("texture mode"), 'T');
946 dialog::addInfo(XLAT("paint pictures"));
947 break;
948 #endif
949
950 #if CAP_DAILY
951 case 7:
952 announce_random();
953 dialog::addBigItem(XLAT("Strange Challenge"), 'z');
954 dialog::addInfo(XLAT("compete with other players on random lands in random geometries"));
955 break;
956 #endif
957
958 #if CAP_RACING
959 case 8:
960 announce_random();
961 dialog::addBigItem(XLAT("Racing"), 'r'-96);
962 dialog::addInfo(XLAT("how fast can you reach the end?"));
963 break;
964
965 case 9:
966 announce_random();
967 dialog::addBigItem(XLAT("Racing in Thurston geometries"), 't'-96);
968 dialog::addInfo(XLAT("race through a maze in exotic 3D geometry!"));
969 break;
970 #endif
971
972 case 20:
973 announce_seasonal();
974 dialog::addBigItem(XLAT1("Halloween"), 'Z');
975 dialog::addInfo(XLAT("Halloween mini-game"));
976 break;
977
978 default:
979 announce_nothing();
980 dialog::addBigItem("", 0);
981 dialog::addInfo("");
982 break;
983 }
984
985 dialog::display();
986 clearMessages();
987
988 timerstart = time(NULL);
989
990 /*
991 initquickqueue();
992 int siz = min(vid.xres, vid.yres) / 8;
993 drawMonsterType(moPrincess, NULL, atscreenpos(siz,siz,siz) * spin(-M_PI/4), 0, 0);
994 drawMonsterType(moKnight, NULL, atscreenpos(vid.xres-siz,siz,siz) * spin(-3*M_PI/4), 0, 0);
995 drawItemType(itOrbYendor, NULL, atscreenpos(siz,vid.yres-siz,siz) * spin(M_PI/4), iinf[itOrbYendor].color, 0, false);
996 drawItemType(itKey, NULL, atscreenpos(siz,vid.yres-siz,siz) * spin(M_PI/4), iinf[itKey].color, 0, false);
997 drawItemType(itHyperstone, NULL, atscreenpos(vid.xres-siz,vid.yres-siz,siz) * spin(3*M_PI/4), iinf[itHyperstone].color, 0, false);
998 quickqueue();
999 */
1000
1001 keyhandler = [] (int sym, int uni) {
1002 dialog::handleNavigation(sym, uni);
1003 if(uni == 'o') uni = 'i';
1004 #if CAP_STARTANIM
1005 else if(uni == startanims::EXPLORE_START_ANIMATION)
1006 startanims::explore();
1007 #endif
1008 #if CAP_RUG
1009 else if(uni == 'M') {
1010 rug::init();
1011 popScreenAll();
1012 resetModes('c');
1013 clearMessages();
1014 welcomeMessage();
1015 vid.wallmode = 3;
1016 vid.monmode = 2;
1017 rug::model_distance *= 2;
1018 rug::init();
1019 }
1020 #endif
1021 #if CAP_TEXTURE && CAP_EDIT
1022 else if(uni == 'T') {
1023 popScreenAll();
1024 pushScreen(texture::showMenu);
1025 resetModes('c');
1026 stop_game();
1027 enable_canvas();
1028 cheater = true;
1029 patterns::canvasback = 0xFFFFFF;
1030 mapeditor::drawplayer = false;
1031 start_game();
1032 clearMessages();
1033 welcomeMessage();
1034 calcparam();
1035 drawthemap();
1036 texture::start_editor();
1037 }
1038 #endif
1039 else if(uni == 'g') {
1040 popScreenAll();
1041 resetModes('c');
1042 clearMessages();
1043 welcomeMessage();
1044 pushScreen(showEuclideanMenu);
1045 }
1046 else if(uni == 'c' || uni == 'i' || uni == 's' || uni == 'C' || uni == '7') {
1047 if(uni == 'C' && !chaosUnlocked) {
1048 return;
1049 }
1050 popScreenAll();
1051 resetModes(uni);
1052 clearMessages();
1053 welcomeMessage();
1054 stampbase = ticks;
1055 if(uni == 's')
1056 multi::configure();
1057 }
1058 else if(uni == 'Z')
1059 halloween::start_all();
1060 #if CAP_RACING && MAXMDIM >= 4
1061 else if(uni == 'r' - 96) {
1062 popScreenAll();
1063 resetModes();
1064 stop_game();
1065 switch_game_mode(rg::racing);
1066 racing::track_code = "OFFICIAL";
1067 specialland = racing::race_lands[rand() % isize(racing::race_lands)];
1068 start_game();
1069 pmodel = mdBand;
1070 pconf.model_orientation = racing::race_angle;
1071 racing::race_advance = 1;
1072 vid.yshift = 0;
1073 pconf.camera_angle = 0;
1074 pconf.xposition = 0;
1075 pconf.yposition = 0;
1076 pconf.scale = 1;
1077 vid.use_smart_range = 1;
1078 vid.smart_range_detail = 3;
1079 }
1080 else if(uni == 't' - 96)
1081 racing::start_thurston();
1082 #endif
1083 #if CAP_TOUR
1084 else if(uni == 't') {
1085 popScreenAll();
1086 resetModes('c');
1087 tour::start();
1088 }
1089 #endif
1090 #if CAP_DAILY
1091 else if(uni == 'z') {
1092 popScreenAll();
1093 pushScreen(daily::showMenu);
1094 }
1095 #endif
1096 else if(uni == 'm') {
1097 popScreen();
1098 pushScreen(showMainMenu);
1099 }
1100 else if(sym == SDLK_F10)
1101 quitmainloop = true;
1102 else if(sym == SDLK_F1)
1103 gotoHelp("@");
1104 else if(sym == SDLK_ESCAPE || uni == ' ') {
1105 popScreen();
1106 timerstart = time(NULL);
1107 stampbase = ticks;
1108 clearMessages();
1109 welcomeMessage();
1110 }
1111 else if(sym == SDLK_F5) {
1112 #if CAP_STARTANIM
1113 startanims::pick();
1114 #endif
1115 daily_mode = 0;
1116 }
1117 };
1118 }
1119
1120 // -- overview --
1121
1122 #if HDR
1123 struct named_functionality {
1124 std::string first;
1125 reaction_t second;
1126 explicit named_functionality() = default;
named_functionalityhr::named_functionality1127 explicit named_functionality(std::string s, reaction_t r) : first(std::move(s)), second(std::move(r)) {}
operator ==(const named_functionality & a,const named_functionality & b)1128 friend bool operator==(const named_functionality& a, const named_functionality& b) { return a.first == b.first; }
operator !=(const named_functionality & a,const named_functionality & b)1129 friend bool operator!=(const named_functionality& a, const named_functionality& b) { return a.first != b.first; }
1130 };
__anona30eab452402() 1131 inline named_functionality named_dialog(string x, reaction_t dialog) { return named_functionality(x, [dialog] () { pushScreen(dialog); }); }
1132 #endif
1133
1134 #if HDR
1135 using o_funcs = vector<named_functionality>;
1136 #endif
1137
1138 EX hookset<void(o_funcs&)> hooks_o_key;
1139
get_o_key()1140 EX named_functionality get_o_key() {
1141 vector<named_functionality> res;
1142 callhooks(hooks_o_key, res);
1143
1144 if(in_full_game() && !yendor::on)
1145 res.push_back(named_dialog(XLAT("world overview"), showOverview));
1146
1147 #if CAP_DAILY
1148 if(daily::on)
1149 res.push_back(named_functionality(XLAT("Strange Challenge"), [] () {
1150 achievement_final(false);
1151 pushScreen(daily::showMenu);
1152 }));
1153 #endif
1154
1155 if(viewdists)
1156 res.push_back(named_functionality(XLAT("experiment with geometry"), runGeometryExperiments));
1157
1158 if(tactic::on)
1159 res.push_back(named_dialog(XLAT("pure tactics mode"), tactic::showMenu));
1160
1161 if(yendor::on)
1162 res.push_back(named_dialog(XLAT("Yendor Challenge"), yendor::showMenu));
1163
1164 if(peace::on)
1165 res.push_back(named_dialog(XLAT("puzzles and exploration"), peace::showMenu));
1166
1167 #if CAP_TEXTURE
1168 if(texture::config.tstate)
1169 res.push_back(named_dialog(XLAT("texture mode"), texture::showMenu));
1170 #endif
1171
1172 dialog::infix = "";
1173
1174 if((geometry != gNormal || NONSTDVAR) && !daily::on)
1175 res.push_back(named_functionality(XLAT("experiment with geometry"), runGeometryExperiments));
1176
1177 if(res.empty()) return named_dialog(XLAT("world overview"), showOverview);
1178
1179 if(isize(res) == 1) return res[0];
1180
1181 return named_dialog(res[0].first + "/...", [res] {
1182 emptyscreen();
1183 dialog::init();
1184 char id = 'o';
1185 for(auto& r: res) {
1186 dialog::addItem(r.first, id++);
1187 dialog::add_action([r] { popScreen(); r.second(); });
1188 }
1189 dialog::display();
1190 });
1191 }
1192
1193 EX int messagelogpos;
1194 EX int timeformat;
1195 EX int stampbase;
1196
gettimestamp(msginfo & m)1197 EX string gettimestamp(msginfo& m) {
1198 char buf[128];
1199 switch(timeformat) {
1200 case 0:
1201 return its(m.turnstamp);
1202 case 1:
1203 strftime(buf, 128, "%H:%M:%S", localtime(&m.rtstamp));
1204 return buf;
1205 case 2:
1206 snprintf(buf, 128, "%2d:%02d", m.gtstamp/60, m.gtstamp % 60);
1207 return buf;
1208 case 3:
1209 { int t = m.stamp - stampbase;
1210 bool sign = t < 0;
1211 if(sign) t = -t;
1212 snprintf(buf, 128, "%2d:%02d.%03d", t/60000, (t/1000) % 60, t % 1000);
1213 string s = buf;
1214 if(sign) s = "-"+s;
1215 return s;
1216 }
1217 }
1218 return "";
1219 }
1220
showMessageLog()1221 EX void showMessageLog() {
1222 DEBBI(DF_GRAPH, ("show message log"));
1223
1224 int lines = vid.yres / vid.fsize - 2;
1225 int maxpos = isize(gamelog) - lines;
1226 messagelogpos = min(messagelogpos, maxpos);
1227 messagelogpos = max(messagelogpos, 0);
1228
1229 for(int y=0; y<lines && messagelogpos+y < isize(gamelog); y++) {
1230 msginfo& m = gamelog[messagelogpos+y];
1231 displaystr(vid.fsize*8, vid.fsize*(y+1), 0, vid.fsize, fullmsg(m), 0xC0C0C0, 0);
1232 displaystr(vid.fsize*7, vid.fsize*(y+1), 0, vid.fsize, gettimestamp(m), 0xC0C0C0, 16);
1233 }
1234
1235 int i0 = vid.yres - vid.fsize;
1236 int xr = vid.xres / 80;
1237
1238 string timeformats[5] = {"turns", "real time", "game time", "precise", "no time"};
1239
1240 displayButton(xr*70, i0, IFM(dialog::keyname(SDLK_ESCAPE) + " - ") + XLAT("go back"), SDLK_ESCAPE, 8);
1241 displayButton(xr*10, i0, IFM("c - ") + XLAT("clear"), 'c', 8);
1242 displayButton(xr*40, i0, IFM("t - ") + XLAT(timeformats[timeformat]), 't', 8);
1243
1244 keyhandler = [lines] (int sym, int uni) {
1245 if(uni == PSEUDOKEY_WHEELDOWN) messagelogpos++;
1246 else if(uni == PSEUDOKEY_WHEELUP) messagelogpos--;
1247 else if(DKEY == SDLK_DOWN) messagelogpos++;
1248 else if(DKEY == SDLK_UP) messagelogpos--;
1249 else if(DKEY == SDLK_PAGEUP) messagelogpos -= lines;
1250 else if(DKEY == SDLK_PAGEDOWN) messagelogpos -= lines;
1251 else if(uni == 'c') gamelog.clear();
1252 else if(uni == 't') { timeformat++; timeformat %= 5; }
1253 else if(doexiton(sym, uni)) popScreen();
1254 };
1255 }
1256
1257 #if CAP_COMMANDLINE
1258
read_menu_args()1259 int read_menu_args() {
1260 using namespace arg;
1261
1262 if(argis("-d:over")) {
1263 PHASEFROM(2); launch_dialog(showOverview);
1264 }
1265 else if(argis("-d:main")) {
1266 PHASEFROM(2); launch_dialog(showMainMenu);
1267 }
1268 else if(argis("-d:mode")) {
1269 PHASEFROM(2); launch_dialog(showChangeMode);
1270 }
1271 else if(argis("-d:history")) {
1272 PHASEFROM(2); launch_dialog(history::history_menu);
1273 }
1274 else if(argis("-d:shmup")) {
1275 PHASEFROM(2); launch_dialog(); multi::configure();
1276 }
1277 else return 1;
1278 return 0;
1279 }
1280
1281 auto ah_menu = addHook(hooks_args, 0, read_menu_args);
1282
1283 #endif
1284
1285 }
1286