Crossfire Server, Trunk  R22047
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];
39  int maxhp,
40  maxsp,
41  maxgrace;
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  mkdir(dirname,0755);
301  memset(hiscore_tables,0,sizeof(hiscore_tables));
302  for (int i =- 1; i < MAX_SKILLS; ++i) {
303  const char *name;
304  int subtype;
305 
306  /*
307  * This gets complicated because the skills are defined internally by the subtype in
308  * the skill object, but our list of skill names is in the order the skills are
309  * initialized in.
310  */
311  if (i == -1) {
312  name = "Overall";
313  subtype = 0;
314  } else {
315  name = skill_names[i];
316  if (!name || !*name) continue; // No such skill
317  subtype = get_skill_client_code(name) + 1;
318  }
319  snprintf(hiscore_tables[subtype].fname, sizeof(hiscore_tables[subtype].fname), "%s/%s/%s", settings.localdir, HIGHSCORE_DIR,name);
320  for ( char *c = hiscore_tables[subtype].fname; *c; ++c ) {
321  if (*c == ' ')
322  *c = '_'; /* avoid spaces in file names */
323  }
324  strncpy(hiscore_tables[subtype].skill_name, name, sizeof(hiscore_tables[subtype].skill_name));
325  hiscore_load(&hiscore_tables[subtype]);
326  }
327  /* Load legacy highscore file if new one was blank */
328  if (hiscore_tables[0].entry[0].exp == 0) {
329  snprintf(hiscore_tables[0].fname, sizeof(hiscore_tables[0].fname), "%s/%s", settings.localdir, OLD_HIGHSCORE);
330  hiscore_load(&hiscore_tables[0]);
331  }
332 }
333 
344 void hiscore_check(object *op, int quiet) {
345  score new_score;
346  score old_score;
347  char bufscore[MAX_BUF];
348  const char *message;
349 
350  if (op->stats.exp == 0 || !op->contr->name_changed)
351  return;
352 
353  if (QUERY_FLAG(op, FLAG_WAS_WIZ)) {
354  if (!quiet)
356  "Since you have been in wizard mode, "
357  "you can't enter the high-score list.");
358  return;
359  }
360  if (!op->stats.exp) {
361  if (!quiet)
363  "You don't deserve to save your character yet.");
364  return;
365  }
366 
367  strncpy(new_score.name, op->name, BIG_NAME);
368  new_score.name[BIG_NAME-1] = '\0';
369  player_get_title(op->contr, new_score.title, sizeof(new_score.title));
370  strncpy(new_score.killer, op->contr->killer, BIG_NAME);
371  if (new_score.killer[0] == '\0') {
372  strcpy(new_score.killer, "a dungeon collapse");
373  }
374  new_score.killer[BIG_NAME-1] = '\0';
375  if (op->map == NULL) {
376  *new_score.maplevel = '\0';
377  } else {
378  strncpy(new_score.maplevel, op->map->name ? op->map->name : op->map->path, BIG_NAME-1);
379  new_score.maplevel[BIG_NAME-1] = '\0';
380  }
381  new_score.maxhp = (int)op->stats.maxhp;
382  new_score.maxsp = (int)op->stats.maxsp;
383  new_score.maxgrace = (int)op->stats.maxgrace;
384  FOR_INV_PREPARE(op, tmp) {
385  if (tmp->type != SKILL) continue;
386  if (!tmp->stats.exp) continue;
387  new_score.exp = tmp->stats.exp;
388  add_score(&hiscore_tables[get_skill_client_code(tmp->name) + 1], &new_score, &old_score);
389 #if 0
390  if (!quiet && new_score.exp > old_score.exp) {
392  "You improved your rating in %s: %" FMT64, tmp->name, new_score.exp);
393  if (old_score.position != -1)
395  draw_one_high_score(&old_score, bufscore, sizeof(bufscore)));
397  draw_one_high_score(&new_score, bufscore, sizeof(bufscore)));
398  }
399 #endif
400  } FOR_INV_FINISH();
401  new_score.exp = op->stats.exp;
402  add_score(&hiscore_tables[0], &new_score, &old_score); // overall
403 
404  /* Everything below here is just related to print messages
405  * to the player. If quiet is set, we can just return
406  * now.
407  */
408  if (quiet)
409  return;
410 
411  if (old_score.position == -1) {
412  if (new_score.position > HIGHSCORE_LENGTH)
413  message = "You didn't enter the highscore list:";
414  else
415  message = "You entered the highscore list:";
416  } else {
417  if (new_score.position > HIGHSCORE_LENGTH)
418  message = "You left the highscore list:";
419  else if (new_score.exp > old_score.exp)
420  message = "You beat your last score:";
421  else
422  message = "You didn't beat your last score:";
423  }
424 
426  if (old_score.position != -1)
428  draw_one_high_score(&old_score, bufscore, sizeof(bufscore)));
430  draw_one_high_score(&new_score, bufscore, sizeof(bufscore)));
431 }
432 
445 void hiscore_display(object *op, int max, const char *match) {
446  int printed_entries;
447  size_t j;
448  int skill_match = 0;
449  int skill_min,skill_max;
450  int len;
451 
452  /* check for per-skill instead of overall report */
453  if (match && strncmp(match, "-s", 2) == 0 ) {
454  match += 2;
455  if (*match == ':') {
456  ++match;
457  if (strchr(match,' ')) {
458  len = strchr(match, ' ') - match;
459  } else {
460  len = strlen(match);
461  }
462  for (int i = 1; i < MAX_SKILLS; ++i) {
463  if (strncmp(match,hiscore_tables[i].skill_name, len) == 0) {
464  skill_match = i;
465  break;
466  }
467  }
468  if (!skill_match) {
470  "Could not match '%.*s' to a skill", len, match);
471  return;
472  }
473  match += len;
474  }
475  else {
476  skill_match = -1; // flag to show all skills
477  if ( max < 100 && max > 10 ) max = 10; // Less output per skill
478  }
479  }
480  while (*match == ' ') ++match;
481 
482  skill_min = skill_max = skill_match;
483  if (skill_match == -1) {
484  skill_min = 1;
485  skill_max = MAX_SKILLS;
486  }
487 
488  /*
489  * Check all skills in skill_names[] order (which should be alphabetical)
490  */
491  for (int s = -1; s <= MAX_SKILLS; ++s) {
492  int skill = s + 1;
493 
494  if (skill < skill_min || skill > skill_max) continue;
495 
496  if (hiscore_tables[skill].skill_name[0] == 0) {
497  continue; // No such skill
498  }
499  if (hiscore_tables[skill].entry[0].exp == 0) {
500  continue; // No entries for this skill
501  }
502  if (skill == 0) {
504  "Overall high scores:");
505  } else {
507  "High scores for the skill [color=red]%s[/color]:", hiscore_tables[skill].skill_name);
508  }
510  "[fixed]Rank Score [print]Who <max hp><max sp><max grace>");
511 
512  printed_entries = 0;
513  for (j = 0; j < HIGHSCORE_LENGTH && hiscore_tables[skill].entry[j].name[0] != '\0' && printed_entries < max; j++) {
514  char scorebuf[MAX_BUF];
515 
516  if (*match != '\0'
517  && !strcasestr_local(hiscore_tables[skill].entry[j].name, match)
518  && !strcasestr_local(hiscore_tables[skill].entry[j].title, match))
519  continue;
520 
521  draw_one_high_score(&hiscore_tables[skill].entry[j], scorebuf, sizeof(scorebuf));
522  printed_entries++;
523 
524  if (op == NULL) {
525  LOG(llevDebug, "%s\n", scorebuf);
526  } else {
528  }
529  }
530  }
531 }
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...)
Definition: main.c:313
char killer[BIG_NAME]
Definition: hiscore.c:36
const char * skill_names[MAX_SKILLS]
Definition: skill_util.c:59
void hiscore_display(object *op, int max, const char *match)
Definition: hiscore.c:445
static void hiscore_save(const score_table *table)
Definition: hiscore.c:79
StringBuffer * buf
Definition: readable.c:1591
void player_get_title(const struct pl *pl, char *buf, size_t bufsize)
Definition: player.c:224
#define BIG_NAME
Definition: define.h:42
char title[BIG_NAME]
Definition: hiscore.c:35
struct scr score
static char * draw_one_high_score(const score *sc, char *buf, size_t size)
Definition: hiscore.c:158
Definition: hiscore.c:33
char fname[MAX_BUF]
Definition: hiscore.c:49
#define MSG_TYPE_ADMIN_HISCORE
Definition: newclient.h:476
static int get_score(char *bp, score *sc)
Definition: hiscore.c:113
#define mkdir(__a, __b)
Definition: win32.h:57
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Definition: main.c:308
#define MSG_TYPE_APPLY
Definition: newclient.h:384
static void hiscore_load(score_table *table)
Definition: hiscore.c:250
#define OLD_HIGHSCORE
Definition: config.h:518
#define strcasestr_local
Definition: compat.h:24
#define MSG_TYPE_COMMAND
Definition: newclient.h:379
int get_skill_client_code(const char *skill_name)
Definition: skill_util.c:107
#define MSG_TYPE_COMMAND_ERROR
Definition: newclient.h:509
int position
Definition: hiscore.c:42
#define snprintf
Definition: win32.h:46
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:596
#define FOR_INV_FINISH()
Definition: define.h:714
#define FMT64
Definition: compat.h:12
int of_close(OutputFile *of)
Definition: output_file.c:61
int maxhp
Definition: hiscore.c:39
int maxsp
Definition: hiscore.c:39
score entry[HIGHSCORE_LENGTH]
Definition: hiscore.c:51
#define QUERY_FLAG(xyz, p)
Definition: define.h:225
static void put_score(const score *sc, char *buf, size_t size)
Definition: hiscore.c:69
static score_table hiscore_tables[MAX_SKILLS+1]
Definition: hiscore.c:57
#define MAX_BUF
Definition: define.h:35
#define MSG_TYPE_ADMIN
Definition: newclient.h:377
signed __int64 int64_t
Definition: win32.h:168
static void add_score(score_table *table, score *new_score, score *old_score)
Definition: hiscore.c:188
void hiscore_init(void)
Definition: hiscore.c:296
const char * localdir
Definition: global.h:246
#define HIGHSCORE_LENGTH
Definition: config.h:527
char maplevel[BIG_NAME]
Definition: hiscore.c:38
struct Settings settings
Definition: init.c:39
FILE * of_open(OutputFile *of, const char *fname)
Definition: output_file.c:30
int maxgrace
Definition: hiscore.c:39
size_t split_string(char *str, char *array[], size_t array_size, char sep)
Definition: utils.c:500
#define NDI_UNIQUE
Definition: newclient.h:245
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.c:51
char name[BIG_NAME]
Definition: hiscore.c:34
#define FLAG_WAS_WIZ
Definition: define.h:234
#define MAX_SKILLS
Definition: skills.h:70
#define HIGHSCORE_DIR
Definition: config.h:519
void hiscore_check(object *op, int quiet)
Definition: hiscore.c:344
#define FOR_INV_PREPARE(op_, it_)
Definition: define.h:707
int64_t exp
Definition: hiscore.c:37