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