1 // Hyperbolic Rogue -- starting and ending games
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3
4 /** \file system.cpp
5 * \brief changing game modes, starting, closing, loading and saving games
6 */
7
8 #include "hyper.h"
9 namespace hr {
10
11 #if HDR
12 /** \brief This namespace has constants used as parameters in functions such as restart_game and wrongmode. */
13 namespace rg {
14 static const char nothing = 0;
15 static const char peace = 'P';
16 static const char inv = 'i';
17 static const char chaos = 'C';
18 static const char tactic = 't';
19 static const char tour = 'T';
20 static const char yendor = 'y';
21 static const char shmup = 's';
22 static const char randpattern = 'r';
23 static const char princess = 'p';
24 static const char daily = 'd';
25 static const char daily_off = 'D';
26 static const char racing = 'R';
27 static const char dualmode = 'U';
28 static const char heptagons = '7';
29
30 /** \brief wrongmode only -- marks 'global' achievements not related to the current mode */
31 static const char global = 'x';
32 /** \brief wrongmode only -- change vid.scfg.players then restart_game(rg::nothing) instead */
33 static const char multi = 'm';
34 /** \brief wrongmode only -- mark achievements for special geometries/variations */
35 static const char special_geometry = 'g';
36 }
37 #endif
38
39 /** \brief is a game map currently loaded (it is false after hr::stop_game and true after hr::start_game) */
40 EX bool game_active;
41
42 /** \brief God mode */
43 EX bool autocheat;
44
45 /** \brief which wall should we fill the Canvas with */
46 EX eWall canvas_default_wall = waNone;
47
48 /** \brief the number of Black Lotuses collected -- but updated only if we manage to escape */
49 EX int truelotus;
50
51 EX int asteroids_generated, asteroid_orbs_generated;
52
53 EX time_t timerstart, savetime;
54 EX bool timerstopped;
55 EX int savecount;
56 EX int save_turns;
57 EX bool doCross = false;
58
59 EX bool gamegen_failure;
60
61 EX eLand top_land;
62
63 /** \brief a comparator for version number strings */
verless(string v,string cmp)64 EX bool verless(string v, string cmp) {
65 if(isdigit(v[0]) && isdigit(v[1]))
66 v = "A" + v;
67 if(isdigit(cmp[0]) && isdigit(cmp[1]))
68 cmp = "A" + cmp;
69 return v < cmp;
70 }
71
72 /** \brief Hooks for welcomeMessage. Return true to capture. */
73 EX hookset<bool()> hooks_welcome_message;
74
75 /** \brief Print the welcome message during the start of game. Depends on the current mode and other settings. */
welcomeMessage()76 EX void welcomeMessage() {
77 if(callhandlers(false, hooks_welcome_message)) return;
78 #if CAP_TOUR
79 else if(tour::on) return; // displayed by tour
80 #endif
81 else if(princess::challenge) {
82 kills[moVizier] = 1;
83 princess::forceMouse = true;
84 if(yendor::everwon)
85 items[itGreenStone] = 99;
86 addMessage(XLAT("Welcome to %the1 Challenge!", moPrincess));
87 addMessage(XLAT("The more Hypersian Rugs you collect, the harder it is.", moPrincess));
88 }
89 else if(randomPatternsMode)
90 addMessage(XLAT("Welcome to the Random Pattern mode!"));
91 else if(tactic::on)
92 addMessage(XLAT("You are playing %the1 in the Pure Tactics mode.", firstland));
93 else if(yendor::on)
94 addMessage(XLAT("Welcome to the Yendor Challenge %1!", its(yendor::challenge)));
95 else if(peace::on) ; // no welcome message
96 else if(shmup::on) ; // welcome message given elsewhere
97 else if(euclid)
98 addMessage(XLAT("Welcome to the Euclidean mode!"));
99 else if(specialland == laHalloween && BITRUNCATED && among(geometry, gSphere, gElliptic))
100 addMessage(XLAT("Welcome to Halloween!"));
101 else if(elliptic && WDIM == 2)
102 addMessage(XLAT("Good luck in the elliptic plane!"));
103 else if(elliptic)
104 addMessage(XLAT("Good luck in the elliptic space!"));
105 else if(sphere)
106 addMessage(XLAT("Welcome to Spherogue!"));
107 else if(in_s2xe())
108 addMessage(XLAT("Welcome to Spherindrogue!"));
109 else if(in_h2xe())
110 addMessage(XLAT("Welcome to Hyper-X-R-Rogue!"));
111 else if(sol)
112 addMessage(XLAT("Welcome to SolvRogue!"));
113 else if(nil)
114 addMessage(XLAT("Welcome to NilRogue!"));
115 else if(sl2) {
116 if(hybrid::csteps == 0)
117 addMessage(XLAT("Welcome to CoverRogue!"));
118 else if(cgi.psl_steps % hybrid::csteps == 0)
119 addMessage(XLAT("Welcome to PSL(2,R)-ogue!"));
120 else
121 addMessage(XLAT("Welcome to SL(2,R)-ogue!"));
122 if(hybrid::underlying == gNormal && BITRUNCATED)
123 addMessage(XLAT("Hint: this is more playable with pure {7,3} or pure {5,4}"));
124 }
125 else if(PURE && geometry == gNormal && !cheater)
126 addMessage(XLAT("Welcome to the Heptagonal Mode!"));
127 else if(cheater || autocheat)
128 addMessage(XLAT("Welcome to HyperRogue! (cheat mode on)"));
129 else
130 addMessage(XLAT("Welcome to HyperRogue!"));
131
132 if(!safety && !daily::on) {
133 auto lv = land_validity(specialland);
134 if(lv.flags & lv::display_error_message)
135 addMessage(XLAT(lv.msg));
136 }
137
138 #if ISMAC
139 addMessage(XLAT("Press F1 or right-shift-click things for help."));
140 #elif !ISMOBILE
141 addMessage(XLAT("Press F1 or right-click things for help."));
142 #endif
143 }
144
145 /** \brief These hooks are called at the start of initgame. */
146 EX hookset<void()> hooks_initgame;
147
148 /** \brief These hooks are called at the end of initgame. */
149 EX hookset<void()> hooks_post_initgame;
150
151 EX bool ineligible_starting_land;
152
153 /** \brief initialize the game */
initgame()154 EX void initgame() {
155 DEBBI(DF_INIT, ("initGame"));
156 callhooks(hooks_initgame);
157
158 if(!safety) fix_land_structure_choice();
159
160 if(multi::players < 1 || multi::players > MAXPLAYER)
161 multi::players = 1;
162 multi::whereto[0].d = MD_UNDECIDED;
163 multi::cpid = 0;
164
165 yendor::init(1);
166
167 if(safety && safetyseed) {
168 shrand(safetyseed);
169 firstland = safetyland;
170 }
171
172 #if CAP_RACING
173 if(racing::on) racing::apply_seed();
174 #endif
175
176 if(!safety) {
177 firstland = specialland;
178 ineligible_starting_land = !landUnlocked(specialland);
179 }
180
181 if(firstland == laNone || firstland == laBarrier)
182 firstland = laCrossroads;
183
184 if(firstland == laOceanWall) firstland = laOcean;
185 if(firstland == laHauntedWall) firstland = laGraveyard;
186 if(firstland == laHaunted && !tactic::on) firstland = laGraveyard;
187 if(firstland == laMercuryRiver) firstland = laTerracotta;
188 if(firstland == laMountain && !tactic::on) firstland = laJungle;
189 if(firstland == laPrincessQuest) firstland = laPalace;
190 if(firstland == laMemory) firstland = laIce;
191 if((isGravityLand(firstland) && !isCyclic(firstland)) || (firstland == laOcean && !safety && !yendor::on))
192 firstland = weirdhyperbolic ? laCrossroads4 : laCrossroads;
193
194 clear_euland(firstland);
195
196 cwt.at = currentmap->gamestart(); cwt.spin = 0; cwt.mirrored = false;
197 cwt.at->land = firstland;
198
199 #if CAP_COMPLEX2
200 if(firstland == laBrownian) brownian::init(cwt.at);
201 #endif
202
203 chaosAchieved = false;
204
205 clearing::direct = 0;
206 clearing::imputed = 0;
207 rosephase = 0;
208
209 if(firstland == laElementalWall) cwt.at->land = randomElementalLand();
210
211 resetview();
212 createMov(cwt.at, 0);
213
214 pregen();
215 setdist(cwt.at, BARLEV, NULL);
216
217 if(isCyclic(specialland) || specialland == laCanvas) {
218 #if CAP_COMPLEX2
219 camelot::anthraxBonus = items[itHolyGrail];
220 #endif
221 cwt.at->move(0)->land = firstland;
222 if(firstland == laWhirlpool) cwt.at->move(0)->wall = waSea;
223
224 setdist(cwt.at->move(0), BARLEV-1, cwt.at);
225
226 if(horo_ok()) {
227 if(specialland == laCamelot)
228 start_camelot(cwt.at);
229 else {
230 heptagon *h = create_altmap(cwt.at, 2, hsA);
231 if(!h) printf("FAIL\n");
232 }
233 }
234 }
235
236 if(tactic::on && firstland == laPower) {
237 items[itOrbSpeed] = 30;
238 items[itOrbWinter] = 30;
239 items[itOrbFlash] = 30;
240 }
241
242 if(firstland == laCA)
243 items[itOrbAether] = 2;
244
245 if(tactic::on && firstland == laCaribbean) {
246 if(hiitemsMax(itRedGem) >= 25) items[itRedGem] = min(hiitemsMax(itRedGem), 50);
247 if(hiitemsMax(itFernFlower) >= 25) items[itFernFlower] = min(hiitemsMax(itFernFlower), 50);
248 if(hiitemsMax(itWine) >= 25) items[itWine] = min(hiitemsMax(itWine), 50);
249 }
250
251 yendor::lastchallenge = yendor::challenge;
252
253 if(shmup::on) shmup::init();
254
255 yendor::init(2);
256
257 #if CAP_RACING
258 if(racing::on) racing::generate_track();
259 #endif
260
261 if(gamegen_failure) return;
262
263 if(euclid && specialland == laPrincessQuest) {
264 cell *c = euc::at(princess::coords());
265 princess::generating = true;
266 c->land = laPalace;
267 setdist(c, 7 - getDistLimit() - genrange_bonus, NULL);
268 princess::generating = false;
269 }
270
271 if(cwt.at->land == laCrossroads2) {
272 cell *c = cwt.at;
273 if(hybri) { c = hybrid::get_where(c).first; PIU( c->cmove(0) ); }
274 c->landparam = 12;
275 c->cmove(0)->landparam = 44;
276 c->cmove(0)->land = laCrossroads2;
277 }
278
279 sword::determine_sword_angles();
280 for(int i=0; i<numplayers(); i++)
281 sword::dir[i] = sword::initial(cwt.at);
282
283 #if CAP_DAILY
284 daily::split();
285 #endif
286
287 // extern int sightrange; sightrange = 9;
288 // cwt.at->land = laHell; items[itHell] = 10;
289 for(int i=BARLEV; i>=7 - getDistLimit() - genrange_bonus; i--) {
290 setdist(cwt.at, i, NULL);
291
292 currentmap->verify();
293 }
294
295 if(doCross) {
296 for(int i=0; i<ittypes; i++) if(itemclass(eItem(i)) == IC_TREASURE) items[i] = 50;
297 for(int i=0; i<motypes; i++) kills[i] = 30;
298 items[itSavedPrincess] = 0;
299 kills[moPrincessMoved] = 0;
300 kills[moPrincessArmedMoved] = 0;
301 kills[moPlayer] = 0;
302 }
303
304 if(quotient && generateAll(firstland)) {
305 for(int i=0; i<isize(currentmap->allcells()); i++)
306 setdist(currentmap->allcells()[i], 8, NULL);
307 }
308
309
310 if(multi::players > 1 && !shmup::on) for(int i=0; i<numplayers(); i++) {
311 int idir = (3 * i) % cwt.at->type;
312 multi::player[i].at = cwt.at->move(idir);
313 // special case -- otherwise they land on a wall
314 if(firstland == laCrossroads2 && i == 1)
315 multi::player[1].at = cwt.at;
316 if(firstland == laCrossroads2 && i == 6)
317 multi::player[6].at = createMov(createMov(cwt.at, 0), 3);
318 setdist(cwt.at->move(idir), 7 - getDistLimit() - genrange_bonus, cwt.at);
319 multi::player[i].spin = 0;
320 multi::flipped[i] = true;
321 multi::whereto[i].d = MD_UNDECIDED;
322 }
323
324 yendor::init(3);
325 peace::simon::init();
326
327 multi::revive_queue.clear();
328 #if CAP_TOUR
329 if(tour::on) tour::presentation(tour::pmRestart);
330 #endif
331
332 if(multi::players > 1 && !shmup::on) {
333 for(cell *pc: player_positions())
334 makeEmpty(pc);
335 }
336 else {
337 makeEmpty(cwt.at);
338 }
339
340 if(specialland == laMinefield && bounded) {
341 bfs();
342 generate_mines();
343 }
344
345 if(in_lovasz()) {
346 cwt.at->item = itOrbInvis;
347 }
348
349 princess::squeaked = false;
350 clearing::current_root = NULL;
351
352 if(!safety) {
353 usedSafety = false;
354 timerstart = time(NULL); turncount = 0; rosewave = 0; rosephase = 0;
355 noiseuntil = 0;
356 sagephase = 0; hardcoreAt = 0;
357 timerstopped = false;
358 savecount = 0; savetime = 0;
359 cheater = 0;
360 if(autocheat) cheater = 1;
361 if(!wfc::use_eclectic) cheater = 1;
362 if(!autocheat && !cheater && geometry == gNormal) patterns::whichShape = 0;
363 hauntedWarning = false;
364 if(!autocheat) {
365 timerghost = true;
366 gen_wandering = true;
367 }
368 truelotus = 0;
369 asteroids_generated = 0;
370 asteroid_orbs_generated = 0;
371 dpgen::in = false;
372 survivalist = true;
373 #if CAP_CRYSTAL
374 crystal::used_compass_inside = false;
375 #endif
376 got_achievements = {};
377 #if CAP_INV
378 if(inv::on) inv::init();
379 #endif
380 #if CAP_COMPLEX2
381 mine::auto_teleport_charges();
382 #endif
383 welcomeMessage();
384 }
385 else {
386 usedSafety = true;
387 safety = false;
388 }
389
390 havewhat = hadwhat = 0; rosemap.clear();
391
392 elec::lightningfast = 0;
393
394 lastsafety = gold();
395 bfs();
396 checkmove();
397 playermoved = true;
398
399 if(quotient || sphere)
400 for(cell *c: currentmap->allcells()) setdist(c, 8, NULL);
401
402 if(!allowChangeRange()) {
403 gamerange_bonus = genrange_bonus = 0;
404 if(vid.use_smart_range == 2) vid.use_smart_range = 1;
405 }
406 if(!allowIncreasedSight()) vid.use_smart_range = 0;
407 callhooks(hooks_post_initgame);
408 }
409
410 bool havesave = true;
411
412 #if CAP_SAVE
413
414 /** \brief A namespace for loading and saving scores and saved games (system.cpp), and for displaying these scores (scores.cpp).
415 *
416 * Most ApplyBox functions are used both for saving savegames and scores to the logfile, loading savegames and scores from the logfile,
417 * and loading highscore information from the logfile. The flags saving, loading, and loadingHi specify what is actually done.
418 */
419 EX namespace scores {
420
421 #if HDR
422 /** \brief the amount of boxes reserved for each hr::score item */
423 #define MAXBOX 500
424 /** \brief currently used boxes in hr::score */
425 #define POSSCORE 406
426 /** \brief a struct to keep local score from an earlier game */
427 struct score {
428 /** \brief version used */
429 string ver;
430 /** \brief all the data of the saved score, see applyBoxes() */
431 int box[MAXBOX];
432 };
433 #endif
434
435 /** \brief the current save */
436 EX score save;
437 /** \brief the index of the next box */
438 EX int boxid;
439
440 /** \brief see hr::applyBox */
441 EX bool saving, loading, loadingHi;
442
443 /** \brief names of all the boxes */
444 EX string boxname[MAXBOX];
445 /** \brief 'fake' boxes should not appear when examining local scores */
446 EX bool fakebox[MAXBOX];
447 /** \brief does this box contain monster kills */
448 EX bool monsbox[MAXBOX];
449
450 /** \brief the next box should contain t */
applyBox(int & t)451 void applyBox(int& t) {
452 if(saving) save.box[boxid++] = t;
453 else if(loading) t = save.box[boxid++];
454 else boxid++;
455 }
456
457 /** \brief the next box should contain tb */
applyBoxBignum(bignum & tb,string name)458 void applyBoxBignum(bignum& tb, string name) {
459 float tf;
460 int ti;
461 if(saving) tf = tb.approx_ld();
462 if(saving) memcpy(&ti, &tf, 4);
463 applyBoxNum(ti, name);
464 if(loading) memcpy(&tf, &ti, 4);
465 if(loading) tb = bignum(tf);
466 }
467
468 /** \brief the next box should contain i, and possibly be named name */
469 EX void applyBoxNum(int& i, string name IS("")) {
470 fakebox[boxid] = (name == "" || name[0] == '@');
471 boxname[boxid] = name;
472 monsbox[boxid] = false;
473 applyBox(i);
474 }
475
476 /** \brief the next box should contain b, and possibly be named name */
applyBoxBool(bool & b,string name="")477 void applyBoxBool(bool& b, string name = "") {
478 int i = b;
479 applyBoxNum(i, name);
480 monsbox[boxid] = false;
481 b = i;
482 }
483
484 /** \brief Save i while saving, do nothing while loading. Use together with hr::scores::applyBoxLoad and boxid++ */
applyBoxSave(int i,string name="")485 void applyBoxSave(int i, string name = "") {
486 fakebox[boxid] = (name == "");
487 boxname[boxid] = name;
488 applyBox(i);
489 }
490
491 /** \brief Load i while loading, do nothing while saving. Use together with hr::scores::applyBoxSave and boxid++ */
applyBoxLoad(string name="")492 int applyBoxLoad(string name = "") {
493 fakebox[boxid] = (name == "");
494 boxname[boxid] = name;
495 int i=0; applyBox(i);
496 return i;
497 }
498
499 /** \brief the next box is the number of collected items it */
applyBoxI(eItem it,bool f=false)500 void applyBoxI(eItem it, bool f = false) {
501 boxname[boxid] = iinf[it].name;
502 fakebox[boxid] = f;
503 monsbox[boxid] = false;
504 if(loadingHi) {
505 updateHi_for_code(it, save.box[boxid++], saved_modecode);
506 }
507 else applyBox(items[it]);
508 }
509
510 vector<eItem> invorb;
511
addinv(eItem it)512 void addinv(eItem it) {
513 invorb.push_back(it);
514 }
515
516 /** \brief Handle the information about orb it. Need to call list_invorb later */
applyBoxOrb(eItem it)517 void applyBoxOrb(eItem it) {
518 applyBoxI(it, true);
519 invorb.push_back(it);
520 }
521
522 /** \brief Handle the OSM information for all orbs that applyBoxOrb has been called for so far */
list_invorb()523 void list_invorb() {
524 for(eItem it: invorb) {
525 #if CAP_INV
526 if(true) {
527 inv::applyBox(it);
528 continue;
529 }
530 #endif
531 int u = 0;
532 applyBoxNum(u);
533 }
534 invorb.clear();
535 }
536
537 /** \brief handle the number of monsters of type m killed */
applyBoxM(eMonster m,bool f=false)538 void applyBoxM(eMonster m, bool f = false) {
539 fakebox[boxid] = f;
540 boxname[boxid] = minf[m].name;
541 monsbox[boxid] = true;
542 applyBox(kills[m]);
543 }
544
545 EX modecode_t saved_modecode;
546
547 /** \brief Call applyBox for all the required values. This will save the values if hr::scores::saving==true, load if hr::scores::loading==true, load into highscores if hr::scores::loadingHi==true */
applyBoxes()548 EX void applyBoxes() {
549 invorb.clear();
550
551 eLand lostin = laNone;
552
553 applyBoxSave((int) timerstart, "time elapsed");
554 time_t timer = time(NULL);
555 applyBoxSave((int) timer, "date");
556 applyBoxSave(gold(), "treasure collected");
557 applyBoxSave(tkills(), "total kills");
558 applyBoxNum(turncount, "turn count");
559 applyBoxNum(cellcount, "cells generated");
560
561 if(loading) timerstart = time(NULL);
562
563 for(int i=0; i<itOrbLightning; i++)
564 if(i == 0) items[i] = 0, applyBoxI(itFernFlower);
565 else applyBoxI(eItem(i));
566
567 for(int i=0; i<43; i++) {
568 if(loading) kills[i] = 0;
569 bool fake =
570 i == moLesserM || i == moNone || i == moWolfMoved || i == moTentacletail ||
571 i == moIvyNext;
572 if(i == moWormtail) applyBoxM(moCrystalSage);
573 else if(i == moWormwait) applyBoxM(moFireFairy);
574 else if(i == moTentacleEscaping) applyBoxM(moMiner);
575 else if(i == moGolemMoved) applyBoxM(moIllusion);
576 else if(i == moTentaclewait) applyBoxOrb(itOrbThorns);
577 else if(i == moGreater) applyBoxOrb(itOrbDragon);
578 else if(i == moGreaterM) applyBoxOrb(itOrbIllusion);
579 else applyBoxM(eMonster(i), fake);
580 }
581
582 if(saving) {
583 int totaltime = savetime;
584 if(!timerstopped) totaltime += timer - timerstart;
585 applyBoxSave((int) totaltime, "time played");
586 }
587 else if(loading) savetime = applyBoxLoad("time played");
588 else boxname[boxid] = "time played", boxid++;
589
590 if(saving) savecount++;
591 applyBoxNum(savecount, "number of saves");
592 if(saving) savecount--;
593 applyBoxNum(cheater, "number of cheats");
594
595 fakebox[boxid] = true;
596 if(saving) applyBoxSave(items[itOrbSafety] ? safetyland : cwt.at->land, "@safetyland");
597 else if(loading) firstland = safetyland = eLand(applyBoxLoad("@safetyland"));
598 else lostin = eLand(save.box[boxid++]);
599
600 for(int i=itOrbLightning; i<25; i++) applyBoxOrb(eItem(i));
601
602 applyBoxI(itRoyalJelly);
603 applyBoxI(itWine);
604 applyBoxI(itSilver);
605 applyBoxI(itEmerald);
606 applyBoxI(itPower);
607 applyBoxOrb(itOrbFire);
608 applyBoxOrb(itOrbInvis);
609 applyBoxOrb(itOrbAether);
610 applyBoxOrb(itOrbPsi);
611 applyBoxM(moBug0);
612 applyBoxM(moBug1);
613 applyBoxM(moBug2);
614 applyBoxM(moVineBeast);
615 applyBoxM(moVineSpirit);
616 applyBoxM(moLancer);
617 applyBoxM(moFlailer);
618 applyBoxM(moEarthElemental);
619 applyBoxM(moDarkTroll);
620 applyBoxM(moWitch);
621 applyBoxM(moWitchFire);
622 applyBoxM(moWitchFlash);
623 applyBoxM(moWitchGhost);
624 applyBoxM(moWitchSpeed);
625 applyBoxM(moEvilGolem);
626 applyBoxM(moWitchWinter);
627 applyBoxI(itHolyGrail);
628 applyBoxI(itGrimoire);
629 applyBoxM(moKnight);
630 applyBoxM(moCultistLeader);
631
632 applyBoxM(moPirate);
633 applyBoxM(moCShark);
634 applyBoxM(moParrot);
635 applyBoxI(itPirate);
636 applyBoxOrb(itOrbTime);
637
638 applyBoxM(moHexSnake);
639 applyBoxM(moRedTroll);
640 applyBoxI(itRedGem);
641 applyBoxOrb(itOrbSpace);
642
643 int geo = geometry;
644 applyBoxNum(geo, "@geometry"); geometry = eGeometry(geo);
645 applyBoxBool(hardcore, "hardcore");
646 applyBoxNum(hardcoreAt, "@hardcoreAt");
647 applyBoxBool(shmup::on, "shmup");
648 if(saving) applyBoxSave(specialland, "euclid land");
649 else if(loading) specialland = eLand(applyBoxLoad("euclid land"));
650 else fakebox[boxid++] = true;
651
652 applyBoxI(itCoast);
653 applyBoxI(itWhirlpool);
654 applyBoxI(itBombEgg);
655 applyBoxM(moBomberbird);
656 applyBoxM(moTameBomberbird);
657 applyBoxM(moAlbatross);
658 applyBoxOrb(itOrbFriend);
659 applyBoxOrb(itOrbAir);
660 applyBoxOrb(itOrbWater);
661
662 applyBoxI(itPalace);
663 applyBoxI(itFjord);
664 applyBoxOrb(itOrbFrog);
665 applyBoxOrb(itOrbDiscord);
666 applyBoxM(moPalace);
667 applyBoxM(moFatGuard);
668 applyBoxM(moSkeleton);
669 applyBoxM(moVizier);
670 applyBoxM(moViking);
671 applyBoxM(moFjordTroll);
672 applyBoxM(moWaterElemental);
673
674 applyBoxI(itSavedPrincess);
675 applyBoxOrb(itOrbLove);
676 applyBoxM(moPrincess);
677 applyBoxM(moPrincessMoved, false); // live Princess for Safety
678 applyBoxM(moPrincessArmedMoved, false); // live Princess for Safety
679 applyBoxM(moMouse);
680 applyBoxNum(princess::saveArmedHP, "@saveArmedHP");
681 applyBoxNum(princess::saveHP, "@saveHP");
682
683 applyBoxI(itIvory);
684 applyBoxI(itElemental);
685 applyBoxI(itZebra);
686 applyBoxI(itFireShard);
687 applyBoxI(itWaterShard);
688 applyBoxI(itAirShard);
689 applyBoxI(itEarthShard);
690
691 applyBoxM(moAirElemental);
692 applyBoxM(moFireElemental);
693 applyBoxM(moFamiliar);
694 applyBoxM(moGargoyle);
695 applyBoxM(moOrangeDog);
696 applyBoxOrb(itOrbSummon);
697 applyBoxOrb(itOrbMatter);
698
699 applyBoxM(moForestTroll);
700 applyBoxM(moStormTroll);
701 applyBoxM(moOutlaw);
702 applyBoxM(moMutant);
703 applyBoxM(moMetalBeast);
704 applyBoxM(moMetalBeast2);
705 applyBoxI(itMutant);
706 applyBoxI(itFulgurite);
707 applyBoxI(itBounty);
708 applyBoxOrb(itOrbLuck);
709 applyBoxOrb(itOrbStunning);
710
711 applyBoxBool(tactic::on, "@tactic");
712 applyBoxNum(elec::lightningfast, "@lightningfast");
713
714 // if(save.box[boxid]) printf("lotus = %d (lost = %d)\n", save.box[boxid], isHaunted(lostin));
715 if(loadingHi && isHaunted(lostin)) boxid++;
716 else applyBoxI(itLotus);
717 applyBoxOrb(itOrbUndeath);
718 applyBoxI(itWindstone);
719 applyBoxOrb(itOrbEmpathy);
720 applyBoxM(moWindCrow);
721 applyBoxOrb(itMutant2);
722 applyBoxOrb(itOrbFreedom);
723 applyBoxM(moRedFox);
724 applyBoxBool(survivalist, "@survivalist");
725 if(loadingHi) applyBoxI(itLotus);
726 else applyBoxNum(truelotus, "lotus/escape");
727
728 int v = int(variation);
729 applyBoxNum(v, "variation");
730 variation = eVariation(v);
731 applyBoxI(itRose);
732 applyBoxOrb(itOrbBeauty);
733 applyBoxI(itCoral);
734 applyBoxOrb(itOrb37);
735 applyBoxOrb(itOrbEnergy);
736 applyBoxM(moRatling);
737 applyBoxM(moFalsePrincess);
738 applyBoxM(moRoseLady);
739 applyBoxM(moRoseBeauty);
740 int ls = (int) land_structure;
741 applyBoxNum(ls, "land structure");
742 land_structure = (eLandStructure) ls;
743 applyBoxNum(multi::players, "shmup players");
744 if(multi::players < 1 || multi::players > MAXPLAYER)
745 multi::players = 1;
746 applyBoxM(moRatlingAvenger);
747 // printf("applybox %d\n", shmup::players);
748
749 applyBoxI(itApple);
750 applyBoxM(moSparrowhawk);
751 applyBoxM(moResearcher);
752 applyBoxI(itDragon);
753 applyBoxM(moDragonHead);
754 applyBoxOrb(itOrbDomination);
755 applyBoxI(itBabyTortoise);
756 applyBoxNum(tortoise::seekbits, "@seekbits");
757 applyBoxM(moTortoise);
758 applyBoxOrb(itOrbShell);
759
760 applyBoxNum(safetyseed, "@safetyseed");
761
762 // (+18)
763 for(int i=0; i<6; i++) {
764 applyBoxNum(multi::treasures[i], "@multi-treasures" + its(i));
765 applyBoxNum(multi::kills[i], "@multi-kills" + its(i));
766 applyBoxNum(multi::deaths[i], "@multi-deaths" + its(i));
767 }
768 // (+8)
769 applyBoxM(moDragonTail);
770 applyBoxI(itKraken);
771 applyBoxM(moKrakenH);
772 applyBoxM(moKrakenT);
773 applyBoxOrb(itOrbSword);
774 applyBoxI(itBarrow);
775 applyBoxM(moDraugr);
776 applyBoxOrb(itOrbSword2);
777 applyBoxI(itTrollEgg);
778 applyBoxOrb(itOrbStone);
779
780 bool sph;
781 sph = false; applyBoxBool(sph, "sphere"); if(sph) geometry = gSphere;
782 sph = false; applyBoxBool(sph, "elliptic"); if(sph) geometry = gElliptic;
783 applyBoxNum(princess::reviveAt, "@reviveAt");
784
785 applyBoxI(itDodeca);
786 applyBoxI(itAmethyst);
787 applyBoxI(itSlime);
788 applyBoxOrb(itOrbNature);
789 applyBoxOrb(itOrbDash);
790 addinv(itOrbRecall);
791 applyBoxM(moBat);
792 applyBoxM(moReptile);
793 applyBoxM(moFriendlyIvy);
794
795 applyBoxI(itGreenGrass);
796 applyBoxI(itBull);
797 applyBoxOrb(itOrbHorns);
798 applyBoxOrb(itOrbBull);
799 applyBoxM(moSleepBull);
800 applyBoxM(moRagingBull);
801 applyBoxM(moHerdBull);
802 applyBoxM(moButterfly);
803 applyBoxM(moGadfly);
804
805 // 10.0:
806 applyBoxNum(hinttoshow, "@hinttoshow"); // 258
807 addinv(itOrbMirror);
808 addinv(itGreenStone);
809 list_invorb();
810 applyBoxBool(inv::on, "inventory"); // 306
811 #if CAP_INV
812 applyBoxNum(inv::rseed, "@inv-rseed");
813 #else
814 { int u; applyBoxNum(u); }
815 #endif
816
817 // 10.1:
818 applyBoxI(itLavaLily);
819 applyBoxI(itHunting);
820 applyBoxI(itBlizzard);
821 applyBoxI(itTerra);
822 applyBoxOrb(itOrbSide1);
823 applyBoxOrb(itOrbSide2);
824 applyBoxOrb(itOrbSide3);
825 applyBoxOrb(itOrbLava);
826 applyBoxOrb(itOrbMorph);
827 applyBoxM(moHunterDog);
828 applyBoxM(moIceGolem);
829 applyBoxM(moVoidBeast);
830 applyBoxM(moJiangshi);
831 applyBoxM(moTerraWarrior);
832 applyBoxM(moSalamander);
833 applyBoxM(moLavaWolf);
834
835 applyBoxOrb(itOrbSlaying);
836 applyBoxOrb(itOrbMagnetism);
837 applyBoxOrb(itOrbPhasing);
838 applyBoxI(itDock);
839 applyBoxI(itGlowCrystal);
840 applyBoxI(itMagnet);
841 applyBoxI(itRuins);
842 applyBoxI(itSwitch);
843 applyBoxM(moNorthPole);
844 applyBoxM(moSouthPole);
845 applyBoxM(moSwitch1);
846 applyBoxM(moSwitch2);
847 applyBoxM(moAltDemon);
848 applyBoxM(moHexDemon);
849 applyBoxM(moPair);
850 applyBoxM(moCrusher);
851 applyBoxM(moMonk);
852
853 bool v2 = false;
854 applyBoxBool(v2, "@variation"); if(loading && v2) variation = eVariation::goldberg;
855 applyBoxNum(gp::param.first, "@gp-first");
856 applyBoxNum(gp::param.second, "@gp-second");
857
858 v2 = false; applyBoxBool(v2); if(loading && v2) variation = eVariation::irregular;
859 applyBoxNum(irr::cellcount, "@irr-cellcount");
860
861 list_invorb();
862
863 applyBoxNum(irr::bitruncations_performed, "@irr-bitruncations");
864
865 applyBoxI(itVarTreasure);
866 applyBoxI(itBrownian);
867 applyBoxI(itWest);
868 applyBoxM(moAcidBird);
869 applyBoxM(moBrownBug);
870 applyBoxM(moVariantWarrior);
871 applyBoxM(moWestHawk);
872 applyBoxM(moFallingDog);
873 applyBoxOrb(itOrbIntensity);
874 applyBoxOrb(itOrbChoice);
875 applyBoxOrb(itOrbGravity);
876 list_invorb();
877
878 applyBoxM(moNarciss);
879 applyBoxM(moMirrorSpirit);
880
881 applyBoxNum(clearing::direct, "@clearing-direct");
882 applyBoxBignum(clearing::imputed, "@clearing-imputed");
883
884 applyBoxOrb(itOrbImpact);
885 applyBoxOrb(itOrbChaos);
886 applyBoxOrb(itOrbPlague);
887 applyBoxI(itEclectic);
888 applyBoxI(itFrog);
889 applyBoxI(itWet);
890 applyBoxM(moFrog);
891 applyBoxM(moPhaser);
892 applyBoxM(moVaulter);
893 applyBoxM(moPike);
894 applyBoxM(moRusalka);
895 list_invorb();
896
897 applyBoxNum(saved_modecode, "modecode");
898 applyBoxBool(ineligible_starting_land, "ineligible_starting_land");
899
900 applyBoxNum(yasc_code, "YASC code");
901 applyBoxBool(casual, "casual mode");
902
903 applyBoxI(itCursed);
904 applyBoxI(itDice);
905 applyBoxOrb(itOrbPurity);
906 applyBoxOrb(itOrbWoods);
907 applyBoxM(moHexer);
908 applyBoxM(moAngryDie);
909 applyBoxM(moAnimatedDie);
910 applyBoxI(itCurseWeakness, true);
911 applyBoxI(itCurseFatigue, true);
912 applyBoxI(itCurseDraining, true);
913 applyBoxI(itCurseRepulsion, true);
914 applyBoxI(itCurseGluttony, true);
915 applyBoxI(itCurseWater, true);
916 list_invorb();
917
918 if(POSSCORE != boxid) printf("ERROR: %d boxes\n", boxid);
919 if(isize(invorb)) { println(hlog, "ERROR: Orbs not taken into account"); exit(1); }
920 }
921
922 /** \brief save the current game values to save */
saveBox()923 EX void saveBox() {
924 boxid = 0; saving = true; applyBoxes(); saving = false;
925 }
926
927 /** \brief load the current game values from save */
loadBox()928 void loadBox() {
929 // have boxid
930 boxid = 0; loading = true; applyBoxes(); loading = false;
931 }
932
933 const int MODECODE_BOX = 387;
934
fill_modecode()935 modecode_t fill_modecode() {
936 dynamicval<int> sp1(multi::players, save.box[197]);
937 dynamicval<eGeometry> sp2(geometry, (eGeometry) save.box[116]);
938 if(among(geometry, gArchimedean, gProduct, gRotSpace, gArbitrary))
939 return 6; /* these would not be saved nor loaded correctly */
940 dynamicval<bool> sp3(shmup::on, save.box[119]);
941 dynamicval<eLandStructure> sp4(land_structure, (eLandStructure) save.box[196]);
942 dynamicval<eVariation> sp5(variation, (eVariation) save.box[186]);
943 dynamicval<int> sp7(gp::param.first, save.box[342]);
944 dynamicval<int> sp8(gp::param.second, save.box[343]);
945 dynamicval<bool> spinv(inv::on, save.box[306]);
946
947 if(save.box[238]) geometry = gSphere;
948 if(save.box[239]) geometry = gElliptic;
949 if(save.box[341]) variation = eVariation::goldberg;
950 if(save.box[344]) variation = eVariation::irregular;
951
952 if(multi::players < 0 || multi::players > MAXPLAYER)
953 return 6;
954
955 if(multi::players == 0)
956 multi::players = 1;
957
958 if(shmup::on && multi::players == 1 && boxid <= 258)
959 return 6; /* not sure why */
960
961 return modecode(2);
962 }
963
964 /** \brief load the current game values from save into the highscore tables */
loadBoxHigh()965 void loadBoxHigh() {
966 saved_modecode = save.box[MODECODE_BOX];
967 boxid = 0; loadingHi = true; applyBoxes(); loadingHi = false;
968 }
969
970 EX }
971
972 // certify that saves and achievements were received
973 // in an official version of HyperRogue
974
975 EX namespace anticheat {
976 EX int certify(const string& s, int a, int b, int c, int d IS(0));
977 EX }
978
979 #if CAP_CERTIFY
980 #include "private/certify.cpp"
981 #endif
982
983 #if !CAP_CERTIFY
984 EX namespace anticheat {
985 EX bool tampered;
save(FILE * f)986 void save(FILE *f) {}
load(FILE * f,scores::score & sc,const string & ver)987 bool load(FILE *f, scores::score& sc, const string& ver) {return true; }
certify(const string & s,int a,int b,int c,int d)988 int certify(const string& s, int a, int b, int c, int d) { return d; }
check(int cv,const string & ver,const string & s,int a,int b,int c,int d=0)989 int check(int cv, const string& ver, const string& s, int a, int b, int c, int d=0) { return cv==d; }
nextid(int & tid,const string & ver,int cert)990 void nextid(int& tid, const string& ver, int cert) { tid++; }
991 EX }
992 #endif
993
994 long long saveposition = -1;
995
remove_emergency_save()996 EX void remove_emergency_save() {
997 #if !ISWINDOWS
998 if(saveposition >= 0) {
999 if(truncate(scorefile, saveposition)) {}
1000 saveposition = -1;
1001 }
1002 #endif
1003 }
1004
1005 scores::score scorebox;
1006
saveStats(bool emergency IS (false))1007 EX void saveStats(bool emergency IS(false)) {
1008 DEBBI(DF_INIT, ("saveStats [", scorefile, "]"));
1009
1010 if(autocheat) return;
1011 #if CAP_TOUR
1012 if(tour::on) return;
1013 #endif
1014 if(randomPatternsMode) return;
1015 if(daily::on) return;
1016 if(peace::on) return;
1017 if(dpgen::in) return;
1018 if(experimental) return;
1019 if(!gold()) return;
1020
1021 remove_emergency_save();
1022
1023 auto& xcode = scores::saved_modecode;
1024
1025 xcode = modecode();
1026
1027 FILE *f = fopen(scorefile, "at");
1028
1029 if(!f) {
1030 // printf("Could not open the score file '%s'!\n", scorefile);
1031 addMessage(XLAT("Could not open the score file: ") + scorefile);
1032 return;
1033 }
1034
1035 if(emergency) {
1036 saveposition = ftell(f);
1037 // if(!timerghost) addMessage(XLAT("Emergency save at ") + its(saveposition));
1038 }
1039
1040 time_t timer;
1041 timer = time(NULL);
1042 char sbuf[128]; strftime(sbuf, 128, "%c", localtime(&timerstart));
1043 char buf[128]; strftime(buf, 128, "%c", localtime(&timer));
1044
1045 if((tactic::on || yendor::on) && !items[itOrbSafety] && !cheater) {
1046 int t = (int) (timer - timerstart);
1047
1048 if(tactic::on) {
1049 int score = items[treasureType(specialland)];
1050
1051 if(score) {
1052 int c =
1053 anticheat::certify(dnameof(specialland), turncount, t, (int) timerstart,
1054 unsigned(xcode)*999 + tactic::id + 256 * score);
1055 fprintf(f, "TACTICS %s %d %d %d %d %d %d %d %d date: %s\n", VER,
1056 tactic::id, specialland, score, turncount, t, int(timerstart),
1057 c, int(xcode), buf);
1058 tactic::record(specialland, score);
1059 anticheat::nextid(tactic::id, VER, c);
1060 }
1061 }
1062
1063 if(yendor::on)
1064 fprintf(f, "YENDOR %s %d %d %d %d %d %d %d %d date: %s\n", VER,
1065 yendor::lastchallenge, items[itOrbYendor], yendor::won, turncount, t, int(timerstart),
1066 anticheat::certify(yendor::won ? "WON" : "LOST", turncount, t, (int) timerstart,
1067 unsigned(xcode)*999 + yendor::lastchallenge + 256 * items[itOrbYendor]),
1068 int(xcode),
1069 buf);
1070
1071 fclose(f);
1072 return;
1073 }
1074
1075 fprintf(f, "HyperRogue: game statistics (version " VER ")\n");
1076 if(cheater)
1077 fprintf(f, "CHEATER! (cheated %d times)\n", cheater);
1078 if(true) {
1079
1080 fprintf(f, VER);
1081 if(!shmup::on) items[itOrbLife] = countMyGolems(moGolem);
1082 if(!shmup::on) items[itOrbFriend] = countMyGolems(moTameBomberbird);
1083 if(!shmup::on) kills[moPrincessMoved] = countMyGolems(moPrincess);
1084 if(!shmup::on) kills[moPrincessArmedMoved] = countMyGolems(moPrincessArmed);
1085 if(!shmup::on) princess::saveHP = countMyGolemsHP(moPrincess);
1086 if(!shmup::on) princess::saveArmedHP = countMyGolemsHP(moPrincessArmed);
1087 scores::saveBox();
1088
1089 for(int i=0; i<scores::boxid; i++) fprintf(f, " %d", scores::save.box[i]);
1090 scorebox = scores::save;
1091 anticheat::save(f);
1092
1093 fprintf(f, "\n");
1094 }
1095 fprintf(f, "Played on: %s - %s (%d %s)\n", sbuf, buf, turncount,
1096 shmup::on ? "knives" : "turns");
1097 fprintf(f, "Total wealth: %d\n", gold());
1098 fprintf(f, "Total enemies killed: %d\n", tkills());
1099 fprintf(f, "cells generated: %d\n", cellcount);
1100 if(pureHardcore()) fprintf(f, "Pure hardcore mode\n");
1101 if(geometry) fprintf(f, "Geometry: %s/%s/%s\n", gp::operation_name().c_str(), ginf[geometry].tiling_name.c_str(), ginf[geometry].quotient_name.c_str());
1102
1103 if(!ls::nice_walls())
1104 fprintf(f, "land structure: %s\n", land_structure_name(true).c_str());
1105
1106 if(shmup::on) fprintf(f, "Shoot-em up mode\n");
1107 if(inv::on) fprintf(f, "Inventory mode\n");
1108 if(multi::players > 1) fprintf(f, "Multi-player (%d players)\n", multi::players);
1109 fprintf(f, "Number of cells explored, by distance from the player:\n");
1110 {for(int i=0; i<10; i++) fprintf(f, " %d", explore[i]);} fprintf(f, "\n");
1111 if(kills[0]) fprintf(f, "walls melted: %d\n", kills[0]);
1112 fprintf(f, "cells travelled: %d\n", celldist(cwt.at));
1113
1114 fprintf(f, "\n");
1115
1116 for(int i=0; i<ittypes; i++) if(items[i])
1117 fprintf(f, "%4dx %s\n", items[i], iinf[i].name);
1118
1119 fprintf(f, "\n");
1120
1121 for(int i=1; i<motypes; i++) if(kills[i])
1122 fprintf(f, "%4dx %s\n", kills[i], minf[i].name);
1123
1124 fprintf(f, "\n\n\n");
1125
1126 #if !ISMOBILE
1127 DEBB(DF_INIT, ("Game statistics saved to ", scorefile));
1128 addMessage(XLAT("Game statistics saved to %1", scorefile));
1129 #endif
1130 fclose(f);
1131 }
1132
1133 bool tamper = false;
1134
1135 // load the save
loadsave()1136 EX void loadsave() {
1137 if(autocheat) return;
1138 #if CAP_TOUR
1139 if(tour::on) return;
1140 #endif
1141 DEBBI(DF_INIT, ("loadSave"));
1142
1143 FILE *f = fopen(scorefile, "rt");
1144 havesave = f;
1145 if(!f) return;
1146 bool ok = false;
1147 int coh = counthints();
1148 auto& sc = scorebox;
1149 while(!feof(f)) {
1150 char buf[12000];
1151 if(fgets(buf, 12000, f) == NULL) break;
1152 if(buf[0] == 'M' && buf[1] == 'O') {
1153 string s = buf;
1154 while(s != "" && s.back() < 32) s.pop_back();
1155 load_modecode_line(s);
1156 }
1157 if(buf[0] == 'H' && buf[1] == 'y') {
1158 if(fscanf(f, "%s", buf) <= 0) break;
1159 sc.ver = buf;
1160 if(sc.ver[1] != '.') sc.ver = '0' + sc.ver;
1161 if(verless(sc.ver, "4.4") || sc.ver == "CHEATER!") { ok = false; continue; }
1162 ok = true;
1163 for(int i=0; i<MAXBOX; i++) {
1164 if(fscanf(f, "%d", &sc.box[i]) <= 0) {
1165 scores::boxid = i;
1166
1167 tamper = anticheat::load(f, sc, sc.ver);
1168
1169 using namespace scores;
1170 for(int i=0; i<boxid; i++) save.box[i] = sc.box[i];
1171 for(int i=boxid; i<MAXBOX; i++) save.box[i] = 0, sc.box[i] = 0;
1172
1173 if(boxid <= MODECODE_BOX) save.box[MODECODE_BOX] = sc.box[MODECODE_BOX] = fill_modecode();
1174
1175 if(save.box[258] >= 0 && save.box[258] < coh) {
1176 hints[save.box[258]].last = save.box[1];
1177 }
1178
1179 loadBoxHigh();
1180
1181 break;
1182 }
1183 }
1184 }
1185 if(buf[0] == 'A' && buf[1] == 'C' && buf[2] == 'H') {
1186 char buf1[80], buf2[80];
1187 sscanf(buf, "%70s%70s", buf1, buf2);
1188 if(buf2 == string("PRINCESS1")) princess::everSaved = true;
1189 if(buf2 == string("YENDOR2")) yendor::everwon = true;
1190 if(buf2 == string("CR4")) chaosUnlocked = true;
1191 }
1192
1193 if(buf[0] == 'T' && buf[1] == 'A' && buf[2] == 'C') {
1194 ok = false;
1195 char buf1[80], ver[10];
1196 int tid, land, score, tc, t, ts, cert;
1197 int xc = -1;
1198 sscanf(buf, "%70s%9s%d%d%d%d%d%d%d%d",
1199 buf1, ver, &tid, &land, &score, &tc, &t, &ts, &cert, &xc);
1200
1201 eLand l2 = eLand(land);
1202 if(land == laMirror && verless(ver, "10.0")) l2 = laMirrorOld;
1203
1204 if(xc == -1)
1205 for(xc=0; xc<32768; xc++)
1206 if(anticheat::check(cert, ver, dnameof(l2), tc, t, ts, xc*999+unsigned(tid) + 256 * score))
1207 break;
1208
1209 if(tid == tactic::id && (anticheat::check(cert, ver, dnameof(l2), tc, t, ts, xc*unsigned(999)+ unsigned(tid) + 256 * score))) {
1210 if(score != 0
1211 && !(land == laOcean && verless(ver, "8.0f"))
1212 && !(land == laTerracotta && verless(ver, "10.3e"))
1213 && !(land == laWildWest && verless(ver, "11.3b") && !verless(ver, "11.3")))
1214 tactic::record(l2, score, xc);
1215 anticheat::nextid(tactic::id, ver, cert);
1216 }
1217 }
1218
1219 if(buf[0] == 'Y' && buf[1] == 'E' && buf[2] == 'N') {
1220 char buf1[80], ver[10];
1221 int cid, oy, won, tc, t, ts, cert=0, xc = -1;
1222 sscanf(buf, "%70s%9s%d%d%d%d%d%d%d%d",
1223 buf1, ver, &cid, &oy, &won, &tc, &t, &ts, &cert, &xc);
1224
1225 if(xc == -1)
1226 for(xc=0; xc<32768; xc++)
1227 if(anticheat::check(cert, ver, won ? "WON" : "LOST", tc, t, ts, xc*999 + cid + 256 * oy))
1228 break;
1229
1230 if(won) if(anticheat::check(cert, ver, won ? "WON" : "LOST", tc, t, ts, xc*999 + cid + 256 * oy)) {
1231 if(xc == 19 && cid == 25) xc = 0;
1232 if(cid > 0 && cid < YENDORLEVELS)
1233 if(!(verless(ver, "8.0f") && oy > 1 && cid == 15))
1234 if(!(verless(ver, "9.3b") && oy > 1 && (cid == 27 || cid == 28)))
1235 {
1236 yendor::bestscore[xc][cid] = max(yendor::bestscore[xc][cid], oy);
1237 }
1238 }
1239 }
1240
1241 }
1242 fclose(f);
1243 if(ok && sc.box[65 + 4 + itOrbSafety - itOrbLightning])
1244 load_last_save();
1245 }
1246
load_last_save()1247 EX void load_last_save() {
1248 auto& sc = scorebox;
1249 anticheat::tampered = tamper;
1250 // printf("box = %d (%d)\n", sc.box[65 + 4 + itOrbSafety - itOrbLightning], boxid);
1251 // printf("boxid = %d\n", boxid);
1252 using namespace scores;
1253 for(int i=0; i<boxid; i++) save.box[i] = sc.box[i];
1254 for(int i=boxid; i<MAXBOX; i++) save.box[i] = 0;
1255 // for(int i=160; i<200; i++) printf("%d: %d ", i, save.box[i]);
1256
1257 if(meaning.count(sc.box[MODECODE_BOX])) {
1258 shstream ss;
1259 ss.s = meaning[sc.box[MODECODE_BOX]];
1260 ss.read(ss.vernum);
1261 mapstream::load_geometry(ss);
1262 }
1263
1264 loadBox();
1265 // printf("boxid = %d\n", boxid);
1266 if(items[itHolyGrail]) {
1267 items[itHolyGrail]--;
1268 camelot::knighted = newRoundTableRadius();
1269 items[itHolyGrail]++;
1270 }
1271 else camelot::knighted = 0;
1272 safety = true;
1273 if(items[itSavedPrincess] < 0) items[itSavedPrincess] = 0;
1274 addMessage(XLAT("Game loaded."));
1275 showstartmenu = false;
1276 // reset unsavable special modes just in case
1277 peace::on = false;
1278 randomPatternsMode = false;
1279 yendor::on = false;
1280 tour::on = false;
1281 save_turns = turncount;
1282 }
1283 #endif
1284
stop_game()1285 EX void stop_game() {
1286 if(!game_active) return;
1287 if(dual::split(stop_game)) return;
1288 DEBBI(DF_INIT, ("stop_game"));
1289 achievement_final(true);
1290 #if CAP_SAVE
1291 if(!casual)
1292 saveStats();
1293 #endif
1294 for(int i=0; i<ittypes; i++) items[i] = 0;
1295 lastkills = 0; for(int i=0; i<motypes; i++) kills[i] = 0;
1296 for(int i=0; i<10; i++) explore[i] = 0;
1297 for(int i=0; i<10; i++) for(int l=0; l<landtypes; l++)
1298 exploreland[i][l] = 0;
1299
1300 for(int i: player_indices())
1301 multi::deaths[i]++;
1302
1303 #if CAP_SAVE
1304 anticheat::tampered = false;
1305 #endif
1306 achievementsReceived.clear();
1307 princess::saved = false;
1308 princess::nodungeon = false;
1309 princess::reviveAt = 0;
1310 princess::forceVizier = false;
1311 princess::forceMouse = false;
1312 #if CAP_COMPLEX2
1313 camelot::knighted = 0;
1314 #endif
1315 // items[itGreenStone] = 100;
1316 clearMemory();
1317 game_active = false;
1318 #if CAP_DAILY
1319 if(daily::on)
1320 daily::turnoff();
1321 #endif
1322 }
1323
default_model()1324 EX eModel default_model() {
1325 if(nonisotropic) return mdGeodesic;
1326 if(GDIM == 3) return mdPerspective;
1327 return mdDisk;
1328 }
1329
1330 EX purehookset hooks_on_geometry_change;
1331
set_geometry(eGeometry target)1332 EX void set_geometry(eGeometry target) {
1333 bool was_default = pmodel == default_model();
1334 callhooks(hooks_on_geometry_change);
1335 if(geometry != target) {
1336 stop_game();
1337 ors::reset();
1338 if(among(target, gProduct, gRotSpace)) {
1339 if(vid.always3) { vid.always3 = false; geom3::apply_always3(); }
1340 if(target == gRotSpace) hybrid::csteps = 0;
1341 hybrid::configure(target);
1342 }
1343 geometry = target;
1344
1345 #if CAP_IRR
1346 if(IRREGULAR) variation = eVariation::bitruncated;
1347 #endif
1348 #if CAP_GP
1349 if(GOLDBERG && gp::param == gp::loc(1,1) && S3 == 3) {
1350 variation = eVariation::bitruncated;
1351 }
1352 if(GOLDBERG && nonorientable) {
1353 if(gp::param.second && gp::param.second != gp::param.first)
1354 gp::param.second = 0;
1355 }
1356 #endif
1357 if(DUAL && geometry != gArchimedean && !hybri)
1358 variation = ginf[geometry].default_variation;
1359 #if CAP_BT
1360 if(bt::in() || WDIM == 3 || kite::in() || arb::in()) if(!hybri) variation = eVariation::pure;
1361 #endif
1362 if(S3 >= OINF) variation = eVariation::pure;
1363 if(INVERSE && !hybri) variation = gp::variation_for(gp::param);
1364 if(ginf[target].default_variation == eVariation::pure && geometry != gArchimedean)
1365 variation = eVariation::pure;
1366 if(was_default) pmodel = default_model();
1367 if(WDIM == 2 && (cgflags & qIDEAL) && vid.always3 && vid.texture_step < 32) vid.texture_step = 32;
1368 if(sl2) nisot::geodesic_movement = true;
1369
1370 if(geometry == gArbitrary) {
1371 arb::convert::base_geometry = geometry;
1372 arb::convert::base_variation = variation;
1373 }
1374
1375 if(rotspace) {
1376 check_cgi(); cgi.require_basics();
1377 hybrid::csteps = cgi.psl_steps;
1378 }
1379 }
1380 }
1381
set_variation(eVariation target)1382 EX void set_variation(eVariation target) {
1383 if(variation != target) {
1384 stop_game();
1385 if(is_subcube_based(target)) {
1386 if(!reg3::in()) geometry = hyperbolic ? gSpace435 : gCell8;
1387 variation = target;
1388 return;
1389 }
1390 if(is_reg3_variation(target)) {
1391 if(!reg3::in()) geometry = hyperbolic ? gSpace435 : gCell8;
1392 variation = target;
1393 return;
1394 }
1395 if(target != eVariation::pure) {
1396 if(bt::in() || sol || kite::in() || WDIM == 3) if(!prod) geometry = gNormal;
1397 }
1398 auto& cd = ginf[gCrystal];
1399 if(target == eVariation::bitruncated && cryst && cd.sides == 8 && cd.vertex == 4) {
1400 cd.vertex = 3;
1401 cd.tiling_name = "{8,3}";
1402 target = eVariation::pure;
1403 }
1404 variation = target;
1405 }
1406 }
1407
stop_tour()1408 void stop_tour() {
1409 while(gamestack::pushed()) {
1410 gamestack::pop();
1411 stop_game();
1412 }
1413 }
1414
switch_game_mode(char switchWhat)1415 EX void switch_game_mode(char switchWhat) {
1416 DEBBI(DF_INIT, ("switch_game_mode ", switchWhat));
1417 switch(switchWhat) {
1418 case rg::peace:
1419 peace::on = !peace::on;
1420 tactic::on = yendor::on = princess::challenge =
1421 randomPatternsMode = inv::on = false;
1422 racing::on = false;
1423 break;
1424
1425 case rg::dualmode:
1426 stop_tour(); tour::on = false;
1427 racing::on = false;
1428 yendor::on = tactic::on = princess::challenge = false;
1429 if(!dual::state) dual::enable();
1430 else dual::disable();
1431 break;
1432
1433 case rg::inv:
1434 inv::on = !inv::on;
1435 if(tactic::on) firstland = specialland = laIce;
1436 tactic::on = yendor::on = princess::challenge =
1437 peace::on = false;
1438 racing::on = false;
1439 break;
1440
1441 case rg::chaos:
1442 if(tactic::on) firstland = laIce;
1443 yendor::on = tactic::on = princess::challenge = false;
1444 land_structure = ls::any_chaos() ? lsNiceWalls : lsChaos;
1445 if(bounded) set_geometry(gNormal);
1446 racing::on = false;
1447 break;
1448
1449 #if CAP_TOUR
1450 case rg::tour:
1451 if(tour::on) stop_tour();
1452 geometry = gNormal;
1453 yendor::on = tactic::on = princess::challenge = peace::on = inv::on = false;
1454 dual::disable();
1455 land_structure = lsNiceWalls;
1456 randomPatternsMode = false;
1457 variation = eVariation::bitruncated;
1458 #if CAP_GP
1459 gp::param = gp::loc(1, 1);
1460 #endif
1461 shmup::on = false;
1462 tour::on = !tour::on;
1463 racing::on = false;
1464 break;
1465 #endif
1466
1467 case rg::yendor:
1468 yendor::on = !yendor::on;
1469 tactic::on = false;
1470 peace::on = false;
1471 inv::on = false;
1472 princess::challenge = false;
1473 randomPatternsMode = false;
1474 land_structure = lsNiceWalls;
1475 racing::on = false;
1476 if(!yendor::on) firstland = laIce;
1477 dual::disable();
1478 break;
1479
1480 #if CAP_RACING
1481 case rg::racing:
1482 racing::on = !racing::on;
1483 shmup::on = racing::on;
1484 peace::on = false;
1485 tour::on = false;
1486 inv::on = false;
1487 land_structure = lsSingle;
1488 princess::challenge = false;
1489 dual::disable();
1490 break;
1491 #endif
1492
1493 case rg::tactic:
1494 tactic::on = !tactic::on;
1495 yendor::on = false;
1496 peace::on = false;
1497 inv::on = false;
1498 randomPatternsMode = false;
1499 princess::challenge = false;
1500 racing::on = false;
1501 land_structure = tactic::on ? lsSingle : lsNiceWalls;
1502 if(!tactic::on) firstland = laIce;
1503 dual::disable();
1504 break;
1505
1506 case rg::shmup:
1507 shmup::on = !shmup::on;
1508 princess::challenge = false;
1509 if(!shmup::on) racing::on = false;
1510 break;
1511
1512 case rg::randpattern:
1513 randomPatternsMode = !randomPatternsMode;
1514 tactic::on = false;
1515 yendor::on = false;
1516 peace::on = false;
1517 princess::challenge = false;
1518 break;
1519
1520 case rg::princess:
1521 princess::challenge = !princess::challenge;
1522 firstland = specialland = princess::challenge ? laPalace : laIce;
1523 shmup::on = false;
1524 tactic::on = false;
1525 yendor::on = false;
1526 land_structure = princess::challenge ? lsSingle : lsNiceWalls;
1527 inv::on = false;
1528 racing::on = false;
1529 dual::disable();
1530 break;
1531
1532 #if CAP_DAILY
1533 case rg::daily:
1534 daily::setup();
1535 break;
1536
1537 case rg::daily_off:
1538 daily::turnoff();
1539 break;
1540 #endif
1541 }
1542 }
1543
start_game()1544 EX void start_game() {
1545 if(game_active) return;
1546 DEBBI(DF_INIT, ("start_game"));
1547 if(dual::state == 1) dual::assign_landsides();
1548 if(dual::split(start_game)) return;
1549 restart:
1550 game_active = true;
1551 gamegen_failure = false;
1552 ignored_memory_warning = false;
1553 check_cgi();
1554 cgi.require_basics();
1555 #if CAP_ARCM
1556 arcm::current_or_fake().compute_geometry();
1557 #endif
1558 initcells();
1559 expansion.reset();
1560
1561 if(randomPatternsMode) {
1562 for(int i=0; i<landtypes; i++) {
1563 randompattern[i] = hrandpos();
1564 // change probability 1/5 to 2/6
1565 if(hrand(5) == 0) {
1566 randompattern[i] -= (randompattern[i] % 5);
1567 }
1568 }
1569 if(randomPatternsMode) specialland = pickLandRPM(laNone);
1570 clearMemoRPM();
1571 }
1572
1573 initgame();
1574 if(gamegen_failure) {
1575 stop_game();
1576 goto restart;
1577 }
1578 canmove = true;
1579 restartGraph();
1580 resetmusic();
1581 resetmusic();
1582 #if CAP_TEXTURE
1583 texture::config.remap();
1584 #endif
1585 subscreens::prepare();
1586 }
1587
1588 // popAllScreens + popAllGames + stop_game + switch_game_mode + start_game
restart_game(char switchWhat IS (rg::nothing))1589 EX void restart_game(char switchWhat IS(rg::nothing)) {
1590 popScreenAll();
1591 stop_game();
1592 switch_game_mode(switchWhat);
1593 start_game();
1594 }
1595
1596 // stop_game + switch_game_mode
stop_game_and_switch_mode(char switchWhat IS (rg::nothing))1597 EX void stop_game_and_switch_mode(char switchWhat IS(rg::nothing)) {
1598 stop_game();
1599 switch_game_mode(switchWhat);
1600 }
1601
1602 EX purehookset hooks_clearmemory;
1603
clearMemory()1604 EX void clearMemory() {
1605 callhooks(hooks_clearmemory);
1606 }
1607
1608 EX bool fixseed = false;
1609 EX int startseed = 0;
1610
1611 EX eLand firstland0;
1612
1613 EX purehookset hooks_initialize;
1614
initAll()1615 EX void initAll() {
1616 callhooks(hooks_initialize);
1617 init_floorcolors();
1618 showstartmenu = true;
1619 ca::init();
1620 #if CAP_COMMANDLINE
1621 arg::read(1);
1622 #endif
1623 srand(time(NULL));
1624 shrand(fixseed ? startseed : time(NULL));
1625
1626 achievement_init(); // not in ANDROID
1627
1628 firstland0 = firstland;
1629
1630 // initlanguage();
1631 initialize_all();
1632 #if CAP_SAVE
1633 loadsave();
1634 if(IRREGULAR) irr::auto_creator();
1635 #endif
1636 start_game();
1637
1638 if(!shmup::on) {
1639 restoreGolems(items[itOrbLife], moGolem); items[itOrbLife] = 0;
1640 restoreGolems(items[itOrbFriend], moTameBomberbird); items[itOrbFriend] = 0;
1641 restoreGolems(kills[moPrincessMoved], moPrincess, princess::saveHP); kills[moPrincessMoved] = 0;
1642 restoreGolems(kills[moPrincessArmedMoved], moPrincessArmed, princess::saveArmedHP); kills[moPrincessArmedMoved] = 0;
1643 }
1644
1645 firstland = firstland0;
1646 polygonal::solve();
1647 }
1648
1649 EX purehookset hooks_final_cleanup;
1650
finishAll()1651 EX void finishAll() {
1652 achievement_final(!items[itOrbSafety]);
1653
1654 #if CAP_SAVE
1655 if(!casual)
1656 saveStats();
1657 #endif
1658 clearMemory();
1659 #if !ISMOBILE
1660 quit_all();
1661 #endif
1662
1663 achievement_close();
1664 callhooks(hooks_final_cleanup);
1665 }
1666
1667
__anon6ad9c2620102() 1668 auto cgm = addHook(hooks_clearmemory, 40, [] () {
1669 pathq.clear();
1670 dcal.clear();
1671 clearshadow();
1672 for(int i=0; i<MAXPLAYER; i++) lastmountpos[i] = NULL;
1673 seenSevenMines = false;
1674 recallCell = NULL;
1675 butterflies.clear();
1676 buggycells.clear();
1677 crush_next.clear();
1678 crush_now.clear();
1679 rosemap.clear();
1680 adj_memo.clear();
1681 }) +
__anon6ad9c2620202(gamedata* gd) 1682 addHook(hooks_gamedata, 0, [] (gamedata* gd) {
1683 gd->store(pathq);
1684 gd->store(dcal);
1685 gd->store(recallCell);
1686 gd->store(butterflies);
1687 gd->store(buggycells);
1688 gd->store(crush_now);
1689 gd->store(crush_next);
1690 gd->store(rosemap);
1691 gd->store(airmap);
1692 gd->store(adj_memo);
1693 gd->store(pd_from);
1694 gd->store(pd_range);
1695 gd->store(pathqm);
1696 gd->store(reachedfrom);
1697 gd->store(gravity_state);
1698 gd->store(last_gravity_state);
1699 gd->store(shpos);
1700 gd->store(cshpos);
1701 gd->store(gp::do_adjm);
1702 }) +
__anon6ad9c2620302() 1703 addHook(hooks_removecells, 0, [] () {
1704 eliminate_if(crush_next, is_cell_removed);
1705 eliminate_if(crush_now, is_cell_removed);
1706 eliminate_if(buggycells, is_cell_removed);
1707 eliminate_if(butterflies, [] (pair<cell*,int>& p) { return is_cell_removed(p.first); });
1708 for(int i=0; i<SHSIZE; i++) for(int p=0; p<MAXPLAYER; p++)
1709 set_if_removed(shpos[i][p], NULL);
1710 });
1711 }
1712