mirror of
https://github.com/zenorogue/hyperrogue.git
synced 2024-11-09 15:39:55 +00:00
395 lines
11 KiB
C++
395 lines
11 KiB
C++
// Hyperbolic Rogue -- local highscore lists
|
|
// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
|
|
|
|
/** \file scores.cpp
|
|
* \brief local highscore lists
|
|
*/
|
|
|
|
#include "hyper.h"
|
|
#if CAP_SAVE
|
|
|
|
namespace hr { namespace scores {
|
|
|
|
vector<score> scores;
|
|
score *currentgame;
|
|
|
|
int scorefrom = 0;
|
|
bool scorerev = false;
|
|
|
|
string csub(const string& str, int q) {
|
|
int i = 0;
|
|
for(int j=0; j<q && i<isize(str); j++) getnext(str.c_str(), i);
|
|
return str.substr(0, i);
|
|
}
|
|
|
|
vector<int> column_width(POSSCORE+1, 4);
|
|
|
|
int colwidth(int scoredisplay) {
|
|
return column_width[scoredisplay];
|
|
/* if(scoredisplay == 0) return 5;
|
|
if(scoredisplay == 1) return 16;
|
|
if(scoredisplay == 5) return 8;
|
|
if(scoredisplay == POSSCORE) return 8;
|
|
if(scoredisplay == 68) return yasc_width;
|
|
return 4; */
|
|
}
|
|
|
|
bool isHardcore(score *S) {
|
|
return S->box[117] && S->box[118] < PUREHARDCORE_LEVEL;
|
|
}
|
|
|
|
int modediff(score *S) {
|
|
int diff = 0;
|
|
eGeometry g = (eGeometry) S->box[116];
|
|
if(S->box[306] != inv::on) diff += 4;
|
|
if(S->box[238]) g = gSphere;
|
|
if(S->box[239]) g = gElliptic;
|
|
if(max(S->box[197], 1) != multi::players) diff += 8;
|
|
|
|
eVariation savevar =
|
|
S->box[341] ? eVariation::goldberg :
|
|
S->box[344] ? eVariation::irregular :
|
|
eVariation(S->box[186]);
|
|
|
|
if(variation != savevar) diff += 16;
|
|
if(GOLDBERG && savevar == eVariation::goldberg && (S->box[342] != gp::param.first || S->box[343] != gp::param.second))
|
|
diff += 16;
|
|
if(S->box[196] != (int) land_structure) diff += 32;
|
|
if(S->box[119] != shmup::on) diff += 64;
|
|
if(pureHardcore() && !isHardcore(S)) diff += 128;
|
|
if(g != gNormal && S->box[120] != specialland)
|
|
diff += 256;
|
|
if(g != geometry) {
|
|
diff += 512;
|
|
}
|
|
return -diff;
|
|
}
|
|
|
|
string modedesc(score *S) {
|
|
eGeometry g = (eGeometry) S->box[116];
|
|
if(S->box[238]) g = gSphere;
|
|
if(S->box[239]) g = gElliptic;
|
|
string s = ginf[g].shortname;
|
|
if(g != gNormal) s += " " + csub(XLATT1((eLand) S->box[120]), 3);
|
|
if(S->box[341]) s += "/GP(" + its(S->box[342])+","+its(S->box[343])+")";
|
|
else if(S->box[186]) s += "/7";
|
|
if(S->box[196]) s += "/C";
|
|
if(S->box[119]) s += "/s";
|
|
if(S->box[197] > 1) s += "/P" + its(S->box[197]);
|
|
if(S->box[306]) s += "/i";
|
|
if(isHardcore(S)) s += "/h";
|
|
return s;
|
|
}
|
|
|
|
string displayfor(int scoredisplay, score* S, bool shorten = false) {
|
|
// printf("S=%p, scoredisplay = %d\n", S, scoredisplay);
|
|
if(S == NULL) {
|
|
if(scoredisplay == POSSCORE) return "mode";
|
|
string str = XLATN(boxname[scoredisplay]);
|
|
if(!shorten) return str;
|
|
if(scoredisplay == 0 || scoredisplay == 65) return XLAT("time");
|
|
if(scoredisplay == 2) return "$$$";
|
|
if(scoredisplay == 3) return XLAT("kills");
|
|
if(scoredisplay == 4) return XLAT("turns");
|
|
if(scoredisplay == 5) return XLAT("cells");
|
|
if(scoredisplay == 67) return XLAT("cheats");
|
|
if(scoredisplay == 66) return XLAT("saves");
|
|
if(scoredisplay == 197) return XLAT("players");
|
|
if(scoredisplay == 68) return XLAT("where");
|
|
return csub(str, 5);
|
|
}
|
|
if(scoredisplay == 0 || scoredisplay == 65) {
|
|
char buf[20];
|
|
int t = S->box[0];
|
|
if(t >= 3600)
|
|
snprintf(buf, 20, "%d:%02d:%02d", t/3600, (t/60)%60, t%60);
|
|
else
|
|
snprintf(buf, 20, "%d:%02d", t/60, t%60);
|
|
return buf;
|
|
}
|
|
if(scoredisplay == POSSCORE) return modedesc(S);
|
|
if(scoredisplay == 68) {
|
|
eLand which = eLand(S->box[68]);
|
|
if(which >= landtypes || which < 0) return "fail";
|
|
return S->yasc_message + XLAT(" in %the1", which);
|
|
}
|
|
if(scoredisplay == 1) {
|
|
time_t tim = S->box[1];
|
|
char buf[128]; strftime(buf, 128, "%c", localtime(&tim));
|
|
return buf;
|
|
}
|
|
return its(S->box[scoredisplay]);
|
|
}
|
|
|
|
int curcol;
|
|
|
|
vector<int> columns;
|
|
|
|
bool monsterpage = false;
|
|
|
|
void showPickScores() {
|
|
|
|
dialog::v.clear();
|
|
|
|
scorerev = false;
|
|
|
|
for(int i=0; i<=POSSCORE; i++) {
|
|
int scoredisplay = i;
|
|
if(!fakebox[scoredisplay]) {
|
|
string s = displayfor(scoredisplay, NULL);
|
|
if(dialog::hasInfix(s))
|
|
dialog::v.push_back(make_pair(s, i));
|
|
}
|
|
}
|
|
sort(dialog::v.begin(), dialog::v.end());
|
|
|
|
cmode = 0;
|
|
gamescreen();
|
|
dialog::init(XLAT("pick scores"));
|
|
if(dialog::infix != "") mouseovers = dialog::infix;
|
|
|
|
dialog::addBreak(50);
|
|
dialog::start_list(900, 900, '1');
|
|
|
|
for(auto& vi: dialog::v) {
|
|
dialog::addItem(vi.first, dialog::list_fake_key++);
|
|
dialog::add_action([&vi] {
|
|
int scoredisplay = vi.second;
|
|
for(int i=0; i<=POSSCORE; i++)
|
|
if(columns[i] == scoredisplay) swap(columns[i], columns[curcol]);
|
|
popScreen();
|
|
});
|
|
}
|
|
dialog::end_list();
|
|
dialog::addBack();
|
|
dialog::display();
|
|
|
|
mouseovers = dialog::infix;
|
|
keyhandler = [] (int sym, int uni) {
|
|
dialog::handleNavigation(sym, uni);
|
|
if(dialog::editInfix(uni)) dialog::list_skip = 0;
|
|
else if(doexiton(sym, uni)) popScreen();
|
|
};
|
|
}
|
|
|
|
int scale = 2;
|
|
|
|
void show() {
|
|
|
|
if(columns.size() == 0) {
|
|
columns.push_back(POSSCORE);
|
|
for(int i=0; i<POSSCORE; i++) {
|
|
if(i == 5) columns.push_back(68);
|
|
else if(i == 68) columns.push_back(5);
|
|
else columns.push_back(i);
|
|
}
|
|
}
|
|
int score_size = vid.fsize / scale;
|
|
int y = score_size * 5/2;
|
|
int bx = score_size;
|
|
getcstat = 0;
|
|
|
|
displaystr(bx*4, score_size, 0, vid.fsize, "#", forecolor, 16);
|
|
displaystr(bx*8, score_size, 0, vid.fsize, XLAT("ver"), forecolor, 16);
|
|
|
|
int at = 9;
|
|
for(int i=0; i<=POSSCORE; i++) {
|
|
int c = columns[i];
|
|
if(bx*at > vid.xres) break;
|
|
string s = displayfor(c, NULL, true);
|
|
auto& cw = column_width[c];
|
|
cw = max(cw, textwidth(score_size, s) / bx + 1);
|
|
if(displaystr(bx*at, score_size, 0, score_size, s, i == curcol ? 0xFFD500 : forecolor, 0))
|
|
getcstat = 1000+i;
|
|
at += colwidth(c);
|
|
}
|
|
|
|
vector<int> next_column_width(POSSCORE+1, 0);
|
|
|
|
if(scorefrom < 0) scorefrom = 0;
|
|
int id = 0;
|
|
int omit = scorefrom;
|
|
int rank = 0;
|
|
while(y < (ISMOBILE ? vid.yres - 5*vid.fsize : vid.yres - 2 * vid.fsize)) {
|
|
if(id >= isize(scores)) break;
|
|
|
|
score& S(scores[id]);
|
|
|
|
if(omit) { omit--; rank++; id++; continue; }
|
|
|
|
bool cur = S.box[MAXBOX-1];
|
|
if(cur) {
|
|
saveBox();
|
|
for(int i=0; i<POSSCORE; i++) S.box[i] = save.box[i];
|
|
S.box[0] = S.box[65];
|
|
}
|
|
color_t col = cur ? 0xFFD500 : 0xC0C0C0;
|
|
|
|
|
|
rank++;
|
|
displaystr(bx*4, y, 0, score_size, its(rank), col, 16);
|
|
|
|
displaystr(bx*8, y, 0, score_size, S.ver, col, 16);
|
|
|
|
int at = 9;
|
|
for(int i=0; i<=POSSCORE; i++) {
|
|
int c = columns[i];
|
|
if(bx*at > vid.xres) break;
|
|
string s = displayfor(c, &S);
|
|
auto& ncw = next_column_width[c];
|
|
ncw = max(ncw, textwidth(score_size, s) / bx + 1);
|
|
if(c == 68) {
|
|
if(displaystr(bx*at, y, 0, score_size, s, col, 0))
|
|
getcstat = 1000+i;
|
|
at += colwidth(c);
|
|
}
|
|
else {
|
|
at += colwidth(c);
|
|
if(displaystr(bx*(at-1), y, 0, score_size, s, col, 16))
|
|
getcstat = 1000+i;
|
|
}
|
|
}
|
|
|
|
y += score_size*5/4; id++;
|
|
}
|
|
|
|
column_width = next_column_width;
|
|
int i0 = vid.yres - vid.fsize;
|
|
int xr = vid.xres / 80;
|
|
|
|
displayButton(xr*10, i0, IFM("s - ") + XLAT("sort"), 's', 8);
|
|
displayButton(xr*30, i0, IFM("t - ") + XLAT("choose"), 't', 8);
|
|
displayButton(xr*50, i0, IFM("z - ") + XLAT("zoom"), 'z', 8);
|
|
displayButton(xr*70, i0, IFM(dialog::keyname(SDLK_ESCAPE) + " - ") + XLAT("go back"), '0', 8);
|
|
|
|
keyhandler = [] (int sym, int uni) {
|
|
if(DKEY == SDLK_LEFT || uni == 'h' || uni == 'a') {
|
|
scorerev = false;
|
|
if(curcol > 0) curcol--;
|
|
}
|
|
else if(DKEY == SDLK_RIGHT || uni == 'l' || uni == 'd') {
|
|
scorerev = false;
|
|
if(curcol < POSSCORE) curcol++;
|
|
}
|
|
else if(sym >= 1000 && sym <= 1000+POSSCORE) {
|
|
scorerev = false;
|
|
curcol = sym - 1000;
|
|
}
|
|
else if(uni == 't') { dialog::infix = ""; pushScreen(showPickScores); }
|
|
else if(DKEY == SDLK_UP || uni == 'k' || uni == 'w')
|
|
scorefrom -= 5;
|
|
else if(DKEY == SDLK_DOWN || uni == 'j' || uni == 'x')
|
|
scorefrom += 5;
|
|
else if(uni == 'z') scale = 3 - scale;
|
|
else if(sym == PSEUDOKEY_WHEELUP)
|
|
scorefrom--;
|
|
else if(sym == PSEUDOKEY_WHEELDOWN)
|
|
scorefrom++;
|
|
else if(uni == 's') {
|
|
if(scorerev) reverse(scores.begin(), scores.end());
|
|
else {
|
|
scorerev = true;
|
|
stable_sort(scores.begin(), scores.end(), [] (const score& s1, const score &s2) {
|
|
return s1.box[columns[curcol]] > s2.box[columns[curcol]];
|
|
});
|
|
}
|
|
}
|
|
else if(doexiton(sym, uni)) popScreen();
|
|
|
|
static int scoredragy;
|
|
static bool lclicked;
|
|
|
|
if(mousepressed) {
|
|
if(!lclicked) {
|
|
// scoredragx = mousex;
|
|
scoredragy = mousey;
|
|
}
|
|
|
|
else {
|
|
while(mousey > scoredragy + vid.fsize) scoredragy += vid.fsize, scorefrom--;
|
|
while(mousey < scoredragy - vid.fsize) scoredragy -= vid.fsize, scorefrom++;
|
|
}
|
|
|
|
lclicked = mousepressed;
|
|
}
|
|
};
|
|
}
|
|
|
|
void load() {
|
|
if(scorefile == "") return;
|
|
scores.clear();
|
|
FILE *f = fopen(scorefile.c_str(), "rt");
|
|
if(!f) {
|
|
printf("Could not open the score file '%s'!\n", scorefile.c_str());
|
|
addMessage(s0 + "Could not open the score file: " + scorefile);
|
|
return;
|
|
}
|
|
string *yasc = nullptr;
|
|
while(!feof(f)) {
|
|
const int buflen = 1200;
|
|
char buf[buflen];
|
|
if(fgets(buf, buflen, f) == NULL) break;
|
|
if(buf[0] == 'H' && buf[1] == 'y') {
|
|
score sc; bool ok = true;
|
|
sc.box[MAXBOX-1] = 0;
|
|
{if(fscanf(f, "%s", buf) <= 0) break;} sc.ver = buf;
|
|
|
|
|
|
for(int i=0; i<MAXBOX; i++) {
|
|
if(fscanf(f, "%d", &sc.box[i]) <= 0) { boxid = i; break; }
|
|
}
|
|
|
|
for(int i=boxid; i<MAXBOX; i++) sc.box[i] = 0;
|
|
|
|
if(!verless(sc.ver, "4.4")) {
|
|
sc.box[0] = sc.box[65];
|
|
// the first executable on Steam included a corruption
|
|
if(sc.box[65] > 1420000000 && sc.box[65] < 1430000000) {
|
|
sc.box[0] = sc.box[65] - sc.box[1];
|
|
sc.box[65] = sc.box[0];
|
|
}
|
|
// do not include saves
|
|
if(sc.box[65 + 4 + itOrbSafety - itOrbLightning]) ok = false;
|
|
}
|
|
else
|
|
sc.box[0] = sc.box[1] - sc.box[0]; // could not save then
|
|
|
|
if(sc.box[2] == 0) continue; // do not list zero scores
|
|
sc.box[POSSCORE] = modediff(&sc);
|
|
|
|
if(ok && boxid > 20) {
|
|
scores.push_back(sc);
|
|
yasc = &scores.back().yasc_message;
|
|
}
|
|
}
|
|
if(buf[0] == 'Y' && buf[1] == 'A' && buf[2] == 'S' && buf[3] == 'C' && buf[4] == ' ') {
|
|
for(int i=5; i<buflen; i++) if(buf[i] == '\n' || buf[i] == '\r') buf[i] = 0;
|
|
*yasc = buf+5;
|
|
}
|
|
}
|
|
|
|
saveBox();
|
|
score sc;
|
|
for(int i=0; i<POSSCORE; i++) sc.box[i] = save.box[i];
|
|
sc.box[POSSCORE] = 0;
|
|
sc.box[MAXBOX-1] = 1; sc.ver = "NOW";
|
|
sc.yasc_message = canmove ? "on the run" : yasc_message;
|
|
scores.push_back(sc);
|
|
|
|
fclose(f);
|
|
clearMessages();
|
|
// addMessage(its(isize(scores))+" games have been recorded in "+scorefile);
|
|
pushScreen(show);
|
|
boxid = 0; applyBoxes();
|
|
reverse(scores.begin(), scores.end());
|
|
scorefrom = 0;
|
|
stable_sort(scores.begin(), scores.end(), [] (const score& s1, const score& s2) {
|
|
return tie(s1.box[POSSCORE], s1.box[2]) > tie(s2.box[POSSCORE], s2.box[2]);
|
|
});
|
|
}
|
|
|
|
}}
|
|
|
|
#endif
|
|
|