Crossfire Server, Trunk
hiscore.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
18 #define _GNU_SOURCE // strcasestr() is a GNU extension in string.h
19 
20 #include "global.h"
21 
22 #include <errno.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 
27 #include "sproto.h"
28 #include "output_file.h"
29 
33 typedef struct scr {
34  char name[BIG_NAME];
35  char title[BIG_NAME];
36  char killer[BIG_NAME];
37  int64_t exp;
39  int maxhp,
42  int position;
43 } score;
44 
48 typedef struct {
49  char fname[MAX_BUF];
50  char skill_name[MAX_BUF];
52 } score_table;
53 
57 static score_table hiscore_tables[MAX_SKILLS + 1]; // One for each skill, plus one for overall
58 
69 static void put_score(const score *sc, char *buf, size_t size) {
70  snprintf(buf, size, "%s:%s:%"FMT64":%s:%s:%d:%d:%d", sc->name, sc->title, sc->exp, sc->killer, sc->maplevel, sc->maxhp, sc->maxsp, sc->maxgrace);
71 }
72 
79 static void hiscore_save(const score_table *table) {
80  FILE *fp;
81  OutputFile of;
82  size_t i;
83  char buf[MAX_BUF];
84 
85  fp = of_open(&of, table->fname);
86  if (fp == NULL)
87  return;
88 
89  for (i = 0; i < HIGHSCORE_LENGTH; i++) {
90  if (table->entry[i].name[0] == '\0')
91  break;
92 
93  put_score(&table->entry[i], buf, sizeof(buf));
94  fprintf(fp, "%s\n", buf);
95  }
96  of_close(&of);
97 }
98 
111 static int get_score(char *bp, score *sc) {
112  char *cp;
113  char *tmp[8];
114 
115  cp = strchr(bp, '\n');
116  if (cp != NULL)
117  *cp = '\0';
118 
119  if (split_string(bp, tmp, 8, ':') != 8)
120  return 0;
121 
122  strncpy(sc->name, tmp[0], BIG_NAME);
123  sc->name[BIG_NAME-1] = '\0';
124 
125  strncpy(sc->title, tmp[1], BIG_NAME);
126  sc->title[BIG_NAME-1] = '\0';
127 
128  sscanf(tmp[2], "%"FMT64, &sc->exp);
129 
130  strncpy(sc->killer, tmp[3], BIG_NAME);
131  sc->killer[BIG_NAME-1] = '\0';
132 
133  strncpy(sc->maplevel, tmp[4], BIG_NAME);
134  sc->maplevel[BIG_NAME-1] = '\0';
135 
136  sscanf(tmp[5], "%d", &sc->maxhp);
137 
138  sscanf(tmp[6], "%d", &sc->maxsp);
139 
140  sscanf(tmp[7], "%d", &sc->maxgrace);
141  return 1;
142 }
143 
156 static char *draw_one_high_score(const score *sc, char *buf, size_t size) {
157  const char *s1;
158  const char *s2;
159 
160  if (strcmp(sc->killer, "quit") == 0 || strcmp(sc->killer, "left") == 0) {
161  s1 = sc->killer;
162  s2 = "the game";
163  } else if (strcmp(sc->killer,"a dungeon collapse") == 0) {
164  s1 = "was last";
165  s2 = "seen";
166  } else {
167  s1 = "was killed by";
168  s2 = sc->killer;
169  }
170  snprintf(buf, size, "[fixed]%3d %10"FMT64"[print] %s%s%s %s %s on map %s <%d><%d><%d>.",
171  sc->position, sc->exp, sc->name, sc->title[0]==',' ? "" : " ", sc->title, s1, s2, sc->maplevel, sc->maxhp, sc->maxsp, sc->maxgrace);
172  return buf;
173 }
174 
186 static void add_score(score_table *table, score *new_score, score *old_score) {
187  size_t i;
188 
189  new_score->position = HIGHSCORE_LENGTH+1;
190  memset(old_score, 0, sizeof(*old_score));
191  old_score->position = -1;
192 
193  /* find existing entry by name */
194  for (i = 0; i < HIGHSCORE_LENGTH; i++) {
195  if (table->entry[i].name[0] == '\0') {
196  table->entry[i] = *new_score;
197  table->entry[i].position = i+1;
198  break;
199  }
200  if (strcmp(new_score->name, table->entry[i].name) == 0) {
201  *old_score = table->entry[i];
202  if (table->entry[i].exp <= new_score->exp) {
203  table->entry[i] = *new_score;
204  table->entry[i].position = i+1;
205  }
206  break;
207  }
208  }
209 
210  if (i >= HIGHSCORE_LENGTH) {
211  /* entry for unknown name */
212 
213  if (new_score->exp < table->entry[i-1].exp) {
214  /* new exp is less than lowest hiscore entry => drop */
215  return;
216  }
217 
218  /* new exp is not less than lowest hiscore entry => add */
219  i--;
220  table->entry[i] = *new_score;
221  table->entry[i].position = i+1;
222  }
223 
224  /* move entry to correct position */
225  while (i > 0 && new_score->exp >= table->entry[i-1].exp) {
226  score tmp;
227 
228  tmp = table->entry[i-1];
229  table->entry[i-1] = table->entry[i];
230  table->entry[i] = tmp;
231 
232  table->entry[i-1].position = i;
233  table->entry[i].position = i+1;
234 
235  i--;
236  }
237 
238  new_score->position = table->entry[i].position;
239  hiscore_save(table);
240 }
241 
248 static void hiscore_load(score_table *table) {
249  FILE *fp;
250  size_t i;
251 
252  i = 0;
253 
254  fp = fopen(table->fname, "r");
255  if (fp == NULL) {
256  if (errno == ENOENT) {
257  LOG(llevDebug, "Highscore file %s does not exist\n", table->fname);
258  } else {
259  LOG(llevError, "Cannot open highscore file %s: %s\n", table->fname, strerror(errno));
260  }
261  } else {
262  LOG(llevDebug, "Reading highscore file %s\n", table->fname);
263  while (i < HIGHSCORE_LENGTH) {
264  char buf[MAX_BUF];
265 
266  if (fgets(buf, MAX_BUF, fp) == NULL) {
267  break;
268  }
269 
270  if (!get_score(buf, &table->entry[i]))
271  break;
272  table->entry[i].position = i+1;
273  i++;
274  }
275 
276  fclose(fp);
277  }
278 
279  while (i < HIGHSCORE_LENGTH) {
280  memset(&table->entry[i], 0, sizeof(table->entry[i]));
281  // This cannot be ++i due the right-to-left association of assignment.
282  table->entry[i].position = i + 1;
283  i++;
284  }
285 }
286 
294 void hiscore_init(void) {
295  char dirname[MAX_BUF];
296 
297  snprintf(dirname, sizeof(dirname), "%s/%s", settings.localdir, HIGHSCORE_DIR);
298 #ifdef WIN32
299  mkdir(dirname);
300 #else
301  mkdir(dirname,0755);
302 #endif
303  memset(hiscore_tables,0,sizeof(hiscore_tables));
304  for (int i =- 1; i < MAX_SKILLS; ++i) {
305  const char *name;
306  int subtype;
307 
308  /*
309  * This gets complicated because the skills are defined internally by the subtype in
310  * the skill object, but our list of skill names is in the order the skills are
311  * initialized in.
312  */
313  if (i == -1) {
314  name = "Overall";
315  subtype = 0;
316  } else {
317  name = skill_names[i];
318  if (!name || !*name) continue; // No such skill
319  subtype = get_skill_client_code(name) + 1;
320  }
321  snprintf(hiscore_tables[subtype].fname, sizeof(hiscore_tables[subtype].fname), "%s/%s/%s", settings.localdir, HIGHSCORE_DIR,name);
322  for ( char *c = hiscore_tables[subtype].fname; *c; ++c ) {
323  if (*c == ' ')
324  *c = '_'; /* avoid spaces in file names */
325  }
326  strncpy(hiscore_tables[subtype].skill_name, name, sizeof(hiscore_tables[subtype].skill_name));
327  hiscore_load(&hiscore_tables[subtype]);
328  }
329  /* Load legacy highscore file if new one was blank */
330  if (hiscore_tables[0].entry[0].exp == 0) {
331  snprintf(hiscore_tables[0].fname, sizeof(hiscore_tables[0].fname), "%s/%s", settings.localdir, OLD_HIGHSCORE);
333  }
334 }
335 
346 void hiscore_check(object *op, int quiet) {
347  score new_score;
348  score old_score;
349  char bufscore[MAX_BUF];
350  const char *message;
351 
352  if (op->stats.exp == 0 || !op->contr->name_changed)
353  return;
354 
355  if (QUERY_FLAG(op, FLAG_WAS_WIZ)) {
356  if (!quiet)
358  "Since you have been in wizard mode, "
359  "you can't enter the high-score list.");
360  return;
361  }
362  if (!op->stats.exp) {
363  if (!quiet)
365  "You don't deserve to save your character yet.");
366  return;
367  }
368 
369  PROFILE_BEGIN();
370  strncpy(new_score.name, op->name, BIG_NAME);
371  new_score.name[BIG_NAME-1] = '\0';
372  player_get_title(op->contr, new_score.title, sizeof(new_score.title));
373  strncpy(new_score.killer, op->contr->killer, BIG_NAME);
374  if (new_score.killer[0] == '\0') {
375  strcpy(new_score.killer, "a dungeon collapse");
376  }
377  new_score.killer[BIG_NAME-1] = '\0';
378  if (op->map == NULL) {
379  *new_score.maplevel = '\0';
380  } else {
381  strncpy(new_score.maplevel, op->map->name ? op->map->name : op->map->path, BIG_NAME-1);
382  new_score.maplevel[BIG_NAME-1] = '\0';
383  }
384  new_score.maxhp = (int)op->stats.maxhp;
385  new_score.maxsp = (int)op->stats.maxsp;
386  new_score.maxgrace = (int)op->stats.maxgrace;
388  if (tmp->type != SKILL) continue;
389  if (!tmp->stats.exp) continue;
390  new_score.exp = tmp->stats.exp;
391  add_score(&hiscore_tables[get_skill_client_code(tmp->name) + 1], &new_score, &old_score);
392 #if 0
393  if (!quiet && new_score.exp > old_score.exp) {
395  "You improved your rating in %s: %" FMT64, tmp->name, new_score.exp);
396  if (old_score.position != -1)
398  draw_one_high_score(&old_score, bufscore, sizeof(bufscore)));
400  draw_one_high_score(&new_score, bufscore, sizeof(bufscore)));
401  }
402 #endif
403  } FOR_INV_FINISH();
404  new_score.exp = op->stats.exp;
405  add_score(&hiscore_tables[0], &new_score, &old_score); // overall
406  PROFILE_END(diff, LOG(llevDebug, "Wrote highscore files for %s (%d ms)\n", op->name, diff/1000));
407 
408  /* Everything below here is just related to print messages
409  * to the player. If quiet is set, we can just return
410  * now.
411  */
412  if (quiet)
413  return;
414 
415  if (old_score.position == -1) {
416  if (new_score.position > HIGHSCORE_LENGTH)
417  message = "You didn't enter the highscore list:";
418  else
419  message = "You entered the highscore list:";
420  } else {
421  if (new_score.position > HIGHSCORE_LENGTH)
422  message = "You left the highscore list:";
423  else if (new_score.exp > old_score.exp)
424  message = "You beat your last score:";
425  else
426  message = "You didn't beat your last score:";
427  }
428 
430  if (old_score.position != -1)
432  draw_one_high_score(&old_score, bufscore, sizeof(bufscore)));
434  draw_one_high_score(&new_score, bufscore, sizeof(bufscore)));
435 }
436 
449 void hiscore_display(object *op, int max, const char *match) {
450  int printed_entries;
451  size_t j;
452  int skill_match = 0;
453  int skill_min,skill_max;
454  int len;
455 
456  /* check for per-skill instead of overall report */
457  if (strncmp(match, "-s", 2) == 0 ) {
458  match += 2;
459  if (*match == ':') {
460  ++match;
461  if (strchr(match,' ')) {
462  len = strchr(match, ' ') - match;
463  } else {
464  len = strlen(match);
465  }
466  for (int i = 1; i < MAX_SKILLS; ++i) {
467  if (strncmp(match,hiscore_tables[i].skill_name, len) == 0) {
468  skill_match = i;
469  break;
470  }
471  }
472  if (!skill_match) {
474  "Could not match '%.*s' to a skill", len, match);
475  return;
476  }
477  match += len;
478  }
479  else {
480  skill_match = -1; // flag to show all skills
481  if ( max < 100 && max > 10 ) max = 10; // Less output per skill
482  }
483  }
484  while (*match == ' ') ++match;
485 
486  skill_min = skill_max = skill_match;
487  if (skill_match == -1) {
488  skill_min = 1;
489  skill_max = MAX_SKILLS;
490  }
491 
492  /*
493  * Check all skills in skill_names[] order (which should be alphabetical)
494  */
495  for (int s = -1; s <= MAX_SKILLS; ++s) {
496  int skill = s + 1;
497 
498  if (skill < skill_min || skill > skill_max) continue;
499 
500  if (hiscore_tables[skill].skill_name[0] == 0) {
501  continue; // No such skill
502  }
503  if (hiscore_tables[skill].entry[0].exp == 0) {
504  continue; // No entries for this skill
505  }
506  if (skill == 0) {
508  "Overall high scores:");
509  } else {
511  "High scores for the skill [color=red]%s[/color]:", hiscore_tables[skill].skill_name);
512  }
514  "[fixed]Rank Score [print]Who <max hp><max sp><max grace>");
515 
516  printed_entries = 0;
517  for (j = 0; j < HIGHSCORE_LENGTH && hiscore_tables[skill].entry[j].name[0] != '\0' && printed_entries < max; j++) {
518  char scorebuf[MAX_BUF];
519 
520  if (*match != '\0'
523  continue;
524 
525  draw_one_high_score(&hiscore_tables[skill].entry[j], scorebuf, sizeof(scorebuf));
526  printed_entries++;
527 
528  if (op == NULL) {
529  LOG(llevDebug, "%s\n", scorebuf);
530  } else {
532  }
533  }
534  }
535 }
hiscore_check
void hiscore_check(object *op, int quiet)
Definition: hiscore.c:346
draw_one_high_score
static char * draw_one_high_score(const score *sc, char *buf, size_t size)
Definition: hiscore.c:156
scr::exp
int64_t exp
Definition: hiscore.c:37
output_file.h
global.h
hiscore_display
void hiscore_display(object *op, int max, const char *match)
Definition: hiscore.c:449
llevError
@ llevError
Definition: logger.h:11
strcasestr_local
#define strcasestr_local
Definition: compat.h:28
HIGHSCORE_LENGTH
#define HIGHSCORE_LENGTH
Definition: config.h:518
scr::maplevel
char maplevel[BIG_NAME]
Definition: hiscore.c:38
QUERY_FLAG
#define QUERY_FLAG(xyz, p)
Definition: define.h:226
get_skill_client_code
int get_skill_client_code(const char *skill_name)
Definition: skill_util.c:107
c
static event_registration c
Definition: citylife.cpp:427
entry
Definition: entry.py:1
scr::maxgrace
int maxgrace
Definition: hiscore.c:41
scr::position
int position
Definition: hiscore.c:42
SKILL
@ SKILL
Definition: object.h:143
HIGHSCORE_DIR
#define HIGHSCORE_DIR
Definition: config.h:511
PROFILE_BEGIN
#define PROFILE_BEGIN(expr)
Definition: global.h:363
Ice.tmp
int tmp
Definition: Ice.py:207
hiscore_save
static void hiscore_save(const score_table *table)
Definition: hiscore.c:79
MSG_TYPE_COMMAND_ERROR
#define MSG_TYPE_COMMAND_ERROR
Definition: newclient.h:529
MSG_TYPE_ADMIN_HISCORE
#define MSG_TYPE_ADMIN_HISCORE
Definition: newclient.h:498
MSG_TYPE_COMMAND
#define MSG_TYPE_COMMAND
Definition: newclient.h:404
settings
struct Settings settings
Definition: init.c:39
score
struct scr score
PROFILE_END
#define PROFILE_END(var, expr)
Definition: global.h:368
score_table::entry
score entry[HIGHSCORE_LENGTH]
Definition: hiscore.c:51
FMT64
#define FMT64
Definition: compat.h:16
titlestruct
Definition: readable.c:107
FLAG_WAS_WIZ
#define FLAG_WAS_WIZ
Definition: define.h:234
scr::killer
char killer[BIG_NAME]
Definition: hiscore.c:36
hiscore_load
static void hiscore_load(score_table *table)
Definition: hiscore.c:248
scr::maxsp
int maxsp
Definition: hiscore.c:40
get_score
static int get_score(char *bp, score *sc)
Definition: hiscore.c:111
of_close
int of_close(OutputFile *of)
Definition: output_file.c:61
scr
Definition: hiscore.c:33
skill_names
const char * skill_names[MAX_SKILLS]
Definition: skill_util.c:59
FOR_INV_FINISH
#define FOR_INV_FINISH()
Definition: define.h:677
say.max
dictionary max
Definition: say.py:148
push.match
bool match
Definition: push.py:61
sproto.h
MAX_SKILLS
#define MAX_SKILLS
Definition: skills.h:70
put_score
static void put_score(const score *sc, char *buf, size_t size)
Definition: hiscore.c:69
hiscore_init
void hiscore_init(void)
Definition: hiscore.c:294
MAX_BUF
#define MAX_BUF
Definition: define.h:35
split_string
size_t split_string(char *str, char *array[], size_t array_size, char sep)
Definition: utils.c:483
scr::maxhp
int maxhp
Definition: hiscore.c:39
diamondslots.message
string message
Definition: diamondslots.py:57
score_table
Definition: hiscore.c:48
NDI_UNIQUE
#define NDI_UNIQUE
Definition: newclient.h:262
LOG
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.c:51
OLD_HIGHSCORE
#define OLD_HIGHSCORE
Definition: config.h:510
of_open
FILE * of_open(OutputFile *of, const char *fname)
Definition: output_file.c:30
give.op
op
Definition: give.py:33
buf
StringBuffer * buf
Definition: readable.c:1610
hiscore_tables
static score_table hiscore_tables[MAX_SKILLS+1]
Definition: hiscore.c:57
scr::title
char title[BIG_NAME]
Definition: hiscore.c:35
make_face_from_files.int
int
Definition: make_face_from_files.py:26
draw_ext_info
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Definition: main.c:309
add_score
static void add_score(score_table *table, score *new_score, score *old_score)
Definition: hiscore.c:186
score_table::fname
char fname[MAX_BUF]
Definition: hiscore.c:49
BIG_NAME
#define BIG_NAME
Definition: define.h:42
player_get_title
void player_get_title(const struct pl *pl, char *buf, size_t bufsize)
Definition: player.c:235
scr::name
char name[BIG_NAME]
Definition: hiscore.c:34
MSG_TYPE_APPLY
#define MSG_TYPE_APPLY
Definition: newclient.h:408
MSG_TYPE_APPLY_ERROR
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:601
MSG_TYPE_ADMIN
#define MSG_TYPE_ADMIN
Definition: newclient.h:402
FOR_INV_PREPARE
#define FOR_INV_PREPARE(op_, it_)
Definition: define.h:670
draw_ext_info_format
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...)
Definition: main.c:319
llevDebug
@ llevDebug
Definition: logger.h:13
give.name
name
Definition: give.py:27
OutputFile
Definition: output_file.h:41
Settings::localdir
const char * localdir
Definition: global.h:246