1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-11-27 14:37:16 +00:00
hyperrogue/system.cpp

1447 lines
39 KiB
C++
Raw Normal View History

// Hyperbolic Rogue -- starting and ending games
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
2016-08-26 09:58:03 +00:00
/** \file system.cpp
* \brief changing game modes, starting, closing, loading and saving games
*/
2016-08-26 09:58:03 +00:00
namespace hr {
2019-08-10 00:16:48 +00:00
#if HDR
namespace rg {
// possible parameters e.g. for restart_game and wrongmode
static const char nothing = 0;
static const char peace = 'P';
static const char inv = 'i';
static const char chaos = 'C';
static const char tactic = 't';
static const char tour = 'T';
static const char yendor = 'y';
static const char shmup = 's';
static const char randpattern = 'r';
static const char princess = 'p';
static const char daily = 'd';
static const char daily_off = 'D';
static const char racing = 'R';
static const char dualmode = 'U';
// wrongmode only -- marks 'global' achievements not related to the current mode
static const char global = 'x';
// wrongmode only -- change vid.scfg.players then restart_game(rg::nothing) instead
static const char multi = 'm';
// wrongmode only -- mark achievements for special geometries/variations
static const char special_geometry = 'g';
}
#endif
2019-08-09 19:00:52 +00:00
EX bool game_active;
2019-08-09 20:07:03 +00:00
EX bool cblind;
EX bool autocheat;
EX bool canvas_invisible;
2016-08-26 09:58:03 +00:00
2019-08-09 20:07:03 +00:00
EX int truelotus;
EX int gamecount;
2016-08-26 09:58:03 +00:00
2019-08-09 20:07:03 +00:00
EX int asteroids_generated, asteroid_orbs_generated;
2019-03-30 16:51:37 +00:00
2019-08-09 20:07:03 +00:00
EX time_t timerstart, savetime;
EX bool timerstopped;
2016-08-26 09:58:03 +00:00
int savecount;
2019-08-09 19:00:52 +00:00
EX bool showoff = false;
EX bool doCross = false;
2016-08-26 09:58:03 +00:00
bool gamegen_failure;
2019-08-09 20:07:03 +00:00
EX eLand top_land;
2018-03-29 22:20:33 +00:00
2017-07-22 23:33:27 +00:00
bool verless(string v, string cmp) {
if(isdigit(v[0]) && isdigit(v[1]))
v = "A" + v;
if(isdigit(cmp[0]) && isdigit(cmp[1]))
cmp = "A" + cmp;
2017-07-16 21:00:55 +00:00
return v < cmp;
}
2018-04-15 11:19:03 +00:00
bool do_use_special_land() {
return
!safety &&
(peace::on || tactic::on || geometry || NONSTDVAR || randomPatternsMode || yendor::on || racing::on);
2018-04-15 11:19:03 +00:00
}
hookset<bool()> *hooks_welcome_message;
2017-08-06 12:50:16 +00:00
void welcomeMessage() {
if(callhandlers(false, hooks_welcome_message)) return;
else if(tactic::trailer) return;
2017-08-06 12:50:16 +00:00
#if CAP_TOUR
else if(tour::on) return; // displayed by tour
#endif
else if(princess::challenge) {
kills[moVizier] = 1;
princess::forceMouse = true;
if(yendor::everwon)
items[itGreenStone] = 99;
addMessage(XLAT("Welcome to %the1 Challenge!", moPrincess));
addMessage(XLAT("The more Hypersian Rugs you collect, the harder it is.", moPrincess));
}
2017-08-06 12:50:16 +00:00
else if(randomPatternsMode)
addMessage(XLAT("Welcome to the Random Pattern mode!"));
else if(tactic::on)
addMessage(XLAT("You are playing %the1 in the Pure Tactics mode.", firstland));
else if(yendor::on)
addMessage(XLAT("Welcome to the Yendor Challenge %1!", its(yendor::challenge)));
else if(peace::on) ; // no welcome message
else if(shmup::on) ; // welcome message given elsewhere
else if(euclid)
addMessage(XLAT("Welcome to the Euclidean mode!"));
else if(specialland == laHalloween && BITRUNCATED && among(geometry, gSphere, gElliptic))
2017-08-06 12:50:16 +00:00
addMessage(XLAT("Welcome to Halloween!"));
else if(elliptic)
addMessage(XLAT("Good luck in the elliptic plane!"));
else if(sphere)
addMessage(XLAT("Welcome to Spherogue!"));
2019-07-30 11:02:24 +00:00
else if(sol)
addMessage(XLAT("Welcome to SolvRogue!"));
else if(PURE && geometry == gNormal && !cheater)
addMessage(XLAT("Welcome to the Heptagonal Mode!"));
else if(cheater || autocheat)
addMessage(XLAT("Welcome to HyperRogue! (cheat mode on)"));
else
addMessage(XLAT("Welcome to HyperRogue!"));
2017-08-06 12:50:16 +00:00
2018-05-15 21:26:04 +00:00
if(do_use_special_land() || firstland != laIce) if(!daily::on) {
2018-04-15 11:19:03 +00:00
auto lv = land_validity(specialland);
if(lv.flags & lv::display_error_message)
addMessage(XLAT(lv.msg));
}
2017-08-06 12:50:16 +00:00
#if ISMAC
addMessage(XLAT("Press F1 or right-shift-click things for help."));
#elif !ISMOBILE
addMessage(XLAT("Press F1 or right-click things for help."));
#endif
}
2017-10-30 21:47:18 +00:00
int trailer_cash0 = 5;
int trailer_cash1 = 15;
bool trailer_safety = true;
hookset<void()> *hooks_initgame;
2016-08-26 09:58:03 +00:00
// initialize the game
2019-08-09 19:18:13 +00:00
EX void initgame() {
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT, ("initGame"));
callhooks(hooks_initgame);
2016-08-26 09:58:03 +00:00
2018-11-17 18:26:35 +00:00
if(multi::players < 1 || multi::players > MAXPLAYER)
multi::players = 1;
multi::whereto[0].d = MD_UNDECIDED;
multi::cpid = 0;
2016-08-26 09:58:03 +00:00
yendor::init(1);
if(safety && safetyseed) {
shrand(safetyseed);
firstland = safetyland;
}
#if CAP_RACING
if(racing::on) racing::apply_seed();
#endif
2018-04-15 11:19:03 +00:00
bool use_special_land = do_use_special_land();
if(use_special_land) firstland = specialland;
2016-08-26 09:58:03 +00:00
if(firstland == laNone || firstland == laBarrier)
firstland = laCrossroads;
2017-03-23 10:53:57 +00:00
if(firstland == laCrossroads5 && !tactic::on) firstland = laCrossroads2; // could not fit!
2016-08-26 09:58:03 +00:00
if(firstland == laOceanWall) firstland = laOcean;
if(firstland == laHauntedWall) firstland = laGraveyard;
2017-10-13 19:28:14 +00:00
if(firstland == laMercuryRiver) firstland = laTerracotta;
2017-03-23 10:53:57 +00:00
if(firstland == laMountain && !tactic::on) firstland = laJungle;
2018-04-15 11:19:13 +00:00
if(firstland == laPrincessQuest) firstland = laPalace;
2018-08-04 20:36:21 +00:00
if((isGravityLand(firstland) && !isCyclic(firstland)) || (firstland == laOcean && !safety && !yendor::on))
firstland = weirdhyperbolic ? laCrossroads4 : laCrossroads;
2016-08-26 09:58:03 +00:00
clear_euland(specialland);
cwt.at = currentmap->gamestart(); cwt.spin = 0; cwt.mirrored = false;
cwt.at->land = firstland;
2018-10-25 00:43:14 +00:00
if(firstland == laBrownian) brownian::init(cwt.at);
2016-08-26 09:58:03 +00:00
chaosAchieved = false;
if(firstland == laElementalWall) cwt.at->land = randomElementalLand();
2016-08-26 09:58:03 +00:00
resetview();
createMov(cwt.at, 0);
2017-03-23 10:53:57 +00:00
2019-05-27 05:17:39 +00:00
pregen();
setdist(cwt.at, BARLEV, NULL);
2016-08-26 09:58:03 +00:00
2019-05-16 14:57:14 +00:00
if(!use_special_land && !safety) {
if(firstland != (princess::challenge ? laPalace : laIce)) cheater++;
}
2017-07-04 13:38:33 +00:00
if((tactic::on || yendor::on || peace::on) && isCyclic(firstland)) {
2016-08-26 09:58:03 +00:00
anthraxBonus = items[itHolyGrail];
cwt.at->move(0)->land = firstland;
if(firstland == laWhirlpool) cwt.at->move(0)->wall = waSea;
2016-08-26 09:58:03 +00:00
setdist(cwt.at->move(0), BARLEV-1, cwt.at);
if(!sphere && !eubinary && !quotient) {
heptagon *h = createAlternateMap(cwt.at, 2, hsA);
2017-10-30 21:47:18 +00:00
if(!h) printf("FAIL\n");
}
2016-08-26 09:58:03 +00:00
}
if(tactic::on && firstland == laPower) {
items[itOrbSpeed] = 30;
items[itOrbWinter] = 30;
items[itOrbFlash] = 30;
}
if(tactic::on && firstland == laCaribbean) {
2017-03-23 10:53:57 +00:00
if(hiitemsMax(itRedGem) >= 25) items[itRedGem] = min(hiitemsMax(itRedGem), 50);
if(hiitemsMax(itFernFlower) >= 25) items[itFernFlower] = min(hiitemsMax(itFernFlower), 50);
if(hiitemsMax(itWine) >= 25) items[itWine] = min(hiitemsMax(itWine), 50);
2016-08-26 09:58:03 +00:00
}
if(tactic::on && tactic::trailer)
2017-10-30 21:47:18 +00:00
items[treasureType(firstland)] = trailer_cash0;
2016-08-26 09:58:03 +00:00
yendor::lastchallenge = yendor::challenge;
2018-11-17 18:26:35 +00:00
if(shmup::on) shmup::init();
2016-08-26 09:58:03 +00:00
yendor::init(2);
2018-11-17 18:30:50 +00:00
#if CAP_RACING
if(racing::on) racing::generate_track();
#endif
if(gamegen_failure) return;
2017-08-06 12:50:16 +00:00
if(euclid && specialland == laPrincessQuest) {
cell *c = *euclideanAtCreate(pair_to_vec(EPX, EPY)).first;
2016-08-26 09:58:03 +00:00
princess::generating = true;
c->land = laPalace;
setdist(c, 7 - getDistLimit() - genrange_bonus, NULL);
2016-08-26 09:58:03 +00:00
princess::generating = false;
}
if(cwt.at->land == laCrossroads2) {
cwt.at->landparam = 12;
createMov(cwt.at, 0)->landparam = 44;
createMov(cwt.at, 0)->land = laCrossroads2;
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
2019-05-28 23:01:13 +00:00
sword::determine_sword_angles();
for(int i=0; i<numplayers(); i++)
sword::dir[i] = sword::initial(cwt.at);
2016-08-26 09:58:03 +00:00
2018-04-30 22:21:18 +00:00
#if CAP_DAILY
daily::split();
#endif
2016-08-26 09:58:03 +00:00
// extern int sightrange; sightrange = 9;
// cwt.at->land = laHell; items[itHell] = 10;
for(int i=BARLEV; i>=7 - getDistLimit() - genrange_bonus; i--) {
if(tactic::trailer && cwt.at->land != laClearing) safety = trailer_safety;
setdist(cwt.at, i, NULL);
2016-08-26 09:58:03 +00:00
if(tactic::trailer) safety = false;
2018-03-29 22:20:33 +00:00
currentmap->verify();
2017-03-23 10:53:57 +00:00
}
if(doCross) {
for(int i=0; i<ittypes; i++) if(itemclass(eItem(i)) == IC_TREASURE) items[i] = 50;
for(int i=0; i<motypes; i++) kills[i] = 30;
items[itSavedPrincess] = 0;
kills[moPrincessMoved] = 0;
kills[moPrincessArmedMoved] = 0;
kills[moPlayer] = 0;
}
2017-03-23 10:53:57 +00:00
if(quotient && generateAll(firstland)) {
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(currentmap->allcells()); i++)
setdist(currentmap->allcells()[i], 8, NULL);
2017-03-23 10:53:57 +00:00
}
if(multi::players > 1 && !shmup::on) for(int i=0; i<numplayers(); i++) {
int idir = (3 * i) % cwt.at->type;
multi::player[i].at = cwt.at->move(idir);
2017-03-23 10:53:57 +00:00
// special case -- otherwise they land on a wall
if(firstland == laCrossroads2 && i == 1)
multi::player[1].at = cwt.at;
2017-03-23 10:53:57 +00:00
if(firstland == laCrossroads2 && i == 6)
multi::player[6].at = createMov(createMov(cwt.at, 0), 3);
setdist(cwt.at->move(idir), 7 - getDistLimit() - genrange_bonus, cwt.at);
2017-03-23 10:53:57 +00:00
multi::player[i].spin = 0;
multi::flipped[i] = true;
multi::whereto[i].d = MD_UNDECIDED;
2016-08-26 09:58:03 +00:00
}
if(tactic::on && tactic::trailer)
2017-10-30 21:47:18 +00:00
items[treasureType(firstland)] = trailer_cash1;
2016-08-26 09:58:03 +00:00
yendor::init(3);
2017-07-04 13:38:33 +00:00
peace::simon::init();
2017-03-23 10:53:57 +00:00
multi::revive_queue.clear();
2017-07-22 23:33:27 +00:00
#if CAP_TOUR
if(tour::on) tour::presentation(tour::pmRestart);
2017-04-08 15:18:29 +00:00
#endif
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
if(multi::players > 1 && !shmup::on) {
for(int i=0; i<numplayers(); i++)
makeEmpty(playerpos(i));
}
else {
for(int i=0; i<numplayers(); i++)
makeEmpty(cwt.at);
2017-03-23 10:53:57 +00:00
}
2016-08-26 09:58:03 +00:00
if(specialland == laMinefield && bounded) {
bfs();
generate_mines();
}
2017-07-16 21:00:55 +00:00
princess::squeaked = false;
2018-04-14 08:24:02 +00:00
clearing::current_root = NULL;
2017-07-16 21:00:55 +00:00
2016-08-26 09:58:03 +00:00
if(!safety) {
usedSafety = false;
timerstart = time(NULL); turncount = 0; rosewave = 0; rosephase = 0;
patterns::whichShape = 0;
2016-08-26 09:58:03 +00:00
noiseuntil = 0;
sagephase = 0; hardcoreAt = 0;
timerstopped = false;
savecount = 0; savetime = 0;
cheater = 0;
if(autocheat) cheater = 1;
hauntedWarning = false;
if(!autocheat) {
timerghost = true;
gen_wandering = true;
}
2016-08-26 09:58:03 +00:00
truelotus = 0;
2019-03-30 16:51:37 +00:00
asteroids_generated = 0;
asteroid_orbs_generated = 0;
2016-08-26 09:58:03 +00:00
survivalist = true;
2019-02-17 17:28:20 +00:00
#if CAP_CRYSTAL
crystal::used_compass_inside = false;
2019-02-17 17:28:20 +00:00
#endif
got_survivalist = false;
2017-07-22 23:33:27 +00:00
#if CAP_INV
2017-07-04 13:38:33 +00:00
if(inv::on) inv::init();
#endif
auto_teleport_charges();
2018-04-14 07:34:33 +00:00
if(!use_special_land) {
2016-08-26 09:58:03 +00:00
if(firstland != (princess::challenge ? laPalace : laIce)) cheater++;
}
2017-08-06 12:50:16 +00:00
welcomeMessage();
2016-08-26 09:58:03 +00:00
}
else {
usedSafety = true;
safety = false;
}
2017-03-23 10:53:57 +00:00
havewhat = hadwhat = 0; rosemap.clear();
2016-08-26 09:58:03 +00:00
elec::lightningfast = 0;
2017-03-23 10:53:57 +00:00
lastsafety = gold();
2016-08-26 09:58:03 +00:00
bfs();
checkmove();
2018-03-25 13:07:11 +00:00
playermoved = true;
if(quotient || sphere)
for(cell *c: currentmap->allcells()) setdist(c, 8, NULL);
if(!allowChangeRange()) {
2018-11-01 17:59:25 +00:00
gamerange_bonus = genrange_bonus = 0;
if(vid.use_smart_range == 2) vid.use_smart_range = 1;
}
if(!allowIncreasedSight()) vid.use_smart_range = 0;
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
bool havesave = true;
2017-07-22 23:33:27 +00:00
#if CAP_SAVE
2017-07-16 21:00:55 +00:00
#define MAXBOX 500
#define POSSCORE 371 // update this when new boxes are added!
2016-08-26 09:58:03 +00:00
struct score {
string ver;
int box[MAXBOX];
};
int savebox[MAXBOX], boxid;
bool saving, loading, loadingHi;
string boxname[MAXBOX];
bool fakebox[MAXBOX];
2017-03-23 10:53:57 +00:00
bool monsbox[MAXBOX];
2016-08-26 09:58:03 +00:00
void applyBox(int& t) {
if(saving) savebox[boxid++] = t;
else if(loading) t = savebox[boxid++];
else boxid++;
}
2019-08-09 20:07:03 +00:00
EX void applyBoxNum(int& i, string name IS("")) {
2016-08-26 09:58:03 +00:00
fakebox[boxid] = (name == "");
boxname[boxid] = name;
2017-03-23 10:53:57 +00:00
monsbox[boxid] = false;
2016-08-26 09:58:03 +00:00
applyBox(i);
}
void applyBoxBool(bool& b, string name = "") {
int i = b;
applyBoxNum(i, name);
2017-03-23 10:53:57 +00:00
monsbox[boxid] = false;
2016-08-26 09:58:03 +00:00
b = i;
}
// just skips the value when loading
void applyBoxSave(int i, string name = "") {
fakebox[boxid] = (name == "");
boxname[boxid] = name;
applyBox(i);
}
int applyBoxLoad(string name = "") {
fakebox[boxid] = (name == "");
boxname[boxid] = name;
int i=0; applyBox(i);
return i;
}
void applyBoxI(eItem it, bool f = false) {
boxname[boxid] = iinf[it].name;
fakebox[boxid] = f;
2017-03-23 10:53:57 +00:00
monsbox[boxid] = false;
2016-08-26 09:58:03 +00:00
if(loadingHi) {
updateHi(it, savebox[boxid++]);
}
else applyBox(items[it]);
}
2017-07-16 21:00:55 +00:00
vector<eItem> invorb;
void addinv(eItem it) {
invorb.push_back(it);
}
void applyBoxOrb(eItem it) {
applyBoxI(it, true);
invorb.push_back(it);
}
void list_invorb() {
for(eItem it: invorb) {
2017-07-22 23:33:27 +00:00
#if CAP_INV
2017-07-16 21:00:55 +00:00
if(true) {
inv::applyBox(it);
continue;
}
#endif
int u = 0;
applyBoxNum(u);
}
invorb.clear();
}
2016-08-26 09:58:03 +00:00
void applyBoxM(eMonster m, bool f = false) {
fakebox[boxid] = f;
boxname[boxid] = minf[m].name;
2017-03-23 10:53:57 +00:00
monsbox[boxid] = true;
2016-08-26 09:58:03 +00:00
applyBox(kills[m]);
}
void applyBoxes() {
2017-07-16 21:00:55 +00:00
invorb.clear();
2016-08-26 09:58:03 +00:00
eLand lostin = laNone;
applyBoxSave((int) timerstart, "time elapsed");
time_t timer = time(NULL);
applyBoxSave((int) timer, "date");
applyBoxSave(gold(), "treasure collected");
applyBoxSave(tkills(), "total kills");
applyBoxNum(turncount, "turn count");
applyBoxNum(cellcount, "cells generated");
2017-03-23 10:53:57 +00:00
if(loading) timerstart = time(NULL);
2016-08-26 09:58:03 +00:00
for(int i=0; i<itOrbLightning; i++)
if(i == 0) items[i] = 0, applyBoxI(itFernFlower);
else applyBoxI(eItem(i));
for(int i=0; i<43; i++) {
if(loading) kills[i] = 0;
bool fake =
2017-03-23 10:53:57 +00:00
i == moLesserM || i == moNone || i == moWolfMoved || i == moTentacletail ||
i == moIvyNext;
2016-08-26 09:58:03 +00:00
if(i == moWormtail) applyBoxM(moCrystalSage);
else if(i == moWormwait) applyBoxM(moFireFairy);
else if(i == moTentacleEscaping) applyBoxM(moMiner);
else if(i == moGolemMoved) applyBoxM(moIllusion);
2017-07-16 21:00:55 +00:00
else if(i == moTentaclewait) applyBoxOrb(itOrbThorns);
else if(i == moGreater) applyBoxOrb(itOrbDragon);
else if(i == moGreaterM) applyBoxOrb(itOrbIllusion);
2016-08-26 09:58:03 +00:00
else applyBoxM(eMonster(i), fake);
}
if(saving) {
2017-03-23 10:53:57 +00:00
int totaltime = savetime;
if(!timerstopped) totaltime += timer - timerstart;
applyBoxSave((int) totaltime, "time played");
2016-08-26 09:58:03 +00:00
}
else if(loading) savetime = applyBoxLoad("time played");
2017-03-23 10:53:57 +00:00
else boxname[boxid] = "time played", boxid++;
2016-08-26 09:58:03 +00:00
if(saving) savecount++;
applyBoxNum(savecount, "number of saves");
if(saving) savecount--;
applyBoxNum(cheater, "number of cheats");
2017-03-23 10:53:57 +00:00
fakebox[boxid] = true;
if(saving) applyBoxSave(items[itOrbSafety] ? safetyland : cwt.at->land, "");
2016-08-26 09:58:03 +00:00
else if(loading) firstland = safetyland = eLand(applyBoxLoad());
else lostin = eLand(savebox[boxid++]);
2017-07-16 21:00:55 +00:00
for(int i=itOrbLightning; i<25; i++) applyBoxOrb(eItem(i));
2016-08-26 09:58:03 +00:00
applyBoxI(itRoyalJelly);
applyBoxI(itWine);
applyBoxI(itSilver);
applyBoxI(itEmerald);
applyBoxI(itPower);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbFire);
applyBoxOrb(itOrbInvis);
applyBoxOrb(itOrbAether);
applyBoxOrb(itOrbPsi);
2016-08-26 09:58:03 +00:00
applyBoxM(moBug0);
applyBoxM(moBug1);
applyBoxM(moBug2);
applyBoxM(moVineBeast);
applyBoxM(moVineSpirit);
applyBoxM(moLancer);
applyBoxM(moFlailer);
applyBoxM(moEarthElemental);
applyBoxM(moDarkTroll);
applyBoxM(moWitch);
applyBoxM(moWitchFire);
applyBoxM(moWitchFlash);
applyBoxM(moWitchGhost);
applyBoxM(moWitchSpeed);
applyBoxM(moEvilGolem);
applyBoxM(moWitchWinter);
applyBoxI(itHolyGrail);
applyBoxI(itGrimoire);
applyBoxM(moKnight);
applyBoxM(moCultistLeader);
applyBoxM(moPirate);
applyBoxM(moCShark);
applyBoxM(moParrot);
applyBoxI(itPirate);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbTime);
2016-08-26 09:58:03 +00:00
applyBoxM(moHexSnake);
applyBoxM(moRedTroll);
applyBoxI(itRedGem);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbSpace);
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
int geo = geometry;
applyBoxNum(geo, ""); geometry = eGeometry(geo);
2016-08-26 09:58:03 +00:00
applyBoxBool(hardcore, "hardcore");
2017-03-23 10:53:57 +00:00
applyBoxNum(hardcoreAt, "");
2016-08-26 09:58:03 +00:00
applyBoxBool(shmup::on, "shmup");
2017-08-06 12:50:16 +00:00
if(saving) applyBoxSave(specialland, "euclid land");
else if(loading) specialland = eLand(applyBoxLoad("euclid land"));
2017-03-23 10:53:57 +00:00
else fakebox[boxid++] = true;
2016-08-26 09:58:03 +00:00
applyBoxI(itCoast);
applyBoxI(itWhirlpool);
applyBoxI(itBombEgg);
applyBoxM(moBomberbird);
applyBoxM(moTameBomberbird);
applyBoxM(moAlbatross);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbFriend);
applyBoxOrb(itOrbAir);
applyBoxOrb(itOrbWater);
2016-08-26 09:58:03 +00:00
applyBoxI(itPalace);
applyBoxI(itFjord);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbFrog);
applyBoxOrb(itOrbDiscord);
2016-08-26 09:58:03 +00:00
applyBoxM(moPalace);
applyBoxM(moFatGuard);
applyBoxM(moSkeleton);
applyBoxM(moVizier);
applyBoxM(moViking);
applyBoxM(moFjordTroll);
applyBoxM(moWaterElemental);
applyBoxI(itSavedPrincess);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbLove);
2016-08-26 09:58:03 +00:00
applyBoxM(moPrincess);
2017-03-23 10:53:57 +00:00
applyBoxM(moPrincessMoved, false); // live Princess for Safety
applyBoxM(moPrincessArmedMoved, false); // live Princess for Safety
2016-08-26 09:58:03 +00:00
applyBoxM(moMouse);
2017-03-23 10:53:57 +00:00
applyBoxNum(princess::saveArmedHP, "");
applyBoxNum(princess::saveHP, "");
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
applyBoxI(itIvory);
2016-08-26 09:58:03 +00:00
applyBoxI(itElemental);
applyBoxI(itZebra);
applyBoxI(itFireShard);
applyBoxI(itWaterShard);
applyBoxI(itAirShard);
applyBoxI(itEarthShard);
applyBoxM(moAirElemental);
applyBoxM(moFireElemental);
2017-03-23 10:53:57 +00:00
applyBoxM(moFamiliar);
2016-08-26 09:58:03 +00:00
applyBoxM(moGargoyle);
applyBoxM(moOrangeDog);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbSummon);
applyBoxOrb(itOrbMatter);
2016-08-26 09:58:03 +00:00
applyBoxM(moForestTroll);
applyBoxM(moStormTroll);
applyBoxM(moOutlaw);
applyBoxM(moMutant);
applyBoxM(moMetalBeast);
applyBoxM(moMetalBeast2);
applyBoxI(itMutant);
applyBoxI(itFulgurite);
applyBoxI(itBounty);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbLuck);
applyBoxOrb(itOrbStunning);
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
applyBoxBool(tactic::on, "");
applyBoxNum(elec::lightningfast, "");
2016-08-26 09:58:03 +00:00
// if(savebox[boxid]) printf("lotus = %d (lost = %d)\n", savebox[boxid], isHaunted(lostin));
if(loadingHi && isHaunted(lostin)) boxid++;
else applyBoxI(itLotus);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbUndeath);
2016-08-26 09:58:03 +00:00
applyBoxI(itWindstone);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbEmpathy);
2016-08-26 09:58:03 +00:00
applyBoxM(moWindCrow);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itMutant2);
applyBoxOrb(itOrbFreedom);
2016-08-26 09:58:03 +00:00
applyBoxM(moRedFox);
applyBoxBool(survivalist);
if(loadingHi) applyBoxI(itLotus);
else applyBoxNum(truelotus, "lotus/escape");
int v = int(variation);
applyBoxNum(v, "variation");
variation = eVariation(v);
2016-08-26 09:58:03 +00:00
applyBoxI(itRose);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbBeauty);
2016-08-26 09:58:03 +00:00
applyBoxI(itCoral);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrb37);
applyBoxOrb(itOrbEnergy);
2016-08-26 09:58:03 +00:00
applyBoxM(moRatling);
applyBoxM(moFalsePrincess);
applyBoxM(moRoseLady);
applyBoxM(moRoseBeauty);
2019-05-27 05:17:39 +00:00
applyBoxNum(chaosmode, "Chaos mode");
2017-03-23 10:53:57 +00:00
applyBoxNum(multi::players, "shmup players");
if(multi::players < 1 || multi::players > MAXPLAYER)
multi::players = 1;
2016-08-26 09:58:03 +00:00
applyBoxM(moRatlingAvenger);
// printf("applybox %d\n", shmup::players);
applyBoxI(itApple);
2017-03-23 10:53:57 +00:00
applyBoxM(moSparrowhawk);
applyBoxM(moResearcher);
2016-08-26 09:58:03 +00:00
applyBoxI(itDragon);
applyBoxM(moDragonHead);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbDomination);
2016-08-26 09:58:03 +00:00
applyBoxI(itBabyTortoise);
2017-03-23 10:53:57 +00:00
applyBoxNum(tortoise::seekbits, "");
2016-08-26 09:58:03 +00:00
applyBoxM(moTortoise);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbShell);
2017-03-23 10:53:57 +00:00
2016-08-26 09:58:03 +00:00
applyBoxNum(safetyseed);
2017-03-23 10:53:57 +00:00
// (+18)
for(int i=0; i<6; i++) {
applyBoxNum(multi::treasures[i]);
applyBoxNum(multi::kills[i]);
applyBoxNum(multi::deaths[i]);
}
// (+8)
applyBoxM(moDragonTail);
applyBoxI(itKraken);
applyBoxM(moKrakenH);
applyBoxM(moKrakenT);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbSword);
2017-03-23 10:53:57 +00:00
applyBoxI(itBarrow);
applyBoxM(moDraugr);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbSword2);
2017-03-23 10:53:57 +00:00
applyBoxI(itTrollEgg);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbStone);
2017-03-23 10:53:57 +00:00
bool sph;
sph = false; applyBoxBool(sph, "sphere"); if(sph) geometry = gSphere;
sph = false; applyBoxBool(sph, "elliptic"); if(sph) geometry = gElliptic;
applyBox(princess::reviveAt);
applyBoxI(itDodeca);
applyBoxI(itAmethyst);
applyBoxI(itSlime);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbNature);
applyBoxOrb(itOrbDash);
addinv(itOrbRecall);
2017-03-23 10:53:57 +00:00
applyBoxM(moBat);
applyBoxM(moReptile);
applyBoxM(moFriendlyIvy);
applyBoxI(itGreenGrass);
applyBoxI(itBull);
2017-07-16 21:00:55 +00:00
applyBoxOrb(itOrbHorns);
applyBoxOrb(itOrbBull);
2017-03-23 10:53:57 +00:00
applyBoxM(moSleepBull);
applyBoxM(moRagingBull);
applyBoxM(moHerdBull);
applyBoxM(moButterfly);
applyBoxM(moGadfly);
2017-07-16 21:00:55 +00:00
// 10.0:
applyBoxNum(hinttoshow); // 258
addinv(itOrbMirror);
addinv(itGreenStone);
list_invorb();
applyBoxBool(inv::on, "inventory"); // 306
2017-07-22 23:33:27 +00:00
#if CAP_INV
2017-07-16 21:00:55 +00:00
applyBoxNum(inv::rseed);
#else
{ int u; applyBoxNum(u); }
#endif
2017-10-08 12:02:03 +00:00
// 10.1:
applyBoxI(itLavaLily);
2017-10-10 12:24:39 +00:00
applyBoxI(itHunting);
2017-10-08 12:02:03 +00:00
applyBoxI(itBlizzard);
applyBoxI(itTerra);
applyBoxOrb(itOrbSide1);
applyBoxOrb(itOrbSide2);
applyBoxOrb(itOrbSide3);
applyBoxOrb(itOrbLava);
applyBoxOrb(itOrbMorph);
applyBoxM(moHunterDog);
applyBoxM(moIceGolem);
applyBoxM(moVoidBeast);
applyBoxM(moJiangshi);
applyBoxM(moTerraWarrior);
applyBoxM(moSalamander);
applyBoxM(moLavaWolf);
2018-01-04 12:32:11 +00:00
applyBoxOrb(itOrbSlaying);
applyBoxOrb(itOrbMagnetism);
applyBoxOrb(itOrbPhasing);
applyBoxI(itDock);
applyBoxI(itGlowCrystal);
applyBoxI(itMagnet);
applyBoxI(itRuins);
applyBoxI(itSwitch);
applyBoxM(moNorthPole);
applyBoxM(moSouthPole);
applyBoxM(moSwitch1);
applyBoxM(moSwitch2);
applyBoxM(moAltDemon);
applyBoxM(moHexDemon);
applyBoxM(moPair);
applyBoxM(moCrusher);
applyBoxM(moMonk);
bool v2 = false;
applyBoxBool(v2); if(loading && v2) variation = eVariation::goldberg;
applyBox(gp::param.first);
applyBox(gp::param.second);
v2 = false; applyBoxBool(v2); if(loading && v2) variation = eVariation::irregular;
applyBox(irr::cellcount);
2017-10-08 12:02:03 +00:00
2018-07-23 03:19:06 +00:00
list_invorb();
2018-08-05 03:07:34 +00:00
applyBox(irr::bitruncations_performed);
2019-01-03 01:14:49 +00:00
applyBoxI(itVarTreasure);
applyBoxI(itBrownian);
applyBoxI(itWest);
applyBoxM(moAcidBird);
applyBoxM(moBrownBug);
applyBoxM(moVariantWarrior);
applyBoxM(moWestHawk);
applyBoxM(moFallingDog);
applyBoxOrb(itOrbIntensity);
applyBoxOrb(itOrbChoice);
applyBoxOrb(itOrbGravity);
list_invorb();
applyBoxM(moNarciss);
applyBoxM(moMirrorSpirit);
2018-08-05 03:07:34 +00:00
2017-03-23 10:53:57 +00:00
if(POSSCORE != boxid) printf("ERROR: %d boxes\n", boxid);
2016-08-26 09:58:03 +00:00
}
void saveBox() {
boxid = 0; saving = true; applyBoxes(); saving = false;
}
void loadBox() {
// have boxid
boxid = 0; loading = true; applyBoxes(); loading = false;
}
void loadBoxHigh() {
2017-03-23 10:53:57 +00:00
dynamicval<int> sp1(multi::players, savebox[197]);
dynamicval<eGeometry> sp2(geometry, (eGeometry) savebox[116]);
dynamicval<bool> sp3(shmup::on, savebox[119]);
2019-05-27 05:17:39 +00:00
dynamicval<int> sp4(chaosmode, savebox[196]);
dynamicval<eVariation> sp5(variation, (eVariation) savebox[186]);
dynamicval<int> sp7(gp::param.first, savebox[342]);
dynamicval<int> sp8(gp::param.second, savebox[343]);
dynamicval<bool> spinv(inv::on, savebox[306]);
2017-03-23 10:53:57 +00:00
if(savebox[238]) geometry = gSphere;
if(savebox[239]) geometry = gElliptic;
if(savebox[341]) variation = eVariation::goldberg;
if(savebox[344]) variation = eVariation::irregular;
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
if(multi::players < 1 || multi::players > MAXPLAYER)
multi::players = 1;
if(shmup::on && multi::players == 1) ;
2016-08-26 09:58:03 +00:00
else {
// have boxid
boxid = 0; loadingHi = true; applyBoxes(); loadingHi = false;
}
}
// certify that saves and achievements were received
// in an official version of HyperRogue
2017-07-22 23:33:27 +00:00
#if CAP_CERTIFY
2017-07-04 13:38:33 +00:00
#include "private/certify.cpp"
2016-08-26 09:58:03 +00:00
#else
namespace anticheat {
bool tampered;
void save(FILE *f) {}
bool load(FILE *f, score& sc, const string& ver) {return true; }
int certify(const string& s, int a, int b, int c, int d=0) { return d; }
int check(int cv, const string& ver, const string& s, int a, int b, int c, int d=0) { return cv==d; }
void nextid(int& tid, const string& ver, int cert) { tid++; }
};
#endif
long long saveposition = -1;
2017-03-23 10:53:57 +00:00
void remove_emergency_save() {
2017-07-22 23:33:27 +00:00
#if !ISWINDOWS
2017-03-23 10:53:57 +00:00
if(saveposition >= 0) {
if(truncate(scorefile, saveposition)) {}
saveposition = -1;
}
#endif
}
2019-08-09 19:18:13 +00:00
EX void saveStats(bool emergency IS(false)) {
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT, ("saveStats [%s]", scorefile));
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
if(autocheat) return;
2017-07-22 23:33:27 +00:00
#if CAP_TOUR
2017-04-08 15:18:29 +00:00
if(tour::on) return;
#endif
2016-08-26 09:58:03 +00:00
if(randomPatternsMode) return;
2019-05-29 14:50:36 +00:00
if(dual::state) return;
2018-08-21 14:10:12 +00:00
if(archimedean) return;
2018-05-15 21:26:04 +00:00
if(daily::on) return;
2017-07-04 13:38:33 +00:00
if(peace::on) return;
2017-07-12 16:03:53 +00:00
if(!gold()) return;
2017-03-23 10:53:57 +00:00
remove_emergency_save();
2016-08-26 09:58:03 +00:00
FILE *f = fopen(scorefile, "at");
if(!f) {
// printf("Could not open the score file '%s'!\n", scorefile);
addMessage(XLAT("Could not open the score file: ") + scorefile);
return;
}
if(emergency) {
saveposition = ftell(f);
2017-03-23 10:53:57 +00:00
// if(!timerghost) addMessage(XLAT("Emergency save at ") + its(saveposition));
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
if(showoff) { fclose(f); return; }
2016-08-26 09:58:03 +00:00
time_t timer;
timer = time(NULL);
char sbuf[128]; strftime(sbuf, 128, "%c", localtime(&timerstart));
char buf[128]; strftime(buf, 128, "%c", localtime(&timer));
if((tactic::on || yendor::on) && !items[itOrbSafety] && !cheater) {
int t = (int) (timer - timerstart);
int xcode = modecode();
if(tactic::on) {
int score = items[treasureType(specialland)];
2016-08-26 09:58:03 +00:00
if(score) {
int c =
anticheat::certify(dnameof(specialland), turncount, t, (int) timerstart,
2016-08-26 09:58:03 +00:00
xcode*999 + tactic::id + 256 * score);
2018-01-06 21:05:22 +00:00
fprintf(f, "TACTICS %s %d %d %d %d %d %d %d %d date: %s\n", VER,
tactic::id, specialland, score, turncount, t, int(timerstart),
2018-01-06 21:05:22 +00:00
c, xcode, buf);
tactic::record(specialland, score);
2016-08-26 09:58:03 +00:00
anticheat::nextid(tactic::id, VER, c);
}
}
if(yendor::on)
2018-01-06 21:05:22 +00:00
fprintf(f, "YENDOR %s %d %d %d %d %d %d %d %d date: %s\n", VER,
2016-08-26 09:58:03 +00:00
yendor::lastchallenge, items[itOrbYendor], yendor::won, turncount, t, int(timerstart),
anticheat::certify(yendor::won ? "WON" : "LOST", turncount, t, (int) timerstart,
xcode*999 + yendor::lastchallenge + 256 * items[itOrbYendor]),
2018-01-06 21:05:22 +00:00
xcode,
2016-08-26 09:58:03 +00:00
buf);
fclose(f);
return;
}
fprintf(f, "HyperRogue: game statistics (version " VER ")\n");
if(cheater)
fprintf(f, "CHEATER! (cheated %d times)\n", cheater);
if(true) {
fprintf(f, VER);
if(!shmup::on) items[itOrbLife] = countMyGolems(moGolem);
if(!shmup::on) items[itOrbFriend] = countMyGolems(moTameBomberbird);
if(!shmup::on) kills[moPrincessMoved] = countMyGolems(moPrincess);
if(!shmup::on) kills[moPrincessArmedMoved] = countMyGolems(moPrincessArmed);
if(!shmup::on) princess::saveHP = countMyGolemsHP(moPrincess);
if(!shmup::on) princess::saveArmedHP = countMyGolemsHP(moPrincessArmed);
saveBox();
for(int i=0; i<boxid; i++) fprintf(f, " %d", savebox[i]);
anticheat::save(f);
fprintf(f, "\n");
}
fprintf(f, "Played on: %s - %s (%d %s)\n", sbuf, buf, turncount,
shmup::on ? "knives" : "turns");
fprintf(f, "Total wealth: %d\n", gold());
fprintf(f, "Total enemies killed: %d\n", tkills());
fprintf(f, "cells generated: %d\n", cellcount);
2017-03-23 10:53:57 +00:00
if(pureHardcore()) fprintf(f, "Pure hardcore mode\n");
if(geometry) fprintf(f, "Geometry: %s/%s/%s\n", gp::operation_name().c_str(), ginf[geometry].tiling_name, ginf[geometry].quotient_name);
2016-08-26 09:58:03 +00:00
if(chaosmode) fprintf(f, "Chaos mode\n");
2017-03-23 10:53:57 +00:00
if(shmup::on) fprintf(f, "Shoot-em up mode\n");
2017-07-04 13:38:33 +00:00
if(inv::on) fprintf(f, "Inventory mode\n");
2017-03-23 10:53:57 +00:00
if(multi::players > 1) fprintf(f, "Multi-player (%d players)\n", multi::players);
2016-08-26 09:58:03 +00:00
fprintf(f, "Number of cells explored, by distance from the player:\n");
2017-03-23 10:53:57 +00:00
{for(int i=0; i<10; i++) fprintf(f, " %d", explore[i]);} fprintf(f, "\n");
2016-08-26 09:58:03 +00:00
if(kills[0]) fprintf(f, "walls melted: %d\n", kills[0]);
fprintf(f, "cells travelled: %d\n", celldist(cwt.at));
2016-08-26 09:58:03 +00:00
fprintf(f, "\n");
for(int i=0; i<ittypes; i++) if(items[i])
fprintf(f, "%4dx %s\n", items[i], iinf[i].name);
fprintf(f, "\n");
for(int i=1; i<motypes; i++) if(kills[i])
fprintf(f, "%4dx %s\n", kills[i], minf[i].name);
fprintf(f, "\n\n\n");
2017-07-22 23:33:27 +00:00
#if ISMOBILE==0
2019-05-12 23:57:40 +00:00
DEBB(DF_INIT, ("Game statistics saved to ", scorefile));
2017-03-23 10:53:57 +00:00
if(!tactic::trailer)
addMessage(XLAT("Game statistics saved to %1", scorefile));
2016-08-26 09:58:03 +00:00
#endif
fclose(f);
}
// load the save
2019-08-09 19:18:13 +00:00
EX void loadsave() {
2017-04-08 15:18:29 +00:00
if(autocheat) return;
2017-07-22 23:33:27 +00:00
#if CAP_TOUR
2017-04-08 15:18:29 +00:00
if(tour::on) return;
#endif
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT, ("loadSave"));
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
gamecount = 0;
2016-08-26 09:58:03 +00:00
FILE *f = fopen(scorefile, "rt");
havesave = f;
if(!f) return;
score sc;
bool ok = false;
bool tamper = false;
2017-07-16 21:00:55 +00:00
int coh = counthints();
2016-08-26 09:58:03 +00:00
while(!feof(f)) {
char buf[120];
if(fgets(buf, 120, f) == NULL) break;
if(buf[0] == 'H' && buf[1] == 'y') {
2017-03-23 10:53:57 +00:00
gamecount++;
if(fscanf(f, "%s", buf) <= 0) break;
sc.ver = buf;
2017-07-16 21:00:55 +00:00
if(sc.ver[1] != '.') sc.ver = '0' + sc.ver;
if(verless(sc.ver, "4.4") || sc.ver == "CHEATER!") { ok = false; continue; }
2016-08-26 09:58:03 +00:00
ok = true;
for(int i=0; i<MAXBOX; i++) {
if(fscanf(f, "%d", &sc.box[i]) <= 0) {
boxid = i;
tamper = anticheat::load(f, sc, sc.ver);
for(int i=0; i<boxid; i++) savebox[i] = sc.box[i];
for(int i=boxid; i<MAXBOX; i++) savebox[i] = 0, sc.box[i] = 0;
2017-07-16 21:00:55 +00:00
if(savebox[258] >= 0 && savebox[258] < coh) {
hints[savebox[258]].last = savebox[1];
}
2016-08-26 09:58:03 +00:00
loadBoxHigh();
break;
}
}
}
if(buf[0] == 'A' && buf[1] == 'C' && buf[2] == 'H') {
char buf1[80], buf2[80];
sscanf(buf, "%70s%70s", buf1, buf2);
if(buf2 == string("PRINCESS1")) princess::everSaved = true;
if(buf2 == string("YENDOR2")) yendor::everwon = true;
if(buf2 == string("CR4")) chaosUnlocked = true;
}
if(buf[0] == 'T' && buf[1] == 'A' && buf[2] == 'C') {
ok = false;
char buf1[80], ver[10];
int tid, land, score, tc, t, ts, cert;
2018-01-06 21:05:22 +00:00
int xc = -1;
sscanf(buf, "%70s%10s%d%d%d%d%d%d%d%d",
buf1, ver, &tid, &land, &score, &tc, &t, &ts, &cert, &xc);
2017-07-22 23:33:27 +00:00
eLand l2 = eLand(land);
if(land == laMirror && verless(ver, "10.0")) l2 = laMirrorOld;
2018-01-06 21:05:22 +00:00
if(xc == -1)
for(xc=0; xc<32768; xc++)
if(anticheat::check(cert, ver, dnameof(l2), tc, t, ts, xc*999+unsigned(tid) + 256 * score))
break;
2016-08-26 09:58:03 +00:00
2018-01-06 21:05:22 +00:00
if(tid == tactic::id && (anticheat::check(cert, ver, dnameof(l2), tc, t, ts, xc*unsigned(999)+ unsigned(tid) + 256 * score))) {
if(score != 0
&& !(land == laOcean && verless(ver, "8.0f"))
2018-01-25 22:25:35 +00:00
&& !(land == laTerracotta && verless(ver, "10.3e"))
2018-01-06 21:05:22 +00:00
) tactic::record(l2, score, xc);
anticheat::nextid(tactic::id, ver, cert);
}
2016-08-26 09:58:03 +00:00
}
if(buf[0] == 'Y' && buf[1] == 'E' && buf[2] == 'N') {
char buf1[80], ver[10];
2018-01-06 21:05:22 +00:00
int cid, oy, won, tc, t, ts, cert=0, xc = -1;
sscanf(buf, "%70s%10s%d%d%d%d%d%d%d%d",
buf1, ver, &cid, &oy, &won, &tc, &t, &ts, &cert, &xc);
if(xc == -1)
for(xc=0; xc<32768; xc++)
if(anticheat::check(cert, ver, won ? "WON" : "LOST", tc, t, ts, xc*999 + cid + 256 * oy))
break;
if(won) if(anticheat::check(cert, ver, won ? "WON" : "LOST", tc, t, ts, xc*999 + cid + 256 * oy)) {
2017-03-23 10:53:57 +00:00
if(xc == 19 && cid == 25) xc = 0;
2016-08-26 09:58:03 +00:00
if(cid > 0 && cid < YENDORLEVELS)
2017-07-16 21:00:55 +00:00
if(!(verless(ver, "8.0f") && oy > 1 && cid == 15))
if(!(verless(ver, "9.3b") && oy > 1 && (cid == 27 || cid == 28)))
2017-03-23 10:53:57 +00:00
{
2016-08-26 09:58:03 +00:00
yendor::bestscore[xc][cid] = max(yendor::bestscore[xc][cid], oy);
}
}
}
}
fclose(f);
if(ok && sc.box[65 + 4 + itOrbSafety - itOrbLightning]) {
anticheat::tampered = tamper;
// printf("box = %d (%d)\n", sc.box[65 + 4 + itOrbSafety - itOrbLightning], boxid);
// printf("boxid = %d\n", boxid);
for(int i=0; i<boxid; i++) savebox[i] = sc.box[i];
for(int i=boxid; i<MAXBOX; i++) savebox[i] = 0;
// for(int i=160; i<200; i++) printf("%d: %d ", i, savebox[i]);
loadBox();
// printf("boxid = %d\n", boxid);
if(items[itHolyGrail]) {
items[itHolyGrail]--;
knighted = newRoundTableRadius();
items[itHolyGrail]++;
}
else knighted = 0;
safety = true;
if(items[itSavedPrincess] < 0) items[itSavedPrincess] = 0;
addMessage(XLAT("Game loaded."));
2017-08-06 12:50:16 +00:00
showstartmenu = false;
// reset unsavable special modes just in case
peace::on = false;
randomPatternsMode = false;
yendor::on = false;
tour::on = false;
2016-08-26 09:58:03 +00:00
}
}
#endif
2019-08-09 19:18:13 +00:00
EX void stop_game() {
if(!game_active) return;
2019-05-28 23:09:38 +00:00
if(dual::split(stop_game)) return;
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT, ("stop_game"));
achievement_final(true);
#if CAP_SAVE
saveStats();
#endif
for(int i=0; i<ittypes; i++) items[i] = 0;
lastkills = 0; for(int i=0; i<motypes; i++) kills[i] = 0;
for(int i=0; i<10; i++) explore[i] = 0;
for(int i=0; i<10; i++) for(int l=0; l<landtypes; l++)
exploreland[i][l] = 0;
for(int i=0; i<numplayers(); i++)
if(multi::playerActive(i))
multi::deaths[i]++;
#if CAP_SAVE
anticheat::tampered = false;
#endif
achievementsReceived.clear();
princess::saved = false;
princess::nodungeon = false;
princess::reviveAt = 0;
princess::forceVizier = false;
princess::forceMouse = false;
knighted = 0;
// items[itGreenStone] = 100;
clearMemory();
game_active = false;
2018-05-26 00:14:52 +00:00
#if CAP_DAILY
if(daily::on)
2018-05-25 23:29:25 +00:00
daily::turnoff();
2018-05-26 00:14:52 +00:00
#endif
}
2019-08-09 19:18:13 +00:00
EX void set_geometry(eGeometry target) {
if(geometry != target) {
2019-08-15 13:05:43 +00:00
int old_DIM = GDIM;
stop_game();
ors::reset();
geometry = target;
if(chaosmode && bounded) chaosmode = false;
#if CAP_RACING
if(bounded) racing::on = false;
#endif
if(euclid6) variation = eVariation::bitruncated;
2019-02-17 17:28:20 +00:00
#if CAP_IRR
if(IRREGULAR) variation = eVariation::bitruncated;
2019-02-17 17:28:20 +00:00
#endif
#if CAP_GP
if(GOLDBERG && gp::param == gp::loc(1,1) && S3 == 3) {
variation = eVariation::bitruncated;
}
if(GOLDBERG && nonorientable) {
if(gp::param.second && gp::param.second != gp::param.first)
gp::param.second = 0;
}
2019-02-17 17:28:20 +00:00
#endif
2018-08-30 00:11:43 +00:00
if(DUAL && geometry != gArchimedean)
variation = ginf[geometry].default_variation;
2019-02-17 17:28:20 +00:00
#if CAP_BT
2019-07-25 10:24:02 +00:00
if(binarytiling || WDIM == 3 || penrose) variation = eVariation::pure;
2019-02-17 17:28:20 +00:00
#endif
2019-08-15 13:05:43 +00:00
if(GDIM == 3 && old_DIM == 2 && pmodel == mdDisk) pmodel = mdPerspective;
if(nonisotropic && old_DIM == 2) pmodel = mdGeodesic;
2019-08-15 13:05:43 +00:00
if(GDIM == 2 && among(pmodel, mdPerspective, mdGeodesic)) pmodel = mdDisk;
if(nonisotropic && old_DIM == 2 && vid.texture_step < 4) vid.texture_step = 4;
}
}
2019-08-09 19:18:13 +00:00
EX void set_variation(eVariation target) {
if(variation != target) {
stop_game();
2019-07-28 09:07:21 +00:00
if(euclid6 || binarytiling || sol || penrose) geometry = gNormal;
auto& cd = ginf[gCrystal];
if(target == eVariation::bitruncated && geometry == gCrystal && cd.sides == 8 && cd.vertex == 4) {
cd.vertex = 3;
cd.tiling_name = "{8,3}";
target = eVariation::pure;
}
variation = target;
}
}
2019-05-29 00:44:30 +00:00
void stop_tour() {
while(gamestack::pushed()) {
gamestack::pop();
stop_game();
}
}
2019-08-09 19:18:13 +00:00
EX void switch_game_mode(char switchWhat) {
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT, ("switch_game_mode ", switchWhat));
switch(switchWhat) {
case rg::peace:
peace::on = !peace::on;
tactic::on = yendor::on = princess::challenge =
randomPatternsMode = inv::on = false;
racing::on = false;
break;
2019-05-29 00:44:30 +00:00
case rg::dualmode:
stop_tour(); tour::on = false;
racing::on = false;
yendor::on = tactic::on = princess::challenge = false;
if(!dual::state) dual::enable();
else dual::disable();
break;
case rg::inv:
inv::on = !inv::on;
if(tactic::on) firstland = laIce;
tactic::on = yendor::on = princess::challenge =
randomPatternsMode = peace::on = false;
racing::on = false;
break;
case rg::chaos:
if(tactic::on) firstland = laIce;
yendor::on = tactic::on = princess::challenge = false;
chaosmode = !chaosmode;
if(bounded) set_geometry(gNormal);
racing::on = false;
break;
2017-07-22 23:33:27 +00:00
#if CAP_TOUR
case rg::tour:
2019-05-29 00:44:30 +00:00
if(tour::on) stop_tour();
geometry = gNormal;
yendor::on = tactic::on = princess::challenge = peace::on = inv::on = false;
2019-05-29 00:44:30 +00:00
dual::disable();
chaosmode = randomPatternsMode = false;
variation = eVariation::bitruncated;
#if CAP_GP
gp::param = gp::loc(1, 1);
#endif
shmup::on = false;
tour::on = !tour::on;
racing::on = false;
break;
2017-04-08 15:18:29 +00:00
#endif
case rg::yendor:
yendor::on = !yendor::on;
tactic::on = false;
peace::on = false;
inv::on = false;
princess::challenge = false;
randomPatternsMode = false;
chaosmode = false;
racing::on = false;
if(!yendor::on) firstland = laIce;
2019-05-29 00:44:30 +00:00
dual::disable();
break;
#if CAP_RACING
case rg::racing:
racing::on = !racing::on;
shmup::on = racing::on;
peace::on = false;
tour::on = false;
inv::on = false;
chaosmode = false;
princess::challenge = false;
2019-07-28 09:07:21 +00:00
if(sol || bounded) set_geometry(gNormal);
2019-05-29 00:44:30 +00:00
dual::disable();
break;
#endif
case rg::tactic:
tactic::on = !tactic::on;
yendor::on = false;
peace::on = false;
inv::on = false;
randomPatternsMode = false;
princess::challenge = false;
racing::on = false;
chaosmode = false;
if(!tactic::on) firstland = laIce;
2019-05-29 00:44:30 +00:00
dual::disable();
break;
case rg::shmup:
shmup::on = !shmup::on;
princess::challenge = false;
if(!shmup::on) racing::on = false;
break;
case rg::randpattern:
randomPatternsMode = !randomPatternsMode;
tactic::on = false;
yendor::on = false;
peace::on = false;
inv::on = false;
princess::challenge = false;
break;
case rg::princess:
princess::challenge = !princess::challenge;
firstland = princess::challenge ? laPalace : laIce;
shmup::on = false;
tactic::on = false;
yendor::on = false;
chaosmode = false;
inv::on = false;
racing::on = false;
2019-05-29 00:44:30 +00:00
dual::disable();
break;
2018-05-01 17:38:42 +00:00
#if CAP_DAILY
case rg::daily:
daily::setup();
break;
case rg::daily_off:
daily::turnoff();
break;
2018-05-01 17:38:42 +00:00
#endif
2016-08-26 09:58:03 +00:00
}
}
2019-08-09 19:18:13 +00:00
EX void start_game() {
if(game_active) return;
2019-05-12 23:57:40 +00:00
DEBBI(DF_INIT, ("start_game"));
2019-05-28 23:09:38 +00:00
if(dual::state == 1) dual::assign_landsides();
if(dual::split(start_game)) return;
restart:
game_active = true;
gamegen_failure = false;
2019-06-06 17:37:17 +00:00
ignored_memory_warning = false;
check_cgi();
cgi.require_basics();
2016-08-26 09:58:03 +00:00
initcells();
expansion.reset();
2016-08-26 09:58:03 +00:00
if(randomPatternsMode) {
for(int i=0; i<landtypes; i++) {
randompattern[i] = hrandpos();
// change probability 1/5 to 2/6
if(hrand(5) == 0) {
randompattern[i] -= (randompattern[i] % 5);
}
}
if(randomPatternsMode) specialland = pickLandRPM(laNone);
2016-08-26 09:58:03 +00:00
clearMemoRPM();
}
initgame();
if(gamegen_failure) {
stop_game();
goto restart;
}
2016-08-26 09:58:03 +00:00
canmove = true;
restartGraph();
resetmusic();
resetmusic();
2018-08-20 00:04:49 +00:00
#if CAP_TEXTURE
texture::config.remap();
2018-11-17 18:30:50 +00:00
#endif
2019-03-09 15:20:06 +00:00
subscreens::prepare();
2016-08-26 09:58:03 +00:00
}
2019-08-09 19:18:13 +00:00
// popAllScreens + popAllGames + stop_game + switch_game_mode + start_game
EX void restart_game(char switchWhat IS(rg::nothing)) {
popScreenAll();
stop_game();
switch_game_mode(switchWhat);
start_game();
}
2019-08-09 19:18:13 +00:00
// stop_game + switch_game_mode
EX void stop_game_and_switch_mode(char switchWhat IS(rg::nothing)) {
stop_game();
switch_game_mode(switchWhat);
}
2019-08-09 19:18:13 +00:00
EX purehookset clearmemory;
2017-07-10 18:47:38 +00:00
2019-08-09 19:18:13 +00:00
EX void clearMemory() {
2017-07-10 18:47:38 +00:00
callhooks(clearmemory);
}
auto cgm = addHook(clearmemory, 40, [] () {
pathq.clear();
dcal.clear();
clearshadow();
2018-02-27 22:43:21 +00:00
for(int i=0; i<MAXPLAYER; i++) lastmountpos[i] = NULL;
2017-07-10 18:47:38 +00:00
seenSevenMines = false;
recallCell = NULL;
butterflies.clear();
buggycells.clear();
2018-01-02 10:15:42 +00:00
crush_next.clear();
crush_now.clear();
rosemap.clear();
adj_memo.clear();
2018-01-26 00:45:49 +00:00
}) +
2019-05-28 23:09:38 +00:00
addHook(hooks_gamedata, 0, [] (gamedata* gd) {
gd->store(pathq);
gd->store(dcal);
gd->store(recallCell);
gd->store(butterflies);
gd->store(buggycells);
gd->store(crush_now);
gd->store(crush_next);
gd->store(rosemap);
2019-05-30 14:12:38 +00:00
gd->store(airmap);
2019-05-28 23:09:38 +00:00
gd->store(adj_memo);
gd->store(pd_from);
gd->store(pd_range);
gd->store(pathqm);
gd->store(reachedfrom);
2019-05-30 14:12:38 +00:00
gd->store(gravity_state);
gd->store(last_gravity_state);
gd->store(shpos);
2019-05-30 14:17:17 +00:00
gd->store(cshpos);
2019-05-28 23:09:38 +00:00
}) +
2018-01-26 00:45:49 +00:00
addHook(hooks_removecells, 0, [] () {
eliminate_if(crush_next, is_cell_removed);
eliminate_if(crush_now, is_cell_removed);
eliminate_if(buggycells, is_cell_removed);
eliminate_if(butterflies, [] (pair<cell*,int>& p) { return is_cell_removed(p.first); });
for(int i=0; i<SHSIZE; i++) for(int p=0; p<MAXPLAYER; p++)
set_if_removed(shpos[p][i], NULL);
});;
}