1 /*
2 * XPilot NG, a multiplayer space war game.
3 *
4 * Copyright (C) TODO Erik Andersson
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include "xpclient.h"
22
23 #define MAX_SCORES 500
24
25 char clientRankFile[PATH_MAX];
26 char clientRankHTMLFile[PATH_MAX];
27 char clientRankHTMLNOJSFile[PATH_MAX];
28
29 /*
30 * Defining one/both of CLIENTRANKINGPAGE/CLIENTNOJSRANKINGPAGE while leaving
31 * CLIENTSCOREFILE undefined grants a rank of last/(current maybe aswell)
32 * xpilot session.
33 */
34 static ScoreNode scores[MAX_SCORES];
35 static int recent[10];
36 static int oldest_cache = 0;
37 static int timesort[MAX_SCORES];
38 static double kd[MAX_SCORES];
39 static int kdsort[MAX_SCORES];
40 static bool client_Scoring = false;
41
swapd(double * d1,double * d2)42 static void swapd(double *d1, double *d2)
43 {
44 double d = *d1;
45 *d1 = *d2;
46 *d2 = d;
47 }
48
swapi(int * i1,int * i2)49 static void swapi(int *i1, int *i2)
50 {
51 int i = *i1;
52 *i1 = *i2;
53 *i2 = i;
54 }
55
56
Time_Sort(void)57 static void Time_Sort(void)
58 {
59 int i;
60
61 for (i = 0; i < MAX_SCORES; i++) {
62 int j;
63
64 for (j = i + 1; j < MAX_SCORES; j++) {
65 if (scores[timesort[i]].timestamp <
66 scores[timesort[j]].timestamp)
67 swapi(×ort[i], ×ort[j]);
68 }
69 }
70 for (i = 0; i < 10; i++)
71 recent[i] = timesort[i];
72
73 oldest_cache = 5;
74 }
75
76 /* This function checks wether the strings contains certain characters
77 that might be hazardous to include on a webpage (ie they screw it up). */
LegalizeName(char string[])78 static void LegalizeName(char string[])
79 {
80 const int length = strlen(string);
81 int i;
82
83 for (i = 0; i < length; i++)
84 switch (string[i]) {
85 case '<':
86 case '>':
87 case '\t':
88 string[i] = '?';
89 default:
90 break;
91 }
92 }
93
94 /* Sort the ranks and save them to the webpage. */
Rank_score(void)95 static void Rank_score(void)
96 {
97 static const char header[] =
98 "<html><head><title>Xpilot Clientrank - Evolved by Mara</title>\n"
99 /* In order to save space/bandwidth, the table is saved as one */
100 /* giant javascript file, instead of writing all the <TR>, <TD>, etc */
101 "<SCRIPT language=\"Javascript\">\n<!-- Hide script\n"
102 "function g(nick, kills, deaths, ratio) {\n"
103 "document.write('<tr><td align=left><tt>', i, '</tt></td>');\n"
104 "document.write('<td align=left><b>', nick, '</b></td>');\n"
105 "document.write('<td align=right>', kills, '</td>');\n"
106 "document.write('<td align=right>', deaths, '</td>');\n"
107 "document.write('</td>');\n"
108 "document.write('<td align=right>', ratio, '</td>');\n"
109 "document.write('</tr>\\n');\n"
110 "i = i + 1\n" "}\n// Hide script --></SCRIPT>\n" "</head><body>\n"
111 /* Head of page */
112 "<h1>XPilot Clientrank - Evolved by Mara</h1>"
113 "<noscript>"
114 "<blink><h1>YOU MUST HAVE JAVASCRIPT FOR THIS PAGE</h1></blink>"
115 "Please go <A href=\"index_nojs.html\">here</A> for the non-js page"
116 "</noscript>\n"
117 "<table><tr><td></td>"
118 "<td align=left><h1><u><b>Player</b></u></h1></td>"
119 "<td align=right><h1><u><b>Kills</b></u></h1></td>"
120 "<td align=right><h1><u><b>Deaths</b></u></h1></td>"
121 "<td align=right><h1><u><b>Ratio</b></u></h1></td>"
122 "</tr>\n" "<SCRIPT language=\"Javascript\">\n" "var i = 1\n";
123
124 static const char headernojs[] =
125 "<html><head><title>XPilot Clientrank - Evolved by Mara</title>\n"
126 "</head><body>\n"
127 /* Head of page */
128 "<h1>XPilot Clientrank</h1>"
129 "<table><tr><td></td>"
130 "<td align=left><h1><u><b>Player</b></u></h1></td>"
131 "<td align=right><h1><u><b>Kills</b></u></h1></td>"
132 "<td align=right><h1><u><b>Deaths</b></u></h1></td>"
133 "<td align=right><h1><u><b>Ratio</b></u></h1></td>" "</tr>\n";
134
135 static const char footer[] =
136 "</table>"
137 "<i>Explanation for rank</i>:<br>"
138 "The numbers are k/d/r, where<br>"
139 "k = The number of times he has shot me<br>"
140 "d = The number of time I have shot him<br>"
141 "r = the quota between k and d<br>" "</body></html>";
142
143 int i;
144
145 for (i = 0; i < MAX_SCORES; i++) {
146 kdsort[i] = i;
147 kd[i] =
148 (scores[i].deaths !=
149 0.0) ? ((double) (scores[i].kills) /
150 (double) (scores[i].deaths)) : 0.0;
151 }
152
153 for (i = 0; i < MAX_SCORES; i++) {
154 int j;
155
156 for (j = i + 1; j < MAX_SCORES; j++) {
157 if (kd[i] < kd[j]) {
158 swapi(&kdsort[i], &kdsort[j]);
159 swapd(&kd[i], &kd[j]);
160 }
161 }
162 }
163
164 if (strlen(clientRankHTMLFile) > 0) {
165 FILE *const file = fopen(clientRankHTMLFile, "w");
166
167 if (file != NULL && fseek(file, 2000, SEEK_SET) == 0) {
168 fprintf(file, "%s", header);
169 for (i = 0; i < MAX_SCORES; i++) {
170 if (scores[kdsort[i]].nick[0] != '\0') {
171 LegalizeName(scores[kdsort[i]].nick);
172 fprintf(file, "g(\"%s\", %u, %u, %.3f);\n",
173 scores[kdsort[i]].nick,
174 scores[kdsort[i]].kills,
175 scores[kdsort[i]].deaths, kd[i]);
176 }
177 }
178 fprintf(file, "</script>");
179 fprintf(file, footer);
180 fclose(file);
181 }
182 }
183
184 if (strlen(clientRankHTMLNOJSFile) > 0) {
185 FILE *const file = fopen(clientRankHTMLNOJSFile, "w");
186
187 if (file != NULL && fseek(file, 2000, SEEK_SET) == 0) {
188 fprintf(file, "%s", headernojs);
189 for (i = 0; i < MAX_SCORES; i++) {
190 if (scores[kdsort[i]].nick[0] != '\0') {
191 LegalizeName(scores[kdsort[i]].nick);
192 fprintf(file,
193 "<tr><td align=left><tt>%d</tt>"
194 "<td align=left><b>%s</b>"
195 "<td align=right>%u"
196 "<td align=right>%u"
197 "<td align=right>%.3f"
198 "</tr>\n",
199 i + 1,
200 scores[kdsort[i]].nick,
201 scores[kdsort[i]].kills,
202 scores[kdsort[i]].deaths, kd[i]);
203 }
204 }
205 fprintf(file, footer);
206 fclose(file);
207 }
208 }
209
210 if (strlen(clientRankHTMLFile) == 0
211 && strlen(clientRankHTMLNOJSFile) == 0)
212 warn("You have not specified clientRankHTMLFile or "
213 "clientRankHTMLNOJSFile.");
214 }
215
Init_scorenode(ScoreNode * node,const char nick[])216 static void Init_scorenode(ScoreNode * node, const char nick[])
217 {
218 strcpy(node->nick, nick);
219 node->kills = 0;
220 node->deaths = 0;
221 }
222
223 /*
224 * Read scores from disk, and zero-initialize the ones that are not used.
225 * Call this on startup.
226 */
Init_saved_scores(void)227 void Init_saved_scores(void)
228 {
229 int i = 0;
230
231 if (strlen(clientRankFile) > 0) {
232 FILE *file = fopen(clientRankFile, "r");
233
234 if (file != NULL) {
235 const int actual = fread(scores, sizeof(ScoreNode),
236 MAX_SCORES, file);
237 if (actual != MAX_SCORES)
238 warn("Error when reading score file!\n");
239
240 i += actual;
241
242 fclose(file);
243 }
244 client_Scoring = true;
245 }
246
247 while (i < MAX_SCORES) {
248 Init_scorenode(&scores[i], "");
249 scores[i].timestamp = 0;
250 timesort[i] = i;
251 i++;
252 }
253 if (client_Scoring)
254 Time_Sort();
255 }
256
Get_saved_score(char * nick)257 static int Get_saved_score(char *nick)
258 {
259 int oldest = 0;
260 int i;
261
262 for (i = 0; i < MAX_SCORES; i++) {
263 if (strcmp(nick, scores[i].nick) == 0)
264 return i;
265 if (scores[i].timestamp < scores[oldest].timestamp)
266 oldest = i;
267 }
268
269 Init_scorenode(&scores[oldest], nick);
270 scores[oldest].timestamp = time(0);
271 return oldest;
272 }
273
274
Find_player(char * nick)275 static int Find_player(char *nick)
276 {
277 int i;
278 for (i = 0; i < 10; i++) {
279 /*if (scores[recent[i]].timestamp > 0) { */
280 if (strcmp(nick, scores[recent[i]].nick) == 0)
281 return i;
282 }
283 i = Get_saved_score(nick);
284 recent[oldest_cache] = i;
285 i = oldest_cache;
286 oldest_cache = (oldest_cache + 1) / 10;
287 return i;
288 }
289
290
Add_rank_Kill(char * nick)291 void Add_rank_Kill(char *nick)
292 {
293 int i = Find_player(nick);
294
295 scores[recent[i]].kills = scores[recent[i]].kills + 1;
296 scores[recent[i]].timestamp = time(0);
297 }
298
Add_rank_Death(char * nick)299 void Add_rank_Death(char *nick)
300 {
301 int i = Find_player(nick);
302
303 scores[recent[i]].deaths = scores[recent[i]].deaths + 1;
304 scores[recent[i]].timestamp = time(0);
305 }
306
Get_kills(char * nick)307 int Get_kills(char *nick)
308 {
309 int i = Find_player(nick);
310
311 return scores[recent[i]].kills;
312 }
313
Get_deaths(char * nick)314 int Get_deaths(char *nick)
315 {
316 int i = Find_player(nick);
317
318 return scores[recent[i]].deaths;
319 }
320
321
322 /* Save the scores to disk (not the webpage). */
Print_saved_scores(void)323 void Print_saved_scores(void)
324 {
325 FILE *file = NULL;
326
327 Rank_score();
328 if (strlen(clientRankFile) > 0 &&
329 (file = fopen(clientRankFile, "w")) != NULL) {
330 const int actual = fwrite(scores, sizeof(ScoreNode),
331 MAX_SCORES, file);
332 if (actual != MAX_SCORES)
333 warn("Error when writing score file!\n");
334
335 fclose(file);
336 }
337 }
338