Atrinik Server  4.0
hiscore.c
Go to the documentation of this file.
1 /*************************************************************************
2  * Atrinik, a Multiplayer Online Role Playing Game *
3  * *
4  * Copyright (C) 2009-2014 Alex Tokar and Atrinik Development Team *
5  * *
6  * Fork from Crossfire (Multiplayer game for X-windows). *
7  * *
8  * This program is free software; you can redistribute it and/or modify *
9  * it under the terms of the GNU General Public License as published by *
10  * the Free Software Foundation; either version 2 of the License, or *
11  * (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License *
19  * along with this program; if not, write to the Free Software *
20  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
21  * *
22  * The author can be reached at admin@atrinik.org *
23  ************************************************************************/
24 
30 #include <global.h>
31 #include <toolkit/string.h>
32 #include <player.h>
33 #include <object.h>
34 
38 typedef struct scr {
40  char name[MAX_BUF];
41 
43  char title[MAX_BUF];
44 
46  char killer[MAX_BUF];
47 
49  char maplevel[MAX_BUF];
50 
52  uint64_t exp;
53 
55  int maxhp, maxsp;
56 
58  int position;
59 } score;
60 
64 typedef struct {
66  char fname[MAX_BUF];
67 
70 } score_table;
71 
76 
86 static void put_score(const score *sc, char *buf, int size)
87 {
88  snprintf(buf, size, "%s:%s:%"PRIu64 ":%s:%s:%d:%d", sc->name, sc->title, sc->exp, sc->killer, sc->maplevel, sc->maxhp, sc->maxsp);
89 }
90 
96 static void hiscore_save(const score_table *table)
97 {
98  FILE *fp;
99  size_t i;
100  char buf[HUGE_BUF];
101 
102  fp = fopen(table->fname, "w");
103 
104  if (!fp) {
105  LOG(BUG, "Cannot create highscore file %s: %s", table->fname, strerror(errno));
106  return;
107  }
108 
109  for (i = 0; i < HIGHSCORE_LENGTH; i++) {
110  if (table->entry[i].name[0] == '\0') {
111  break;
112  }
113 
114  put_score(&table->entry[i], buf, sizeof(buf));
115  fprintf(fp, "%s\n", buf);
116  }
117 
118  if (ferror(fp)) {
119  LOG(BUG, "Cannot write to highscore file %s: %s", table->fname, strerror(errno));
120  fclose(fp);
121  } else if (fclose(fp) != 0) {
122  LOG(BUG, "Cannot write to highscore file %s: %s", table->fname, strerror(errno));
123  }
124 }
125 
136 static int get_score(char *bp, score *sc)
137 {
138  char *cp, *tmp[7];
139 
140  cp = strchr(bp, '\n');
141 
142  if (cp) {
143  *cp = '\0';
144  }
145 
146  if (string_split(bp, tmp, arraysize(tmp), ':') != arraysize(tmp)) {
147  return 0;
148  }
149 
150  strncpy(sc->name, tmp[0], sizeof(sc->name));
151  sc->name[sizeof(sc->name) - 1] = '\0';
152 
153  strncpy(sc->title, tmp[1], sizeof(sc->title));
154  sc->title[sizeof(sc->title) - 1] = '\0';
155 
156  sscanf(tmp[2], "%"PRIu64, &sc->exp);
157 
158  strncpy(sc->killer, tmp[3], sizeof(sc->killer));
159  sc->killer[sizeof(sc->killer) - 1] = '\0';
160 
161  strncpy(sc->maplevel, tmp[4], sizeof(sc->maplevel));
162  sc->maplevel[sizeof(sc->maplevel) - 1] = '\0';
163 
164  sscanf(tmp[5], "%d", &sc->maxhp);
165  sscanf(tmp[6], "%d", &sc->maxsp);
166  return 1;
167 }
168 
180 static char *draw_one_high_score(const score *sc, char *buf, size_t size)
181 {
182  if (sc->killer[0] == '\0') {
183  snprintf(buf, size, "[green]%3d[/green] %s [green]%s[/green] the %s (%s) <%d><%d>.", sc->position, string_format_number_comma(sc->exp), sc->name, sc->title, sc->maplevel, sc->maxhp, sc->maxsp);
184  } else {
185  const char *s1, *s2;
186 
187  if (!strcmp(sc->killer, "left")) {
188  s1 = sc->killer;
189  s2 = "the game";
190  } else {
191  s1 = "was killed by";
192  s2 = sc->killer;
193  }
194 
195  snprintf(buf, size, "[green]%3d[/green] %s [green]%s[/green] the %s %s %s on map %s <%d><%d>.", sc->position, string_format_number_comma(sc->exp), sc->name, sc->title, s1, s2, sc->maplevel, sc->maxhp, sc->maxsp);
196  }
197 
198  return buf;
199 }
200 
211 static void add_score(score_table *table, score *new_score, score *old_score)
212 {
213  size_t i;
214 
215  new_score->position = HIGHSCORE_LENGTH + 1;
216  memset(old_score, 0, sizeof(*old_score));
217  old_score->position = -1;
218 
219  /* Find existing entry by name */
220  for (i = 0; i < HIGHSCORE_LENGTH; i++) {
221  if (table->entry[i].name[0] == '\0') {
222  table->entry[i] = *new_score;
223  table->entry[i].position = i + 1;
224  break;
225  }
226 
227  if (strcmp(new_score->name, table->entry[i].name) == 0) {
228  *old_score = table->entry[i];
229 
230  if (table->entry[i].exp <= new_score->exp) {
231  table->entry[i] = *new_score;
232  table->entry[i].position = i + 1;
233  }
234 
235  break;
236  }
237  }
238 
239  /* Entry for unknown name */
240  if (i >= HIGHSCORE_LENGTH) {
241  /* New exp is less than lowest hiscore entry => drop */
242  if (new_score->exp < table->entry[i - 1].exp) {
243  return;
244  }
245 
246  /* New exp is not less than lowest hiscore entry => add */
247  i--;
248  table->entry[i] = *new_score;
249  table->entry[i].position = i + 1;
250  }
251 
252  /* Move entry to correct position */
253  while (i > 0 && new_score->exp >= table->entry[i - 1].exp) {
254  score tmp;
255 
256  tmp = table->entry[i - 1];
257  table->entry[i - 1] = table->entry[i];
258  table->entry[i] = tmp;
259 
260  table->entry[i - 1].position = i;
261  table->entry[i].position = i + 1;
262 
263  i--;
264  }
265 
266  new_score->position = table->entry[i].position;
267  hiscore_save(table);
268 }
269 
275 static void hiscore_load(score_table *table)
276 {
277  FILE *fp;
278  size_t i = 0;
279 
280  fp = fopen(table->fname, "r");
281 
282  if (fp == NULL) {
283  if (errno != ENOENT) {
284  LOG(ERROR, "Cannot open highscore file %s: %s", table->fname, strerror(errno));
285  }
286  } else {
287  while (i < HIGHSCORE_LENGTH) {
288  char buf[HUGE_BUF];
289 
290  if (!fgets(buf, sizeof(buf), fp)) {
291  break;
292  }
293 
294  if (!get_score(buf, &table->entry[i])) {
295  break;
296  }
297 
298  table->entry[i].position = i + 1;
299  i++;
300  }
301 
302  fclose(fp);
303  }
304 
305  while (i < HIGHSCORE_LENGTH) {
306  memset(&table->entry[i], 0, sizeof(table->entry[i]));
307  table->entry[i].position = i + 1;
308  i++;
309  }
310 }
311 
315 void hiscore_init(void)
316 {
317  snprintf(hiscore_table.fname, sizeof(hiscore_table.fname), "%s/highscore", settings.datapath);
318  hiscore_load(&hiscore_table);
319 }
320 
332 void hiscore_check(object *op, int quiet)
333 {
334  score new_score, old_score;
335  char bufscore[HUGE_BUF];
336  const char *message;
337 
338  if (!op->stats.exp) {
339  return;
340  }
341 
342  snprintf(VS(new_score.name), "%s", op->name);
343  snprintf(VS(new_score.title), "%s", op->race);
344 
345  const char *killer = player_get_killer(CONTR(op));
346  if (killer != NULL) {
347  snprintf(VS(new_score.killer), "%s", killer);
348  } else {
349  *new_score.killer = '\0';
350  }
351 
352  new_score.exp = op->stats.exp;
353 
354  if (op->map == NULL) {
355  *new_score.maplevel = '\0';
356  } else {
357  snprintf(VS(new_score.maplevel),
358  "%s",
359  op->map->name != NULL ? op->map->name : op->map->path);
360 
361  /* Replace the colons in the map name with spaces, since the
362  * saving/loading mechanism uses colons to separate the values. */
363  string_replace_char(new_score.maplevel, ":", ' ');
364  }
365 
366  new_score.maxhp = (int) op->stats.maxhp;
367  new_score.maxsp = (int) op->stats.maxsp;
368  add_score(&hiscore_table, &new_score, &old_score);
369 
370  /* Everything below here is just related to print messages
371  * to the player. If quiet is set, we can just return
372  * now. */
373  if (quiet) {
374  return;
375  }
376 
377  if (old_score.position == -1) {
378  if (new_score.position > HIGHSCORE_LENGTH) {
379  message = "You didn't enter the highscore list:";
380  } else {
381  message = "You entered the highscore list:";
382  }
383  } else {
384  if (new_score.position > HIGHSCORE_LENGTH) {
385  message = "You left the highscore list:";
386  } else if (new_score.exp > old_score.exp) {
387  message = "You beat your last score:";
388  } else {
389  message = "You didn't beat your last score:";
390  }
391  }
392 
393  draw_info(COLOR_WHITE, op, message);
394 
395  if (old_score.position != -1) {
396  draw_info(COLOR_WHITE, op, draw_one_high_score(&old_score, bufscore, sizeof(bufscore)));
397  }
398 
399  draw_info(COLOR_WHITE, op, draw_one_high_score(&new_score, bufscore, sizeof(bufscore)));
400 }
401 
412 void hiscore_display(object *op, int max, const char *match)
413 {
414  int printed_entries = 0;
415  size_t j;
416 
417  draw_info(COLOR_WHITE, op, "Nr Score Who <max hp><max sp>");
418 
419  for (j = 0; j < HIGHSCORE_LENGTH && hiscore_table.entry[j].name[0] != '\0' && printed_entries < max; j++) {
420  char scorebuf[MAX_BUF];
421 
422  if (match && !strcasestr(hiscore_table.entry[j].name, match) && !strcasestr(hiscore_table.entry[j].title, match)) {
423  continue;
424  }
425 
426  draw_one_high_score(&hiscore_table.entry[j], scorebuf, sizeof(scorebuf));
427  printed_entries++;
428 
429  draw_info(COLOR_WHITE, op, scorebuf);
430  }
431 }
shstr * name
Definition: map.h:553
const char * race
Definition: object.h:174
char datapath[MAX_BUF]
Definition: global.h:343
static score_table hiscore_table
Definition: hiscore.c:75
shstr * path
Definition: map.h:568
static void add_score(score_table *table, score *new_score, score *old_score)
Definition: hiscore.c:211
Definition: hiscore.c:38
char fname[MAX_BUF]
Definition: hiscore.c:66
int64_t exp
Definition: living.h:69
char maplevel[MAX_BUF]
Definition: hiscore.c:49
static void put_score(const score *sc, char *buf, int size)
Definition: hiscore.c:86
static int get_score(char *bp, score *sc)
Definition: hiscore.c:136
char title[MAX_BUF]
Definition: hiscore.c:43
const char * player_get_killer(player *pl)
Definition: player.c:2676
int16_t maxsp
Definition: living.h:81
int32_t maxhp
Definition: living.h:75
char killer[MAX_BUF]
Definition: hiscore.c:46
int position
Definition: hiscore.c:58
struct mapdef * map
Definition: object.h:139
const char * name
Definition: object.h:168
static void hiscore_save(const score_table *table)
Definition: hiscore.c:96
struct scr score
int maxhp
Definition: hiscore.c:55
static void hiscore_load(score_table *table)
Definition: hiscore.c:275
score entry[HIGHSCORE_LENGTH]
Definition: hiscore.c:69
void hiscore_check(object *op, int quiet)
Definition: hiscore.c:332
void hiscore_init(void)
Definition: hiscore.c:315
void hiscore_display(object *op, int max, const char *match)
Definition: hiscore.c:412
uint64_t exp
Definition: hiscore.c:52
char name[MAX_BUF]
Definition: hiscore.c:40
struct settings_struct settings
Definition: init.c:55
living stats
Definition: object.h:481
#define HIGHSCORE_LENGTH
Definition: config.h:142
static char * draw_one_high_score(const score *sc, char *buf, size_t size)
Definition: hiscore.c:180