1 // Hyperbolic Rogue -- help routines
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file help.cpp
5  *  \brief Building and displaying help text
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX string help;
12 
13 EX function<void()> help_delegate;
14 
15 #if HDR
16 struct help_extension {
17   char key;
18   string text;
19   string subtext;
20   color_t color;
21   reaction_t action;
help_extensionhr::help_extension22   help_extension() { color = forecolor; }
help_extensionhr::help_extension23   help_extension(char k, string t, reaction_t a) : key(k), text(t), action(a) { color = forecolor; }
24   };
25 #endif
26 
27 EX vector<help_extension> help_extensions;
28 
29 vector<string> quick_keys = {
30   "1 = orthogonal/Gans model/FPP",
31   "2 = small Poincare model/stereographic projection/SPP",
32   "3 = big Poincare model/stereographic projection/TPP",
33   "4 = Klein model/gnomonic projection",
34   "5 = change wall display mode",
35   "6 = change grid",
36   "7 = change heptagon marking",
37   "8 = monster display mode"
38   };
39 
40 vector<string> normal_keys = {
41   "qweasdzxc, hjklyubn, numpad = move/skip turn",
42   "g = drop a Dead Orb",
43   "t = use a ranged Orb (target center of the screen)"
44   };
45 
46 vector<string> extra_keys = {
47   "o = world overview (or another meaning in special modes)",
48   "v = menu",
49   "F1 = help",
50   "F5 = restart game",
51   "F10 = quit game",
52   "Esc = quest status",
53   "Alt+Enter = full screen",
54   "Alt = highlight interesting stuff",
55   "click left mouse button = move/skip",
56   "[shift+]click left mouse button = use ranged Orb (depending on mouse settings)",
57   "click right mouse button = context help",
58   "mousewheel up = panning",
59   "hold middle mouse button = panning",
60   "lctrl + hold middle button = move the screen",
61   "mousewheel down = move/skip",
62   "rshift + mousewheel = change projection",
63   "lshift + mousewheel = change zoom (lctrl to keep center)",
64   "lctrl + mousewheel = reset the map center",
65   "shift + F2 = disable the HUD",
66   "shift + F3 = disable the FPS",
67   "shift + F4 = disable the map",
68   "space = recenter",
69   "ctrl + <key> = more precision"
70   };
71 
72 vector<string> extra_keys_2d = {
73   "arrows = panning",
74   "PageUp/Down = rotate the screen",
75   };
76 
77 vector<string> extra_keys_3d = {
78   "arrows = rotate the camera",
79   "rshift+arrows = strafe",
80   "lshift+arrows = rotate the model (in rug mode)",
81   "end = move camera forward",
82   "home = move camera backward",
83   "shift+Home/End = zoom",
84   "PageUp/Down = rotate the screen",
85   "move mouse = rotate camera (in rug, only with lctrl)",
86   };
87 
buildHelpText()88 void buildHelpText() {
89   DEBBI(DF_GRAPH, ("buildHelpText"));
90 
91   help = XLAT("Welcome to HyperRogue");
92 #if ISANDROID
93   help += XLAT(" for Android");
94 #endif
95 #if ISIOS
96   help += XLAT(" for iOS");
97 #endif
98   help += XLAT("! (version %1)\n\n", VER);
99 
100   help += XLAT(
101     "You have been trapped in a strange, non-Euclidean world. Collect as much treasure as possible "
102     "before being caught by monsters. The more treasure you collect, the more "
103     "monsters come to hunt you, as long as you are in the same land type. The "
104     "Orbs of Yendor are the ultimate treasure; get at least one of them to win the game!"
105     );
106   help += XLAT(" (press ESC for some hints about it).");
107   help += "\n\n";
108 
109   if(!shmup::on && !hardcore)
110     help += XLAT(
111       "You can fight most monsters by moving into their location. "
112       "The monster could also kill you by moving into your location, but the game "
113       "automatically cancels all moves which result in that.\n\n"
114       );
115 
116   if(shmup::on) {
117     help += XLAT(
118       "Shmup (shoot'em up) mode: You can play a hyperbolic shoot'em up game. The game is based "
119       "on the usual turn-based grid-based HyperRogue, but there are some changes. You fight by "
120       "throwing knives, and you have three extra lives. There are no allies, so all Orbs "
121       "related to allies give you extra lives instead (up to 5). Some other rules have been "
122       "adapted too.\n\n");
123     }
124 
125   if(shmup::on && multi::players > 1) {
126     help += XLAT(
127       "Multiplayer: Play cooperatively (locally); treasures, kills, and deaths are calculated "
128       "for each player too, for more competitive play. Orbs and treasures are shared, orbs drain "
129       "faster, knives recharge slower, and player characters are not allowed to separate.\n\n");
130     }
131 
132   if(multi::players > 1 && !shmup::on) {
133     help += XLAT(
134       "Turn-based multiplayer: Turns are executed in parallel. A player can leave the game "
135       "by pressing a designated key (useful when about to get killed or lost). The following "
136       "Orbs work to bring such players back: ");
137 
138     help += XLATN(iinf[itOrbLife].name); help += ", ";
139     help += XLATN(iinf[itOrbFriend].name); help += ", ";
140     help += XLATN(iinf[itOrbUndeath].name); help += ", ";
141     help += XLATN(iinf[itOrbTeleport].name); help += ", ";
142     help += XLATN(iinf[itOrbSafety].name); help += "\n\n";
143     }
144 
145 #if CAP_INV
146   if(inv::on)
147   help += XLAT(
148     inv::helptext
149     );
150   else
151 #endif
152   help += XLAT(
153     "There are many lands in HyperRogue. Collect 10 treasure "
154     "in the given land type to complete it; this enables you to "
155     "find the magical Orbs of this land, and in some cases "
156     "get access to new lands. At 25 treasures "
157     "this type of Orbs starts appearing in other lands as well. Press 'o' to "
158     "get the details of all the Lands.\n\n");
159   help += "\n\n";
160 
161 #if ISMOBILE
162   help += XLAT(
163     "Usually, you move by touching somewhere on the map; you can also touch one "
164     "of the four buttons on the map corners to change this (to scroll the map "
165     "or get information about map objects). You can also touch the "
166     "numbers displayed to get their meanings.\n"
167     );
168 #else
169   if(DEFAULTCONTROL)
170     help += XLAT(
171       "Move with mouse, num pad, qweadzxc, or hjklyubn. Wait by pressing 's' or '.'. Spin the world with arrows, PageUp/Down, and Space. "
172       "To save the game you need an Orb of Safety. Press 'v' for the main menu (configuration, special modes, etc.), ESC for the quest status.\n\n"
173       );
174   help += XLAT(
175     "You can right click any element to get more information about it.\n\n"
176     );
177 #if ISMAC
178   help += XLAT("(You can also use right Shift)\n\n");
179 #endif
180 #endif
181   help += XLAT("See more on the website: ")
182     + "http//roguetemple.com/z/hyper/\n\n";
183 
184 #if CAP_TOUR
185   help += XLAT("Try the Guided Tour to help with understanding the "
186     "geometry of HyperRogue (menu -> special modes).\n\n");
187 #endif
188 
189   help += XLAT("Still confused? Read the FAQ on the HyperRogue website!\n\n");
190 
191   help_extensions.clear();
192 
193   help_extensions.push_back(help_extension{'c', XLAT("credits"), [] () { buildCredits(); }});
194 #if ISMOBILE == 0
195   help_extensions.push_back(help_extension{'k', XLAT("advanced keyboard shortcuts"), [] () {
196     help = "";
197     for(string s: normal_keys) help += s, help += "\n";
198     for(string s: extra_keys) help += s, help += "\n";
199     help += "\n\nQuick keys:\n";
200     for(string s: quick_keys) help += s, help += "\n";
201     if(GDIM == 3 || rug::rugged) {
202       help += "\n\nIn 3D modes:\n";
203       for(string s: extra_keys_3d) help += s, help += "\n";
204       }
205     else {
206       help += "\n\nIn 2D modes:\n";
207       for(string s: extra_keys_2d) help += s, help += "\n";
208       }
209     }});
210 #endif
211   }
212 
standard_help()213 EX string standard_help() {
214   return XLAT("Press F1 or right click for help");
215   }
216 
buildCredits()217 EX void buildCredits() {
218   help = "";
219   help += XLAT("game design, programming, texts and graphics by Zeno Rogue <zeno@attnam.com>\n\n");
220   if(lang() != 0)
221     help += XLAT("add credits for your translation here");
222 #if !NOLICENSE
223   help += XLAT(
224     "released under GNU General Public License version 2 and thus "
225     "comes with absolutely no warranty; see COPYING for details\n\n"
226     );
227 #endif
228   help += XLAT(
229     "special thanks to the following people for their bug reports, feature requests, porting, and other help:\n\n%1\n\n",
230     "Konstantin Stupnik, ortoslon, chrysn, Adam Borowski, Damyan Ivanov, Ryan Farnsley, mcobit, Darren Grey, tricosahedron, Maciej Chojecki, Marek Čtrnáct, "
231     "wonderfullizardofoz, Piotr Migdał, tehora, Michael Heerdegen, Sprite Guard, zelda0x181e, Vipul, snowyowl0, Patashu, phenomist, Alan Malloy, Tom Fryers, Sinquetica, _monad, CtrlAltDestroy, jruderman, "
232     "Kojiguchi Kazuki, baconcow, Alan, SurelyYouJest, hotdogPi, DivisionByZero, xXxWeedGokuxXx, jpystynen, Dmitry Marakasov, Alexandre Moine, Arthur O'Dwyer, "
233     "Triple_Agent_AAA, bluetailedgnat, Allalinor, Shitford, KittyTac, Christopher King, KosGD, TravelDemon, Bubbles, rdococ, frozenlake, MagmaMcFry, "
234     "Snakebird Priestess, roaringdragon2, Stopping Dog, bengineer8, Sir Light IJIJ, ShadeBlade, Saplou, shnourok, Ralith, madasa, 6% remaining, Chimera245, Remik Pi, alien foxcat thing, "
235     "Piotr Grochowski, Ann, still-flow, tyzone, Paradoxica, LottieRatWorld"
236     );
237 #ifdef EXTRALICENSE
238   help += EXTRALICENSE;
239 #endif
240 #if !ISMOBILE
241   help += XLAT(
242     "\n\nSee sounds/credits.txt for credits for sound effects"
243     );
244   #endif
245   if(musiclicense != "") help += musiclicense;
246   }
247 
pushtext(stringpar p)248 string pushtext(stringpar p) {
249   string s = XLAT(
250     "\n\nNote: when pushing %the1 off a heptagonal cell, you can control the pushing direction "
251     "by clicking left or right half of the heptagon.", p);
252 #if !ISMOBILE
253   s += XLAT(" With the keyboard, you can rotate the view for a similar effect (Page Up/Down).");
254 #endif
255   return s;
256   }
257 
princedesc()258 string princedesc() {
259   if(princessgender() == GEN_M)
260     return XLAT("Apparently a prince is kept locked somewhere, but you won't ever find him in this hyperbolic palace. ");
261   else
262     return XLAT("Apparently a princess is kept locked somewhere, but you won't ever find her in this hyperbolic palace. ");
263   }
264 
helptitle(string s,color_t col)265 EX string helptitle(string s, color_t col) {
266   return "@" + its(col) + "\t" + s + "\n";
267   }
268 
princessReviveHelp()269 string princessReviveHelp() {
270   if(inv::on) return "";
271   string h = "\n\n" +
272     XLAT("Killed %1 can be revived with Orb of the Love, after you collect 20 more $$$.", moPrincess);
273   if(princess::reviveAt)
274     h += "\n\n" +
275     XLAT("%The1 will be revivable at %2 $$$", moPrincess, its(princess::reviveAt));
276   return h;
277   }
278 
describeOrb(string & help,const orbinfo & oi)279 void describeOrb(string& help, const orbinfo& oi) {
280   if(inv::on) return;
281   eOrbLandRelation olr = getOLR(oi.orb, getPrizeLand());
282   eItem tr = treasureType(oi.l);
283   eItem tt = treasureTypeUnlock(cwt.at->land, oi.orb);
284   if(olr == olrGuest) {
285     for(auto& oi1: orbinfos)
286       if((oi1.flags & orbgenflags::NATIVE) && oi1.orb == oi.orb)
287         tr = treasureType(oi1.l);
288     }
289   help += "\n\n" + XLAT(olrDescriptions[olr], cwt.at->land, tr, tt);
290   int t = items[tr] * landMultiplier(oi.l);
291   if(t >= 25)
292   if(olr == olrPrize25 || olr == olrPrize3 || olr == olrGuest || olr == olrMonster || olr == olrAlways) {
293     for(auto& oi1: orbinfos)
294       if((oi1.flags & orbgenflags::NATIVE) && oi1.orb == oi.orb) {
295         help += XLAT("\nSpawn rate (as prize Orb): %1%/%2\n",
296           its(int(.5 + 100 * orbprizefun(t))),
297           its(oi1.gchance));
298         }
299     }
300   if(t >= 10)
301   if(olr == olrHub) {
302     help += XLAT("\nSpawn rate (in Hubs): %1%/%2\n",
303       its(int(.5 + 100 * orbcrossfun(t))),
304       its(oi.gchance));
305     }
306   }
307 
other_geometry()308 string other_geometry() {
309   return XLAT("Note: the rules above correspond to the standard geometry; actual rules in other geometries may be different. ");
310   }
311 
other_land()312 string other_land() {
313   return XLAT("Note: the rules refer to colors which are not visible in other lands. ");
314   }
315 
other_geometry_land()316 string other_geometry_land() {
317   if(S7 != 7 || !BITRUNCATED) return other_geometry();
318   else return other_land();
319   }
320 
forbidden_marked()321 string forbidden_marked() {
322   return XLAT("When the 'mark heptagons' option (hotkey '7') is on, forbidden moves are marked.");
323   }
324 
forbidden_unmarked()325 string forbidden_unmarked() {
326   return XLAT("When the 'mark heptagons' option (hotkey '7') is on, moves between unmarked cells are forbidden.");
327   }
328 
329 string hyperstone_optional = "Completing the quest in this land is not necessary for the Hyperstone Quest.";
330 
331 string power_help =
332   "The amount of Orbs obtained by using Orbs of Mirroring is "
333   "multiplied by sqrt(1+p/20), where p is the number of Powerstones "
334   "collected. This also affects the mirrorings which happened before "
335   "collecting the Powerstones.";
336 
generateHelpForItem(eItem it)337 EX string generateHelpForItem(eItem it) {
338 
339    string help = helptitle(XLATN(iinf[it].name), iinf[it].color);
340 
341    #if CAP_CRYSTAL
342    if(it == itCompass && cryst)
343      help += crystal::compass_help();
344    else
345    #endif
346      help += XLAT(iinf[it].help);
347 
348    if(it == itSavedPrincess || it == itOrbLove) if(!inv::on)
349      help += princessReviveHelp();
350 
351    if(it == itTrollEgg)
352      help += XLAT("\n\nAfter the Trolls leave, you have 750 turns to collect %the1, or it gets stolen.", it);
353 
354    if(it == itIvory || it == itAmethyst || it == itLotus || it == itMutant) {
355      help += XLAT(
356        "\n\nEasy %1 might disappear when you collect more of its kind.", it);
357      if(it != itMutant) help += XLAT(
358        " You need to go deep to collect lots of them.");
359      }
360 
361 #if ISMOBILE
362    if(it == itOrbSafety)
363      help += XLAT("This might be very useful for devices with limited memory.");
364 #else
365    if(it == itOrbSafety)
366      help += XLAT("Thus, it is potentially useful for extremely long games, which would eat all the memory on your system otherwise.\n");
367 #endif
368 
369    if(isRangedOrb(it)) {
370      help += XLAT("\nThis is a ranged Orb. ");
371 #if ISMOBILE
372      if(vid.shifttarget&2)
373        help += XLAT("\nRanged Orbs can be targeted by long touching the desired location.");
374      else
375        help += XLAT("\nRanged Orbs can be targeted by touching the desired location.");
376 #else
377      if(vid.shifttarget&1)
378        help += XLAT("\nRanged Orbs can be targeted by shift-clicking the desired location. ");
379      else
380        help += XLAT("\nRanged Orbs can be targeted by clicking the desired location. ");
381      help += XLAT("You can also scroll to the desired location and then press 't'.");
382 #endif
383      help += XLAT("\nYou can never target cells which are adjacent to the player character, or ones out of the sight range.");
384      }
385 
386 #if ISMOBILE
387    if(it == itGreenStone)
388      help += XLAT("You can touch the Dead Orb in your inventory to drop it.");
389 #else
390    if(it == itGreenStone)
391      help += XLAT("You can press 'g' or click them in the list to drop a Dead Orb.");
392 #endif
393    if(it == itOrbLightning || it == itOrbFlash)
394      help += XLAT("\n\nThis Orb is triggered on your first attack or illegal move.");
395    if(it == itOrbShield)
396      help += XLAT("\n\nThis Orb protects you from attacks, scents, and insulates you "
397        "from electricity. It does not let you go through deadly terrain, but "
398        "if you are attacked with fire, it lets you stay in place in it.");
399 
400   if(it == itOrbWinter)
401     help += XLAT("\n\nThis orb also allows you to collect items encased in ice.");
402 
403   if(it == itOrbIntensity && inv::on)
404     help += XLAT("\n\nIn the Orb Strategy Mode, the effect is increased to +100%.");
405 
406   if(it == itOrbEmpathy) {
407     int cnt = 0;
408     for(int i=0; i<ittypes; i++) {
409       eItem it2 = eItem(i);
410       if(isEmpathyOrb(it2)) {
411         help += cnt ? XLAT(", %1", it2) : XLAT(" %1", it2);
412         cnt++;
413         }
414       }
415     help += XLAT("\n\nAdditionally, your allies are protected from your indirect attacks.");
416     }
417 
418 #if CAP_INV
419   if(inv::on) {
420     if(it == itOrbYendor || it == itHell) {
421       help += XLAT(
422         "\n\nIn the Orb Strategy Mode, Orbs of Yendor appear in Hell after "
423         "you collect 25 Demon Daisies in Hell, in Crossroads/Ocean after you collect 50, "
424         "and everywhere after you collect 100.");
425       }
426 
427 /*    if(it == itBone || it == itGreenStone) {
428       help += XLAT(
429         "\n\nIn the Orb Strategy Mode, dead orbs are available once you collect "
430         "10 Necromancer Totems in the Graveyard."
431         );
432       } */
433 
434     if(it == itFeather || it == itOrbSafety) {
435       help += XLAT(
436         "\n\nIn the Orb Strategy Mode, Orbs of Safety can be gained by "
437         "collecting Phoenix Feathers in the Land of Eternal Motion. "
438         "You can also find unlimited Orbs of Safety in the Crossroads "
439         "and the Ocean (after collecting 25 Phoenix Feathers) "
440         "and in the Prairie."
441         );
442       }
443 
444     if(it == itOrbYendor || it == itHolyGrail)
445       help += XLAT(
446         "\n\nCollect %the1 to gain an extra Orb of the Mirror. "
447         "You can gain further Orbs of the Mirror by collecting 2, 4, 8...",
448         it
449         );
450 
451     if(it == itPower)
452       help += "\n\n" + XLAT(power_help);
453 
454     if(it == itOrbLuck)
455       help += XLAT(
456         "\n\nIn the Orb Strategy Mode, the Orb of Luck also "
457         "significantly increases the frequency of Great Walls, Crossroads IV, "
458         "and sub-lands."
459         );
460 
461     if(it == itBone)
462       help += XLAT(
463         "\n\nIn the Orb Strategy Mode, each 25 Necromancer's Totems "
464         "you are given a random offensive Orb."
465         );
466 
467     if(inv::remaining[it] || inv::usedup[it]) help += "\n\n" + inv::osminfo(it);
468     inv::whichorbinfo = it;
469     inv::compute();
470     if(inv::orbinfoline != "") help += "\n\n" + inv::orbinfoline;
471     if(inv::extra != "") help += "\n\nExtras:" + inv::extra;
472     }
473 #endif
474 
475   if(it == itOrbLuck) {
476     help += XLAT("\n\nAdditionally, the probabilities of generating terrain features are subtly changed in the following lands:");
477 
478     int cnt = 0;
479     for(int i=0; i<landtypes; i++) {
480       eLand land = eLand(i);
481       if(isLuckyLand(land)) {
482         help += cnt ? XLAT(", %1", land) : XLAT(" %1", land);
483         cnt++;
484         }
485       }
486     }
487 
488   if(itemclass(it) == IC_ORB || it == itGreenStone || it == itOrbYendor) {
489     for(auto& oi: orbinfos) {
490       if(oi.orb == it && oi.is_native()) describeOrb(help, oi);
491       }
492     }
493 
494   if(itemclass(it) == IC_TREASURE) {
495     for(auto& oi: orbinfos) {
496       if(treasureType(oi.l) == it) {
497         if(oi.gchance > 0) {
498           help += "\n\n";
499           help += XLAT("\n\nOrb unlocked: %1", oi.orb);
500           describeOrb(help, oi);
501           }
502         else if(oi.l == cwt.at->land || inv::on) {
503           help += "\n\n";
504           help += XLAT("Secondary orb: %1", oi.orb);
505           describeOrb(help, oi);
506           }
507         }
508       }
509     }
510 
511   if(it == itOrb37 && (S7 != 7 || !BITRUNCATED))
512     help += "\n\n" + other_geometry() + forbidden_unmarked();
513 
514   if(it == itOrbLava && (S7 != 7 || !BITRUNCATED))
515     help += "\n\n" + other_geometry() + forbidden_unmarked();
516 
517   if(among(it, itOrbSide2, itOrbSide3) && !among(S7, 6, 7))
518     help += "\n\n" + other_geometry() + XLAT("This orb lets you attack adjacent cells %1 steps from the primary target.", its(it - itOrbSide1 + 1));
519 
520 #if CAP_INV
521   if(inv::on && it == itInventory)
522     help += "\n\n" + XLAT(inv::helptext);
523 #endif
524 
525   if(in_full_game() && !required_for_hyperstones(it) && it != itHyperstone)
526     help += "\n\n" + XLAT(hyperstone_optional);
527 
528 #if CAP_DAILY
529   if(daily::on && it == itOrbLove)
530     help += "\n\n" + XLAT("The Orb of Love gives no bonus score in the Strange Challenge.");
531 #endif
532 
533   return help;
534   }
535 
addMinefieldExplanation(string & s)536 void addMinefieldExplanation(string& s) {
537 
538   s += XLAT(
539     "\n\nOnce you collect a Bomberbird Egg, "
540     "stepping on a cell with no adjacent mines also reveals the adjacent cells. "
541     "Collecting even more Eggs will increase the radius."
542     );
543 
544   s += "\n\n";
545 #if !ISMOBILE
546   s += XLAT("Known mines may be marked by pressing 'm'. Your allies won't step on marked mines.");
547 #else
548   s += XLAT("Known mines may be marked by touching while in drag mode. Your allies won't step on marked mines.");
549 #endif
550   }
551 
generateHelpForWall(eWall w)552 EX string generateHelpForWall(eWall w) {
553 
554   string s = helptitle(XLATN(winf[w].name), winf[w].color);
555 
556   s += XLAT(winf[w].help);
557   if(w == waMineMine || w == waMineUnknown || w == waMineOpen)
558     addMinefieldExplanation(s);
559   if(isThumper(w)) s += pushtext(w);
560   if((w == waClosePlate || w == waOpenPlate) && PURE)
561     s += "\n\n(For the heptagonal mode, the radius has been reduced to 2 for closing plates.)";
562   return s;
563   }
564 
buteol(string & s,int current,int req)565 void buteol(string& s, int current, int req) {
566   int siz = isize(s);
567   if(s[siz-1] == '\n') s.resize(siz-1);
568   char buf[100]; sprintf(buf, " (%d/%d)", current, req);
569   s += buf; s += "\n";
570   }
571 
generateHelpForMonster(eMonster m)572 EX string generateHelpForMonster(eMonster m) {
573   string s = helptitle(XLATN(minf[m].name), minf[m].color);
574 
575   if(m == moPlayer) {
576 #if CAP_TOUR
577     if(tour::on || peace::on)
578       return s+XLAT(
579         "A tourist from another world. They mutter something about the 'tutorial', "
580         "and claim that they are here just to learn, and to leave without any treasures. "
581         "Do not kill them!"
582         );
583 #endif
584 
585     s += XLAT(
586         "This monster has come from another world, presumably to steal our treasures. "
587         "Not as fast as an Eagle, not as resilient as the guards from the Palace, "
588         "and not as huge as the Mutant Ivy from the Clearing; however, "
589         "they are very dangerous because of their intelligence, "
590         "and access to magical powers.\n\n");
591 
592     if(cheater)
593       s += XLAT("Actually, their powers appear god-like...\n\n");
594 
595     else if(!hardcore)
596       s += XLAT(
597        "Rogues will never make moves which result in their immediate death. "
598        "Even when cornered, they are able to instantly teleport back to their "
599        "home world at any moment, taking the treasures forever... but "
600        "at least they will not steal anything further!\n\n"
601        );
602 
603     if(!euclid)
604       s += XLAT(
605         "Despite this intelligence, Rogues appear extremely surprised "
606         "by the most basic facts about geometry. They must come from "
607         "some really strange world.\n\n"
608         );
609 
610     if(shmup::on)
611       s += XLAT("In the Shoot'em Up mode, you are armed with thrown Knives.");
612 
613     return s;
614     }
615 
616   s += XLAT(minf[m].help);
617   if(m == moPalace || m == moSkeleton)
618     s += pushtext(m);
619   if(m == moTroll) s += XLAT(trollhelp2);
620 
621   if(isMonsterPart(m))
622     s += XLAT("\n\nThis is a part of a monster. It does not count for your total kills.", m);
623 
624   if(isFriendly(m))
625     s += XLAT("\n\nThis is a friendly being. It does not count for your total kills.", m);
626 
627   if(m == moTortoise)
628     s += XLAT("\n\nTortoises are not monsters! They are just annoyed. They do not count for your total kills.", m);
629 
630   if(isGhost(m))
631     s += XLAT("\n\nA Ghost never moves to a cell which is adjacent to another Ghost of the same kind.", m);
632 
633   if(m == moMutant) {
634     using namespace clearing;
635     if(direct)
636       s += XLAT("\n\nLeaves cut directly: %1", its(direct));
637     if(kills[moMutant])
638       s += XLAT("\n\nLeaves cut onscreen: %1", its(kills[moMutant]));
639     if(imputed.nonzero())
640       s += XLAT("\n\nLeaves cut offscreen (approximately): %1", imputed.get_str(10000));
641     }
642 
643   eItem it = frog_power(m);
644   if(it)
645     s += XLAT("\n\nThis Frog uses the power of %the1. You get 5 charges yourself for killing it.", it);
646 
647   if(m == moBat || m == moEagle)
648     s += XLAT("\n\nFast flying creatures may attack or go against gravity only in their first move.", m);
649 
650   if(m == moAltDemon)
651     s += "\n\n" + other_geometry_land() + forbidden_unmarked();
652 
653   if(among(m, moHexDemon, moHexSnake, moHexSnakeTail))
654     s += "\n\n" + other_geometry_land() + forbidden_marked();
655 
656   if(among(m, moKrakenT, moKrakenH) && (S7 != 7 || !BITRUNCATED))
657     s += "\n\n" + other_geometry() + XLAT("Forbidden cells are marked with a different color.");
658 
659   return s;
660   }
661 
add_reqs(eLand l,string & s)662 void add_reqs(eLand l, string& s) {
663   back:
664 
665   switch(l) {
666     #define LAND(a,b,c,d,e,f,g) case c:
667     #define REQ(x) x return;
668     #define REQAS(x,y) y l = x; goto back;
669     #define GOLD(x) NUMBER(gold(), x, XLAT("Treasure required: %1 $$$.\n", its(x)))
670     #define KILL(who, where) NUMBER(kills[who], 1, XLAT("Kills required: %1 (%2).\n", who, where))
671     #define ITEMS(kind, number) NUMBER(items[kind], number, XLAT("Treasure required: %1 x %2.\n", its(number), kind))
672     #define NEVER ;
673     #define ALWAYS s += XLAT("Always available.\n");
674     #define KILLS(x) NUMBER(tkills(), x, XLAT("Kills required: %1.\n", its(x)))
675     #define AKILL(who, where) s += XLAT("Alternatively: kill a %1 in %the2.\n", who, where); buteol(s, kills[who], 1);
676     #define ORD(a, b) b a
677     #define ALT(x) // if([&] { x return true; } ()) return true;
678     #define NUMBER(val, required, description) s += description; buteol(s, val, required);
679     #define COND(x,y) s += (y);
680     #define ITEMS_TOTAL(list, z) \
681       { int now = 0; string t = "("; for(eItem i: list) { if(t!="(") t += " | "; t += XLATN(iinf[i].name); now += items[i]; } t += ")"; s += XLAT("Treasure required: %1 x %2.\n", its(z), t); buteol(s, now, z); }
682     #define ACCONLY(z) s += XLAT("Accessible only from %the1.\n", z);
683     #define ACCONLY2(z,x) s += XLAT("Accessible only from %the1 or %the2.\n", z, x);
684     #define ACCONLY3(z,y,x) s += XLAT("Accessible only from %the1 or %the2.\n", z, y, x);
685     #define ACCONLYF(z) s += XLAT("Accessible only from %the1 (until finished).\n", z);
686     #include "content.cpp"
687 
688     case landtypes: return;
689     }
690 
691   }
692 
generateHelpForLand(eLand l)693 EX string generateHelpForLand(eLand l) {
694   string s = helptitle(XLATN(linf[l].name), linf[l].color);
695 
696   if(l == laPalace) s += princedesc();
697 
698   s += XLAT(linf[l].help);
699 
700   if(l == laMinefield) addMinefieldExplanation(s);
701 
702   s += "\n\n";
703 
704   add_reqs(l, s);
705 
706   if(l == laPower && inv::on)
707     help += XLAT(power_help) + "\n\n";
708 
709   if(isCoastal(l))
710     s += XLAT("Coastal region -- connects inland and aquatic regions.\n");
711 
712   if(isPureSealand(l))
713     s += XLAT("Aquatic region -- accessible only from coastal regions and other aquatic regions.\n");
714 
715   if(in_full_game() && !required_for_hyperstones(treasureType(l)) && !isCrossroads(l))
716     s += XLAT(hyperstone_optional);
717 
718   int rl = isRandland(l);
719   if(rl == 2)
720     s += XLAT("Variants of %the1 are always available in the Random Pattern Mode.", l);
721   else if(rl == 1)
722     s += XLAT(
723       "Variants of %the1 are available in the Random Pattern Mode after "
724       "getting a highscore of at least 10 %2.", l, treasureType(l));
725 
726   if(l == laPrincessQuest) {
727     s += XLAT("Unavailable in the shmup mode.\n");
728     s += XLAT("Unavailable in the multiplayer mode.\n");
729     }
730 
731   /* if(noChaos(l))
732     s += XLAT("Unavailable in the Chaos mode.\n"); */
733 
734   if(l == laWildWest)
735     s += XLAT("Bonus land, available only in some special modes.\n");
736 
737   if(l == laWhirlpool)
738     s += XLAT("Orbs of Safety always appear here, and may be used to escape.\n");
739 
740   /* if(isHaunted(l) || l == laDungeon)
741     s += XLAT("You may be unable to leave %the1 if you are not careful!\n", l); */
742 
743   if(l == laStorms) {
744     if(elec::lightningfast == 0) s += XLAT("\nSpecial conduct (still valid)\n");
745     else s += XLAT("\nSpecial conduct failed:\n");
746 
747     s += XLAT(
748       "Avoid escaping from a discharge (\"That was close\").");
749     }
750 
751   if(isHaunted(l)) {
752     if(survivalist) s += XLAT("\nSpecial conduct (still valid)\n");
753     else s += XLAT("\nSpecial conduct failed:\n");
754 
755     s += XLAT(
756       "Avoid chopping trees, using Orbs, and non-graveyard monsters in the Haunted Woods."
757       );
758     }
759 
760   #if CAP_CRYSTAL
761   if(l == laCamelot && cryst) {
762     if(!crystal::used_compass_inside) s += XLAT("\nSpecial conduct (still valid)\n");
763     else s += XLAT("\nSpecial conduct failed:\n");
764 
765     s += XLAT(
766       "Do not use compases.\n\n");
767 
768     s += XLAT("Crystal Camelot is an octahedron in 'pure' 3D crystal geometry (and a similar polytope in other pure crystals), "
769       "and an Euclidean ball in bitruncated/Goldberg crystals.");
770     }
771   #endif
772 
773   auto lv = land_validity(l);
774   if(lv.flags & lv::display_in_help)
775     s += "\n\n" + XLAT(lv.msg);
776 
777 #if !ISMOBILE
778   if(l == laCA) {
779     s += XLAT("\n\nOption -mineadj 1 can be added for Moore neighborhoods.");
780     s += XLAT("\n\nHint: use 'm' to toggle cells quickly");
781     }
782 #endif
783 
784   return s;
785   }
786 
787 EX bool instat;
788 
turnstring(int i)789 string turnstring(int i) {
790   if(i == 1) return XLAT("1 turn");
791   else return XLAT("%1 turns", its(i));
792   }
793 
794 reaction_t helpgenerator;
bygen(reaction_t h)795 EX string bygen(reaction_t h) {
796   helpgenerator = h;
797   return "HELPGEN";
798   }
799 
800 void gotoHelpFor(eLand l);
801 
gotoHelpFor(eItem i)802 void gotoHelpFor(eItem i) {
803   help = generateHelpForItem(i);
804   }
805 
gotoHelpFor(eWall w)806 void gotoHelpFor(eWall w) {
807   help = generateHelpForWall(w);
808   }
809 
gotoHelpFor(eMonster m)810 void gotoHelpFor(eMonster m) {
811   help = generateHelpForMonster(m);
812   }
813 
appendHelp(string s)814 EX void appendHelp(string s) {
815   auto h = helpgenerator;
816   if(help == "HELPGEN")
817     bygen([h,s] { h(); help += s; });
818   else
819     help += s;
820   }
821 
822 unsigned char lastval;
823 int windtotal;
824 
825 EX hookset<void(cell*)> hooks_mouseover;
826 
set_help_to(T t)827 template<class T> void set_help_to(T t) {
828   help = bygen([t] { gotoHelpFor(t); });
829   }
830 
describeMouseover()831 EX void describeMouseover() {
832   DEBBI(DF_GRAPH, ("describeMouseover"));
833 
834   cell *c = mousing ? mouseover : playermoved ? NULL : centerover;
835   string& out = mouseovers;
836   if(!c || instat || getcstat != '-') { }
837   else if(c->wall != waInvisibleFloor) {
838     out = XLAT1(linf[c->land].name);
839     set_help_to(c->land);
840 
841     if(WDIM == 3 && isGravityLand(c->land)) out += " [" + its(gravityLevel(c)) + "]";
842 
843     if(c->land == laTemple && c->master->alt) {
844       int lev = -celldistAlt(c);
845       int ts = temple_layer_size();
846       out += " (" + its(lev/ts+1) + ":" + its(lev%ts) + ")";
847       }
848     if(isIcyLand(c))
849       out += " (" + fts(heat::celsius(c)) + " °C)";
850     if(c->land == laBrownian && c->wall == waNone)
851       out += XLAT(" (level %1)", its(snakelevel(c)));
852     if(c->land == laDryForest && c->landparam)
853       out += " (" + its(c->landparam)+"/10)";
854     if(c->land == laOcean && ls::any_chaos())
855       out += " (" + its(c->CHAOSPARAM)+"S"+its(c->SEADIST)+"L"+its(c->LANDDIST)+")";
856     else if(c->land == laOcean && c->landparam <= 25) {
857       if(shmup::on)
858         out += " (" + its(c->landparam)+")";
859       else {
860         bool b = c->landparam >= tide[(turncount-1) % tidalsize];
861         int t = 1;
862         for(; t < 1000 && b == (c->landparam >= tide[(turncount+t-1) % tidalsize]); t++) ;
863         if(b)
864           out += " (" + turnstring(t) + XLAT(" to surface") + ")";
865         else
866           out += " (" + turnstring(t) + XLAT(" to submerge") + ")";
867         }
868       }
869     #if CAP_FIELD
870     else if(c->land == laVolcano) {
871       int id = lavatide(c, -1)/4;
872       if(id < 96/4)
873         out += " (" + turnstring(96/4-id) + XLAT(" to go cold") + ")";
874       else
875         out += " (" + turnstring(256/4-id) + XLAT(" to submerge") + ")";
876       }
877     else if(c->land == laBlizzard) {
878       int wm = windmap::at(c);
879       windtotal += (signed char) (wm-lastval);
880       lastval = wm;
881       if(c == cwt.at) windtotal = 0;
882       out += " [" + its(windtotal) + "]";
883       }
884     #endif
885 
886     if(c->land == laTortoise && tortoise::seek()) out += " " + tortoise::measure(getBits(c));
887 
888     // describe the shadow path
889     if(among(c->land, laGraveyard, laCursed) && shpos.size()) {
890       string shadowtimes;
891       vector<cell*> route;
892       for(int s=1; s<SHSIZE; s++) {
893         bool shadow = false;
894         for(int p: player_indices())
895           if(shpos[(cshpos+s)%SHSIZE][p] == c)
896             shadow = true;
897         if(shadow) {
898           if(shadowtimes == "")
899             shadowtimes = its(s);
900           else
901             shadowtimes += " " + its(s);
902           }
903         }
904       if(shadowtimes != "")
905         out += XLAT(" (shadow in %1)", shadowtimes);
906       }
907 
908     if(buggyGeneration) {
909       char buf[80]; sprintf(buf, " %p H=%d M=%d", hr::voidp(c), c->landparam, c->mpdist); out += buf;
910       }
911 
912     if(randomPatternsMode)
913       out += " " + describeRPM(c->land);
914 
915     if(cheater && euc::in(2)) {
916       auto co = euc2_coordinates(c);
917       out += " (" + its(co.first);
918       for(int i=1; i<WDIM; i++) out += "," + its(co.second);
919       out += ")";
920       }
921 
922     if(cheater && euc::in(3) && !(cgflags & qPORTALSPACE)) {
923       auto co = euc::get_ispacemap()[c->master];
924       out += " (" + its(co[0]);
925       for(int i=1; i<WDIM; i++) out += "," + its(co[i]);
926       out += ")";
927       }
928 
929     #if CAP_CRYSTAL
930     if(geometry == gCrystal344 && cheater && crystal::view_coordinates) {
931       out += " (";
932       auto co = crystal::get_coord(c->master);
933       for(int i=0; i<4; i++) {
934         if(i) out += ",";
935         out += its(co[i]);
936         }
937       out += ")";
938       }
939     #endif
940 
941     if(c->wall && !(c->wall == waChasm && c->land == laDual && ctof(c)) &&
942       !(c->land == laMemory) &&
943       !((c->wall == waFloorA || c->wall == waFloorB) && c->item)) {
944       out += ", "; out += XLAT1(winf[c->wall].name);
945 
946       if(c->wall == waRose) out += " (" + its(7-rosephase) + ")";
947       if(c->wall == waTerraWarrior) out += " (" + its(c->wparam) + ")";
948       #if CAP_COMPLEX2
949       if(isDie(c->wall)) out += " (" + dice::describe(c) + ")";
950       #endif
951 
952       if((c->wall == waBigTree || c->wall == waSmallTree) && c->land != laDryForest)
953         help =
954           "Trees in this forest can be chopped down. Big trees take two turns to chop down.";
955       else
956         if(c->wall != waSea && c->wall != waPalace && c->wall != waDeadfloor)
957         if(!((c->wall == waCavefloor || c->wall == waCavewall) && (c->land == laEmerald || c->land == laCaves)))
958         if(!((isAlch(c->wall) && c->land == laAlchemist)))
959           set_help_to(c->wall);
960       }
961 
962     if(isActivable(c)) out += XLAT(" (touch to activate)");
963 
964     if(hasTimeout(c)) out += " [" + turnstring(c->wparam) + "]";
965 
966     if(isReptile(c->wall))
967       out += " [" + turnstring((unsigned char) c->wparam) + "]";
968 
969     #if CAP_COMPLEX2
970     if(c->monst == moKnight)
971       out += XLAT(", %1 the Knight", camelot::knight_name(c));
972     #else
973     if(0) ;
974     #endif
975 
976     else if(c->monst) {
977       out += ", "; out += XLAT1(minf[c->monst].name);
978       #if CAP_COMPLEX2
979       if(isDie(c->monst))
980         out += " (" + dice::describe(c) + ")";
981       #endif
982       if(hasHitpoints(c->monst))
983         out += " (" + its(c->hitpoints)+" HP)";
984       if(isMutantIvy(c))
985         out += " (" + its((c->stuntime - mutantphase) & 15) + "*)";
986       else if(c->stuntime)
987         out += " (" + its(c->stuntime) + "*)";
988 
989       if(c->monst == moTortoise && tortoise::seek())
990         out += " " + tortoise::measure(tortoise::getb(c));
991 
992       set_help_to(c->monst);
993       }
994 
995     if(c->item && !itemHiddenFromSight(c)) {
996       out += ", ";
997       out += XLAT1(iinf[c->item].name);
998       if(c->item == itBarrow) out += " (x" + its(c->landparam) + ")";
999       #if CAP_COMPLEX2
1000       if(c->land == laHunting) {
1001         int i = ambush::size(c, c->item);
1002         if(i) out += " (" + XLAT("ambush:") + " " + its(i) + ")";
1003         }
1004       #endif
1005       if(c->item == itBabyTortoise && tortoise::seek())
1006         out += " " + tortoise::measure(tortoise::babymap[c]);
1007       if(!c->monst) set_help_to(c->item);
1008       }
1009 
1010     if(isPlayerOn(c) && !shmup::on) out += XLAT(", you"), help = generateHelpForMonster(moPlayer);
1011 
1012     shmup::addShmupHelp(out);
1013 
1014     if(rosedist(c) == 1)
1015       out += ", wave of scent (front)";
1016 
1017     if(rosedist(c) == 2)
1018       out += ", wave of scent (back)";
1019 
1020     if(sword::at(c)) out += ", Energy Sword";
1021 
1022     if(rosedist(c) || c->land == laRose || c->wall == waRose)
1023       appendHelp(string("\n\n") + rosedesc);
1024 
1025     if(isWarped(c) && !isWarpedType(c->land))
1026       out += ", warped";
1027 
1028     if(isWarped(c)) {
1029       appendHelp(string("\n\n") + XLAT(warpdesc));
1030 
1031       if(S7 != 7 || !BITRUNCATED) if(c->item != itOrb37)
1032         appendHelp("\n\n" + other_geometry() + forbidden_unmarked());
1033       }
1034 
1035     if(isElectricLand(c) || isElectricLand(cwt.at->land)) {
1036       using namespace elec;
1037       eCharge ch = getCharge(c);
1038       if(ch == ecCharged) appendHelp("\n\nThis cell is charged.");
1039       if(ch == ecGrounded) appendHelp("\n\nThis cell is grounded.");
1040       if(ch == ecConductor) appendHelp("\n\nThis cell is currently conductive.");
1041       if(ch == ecIsolator) appendHelp("\n\nThis cell is currently not conductive.");
1042       }
1043     }
1044   else {
1045     shmup::addShmupHelp(out);
1046     }
1047 
1048   callhooks(hooks_mouseover, c);
1049 
1050   if(mousey < vid.fsize * 3/2 && getcstat == '-' && !instat) getcstat = SDLK_F1;
1051   #if CAP_TOUR
1052   if(tour::on && !tour::texts) {
1053     if(tour::slides[tour::currentslide].flags & tour::NOTITLE)
1054       mouseovers = "";
1055     else
1056       mouseovers = XLAT(tour::slides[tour::currentslide].name);
1057     }
1058   #endif
1059   }
1060 
showHelp()1061 EX void showHelp() {
1062   cmode = sm::HELP | sm::DOTOUR;
1063   getcstat = SDLK_ESCAPE;
1064   if(help == "HELPFUN") {
1065     help_delegate();
1066     return;
1067     }
1068 
1069   gamescreen(2);
1070   string help2;
1071   if(help[0] == '@') {
1072     int iv = help.find("\t");
1073     int id = help.find("\n");
1074     dialog::init(help.substr(iv+1, id-iv-1), atoi(help.c_str()+1), 120, 100);
1075     dialog::addHelp(help.substr(id+1));
1076     }
1077   else {
1078     dialog::init("help", forecolor, 120, 100);
1079     dialog::addHelp(help);
1080     }
1081 
1082   for(auto& he: help_extensions) {
1083     if(he.subtext != "")
1084       dialog::addSelItem(he.text, he.subtext, he.key);
1085     else
1086       dialog::addItem(he.text, he.key);
1087     dialog::lastItem().color = he.color;
1088     }
1089 
1090   dialog::display();
1091 
1092   keyhandler = [] (int sym, int uni) {
1093     dialog::handleNavigation(sym, uni);
1094 
1095     for(auto& he: help_extensions)
1096       if(uni == he.key) {
1097         // we need to copy he.action
1098         // as otherwise it could clear the extensions,
1099         // leading to errors
1100         auto act = he.action;
1101         act();
1102         return;
1103         }
1104     if(sym == SDLK_F1)  {
1105       auto i = help;
1106       buildHelpText();
1107       if(help == i) popScreen();
1108       }
1109     else if(doexiton(sym, uni))
1110       popScreen();
1111     };
1112   }
1113 
1114 EX hookset<bool()> hooks_default_help;
1115 
gotoHelp(const string & h)1116 EX void gotoHelp(const string& h) {
1117   help = h;
1118   help_extensions.clear();
1119   pushScreen(showHelp);
1120   if(help == "@") {
1121 
1122     if(callhandlers(false, hooks_default_help)) return;
1123 
1124 #if CAP_RUG
1125     if(rug::rugged) {
1126       help = rug::makehelp();
1127 
1128       help += "\n\n";
1129 
1130       for(string s: extra_keys_3d) help += s, help += "\n";
1131 
1132       help += "\n\n";
1133 
1134       help_extensions.push_back(help_extension{'m', XLAT("Hypersian Rug menu"), [] () { popScreen(); rug::select(); }});
1135       help_extensions.push_back(help_extension{'h', XLAT("HyperRogue help"), [] () { buildHelpText(); }});
1136       return;
1137       }
1138 #endif
1139 
1140     buildHelpText();
1141     }
1142   if(help == "HELPGEN") helpgenerator();
1143   }
1144 
subhelp(const string & h)1145 EX void subhelp(const string& h) {
1146   string oldhelp = help;
1147   auto ext = help_extensions;
1148   reaction_t back = [oldhelp, ext] () {
1149     help = oldhelp;
1150     help_extensions = ext;
1151     };
1152   help = h;
1153   help_extensions.clear();
1154   if(help == "HELPGEN") helpgenerator();
1155   help_extensions.push_back(help_extension{'z', XLAT("back"), back});
1156   }
1157 
gotoHelpFor(eLand l)1158 EX void gotoHelpFor(eLand l) {
1159   help = generateHelpForLand(l);
1160 
1161   int beastcount = 0;
1162   for(int m0=0; m0<motypes; m0++)
1163     if(isNative(l, eMonster(m0)) && !nodisplay(eMonster(m0))) beastcount++;
1164 
1165   auto listbeasts = [l] () {
1166     char nextmonster = 'a';
1167     for(int m0=0; m0<motypes; m0++) {
1168       const eMonster m = eMonster(m0);
1169       if(isNative(l, m) && !nodisplay(m)) {
1170         help_extension hex;
1171         hex.key = nextmonster++;
1172         hex.text = XLATN(minf[m].name);
1173         hex.action = [m] () {
1174           subhelp(bygen([m] () { gotoHelpFor(m); }));
1175           };
1176         help_extensions.push_back(hex);
1177         }
1178       }
1179     };
1180 
1181   if(beastcount > 3)
1182   help_extensions.push_back(help_extension{'b', XLAT("bestiary of %the1", l), [l, listbeasts] () {
1183     subhelp(helptitle(XLAT("bestiary of %the1", l), 0xC00000));
1184     listbeasts();
1185      }});
1186   else listbeasts();
1187 
1188   if(l == laTortoise)
1189     help_extensions.push_back(help_extension{'s', XLAT("Galápagos shading"), [] () {
1190       tortoise::shading_enabled = !tortoise::shading_enabled;
1191       }});
1192 
1193   help_extensions.push_back(help_extension{'w', XLAT("wiki"), [l] () {
1194     open_wiki(linf[l].name);
1195     }});
1196   }
1197 }
1198