1 /*
2 * XPilot NG, a multiplayer space war game.
3 *
4 * Copyright (C) 1999-2004 by
5 *
6 * Marcus Sundberg <mackan@stacken.kth.se>
7 * Kristian S�derblom <kps@users.sourceforge.net>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24 #include "xpserver.h"
25
26 /* MAX_SCORES = how many players we remember */
27 #define MAX_SCORES 300
28
29 static bool Rank_parse_rankfile(FILE *file);
30
31 /* Score data */
32 static ranknode_t ranknodes[MAX_SCORES];
33
34 typedef struct rank {
35 int ind;
36 double ratio;
37 } rank_t;
38
39 static rank_t rank_base[MAX_SCORES];
40
41 static double sc_table[MAX_SCORES];
42 static double kr_table[MAX_SCORES];
43 static double kd_table[MAX_SCORES];
44 static double hf_table[MAX_SCORES];
45
46 static bool playerstag = false;
47 static int num_players = 0;
48 static int rank_entries = 0;
49
rank_cmp(const void * p1,const void * p2)50 static int rank_cmp(const void *p1, const void *p2)
51 {
52 const rank_t *r1, *r2;
53
54 r1 = (const rank_t *)p1;
55 r2 = (const rank_t *)p2;
56
57 /*
58 * Function qsort(3) normally sorts array elements into ascending order.
59 * We want a descending order (greatest ratio first), that's why we
60 * tell qsort a greater ratio is less than a lesser ratio.
61 */
62 if (r1->ratio < r2->ratio)
63 return 1;
64 if (r1->ratio > r2->ratio)
65 return -1;
66 return 0;
67 }
68
rank_showtime(const time_t t)69 static char *rank_showtime(const time_t t)
70 {
71 static char buf[80];
72
73 strftime(buf, sizeof(buf), "%d\xA0%b\xA0%Y\xA0%H:%M\xA0UTC",
74 gmtime(&t));
75 return buf;
76 }
77
78 /*
79 * Encode 'str' for XML (xml set to nonzero) or HTML (xml set to zero).
80 */
encode(const char * str,int xml)81 static char *encode(const char *str, int xml)
82 {
83 static char result[MAX_CHARS];
84 char c;
85
86 result[0] = '\0';
87 while ((c = *str++) != '\0') {
88 if (c == '<')
89 strlcat(result, "<", sizeof(result));
90 else if (c == '>')
91 strlcat(result, ">", sizeof(result));
92 else if (c == '&')
93 strlcat(result, "&", sizeof(result));
94 else if (c == '\'' && xml)
95 strlcat(result, "'", sizeof(result));
96 else if (c == '"')
97 strlcat(result, """, sizeof(result));
98 else {
99 char tmp[2];
100
101 sprintf(tmp, "%c", c);
102 strlcat(result, tmp, sizeof(result));
103 }
104 }
105
106 return result;
107 }
108
109 /* Here's where we calculate the ranks. Figure it out yourselves! */
SortRankings(void)110 static void SortRankings(void)
111 {
112 double lowSC = 0.0, highSC = 0.0;
113 double lowKD = 0.0, highKD = 0.0;
114 double lowKR = 0.0, highKR = 0.0;
115 double lowHF = 0.0, highHF = 0.0;
116 bool foundFirst = false;
117 int k;
118
119 /* Ok, there are two loops: the first one calculates the scores and
120 records lowest and highest scores. The second loop combines the
121 scores into a rank. I cannot do it in one loop since I need to
122 know low- and highmarks for each score before I can calculate the
123 rank. */
124 for (k = 0; k < MAX_SCORES; k++) {
125 ranknode_t *rank = &ranknodes[k];
126 double attenuation, kills, sc, kd, kr, hf;
127
128 if (strlen(rank->name) == 0)
129 continue;
130
131 /* The attenuation affects players with less than 300 rounds. */
132 attenuation = (rank->rounds < 300) ?
133 ((double) rank->rounds / 300.0) : 1.0;
134
135 kills = rank->kills;
136 sc = (double) rank->score * attenuation;
137 kd = ((rank->deaths != 0) ?
138 (kills / (double) rank->deaths) :
139 (kills)) * attenuation;
140 kr = ((rank->rounds != 0) ?
141 (kills / (double) rank->rounds) :
142 (kills)) * attenuation;
143 hf = ((rank->ballsLost != 0) ?
144 ((double) rank->ballsCashed /
145 (double) rank->ballsLost) :
146 (double) rank->ballsCashed) * attenuation;
147
148 sc_table[k] = sc;
149 kd_table[k] = kd;
150 kr_table[k] = kr;
151 hf_table[k] = hf;
152
153 if (!foundFirst) {
154 lowSC = highSC = sc;
155 lowKD = highKD = kd;
156 lowKR = highKR = kr;
157 lowHF = highHF = hf;
158 foundFirst = true;
159 } else {
160 if (sc > highSC)
161 highSC = sc;
162 else if (sc < lowSC)
163 lowSC = sc;
164
165 if (kd > highKD)
166 highKD = kd;
167 else if (kd < lowKD)
168 lowKD = kd;
169
170 if (kr > highKR)
171 highKR = kr;
172 else if (kr < lowKR)
173 lowKR = kr;
174
175 if (hf > highHF)
176 highHF = hf;
177 else if (hf < lowHF)
178 lowHF = hf;
179 }
180 }
181
182 /* Normalize */
183 highSC -= lowSC;
184 highKD -= lowKD;
185 highKR -= lowKR;
186 highHF -= lowHF;
187
188
189 {
190 const double factorSC = (highSC != 0.0) ? (100.0 / highSC) : 0.0;
191 const double factorKD = (highKD != 0.0) ? (100.0 / highKD) : 0.0;
192 const double factorKR = (highKR != 0.0) ? (100.0 / highKR) : 0.0;
193 const double factorHF = (highHF != 0.0) ? (100.0 / highHF) : 0.0;
194 int i;
195
196 rank_entries = 0;
197 for (i = 0; i < MAX_SCORES; i++) {
198 ranknode_t *rank = &ranknodes[i];
199 double sc, kd, kr, hf, rsc, rkd, rkr, rhf;
200
201 rank_base[i].ind = i;
202 if (strlen(rank->name) == 0) {
203 rank_base[i].ratio = -1;
204 continue;
205 }
206 rank_entries++;
207
208 sc = sc_table[i];
209 kd = kd_table[i];
210 kr = kr_table[i];
211 hf = hf_table[i];
212
213 rsc = (sc - lowSC) * factorSC;
214 rkd = (kd - lowKD) * factorKD;
215 rkr = (kr - lowKR) * factorKR;
216 rhf = (hf - lowHF) * factorHF;
217
218 rank_base[i].ratio = 0.20 * rsc + 0.30 * rkd + 0.30 * rkr + 0.20 * rhf;
219
220 /* KHS: maximum survived time serves as factor */
221 if(options.survivalScore != 0.0){
222 rank_base[i].ratio=rank->max_survival_time;
223 }
224 }
225
226
227 /* And finally we sort the ranks, wheee! */
228 qsort(rank_base, MAX_SCORES, sizeof(rank_t), rank_cmp);
229 }
230 }
231
Rank_get_logout_message(ranknode_t * rank)232 static const char *Rank_get_logout_message(ranknode_t *rank)
233 {
234 static char msg[MSG_LEN];
235 player_t *pl;
236
237 assert(strlen(rank->name) > 0);
238 pl = Get_player_by_name(rank->name, NULL, NULL);
239 if (pl) {
240 if (Player_is_paused(pl))
241 snprintf(msg, sizeof(msg), "paused");
242 else
243 snprintf(msg, sizeof(msg), "playing");
244 }
245 else
246 snprintf(msg, sizeof(msg), "%s", rank_showtime(rank->timestamp));
247
248 return msg;
249 }
250
251 /* Sort the ranks and save them to the webpage. */
Rank_write_webpage(void)252 void Rank_write_webpage(void)
253 {
254 static const char stdcss[] =
255 " <style type=\"text/css\">\n"
256 " body {\n"
257 " font-family: sans-serif;\n"
258 " color: #000000;\n"
259 " background-color: #ffffff;\n"
260 " }\n"
261 " table {\n"
262 " font-size: small;\n"
263 " border-collapse: collapse;\n"
264 " border-spacing: 0;\n"
265 " }\n"
266 " tr.odd {\n"
267 " color: #000000;\n"
268 " background-color: #d0d8e0;\n"
269 " }\n"
270 " tr.even {\n"
271 " color: #000000;\n"
272 " background-color: #e0e8f0;\n"
273 " }\n"
274 " th, td {\n"
275 " padding: 0.2em 0.5em;\n"
276 " }\n"
277 " th {\n"
278 " color: #000000;\n"
279 " background-color: #ffffff;\n"
280 " border: solid #808890;\n"
281 " border-width: 1px 0 1px 0;\n"
282 " font-weight: bold;\n"
283 " }\n"
284 " td.player {\n"
285 " font-weight: bold;\n"
286 " }\n"
287 " a:link {\n"
288 " color: #0000c0;\n"
289 " background-color: #ffffff;\n"
290 " }\n"
291 " a:visited {\n"
292 " color: #c000c0;\n"
293 " background-color: #ffffff;\n"
294 " }\n"
295 " </style>\n";
296
297 char *filename;
298 FILE *file;
299 int i;
300
301 SortRankings();
302
303 filename = options.rankWebpageFileName;
304 if (!filename)
305 return;
306
307 file = fopen(filename, "w");
308 if (!file) {
309 error("Couldn't open ranking file \"%s\" for writing", filename);
310 return;
311 }
312
313 fprintf(file,
314 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n"
315 " \"http://www.w3.org/TR/HTML4/strict.dtd\">\n"
316 "<html lang=\"en\">\n"
317 "<head>\n"
318 " <title>%s @ %s</title>\n"
319 " <meta http-equiv=\"Content-Type\" "
320 "content=\"text/html; charset=ISO-8859-1\">\n",
321 options.mapName, Server.host);
322
323 if (options.rankWebpageCSS != NULL)
324 fprintf(file,
325 " <link rel=\"StyleSheet\" type=\"text/css\" href=\"%s\" />\n",
326 options.rankWebpageCSS);
327 else
328 fprintf(file, "%s", stdcss);
329
330 fprintf(file,
331 "</head>\n"
332 "\n"
333 "<body>\n"
334 " <h1>%s @ %s</h1>\n" /* mapname @ servername */
335 "\n"
336 " <p>\n"
337 " <a href=\"http://xpilot.sourceforge.net/rank-info.html\">"
338 "How does the ranking work?</a>\n"
339 " </p>\n"
340 "\n"
341 " <table>\n",
342 options.mapName, Server.host);
343
344 for (i = 0; i < MAX_SCORES; i++) {
345 ranknode_t *rank = &ranknodes[rank_base[i].ind];
346
347 if (strlen(rank->name) == 0)
348 continue;
349
350 if (i % 20 == 0)
351 fprintf(file,
352 " <tr>\n"
353 " <th class=\"rank\" align=\"left\">Rank</th>\n"
354 " <th class=\"player\" align=\"left\">Player</th>\n"
355 " <th class=\"score\" align=\"left\">Score</th>\n"
356 " <th class=\"kills\" align=\"left\">Kills</th>\n"
357 " <th class=\"deaths\" align=\"left\">Deaths</th>\n"
358 " <th class=\"rounds\" align=\"left\">Rounds</th>\n"
359 " <th class=\"shots\" align=\"left\">Shots</th>\n"
360 " <th class=\"deadliest\" align=\"left\">Deadliest</th>\n"
361 " <th class=\"balls\" align=\"left\">Balls</th>\n"
362 " <th class=\"ratio\" align=\"left\">Ratio</th>\n"
363 " <th class=\"user\" align=\"right\">User</th>\n"
364 " <th class=\"host\" align=\"left\">Host</th>\n"
365 " <th class=\"logout\" align=\"left\">Logout</th>\n"
366 " </tr>\n");
367
368 fprintf(file,
369 " <tr class=\"%s\">\n"
370 " <td class=\"rank\" align=\"right\">%d</td>\n"
371 " <td class=\"player\" align=\"left\">%s</td>\n",
372 i % 2 == 0 ? "odd" : "even", /* sic */
373 i + 1,
374 encode(rank->name, 0));
375
376 fprintf(file,
377 " <td class=\"score\" align=\"right\">%.1f</td>\n"
378 " <td class=\"kills\" align=\"right\">%u</td>\n"
379 " <td class=\"deaths\" align=\"right\">%u</td>\n"
380 " <td class=\"rounds\" align=\"right\">%u</td>\n"
381 " <td class=\"shots\" align=\"right\">%u</td>\n"
382 " <td class=\"deadliest\" align=\"right\">%u</td>\n"
383 " <td class=\"balls\" align=\"left\">%u/%u/%u/%u/%.2f</td>\n"
384 " <td class=\"ratio\" align=\"right\">%.2f</td>\n"
385 " <td class=\"user\" align=\"right\">%s</td>\n",
386 rank->score,
387 rank->kills, rank->deaths,
388 rank->rounds, rank->shots,
389 rank->deadliest,
390 rank->ballsCashed, rank->ballsSaved,
391 rank->ballsWon, rank->ballsLost,
392 rank->bestball,
393 rank_base[i].ratio,
394 encode(rank->user, 0));
395
396 fprintf(file,
397 " <td class=\"host\" align=\"left\">%s</td>\n"
398 " <td class=\"logout\" align=\"left\">%s</td>\n"
399 " </tr>\n",
400 encode(rank->host, 0),
401 Rank_get_logout_message(rank));
402 }
403
404 fprintf(file,
405 " </table>\n"
406 "\n"
407 " <p>\n"
408 " <em>Explanation for ballstats</em>:<br>\n"
409 " The numbers are c/s/w/l/b, where<br>\n"
410 " c = The number of enemy balls you have cashed.<br>\n"
411 " s = The number of your own balls you have returned.<br>\n"
412 " w = The number of enemy balls your team has cashed.<br>\n"
413 " l = The number of your own balls you have lost.<br>\n"
414 " b = The fastest ballrun you have made.<br>\n"
415 " </p>\n"
416 "\n"
417 " <p>\n"
418 " Page generated by " PACKAGE_STRING " on %s\n"
419 " </p>\n"
420 "</body>\n"
421 "</html>\n",
422 rank_showtime(time(NULL)));
423
424 fclose(file);
425 }
426
427
Rank_get_stats(const char * name,char * buf,size_t size)428 bool Rank_get_stats(const char *name, char *buf, size_t size)
429 {
430 ranknode_t *r = Rank_get_by_name(name);
431
432 if (r == NULL)
433 return false;
434
435 snprintf(buf, size,
436 "%-15s SC: %7.1f K/D: %5d/%5d R: %4d SH: %6d Dl: %d "
437 "B: %d/%d/%d/%d/%.2f TM: %.2f",
438 r->name, r->score, r->kills, r->deaths, r->rounds, r->shots, r->deadliest,
439 r->ballsCashed, r->ballsSaved, r->ballsWon, r->ballsLost,
440 r->bestball,r->max_survival_time);
441
442 return true;
443 }
444
445
446 /* Send a line with the ranks of the current players to the game. */
Rank_show_ranks(void)447 void Rank_show_ranks(void)
448 {
449 char msg[MSG_LEN];
450 char tmpbuf[MSG_LEN];
451 int i, num = 0, numranks = 0;
452
453 snprintf(msg, sizeof(msg), "Ranks: ");
454
455 for (i = 0; i < MAX_SCORES; i++) {
456 ranknode_t *rank = &ranknodes[rank_base[i].ind];
457
458 if (strlen(rank->name) > 0)
459 numranks++;
460
461 if (rank->pl != NULL) {
462 if (num > 0)
463 strlcat(msg, ", ", sizeof(msg));
464 snprintf(tmpbuf, sizeof(tmpbuf), "%s [%d]", rank->name, i + 1);
465 strlcat(msg, tmpbuf, sizeof(msg));
466 num++;
467 }
468 }
469
470 strlcat(msg, ".", sizeof(msg));
471 Set_message(msg);
472
473 /* show a few best ranks */
474 snprintf(msg, sizeof(msg), " < Top %d ranks: ",
475 numranks < 3 ? numranks : 3);
476 num = 0;
477 for (i = 0; i < MAX_SCORES; i++) {
478 ranknode_t *rank = &ranknodes[rank_base[i].ind];
479
480 if (strlen(rank->name) == 0)
481 continue;
482
483 if (num > 0)
484 strlcat(msg, ", ", sizeof(msg));
485 snprintf(tmpbuf, sizeof(tmpbuf),
486 (options.survivalScore == 0.0) ?
487 "%d. %s (%.2f)" : "%d. %s (%.1f)",
488 num + 1, rank->name, rank_base[i].ratio);
489 strlcat(msg, tmpbuf, sizeof(msg));
490 num++;
491 if (num > 2)
492 break;
493 }
494
495 strlcat(msg, " >", sizeof(msg));
496 Set_message(msg);
497
498 return;
499 }
500
Init_ranknode(ranknode_t * rank,const char * name,const char * user,const char * host)501 static void Init_ranknode(ranknode_t *rank,
502 const char *name, const char *user, const char *host)
503 {
504 memset(rank, 0, sizeof(ranknode_t));
505 strlcpy(rank->name, name, sizeof(rank->name));
506 strlcpy(rank->user, user, sizeof(rank->user));
507 strlcpy(rank->host, host, sizeof(rank->host));
508 }
509
510
Rank_get_by_name(const char * name)511 ranknode_t *Rank_get_by_name(const char *name)
512 {
513 int i;
514 player_t *pl;
515
516 assert(name != NULL);
517
518 for (i = 0; i < MAX_SCORES; i++) {
519 ranknode_t *rank = &ranknodes[i];
520
521 if (!strcasecmp(name, rank->name))
522 return rank;
523 }
524
525 /*
526 * If name doesn't equal any nick in the ranking list (ignoring case),
527 * let's see if it could be an abbreviation of the nick of some player
528 * who is currently playing.
529 */
530 pl = Get_player_by_name(name, NULL, NULL);
531 if (pl && pl->rank)
532 return pl->rank;
533
534 return NULL;
535 }
536
537 /* Read scores from disk, and zero-initialize the ones that are not used.
538 Call this on startup. */
Rank_init_saved_scores(void)539 void Rank_init_saved_scores(void)
540 {
541 int i;
542 FILE *file;
543
544 for (i = 0; i < MAX_SCORES; i++) {
545 ranknode_t *rank = &ranknodes[i];
546
547 memset(rank, 0, sizeof(ranknode_t));
548 }
549
550 if (getenv("XPILOTSCOREFILE")) {
551 warn("Environment variable XPILOTSCOREFILE is obsolete.");
552 warn("Use server option rankFileName instead.");
553 }
554
555 if (getenv("XPILOTRANKINGPAGE")) {
556 warn("Environment variable XPILOTRANKINGPAGE is obsolete.");
557 warn("Use server option rankWebpageFileName instead.");
558 }
559
560 if (getenv("XPILOTNOJSRANKINGPAGE")) {
561 warn("Environment variable XPILOTNOJSRANKINGPAGE is obsolete.");
562 warn("Use server option rankWebpageFileName instead.");
563 }
564
565 if (!options.rankFileName)
566 return;
567
568 file = fopen(options.rankFileName, "r");
569 if (!file) {
570 if (errno != ENOENT)
571 error("Couldn't open rank file \"%s\"", options.rankFileName);
572 return;
573 }
574
575 Rank_parse_rankfile(file);
576
577 fclose(file);
578
579 xpprintf("%s Rank file with %d entries opened successfully.\n",
580 showtime(), num_players);
581 }
582
583 /*
584 * A player has logged in. Find his info or create new info by kicking
585 * the player who hasn't played for the longest time.
586 */
Rank_get_saved_score(player_t * pl)587 void Rank_get_saved_score(player_t * pl)
588 {
589 ranknode_t *rank, *unused = NULL;
590 int i;
591
592 updateScores = true;
593
594 for (i = 0; i < MAX_SCORES; i++) {
595 rank = &ranknodes[i];
596 if (!strcasecmp(pl->name, rank->name)) {
597 if (rank->pl == NULL) {
598 /* Ok, found it. */
599 rank->pl = pl;
600 Player_set_score(pl,rank->score);
601 pl->rank = rank;
602 } else {
603 /* That ranknode is already in use by another player! */
604 Player_set_score(pl,0);
605 pl->rank = NULL;
606 }
607 return;
608 }
609 }
610
611 /* find unused rank node */
612 for (i = 0; i < MAX_SCORES; i++) {
613 rank = &ranknodes[i];
614
615 if (strlen(rank->name) == 0) {
616 unused = rank;
617 /*warn("found unused node %d", i);*/
618 break;
619 }
620 }
621
622 /*
623 * If all entries are in use, use the least-recently-used node
624 * of the bottom half of the list.
625 */
626 if (!unused) {
627 for (i = MAX_SCORES / 2; i < MAX_SCORES; i++) {
628 rank = &ranknodes[rank_base[i].ind];
629
630 /*warn("i is %d, index is %d, timestamp is %u",
631 i, rank_base[i].ind, rank->timestamp); */
632
633 if (!unused || rank->timestamp < unused->timestamp)
634 unused = rank;
635 }
636 }
637
638 rank = unused;
639 /*warn("timestamp of lru node = %u", rank->timestamp);*/
640
641 Init_ranknode(rank, pl->name, pl->username, pl->hostname);
642 rank->pl = pl;
643 rank->timestamp = time(NULL);
644 Player_set_score(pl,0);
645 pl->rank = rank;
646 }
647
648 /* A player has quit, save his info and mark him as not playing. */
Rank_save_score(player_t * pl)649 void Rank_save_score(player_t * pl)
650 {
651 ranknode_t *rank = pl->rank;
652
653 rank->score = Get_Score(pl);
654 rank->pl = NULL;
655 rank->timestamp = time(NULL);
656 }
657
658 /* Save the scores to disk (not the webpage). */
Rank_write_rankfile(void)659 void Rank_write_rankfile(void)
660 {
661 FILE *file = NULL;
662 char tmp_file[PATH_MAX];
663 int i;
664
665 if (!options.rankFileName)
666 return;
667
668 snprintf(tmp_file, sizeof(tmp_file), "%s-new", options.rankFileName);
669
670 file = fopen(tmp_file, "w");
671 if (file == NULL) {
672 error("Open temporary file \"%s\"", tmp_file);
673 goto failed;
674 }
675
676 if (fprintf(file,
677 "<?xml version=\"1.0\"?>\n"
678 "<XPilotNGRank version=\"1.0\">\n"
679 "<Players>\n") < 0)
680 goto writefailed;
681
682
683 for (i = 0; i < rank_entries; i++) {
684 ranknode_t *rank = &ranknodes[rank_base[i].ind];
685
686 if (strlen(rank->name) == 0)
687 continue;
688
689 if (fprintf(file, "<Player "
690 "name=\"%s\" ", encode(rank->name, 1)) < 0)
691 goto writefailed;
692
693 if (fprintf(file,
694 "user=\"%s\" ", encode(rank->user, 1)) < 0)
695 goto writefailed;
696
697 if (fprintf(file,
698 "host=\"%s\" ", encode(rank->host, 1)) < 0)
699 goto writefailed;
700
701 if (rank->score != 0.0
702 && fprintf(file, "score=\"%.2f\" ", rank->score) < 0)
703 goto writefailed;
704
705 if (rank->kills > 0
706 && fprintf(file, "kills=\"%d\" ", rank->kills) < 0)
707 goto writefailed;
708
709 if (rank->deaths > 0
710 && fprintf(file, "deaths=\"%d\" ", rank->deaths) < 0)
711 goto writefailed;
712
713 if (rank->rounds > 0
714 && fprintf(file, "rounds=\"%d\" ", rank->rounds) < 0)
715 goto writefailed;
716
717 if (rank->shots > 0
718 && fprintf(file, "shots=\"%d\" ", rank->shots) < 0)
719 goto writefailed;
720
721 if (rank->deadliest > 0
722 && fprintf(file, "deadliest=\"%d\" ", rank->deadliest) < 0)
723 goto writefailed;
724
725 if (rank->ballsCashed > 0
726 && fprintf(file, "ballscashed=\"%d\" ", rank->ballsCashed) < 0)
727 goto writefailed;
728
729 if (rank->ballsSaved > 0
730 && fprintf(file, "ballssaved=\"%d\" ", rank->ballsSaved) < 0)
731 goto writefailed;
732
733 if (rank->ballsWon > 0
734 && fprintf(file, "ballswon=\"%d\" ", rank->ballsWon) < 0)
735 goto writefailed;
736
737 if (rank->ballsLost > 0
738 && fprintf(file, "ballslost=\"%d\" ", rank->ballsLost) < 0)
739 goto writefailed;
740
741 if (rank->bestball > 0
742 && fprintf(file, "bestball=\"%.2f\" ", rank->bestball) < 0)
743 goto writefailed;
744
745 if (rank->max_survival_time > 0
746 && fprintf(file, "max_survival_time=\"%.2f\" ",
747 rank->max_survival_time) < 0)
748 goto writefailed;
749
750 if (fprintf(file, "timestamp=\"%u\" ", (unsigned)rank->timestamp) < 0)
751 goto writefailed;
752
753 if (fprintf(file, "/>\n") < 0)
754 goto writefailed;
755 }
756
757 if (fprintf(file,
758 "</Players>\n"
759 "</XPilotNGRank>\n") < 0)
760 goto writefailed;
761
762
763 if (fclose(file) != 0) {
764 error("Close temporary file \"%s\"", tmp_file);
765 goto failed;
766 }
767 file = NULL;
768
769 /* Overwrite old rank file. */
770 if (rename(tmp_file, options.rankFileName) < 0) {
771 error("Rename \"%s\" to \"%s\"", tmp_file, options.rankFileName);
772 goto failed;
773 }
774
775 remove(tmp_file);
776
777 /*xpprintf("%s Rank file with %d entries written successfully.\n",
778 showtime(), rank_entries);*/
779
780 return;
781
782 writefailed:
783 error("Write temporary file \"%s\"", tmp_file);
784
785 failed:
786
787 if (file) {
788 fclose(file);
789 remove(tmp_file);
790 }
791 warn("Couldn't save ranking data to file \"%s\".", options.rankFileName);
792
793 return;
794 }
795
796
tagstart(void * data,const char * el,const char ** attr)797 static void tagstart(void *data, const char *el, const char **attr)
798 {
799 static bool xptag = false;
800
801 UNUSED_PARAM(data);
802
803 if (!strcasecmp(el, "XPilotNGRank")) {
804 double version = -1;
805
806 while (*attr) {
807 if (!strcasecmp(*attr, "version"))
808 version = atof(*(attr + 1));
809 attr += 2;
810 }
811 if (version > 1.0) {
812 warn("Rank file has newer version than this server recognizes.");
813 warn("The file might use unsupported features.");
814 }
815 xptag = true;
816 return;
817 }
818
819 if (!xptag) {
820 fatal("This doesn't look like a rank file "
821 " (XPilotNGRank must be first tag).");
822 return; /* not reached */
823 }
824
825 if (!strcasecmp(el, "Player")) {
826 ranknode_t *rank;
827
828 if (!playerstag)
829 fatal("Player tag in rank file without Players.");
830
831 rank = &ranknodes[num_players++];
832 memset(rank, 0, sizeof(ranknode_t));
833
834 while (*attr) {
835 if (!strcasecmp(*attr, "name"))
836 strlcpy(rank->name, *(attr + 1), sizeof(rank->name));
837 if (!strcasecmp(*attr, "user"))
838 strlcpy(rank->user, *(attr + 1), sizeof(rank->user));
839 if (!strcasecmp(*attr, "host"))
840 strlcpy(rank->host, *(attr + 1), sizeof(rank->host));
841 if (!strcasecmp(*attr, "score"))
842 rank->score = atof(*(attr + 1));
843 if (!strcasecmp(*attr, "kills"))
844 rank->kills = atoi(*(attr + 1));
845 if (!strcasecmp(*attr, "deaths"))
846 rank->deaths = atoi(*(attr + 1));
847 if (!strcasecmp(*attr, "rounds"))
848 rank->rounds = atoi(*(attr + 1));
849 if (!strcasecmp(*attr, "shots"))
850 rank->shots = atoi(*(attr + 1));
851 if (!strcasecmp(*attr, "deadliest"))
852 rank->deadliest = atoi(*(attr + 1));
853 if (!strcasecmp(*attr, "ballssaved"))
854 rank->ballsSaved = atoi(*(attr + 1));
855 if (!strcasecmp(*attr, "ballslost"))
856 rank->ballsLost = atoi(*(attr + 1));
857 if (!strcasecmp(*attr, "ballswon"))
858 rank->ballsWon = atoi(*(attr + 1));
859 if (!strcasecmp(*attr, "ballscashed"))
860 rank->ballsCashed = atoi(*(attr + 1));
861 if (!strcasecmp(*attr, "bestball"))
862 rank->bestball = atof(*(attr + 1));
863 if (!strcasecmp(*attr, "max_survival_time"))
864 rank->max_survival_time = atof(*(attr + 1));
865 if (!strcasecmp(*attr, "timestamp"))
866 rank->timestamp = atoi(*(attr + 1));
867
868 attr += 2;
869 }
870
871 return;
872 }
873
874 if (!strcasecmp(el, "Players")) {
875 playerstag = true;
876 return;
877 }
878
879 warn("Unknown tag in rank file: \"%s\"", el);
880 return;
881 }
882
tagend(void * data,const char * el)883 static void tagend(void *data, const char *el)
884 {
885 UNUSED_PARAM(data);
886
887 if (!strcasecmp(el, "Players"))
888 playerstag = false;
889
890 return;
891 }
892
Rank_parse_rankfile(FILE * file)893 static bool Rank_parse_rankfile(FILE *file)
894 {
895 char buf[8192];
896 int len, fd;
897 XML_Parser p = XML_ParserCreate(NULL);
898
899 fd = fileno(file);
900 if (fd == -1)
901 return false;
902
903 if (!p) {
904 warn("Creating Expat instance for ranking file parsing failed.\n");
905 return false;
906 }
907 XML_SetElementHandler(p, tagstart, tagend);
908 do {
909 len = read(fd, buf, sizeof(buf));
910 if (len < 0) {
911 error("Error reading rankfile!");
912 return false;
913 }
914 if (!XML_Parse(p, buf, len, !len)) {
915 warn("Parse error reading rankfile at line %d:\n%s\n",
916 XML_GetCurrentLineNumber(p),
917 XML_ErrorString(XML_GetErrorCode(p)));
918 return false;
919 }
920 } while (len);
921 return true;
922 }
923