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  LOG(llevDebug, "Writing highscore files %s\n", table->fname);
86 
87  fp = of_open(&of, table->fname);
88  if (fp == NULL)
89  return;
90 
91  for (i = 0; i < HIGHSCORE_LENGTH; i++) {
92  if (table->entry[i].name[0] == '\0')
93  break;
94 
95  put_score(&table->entry[i], buf, sizeof(buf));
96  fprintf(fp, "%s\n", buf);
97  }
98  of_close(&of);
99 }
100 
113 static int get_score(char *bp, score *sc) {
114  char *cp;
115  char *tmp[8];
116 
117  cp = strchr(bp, '\n');
118  if (cp != NULL)
119  *cp = '\0';
120 
121  if (split_string(bp, tmp, 8, ':') != 8)
122  return 0;
123 
124  strncpy(sc->name, tmp[0], BIG_NAME);
125  sc->name[BIG_NAME-1] = '\0';
126 
127  strncpy(sc->title, tmp[1], BIG_NAME);
128  sc->title[BIG_NAME-1] = '\0';
129 
130  sscanf(tmp[2], "%"FMT64, &sc->exp);
131 
132  strncpy(sc->killer, tmp[3], BIG_NAME);
133  sc->killer[BIG_NAME-1] = '\0';
134 
135  strncpy(sc->maplevel, tmp[4], BIG_NAME);
136  sc->maplevel[BIG_NAME-1] = '\0';
137 
138  sscanf(tmp[5], "%d", &sc->maxhp);
139 
140  sscanf(tmp[6], "%d", &sc->maxsp);
141 
142  sscanf(tmp[7], "%d", &sc->maxgrace);
143  return 1;
144 }
145 
158 static char *draw_one_high_score(const score *sc, char *buf, size_t size) {
159  const char *s1;
160  const char *s2;
161 
162  if (strcmp(sc->killer, "quit") == 0 || strcmp(sc->killer, "left") == 0) {
163  s1 = sc->killer;
164  s2 = "the game";
165  } else if (strcmp(sc->killer,"a dungeon collapse") == 0) {
166  s1 = "was last";
167  s2 = "seen";
168  } else {
169  s1 = "was killed by";
170  s2 = sc->killer;
171  }
172  snprintf(buf, size, "[fixed]%3d %10"FMT64"[print] %s%s%s %s %s on map %s <%d><%d><%d>.",
173  sc->position, sc->exp, sc->name, sc->title[0]==',' ? "" : " ", sc->title, s1, s2, sc->maplevel, sc->maxhp, sc->maxsp, sc->maxgrace);
174  return buf;
175 }
176 
188 static void add_score(score_table *table, score *new_score, score *old_score) {
189  size_t i;
190 
191  new_score->position = HIGHSCORE_LENGTH+1;
192  memset(old_score, 0, sizeof(*old_score));
193  old_score->position = -1;
194 
195  /* find existing entry by name */
196  for (i = 0; i < HIGHSCORE_LENGTH; i++) {
197  if (table->entry[i].name[0] == '\0') {
198  table->entry[i] = *new_score;
199  table->entry[i].position = i+1;
200  break;
201  }
202  if (strcmp(new_score->name, table->entry[i].name) == 0) {
203  *old_score = table->entry[i];
204  if (table->entry[i].exp <= new_score->exp) {
205  table->entry[i] = *new_score;
206  table->entry[i].position = i+1;
207  }
208  break;
209  }
210  }
211 
212  if (i >= HIGHSCORE_LENGTH) {
213  /* entry for unknown name */
214 
215  if (new_score->exp < table->entry[i-1].exp) {
216  /* new exp is less than lowest hiscore entry => drop */
217  return;
218  }
219 
220  /* new exp is not less than lowest hiscore entry => add */
221  i--;
222  table->entry[i] = *new_score;
223  table->entry[i].position = i+1;
224  }
225 
226  /* move entry to correct position */
227  while (i > 0 && new_score->exp >= table->entry[i-1].exp) {
228  score tmp;
229 
230  tmp = table->entry[i-1];
231  table->entry[i-1] = table->entry[i];
232  table->entry[i] = tmp;
233 
234  table->entry[i-1].position = i;
235  table->entry[i].position = i+1;
236 
237  i--;
238  }
239 
240  new_score->position = table->entry[i].position;
241  hiscore_save(table);
242 }
243 
250 static void hiscore_load(score_table *table) {
251  FILE *fp;
252  size_t i;
253 
254  i = 0;
255 
256  fp = fopen(table->fname, "r");
257  if (fp == NULL) {
258  if (errno == ENOENT) {
259  LOG(llevDebug, "Highscore file %s does not exist\n", table->fname);
260  } else {
261  LOG(llevError, "Cannot open highscore file %s: %s\n", table->fname, strerror(errno));
262  }
263  } else {
264  LOG(llevDebug, "Reading highscore file %s\n", table->fname);
265  while (i < HIGHSCORE_LENGTH) {
266  char buf[MAX_BUF];
267 
268  if (fgets(buf, MAX_BUF, fp) == NULL) {
269  break;
270  }
271 
272  if (!get_score(buf, &table->entry[i]))
273  break;
274  table->entry[i].position = i+1;
275  i++;
276  }
277 
278  fclose(fp);
279  }
280 
281  while (i < HIGHSCORE_LENGTH) {
282  memset(&table->entry[i], 0, sizeof(table->entry[i]));
283  // This cannot be ++i due the right-to-left association of assignment.
284  table->entry[i].position = i + 1;
285  i++;
286  }
287 }
288 
296 void hiscore_init(void) {
297  char dirname[MAX_BUF];
298 
299  snprintf(dirname, sizeof(dirname), "%s/%s", settings.localdir, HIGHSCORE_DIR);
300 #ifdef WIN32
301  mkdir(dirname);
302 #else
303  mkdir(dirname,0755);
304 #endif
305  memset(hiscore_tables,0,sizeof(hiscore_tables));
306  for (int i =- 1; i < MAX_SKILLS; ++i) {
307  const char *name;
308  int subtype;
309 
310  /*
311  * This gets complicated because the skills are defined internally by the subtype in
312  * the skill object, but our list of skill names is in the order the skills are
313  * initialized in.
314  */
315  if (i == -1) {
316  name = "Overall";
317  subtype = 0;
318  } else {
319  name = skill_names[i];
320  if (!name || !*name) continue; // No such skill
321  subtype = get_skill_client_code(name) + 1;
322  }
323  snprintf(hiscore_tables[subtype].fname, sizeof(hiscore_tables[subtype].fname), "%s/%s/%s", settings.localdir, HIGHSCORE_DIR,name);
324  for ( char *c = hiscore_tables[subtype].fname; *c; ++c ) {
325  if (*c == ' ')
326  *c = '_'; /* avoid spaces in file names */
327  }
328  strncpy(hiscore_tables[subtype].skill_name, name, sizeof(hiscore_tables[subtype].skill_name));
329  hiscore_load(&hiscore_tables[subtype]);
330  }
331  /* Load legacy highscore file if new one was blank */
332  if (hiscore_tables[0].entry[0].exp == 0) {
333  snprintf(hiscore_tables[0].fname, sizeof(hiscore_tables[0].fname), "%s/%s", settings.localdir, OLD_HIGHSCORE);
335  }
336 }
337 
348 void hiscore_check(object *op, int quiet) {
349  score new_score;
350  score old_score;
351  char bufscore[MAX_BUF];
352  const char *message;
353 
354  if (op->stats.exp == 0 || !op->contr->name_changed)
355  return;
356 
357  if (QUERY_FLAG(op, FLAG_WAS_WIZ)) {
358  if (!quiet)
360  "Since you have been in wizard mode, "
361  "you can't enter the high-score list.");
362  return;
363  }
364  if (!op->stats.exp) {
365  if (!quiet)
367  "You don't deserve to save your character yet.");
368  return;
369  }
370 
371  strncpy(new_score.name, op->name, BIG_NAME);
372  new_score.name[BIG_NAME-1] = '\0';
373  player_get_title(op->contr, new_score.title, sizeof(new_score.title));
374  strncpy(new_score.killer, op->contr->killer, BIG_NAME);
375  if (new_score.killer[0] == '\0') {
376  strcpy(new_score.killer, "a dungeon collapse");
377  }
378  new_score.killer[BIG_NAME-1] = '\0';
379  if (op->map == NULL) {
380  *new_score.maplevel = '\0';
381  } else {
382  strncpy(new_score.maplevel, op->map->name ? op->map->name : op->map->path, BIG_NAME-1);
383  new_score.maplevel[BIG_NAME-1] = '\0';
384  }
385  new_score.maxhp = (int)op->stats.maxhp;
386  new_score.maxsp = (int)op->stats.maxsp;
387  new_score.maxgrace = (int)op->stats.maxgrace;
389  if (tmp->type != SKILL) continue;
390  if (!tmp->stats.exp) continue;
391  new_score.exp = tmp->stats.exp;
392  add_score(&hiscore_tables[get_skill_client_code(tmp->name) + 1], &new_score, &old_score);
393 #if 0
394  if (!quiet && new_score.exp > old_score.exp) {
396  "You improved your rating in %s: %" FMT64, tmp->name, new_score.exp);
397  if (old_score.position != -1)
399  draw_one_high_score(&old_score, bufscore, sizeof(bufscore)));
401  draw_one_high_score(&new_score, bufscore, sizeof(bufscore)));
402  }
403 #endif
404  } FOR_INV_FINISH();
405  new_score.exp = op->stats.exp;
406  add_score(&hiscore_tables[0], &new_score, &old_score); // overall
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:348
draw_one_high_score
static char * draw_one_high_score(const score *sc, char *buf, size_t size)
Definition: hiscore.c:158
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:425
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
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
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:250
scr::maxsp
int maxsp
Definition: hiscore.c:40
get_score
static int get_score(char *bp, score *sc)
Definition: hiscore.c:113
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:675
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:296
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:500
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:1606
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:188
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:668
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:245