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