1 // Hyperbolic Rogue -- minor modes
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3
4 /** \file yendor.cpp
5 * \brief Yendor Quest/Challenge, Pure Tactics Mode, Peace Mode
6 */
7
8 #include "hyper.h"
9 namespace hr {
10
11 namespace peace { extern bool on; }
12
hiitemsMax(eItem it)13 EX int hiitemsMax(eItem it) {
14 int mx = 0;
15 for(auto& a: hiitems) if(a.second[it] > mx) mx = a.second[it];
16 return mx;
17 }
18
19 /** 1 - just return UNKNOWN if id not assigned; 2 - assign without writing to file; 3 - assign with writing to file */
20 EX modecode_t modecode(int mode IS(3));
21
22 typedef vector<pair<int, string> > subscoreboard;
23
displayScore(subscoreboard & s,int x)24 void displayScore(subscoreboard& s, int x) {
25 int vf = min((vid.yres-64) / 70, vid.xres/80);
26
27 if(get_sync_status() == 1) {
28 displayfr(x, 56, 1, vf, "(syncing)", 0xC0C0C0, 0);
29 }
30 else {
31 sort(s.begin(), s.end());
32 for(int i=0; i<isize(s); i++) {
33 int i0 = 56 + i * vf;
34 displayfr(x, i0, 1, vf, its(-s[i].first), 0xC0C0C0, 16);
35 displayfr(x+8, i0, 1, vf, s[i].second, 0xC0C0C0, 0);
36 }
37 }
38 }
39
40 EX namespace yendor {
41
42 EX bool on = false;
43 EX bool generating = false;
44 EX bool path = false;
45 EX bool everwon = false;
46 EX bool won = false;
47 bool easy = false;
48
49 EX int challenge; // id of the challenge
50 EX int lastchallenge;
51
52 #if HDR
53 #define YF_DEAD 1
54 #define YF_WALLS 2
55 #define YF_END 4
56 #define YF_DEAD5 8
57
58 #define YF_NEAR_IVY 16
59 #define YF_NEAR_ELEM 32
60 #define YF_NEAR_OVER 64
61 #define YF_NEAR_RED 128
62 #define YF_REPEAT 512
63 #define YF_NEAR_TENT 1024
64
65 #define YF_START_AL 2048
66 #define YF_START_CR 4096
67 #define YF_CHAOS 8192
68 #define YF_RECALL 16384
69 #define YF_NEAR_FJORD 32768
70
71 #define YF_START_ANY (YF_START_AL|YF_START_CR)
72
73 struct yendorlevel {
74 eLand l;
75 int flags;
76 };
77
78 #define YENDORLEVELS 34
79 #endif
80
81 EX map<modecode_t, array<int, YENDORLEVELS>> bestscore;
82
83 EX eLand nexttostart;
84
85 yendorlevel levels[YENDORLEVELS] = {
86 {laNone, 0},
87 {laHell, YF_DEAD}, // FORCE BARRIERS?
88 {laGraveyard, YF_DEAD5},
89 {laDesert, YF_NEAR_IVY}, // IVY OR TENTACLE?
90 {laMinefield, YF_END}, // NOT WON, SEEMS OKAY
91 {laEmerald, 0}, // WON FINE
92 {laOvergrown, 0}, // WON, TOO EASY?
93 {laMotion, YF_START_AL | YF_END}, // NOT WON, SEEMS OKAY
94 {laAlchemist, 0}, // ALMOST WON
95 {laIvoryTower,YF_START_CR | YF_NEAR_ELEM | YF_REPEAT}, // won cool
96 {laMirrorOld, YF_NEAR_OVER}, // OK
97 {laWhirlpool, 0}, // cool
98 {laIce, YF_NEAR_ELEM}, // OK
99 {laHive, YF_NEAR_RED}, // OK
100 {laCaribbean, 0}, // seems OK
101 {laOcean, YF_WALLS}, // well... stupid, add Caribbean/Fjord
102 {laPalace, 0}, // more trapdoors!
103 {laZebra, 0}, // TOO HARD?
104 {laWineyard, 0}, // hard-ish
105 {laStorms, 0}, // ?
106 {laLivefjord, 0},
107 {laJungle, 0},
108 {laPower, YF_START_CR},
109 {laWildWest, 0},
110 {laWhirlwind, YF_NEAR_TENT},
111 {laHell, YF_CHAOS | YF_DEAD},
112 {laDragon, YF_DEAD},
113 {laReptile, 0},
114 {laTortoise, YF_RECALL},
115 {laCocytus, YF_NEAR_FJORD},
116 {laRuins, YF_DEAD},
117 {laCaves, YF_DEAD5},
118 {laWestWall, YF_START_CR},
119 {laEclectic, 0},
120 // {laVariant, YF_DEAD5}, (I do not think this works)
121 };
122
123 int tscorelast;
124
uploadScore()125 void uploadScore() {
126 int tscore = 0;
127 for(int i=1; i<YENDORLEVELS; i++)
128 if(bestscore[0][i]) tscore += 999 + bestscore[0][i];
129 // printf("Yendor score = %d\n", tscore);
130
131 if(tscore > tscorelast) {
132 tscorelast = tscore;
133 if(tscore >= 1000) achievement_gain("YENDC1", rg::global);
134 if(tscore >= 5000) achievement_gain("YENDC2", rg::global);
135 if(tscore >= 15000) achievement_gain("YENDC3", rg::global);
136 }
137
138 achievement_score(LB_YENDOR_CHALLENGE, tscore);
139 }
140
clev()141 EX yendorlevel& clev() { return levels[challenge]; }
142
changeland(int i,eLand l)143 EX eLand changeland(int i, eLand l) {
144 if(l == laIvoryTower) return laNone;
145 if((clev().flags & YF_START_ANY) && i < 20 && l != clev().l) return clev().l;
146 if((clev().flags & YF_END) && i > 80 && l == clev().l) return laIce;
147 return laNone;
148 }
149
150 eLand first, second, last;
151
152 #if HDR
153 struct yendorinfo {
154 cell *path[YDIST];
155 cell *actualKey;
156 bool found;
157 bool foundOrb;
158 int howfar;
159 bignum age;
yendorinfohr::yendor::yendorinfo160 yendorinfo() { actualKey = NULL; }
keyhr::yendor::yendorinfo161 cell* key() { return path[YDIST-1]; }
actual_keyhr::yendor::yendorinfo162 cell *actual_key() { return actualKey ? actualKey : key(); }
orbhr::yendor::yendorinfo163 cell* orb() { return path[0]; }
164 };
165 #endif
166
167 EX vector<yendorinfo> yi;
168
169 #if HDR
170 #define NOYENDOR 999999
171 #endif
172 EX int yii = NOYENDOR;
173
hardness()174 EX int hardness() {
175 if(peace::on) return 15; // just to generate monsters
176 if(!yendor::generating && !yendor::path && !yendor::on) return 0;
177 int thf = 0;
178 for(int i=0; i<isize(yi); i++) {
179 yendorinfo& ye ( yi[i] );
180 if(!ye.foundOrb && ye.howfar > 25)
181 thf += (ye.howfar - 25);
182 }
183 thf -= 2 * (YDIST - 25);
184 if(thf<0) thf = 0;
185 return items[itOrbYendor] * 5 + (thf * 5) / (YDIST-25);
186 }
187
188 #if HDR
189 enum eState { ysUntouched, ysLocked, ysUnlocked };
190 #endif
191
state(cell * yendor)192 EX eState state(cell *yendor) {
193 for(int i=0; i<isize(yi); i++) if(yi[i].path[0] == yendor)
194 return yi[i].found ? ysUnlocked : ysLocked;
195 return ysUntouched;
196 }
197
control(pathgen & p,int i,cellwalker & ycw)198 EX bool control(pathgen& p, int i, cellwalker& ycw) {
199
200 // change lands in the Challenge
201 if(i > BARLEV-6) {
202 p.last_id = i+7-BARLEV;
203 setdist(p.path[p.last_id], 7, p.path[i+6-BARLEV]);
204 if(yendor::challenge && !euclid && ycw.at->land != laIvoryTower) {
205 eLand ycl = yendor::changeland(i, ycw.at->land);
206 if(ycl) {
207 if(weirdhyperbolic) {
208 buildBarrierNowall(ycw.at, ycl);
209 }
210 else if(hyperbolic && ishept(ycw.at)) {
211 int bd = 2 + hrand(2) * 3;
212 buildBarrier(ycw.at, bd, ycl);
213 if(ycw.at->bardir != NODIR && ycw.at->bardir != NOBARRIERS)
214 extendBarrier(ycw.at);
215 }
216 }
217 }
218 }
219
220 // follow the branch in Yendorian
221 if(ycw.at->land == laEndorian) {
222 int bestval = -2000;
223 int best = 0, qbest = 0;
224 for(int d=0; d<ycw.at->type; d++) {
225 setdist(ycw.at, 7, ycw.peek());
226 cell *c1 = (ycw+d).cpeek();
227 int val = d * (ycw.at->type - d);
228 if(c1->wall == waTrunk) val += (i < YDIST-20 ? 1000 : -1000);
229 if(val > bestval) qbest = 0, bestval = val;
230 if(val == bestval) if(hrand(++qbest) == 0) best = d;
231 }
232 ycw += best;
233 return true;
234 }
235 return false;
236 }
237
check(cell * yendor)238 EX bool check(cell *yendor) {
239 int byi = isize(yi);
240 for(int i=0; i<isize(yi); i++) if(yi[i].path[0] == yendor) byi = i;
241 if(byi < isize(yi) && yi[byi].found) return false;
242 if(byi == isize(yi)) {
243 retry:
244 int creation_attempt = 0;
245 yendorinfo nyi;
246 nyi.howfar = 0;
247
248 generating = true;
249
250 auto p = generate_random_path_randomdir(yendor, YDIST-1, true);
251 for(int i=0; i<YDIST; i++) nyi.path[i] = p.path[i];
252
253 nyi.found = false;
254 nyi.foundOrb = false;
255
256 for(int i=1; i<YDIST; i++) {
257 setdist(nyi.path[i], 7, nyi.path[i-1]);
258 if(isEquidLand(nyi.path[i]->land)) {
259 // println(hlog, i, ": ", coastvalEdge(nyi.path[i]));
260 buildEquidistant(nyi.path[i]);
261 }
262 }
263
264 setdist(nyi.path[YDIST-1], 7, nyi.path[YDIST-2]);
265 cell *key = nyi.path[YDIST-1];
266
267 yendor::generating = false;
268
269 for(int b=10; b>=5; b--) setdist(key, b, nyi.path[YDIST-2]);
270
271 if(inmirror(key) || (geometry == gNormal && celldistance(key, yendor) < YDIST/2)) {
272 creation_attempt++;
273 if(creation_attempt > 100) {
274 yendor->item = itNone;
275 addMessage(XLAT("%The1 turned out to be an illusion!", itOrbYendor));
276 return false;
277 }
278 goto retry;
279 }
280
281 for(int i=-1; i<key->type; i++) {
282 cell *c2 = i >= 0 ? key->move(i) : key;
283 checkTide(c2);
284 c2->monst = moNone; c2->item = itNone;
285 if(!passable(c2, NULL, P_MIRROR | P_MONSTER)) {
286 if(c2->wall == waCavewall) c2->wall = waCavefloor;
287 else if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
288 else if(c2->wall == waLake) c2->wall = waFrozenLake;
289 else if(c2->land == laCaribbean) c2->wall = waCIsland;
290 else if(c2->land == laOcean) c2->wall = waCIsland;
291 else if(c2->land == laRedRock) c2->wall = waRed3;
292 else if(c2->land == laWhirlpool)
293 c2->wall = waBoat, c2->monst = moPirate, c2->item = itOrbWater;
294 else c2->wall = waNone;
295 }
296 if(c2->wall == waReptile) c2->wall = waNone;
297 if(c2->wall == waMineMine || c2->wall == waMineUnknown)
298 c2->wall = waMineOpen;
299 if(c2->wall == waTrapdoor && i == -1)
300 c2->wall = waGargoyleFloor;
301 if(c2->land == laLivefjord) {
302 c2->wall = waSea;
303 for(int i=0; i<c2->type; i++)
304 c2->move(i)->wall = waSea;
305 }
306 if(isGravityLand(c2->land) && key->land == c2->land && c2->land != laWestWall &&
307 c2->landparam < key->landparam && c2->wall != waTrunk)
308 c2->wall = waPlatform;
309 if(c2->land == laReptile && i >= 0)
310 c2->wall = waChasm;
311 if(c2->land == laMirrorWall && i == -1)
312 c2->wall = waNone;
313 }
314 key->item = itKey;
315
316 bool split_found = false;
317
318 if(key->land == laWestWall && trees_known()) {
319
320 int t = type_in(expansion, yendor, [yendor] (cell *c) { return celldistance(yendor, c); });
321 int maxage = 10;
322 for(int i=0; i<min(items[itOrbYendor], 8); i++)
323 maxage *= 10;
324
325 nyi.age = maxage - hrand(maxage/2);
326 bignum full_id = p.full_id_0 - nyi.age;
327 bool onlychild = true;
328
329 cellwalker ycw = p.start;
330 ycw--; if(S3 == 3) ycw--;
331
332 for(int i=0; i<YDIST-1; i++) {
333
334 if(i == 1) onlychild = true;
335 if(!onlychild) ycw++;
336 if(valence() == 3) ycw++;
337
338 onlychild = false;
339
340 for(int tch: expansion.children[t]) {
341 ycw++;
342 if(i == 1)
343 tch = type_in(expansion, ycw.cpeek(), [yendor] (cell *c) { return celldistance(yendor, c); });
344 auto& sub_id = expansion.get_descendants(YDIST-i-2, tch);
345 if(full_id < sub_id) {
346 if(!split_found && !(p.full_id_0 < sub_id)) {
347 // ycw.at->item = itRuby;
348 split_found = true;
349 setdist(ycw.at, 6, NULL);
350 auto tt = type_in(expansion, ycw.at, coastvalEdge);
351 // println(hlog, "at split, ydist-type: ", t, " coast-type: ", tt);
352 if(t != tt) {
353 // try again!
354 key->item = itNone;
355 return check(yendor);
356 }
357 }
358 t = tch;
359 break;
360 }
361
362 full_id.addmul(sub_id, -1);
363 if(!split_found) p.full_id_0.addmul(sub_id, -1);
364 onlychild = true;
365 }
366
367 if(inmirror(ycw)) ycw = mirror::reflect(ycw);
368 ycw += wstep;
369 setdist(ycw.at, 8, ycw.peek());
370 buildEquidistant(ycw.at);
371 // println(hlog, "actual key #", i, ": ", ycw.at->landparam);
372 }
373
374 nyi.actualKey = ycw.at;
375 ycw.at->item = itKey;
376 key->item = itNone;
377 }
378
379 yi.push_back(nyi);
380 }
381 dynamicval<bool> b(changes.on, false);
382 addMessage(XLAT("You need to find the right Key to unlock this Orb of Yendor!"));
383 if(yi[byi].actualKey)
384 addMessage(XLAT("You feel that these directions are %1 turns old.", yi[byi].age.get_str(100)));
385 if(yii != byi) {
386 yii = byi;
387 achievement_gain_once("YENDOR1");
388 playSound(yendor, "pickup-yendor");
389 return true;
390 }
391 return false;
392 }
393
onpath()394 EX void onpath() {
395 path = false;
396 if(yii < isize(yi)) {
397 for(int i=0; i<YDIST; i++) if(yi[yii].path[i]->cpdist <= 7) {
398 if(i > yi[yii].howfar) yi[yii].howfar = i;
399 path = true;
400 }
401 }
402 }
403
get_land_structure()404 EX eLandStructure get_land_structure() {
405 if(clev().flags & YF_CHAOS)
406 return lsChaos;
407 if(clev().l == laWhirlpool)
408 return lsSingle;
409 return lsNiceWalls;
410 }
411
init(int phase)412 EX void init(int phase) {
413 if(!on) return;
414
415 if(phase == 1) {
416 won = false;
417 if(!easy) items[itOrbYendor] = bestscore[modecode()][challenge];
418 land_structure = get_land_structure();
419 specialland = clev().l;
420 if(clev().flags & YF_START_AL) {
421 specialland = laAlchemist;
422 items[itElixir] = 50;
423 items[itFeather] = 50;
424 }
425 if(specialland == laPower)
426 items[itOrbSpeed] = 60, items[itOrbWinter] = 60;
427 if(clev().flags & YF_START_CR) {
428 specialland = laCrossroads;
429 }
430 if(specialland == laGraveyard) items[itBone] = 10;
431 if(specialland == laEmerald) items[itEmerald] = 10;
432 if(specialland == laCocytus) items[itFjord] = 10;
433 if(!euclid) {
434 if(clev().flags & YF_DEAD) items[itGreenStone] = 100;
435 if(clev().flags & YF_DEAD5) items[itGreenStone] = 5;
436 }
437 if(clev().flags & YF_RECALL) {
438 int yq = items[itOrbYendor];
439 items[itOrbRecall] = 60 - yq;
440 items[itOrbTime] = 60 - yq;
441 items[itOrbEnergy] = 60 - yq;
442 items[itOrbTeleport] = 60 - yq;
443 items[itOrbSpace] = 60 - yq;
444 items[itOrbDash] = 60 - yq;
445 items[itOrbFrog] = 60 - yq;
446 }
447 nexttostart = laNone;
448 }
449
450 if(phase == 2) {
451 cell *c2 = cwt.at->move(0);
452 c2->land = firstland;
453 if(firstland == laRlyeh) c2->wall = waNone;
454 yendor::check(c2);
455 if(clev().flags & YF_NEAR_IVY)
456 nexttostart = laJungle;
457 if(clev().flags & YF_NEAR_FJORD)
458 nexttostart = laLivefjord;
459 if(clev().flags & YF_NEAR_TENT)
460 nexttostart = laRlyeh;
461 if(clev().flags & YF_NEAR_ELEM) {
462 if(firstland == laIce) {
463 nexttostart = laEWater;
464 items[itWaterShard] = 10;
465 }
466 else nexttostart = laEAir;
467 }
468 if(clev().flags & YF_NEAR_OVER)
469 nexttostart = laOvergrown;
470 if(clev().flags & YF_NEAR_RED) {
471 nexttostart = laRedRock;
472 items[itRedGem] = 25;
473 }
474 if(clev().flags & YF_WALLS) {
475 items[itPirate] += 25;
476 items[itFjord] += 25;
477 }
478 if(clev().l == laWhirlpool) {
479 items[itWhirlpool] += 10;
480 items[itOrbWater] += 150;
481 }
482 }
483
484 if(phase == 3) {
485 cell *c2 = cwt.at->move(0);
486 makeEmpty(c2);
487 c2->item = itOrbYendor;
488 nexttostart = laNone;
489 if(clev().flags & YF_RECALL)
490 saveRecall(cwt);
491 }
492 }
493
levelUnlocked(int i)494 bool levelUnlocked(int i) {
495 yendorlevel& ylev(levels[i]);
496
497 eItem t = treasureType(ylev.l);
498 if(ylev.l != laWildWest && hiitemsMax(t) < 10) return false;
499 if((ylev.flags & YF_NEAR_ELEM) && hiitemsMax(itElemental) < 10) return false;
500 if((ylev.flags & YF_NEAR_RED) && hiitemsMax(itRedGem) < 10) return false;
501 if((ylev.flags & YF_NEAR_OVER) && hiitemsMax(itMutant) < 10) return false;
502 if((ylev.flags & YF_NEAR_TENT) && hiitemsMax(itStatue) < 10) return false;
503 if((ylev.flags & YF_NEAR_FJORD) && hiitemsMax(itFjord) < 10) return false;
504 if((ylev.flags & YF_CHAOS) && !chaosUnlocked) return false;
505 if((ylev.flags & (YF_DEAD|YF_DEAD5)) && hiitemsMax(itBone) < 10) return false;
506 if((ylev.flags & YF_RECALL) && hiitemsMax(itSlime) < 10) return false;
507 return true;
508 }
509
510 struct scoredata {
511 string username;
512 int scores[landtypes];
513 };
514 vector<scoredata> scoreboard;
515
516 EX const char *chelp =
517 "There are many possible solutions to the Yendor Quest. In the Yendor "
518 "Challenge, you will try many of them!\n\n"
519 "Each challenge takes part in a specific land, and you have to use what "
520 "you have available.\n\n"
521
522 "You need to obtain an Orb of Yendor in the normal game to activate "
523 "this challenge, and (ever) collect 10 treasures in one or two lands "
524 "to activate a specific level.\n\n"
525
526 "After you complete each challenge, you can try it again, on a harder "
527 "difficulty level.\n\n"
528
529 "All the solutions showcased in the Yendor Challenge work in the normal "
530 "play too. However, passages to other lands, and (sometimes) some land features "
531 "are disabled in the Yendor "
532 "Challenge, so that you have to use the expected method. Also, "
533 "the generation rules are changed slightly for the Palace "
534 "and Minefield while you are looking for the Orb of Yendor, "
535 "to make the challenge more balanced "
536 "(but these changes are also active during the normal Yendor Quest).\n\n"
537
538 "You get 1000 points for each challenge won, and 1 extra point for "
539 "each extra difficulty level.";
540
char_to_yendor(char c)541 int char_to_yendor(char c) {
542 if(c >= 'a' && c <= 'z')
543 return c - 'a' + 1;
544 if(c >= 'A' && c <= 'Z')
545 return c - 'A' + 1 + 26;
546 return 0;
547 }
548
name(int i)549 EX string name(int i) {
550 yendorlevel& ylev(levels[i]);
551
552 string s = XLATT1(ylev.l);
553 if(!euclid) {
554 if(ylev.flags & YF_CHAOS) { s = "Chaos mode"; }
555 if(ylev.flags & YF_NEAR_IVY) { s += "+"; s += XLATT1(laJungle); }
556 if(ylev.flags & YF_NEAR_FJORD) { s += "+"; s += XLATT1(laLivefjord); }
557 if(ylev.flags & YF_NEAR_TENT) { s += "+"; s += XLATT1(laRlyeh); }
558 if(ylev.flags & YF_NEAR_ELEM) { s += "+"; s += XLATT1(laElementalWall); }
559 if(ylev.flags & YF_NEAR_OVER) { s += "+"; s += XLATT1(laOvergrown); }
560 if(ylev.flags & YF_NEAR_RED) { s += "+"; s += XLATT1(laRedRock); }
561 if(ylev.flags & YF_START_AL) { s += "+"; s += XLATT1(laAlchemist); }
562 if(ylev.flags & YF_DEAD) { s += "+"; s += XLATT1(itGreenStone); }
563 if(ylev.flags & YF_RECALL) { s += "+"; s += XLATT1(itOrbRecall); }
564 }
565
566 return s;
567 }
568
569
showMenu()570 EX void showMenu() {
571 set_priority_board(LB_YENDOR_CHALLENGE);
572 int s = vid.fsize;
573 vid.fsize = vid.fsize * 4/5;
574 dialog::init(XLAT("Yendor Challenge"), iinf[itOrbYendor].color, 150, 100);
575
576 for(int i=1; i<YENDORLEVELS; i++) {
577 string s;
578
579 if(autocheat || levelUnlocked(i)) {
580 s = name(i);
581 }
582
583 else {
584 s = "(locked)";
585 }
586
587 string v;
588 if(bestscore[modecode()][i] == 1)
589 v = XLAT(" (won!)");
590 else if(bestscore[modecode()][i])
591 v = XLAT(" (won at level %1!)", its(bestscore[modecode()][i]));
592
593 dialog::addSelItem(s, v, i > 26 ? 'A' + i - 27 : 'a' + i-1);
594 }
595
596 dialog::addBreak(60);
597 if (yendor::on)
598 dialog::addItem(XLAT("Return to the normal game"), '0');
599 dialog::addSelItem(
600 easy ? XLAT("Challenges do not get harder") : XLAT("Each challenge gets harder after each victory"),
601 " " + (easy ? XLAT("easy") : XLAT("challenge")), '1');
602
603 dialog::addBack();
604 dialog::addHelp();
605 dialog::display();
606
607 int yc = char_to_yendor(getcstat);
608
609 if(yc > 0 && yc < YENDORLEVELS) {
610 subscoreboard scorehere;
611
612 for(int i=0; i<isize(scoreboard); i++) {
613 int sc = scoreboard[i].scores[yc];
614 if(sc > 0)
615 scorehere.push_back(
616 make_pair(-sc, scoreboard[i].username));
617 }
618
619 displayScore(scorehere, vid.xres / 4);
620 }
621
622 yendor::uploadScore();
623 vid.fsize = s;
624
625 keyhandler = [] (int sym, int uni) {
626 dialog::handleNavigation(sym, uni);
627 int new_challenge = char_to_yendor(uni);
628 if(new_challenge && new_challenge < YENDORLEVELS) {
629 if(levelUnlocked(new_challenge) || autocheat) dialog::do_if_confirmed([new_challenge] {
630 challenge = new_challenge;
631 restart_game(yendor::on ? rg::nothing : rg::yendor);
632 });
633 else
634 addMessage("Collect 10 treasures in various lands to unlock the challenges there");
635 }
636 else if(uni == '0') {
637 if(yendor::on) restart_game(rg::yendor);
638 }
639 else if(uni == '1') easy = !easy;
640 else if(uni == '2' || sym == SDLK_F1) gotoHelp(chelp);
641 else if(doexiton(sym, uni)) popScreen();
642 };
643 }
644
collected(cell * c2)645 EX void collected(cell* c2) {
646 LATE( collected(c2); )
647 playSound(c2, "tada");
648 items[itOrbShield] += 31;
649 for(int i=0; i<isize(yendor::yi); i++)
650 if(yendor::yi[i].path[0] == c2)
651 changes.value_set(yendor::yi[i].foundOrb, true);
652 // Shielding always, so that we know that it protects!
653 int powers = 0;
654 for(int i=0; i<1000 && powers < 4; i++) {
655 vector< pair<eItem, int> > choices = {
656 {itOrbSpeed, 31},
657 {itOrbLightning, 78},
658 {itOrbFlash, 78},
659 {itOrbTime, 78},
660 {itOrbWinter, 151},
661 {itOrbDigging, 151},
662 {itOrbTeleport, 151},
663 {itOrbThorns, 151},
664 {itOrbInvis, 151},
665 {itOrbPsi, 151},
666 {itOrbAether, 151},
667 {itOrbFire, 151},
668 {itOrbSpace, 78}
669 };
670 auto p = hrand_elt(choices);
671 auto orb = p.first;
672 if(items[orb] && i < 500) continue;
673 if(among(getOLR(orb, getPrizeLand()), olrDangerous, olrUseless, olrForbidden)) continue;
674 items[orb] += p.second;
675 powers++;
676 }
677 items[itOrbYendor]++;
678 items[itKey]--;
679 changes.value_set(yendor::everwon, true);
680 if(yendor::on) {
681 changes.value_set(yendor::won, true);
682 if(!cheater) {
683 yendor::bestscore[modecode()][yendor::challenge] =
684 max(yendor::bestscore[modecode()][yendor::challenge], items[itOrbYendor]);
685 yendor::uploadScore();
686 }
687 }
688 addMessage(XLAT("CONGRATULATIONS!"));
689 achievement_collection(itOrbYendor);
690 achievement_victory(false);
691 }
692
__anon1421716e0502() 693 auto hooks = addHook(hooks_clearmemory, 0, [] () {
694 yendor::yii = NOYENDOR; yendor::yi.clear();
695 }) + addHook(hooks_removecells, 0, [] () {
__anon1421716e0702(yendorinfo& i) 696 eliminate_if(yendor::yi, [] (yendorinfo& i) {
697 for(int j=0; j<YDIST; j++) if(is_cell_removed(i.path[j])) {
698 DEBB(DF_MEMORY, ("removing a Yendor"));
699 if(&yi[yii] == &i) yii = NOYENDOR;
700 return true;
701 }
702 return false;
703 });
704 });
705 EX }
706
707 #define MAXTAC 20
708 EX namespace tactic {
709
710 EX bool on = false;
711 EX int id;
712
713 map<modecode_t, array<int, landtypes>> recordsum;
714 map<modecode_t, array<array<int, MAXTAC>, landtypes> > lsc;
715
716 eLand lasttactic;
717
718 struct scoredata {
719 string username;
720 int scores[landtypes];
721 };
722 map<modecode_t, vector<scoredata>> scoreboard;
723
chances(eLand l,modecode_t xc IS (modecode ()))724 EX int chances(eLand l, modecode_t xc IS(modecode())) {
725 if(xc != 0 && l != laCamelot) return 3;
726 for(auto& ti: land_tac)
727 if(ti.l == l)
728 return ti.tries;
729 return 0;
730 }
731
tacmultiplier(eLand l)732 int tacmultiplier(eLand l) {
733 if(modecode() != 0 && l != laCamelot) return 1;
734 if(modecode() != 0 && l == laCamelot) return 3;
735 for(auto& ti: land_tac)
736 if(ti.l == l)
737 return ti.multiplier;
738 return 0;
739 }
740
tacticUnlocked(eLand l)741 bool tacticUnlocked(eLand l) {
742 if(autocheat) return true;
743 if(l == laWildWest || l == laDual) return true;
744 return hiitemsMax(treasureType(l)) * landMultiplier(l) >= 20;
745 }
746
record(eLand land,int score,modecode_t xc IS (modecode ()))747 EX void record(eLand land, int score, modecode_t xc IS(modecode())) {
748 if(land >=0 && land < landtypes) {
749 for(int i=MAXTAC-1; i; i--) lsc[xc][land][i] = lsc[xc][land][i-1];
750 tactic::lsc[xc][land][0] = score;
751 }
752 int t = chances(land, xc);
753 int csum = 0;
754 for(int i=0; i<t; i++) if(lsc[xc][land][i] > 0) csum += lsc[xc][land][i];
755 if(csum > recordsum[xc][land]) recordsum[xc][land] = csum;
756 }
757
record()758 void record() {
759 record(lasttactic, items[treasureType(lasttactic)]);
760 }
761
unrecord(eLand land,flagtype xc=modecode ())762 void unrecord(eLand land, flagtype xc = modecode()) {
763 if(land >=0 && land < landtypes) {
764 for(int i=0; i<MAXTAC-1; i++) lsc[xc][land][i] = lsc[xc][land][i+1];
765 lsc[xc][land][MAXTAC-1] = -1;
766 }
767 }
768
unrecord()769 void unrecord() {
770 unrecord(lasttactic);
771 }
772
773 int tscorelast;
774
uploadScoreCode(int code,int lb)775 void uploadScoreCode(int code, int lb) {
776 int tscore = 0;
777 for(int i=0; i<landtypes; i++)
778 tscore += recordsum[code][i] * tacmultiplier(eLand(i));
779 // printf("PTM score = %d\n", tscore);
780
781 if(code == 0 && tscore > tscorelast) {
782 tscorelast = tscore;
783 if(tscore >= 1000) achievement_gain("PTM1", 'x');
784 if(tscore >= 5000) achievement_gain("PTM2", 'x');
785 if(tscore >= 15000) achievement_gain("PTM3", 'x');
786 }
787 achievement_score(lb, tscore);
788 }
789
uploadScore()790 void uploadScore() {
791 uploadScoreCode(0, LB_PURE_TACTICS);
792 uploadScoreCode(2, LB_PURE_TACTICS_SHMUP);
793 uploadScoreCode(4, LB_PURE_TACTICS_COOP);
794 }
795
showMenu()796 EX void showMenu() {
797
798 flagtype xc = modecode();
799
800 if(xc == 0) set_priority_board(LB_PURE_TACTICS);
801 if(xc == 2) set_priority_board(LB_PURE_TACTICS_SHMUP);
802 if(xc == 4) set_priority_board(LB_PURE_TACTICS_COOP);
803
804 cmode = sm::ZOOMABLE;
805 if(mouseovers == "" || mouseovers == " ")
806 mouseovers = XLAT("pure tactics mode");
807 else
808 mouseovers = XLAT("pure tactics mode") + " - " + mouseovers;
809
810 if(dialog::infix != "") mouseovers = mouseovers + " - " + dialog::infix;
811 else mouseovers = mouseovers + " - " + XLAT("press letters to search");
812
813 {
814 dynamicval<bool> t(tactic::on, true);
815 generateLandList([] (eLand l) {
816 if(dialog::infix != "" && !dialog::hasInfix(linf[l].name)) return false;
817 return !!(land_validity(l).flags & lv::appears_in_ptm);
818 });
819 }
820
821 int nl = isize(landlist);
822
823 int nlm = nl;
824 int ofs = dialog::infix != "" ? 0 : dialog::handlePage(nl, nlm, (nl+1)/2);
825
826 int vf = nlm ? min((vid.yres-64-vid.fsize) / nlm, vid.xres/40) : vid.xres/40;
827
828 int xr = vid.xres / 64;
829
830 if(on) record(specialland, items[treasureType(specialland)]);
831
832 getcstat = SDLK_ESCAPE;
833
834 map<int, int> land_for;
835
836 for(int i=0; i<nl; i++) {
837 int i1 = i + ofs;
838 eLand l = landlist[i1];
839
840 int i0 = 56 + i * vf;
841 color_t col;
842
843 int ch = chances(l);
844
845 if(!ch) continue;
846
847 bool unlocked = tacticUnlocked(l);
848
849 if(unlocked) col = linf[l].color; else col = 0x202020;
850
851 int keyhere;
852 if(nlm <= 9) {
853 keyhere = '1' + i;
854 if(displayfrZH(xr*1, i0, 1, vf-4, s0+char(keyhere), 0xFFFFFF, 0))
855 getcstat = keyhere;
856 }
857 else
858 keyhere = 1000 + i1;
859 land_for[keyhere] = i1;
860
861 if(displayfrZH(xr*(nlm <= 9 ? 2.5 : 1), i0, 1, vf-4, XLAT1(linf[l].name), col, 0) && unlocked)
862 getcstat = keyhere;
863
864 if(unlocked || autocheat) {
865 for(int ii=0; ii<ch; ii++)
866 if(displayfrZH(xr*(24+2*ii), i0, 1, (vf-4)*4/5, lsc[xc][l][ii] > 0 ? its(lsc[xc][l][ii]) : "-", col, 16))
867 getcstat = keyhere;
868
869 if(displayfrZH(xr*(24+2*10), i0, 1, (vf-4)*4/5,
870 its(recordsum[xc][l]) + " x" + its(tacmultiplier(l)), col, 0))
871 getcstat = keyhere;
872 }
873 else {
874 int m = landMultiplier(l);
875 displayfrZ(xr*26, i0, 1, (vf-4)*4/5,
876 XLAT("Collect %1x %2 to unlock", its((20+m-1)/m), treasureType(l)),
877 col, 0);
878 }
879 }
880
881 dialog::displayPageButtons(3, dialog::infix == "");
882
883 uploadScore();
884 if(on) unrecord(specialland);
885
886 if(land_for.count(getcstat)) {
887 int ld = landlist[land_for.at(getcstat)];
888 subscoreboard scorehere;
889 for(int i=0; i<isize(scoreboard[xc]); i++) {
890 int sc = scoreboard[xc][i].scores[ld];
891 if(sc > 0)
892 scorehere.push_back(
893 make_pair(-sc, scoreboard[xc][i].username));
894 }
895 displayScore(scorehere, xr * 50);
896 }
897
898 keyhandler = [land_for] (int sym, int uni) {
899 if(land_for.count(uni)) {
900 eLand ll = landlist[land_for.at(uni)];
901 dialog::do_if_confirmed([ll] {
902 stop_game();
903 specialland = ll;
904 restart_game(tactic::on ? rg::nothing : rg::tactic);
905 });
906 }
907 else if(uni == '0') {
908 if(tactic::on) {
909 stop_game();
910 firstland = laIce;
911 restart_game(rg::tactic);
912 }
913 else popScreen();
914 }
915
916 else if(sym == SDLK_F1) gotoHelp(
917 "In the pure tactics mode, you concentrate on a specific land. "
918 "Your goal to obtain as high score as possible, without using "
919 "features of the other lands. You can then compare your score "
920 "with your friends!\n\n"
921
922 "You need to be somewhat proficient in the normal game to "
923 "unlock the given land in this challenge "
924 "(collect 20 treasure in the given land, or 2 in case of Camelot).\n\n"
925
926 "Since getting high scores in some lands is somewhat luck dependent, "
927 "you play each land N times, and your score is based on N consecutive "
928 "plays. The value of N depends on how 'fast' the land is to play, and "
929 "how random it is.\n\n"
930
931 "In the Caribbean, you can access Orbs of Thorns, Aether, and "
932 "Space if you have ever collected 25 treasure in their native lands.\n\n"
933
934 "The rate of treasure spawn is static in this mode. It is not "
935 "increased by killing monsters.\n\n"
936
937 "Good luck, and have fun!"
938 );
939 else if(dialog::infix == "" && dialog::handlePageButtons(uni)) ;
940 else if(dialog::editInfix(uni)) ;
941 else if(doexiton(sym, uni)) popScreen();
942 };
943 }
944
start()945 EX void start() {
946 dialog::infix = "";
947 popScreenAll();
948 pushScreen(tactic::showMenu);
949 }
950 EX }
951
952 map<string, modecode_t> code_for;
953 EX map<modecode_t, string> meaning;
954
955 char xcheat;
956
save_mode_data(hstream & f)957 void save_mode_data(hstream& f) {
958 mapstream::save_geometry(f);
959
960 if(yendor::on || tactic::on)
961 f.write<char>(0);
962 else
963 f.write<char>(land_structure);
964 f.write<char>(shmup::on);
965 f.write<char>(inv::on);
966 #if CAP_TOUR
967 f.write<char>(tour::on);
968 #else
969 f.write<char>(false);
970 #endif
971 f.write<char>(peace::on);
972 f.write<char>(peace::otherpuzzles);
973 f.write<char>(peace::explore_other);
974 f.write<char>(multi::players);
975 f.write<char>(xcheat);
976 if(casual) {
977 f.write<char>(1);
978 }
979 }
980
modecode(int mode)981 EX modecode_t modecode(int mode) {
982 modecode_t x = legacy_modecode();
983 if(x != UNKNOWN) return x;
984
985 xcheat = (cheater ? 1 : 0);
986 shstream ss;
987 ss.write(ss.vernum);
988 save_mode_data(ss);
989
990 if(code_for.count(ss.s)) return code_for[ss.s];
991
992 if(mode == 1) return UNKNOWN;
993
994 modecode_t next = 100000;
995 while(meaning.count(next)) next++;
996
997 meaning[next] = ss.s;
998 code_for[ss.s] = next;
999
1000 if(mode == 2) return next;
1001
1002 FILE *f = fopen(scorefile, "at");
1003 string s = as_hexstring(ss.s);
1004 fprintf(f, "MODE %d %s\n", next, s.c_str());
1005 fclose(f);
1006
1007 return next;
1008 }
1009
load_modecode_line(string s)1010 EX void load_modecode_line(string s) {
1011 int code = atoi(&s[5]);
1012 int pos = 5;
1013 while(s[pos] != ' ' && s[pos]) pos++;
1014 if(!s[pos]) return;
1015 pos++;
1016 string t = from_hexstring(s.substr(pos));
1017 code_for[t] = code;
1018 meaning[code] = t;
1019 }
1020
1021 EX namespace peace {
1022
1023 EX bool on = false;
1024 EX bool hint = false;
1025
1026 EX bool otherpuzzles = true;
1027
1028 EX bool explore_other = false;
1029
1030 vector<eLand> simonlevels = {
1031 laCrossroads, laCrossroads2, laRlyeh,
1032 laDesert, laCaves, laAlchemist, laEmerald,
1033 laWineyard, laDeadCaves, laRedRock, laPalace,
1034 laLivefjord, laDragon
1035 };
1036
1037 vector<eLand> puzzlelevels = {
1038 laBurial, laTortoise, laCamelot, laPalace
1039 };
1040
1041 vector<eLand> explorelevels = {
1042 laIce, laJungle, laMirror, laDryForest, laCaribbean, laOcean, laZebra,
1043 laOvergrown, laWhirlwind, laWarpCoast, laReptile,
1044 laElementalWall, laAlchemist
1045 };
1046
1047 vector<eLand> levellist;
1048
getNext(eLand last)1049 eLand getNext(eLand last) {
1050 if(!peace::on) return laNone;
1051 if(levellist.empty()) showMenu();
1052 if(isElemental(last) && hrand(100) < 90)
1053 return laNone;
1054 else if(createOnSea(last))
1055 return getNewSealand(last);
1056 else if(isCrossroads(last)) {
1057 while(isCrossroads(last) || last == laCaribbean || last == laCamelot)
1058 last = hrand_elt(levellist);
1059 if(last == laElementalWall) last = laEFire;
1060 return last;
1061 }
1062 else return pick(laCrossroads, laCrossroads2);
1063 }
1064
isAvailable(eLand l)1065 bool isAvailable(eLand l) {
1066 for(int i=0; explorelevels[i]; i++) if(explorelevels[i] == l) return true;
1067 return false;
1068 }
1069
1070 EX namespace simon {
1071
1072 vector<cell*> path;
1073 int tobuild;
1074
build()1075 void build() {
1076 if(otherpuzzles || !on) return;
1077 while(isize(path) < tobuild) {
1078 cell *cp = path[isize(path)-1];
1079 cell *cp2 = path[isize(path)-2];
1080 vector<pair<cell*, cell*>> clister;
1081 clister.emplace_back(cp, cp);
1082
1083 int id = 0;
1084 manual_celllister cl;
1085 while(id < isize(clister)) {
1086 cell *c = clister[id].first;
1087 cell *fr = clister[id].second;
1088 setdist(c, 5, NULL);
1089
1090 forCellEx(c2,c)
1091 if(!cl.listed(c2) && passable(c2, c, 0) && (c2->land == specialland || c2->land == laTemple) && !c2->item) {
1092 if(!id) fr = c2;
1093 bool next;
1094 if(specialland == laRlyeh)
1095 next = c2->land == laTemple && (cp2->land == laRlyeh || celldistAlt(c2) < celldistAlt(cp2) - 8);
1096 else
1097 next = celldistance(c2, cp2) == 8;
1098 if(next) {
1099 path.push_back(fr);
1100 fr->item = itDodeca;
1101 goto again;
1102 }
1103 clister.emplace_back(c2, fr);
1104 cl.add(c2);
1105 }
1106 id++;
1107 }
1108 printf("Path broken, searched = %d\n", id);
1109 for(auto t: clister) t.first->item = itPirate;
1110 return;
1111 again: ;
1112 }
1113 }
1114
extend()1115 EX void extend() {
1116 int i = 0;
1117 while(i<isize(path) && path[i]->item != itDodeca) i++;
1118 if(tobuild == i+9)
1119 addMessage("You must collect all the dodecahedra on the path!");
1120 tobuild = i + 9;
1121 build();
1122 }
1123
init()1124 EX void init() {
1125 tobuild = 0;
1126 if(!on) return;
1127 if(otherpuzzles) { items[itGreenStone] = 500; return; }
1128 cell *c2 = cwt.at->move(0);
1129 makeEmpty(c2);
1130 c2->item = itOrbYendor;
1131
1132 path.clear();
1133 path.push_back(cwt.at);
1134 path.push_back(c2);
1135 extend();
1136 }
1137
restore()1138 EX void restore() {
1139 for(int i=1; i<isize(path); i++)
1140 if(path[i]->item == itNone && items[itDodeca])
1141 path[i]->item = itDodeca, items[itDodeca]--;
1142 }
1143 EX }
1144
1145 EX bool in_minefield, in_dual;
1146
reset_modes()1147 void reset_modes() {
1148 stop_game();
1149 if(in_minefield) {
1150 geometry = gNormal;
1151 variation = eVariation::bitruncated;
1152 in_minefield = false;
1153 }
1154 if(in_dual) {
1155 stop_game_and_switch_mode(rg::dualmode);
1156 geometry = gNormal;
1157 variation = eVariation::bitruncated;
1158 in_dual = false;
1159 }
1160 }
1161
showMenu()1162 EX void showMenu() {
1163 string title =
1164 !otherpuzzles ? XLAT("memory game") :
1165 explore_other ? XLAT("exploration") :
1166 XLAT("puzzles");
1167 dialog::init(title, 0x40A040, 150, 100);
1168
1169 int kind = 0;
1170
1171 if(!otherpuzzles) {
1172 levellist = simonlevels, kind = 1;
1173 dialog::addInfo("Collect as many dodecahedra as you can!");
1174 dialog::addInfo("You have to return to the starting location!");
1175 dialog::addBreak(50);
1176 }
1177 else if(explore_other)
1178 levellist = explorelevels, kind = 2;
1179 else {
1180 levellist = puzzlelevels, kind = 0;
1181 dialog::addInfo("This mode removes roguelike elements,");
1182 dialog::addInfo("focusing on puzzles and exploration");
1183 dialog::addBreak(50);
1184 }
1185
1186 char key = 'a';
1187 for(auto lev: levellist) {
1188 if(kind == 0) switch(lev) {
1189 case laBurial:
1190 dialog::addItem("excavate the treasures using your magical shovel", 'a');
1191 break;
1192 case laTortoise:
1193 dialog::addItem("find an adult tortoise matching the baby", 'b');
1194 break;
1195 case laCamelot:
1196 dialog::addItem("find the center of the Round Table in Camelot", 'c');
1197 break;
1198 case laPalace:
1199 dialog::addItem("follow the mouse", 'd');
1200 break;
1201 default: ;
1202 }
1203 else
1204 dialog::addItem(XLAT1(linf[lev].name), key++);
1205 dialog::add_action([lev] {
1206 dialog::do_if_confirmed([lev] {
1207 reset_modes();
1208 firstland = specialland = lev;
1209 if(!peace::on)
1210 stop_game_and_switch_mode(rg::peace);
1211 start_game();
1212 popScreenAll();
1213 });
1214 });
1215 }
1216
1217 if(kind == 1) {
1218 dialog::addBreak(100);
1219 dialog::addItem(XLAT("other puzzles"), '1');
1220 dialog::add_action([] { otherpuzzles = true; explore_other = false; });
1221 }
1222
1223 if(kind == 2) {
1224 dialog::addBreak(100);
1225 dialog::addItem(XLAT("other puzzles"), '1');
1226 dialog::add_action([] { otherpuzzles = true; explore_other = false; });
1227 }
1228
1229 if(kind == 0) {
1230 dialog::addItem(XLAT("hyperbolic Minesweeper"), 'e');
1231 dialog::add_action([] {
1232 dialog::do_if_confirmed([] {
1233 reset_modes();
1234 if(peace::on) stop_game_and_switch_mode(rg::peace);
1235 specialland = firstland = laMinefield;
1236 if(!bounded) {
1237 geometry = gBring;
1238 variation = eVariation::goldberg;
1239 gp::param = gp::loc(2, 1);
1240 mine_adjacency_rule = true;
1241 in_minefield = true;
1242 bounded_mine_percentage = .2;
1243 }
1244 start_game();
1245 popScreenAll();
1246 });
1247 });
1248 dialog::addItem(XLAT("dual geometry puzzle"), 'f');
1249 dialog::add_action([] {
1250 dialog::do_if_confirmed([] {
1251 reset_modes();
1252 if(peace::on) stop_game_and_switch_mode(rg::peace);
1253 restart_game(rg::dualmode);
1254 in_dual = true;
1255 popScreenAll();
1256 pushScreen(dpgen::show_menu);
1257 });
1258 });
1259 dialog::addItem(XLAT("memory game"), 'g');
1260 dialog::add_action([] { otherpuzzles = false; });
1261 dialog::addItem(XLAT("exploration"), 'h');
1262 dialog::add_action([] { explore_other = true; });
1263 }
1264
1265 dialog::addBreak(100);
1266
1267 dialog::addBoolItem(XLAT("display hints"), hint, '2');
1268 dialog::add_action([] {
1269 hint = !hint; popScreen();
1270 });
1271 dialog::addItem(XLAT("Return to the normal game"), '0');
1272 dialog::add_action([] {
1273 reset_modes();
1274 if(peace::on) stop_game_and_switch_mode(rg::peace);
1275 });
1276
1277 dialog::addBack();
1278 dialog::display();
1279 }
1280
1281 auto aNext = addHook(hooks_nextland, 100, getNext);
1282 }
1283
1284 #if CAP_COMMANDLINE
read_mode_args()1285 int read_mode_args() {
1286 using namespace arg;
1287 if(argis("-Y")) {
1288 yendor::on = true;
1289 shift(); yendor::challenge = argi();
1290 }
1291 else if(argis("-peace")) {
1292 peace::otherpuzzles = true;
1293 stop_game_and_switch_mode(peace::on ? 0 : rg::peace);
1294 }
1295 else if(argis("-pmem")) {
1296 peace::otherpuzzles = false;
1297 stop_game_and_switch_mode(peace::on ? 0 : rg::peace);
1298 }
1299 TOGGLE('T', tactic::on, stop_game_and_switch_mode(rg::tactic))
1300
1301 else return 1;
1302 return 0;
1303 }
1304
1305 auto ah = addHook(hooks_args, 0, read_mode_args);
1306 #endif
1307 }
1308