1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-11-04 21:26:17 +00:00
hyperrogue/yendor.cpp

1097 lines
34 KiB
C++
Raw Normal View History

2016-08-26 09:58:03 +00:00
// Hyperbolic Rogue
// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
2016-08-26 09:58:03 +00:00
// Yendor Quest, together with the Yendor Challenge
// also, the Pure Tactics Mode and the Peace Mode
2016-08-26 09:58:03 +00:00
2017-07-04 13:38:33 +00:00
namespace peace { extern bool on; }
2016-08-26 09:58:03 +00:00
int hiitemsMax(eItem it) {
int mx = 0;
2017-08-17 23:40:07 +00:00
for(auto& a: hiitems) if(a.second[it] > mx) mx = a.second[it];
2016-08-26 09:58:03 +00:00
return mx;
}
int modecode();
typedef vector<pair<int, string> > subscoreboard;
void displayScore(subscoreboard& s, int x) {
int vf = min((vid.yres-64) / 70, vid.xres/80);
if(syncstate == 1) {
displayfr(x, 56, 1, vf, "(syncing)", 0xC0C0C0, 0);
}
else {
sort(s.begin(), s.end());
for(int i=0; i<size(s); i++) {
int i0 = 56 + i * vf;
displayfr(x, i0, 1, vf, its(-s[i].first), 0xC0C0C0, 16);
displayfr(x+8, i0, 1, vf, s[i].second, 0xC0C0C0, 0);
}
}
}
namespace yendor {
bool on = false;
bool generating = false;
bool path = false;
bool everwon = false;
bool won = false;
bool easy = false;
int challenge; // id of the challenge
int lastchallenge;
2018-02-11 22:30:36 +00:00
#define YENDORLEVELS 32
2017-08-17 23:40:07 +00:00
map<modecode_t, array<int, YENDORLEVELS>> bestscore;
2016-08-26 09:58:03 +00:00
eLand nexttostart;
#define LAND_YENDOR_CHAOS 41
yendorlevel levels[YENDORLEVELS] = {
{laNone, 0},
{laHell, YF_DEAD}, // FORCE BARRIERS?
{laGraveyard, YF_DEAD5},
{laDesert, YF_NEAR_IVY}, // IVY OR TENTACLE?
{laMinefield, YF_END}, // NOT WON, SEEMS OKAY
{laEmerald, 0}, // WON FINE
{laOvergrown, 0}, // WON, TOO EASY?
{laMotion, YF_START_AL | YF_END}, // NOT WON, SEEMS OKAY
{laAlchemist, 0}, // ALMOST WON
{laIvoryTower,YF_START_CR | YF_NEAR_ELEM | YF_REPEAT}, // won cool
2017-07-22 23:33:27 +00:00
{laMirrorOld, YF_NEAR_OVER}, // OK
2016-08-26 09:58:03 +00:00
{laWhirlpool, 0}, // cool
{laIce, YF_NEAR_ELEM}, // OK
{laHive, YF_NEAR_RED}, // OK
{laCaribbean, 0}, // seems OK
{laOcean, YF_WALLS}, // well... stupid, add Caribbean/Fjord
{laPalace, 0}, // more trapdoors!
{laZebra, 0}, // TOO HARD?
{laWineyard, 0}, // hard-ish
{laStorms, 0}, // ?
{laLivefjord, 0},
{laJungle, 0},
{laPower, YF_START_CR},
{laWildWest, 0},
{laWhirlwind, YF_NEAR_TENT},
{laHell, YF_CHAOS | YF_DEAD},
2017-03-23 10:53:57 +00:00
{laDragon, YF_DEAD},
{laReptile, 0},
{laTortoise, YF_RECALL},
{laCocytus, YF_NEAR_FJORD},
2018-02-11 22:30:36 +00:00
{laRuins, YF_DEAD},
{laCaves, YF_DEAD5}
2016-08-26 09:58:03 +00:00
};
2017-03-23 10:53:57 +00:00
int tscorelast;
2016-08-26 09:58:03 +00:00
void uploadScore() {
int tscore = 0;
for(int i=1; i<YENDORLEVELS; i++)
if(bestscore[0][i]) tscore += 999 + bestscore[0][i];
// printf("Yendor score = %d\n", tscore);
2017-03-23 10:53:57 +00:00
if(tscore > tscorelast) {
tscorelast = tscore;
if(tscore >= 1000) achievement_gain("YENDC1", rg::global);
if(tscore >= 5000) achievement_gain("YENDC2", rg::global);
if(tscore >= 15000) achievement_gain("YENDC3", rg::global);
2017-03-23 10:53:57 +00:00
}
2016-08-26 09:58:03 +00:00
achievement_score(LB_YENDOR_CHALLENGE, tscore);
}
yendorlevel& clev() { return levels[challenge]; }
eLand changeland(int i, eLand l) {
if((clev().flags & YF_START_ANY) && i < 20 && l != clev().l) return clev().l;
if((clev().flags & YF_END) && i > 80 && l == clev().l) return laIce;
return laNone;
}
string name;
eLand first, second, last;
struct yendorinfo {
cell *path[YDIST];
bool found;
bool foundOrb;
int howfar;
2017-08-06 12:50:16 +00:00
cell* key() { return path[YDIST-1]; }
cell* orb() { return path[0]; }
2016-08-26 09:58:03 +00:00
};
vector<yendorinfo> yi;
2017-03-23 10:53:57 +00:00
#define NOYENDOR 999999
int yii = NOYENDOR;
2016-08-26 09:58:03 +00:00
int hardness() {
2017-07-04 13:38:33 +00:00
if(peace::on) return 15; // just to generate monsters
2017-07-12 16:03:53 +00:00
if(!yendor::generating && !yendor::path && !yendor::on) return 0;
2016-08-26 09:58:03 +00:00
int thf = 0;
for(int i=0; i<size(yi); i++) {
yendorinfo& ye ( yi[i] );
if(!ye.foundOrb && ye.howfar > 25)
thf += (ye.howfar - 25);
}
thf -= 2 * (YDIST - 25);
if(thf<0) thf = 0;
return items[itOrbYendor] * 5 + (thf * 5) / (YDIST-25);
}
enum eState { ysUntouched, ysLocked, ysUnlocked };
eState state(cell *yendor) {
for(int i=0; i<size(yi); i++) if(yi[i].path[0] == yendor)
return yi[i].found ? ysUnlocked : ysLocked;
return ysUntouched;
}
2017-03-23 10:53:57 +00:00
bool check(cell *yendor) {
2016-08-26 09:58:03 +00:00
int byi = size(yi);
for(int i=0; i<size(yi); i++) if(yi[i].path[0] == yendor) byi = i;
2017-03-23 10:53:57 +00:00
if(byi < size(yi) && yi[byi].found) return false;
2016-08-26 09:58:03 +00:00
if(byi == size(yi)) {
yendorinfo nyi;
nyi.path[0] = yendor;
nyi.howfar = 0;
if(euclid) {
int di = hrand(6);
int dj = (di+1) % 6;
int qty = hrand(YDIST-1);
int tot = 0;
for(int i=0; i<YDIST-1; i++) {
tot += qty;
if(tot >= YDIST-1) {
tot -= YDIST-1;
nyi.path[i+1] = createMov(nyi.path[i], di);
}
else
nyi.path[i+1] = createMov(nyi.path[i], dj);
}
}
else {
bool endorian_change = true;
bool in_endorian = false;
cellwalker lig(yendor, hrand(yendor->type));
for(int i=0; i<YDIST-1; i++) {
if(lig.c->land == laEndorian)
in_endorian = true;
else if(!isTechnicalLand(lig.c->land))
in_endorian = false;
nyi.path[i] = lig.c;
2018-03-24 11:59:01 +00:00
lig += wstep;
2017-07-16 21:00:55 +00:00
if(inmirror(lig)) lig = mirror::reflect(lig);
2018-03-24 11:59:01 +00:00
lig += 3;
2016-08-26 09:58:03 +00:00
if(lig.c->type == 7) {
if(in_endorian && endorian_change && i >= YDIST - 20) {
// make the last leg a bit more difficult
2018-03-24 11:59:01 +00:00
lig += (hrand(2)*3-1);
2016-08-26 09:58:03 +00:00
endorian_change = false;
}
else
2018-03-24 11:59:01 +00:00
lig += hrand(2);
2016-08-26 09:58:03 +00:00
}
}
nyi.path[YDIST-1] = lig.c;
}
generating = true;
for(int i=1; i<YDIST-1; i++) {
cell *c = nyi.path[i];
cell *prev = nyi.path[i-1];
setdist(c, 10, prev);
setdist(c, 9, prev);
setdist(c, 8, prev);
setdist(c, 7, prev);
if(challenge && i+BARLEV-7 < YDIST-5 && !euclid) {
cell *c2 = nyi.path[i+BARLEV-7];
if(c2->land == laIvoryTower) continue;
eLand ycl = changeland(i, c2->land);
if(ishept(c2) && ycl) {
int bd = 2 + hrand(2) * 3;
// printf("barrier at %d\n", i);
buildBarrier(c2, bd, ycl);
if(c2->bardir != NODIR && c2->bardir != NOBARRIERS)
extendBarrier(c2);
}
}
}
nyi.found = false;
nyi.foundOrb = false;
cell *key = nyi.path[YDIST-1];
generating = false;
for(int b=10; b>=5; b--) setdist(key, b, nyi.path[YDIST-2]);
2017-03-23 10:53:57 +00:00
2016-08-26 09:58:03 +00:00
for(int i=-1; i<key->type; i++) {
cell *c2 = i >= 0 ? key->mov[i] : key;
checkTide(c2);
c2->monst = moNone; c2->item = itNone;
if(!passable(c2, NULL, P_MIRROR | P_MONSTER)) {
if(c2->wall == waCavewall) c2->wall = waCavefloor;
else if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
else if(c2->wall == waLake) c2->wall = waFrozenLake;
else if(c2->land == laCaribbean) c2->wall = waCIsland;
else if(c2->land == laOcean) c2->wall = waCIsland;
else if(c2->land == laRedRock) c2->wall = waRed3;
else if(c2->land == laWhirlpool)
c2->wall = waBoat, c2->monst = moPirate, c2->item = itOrbWater;
else c2->wall = waNone;
}
2017-03-23 10:53:57 +00:00
if(c2->wall == waReptile) c2->wall = waNone;
2016-08-26 09:58:03 +00:00
if(c2->wall == waMineMine || c2->wall == waMineUnknown)
c2->wall = waMineOpen;
if(c2->wall == waTrapdoor && i == -1)
c2->wall = waGargoyleFloor;
if(c2->land == laLivefjord) {
c2->wall = waSea;
for(int i=0; i<c2->type; i++)
c2->mov[i]->wall = waSea;
}
if(isGravityLand(c2->land) && key->land == c2->land &&
c2->landparam < key->landparam && c2->wall != waTrunk)
c2->wall = waPlatform;
2017-03-23 10:53:57 +00:00
if(c2->land == laReptile && i >= 0)
c2->wall = waChasm;
2017-07-16 21:00:55 +00:00
if(c2->land == laMirrorWall && i == -1)
c2->wall = waNone;
2016-08-26 09:58:03 +00:00
}
key->item = itKey;
yi.push_back(nyi);
}
addMessage(XLAT("You need to find the right Key to unlock this Orb of Yendor!"));
2017-03-23 10:53:57 +00:00
if(yii != byi) {
yii = byi;
achievement_gain("YENDOR1");
playSound(yendor, "pickup-yendor");
return true;
}
2016-08-26 09:58:03 +00:00
return false;
}
void onpath() {
path = false;
if(yii < size(yi)) {
for(int i=0; i<YDIST; i++) if(yi[yii].path[i]->cpdist <= 7) {
if(i > yi[yii].howfar) yi[yii].howfar = i;
path = true;
}
}
}
void init(int phase) {
if(!on) return;
if(phase == 1) {
won = false;
if(!easy) items[itOrbYendor] = bestscore[modecode()][challenge];
chaosmode = (clev().flags & YF_CHAOS);
2018-04-14 07:34:33 +00:00
specialland = clev().l;
2016-08-26 09:58:03 +00:00
if(clev().flags & YF_START_AL) {
2018-04-14 07:34:33 +00:00
specialland = laAlchemist;
2016-08-26 09:58:03 +00:00
items[itElixir] = 50;
items[itFeather] = 50;
}
2018-04-14 07:34:33 +00:00
if(specialland == laPower)
2016-08-26 09:58:03 +00:00
items[itOrbSpeed] = 60, items[itOrbWinter] = 60;
if(clev().flags & YF_START_CR) {
2018-04-14 07:34:33 +00:00
specialland = laCrossroads;
2016-08-26 09:58:03 +00:00
}
2018-04-14 07:34:33 +00:00
if(specialland == laGraveyard) items[itBone] = 10;
if(specialland == laEmerald) items[itEmerald] = 10;
if(specialland == laCocytus) items[itFjord] = 10;
2016-08-26 09:58:03 +00:00
if(!euclid) {
if(clev().flags & YF_DEAD) items[itGreenStone] = 100;
if(clev().flags & YF_DEAD5) items[itGreenStone] = 5;
}
2017-03-23 10:53:57 +00:00
if(clev().flags & YF_RECALL) {
int yq = items[itOrbYendor];
items[itOrbRecall] = 60 - yq;
items[itOrbTime] = 60 - yq;
items[itOrbEnergy] = 60 - yq;
items[itOrbTeleport] = 60 - yq;
items[itOrbSpace] = 60 - yq;
items[itOrbDash] = 60 - yq;
items[itOrbFrog] = 60 - yq;
}
2016-08-26 09:58:03 +00:00
nexttostart = laNone;
}
if(phase == 2) {
cell *c2 = cwt.c->mov[0];
c2->land = firstland;
if(firstland == laRlyeh) c2->wall = waNone;
2017-03-23 10:53:57 +00:00
yendor::check(c2);
2016-08-26 09:58:03 +00:00
if(clev().flags & YF_NEAR_IVY)
nexttostart = laJungle;
2017-08-06 12:50:16 +00:00
if(clev().flags & YF_NEAR_FJORD)
nexttostart = laLivefjord;
2016-08-26 09:58:03 +00:00
if(clev().flags & YF_NEAR_TENT)
nexttostart = laRlyeh;
if(clev().flags & YF_NEAR_ELEM) {
if(firstland == laIce) {
nexttostart = laEWater;
items[itWaterShard] = 10;
}
else nexttostart = laEAir;
}
if(clev().flags & YF_NEAR_OVER)
nexttostart = laOvergrown;
if(clev().flags & YF_NEAR_RED) {
nexttostart = laRedRock;
items[itRedGem] = 25;
}
if(clev().flags & YF_WALLS) {
items[itPirate] += 25;
items[itFjord] += 25;
}
if(clev().l == laWhirlpool) {
items[itWhirlpool] += 10;
items[itOrbWater] += 150;
}
}
if(phase == 3) {
cell *c2 = cwt.c->mov[0];
makeEmpty(c2);
c2->item = itOrbYendor;
nexttostart = laNone;
2017-03-23 10:53:57 +00:00
if(clev().flags & YF_RECALL) recallCell = cwt.c;
2016-08-26 09:58:03 +00:00
}
}
bool levelUnlocked(int i) {
yendorlevel& ylev(levels[i]);
eItem t = treasureType(ylev.l);
if(ylev.l != laWildWest && hiitemsMax(t) < 10) return false;
if((ylev.flags & YF_NEAR_ELEM) && hiitemsMax(itElemental) < 10) return false;
if((ylev.flags & YF_NEAR_RED) && hiitemsMax(itRedGem) < 10) return false;
if((ylev.flags & YF_NEAR_OVER) && hiitemsMax(itMutant) < 10) return false;
if((ylev.flags & YF_NEAR_TENT) && hiitemsMax(itStatue) < 10) return false;
2017-08-06 12:50:16 +00:00
if((ylev.flags & YF_NEAR_FJORD) && hiitemsMax(itFjord) < 10) return false;
2016-08-26 09:58:03 +00:00
if((ylev.flags & YF_CHAOS) && !chaosUnlocked) return false;
if((ylev.flags & (YF_DEAD|YF_DEAD5)) && hiitemsMax(itBone) < 10) return false;
2017-03-23 10:53:57 +00:00
if((ylev.flags & YF_RECALL) && hiitemsMax(itSlime) < 10) return false;
2016-08-26 09:58:03 +00:00
return true;
}
struct scoredata {
string username;
int scores[landtypes];
};
vector<scoredata> scoreboard;
2017-07-10 18:47:38 +00:00
const char *chelp =
"There are many possible solutions to the Yendor Quest. In the Yendor "
"Challenge, you will try many of them!\n\n"
"Each challenge takes part in a specific land, and you have to use what "
"you have available.\n\n"
"You need to obtain an Orb of Yendor in the normal game to activate "
"this challenge, and (ever) collect 10 treasures in one or two lands "
"to activate a specific level.\n\n"
"After you complete each challenge, you can try it again, on a harder "
"difficulty level.\n\n"
"All the solutions showcased in the Yendor Challenge work in the normal "
"play too. However, passages to other lands, and (sometimes) some land features "
"are disabled in the Yendor "
"Challenge, so that you have to use the expected method. Also, "
"the generation rules are changed slightly for the Palace "
"and Minefield while you are looking for the Orb of Yendor, "
"to make the challenge more balanced "
"(but these changes are also active during the normal Yendor Quest).\n\n"
"You get 1000 points for each challenge won, and 1 extra point for "
"each extra difficulty level.";
int char_to_yendor(char c) {
if(c >= 'a' && c <= 'z')
return c - 'a' + 1;
if(c >= 'A' && c <= 'Z')
return c - 'A' + 1 + 26;
return 0;
}
2017-07-10 18:47:38 +00:00
2016-08-26 09:58:03 +00:00
void showMenu() {
int s = vid.fsize;
vid.fsize = vid.fsize * 4/5;
2017-03-23 10:53:57 +00:00
dialog::init(XLAT("Yendor Challenge"), iinf[itOrbYendor].color, 150, 100);
2016-08-26 09:58:03 +00:00
for(int i=1; i<YENDORLEVELS; i++) {
string s;
yendorlevel& ylev(levels[i]);
2017-03-23 10:53:57 +00:00
if(autocheat || levelUnlocked(i)) {
2016-08-26 09:58:03 +00:00
s = XLATT1(ylev.l);
if(!euclid) {
if(ylev.flags & YF_CHAOS) { s = "Chaos mode"; }
if(ylev.flags & YF_NEAR_IVY) { s += "+"; s += XLATT1(laJungle); }
2017-08-06 12:50:16 +00:00
if(ylev.flags & YF_NEAR_FJORD) { s += "+"; s += XLATT1(laLivefjord); }
2016-08-26 09:58:03 +00:00
if(ylev.flags & YF_NEAR_TENT) { s += "+"; s += XLATT1(laRlyeh); }
if(ylev.flags & YF_NEAR_ELEM) { s += "+"; s += XLATT1(laElementalWall); }
if(ylev.flags & YF_NEAR_OVER) { s += "+"; s += XLATT1(laOvergrown); }
if(ylev.flags & YF_NEAR_RED) { s += "+"; s += XLATT1(laRedRock); }
if(ylev.flags & YF_START_AL) { s += "+"; s += XLATT1(laAlchemist); }
if(ylev.flags & YF_DEAD) { s += "+"; s += XLATT1(itGreenStone); }
2017-03-23 10:53:57 +00:00
if(ylev.flags & YF_RECALL) { s += "+"; s += XLATT1(itOrbRecall); }
2016-08-26 09:58:03 +00:00
}
}
else {
s = "(locked)";
}
string v;
if(bestscore[modecode()][i] == 1)
v = XLAT(" (won!)");
else if(bestscore[modecode()][i])
v = XLAT(" (won at level %1!)", its(bestscore[modecode()][i]));
dialog::addSelItem(s, v, i > 26 ? 'A' + i - 27 : 'a' + i-1);
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
dialog::addBreak(60);
dialog::addItem(XLAT("Return to the normal game"), '0');
dialog::addSelItem(XLAT(
2016-08-26 09:58:03 +00:00
easy ? "Challenges do not get harder" : "Each challenge gets harder after each victory"),
" " + XLAT(easy ? "easy" : "challenge"), '1');
2017-03-23 10:53:57 +00:00
dialog::display();
int yc = char_to_yendor(getcstat);
2016-08-26 09:58:03 +00:00
if(yc > 0 && yc < YENDORLEVELS) {
subscoreboard scorehere;
for(int i=0; i<size(scoreboard); i++) {
int sc = scoreboard[i].scores[yc];
if(sc > 0)
scorehere.push_back(
make_pair(-sc, scoreboard[i].username));
}
displayScore(scorehere, vid.xres / 4);
}
yendor::uploadScore();
vid.fsize = s;
2017-07-10 18:47:38 +00:00
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
int new_challenge = char_to_yendor(uni);
if(new_challenge && new_challenge < YENDORLEVELS) {
if(levelUnlocked(new_challenge) || autocheat) {
challenge = new_challenge;
restartGame(yendor::on ? 0 : rg::yendor);
2017-07-10 18:47:38 +00:00
}
else
addMessage("Collect 10 treasures in various lands to unlock the challenges there");
2016-08-26 09:58:03 +00:00
}
2017-07-10 18:47:38 +00:00
else if(uni == '0') {
if(yendor::on) restartGame(rg::yendor);
2017-07-10 18:47:38 +00:00
}
else if(uni == '1') easy = !easy;
else if(uni == '2' || sym == SDLK_F1) gotoHelp(chelp);
else if(doexiton(sym, uni)) popScreen();
};
2016-08-26 09:58:03 +00:00
}
2017-07-10 18:47:38 +00:00
2017-07-04 13:38:33 +00:00
void collected(cell* c2) {
int pg = gold();
playSound(c2, "tada");
items[itOrbShield] += 31;
for(int i=0; i<size(yendor::yi); i++)
if(yendor::yi[i].path[0] == c2)
yendor::yi[i].foundOrb = true;
// Shielding always, so that we know that it protects!
for(int i=0; i<4; i++) switch(hrand(13)) {
case 0: items[itOrbSpeed] += 31; break;
case 1: items[itOrbLightning] += 78; break;
case 2: items[itOrbFlash] += 78; break;
case 3: items[itOrbTime] += 78; break;
case 4: items[itOrbWinter] += 151; break;
case 5: items[itOrbDigging] += 151; break;
case 6: items[itOrbTeleport] += 151; break;
case 7: items[itOrbThorns] += 151; break;
case 8: items[itOrbInvis] += 151; break;
case 9: items[itOrbPsi] += 151; break;
case 10: items[itOrbAether] += 151; break;
case 11: items[itOrbFire] += 151; break;
case 12: items[itOrbSpace] += 78; break;
}
items[itOrbYendor]++;
items[itKey]--;
yendor::everwon = true;
if(yendor::on) {
yendor::won = true;
if(!cheater) {
dynamicval<bool> c(chaosmode, false);
yendor::bestscore[modecode()][yendor::challenge] =
max(yendor::bestscore[modecode()][yendor::challenge], items[itOrbYendor]);
yendor::uploadScore();
}
}
addMessage(XLAT("CONGRATULATIONS!"));
achievement_collection(itOrbYendor, pg, gold());
achievement_victory(false);
}
2017-07-10 18:47:38 +00:00
auto hooks = addHook(clearmemory, 0, [] () {
yendor::yii = NOYENDOR; yendor::yi.clear();
2018-01-26 00:45:49 +00:00
}) + addHook(hooks_removecells, 0, [] () {
eliminate_if(yendor::yi, [] (yendorinfo& i) {
for(int j=0; j<YDIST; j++) if(is_cell_removed(i.path[j])) {
DEBSM(printf("removing a Yendor\n");)
if(&yi[yii] == &i) yii = NOYENDOR;
return true;
}
return false;
});
2017-07-10 18:47:38 +00:00
});
2016-08-26 09:58:03 +00:00
};
#define MAXTAC 20
namespace tactic {
bool trailer = false;
bool on = false;
int id;
2017-08-17 23:40:07 +00:00
map<modecode_t, array<int, landtypes>> recordsum;
map<modecode_t, array<array<int, MAXTAC>, landtypes> > lsc;
2016-08-26 09:58:03 +00:00
eLand lasttactic;
struct scoredata {
string username;
int scores[landtypes];
};
2017-08-17 23:40:07 +00:00
map<modecode_t, vector<scoredata>> scoreboard;
2016-08-26 09:58:03 +00:00
int chances(eLand l) {
if(modecode() != 0 && l != laCamelot) return 3;
2017-11-03 18:31:42 +00:00
for(auto& ti: land_tac)
if(ti.l == l)
return ti.tries;
2016-08-26 09:58:03 +00:00
return 0;
}
int tacmultiplier(eLand l) {
if(modecode() != 0 && l != laCamelot) return 1;
if(modecode() != 0 && l == laCamelot) return 3;
2017-11-03 18:31:42 +00:00
for(auto& ti: land_tac)
if(ti.l == l)
return ti.multiplier;
2016-08-26 09:58:03 +00:00
return 0;
}
2017-11-03 18:31:42 +00:00
bool tacticUnlocked(eLand l) {
2017-03-23 10:53:57 +00:00
if(autocheat) return true;
2017-10-30 08:05:16 +00:00
if(l == laWildWest || l == laDual) return true;
2016-08-26 09:58:03 +00:00
return hiitemsMax(treasureType(l)) * landMultiplier(l) >= 20;
}
void record(eLand land, int score, int xc = modecode()) {
if(land >=0 && land < landtypes) {
for(int i=MAXTAC-1; i; i--) lsc[xc][land][i] = lsc[xc][land][i-1];
tactic::lsc[xc][land][0] = score;
}
int t = chances(land);
int csum = 0;
for(int i=0; i<t; i++) if(lsc[xc][land][i] > 0) csum += lsc[xc][land][i];
if(csum > recordsum[xc][land]) recordsum[xc][land] = csum;
}
void record() {
record(lasttactic, items[treasureType(lasttactic)]);
}
void unrecord(eLand land, int xc = modecode()) {
if(land >=0 && land < landtypes) {
for(int i=0; i<MAXTAC-1; i++) lsc[xc][land][i] = lsc[xc][land][i+1];
lsc[xc][land][MAXTAC-1] = -1;
}
}
void unrecord() {
unrecord(lasttactic);
}
2017-03-23 10:53:57 +00:00
int tscorelast;
2016-08-26 09:58:03 +00:00
void uploadScoreCode(int code, int lb) {
int tscore = 0;
for(int i=0; i<landtypes; i++)
tscore += recordsum[code][i] * tacmultiplier(eLand(i));
// printf("PTM score = %d\n", tscore);
2017-03-23 10:53:57 +00:00
if(code == 0 && tscore > tscorelast) {
tscorelast = tscore;
if(tscore >= 1000) achievement_gain("PTM1", 'x');
if(tscore >= 5000) achievement_gain("PTM2", 'x');
if(tscore >= 15000) achievement_gain("PTM3", 'x');
}
2016-08-26 09:58:03 +00:00
achievement_score(lb, tscore);
}
void uploadScore() {
uploadScoreCode(0, LB_PURE_TACTICS);
uploadScoreCode(2, LB_PURE_TACTICS_SHMUP);
uploadScoreCode(4, LB_PURE_TACTICS_COOP);
}
2017-03-23 10:53:57 +00:00
2016-08-26 09:58:03 +00:00
void showMenu() {
2017-07-22 23:33:27 +00:00
cmode = sm::ZOOMABLE;
2016-08-26 09:58:03 +00:00
mouseovers = XLAT("pure tactics mode") + " - " + mouseovers;
2017-11-03 18:31:42 +00:00
{
dynamicval<bool> t(tactic::on, true);
2018-04-13 11:08:41 +00:00
generateLandList([] (eLand l) { return land_validity(l).flags & lv::appears_in_ptm; });
2017-11-03 18:31:42 +00:00
}
int nl = size(landlist);
2017-03-23 10:53:57 +00:00
int nlm;
int ofs = dialog::handlePage(nl, nlm, (nl+1)/2);
2017-03-23 10:53:57 +00:00
int vf = min((vid.yres-64-vid.fsize) / nlm, vid.xres/40);
2016-08-26 09:58:03 +00:00
int xr = vid.xres / 64;
if(on) record(specialland, items[treasureType(specialland)]);
2016-08-26 09:58:03 +00:00
int xc = modecode();
2017-03-23 10:53:57 +00:00
getcstat = SDLK_ESCAPE;
2016-08-26 09:58:03 +00:00
for(int i=0; i<nl; i++) {
2017-03-23 10:53:57 +00:00
int i1 = i + ofs;
2017-11-03 18:31:42 +00:00
eLand l = landlist[i1];
2017-03-23 10:53:57 +00:00
2016-08-26 09:58:03 +00:00
int i0 = 56 + i * vf;
int col;
int ch = chances(l);
if(!ch) continue;
2017-11-03 18:31:42 +00:00
bool unlocked = tacticUnlocked(l);
2016-08-26 09:58:03 +00:00
if(unlocked) col = linf[l].color; else col = 0x202020;
2017-03-23 10:53:57 +00:00
if(displayfrZ(xr*1, i0, 1, vf-4, XLAT1(linf[l].name), col, 0) && unlocked) {
getcstat = 1000 + i1;
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
if(unlocked || autocheat) {
2016-08-26 09:58:03 +00:00
for(int ii=0; ii<ch; ii++)
2017-08-17 23:40:07 +00:00
if(displayfrZ(xr*(24+2*ii), i0, 1, (vf-4)*4/5, lsc[xc][l][ii] > 0 ? its(lsc[xc][l][ii]) : "-", col, 16))
2017-03-23 10:53:57 +00:00
getcstat = 1000 + i1;
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
if(displayfrZ(xr*(24+2*10), i0, 1, (vf-4)*4/5,
2016-08-26 09:58:03 +00:00
its(recordsum[xc][l]) + " x" + its(tacmultiplier(l)), col, 0))
2017-03-23 10:53:57 +00:00
getcstat = 1000 + i1;
2016-08-26 09:58:03 +00:00
}
else {
int m = landMultiplier(l);
2017-03-23 10:53:57 +00:00
displayfrZ(xr*26, i0, 1, (vf-4)*4/5,
2016-08-26 09:58:03 +00:00
XLAT("Collect %1x %2 to unlock", its((20+m-1)/m), treasureType(l)),
col, 0);
}
}
2017-03-23 10:53:57 +00:00
dialog::displayPageButtons(3, true);
2016-08-26 09:58:03 +00:00
uploadScore();
if(on) unrecord(specialland);
2016-08-26 09:58:03 +00:00
2017-11-03 18:31:42 +00:00
if(getcstat >= 1000 && getcstat < 1000 + size(landlist)) {
int ld = landlist[getcstat-1000];
2016-08-26 09:58:03 +00:00
subscoreboard scorehere;
for(int i=0; i<size(scoreboard[xc]); i++) {
int sc = scoreboard[xc][i].scores[ld];
if(sc > 0)
scorehere.push_back(
make_pair(-sc, scoreboard[xc][i].username));
}
displayScore(scorehere, xr * 50);
}
2017-07-10 18:47:38 +00:00
keyhandler = [] (int sym, int uni) {
2017-11-03 18:31:42 +00:00
if(uni >= 1000 && uni < 1000 + size(landlist)) {
specialland = landlist[uni - 1000];
restartGame(tactic::on ? 0 : rg::tactic);
2017-07-10 18:47:38 +00:00
}
else if(uni == '0') {
firstland = laIce;
if(tactic::on) restartGame(rg::tactic);
2017-07-16 21:00:55 +00:00
else popScreen();
2017-07-10 18:47:38 +00:00
}
2016-08-26 09:58:03 +00:00
2017-07-10 18:47:38 +00:00
else if(sym == SDLK_F1) gotoHelp(
2016-08-26 09:58:03 +00:00
"In the pure tactics mode, you concentrate on a specific land. "
"Your goal to obtain as high score as possible, without using "
"features of the other lands. You can then compare your score "
"with your friends!\n\n"
"You need to be somewhat proficient in the normal game to "
"unlock the given land in this challenge "
"(collect 20 treasure in the given land, or 2 in case of Camelot).\n\n"
"Since getting high scores in some lands is somewhat luck dependent, "
"you play each land N times, and your score is based on N consecutive "
"plays. The value of N depends on how 'fast' the land is to play, and "
"how random it is.\n\n"
"In the Caribbean, you can access Orbs of Thorns, Aether, and "
"Space if you have ever collected 25 treasure in their native lands.\n\n"
"The rate of treasure spawn is static in this mode. It is not "
"increased by killing monsters.\n\n"
2017-07-10 18:47:38 +00:00
"Good luck, and have fun!"
);
else if(dialog::handlePageButtons(uni)) ;
else if(doexiton(sym, uni)) popScreen();
};
2016-08-26 09:58:03 +00:00
}
2017-07-10 18:47:38 +00:00
2016-08-26 09:58:03 +00:00
};
2017-03-23 10:53:57 +00:00
int modecodetable[42][6] = {
{ 0, 38, 39, 40, 41, 42}, // softcore hyperbolic
{ 7, 43, 44, 45, 46, 47}, // hardcore hyperbolic
{ 2, 4, 9, 11, 48, 49}, // shmup hyperbolic
{ 13, 50, 51, 52, 53, 54}, // softcore heptagonal hyperbolic
{ 16, 55, 56, 57, 58, 59}, // hardcore heptagonal hyperbolic
{ 14, 15, 17, 18, 60, 61}, // shmup heptagonal hyperbolic
{ 1, 62, 63, 64, 65, 66}, // softcore euclidean
{ 8, 67, 68, 69, 70, 71}, // hardcore euclidean
{ 3, 5, 10, 12, 72, 73}, // shmup euclidean
{110,111,112,113,114,115}, // softcore spherical
{116,117,118,119,120,121}, // hardcore spherical
{122,123,124,125,126,127}, // shmup spherical
{128,129,130,131,132,133}, // softcore heptagonal spherical
{134,135,136,137,138,139}, // hardcore heptagonal spherical
{140,141,142,143,144,145}, // shmup heptagonal spherical
{146,147,148,149,150,151}, // softcore elliptic
{152,153,154,155,156,157}, // hardcore elliptic
{158,159,160,161,162,163}, // shmup elliptic
{164,165,166,167,168,169}, // softcore heptagonal elliptic
{170,171,172,173,174,175}, // hardcore heptagonal elliptic
{176,177,178,179,180,181}, // shmup heptagonal elliptic
{ 19, 74, 75, 76, 77, 78}, // softcore hyperbolic chaosmode
{ 26, 79, 80, 81, 82, 83}, // hardcore hyperbolic chaosmode
{ 21, 23, 28, 30, 84, 85}, // shmup hyperbolic chaosmode
{ 32, 86, 87, 88, 89, 90}, // softcore heptagonal hyperbolic chaosmode
{ 35, 91, 92, 93, 94, 95}, // hardcore heptagonal hyperbolic chaosmode
{ 33, 34, 36, 37, 96, 97}, // shmup heptagonal hyperbolic chaosmode
{ 20, 98, 99,100,101,102}, // softcore euclidean chaosmode
{ 27,103,104,105,106,107}, // hardcore euclidean chaosmode
{ 22, 24, 29, 31,108,109}, // shmup euclidean chaosmode
{182,183,184,185,186,187}, // softcore spherical chaosmode
{188,189,190,191,192,193}, // hardcore spherical chaosmode
{194,195,196,197,198,199}, // shmup spherical chaosmode
{200,201,202,203,204,205}, // softcore heptagonal spherical chaosmode
{206,207,208,209,210,211}, // hardcore heptagonal spherical chaosmode
{212,213,214,215,216,217}, // shmup heptagonal spherical chaosmode
{218,219,220,221,222,223}, // softcore elliptic chaosmode
{224,225,226,227,228,229}, // hardcore elliptic chaosmode
{230,231,232,233,234,235}, // shmup elliptic chaosmode
{236,237,238,239,240,241}, // softcore heptagonal elliptic chaosmode
{242,243,244,245,246,247}, // hardcore heptagonal elliptic chaosmode
{248,249,250,251,252,253}, // shmup heptagonal elliptic chaosmode
};
// unused code: 25
2017-07-16 21:00:55 +00:00
int newmodecode = 255;
2017-03-23 10:53:57 +00:00
2016-08-26 09:58:03 +00:00
int modecode() {
2017-07-22 23:33:27 +00:00
#if CAP_SAVE
if(anticheat::tampered || cheater) return 6;
#endif
2017-08-17 23:40:07 +00:00
2017-03-23 10:53:57 +00:00
int xcode = 0;
if(shmup::on) xcode += 2;
else if(pureHardcore()) xcode ++;
if(euclid) xcode += 6;
2018-01-06 21:34:03 +00:00
else if(nonbitrunc) xcode += 3;
2017-03-23 10:53:57 +00:00
if(sphere) {
xcode += 9;
if(elliptic) xcode += 6;
2018-01-06 21:34:03 +00:00
if(nonbitrunc) xcode += 3;
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
if(chaosmode) xcode += 21;
int np = numplayers()-1; if(np<0 || np>5) np=5;
2017-08-17 23:40:07 +00:00
int mct = modecodetable[xcode][np];
if(geometry == gTorus) mct += 512;
if(geometry == gQuotient) mct += 1024;
if(geometry == gQuotient2) mct += 1536;
#if CAP_INV
if(inv::on) mct += 2048;
#endif
if(peace::on) mct += 4096;
#if CAP_TOUR
if(tour::on) mct += 8192;
#endif
if(numplayers() == 7) mct += 16384;
if(gp::on) {
mct += 32768;
mct += gp::param.first << 16;
mct += gp::param.second << 21;
}
mct += ginf[geometry].xcode;
2017-08-17 23:40:07 +00:00
return mct;
2016-08-26 09:58:03 +00:00
}
2017-03-23 10:53:57 +00:00
void buildmodetable() {
bool codeused[600];
for(int q=0; q<600; q++) codeused[q] = 0;
codeused[6] = true; // cheater
printf("int modecodetable[42][6] = {\n");
for(int b=0; b<42; b++) {
extern bool hardcore;
hardcore = (b%3 == 1);
shmup::on = (b%3 == 2);
2018-01-06 21:34:03 +00:00
nonbitrunc = (b/3)%7 == 1 || (b/3)%7 == 4 || (b/3)%7 == 6;
2017-03-23 10:53:57 +00:00
geometry = gNormal;
if((b/3)%7 == 2) geometry = gEuclid;
if((b/3)%7 >= 3) geometry = gSphere;
if((b/3)%7 >= 5) geometry = gElliptic;
chaosmode = b >= 21;
printf(" {");
for(int p=0; p<6; p++) {
multi::players = p+1;
if(p) printf(",");
int mc = modecode();
if(codeused[mc]) mc = newmodecode++;
codeused[mc] = true;
printf("%3d", mc);
}
printf("}, //");
if(hardcore) printf(" hardcore");
else if(shmup::on) printf(" shmup");
else printf(" softcore");
2018-01-06 21:34:03 +00:00
if(nonbitrunc) printf(" heptagonal");
2017-03-23 10:53:57 +00:00
if(euclid) printf(" euclidean");
else if(elliptic) printf(" elliptic");
else if(sphere) printf(" spherical");
else printf(" hyperbolic");
if(chaosmode) printf(" chaosmode");
printf("\n");
}
printf(" }\n");
for(int i=0; i<newmodecode; i++) if(!codeused[i]) printf("// unused code: %d\n", i);
printf("int newmodecode = %d;\n", newmodecode);
}
2017-07-04 13:38:33 +00:00
namespace peace {
bool on = false;
bool hint = false;
bool otherpuzzles;
eLand simonlevels[] = {
laCrossroads, laCrossroads2, laDesert, laCaves, laAlchemist, laRlyeh, laEmerald,
laWineyard, laDeadCaves, laRedRock, laPalace,
laLivefjord, laDragon,
laNone
};
eLand explorelevels[] = {
laBurial, laTortoise, laCamelot, laPalace,
laIce, laJungle, laMirror, laDryForest, laCaribbean, laOcean, laZebra,
laOvergrown, laWhirlwind, laWarpCoast, laReptile,
laElementalWall, laAlchemist,
laNone
};
eLand *levellist;
int qty;
2017-08-06 12:50:16 +00:00
void listLevels() {
levellist = otherpuzzles ? explorelevels : simonlevels;
for(qty = 0; levellist[qty]; qty++);
}
2017-07-04 13:38:33 +00:00
eLand getNext(eLand last) {
2017-07-10 18:47:38 +00:00
if(!peace::on) return laNone;
2017-08-06 12:50:16 +00:00
if(!qty) listLevels();
2017-07-04 13:38:33 +00:00
if(isElemental(last) && hrand(100) < 90)
return laNone;
else if(createOnSea(last))
return getNewSealand(last);
else if(isCrossroads(last)) {
while(isCrossroads(last) || last == laCaribbean || last == laCamelot)
last = levellist[hrand(qty)];
if(last == laElementalWall) last = laEFire;
return last;
}
else return pick(laCrossroads, laCrossroads2);
}
bool isAvailable(eLand l) {
for(int i=0; explorelevels[i]; i++) if(explorelevels[i] == l) return true;
return false;
}
2017-07-22 23:33:27 +00:00
const char *chelp =
"In the peaceful mode, you just explore the world, "
"without any battles; there are also several "
"navigational puzzles available. In the memory game, "
"you have to collect as many Dodecahedra as you can, "
"and return to the starting point -- hyperbolic geometry "
"makes this extremely difficult! Other hyperbolic puzzles "
"include the Burial Grounds (excavate the treasures "
"using your magical sword), Galápagos (try to find an adult "
"tortoise matching the baby), Camelot (find the center of "
"a large hyperbolic circle), and Palace (follow the mouse). "
"Other places listed are for exploration.";
2017-07-04 13:38:33 +00:00
namespace simon {
vector<cell*> path;
int tobuild;
void build() {
if(otherpuzzles || !on) return;
while(size(path) < tobuild) {
cell *cp = path[size(path)-1];
cell *cp2 = path[size(path)-2];
vector<pair<cell*, cell*>> clister;
clister.emplace_back(cp, cp);
int id = 0;
sval++;
while(id < size(clister)) {
cell *c = clister[id].first;
cell *fr = clister[id].second;
setdist(c, 5, NULL);
forCellEx(c2,c)
2017-08-06 12:50:16 +00:00
if(!eq(c2->aitmp, sval) && passable(c2, c, 0) && (c2->land == specialland || c2->land == laTemple) && !c2->item) {
2017-07-04 13:38:33 +00:00
if(!id) fr = c2;
bool next;
2017-08-06 12:50:16 +00:00
if(specialland == laRlyeh)
2017-07-04 13:38:33 +00:00
next = c2->land == laTemple && (cp2->land == laRlyeh || celldistAlt(c2) < celldistAlt(cp2) - 8);
else
next = celldistance(c2, cp2) == 8;
if(next) {
path.push_back(fr);
fr->item = itDodeca;
goto again;
}
clister.emplace_back(c2, fr);
c2->aitmp = sval;
}
id++;
}
printf("Path broken, searched = %d\n", id);
for(auto t: clister) t.first->item = itPirate;
return;
again: ;
}
}
void extend() {
int i = 0;
while(i<size(path) && path[i]->item != itDodeca) i++;
if(tobuild == i+9)
addMessage("You must collect all the dodecahedra on the path!");
tobuild = i + 9;
build();
}
void init() {
tobuild = 0;
if(!on) return;
if(otherpuzzles) { items[itGreenStone] = 500; return; }
cell *c2 = cwt.c->mov[0];
makeEmpty(c2);
c2->item = itOrbYendor;
path.clear();
path.push_back(cwt.c);
path.push_back(c2);
extend();
}
void restore() {
for(int i=1; i<size(path); i++)
if(path[i]->item == itNone && items[itDodeca])
path[i]->item = itDodeca, items[itDodeca]--;
}
}
2017-08-06 12:50:16 +00:00
2017-07-10 18:47:38 +00:00
void showMenu() {
2017-08-06 12:50:16 +00:00
listLevels();
2017-07-22 23:33:27 +00:00
dialog::init(XLAT(otherpuzzles ? "puzzles and exploration" : "memory game"), 0x40A040, 150, 100);
2017-07-10 18:47:38 +00:00
2017-08-06 12:50:16 +00:00
for(int i = 0; i<qty; i++)
dialog::addItem(XLAT1(linf[levellist[i]].name), 'a'+i);
2017-07-10 18:47:38 +00:00
dialog::addBreak(100);
2017-07-22 23:33:27 +00:00
dialog::addItem(XLAT(otherpuzzles ? "memory game" : "puzzles and exploration"), '1');
2017-07-10 18:47:38 +00:00
dialog::addBoolItem(XLAT("display hints"), hint, '2');
2017-07-22 23:33:27 +00:00
dialog::addItem(XLAT("help"), SDLK_F1);
2017-07-10 18:47:38 +00:00
dialog::addItem(XLAT("Return to the normal game"), '0');
dialog::display();
2017-07-04 13:38:33 +00:00
2017-07-10 18:47:38 +00:00
keyhandler = [] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
if(uni == '1') otherpuzzles = !otherpuzzles;
else if(uni >= 'a' && uni < 'a' + qty) {
2017-08-06 12:50:16 +00:00
specialland = levellist[uni - 'a'];
restartGame(peace::on ? 0 : rg::peace);
2017-07-10 18:47:38 +00:00
}
else if(uni == '2') { hint = !hint; popScreen(); }
else if(uni == '0') {
firstland = laIce;
if(peace::on) restartGame(rg::peace);
2017-07-10 18:47:38 +00:00
}
else if(uni == 'h' || sym == SDLK_F1) gotoHelp(chelp);
else if(doexiton(sym, uni)) popScreen();
};
2017-07-04 13:38:33 +00:00
}
2017-07-10 18:47:38 +00:00
auto aNext = addHook(hooks_nextland, 100, getNext);
2017-07-04 13:38:33 +00:00
};