2019-08-10 11:43:24 +00:00
|
|
|
// Hyperbolic Rogue -- local highscore lists
|
2018-02-08 23:40:26 +00:00
|
|
|
// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
|
|
|
|
|
2019-08-10 11:43:24 +00:00
|
|
|
/** \file scores.cpp
|
|
|
|
* \brief local highscore lists
|
|
|
|
*/
|
|
|
|
|
2019-09-05 07:15:40 +00:00
|
|
|
#include "hyper.h"
|
2017-07-22 23:33:27 +00:00
|
|
|
#if CAP_SAVE
|
2017-07-12 16:03:53 +00:00
|
|
|
|
2018-06-11 16:00:40 +00:00
|
|
|
namespace hr { namespace scores {
|
2017-07-12 16:03:53 +00:00
|
|
|
|
2017-07-10 18:47:38 +00:00
|
|
|
vector<score> scores;
|
2017-07-12 16:03:53 +00:00
|
|
|
score *currentgame;
|
2017-07-10 18:47:38 +00:00
|
|
|
|
|
|
|
int scorefrom = 0;
|
|
|
|
bool scorerev = false;
|
|
|
|
|
2017-07-12 17:50:39 +00:00
|
|
|
string csub(const string& str, int q) {
|
|
|
|
int i = 0;
|
2018-06-22 12:47:24 +00:00
|
|
|
for(int j=0; j<q && i<isize(str); j++) getnext(str.c_str(), i);
|
2017-07-12 17:50:39 +00:00
|
|
|
return str.substr(0, i);
|
|
|
|
}
|
|
|
|
|
2018-02-01 02:07:01 +00:00
|
|
|
int colwidth(int scoredisplay) {
|
2017-07-12 16:03:53 +00:00
|
|
|
if(scoredisplay == 0) return 5;
|
|
|
|
if(scoredisplay == 1) return 16;
|
|
|
|
if(scoredisplay == 5) return 8;
|
2017-07-12 17:50:39 +00:00
|
|
|
if(scoredisplay == POSSCORE) return 8;
|
2017-07-12 16:03:53 +00:00
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
|
2017-07-12 17:50:39 +00:00
|
|
|
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];
|
2017-07-16 21:00:55 +00:00
|
|
|
if(S->box[306] != inv::on) diff += 4;
|
2017-07-12 17:50:39 +00:00
|
|
|
if(S->box[238]) g = gSphere;
|
|
|
|
if(S->box[239]) g = gElliptic;
|
|
|
|
if(max(S->box[197], 1) != multi::players) diff += 8;
|
2018-08-28 15:17:34 +00:00
|
|
|
|
|
|
|
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))
|
2018-04-10 22:30:50 +00:00
|
|
|
diff += 16;
|
2017-07-12 17:50:39 +00:00
|
|
|
if(S->box[196] != chaosmode) diff += 32;
|
|
|
|
if(S->box[119] != shmup::on) diff += 64;
|
|
|
|
if(pureHardcore() && !isHardcore(S)) diff += 128;
|
2017-08-06 12:50:16 +00:00
|
|
|
if(g != gNormal && S->box[120] != specialland)
|
2017-07-12 17:50:39 +00:00
|
|
|
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;
|
2017-10-28 23:57:34 +00:00
|
|
|
string s = ginf[g].shortname;
|
2017-07-12 17:50:39 +00:00
|
|
|
if(g != gNormal) s += " " + csub(XLATT1((eLand) S->box[120]), 3);
|
2018-04-10 22:30:50 +00:00
|
|
|
if(S->box[341]) s += "/GP(" + its(S->box[342])+","+its(S->box[343])+")";
|
|
|
|
else if(S->box[186]) s += "/7";
|
2017-07-12 17:50:39 +00:00
|
|
|
if(S->box[196]) s += "/C";
|
|
|
|
if(S->box[119]) s += "/s";
|
|
|
|
if(S->box[197] > 1) s += "/P" + its(S->box[197]);
|
2017-07-16 21:00:55 +00:00
|
|
|
if(S->box[306]) s += "/i";
|
2017-07-12 17:50:39 +00:00
|
|
|
if(isHardcore(S)) s += "/h";
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2018-02-01 02:07:01 +00:00
|
|
|
string displayfor(int scoredisplay, score* S, bool shorten = false) {
|
2017-07-10 18:47:38 +00:00
|
|
|
// printf("S=%p, scoredisplay = %d\n", S, scoredisplay);
|
|
|
|
if(S == NULL) {
|
2017-07-12 17:50:39 +00:00
|
|
|
if(scoredisplay == POSSCORE) return "mode";
|
2017-07-12 16:03:53 +00:00
|
|
|
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");
|
2017-07-12 17:50:39 +00:00
|
|
|
return csub(str, 5);
|
2017-07-10 18:47:38 +00:00
|
|
|
}
|
2017-07-12 16:03:53 +00:00
|
|
|
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);
|
2017-07-10 18:47:38 +00:00
|
|
|
return buf;
|
|
|
|
}
|
2017-07-12 17:50:39 +00:00
|
|
|
if(scoredisplay == POSSCORE) return modedesc(S);
|
2017-07-10 18:47:38 +00:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<pair<string, int> > pickscore_options;
|
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
int curcol;
|
|
|
|
|
|
|
|
vector<int> columns;
|
|
|
|
|
|
|
|
bool monsterpage = false;
|
|
|
|
|
|
|
|
void showPickScores() {
|
|
|
|
|
|
|
|
pickscore_options.clear();
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
scorerev = false;
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2017-07-12 17:50:39 +00:00
|
|
|
for(int i=0; i<=POSSCORE; i++) {
|
2018-02-01 02:07:01 +00:00
|
|
|
int scoredisplay = i;
|
|
|
|
if(!fakebox[scoredisplay]) {
|
|
|
|
string s = displayfor(scoredisplay, NULL);
|
2017-12-09 03:01:56 +00:00
|
|
|
if(dialog::hasInfix(s))
|
2017-07-12 16:03:53 +00:00
|
|
|
if(monsbox[scoredisplay] == monsterpage)
|
|
|
|
pickscore_options.push_back(make_pair(s, i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort(pickscore_options.begin(), pickscore_options.end());
|
|
|
|
|
|
|
|
int q = (int) pickscore_options.size();
|
|
|
|
int percolumn = vid.yres / (vid.fsize+3) - 4;
|
|
|
|
int qcolumns = 1 + (q-1) / percolumn;
|
|
|
|
|
|
|
|
for(int i=0; i<q; i++) {
|
|
|
|
int x = 16 + (vid.xres * (i/percolumn)) / qcolumns;
|
|
|
|
int y = (vid.fsize+3) * (i % percolumn) + vid.fsize*2;
|
|
|
|
|
2018-02-01 02:07:01 +00:00
|
|
|
int scoredisplay = pickscore_options[i].second;
|
2017-07-12 16:03:53 +00:00
|
|
|
if(q <= 9)
|
|
|
|
pickscore_options[i].first = pickscore_options[i].first + " [" + its(i+1) + "]";
|
2018-02-01 02:07:01 +00:00
|
|
|
if(!fakebox[scoredisplay])
|
2017-07-12 16:03:53 +00:00
|
|
|
displayButton(x, y, pickscore_options[i].first, 1000+i, 0);
|
|
|
|
}
|
|
|
|
|
2017-08-18 00:47:10 +00:00
|
|
|
displayButton(vid.xres/2, vid.yres - vid.fsize*2, "kills", '/', 8);
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2017-12-09 03:01:56 +00:00
|
|
|
mouseovers = dialog::infix;
|
2017-07-12 16:03:53 +00:00
|
|
|
keyhandler = [] (int sym, int uni) {
|
2017-12-09 03:01:56 +00:00
|
|
|
if(uni == '/' && dialog::infix == "") monsterpage = !monsterpage; else
|
2017-07-12 16:03:53 +00:00
|
|
|
if(uni >= '1' && uni <= '9') uni = uni + 1000 - '1';
|
2018-06-22 12:47:24 +00:00
|
|
|
else if(uni >= 1000 && uni < 1000 + isize(pickscore_options)) {
|
2018-02-01 02:07:01 +00:00
|
|
|
int scoredisplay = pickscore_options[uni - 1000].second;
|
2017-07-12 17:50:39 +00:00
|
|
|
for(int i=0; i<=POSSCORE; i++)
|
2017-07-12 16:03:53 +00:00
|
|
|
if(columns[i] == scoredisplay) swap(columns[i], columns[curcol]);
|
|
|
|
popScreen();
|
|
|
|
}
|
2017-12-09 03:01:56 +00:00
|
|
|
else if(dialog::editInfix(uni)) ;
|
2017-07-12 16:03:53 +00:00
|
|
|
else if(doexiton(sym, uni)) popScreen();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
void show() {
|
|
|
|
|
2017-07-12 17:50:39 +00:00
|
|
|
if(columns.size() == 0) {
|
|
|
|
columns.push_back(POSSCORE);
|
|
|
|
for(int i=0; i<POSSCORE; i++) columns.push_back(i);
|
|
|
|
}
|
2017-07-12 16:03:53 +00:00
|
|
|
int y = vid.fsize * 5/2;
|
|
|
|
int bx = vid.fsize;
|
|
|
|
getcstat = 0;
|
|
|
|
|
|
|
|
displaystr(bx*4, vid.fsize, 0, vid.fsize, "#", forecolor, 16);
|
|
|
|
displaystr(bx*8, vid.fsize, 0, vid.fsize, XLAT("ver"), forecolor, 16);
|
|
|
|
|
|
|
|
int at = 9;
|
2017-07-12 17:50:39 +00:00
|
|
|
for(int i=0; i<=POSSCORE; i++) {
|
2018-02-01 02:07:01 +00:00
|
|
|
int c = columns[i];
|
2017-07-12 16:03:53 +00:00
|
|
|
if(bx*at > vid.xres) break;
|
2018-02-01 02:07:01 +00:00
|
|
|
if(displaystr(bx*at, vid.fsize, 0, vid.fsize, displayfor(c, NULL, true), i == curcol ? 0xFFD500 : forecolor, 0))
|
2017-07-12 16:03:53 +00:00
|
|
|
getcstat = 1000+i;
|
2018-02-01 02:07:01 +00:00
|
|
|
at += colwidth(c);
|
2017-07-12 16:03:53 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 18:47:38 +00:00
|
|
|
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)) {
|
2018-06-22 12:47:24 +00:00
|
|
|
if(id >= isize(scores)) break;
|
2017-07-10 18:47:38 +00:00
|
|
|
|
|
|
|
score& S(scores[id]);
|
|
|
|
|
|
|
|
if(omit) { omit--; rank++; id++; continue; }
|
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
bool cur = S.box[MAXBOX-1];
|
|
|
|
if(cur) {
|
|
|
|
saveBox();
|
2020-03-27 19:24:01 +00:00
|
|
|
for(int i=0; i<POSSCORE; i++) S.box[i] = save.box[i];
|
2017-07-12 16:03:53 +00:00
|
|
|
S.box[0] = S.box[65];
|
|
|
|
}
|
2018-09-04 17:53:42 +00:00
|
|
|
color_t col = cur ? 0xFFD500 : 0xC0C0C0;
|
2017-07-10 18:47:38 +00:00
|
|
|
|
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
rank++;
|
|
|
|
displaystr(bx*4, y, 0, vid.fsize, its(rank), col, 16);
|
|
|
|
|
|
|
|
displaystr(bx*8, y, 0, vid.fsize, S.ver, col, 16);
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
int at = 9;
|
2017-07-12 17:50:39 +00:00
|
|
|
for(int i=0; i<=POSSCORE; i++) {
|
2018-02-01 02:07:01 +00:00
|
|
|
int c = columns[i];
|
2017-07-12 16:03:53 +00:00
|
|
|
if(bx*at > vid.xres) break;
|
2018-02-01 02:07:01 +00:00
|
|
|
at += colwidth(c);
|
|
|
|
if(displaystr(bx*(at-1), y, 0, vid.fsize, displayfor(c, &S), col, 16))
|
2017-07-12 16:03:53 +00:00
|
|
|
getcstat = 1000+i;
|
|
|
|
}
|
2017-07-10 18:47:38 +00:00
|
|
|
|
|
|
|
y += vid.fsize*5/4; id++;
|
|
|
|
}
|
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
int i0 = vid.yres - vid.fsize;
|
|
|
|
int xr = vid.xres / 80;
|
|
|
|
|
|
|
|
displayButton(xr*10, i0, IFM("s - ") + XLAT("sort"), 's', 8);
|
2017-07-12 17:50:39 +00:00
|
|
|
displayButton(xr*30, i0, IFM("t - ") + XLAT("choose"), 't', 8);
|
2018-06-12 22:00:01 +00:00
|
|
|
displayButton(xr*50, i0, IFM(dialog::keyname(SDLK_ESCAPE) + " - ") + XLAT("go back"), '0', 8);
|
2017-07-10 18:47:38 +00:00
|
|
|
|
|
|
|
keyhandler = [] (int sym, int uni) {
|
2018-12-06 11:31:51 +00:00
|
|
|
if(DKEY == SDLK_LEFT || uni == 'h' || uni == 'a') {
|
2018-02-01 02:07:01 +00:00
|
|
|
scorerev = false;
|
2017-07-12 16:03:53 +00:00
|
|
|
if(curcol > 0) curcol--;
|
|
|
|
}
|
2018-12-06 11:31:51 +00:00
|
|
|
else if(DKEY == SDLK_RIGHT || uni == 'l' || uni == 'd') {
|
2018-02-01 02:07:01 +00:00
|
|
|
scorerev = false;
|
2017-07-12 17:50:39 +00:00
|
|
|
if(curcol < POSSCORE) curcol++;
|
2017-07-12 16:03:53 +00:00
|
|
|
}
|
2018-02-01 02:07:01 +00:00
|
|
|
else if(sym >= 1000 && sym <= 1000+POSSCORE) {
|
|
|
|
scorerev = false;
|
2017-07-12 16:03:53 +00:00
|
|
|
curcol = sym - 1000;
|
2018-02-01 02:07:01 +00:00
|
|
|
}
|
2018-12-06 11:31:51 +00:00
|
|
|
else if(uni == 't') { dialog::infix = ""; pushScreen(showPickScores); }
|
|
|
|
else if(DKEY == SDLK_UP || uni == 'k' || uni == 'w')
|
2017-07-10 18:47:38 +00:00
|
|
|
scorefrom -= 5;
|
2018-12-06 11:31:51 +00:00
|
|
|
else if(DKEY == SDLK_DOWN || uni == 'j' || uni == 'x')
|
2017-07-10 18:47:38 +00:00
|
|
|
scorefrom += 5;
|
|
|
|
else if(sym == PSEUDOKEY_WHEELUP)
|
|
|
|
scorefrom--;
|
|
|
|
else if(sym == PSEUDOKEY_WHEELDOWN)
|
|
|
|
scorefrom++;
|
2018-12-06 11:31:51 +00:00
|
|
|
else if(uni == 's') {
|
2018-02-01 02:07:01 +00:00
|
|
|
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]];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2017-07-10 18:47:38 +00:00
|
|
|
else if(doexiton(sym, uni)) popScreen();
|
2017-07-12 16:03:53 +00:00
|
|
|
|
|
|
|
static int scoredragy;
|
|
|
|
static bool lclicked;
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
if(mousepressed) {
|
|
|
|
if(!lclicked) {
|
|
|
|
// scoredragx = mousex;
|
2017-07-10 18:47:38 +00:00
|
|
|
scoredragy = mousey;
|
|
|
|
}
|
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
else {
|
2017-07-10 18:47:38 +00:00
|
|
|
while(mousey > scoredragy + vid.fsize) scoredragy += vid.fsize, scorefrom--;
|
|
|
|
while(mousey < scoredragy - vid.fsize) scoredragy -= vid.fsize, scorefrom++;
|
|
|
|
}
|
2017-07-12 16:03:53 +00:00
|
|
|
|
|
|
|
lclicked = mousepressed;
|
2017-07-10 18:47:38 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
void load() {
|
|
|
|
scores.clear();
|
|
|
|
FILE *f = fopen(scorefile, "rt");
|
|
|
|
if(!f) {
|
|
|
|
printf("Could not open the score file '%s'!\n", scorefile);
|
|
|
|
addMessage(s0 + "Could not open the score file: " + scorefile);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
while(!feof(f)) {
|
|
|
|
char buf[120];
|
|
|
|
if(fgets(buf, 120, 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;
|
|
|
|
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
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;
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2017-07-16 21:00:55 +00:00
|
|
|
if(!verless(sc.ver, "4.4")) {
|
2017-07-12 16:03:53 +00:00
|
|
|
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
|
2017-07-12 17:50:39 +00:00
|
|
|
sc.box[POSSCORE] = modediff(&sc);
|
2017-07-12 16:03:53 +00:00
|
|
|
|
|
|
|
if(ok && boxid > 20) scores.push_back(sc);
|
2017-07-10 18:47:38 +00:00
|
|
|
}
|
|
|
|
}
|
2017-07-12 16:03:53 +00:00
|
|
|
|
|
|
|
saveBox();
|
|
|
|
score sc;
|
2020-03-27 19:24:01 +00:00
|
|
|
for(int i=0; i<POSSCORE; i++) sc.box[i] = save.box[i];
|
2017-07-12 17:50:39 +00:00
|
|
|
sc.box[POSSCORE] = 0;
|
2017-07-12 16:03:53 +00:00
|
|
|
sc.box[MAXBOX-1] = 1; sc.ver = "NOW";
|
|
|
|
scores.push_back(sc);
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2017-07-12 16:03:53 +00:00
|
|
|
fclose(f);
|
|
|
|
clearMessages();
|
2018-06-22 12:47:24 +00:00
|
|
|
// addMessage(its(isize(scores))+" games have been recorded in "+scorefile);
|
2017-07-12 16:03:53 +00:00
|
|
|
pushScreen(show);
|
|
|
|
boxid = 0; applyBoxes();
|
2017-07-12 17:50:39 +00:00
|
|
|
reverse(scores.begin(), scores.end());
|
2017-07-12 16:03:53 +00:00
|
|
|
scorefrom = 0;
|
2018-02-01 02:07:01 +00:00
|
|
|
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]);
|
|
|
|
});
|
2017-07-12 16:03:53 +00:00
|
|
|
}
|
2017-07-10 18:47:38 +00:00
|
|
|
|
2018-06-11 16:00:40 +00:00
|
|
|
}}
|
2017-07-10 18:47:38 +00:00
|
|
|
|
|
|
|
#endif
|
|
|
|
|