Crossfire Server  1.75.0
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,
42  maxsp,
43  maxgrace;
44  int position;
45 };
46 
50 struct score_table {
51  char fname[MAX_BUF];
52  char skill_name[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);
334  hiscore_load(&hiscore_tables[0]);
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;
389  FOR_INV_PREPARE(op, tmp) {
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'
523  && !strcasestr_local(hiscore_tables[skill].entry[j].name, match)
524  && !strcasestr_local(hiscore_tables[skill].entry[j].title, match))
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 }
Error, serious thing.
Definition: logger.h:11
static event_registration c
Definition: citylife.cpp:424
static void add_score(score_table *table, score *new_score, score *old_score)
Adds the given score-structure to the high-score list, but only if it was good enough to deserve a pl...
Definition: hiscore.cpp:188
const char * skill_names[MAX_SKILLS]
Will contain a number-name mapping for skills, initialized by init_skills().
Definition: skill_util.cpp:57
#define PROFILE_BEGIN(expr)
Definition: global.h:364
void LOG(LogLevel logLevel, const char *format,...)
Logs a message to stderr, or to file.
Definition: logger.cpp:58
char title[BIG_NAME]
Title.
Definition: hiscore.cpp:37
void draw_ext_info_format(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *format,...) PRINTF_ARGS(6
#define BIG_NAME
Definition: define.h:42
int16_t maxgrace
Maximum grace.
Definition: living.h:45
char fname[MAX_BUF]
Filename of the backing file.
Definition: hiscore.cpp:51
#define MSG_TYPE_ADMIN_HISCORE
Hiscore list.
Definition: newclient.h:517
char name[BIG_NAME]
Name.
Definition: hiscore.cpp:36
void hiscore_display(object *op, int max, const char *match)
Displays the high score file.
Definition: hiscore.cpp:451
int position
Position in the highscore list.
Definition: hiscore.cpp:44
#define PROFILE_END(var, expr)
Definition: global.h:369
Global type definitions and header inclusions.
struct player * contr
Pointer to the player which control this object.
Definition: object.h:284
void draw_ext_info(int flags, int pri, const object *pl, uint8_t type, uint8_t subtype, const char *message)
Sends message to player(s).
Definition: main.cpp:308
#define MSG_TYPE_APPLY
Applying objects.
Definition: newclient.h:425
A highscore table.
Definition: hiscore.cpp:50
#define OLD_HIGHSCORE
Definition: config.h:518
#define strcasestr_local
Definition: compat.h:28
size_t split_string(char *str, char *array[], size_t array_size, char sep)
Splits a string delimited by passed in sep value into characters into an array of strings...
Definition: utils.cpp:473
#define MSG_TYPE_COMMAND
Responses to commands, eg, who.
Definition: newclient.h:420
int get_skill_client_code(const char *skill_name)
Return the code of the skill for a client, the index in the skill_names array.
Definition: skill_util.cpp:114
FILE * of_open(OutputFile *of, const char *fname)
Opens an output file.
Definition: output_file.cpp:30
static score_table hiscore_tables[MAX_SKILLS+1]
The highscore table.
Definition: hiscore.cpp:59
char killer[BIG_NAME]
Name (+ title) or "left".
Definition: hiscore.cpp:38
#define MSG_TYPE_COMMAND_ERROR
Bad syntax/can&#39;t use command.
Definition: newclient.h:550
char path[HUGE_BUF]
Filename of the map.
Definition: map.h:360
#define QUERY_FLAG(xyz, p)
Definition: define.h:386
Information on one title.
Definition: readable.cpp:108
void player_get_title(const player *pl, char *buf, size_t bufsize)
Returns the player&#39;s title.
Definition: player.cpp:233
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:637
#define FOR_INV_FINISH()
Finishes FOR_INV_PREPARE().
Definition: define.h:700
#define FMT64
Definition: compat.h:16
static void hiscore_save(const score_table *table)
Saves the highscore_table into the highscore file.
Definition: hiscore.cpp:81
static char * draw_one_high_score(const score *sc, char *buf, size_t size)
Formats one score to display to a player.
Definition: hiscore.cpp:158
static void put_score(const score *sc, char *buf, size_t size)
Writes the given score structure to specified buffer.
Definition: hiscore.cpp:71
char killer[BIG_NAME]
Who killed this player.
Definition: player.h:192
The score structure is used when treating new high-scores.
Definition: hiscore.cpp:35
struct Settings settings
Global settings.
Definition: init.cpp:139
score entry[HIGHSCORE_LENGTH]
The entries in decreasing exp order.
Definition: hiscore.cpp:53
living stats
Str, Con, Dex, etc.
Definition: object.h:378
#define MAX_BUF
Used for all kinds of things.
Definition: define.h:35
#define MSG_TYPE_ADMIN
Definition: newclient.h:418
int64_t exp
Experience.
Definition: hiscore.cpp:39
struct mapstruct * map
Pointer to the map in which this object is present.
Definition: object.h:305
char maplevel[BIG_NAME]
Killed on what level.
Definition: hiscore.cpp:40
Also see SKILL_TOOL (74) below.
Definition: object.h:148
uint32_t name_changed
If true, the player has set a name.
Definition: player.h:147
const char * localdir
Read/write data files.
Definition: global.h:250
#define HIGHSCORE_LENGTH
How many entries there are room for.
Definition: config.h:526
sstring name
The name of the object, obviously...
Definition: object.h:319
int maxgrace
Max grace when killed.
Definition: hiscore.cpp:41
Only for debugging purposes.
Definition: logger.h:13
int of_close(OutputFile *of)
Closes an output file.
Definition: output_file.cpp:61
static void hiscore_load(score_table *table)
Loads the hiscore_table from the highscore file.
Definition: hiscore.cpp:250
int maxsp
Max sp when killed.
Definition: hiscore.cpp:41
void hiscore_check(object *op, int quiet)
Checks if player should enter the hiscore, and if so writes her into the list.
Definition: hiscore.cpp:348
Functions for creating text output files.
int64_t exp
Experience.
Definition: living.h:47
int16_t maxsp
Max spell points.
Definition: living.h:43
#define NDI_UNIQUE
Print immediately, don&#39;t buffer.
Definition: newclient.h:270
#define FLAG_WAS_WIZ
Player was once a wiz.
Definition: define.h:221
StringBuffer * buf
Definition: readable.cpp:1563
int16_t maxhp
Max hit points.
Definition: living.h:41
void hiscore_init(void)
Initializes the module.
Definition: hiscore.cpp:296
#define MAX_SKILLS
This is the maximum number of skills the game may handle.
Definition: skills.h:70
static int get_score(char *bp, score *sc)
The opposite of put_score(), get_score reads from the given buffer into a static score structure...
Definition: hiscore.cpp:113
#define HIGHSCORE_DIR
Definition: config.h:519
int maxhp
Max hp when killed.
Definition: hiscore.cpp:41
#define FOR_INV_PREPARE(op_, it_)
Constructs a loop iterating over the inventory of an object.
Definition: define.h:693
char * name
Name of map as given by its creator.
Definition: map.h:323