1 // Hyperbolic Rogue -- the mission screen
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file quit.cpp
5  *  \brief the mission screen, and routines related to it
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
quitsaves()11 EX bool quitsaves() {
12   if(casual) return false;
13   return (items[itOrbSafety] && CAP_SAVE && !arcm::in());
14   }
15 
needConfirmationEvenIfSaved()16 EX bool needConfirmationEvenIfSaved() {
17   return canmove && (gold() >= 30 || tkills() >= 50) && !cheater;
18   }
19 
needConfirmation()20 EX bool needConfirmation() {
21   if(casual) return needConfirmationEvenIfSaved() && turncount > save_turns + 10;
22   else return needConfirmationEvenIfSaved() && !quitsaves();
23   }
24 
getgametime()25 EX int getgametime() {
26   return (int) (savetime + (timerstopped ? 0 : (time(NULL) - timerstart)));
27   }
28 
getgametime_s(int timespent IS (getgametime ()))29 EX string getgametime_s(int timespent IS(getgametime())) {
30   char buf[20];
31   sprintf(buf, "%d:%02d", timespent/60, timespent % 60);
32   return buf;
33   }
34 
35 EX bool display_yasc_codes;
36 
timeline()37 string timeline() {
38   string s;
39   if(shmup::on)
40     s = XLAT("%1 knives (%2)", its(turncount), getgametime_s());
41   else {
42     s = XLAT("%1 turns (%2)", its(turncount), getgametime_s());
43     if(display_yasc_codes)
44       s+= XLAT(" YASC code: ") + its(yasc_code);
45     }
46   return s;
47   }
48 
noaction()49 EX void noaction() {}
50 
51 EX function<void()> cancel = noaction;
52 
53 #if HDR
54 struct hint {
55   time_t last;
56   function<bool()> usable;
57   function<void()> display;
58   function<void()> action;
59   };
60 #endif
61 
62 EX hint hints[] = {
63 
64   {
65     0,
__anonde946d200102() 66     []() {
67       return !inv::on && items[localTreasureType()] >= 18 && in_full_game();
68       },
__anonde946d200202() 69     []() {
70        dialog::addHelp(XLAT(
71         "If you collect too many treasures in a given land, it will become "
72         "extremely dangerous. Try other lands once you have enough!"));
73       },
74     noaction},
75 
76   {
77     0,
__anonde946d200302() 78     []() {
79       return !ISMOBILE;
80       },
__anonde946d200402() 81     []() {
82        dialog::addHelp(XLAT(
83         "Remember that you can right click almost anything for more information."));
84 #if ISMAC
85        dialog::addHelp(XLAT(
86          "(You can also use right Shift)\n\n"));
87 #endif
88       },
89     noaction},
90 
91   {
92     0,
__anonde946d200502() 93     []() { return !canmove; },
__anonde946d200602() 94     []() {
95       dialog::addHelp(XLAT(
96         "Want to understand the geometry in HyperRogue? Try the Guided Tour!"
97         ));
98       dialog::addBreak(50);
99       dialog::addItem(XLAT("guided tour"), 'z');
100       },
__anonde946d200702() 101     []() {
102 #if CAP_TOUR
103       tour::start();
104 #else
105       addMessage("Not in this version");
106 #endif
107       }},
108 
109   {
110     0,
__anonde946d200802() 111     []() { return !inv::on && in_full_game(); },
__anonde946d200902() 112     []() {
113       dialog::addHelp(XLAT(
114         "Collecting 25 treasures in a given land may be dangerous, "
115         "but allows magical Orbs of this land to appear in other places!"
116         ));
117       },
118     noaction},
119 
120   {
121     0,
__anonde946d200a02() 122     []() { return !canmove; },
__anonde946d200b02() 123     []() {
124       dialog::addInfo(XLAT(
125         "Press ESC to view this screen during the game."
126         ));
127       },
128     noaction
129     },
130   {
131     0,
__anonde946d200c02() 132     []() { return in_full_game(); },
__anonde946d200d02() 133     []() {
134       dialog::addInfo(
135 #if ISMOBILE
136         XLAT("The 'world overview' shows all the lands in HyperRogue.")
137 #else
138         XLAT("Press 'o' to see all the lands in HyperRogue.")
139 #endif
140         );
141       dialog::addBreak(50);
142       dialog::addItem(XLAT("world overview"), 'z');
143       },
__anonde946d200e02() 144     []() {
145       pushScreen(showOverview);
146       }},
147   {
148     0,
__anonde946d200f02() 149     []() { return !canmove; },
__anonde946d201002() 150     []() {
151       dialog::addHelp(XLAT(
152         "Want another type of game? Want more challenge?\n"
153         "HyperRogue has many special modes and challenges that "
154         "significantly change the gameplay. Try them!"
155         ));
156       dialog::addBreak(50);
157       dialog::addItem(XLAT("special game modes"), 'z');
158       },
__anonde946d201102() 159     []() {
160       pushScreen(showChangeMode);
161       }},
162 
163   {
164     0,
__anonde946d201202() 165     []() { return true; },
__anonde946d201302() 166     []() {
167       dialog::addInfo(XLAT(
168         "Hyperbolic geometry can be shown in many ways."
169         ));
170       dialog::addBreak(50);
171       dialog::addItem(XLAT("special display modes"), 'z');
172       },
__anonde946d201402() 173     []() {
174       pushScreen(models::model_menu);
175       }},
176 
177   {
178     0,
__anonde946d201502() 179     []() { return !canmove && !inv::on; },
__anonde946d201602() 180     []() {
181       dialog::addHelp(XLAT(
182         "You do not want to lose the game from a single mistake?\n"
183         "Do you want to use the Orbs strategically?\n"
184         "Try the Orb Strategy mode!")
185         );
186       dialog::addBreak(50);
187       dialog::addItem(XLAT("Orb Strategy mode"), 'z');
188       },
__anonde946d201702() 189     []() {
190 #if CAP_INV
191       restart_game(rg::inv);
192 #endif
193       }
194     },
195   {
196     0,
__anonde946d201802() 197     []() { return CAP_RUG && geometry == gNormal; },
__anonde946d201902() 198     []() {
199       dialog::addHelp(XLAT(
200         "Do you think you are playing on a ball? "
201         "This is far from the truth!\n"
202         ));
203       dialog::addBreak(50);
204       dialog::addItem(XLAT("hypersian rug mode"), 'z');
205       },
__anonde946d201a02() 206     [] () {
207 #if CAP_RUG
208       popScreen();
209       int wm, mm;
210       rug::init();
211       wm = vid.wallmode;
212       mm = vid.monmode;
213       vid.wallmode = 3;
214       vid.monmode = 2;
215       cancel = [wm, mm] () {
216         rug::close();
217         vid.wallmode = wm;
218         vid.monmode = mm;
219         };
220 #endif
221       }
222     },
223 
224   {
225     0,
__anonde946d201c02() 226     []() { return !canmove && geometry == gNormal && celldist(cwt.at) >= 50; },
__anonde946d201d02() 227     []() {
228       dialog::addHelp(XLAT(
229         "Did you know that the path you take during the game "
230         "is usually very close to a straight line?\n"
231         ));
232       dialog::addBreak(50);
233       dialog::addItem(XLAT("Show me!"), 'z');
234       },
__anonde946d201e02() 235     [] () {
236       popScreen();
237       auto m = pmodel;
238       pmodel = mdBand;
239       int r = models::rotation;
240       bool h = history::includeHistory;
241       models::rotation = 0;
242       history::includeHistory = true;
243       history::create_playerpath();
244       cancel = [m,r,h] () {
245         history::clear(); pmodel = m;
246         models::rotation = r;
247         history::includeHistory = h;
248         fullcenter(); };
249       }
250     },
251 
252   {
253     0,
__anonde946d202002() 254     []() { return !canmove && sizes_known() && celldist(cwt.at) >= 50; },
__anonde946d202102() 255     []() {
256       int d = celldist(cwt.at);
257       string s = expansion.approximate_descendants(d, 10000);
258       dialog::addHelp(XLAT(
259         "You are %1 cells away from the starting point, or "
260         "the place where you used an Orb of Safety last time. "
261         "There are %2 such cells.\n",
262         its(d), s
263         ));
264       dialog::addBreak(50);
265       dialog::addItem(XLAT("expansion"), 'z');
266       },
__anonde946d202202() 267     [] () {
268       viewdists = !viewdists;
269       popScreen();
270       cancel = [] () { viewdists = false; };
271       }
272     },
273 
274   {
275     0,
__anonde946d202402() 276     []() { return !canmove && showHalloween(); },
__anonde946d202502() 277     []() {
278       dialog::addItem(XLAT("Halloween mini-game"), 'z');
279       },
__anonde946d202602() 280     [] () {
281       if(!sphere) {
282         resetModes();
283         stop_game();
284         specialland = laHalloween;
285         set_geometry(gSphere);
286         start_game();
287         pconf.alpha = 999;
288         pconf.scale = 998;
289         }
290       else {
291         resetModes();
292         pconf.alpha = 1;
293         pconf.scale = 1;
294         }
295       }
296     },
297 
__anonde946d202702() 298   {-1, []() { return false; }, noaction, noaction}
299   };
300 
301 EX int hinttoshow;
302 
contstr()303 string contstr() {
304   return canmove ? XLAT("continue") : XLAT("see how it ended");
305   }
306 
nextHyperstone()307 eLand nextHyperstone() {
308   generateLandList(isLandIngame);
309   for(eLand l: landlist)
310     if(items[treasureType(l)] < R10 && !isCrossroads(l) && l != laPrincessQuest && l != laCamelot)
311       return l;
312   if(items[itHyperstone] >= 10) return laNone;
313   return laCrossroads;
314   }
315 
showMission()316 EX void showMission() {
317 
318   cmode = sm::DOTOUR | sm::MISSION | sm::CENTER;
319   gamescreen(1); drawStats();
320 
321   dialog::init(
322 #if CAP_TOUR
323     tour::on ? (canmove ? XLAT("guided tour") : XLAT("GAME OVER")) :
324 #endif
325     (cheater && !autocheat)? XLAT("It is a shame to cheat!") :
326     racing::on ? "racing mode" :
327     (canmove && princess::challenge) ? XLAT("%1 Challenge", moPrincess) :
328     canmove ? XLAT("Quest status") :
329     XLAT("GAME OVER"),
330     0xC00000, 200, 100
331     );
332   keyhandler = handleKeyQuit;
333 
334   #if CAP_COMPLEX2
335   bool sweeper = mine::in_minesweeper();
336   #else
337   const bool sweeper = false;
338   #endif
339 
340   if(!peace::on && !racing::on && !sweeper && !in_lovasz())
341     dialog::addInfo(XLAT("Your score: %1", its(gold())));
342   if(!peace::on && !racing::on && !sweeper && !in_lovasz())
343     dialog::addInfo(XLAT("Enemies killed: %1", its(tkills())));
344 
345 #if CAP_TOUR
346   if(tour::on) ; else
347 #endif
348   if(items[itOrbYendor]) {
349     dialog::addInfo(XLAT("Orbs of Yendor found: %1", its(items[itOrbYendor])), iinf[itOrbYendor].color);
350     dialog::addInfo(XLAT("CONGRATULATIONS!"), iinf[itOrbYendor].color);
351     }
352   #if CAP_COMPLEX2
353   else if(mine::in_minesweeper()) {
354     int to_uncover = kills[moBomberbird];
355     if(to_uncover) {
356       dialog::addInfo(XLAT("Uncover all cells which do not contain mines"));
357       dialog::addInfo(XLAT("Cells to uncover: %1", its(to_uncover)));
358       }
359     else {
360       dialog::addInfo(XLAT("CONGRATULATIONS!"), iinf[itOrbYendor].color);
361       dialog::addInfo(XLAT("You won in %1", getgametime_s(mine::victory_time)));
362       }
363     }
364   #endif
365   else if(in_lovasz()) {
366     int score = 0, all = 0;
367     for(cell *c: currentmap->allcells()) {
368       if(c->wall == waChasm || c->item == itOrbInvis)
369         score++;
370       all++;
371       }
372     dialog::addInfo(XLAT("Dropped floors: %1/%2", its(score), its(all)));
373     if(score == all) dialog::addInfo(XLAT("CONGRATULATIONS!"), iinf[itOrbYendor].color);
374     if(score == all && geometry == gKleinQuartic && variation == eVariation::untruncated && gp::param == gp::loc(1,1))
375       achievement_gain_once("LOVASZ", rg::special_geometry);
376     }
377   else {
378     if(0)
379       ;
380 #if CAP_TOUR
381     else if(tour::on)
382       ;
383 #endif
384     else if(racing::on) ;
385     else if(princess::challenge)
386       dialog::addInfo(XLAT("Follow the Mouse and escape with %the1!", moPrincess));
387     else if(!in_full_game()) ;
388     else if(casual && savecount == 0) {
389       dialog::addInfo(XLAT("Find an Orb of Safety to save your game"));
390       }
391     else if(gold() < R30)
392       dialog::addInfo(XLAT("Collect %1 $$$ to access more worlds", its(R30)));
393     else if(gold() < R60)
394       dialog::addInfo(XLAT("Collect %1 $$$ to access even more lands", its(R60)));
395     else if(!landUnlocked(laHell))
396       dialog::addInfo(XLAT("Collect at least %1 treasures in each of %2 types to access Hell", its(R10), its(lands_for_hell())));
397     else if(items[itHell] < R10)
398       dialog::addInfo(XLAT("Collect at least %1 Demon Daisies to find the Orbs of Yendor", its(R10)));
399     else if(isize(yendor::yi) == 0)
400       dialog::addInfo(XLAT("Look for the Orbs of Yendor in Hell or in the Crossroads!"));
401     else
402       dialog::addInfo(XLAT("Unlock the Orb of Yendor!"));
403     }
404 
405   if(!timerstopped && !canmove) {
406     savetime += time(NULL) - timerstart;
407     timerstopped = true;
408     }
409   if(canmove && !timerstart)
410     timerstart = time(NULL);
411 
412   if(princess::challenge) ;
413 #if CAP_TOUR
414   else if(tour::on) ;
415 #endif
416   else if(peace::on) ;
417   else if(racing::on) ;
418   else if(!in_full_game()) ;
419   else if(tkills() < R100)
420     dialog::addInfo(XLAT("Defeat %1 enemies to access the Graveyard", its(R100)));
421   else if(kills[moVizier] == 0 && (items[itFernFlower] < U5 || items[itGold] < U5))
422     dialog::addInfo(XLAT("Kill a Vizier in the Palace to access Emerald Mine"));
423   else if(items[itEmerald] < U5)
424     dialog::addInfo(XLAT("Collect 5 Emeralds to access Camelot"));
425   else if(landUnlocked(laHell) && ls::any_order()) {
426     eLand l = nextHyperstone();
427     if(l)
428         dialog::addInfo(
429             l ? XLAT("Hyperstone Quest: collect at least %3 points in %the2", treasureType(l), l, its(R10))
430               : XLAT("Hyperstone Quest: collect at least %3 %1 in %the2", treasureType(l), l, its(R10))
431             );
432     else
433       dialog::addInfo(XLAT("Hyperstone Quest completed!"), iinf[itHyperstone].color);
434     }
435   else dialog::addInfo(XLAT("Some lands unlock at specific treasures or kills"));
436   if(cheater && !autocheat) {
437     dialog::addInfo(XLAT("you have cheated %1 times", its(cheater)), 0xFF2020);
438     }
439   else if(!racing::on) {
440     dialog::addInfo(timeline(), dialog::dialogcolor);
441     }
442 
443   dialog::addBreak(100);
444 
445 #if CAP_TOUR
446   if(!tour::on) {
447     hints[hinttoshow].display();
448     dialog::addBreak(100);
449     }
450 #endif
451 
452   bool intour = false;
453 
454 #if CAP_TOUR
455   intour = tour::on;
456 #endif
457 
458   if(intour) {
459 #if CAP_TOUR
460     if(canmove) {
461       if(sphere) {
462         dialog::addItem(XLAT("return to your game"), '1');
463         dialog::addItem(pconf.alpha < 2 ? XLAT("orthogonal projection") : XLAT("stereographic projection"), '3');
464         }
465       else if(euclid) {
466         dialog::addItem(XLAT("return to your game"), '2');
467         dialog::addBreak(100);
468         }
469       else {
470         dialog::addItem(XLAT("spherical geometry"), '1');
471         dialog::addItem(XLAT("Euclidean geometry"), '2');
472         }
473       // dialog::addItem(XLAT("more curved hyperbolic geometry"), '3');
474       }
475     if(!items[itOrbTeleport])
476       dialog::addItem(XLAT("teleport away"), '4');
477     else if(!items[itOrbAether])
478       dialog::addItem(XLAT("move through walls"), '4');
479     else
480       dialog::addItem(XLAT("flash"), '4');
481     if(canmove) {
482       if(tour::slidecommand != "")
483         dialog::addItem(tour::slidecommand, '5');
484       dialog::addItem(XLAT("static mode"), '6');
485       dialog::addItem(XLAT("enable/disable texts"), '7');
486       dialog::addItem(XLAT("next slide"), SDLK_RETURN);
487       dialog::addItem(XLAT("previous slide"), SDLK_BACKSPACE);
488       dialog::addItem(XLAT("list of slides"), '9');
489       dialog::addItem(XLAT("leave the tour mode"), '0');
490       }
491     else
492       dialog::addBreak(200);
493     dialog::addItem(XLAT("main menu"), 'v');
494     dialog::addItem("continue", SDLK_ESCAPE);
495 #endif
496     }
497   else {
498     dialog::addItem(contstr(), SDLK_ESCAPE);
499     dialog::addItem(XLAT("main menu"), 'v');
500     dialog::addItem(XLAT("restart"), SDLK_F5);
501     if(inv::on && items[itInventory])
502       dialog::addItem(XLAT("inventory"), 'i');
503     if(racing::on)
504       dialog::addItem(XLAT("racing menu"), 'o');
505 #if !ISMOBILE
506     dialog::addItem(quitsaves() ? XLAT("save") : XLAT("quit"), SDLK_F10);
507 #endif
508     if(casual || ISMOBILE) {
509       if(savecount)
510         dialog::addItem(XLAT("load (%1 turns passed)", its(turncount - save_turns)), SDLK_F9);
511       else
512         dialog::addItem(XLAT("how to find an Orb of Safety?"), SDLK_F9);
513       }
514 #if CAP_ANDROIDSHARE
515     dialog::addItem(XLAT("SHARE"), 's'-96);
516 #endif
517     }
518   dialog::addItem(XLAT("message log"), 'l');
519 
520   dialog::display();
521   }
522 
safety_help()523 EX string safety_help() {
524   return XLAT(
525     "To save the game you need an Orb of Safety.\n\n"
526     "Orbs of Safety appear:\n\n"
527     "* in the Crossroads and the Land of Eternal Motion, after you collect %1 Phoenix Feathers in the Land of Eternal Motion\n\n"
528     "* in the Ocean after you unlock it (%2 treasures)\n\n"
529     "* in the Prairie after you unlock it (%3 treasures)\n\n",
530     its(inv::on ? 25 : 10),
531     its(R30), its(R90)
532     );
533   }
534 
535 
handleKeyQuit(int sym,int uni)536 EX void handleKeyQuit(int sym, int uni) {
537   dialog::handleNavigation(sym, uni);
538   // ignore the camera movement keys
539 
540 #if CAP_RUG
541   if(rug::rugged && (sym == SDLK_UP || sym == SDLK_DOWN || sym == SDLK_PAGEUP || sym == SDLK_PAGEDOWN ||
542     sym == SDLK_RIGHT || sym == SDLK_LEFT))
543     sym = 0;
544 #endif
545 
546   if(sym == SDLK_RETURN || sym == SDLK_KP_ENTER || sym == SDLK_F10) {
547     if(needConfirmation()) pushScreen([] {
548       dialog::confirm_dialog(
549         XLAT("This will exit HyperRogue without saving your current game. Are you sure?") + "\n\n" +
550         safety_help(),
551         [] {
552         quitmainloop = true;
553         });
554       });
555     else quitmainloop = true;
556     }
557   else if(uni == 'r' || sym == SDLK_F5) dialog::do_if_confirmed([] {
558     restart_game(rg::nothing);
559     msgs.clear();
560     });
561   else if(uni == 'v') popScreenAll(), pushScreen(showMainMenu);
562   else if(uni == 'l') popScreenAll(), pushScreen(showMessageLog), messagelogpos = isize(gamelog);
563   else if(uni == 'z') hints[hinttoshow].action();
564   #if CAP_SAVE
565   else if(sym == SDLK_F9) {
566     if(casual && savecount) {
567       stop_game();
568       load_last_save();
569       start_game();
570       }
571     else
572       gotoHelp(safety_help());
573     }
574   #endif
575   else if(sym == SDLK_F3 || (sym == ' ' || sym == SDLK_HOME))
576     fullcenter();
577   else if(uni == 'o') get_o_key().second();
578 #if CAP_INV
579   else if(uni == 'i' && inv::on)
580     pushScreen(inv::show);
581 #endif
582 #if CAP_SAVE
583   else if(uni == 't') {
584     msgs.clear();
585     scores::load();
586     }
587   #endif
588 
589   else if(doexiton(sym, uni) && !didsomething) {
590     popScreen();
591     msgs.clear();
592     if(!canmove) {
593       addMessage(XLAT("GAME OVER"));
594       addMessage(timeline());
595       }
596     }
597   }
598 
counthints()599 EX int counthints() {
600   for(int h=0;; h++) if(hints[h].last < 0) return h;
601   }
602 
showMissionScreen()603 EX void showMissionScreen() {
604   cancel(); cancel = noaction;
605   popScreenAll();
606   achievement_final(false);
607 
608   if(daily::on) {
609     #if CAP_DAILY
610     pushScreen(daily::showMenu);
611     #endif
612     }
613   else
614     pushScreen(showMission);
615 
616 #if CAP_TOUR
617   if(!tour::on)
618 #endif
619   {
620     int ch = counthints();
621     hinttoshow = ch;
622     int h;
623     for(h=0; h < ch; h++) {
624       if(!hints[h].usable()) continue;
625       if(hinttoshow == ch || hints[h].last < hints[hinttoshow].last) hinttoshow = h;
626       }
627     hints[hinttoshow].last = time(NULL);
628     }
629 
630   dialog::highlight_text = contstr();
631   }
632 
633 }
634