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(&timesort[i], &timesort[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