/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
*
* Copyright (©) 2005,2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
* Copyright (©) 1992,2007 Frank Tore Johansen
*
* Deliantra is free software: you can redistribute it and/or modify it under
* the terms of the Affero GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Affero GNU General Public License
* and the GNU General Public License along with this program. If not, see
* .
*
* The authors can be reached via e-mail to
*/
#include
#include
/*
* The score structure is used when treating new high-scores
*/
typedef struct scr
{
char name[64]; // name */
char title[64]; // Title */
char killer[64]; // name (+ title) or "quit" */
sint64 exp; // Experience */
char maplevel[128]; // Killed on what level */
int maxhp, maxsp, maxgrace; // Max hp, sp, grace when killed */
int position; // Position in the highscore list */
} score;
/*
* spool works mostly like strtok(char *, ":"), but it can also
* log a specified error message if something goes wrong.
*/
static char *
spool (char *bp, const char *error)
{
static char *prev_pos = NULL;
char *next_pos;
if (bp == NULL)
{
if (prev_pos == NULL)
{
LOG (llevError, "Called spool (%s) with NULL without previous call.\n", error);
return NULL;
}
bp = prev_pos;
}
if (*bp == '\0')
{
LOG (llevError, "spool: End of line at %s\n", error);
return NULL;
}
if ((next_pos = strchr (bp, ':')) != NULL)
{
*next_pos = '\0';
prev_pos = next_pos + 1;
}
else
prev_pos = NULL;
return bp;
}
/*
* Does what it says, copies the contents of the first score structure
* to the second one.
*/
static void
copy_score (score *sc1, score *sc2)
{
assign (sc2->name , sc1->name);
assign (sc2->title , sc1->title);
assign (sc2->killer , sc1->killer);
assign (sc2->maplevel, sc1->maplevel);
sc2->exp = sc1->exp;
sc2->maxhp = sc1->maxhp;
sc2->maxsp = sc1->maxsp;
sc2->maxgrace = sc1->maxgrace;
}
/*
* Writes the given score structure to a static buffer, and returns
* a pointer to it.
*/
static char *
put_score (score *sc)
{
static char buf[MAX_BUF];
sprintf (buf, "%s:%s:%" PRId64 ":%s:%s:%d:%d:%d",
sc->name, sc->title, (sint64)sc->exp, sc->killer,
sc->maplevel, sc->maxhp, sc->maxsp, sc->maxgrace);
return buf;
}
/*
* The oposite of put_score, get_score reads from the given buffer into
* a static score structure, and returns a pointer to it.
*/
static score *
get_score (char *bp)
{
static score sc;
char *cp;
if ((cp = strchr (bp, '\n')) != NULL)
*cp = '\0';
if ((cp = spool (bp, "name")) == NULL)
return 0;
assign (sc.name, cp);
if ((cp = spool (0, "title")) == NULL)
return 0;
assign (sc.title, cp);
if ((cp = spool (0, "score")) == NULL)
return 0;
sscanf (cp, "%" SCNd64, &sc.exp);
if ((cp = spool (0, "killer")) == NULL)
return 0;
assign (sc.killer, cp);
if ((cp = spool (0, "map")) == NULL)
return 0;
assign (sc.maplevel, cp);
if ((cp = spool (0, "maxhp")) == NULL)
return 0;
sscanf (cp, "%d", &sc.maxhp);
if ((cp = spool (0, "maxsp")) == NULL)
return 0;
sscanf (cp, "%d", &sc.maxsp);
if ((cp = spool (0, "maxgrace")) == NULL)
return 0;
sscanf (cp, "%d", &sc.maxgrace);
return ≻
}
static char *
draw_one_high_score (score *sc)
{
static char retbuf[MAX_BUF];
if (!strncmp (sc->killer, "quit", MAX_NAME))
sprintf (retbuf, "%3d %10lld %s the %s quit the game on map %s [%d][%d][%d].",
sc->position, (long long) sc->exp, sc->name, sc->title, sc->maplevel, sc->maxhp, sc->maxsp, sc->maxgrace);
else if (!strncmp (sc->killer, "leaving", MAX_NAME))
sprintf (retbuf, "%3d %10lld %s the %s left the game on map %s [%d][%d][%d].",
sc->position, (long long) sc->exp, sc->name, sc->title, sc->maplevel, sc->maxhp, sc->maxsp, sc->maxgrace);
else
sprintf (retbuf, "%3d %10lld %s the %s was killed by %s on map %s [%d][%d][%d].",
sc->position, (long long) sc->exp, sc->name, sc->title, sc->killer, sc->maplevel, sc->maxhp, sc->maxsp, sc->maxgrace);
return retbuf;
}
/*
* add_score() adds the given score-structure to the high-score list, but
* only if it was good enough to deserve a place.
*/
static score *
add_score (score *new_score)
{
FILE *fp;
static score old_score;
score *tmp_score, pscore[HIGHSCORE_LENGTH];
char buf[MAX_BUF], filename[MAX_BUF], *bp;
int nrofscores = 0, flag = 0, i, comp;
new_score->position = HIGHSCORE_LENGTH + 1;
old_score.position = -1;
sprintf (filename, "%s/%s", settings.localdir, HIGHSCORE);
if ((fp = open_and_uncompress (filename, 1, &comp)) != NULL)
{
while (fgets (buf, MAX_BUF, fp) != NULL && nrofscores < HIGHSCORE_LENGTH)
{
if ((tmp_score = get_score (buf)) == NULL)
break;
if (!flag && new_score->exp >= tmp_score->exp)
{
copy_score (new_score, &pscore[nrofscores]);
new_score->position = nrofscores;
flag = 1;
if (++nrofscores >= HIGHSCORE_LENGTH)
break;
}
if (!strcmp (new_score->name, tmp_score->name))
{ /* Another entry */
copy_score (tmp_score, &old_score);
old_score.position = nrofscores;
if (flag)
continue;
}
copy_score (tmp_score, &pscore[nrofscores++]);
}
close_and_delete (fp, comp);
}
if (old_score.position != -1 && old_score.exp >= new_score->exp)
return &old_score; /* Did not beat old score */
if (!flag && nrofscores < HIGHSCORE_LENGTH)
copy_score (new_score, &pscore[nrofscores++]);
if ((fp = fopen (filename, "w")) == NULL)
{
LOG (llevError, "Cannot write to highscore file %s: %s\n", filename, strerror (errno));
return NULL;
}
for (i = 0; i < nrofscores; i++)
{
bp = put_score (&pscore[i]);
fprintf (fp, "%s\n", bp);
}
fclose (fp);
if (flag)
{
/* Eneq(@csd.uu.se): Patch to fix error in adding a new score to the
hiscore-list */
if (old_score.position == -1)
return new_score;
return &old_score;
}
new_score->position = -1;
if (old_score.position != -1)
return &old_score;
if (nrofscores)
{
copy_score (&pscore[nrofscores - 1], &old_score);
return &old_score;
}
LOG (llevError, "Highscore error.\n");
return NULL;
}
void
check_score (object *op)
{
score new_score;
score *old_score;
if (op->stats.exp == 0)
return;
assign (new_score.name, op->name);
assign (new_score.title, op->title.length () ? &op->title : op->contr->title);
assign (new_score.killer, op->contr->killer_name ());
assign (new_score.maplevel, op->map ? op->map->name ? &op->map->name : &op->map->path : "");
new_score.exp = op->stats.exp;
new_score.maxhp = op->stats.maxhp;
new_score.maxsp = op->stats.maxsp;
new_score.maxgrace = op->stats.maxgrace;
if ((old_score = add_score (&new_score)) == NULL)
{
new_draw_info (NDI_UNIQUE, 0, op, "Error in the highscore list.");
return;
}
if (new_score.position == -1)
{
new_score.position = HIGHSCORE_LENGTH + 1; /* Not strictly correct... */
if (!strcmp (old_score->name, new_score.name))
new_draw_info (NDI_UNIQUE, 0, op, "You didn't beat your last highscore:");
else
new_draw_info (NDI_UNIQUE, 0, op, "You didn't enter the highscore list:");
new_draw_info (NDI_UNIQUE, 0, op, draw_one_high_score (old_score));
new_draw_info (NDI_UNIQUE, 0, op, draw_one_high_score (&new_score));
return;
}
if (old_score->exp >= new_score.exp)
new_draw_info (NDI_UNIQUE, 0, op, "You didn't beat your last score:");
else
new_draw_info (NDI_UNIQUE, 0, op, "You beat your last score:");
new_draw_info (NDI_UNIQUE, 0, op, draw_one_high_score (old_score));
new_draw_info (NDI_UNIQUE, 0, op, draw_one_high_score (&new_score));
}