/*
* 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 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 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.
*/
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));
}
/* displays the high score file. object is the calling object
* (null if being called via command line.) max is the maximum
* number of scores to display. match, if set, is the name or class
* to match to.
*/
void
display_high_score (object *op, int max, const char *match)
{
const size_t maxchar = 80;
FILE *fp;
char buf[MAX_BUF], *scorebuf, *bp, *cp;
int i = 0, j = 0, comp;
score *sc;
sprintf (buf, "%s/%s", settings.localdir, HIGHSCORE);
if ((fp = open_and_uncompress (buf, 0, &comp)) == NULL)
{
LOG (llevError, "Cannot open highscore file %s: %s\n", buf, strerror (errno));
if (op != NULL)
new_draw_info (NDI_UNIQUE, 0, op, "There is no highscore file.");
return;
}
new_draw_info (NDI_UNIQUE, 0, op, "Nr Score Who [max hp][max sp][max grace]");
while (fgets (buf, MAX_BUF, fp) != NULL)
{
if (j >= HIGHSCORE_LENGTH || i >= (max - 1))
break;
if ((sc = get_score (buf)) == NULL)
break;
sc->position = ++j;
if (match == NULL)
{
scorebuf = draw_one_high_score (sc);
i++;
}
else
{
if (!strcasecmp (sc->name, match) || !strcasecmp (sc->title, match))
{
scorebuf = draw_one_high_score (sc);
i++;
}
else
continue;
}
/* Replaced what seemed to an overly complicated word wrap method
* still word wraps, but assumes at most 2 lines of data.
* mw - 2-12-97
*/
assign (buf, scorebuf);
cp = buf;
while (strlen (cp) > maxchar)
{
bp = cp + maxchar - 1;
while (*bp != ' ' && bp > cp)
bp--;
*bp = '\0';
if (op == NULL)
LOG (llevDebug, "%s\n", cp);
else
new_draw_info (NDI_UNIQUE, 0, op, cp);
sprintf (buf, " %s", bp + 1);
cp = buf;
i++;
}
if (op == NULL)
LOG (llevDebug, "%s\n", buf);
else
new_draw_info (NDI_UNIQUE, 0, op, buf);
}
close_and_delete (fp, comp);
}