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:51
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
Settings::localdir
const char * localdir
Definition: global.h:249
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
Ice.tmp
int tmp
Definition: Ice.py:207
PROFILE_BEGIN
#define PROFILE_BEGIN(expr)
Definition: global.h:372
MSG_TYPE_COMMAND_ERROR
#define MSG_TYPE_COMMAND_ERROR
Definition: newclient.h:518
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:487
buf
StringBuffer * buf
Definition: readable.cpp:1552
MSG_TYPE_COMMAND
#define MSG_TYPE_COMMAND
Definition: newclient.h:393
name
Plugin animator file specs[Config] name
Definition: animfiles.txt:4
draw_ext_info
vs only yadda is in because all tags get reset on the next draw_ext_info In the second since it is all in one draw_ext_info
Definition: media-tags.txt:61
score::maxsp
int maxsp
Definition: hiscore.cpp:42
PROFILE_END
#define PROFILE_END(var, expr)
Definition: global.h:377
score_table::entry
score entry[HIGHSCORE_LENGTH]
Definition: hiscore.cpp:53
FMT64
#define FMT64
Definition: compat.h:16
player::killer
char killer[BIG_NAME]
Definition: player.h:190
FLAG_WAS_WIZ
#define FLAG_WAS_WIZ
Definition: define.h:234
score::maxgrace
int maxgrace
Definition: hiscore.cpp:43
sc
Player Stats effect how well a character can survie and interact inside the crossfire world This section discusses the various what they and how they effect the player s actions Also in this section are the stat modifiers that specific classes professions bring Player and sps the current and maximum the Current and Maximum The Current Sp can go somewhat negative When Sp is negative not all spells can be and a more negative Sp makes spell casting less likey to succeed can affect Damage and how the characters as well as how often the character can attack this affects the prices when buying and selling items if this drops the player will start losing hit points wd Cleric or Dwarf sm Elf wd Fireborn sc
Definition: stats.txt:96
of
a copper bar weighs and has a value of
Definition: ore.txt:3
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
message
TIPS on SURVIVING Crossfire is populated with a wealth of different monsters These monsters can have varying immunities and attack types In some of them can be quite a bit smarter than others It will be important for new players to learn the abilities of different monsters and learn just how much it will take to kill them This section discusses how monsters can interact with players Most monsters in the game are out to mindlessly kill and destroy the players These monsters will help boost a player s after he kills them When fighting a large amount of monsters in a single attempt to find a narrower hallway so that you are not being attacked from all sides Charging into a room full of Beholders for instance would not be open the door and fight them one at a time For there are several maps designed for them Find these areas and clear them out All throughout these a player can find signs and books which they can read by stepping onto them and hitting A to apply the book sign These messages will help the player to learn the system One more always keep an eye on your food If your food drops to your character will soon so BE CAREFUL ! NPCs Non Player Character are special monsters which have intelligence Players may be able to interact with these monsters to help solve puzzles and find items of interest To speak with a monster you suspect to be a simply move to an adjacent square to them and push the double ie Enter your message
Definition: survival-guide.txt:34
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
player::maplevel
char maplevel[MAX_BUF]
Definition: player.h:109
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
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:251
OLD_HIGHSCORE
#define OLD_HIGHSCORE
Definition: config.h:518
give.op
op
Definition: give.py:33
score::position
int position
Definition: hiscore.cpp:44
OutputFile
Definition: output_file.h:41
score::exp
int64_t exp
Definition: hiscore.cpp:39
hiscore_init
void hiscore_init(void)
Definition: hiscore.cpp:296
split_string
size_t split_string(char *str, char *array[], size_t array_size, char sep)
Definition: utils.cpp:473
make_face_from_files.int
int
Definition: make_face_from_files.py:32
score::killer
char killer[BIG_NAME]
Definition: hiscore.cpp:38
skill
skill
Definition: arch-handbook.txt:585
hiscore_check
void hiscore_check(object *op, int quiet)
Definition: hiscore.cpp:348
score::name
char name[BIG_NAME]
Definition: hiscore.cpp:36
player::title
char title[BIG_NAME]
Definition: player.h:184
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:397
MSG_TYPE_APPLY_ERROR
#define MSG_TYPE_APPLY_ERROR
Definition: newclient.h:590
MSG_TYPE_ADMIN
#define MSG_TYPE_ADMIN
Definition: newclient.h:391
FOR_INV_PREPARE
#define FOR_INV_PREPARE(op_, it_)
Definition: define.h:670
llevDebug
@ llevDebug
Definition: logger.h:13
player_get_title
void player_get_title(const player *pl, char *buf, size_t bufsize)
Definition: player.cpp:232