Crossfire Server, Trunk
hiscore.cpp
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 #ifndef _GNU_SOURCE
19 #define _GNU_SOURCE // strcasestr() is a GNU extension in string.h
20 #endif
21 
22 #include "global.h"
23 
24 #include <errno.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 
29 #include "sproto.h"
30 #include "output_file.h"
31 
35 struct score {
36  char name[BIG_NAME];
37  char title[BIG_NAME];
38  char killer[BIG_NAME];
39  int64_t exp;
41  int maxhp,
44  int position;
45 };
46 
50 struct score_table {
51  char fname[MAX_BUF];
54 };
55 
59 static score_table hiscore_tables[MAX_SKILLS + 1]; // One for each skill, plus one for overall
60 
71 static void put_score(const score *sc, char *buf, size_t size) {
72  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);
73 }
74 
81 static void hiscore_save(const score_table *table) {
82  FILE *fp;
83  OutputFile of;
84  size_t i;
85  char buf[MAX_BUF];
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  PROFILE_BEGIN();
372  strncpy(new_score.name, op->name, BIG_NAME);
373  new_score.name[BIG_NAME-1] = '\0';
374  player_get_title(op->contr, new_score.title, sizeof(new_score.title));
375  strncpy(new_score.killer, op->contr->killer, BIG_NAME);
376  if (new_score.killer[0] == '\0') {
377  strcpy(new_score.killer, "a dungeon collapse");
378  }
379  new_score.killer[BIG_NAME-1] = '\0';
380  if (op->map == NULL) {
381  *new_score.maplevel = '\0';
382  } else {
383  strncpy(new_score.maplevel, op->map->name ? op->map->name : op->map->path, BIG_NAME-1);
384  new_score.maplevel[BIG_NAME-1] = '\0';
385  }
386  new_score.maxhp = (int)op->stats.maxhp;
387  new_score.maxsp = (int)op->stats.maxsp;
388  new_score.maxgrace = (int)op->stats.maxgrace;
390  if (tmp->type != SKILL) continue;
391  if (!tmp->stats.exp) continue;
392  new_score.exp = tmp->stats.exp;
393  add_score(&hiscore_tables[get_skill_client_code(tmp->name) + 1], &new_score, &old_score);
394 #if 0
395  if (!quiet && new_score.exp > old_score.exp) {
397  "You improved your rating in %s: %" FMT64, tmp->name, new_score.exp);
398  if (old_score.position != -1)
400  draw_one_high_score(&old_score, bufscore, sizeof(bufscore)));
402  draw_one_high_score(&new_score, bufscore, sizeof(bufscore)));
403  }
404 #endif
405  } FOR_INV_FINISH();
406  new_score.exp = op->stats.exp;
407  add_score(&hiscore_tables[0], &new_score, &old_score); // overall
408  PROFILE_END(diff, LOG(llevDebug, "Wrote highscore files for %s (%ld ms)\n", op->name, diff/1000));
409 
410  /* Everything below here is just related to print messages
411  * to the player. If quiet is set, we can just return
412  * now.
413  */
414  if (quiet)
415  return;
416 
417  if (old_score.position == -1) {
418  if (new_score.position > HIGHSCORE_LENGTH)
419  message = "You didn't enter the highscore list:";
420  else
421  message = "You entered the highscore list:";
422  } else {
423  if (new_score.position > HIGHSCORE_LENGTH)
424  message = "You left the highscore list:";
425  else if (new_score.exp > old_score.exp)
426  message = "You beat your last score:";
427  else
428  message = "You didn't beat your last score:";
429  }
430 
432  if (old_score.position != -1)
434  draw_one_high_score(&old_score, bufscore, sizeof(bufscore)));
436  draw_one_high_score(&new_score, bufscore, sizeof(bufscore)));
437 }
438 
451 void hiscore_display(object *op, int max, const char *match) {
452  int printed_entries;
453  size_t j;
454  int skill_match = 0;
455  int skill_min,skill_max;
456  int len;
457 
458  /* check for per-skill instead of overall report */
459  if (strncmp(match, "-s", 2) == 0 ) {
460  match += 2;
461  if (*match == ':') {
462  ++match;
463  if (strchr(match,' ')) {
464  len = strchr(match, ' ') - match;
465  } else {
466  len = strlen(match);
467  }
468  for (int i = 1; i < MAX_SKILLS; ++i) {
469  if (strncmp(match,hiscore_tables[i].skill_name, len) == 0) {
470  skill_match = i;
471  break;
472  }
473  }
474  if (!skill_match) {
476  "Could not match '%.*s' to a skill", len, match);
477  return;
478  }
479  match += len;
480  }
481  else {
482  skill_match = -1; // flag to show all skills
483  if ( max < 100 && max > 10 ) max = 10; // Less output per skill
484  }
485  }
486  while (*match == ' ') ++match;
487 
488  skill_min = skill_max = skill_match;
489  if (skill_match == -1) {
490  skill_min = 1;
491  skill_max = MAX_SKILLS;
492  }
493 
494  /*
495  * Check all skills in skill_names[] order (which should be alphabetical)
496  */
497  for (int s = -1; s <= MAX_SKILLS; ++s) {
498  int skill = s + 1;
499 
500  if (skill < skill_min || skill > skill_max) continue;
501 
502  if (hiscore_tables[skill].skill_name[0] == 0) {
503  continue; // No such skill
504  }
505  if (hiscore_tables[skill].entry[0].exp == 0) {
506  continue; // No entries for this skill
507  }
508  if (skill == 0) {
510  "Overall high scores:");
511  } else {
513  "High scores for the skill [color=red]%s[/color]:", hiscore_tables[skill].skill_name);
514  }
516  "[fixed]Rank Score [print]Who <max hp><max sp><max grace>");
517 
518  printed_entries = 0;
519  for (j = 0; j < HIGHSCORE_LENGTH && hiscore_tables[skill].entry[j].name[0] != '\0' && printed_entries < max; j++) {
520  char scorebuf[MAX_BUF];
521 
522  if (*match != '\0'
525  continue;
526 
527  draw_one_high_score(&hiscore_tables[skill].entry[j], scorebuf, sizeof(scorebuf));
528  printed_entries++;
529 
530  if (op == NULL) {
531  LOG(llevDebug, "%s\n", scorebuf);
532  } else {
534  }
535  }
536  }
537 }
hiscore_load
static void hiscore_load(score_table *table)
Definition: hiscore.cpp:250
draw_one_high_score
static char * draw_one_high_score(const score *sc, char *buf, size_t size)
Definition: hiscore.cpp:158
output_file.h
global.h
settings
struct Settings settings
Definition: init.cpp:139
llevError
@ llevError
Definition: logger.h:11
LOG
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.cpp:58
strcasestr_local
#define strcasestr_local
Definition: compat.h:28
of_close
int of_close(OutputFile *of)
Definition: output_file.cpp:61
of_open
FILE * of_open(OutputFile *of, const char *fname)
Definition: output_file.cpp:30
HIGHSCORE_LENGTH
#define HIGHSCORE_LENGTH
Definition: config.h:526
QUERY_FLAG
#define QUERY_FLAG(xyz, p)
Definition: define.h:226
score::title
char title[BIG_NAME]
Definition: hiscore.cpp:37
get_skill_client_code
int get_skill_client_code(const char *skill_name)
Definition: skill_util.cpp:116
c
static event_registration c
Definition: citylife.cpp:425
entry
Definition: entry.py:1
add_score
static void add_score(score_table *table, score *new_score, score *old_score)
Definition: hiscore.cpp:188
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,...) PRINTF_ARGS(6
SKILL
@ SKILL
Definition: object.h:148
HIGHSCORE_DIR
#define HIGHSCORE_DIR
Definition: config.h:519
PROFILE_BEGIN
#define PROFILE_BEGIN(expr)
Definition: global.h:373
Ice.tmp
int tmp
Definition: Ice.py:207
MSG_TYPE_COMMAND_ERROR
#define MSG_TYPE_COMMAND_ERROR
Definition: newclient.h:532
hiscore_tables
static score_table hiscore_tables[MAX_SKILLS+1]
Definition: hiscore.cpp:59
MSG_TYPE_ADMIN_HISCORE
#define MSG_TYPE_ADMIN_HISCORE
Definition: newclient.h:501
buf
StringBuffer * buf
Definition: readable.cpp:1565
MSG_TYPE_COMMAND
#define MSG_TYPE_COMMAND
Definition: newclient.h:407
score::maxsp
int maxsp
Definition: hiscore.cpp:42
PROFILE_END
#define PROFILE_END(var, expr)
Definition: global.h:378
score_table::entry
score entry[HIGHSCORE_LENGTH]
Definition: hiscore.cpp:53
FMT64
#define FMT64
Definition: compat.h:16
FLAG_WAS_WIZ
#define FLAG_WAS_WIZ
Definition: define.h:234
score::maxgrace
int maxgrace
Definition: hiscore.cpp:43
split_string
size_t split_string(char *str, char *array[], size_t array_size, char sep)
Definition: utils.cpp:473
score::maxhp
int maxhp
Definition: hiscore.cpp:41
hiscore_save
static void hiscore_save(const score_table *table)
Definition: hiscore.cpp:81
get_score
static int get_score(char *bp, score *sc)
Definition: hiscore.cpp:113
skill_names
const char * skill_names[MAX_SKILLS]
Definition: skill_util.cpp:59
title
Definition: readable.cpp:108
FOR_INV_FINISH
#define FOR_INV_FINISH()
Definition: define.h:677
score_table::skill_name
char skill_name[MAX_BUF]
Definition: hiscore.cpp:52
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
MAX_BUF
#define MAX_BUF
Definition: define.h:35
hiscore_display
void hiscore_display(object *op, int max, const char *match)
Definition: hiscore.cpp:451
score::maplevel
char maplevel[BIG_NAME]
Definition: hiscore.cpp:40
score
Definition: hiscore.cpp:35
diamondslots.message
string message
Definition: diamondslots.py:57
score_table
Definition: hiscore.cpp:50
put_score
static void put_score(const score *sc, char *buf, size_t size)
Definition: hiscore.cpp:71
NDI_UNIQUE
#define NDI_UNIQUE
Definition: newclient.h:265
OLD_HIGHSCORE
#define OLD_HIGHSCORE
Definition: config.h:518
give.op
op
Definition: give.py:33
score::position
int position
Definition: hiscore.cpp:44
score::exp
int64_t exp
Definition: hiscore.cpp:39
hiscore_init
void hiscore_init(void)
Definition: hiscore.cpp:296
make_face_from_files.int
int
Definition: make_face_from_files.py:32
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.cpp:308
score::killer
char killer[BIG_NAME]
Definition: hiscore.cpp:38
hiscore_check
void hiscore_check(object *op, int quiet)
Definition: hiscore.cpp:348
score::name
char name[BIG_NAME]
Definition: hiscore.cpp:36
score_table::fname
char fname[MAX_BUF]
Definition: hiscore.cpp:51
BIG_NAME
#define BIG_NAME
Definition: define.h:42
MSG_TYPE_APPLY
#define MSG_TYPE_APPLY
Definition: newclient.h:411
MSG_TYPE_APPLY_ERROR
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:604
MSG_TYPE_ADMIN
#define MSG_TYPE_ADMIN
Definition: newclient.h:405
FOR_INV_PREPARE
#define FOR_INV_PREPARE(op_, it_)
Definition: define.h:670
llevDebug
@ llevDebug
Definition: logger.h:13
give.name
name
Definition: give.py:27
OutputFile
Definition: output_file.h:41
player_get_title
void player_get_title(const player *pl, char *buf, size_t bufsize)
Definition: player.cpp:232
Settings::localdir
const char * localdir
Definition: global.h:249