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