]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/playerstats.qc
Cleanup endmatch/shutdown for gamereport
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / playerstats.qc
1 #ifdef SVQC
2 //float PS_PM_IN_DB;   // playerstats_prematch_in_db      // db for info COLLECTED at the beginning of a match
3 float PS_GR_OUT_DB;  // playerstats_gamereport_out_db   // db of info SENT at the end of a match
4 //float PS_GR_IN_DB;   // playerstats_gamereport_in_db    // db for info COLLECTED at the end of a match
5 //float PS_B_IN_DB;    // playerstats_playerbasic_in_db   // db for info COLLECTED for basic player info (ELO)
6 // http://stats.xonotic.org/player/GgXRw6piDtFIbMArMuiAi8JG4tiin8VLjZgsKB60Uds=/elo.txt
7 #endif
8
9 #ifdef MENUQC
10 //float PS_D_IN_DB; // playerstats_playerdetail_in_db  // db for info COLLECTED for detailed player profile display
11 // http://stats.xonotic.org/player/me
12 #endif
13
14 #ifdef SVQC
15 //string PS_PM_IN_EVL;   // playerstats_prematch_in_events_last
16 string PS_GR_OUT_TL;   // playerstats_gamereport_out_teams_last
17 string PS_GR_OUT_PL;   // playerstats_gamereport_out_players_las
18 string PS_GR_OUT_EVL;  // playerstats_gamereport_out_events_last
19 //string PS_GR_IN_PL;    // playerstats_gamereport_in_players_last
20 //string PS_GR_IN_EVL;   // playerstats_gamereport_in_events_last
21 //string PS_B_IN_PL;     // playerstats_playerbasic_in_players_las
22 //string PS_B_IN_EVL;    // playerstats_playerbasic_in_events_last
23 #endif
24
25 #ifdef MENUQC
26 //string PS_D_IN_EVL; // playerstats_playerdetail_in_events_last
27 #endif
28
29 #ifdef SVQC
30 void PlayerStats_Prematch(void)
31 {
32         //foobar
33 }
34
35 void PlayerStats_GameReport_AddPlayer(entity e)
36 {
37         string s;
38
39         if(PS_GR_OUT_DB < 0) { return; }
40         if(e.playerstats_id) { return; }
41
42         s = string_null;
43         if(e.crypto_idfp != "" && e.cvar_cl_allow_uidtracking == 1)
44                 { s = e.crypto_idfp; }
45         else if(IS_BOT_CLIENT(e))
46                 { s = sprintf("bot#%g#%s", skill, e.cleanname); }
47
48         if((s == "") || find(world, playerstats_id, s)) // already have one of the ID - next one can't be tracked then!
49         {
50                 if(IS_BOT_CLIENT(e))
51                         { s = sprintf("bot#%d", e.playerid); }
52                 else
53                         { s = sprintf("player#%d", e.playerid); }
54         }
55
56         e.playerstats_id = strzone(s);
57
58         string key = sprintf("%s:*", e.playerstats_id);
59         string p = db_get(PS_GR_OUT_DB, key);
60         
61         if(p == "")
62         {
63                 if(PS_GR_OUT_PL)
64                 {
65                         db_put(PS_GR_OUT_DB, key, PS_GR_OUT_PL);
66                         strunzone(PS_GR_OUT_PL);
67                 }
68                 else { db_put(PS_GR_OUT_DB, key, "#"); }
69                 PS_GR_OUT_PL = strzone(e.playerstats_id);
70         }
71 }
72
73 void PlayerStats_GameReport_AddTeam(float t)
74 {
75         if(PS_GR_OUT_DB < 0) { return; }
76
77         string key = sprintf("%d", t);
78         string p = db_get(PS_GR_OUT_DB, key);
79         
80         if(p == "")
81         {
82                 if(PS_GR_OUT_TL)
83                 {
84                         db_put(PS_GR_OUT_DB, key, PS_GR_OUT_TL);
85                         strunzone(PS_GR_OUT_TL);
86                 }
87                 else { db_put(PS_GR_OUT_DB, key, "#"); }
88                 PS_GR_OUT_TL = strzone(key);
89         }
90 }
91
92 void PlayerStats_GameReport_AddEvent(string event_id)
93 {
94         if(PS_GR_OUT_DB < 0) { return; }
95
96         string key = sprintf("*:%s", event_id);
97         string p = db_get(PS_GR_OUT_DB, key);
98         
99         if(p == "")
100         {
101                 if(PS_GR_OUT_EVL)
102                 {
103                         db_put(PS_GR_OUT_DB, key, PS_GR_OUT_EVL);
104                         strunzone(PS_GR_OUT_EVL);
105                 }
106                 else { db_put(PS_GR_OUT_DB, key, "#"); }
107                 PS_GR_OUT_EVL = strzone(event_id);
108         }
109 }
110
111 // referred to by PS_GR_P_ADDVAL and PS_GR_T_ADDVAL
112 float PlayerStats_GameReport_Event(string prefix, string event_id, float value)
113 {
114         if((prefix == "") || PS_GR_OUT_DB < 0) { return 0; }
115
116         string key = sprintf("%s:%s", prefix, event_id);
117         float val = stof(db_get(PS_GR_OUT_DB, key));
118         val += value;
119         db_put(PS_GR_OUT_DB, key, ftos(val));
120         return val;
121 }
122
123 void PlayerStats_GameReport_Accuracy(entity p)
124 {
125     entity w;
126     float i;
127
128         #define PAC p.accuracy
129     for(i = WEP_FIRST; i <= WEP_LAST; ++i)
130     {
131         w = get_weaponinfo(i);
132         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-hit"), PAC.(accuracy_hit[i-1]));
133         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-fired"), PAC.(accuracy_fired[i-1]));
134         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-cnt-hit"), PAC.(accuracy_cnt_hit[i-1]));
135         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-cnt-fired"), PAC.(accuracy_cnt_fired[i-1]));
136         PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-frags"), PAC.(accuracy_frags[i-1]));
137     }
138     #undef PAC
139 }
140
141 void PlayerStats_GameReport_FinalizePlayer(entity p)
142 {
143         if((p.playerstats_id == "") || PS_GR_OUT_DB < 0) { return; }
144
145         // add global info!
146         if(p.alivetime)
147         {
148                 PS_GR_P_ADDVAL(p, PLAYERSTATS_ALIVETIME, time - p.alivetime);
149                 p.alivetime = 0;
150         }
151
152         db_put(PS_GR_OUT_DB, sprintf("%s:_playerid", p.playerstats_id), ftos(p.playerid));
153
154         if(p.cvar_cl_allow_uid2name == 1 || IS_BOT_CLIENT(p))
155                 db_put(PS_GR_OUT_DB, sprintf("%s:_netname", p.playerstats_id), p.netname);
156
157         if(teamplay)
158                 db_put(PS_GR_OUT_DB, sprintf("%s:_team", p.playerstats_id), ftos(p.team));
159
160         if(stof(db_get(PS_GR_OUT_DB, sprintf("%s:%s", p.playerstats_id, PLAYERSTATS_ALIVETIME))) > 0)
161                 PS_GR_P_ADDVAL(p, PLAYERSTATS_JOINS, 1);
162
163         PlayerStats_GameReport_Accuracy(p);
164
165         if(IS_REAL_CLIENT(p))
166         {
167                 if(p.latency_cnt)
168                 {
169                         float latency = (p.latency_sum / p.latency_cnt);
170                         if(latency) { PS_GR_P_ADDVAL(p, PLAYERSTATS_AVGLATENCY, latency); }
171                 }
172         }
173
174         strunzone(p.playerstats_id);
175         p.playerstats_id = string_null;
176 }
177
178 .float scoreboard_pos;
179 void PlayerStats_GameReport_EndMatch(float finished)
180 {
181         if(PS_GR_OUT_DB < 0) { return; }
182         
183         PlayerScore_Sort(score_dummyfield, 0, 0, 0);
184         PlayerScore_Sort(scoreboard_pos, 1, 1, 1);
185         if(teamplay) { PlayerScore_TeamStats(); }
186
187         entity p;
188         FOR_EACH_CLIENT(p)
189         {
190                 // add personal score rank
191                 PS_GR_P_ADDVAL(p, PLAYERSTATS_RANK, p.score_dummyfield);
192
193                 // scoreboard data
194                 if(p.scoreboard_pos)
195                 {
196                         // scoreboard is valid!
197                         PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_VALID, 1);
198
199                         // add scoreboard position
200                         PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_POS, p.scoreboard_pos);
201
202                         // add scoreboard data
203                         PlayerScore_PlayerStats(p);
204
205                         // if the match ended normally, add winning info
206                         if(finished)
207                         {
208                                 PS_GR_P_ADDVAL(p, PLAYERSTATS_WINS, p.winning);
209                                 PS_GR_P_ADDVAL(p, PLAYERSTATS_MATCHES, 1);
210                         }
211                 }
212
213                 // collect final player information
214                 PlayerStats_GameReport_FinalizePlayer(p);
215         }
216
217         if(autocvar_g_playerstats_gamereport_uri != "")
218         {
219                 PlayerStats_GameReport_DelayMapVote = TRUE;
220                 url_multi_fopen(
221                         autocvar_g_playerstats_gamereport_uri,
222                         FILE_APPEND,
223                         PlayerStats_GameReport_Handler,
224                         world
225                 );
226         }
227         else
228         {
229                 PlayerStats_GameReport_DelayMapVote = FALSE;
230                 db_close(PS_GR_OUT_DB);
231                 PS_GR_OUT_DB = -1;
232         }
233 }
234
235 void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that scores are added properly
236 {
237         string uri;
238         PS_GR_OUT_DB = -1;
239         PlayerStats_GameReport_DelayMapVote = FALSE;
240         uri = autocvar_g_playerstats_uri;
241         if(uri == "")
242                 return;
243         PS_GR_OUT_DB = db_create();
244         if(PS_GR_OUT_DB >= 0)
245                 PlayerStats_GameReport_DelayMapVote = TRUE; // must wait for it at match end
246
247         serverflags |= SERVERFLAG_PLAYERSTATS;
248
249         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ALIVETIME);
250         PlayerStats_GameReport_AddEvent(PLAYERSTATS_AVGLATENCY);
251         PlayerStats_GameReport_AddEvent(PLAYERSTATS_WINS);
252         PlayerStats_GameReport_AddEvent(PLAYERSTATS_MATCHES);
253         PlayerStats_GameReport_AddEvent(PLAYERSTATS_JOINS);
254         PlayerStats_GameReport_AddEvent(PLAYERSTATS_SCOREBOARD_VALID);
255         PlayerStats_GameReport_AddEvent(PLAYERSTATS_SCOREBOARD_POS);
256         PlayerStats_GameReport_AddEvent(PLAYERSTATS_RANK);
257
258     // accuracy stats
259     entity w;
260     float i;
261     for(i = WEP_FIRST; i <= WEP_LAST; ++i)
262     {
263         w = get_weaponinfo(i);
264         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-hit"));
265         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-fired"));
266         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-cnt-hit"));
267         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-cnt-fired"));
268         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-frags"));
269     }
270
271         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3);
272         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5);
273         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10);
274         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15);
275         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20);
276         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25);
277         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30);
278         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_BOTLIKE);
279         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD);
280         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM);
281 }
282
283 void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
284 {
285         string t, tn;
286         string p, pn;
287         string e, en;
288         string nn, tt;
289         string s;
290
291         switch(status)
292         {
293                 // ======================================
294                 // -- OUTGOING GAME REPORT INFORMATION --
295                 // ======================================
296                 /* SPECIFICATIONS:
297                  * V: format version (always a fixed number) - this MUST be the first line!
298                  * #: comment (MUST be ignored by any parser)
299                  * R: release information on the server
300                  * G: game type
301                  * O: mod name (icon request) as in server browser
302                  * M: map name
303                  * I: match ID (see "matchid" in g_world.qc)
304                  * S: "hostname" of the server
305                  * C: number of "unpure" cvar changes
306                  * U: UDP port number of the server
307                  * D: duration of the match
308                  * L: "ladder" in which the server is participating in
309                  * P: player ID of an existing player; this also sets the owner for all following "n", "e" and "t" lines (lower case!)
310                  * Q: team number of an existing team (format: team#NN); this also sets the owner for all following "e" lines (lower case!)
311                  * i: player index
312                  * n: nickname of the player (optional)
313                  * t: team ID
314                  * e: followed by an event name, a space, and the event count/score
315                  *  event names can be:
316                  *   alivetime: total playing time of the player
317                  *   avglatency: average network latency compounded throughout the match
318                  *   wins: number of games won (can only be set if matches is set)
319                  *   matches: number of matches played to the end (not aborted by map switch)
320                  *   joins: number of matches joined (always 1 unless player never played during the match)
321                  *   scoreboardvalid: set to 1 if the player was there at the end of the match
322                  *   total-<scoreboardname>: total score of that scoreboard item
323                  *   scoreboard-<scoreboardname>: end-of-game score of that scoreboard item (can differ in non-team games)
324                  *   achievement-<achievementname>: achievement counters (their "count" is usually 1 if nonzero at all)
325                  *   kills-<index>: number of kills against the indexed player
326                  *   rank <number>: rank of player
327                  *   acc-<weapon netname>-hit: total damage dealt
328                  *   acc-<weapon netname>-fired: total damage that all fired projectiles *could* have dealt
329                  *   acc-<weapon netname>-cnt-hit: amount of shots that actually hit
330                  *   acc-<weapon netname>-cnt-fired: amount of fired shots
331                  *   acc-<weapon netname>-frags: amount of frags dealt by weapon
332                  */
333                 case URL_READY_CANWRITE:
334                 {
335                         url_fputs(fh, "V 9\n");
336                         #ifdef WATERMARK
337                         url_fputs(fh, sprintf("R %s\n", WATERMARK));
338                         #endif
339                         url_fputs(fh, sprintf("G %s\n", GetGametype()));
340                         url_fputs(fh, sprintf("O %s\n", modname));
341                         url_fputs(fh, sprintf("M %s\n", GetMapname()));
342                         url_fputs(fh, sprintf("I %s\n", matchid));
343                         url_fputs(fh, sprintf("S %s\n", cvar_string("hostname")));
344                         url_fputs(fh, sprintf("C %d\n", cvar_purechanges_count));
345                         url_fputs(fh, sprintf("U %d\n", cvar("port")));
346                         url_fputs(fh, sprintf("D %f\n", max(0, time - game_starttime)));
347                         url_fputs(fh, sprintf("L %s\n", autocvar_g_playerstats_gamereport_ladder));
348
349                         // TEAMS
350                         if(teamplay)
351                         {
352                                 for(t = PS_GR_OUT_TL; (tn = db_get(PS_GR_OUT_DB, sprintf("%d", stof(t)))) != ""; t = tn)
353                                 {
354                                         // start team section
355                                         url_fputs(fh, sprintf("Q team#%s\n", t));
356
357                                         // output team events // todo: does this do unnecessary loops? perhaps we should do a separate "team_events_last" tracker..."
358                                         for(e = PS_GR_OUT_EVL; (en = db_get(PS_GR_OUT_DB, sprintf("*:%s", e))) != ""; e = en)
359                                         {
360                                                 float v = stof(db_get(PS_GR_OUT_DB, sprintf("team#%d:%s", stof(t), e)));
361                                                 if(v != 0) { url_fputs(fh, sprintf("e %s %g\n", e, v)); }
362                                         }
363                                 }
364                         }
365
366                         // PLAYERS
367                         for(p = PS_GR_OUT_PL; (pn = db_get(PS_GR_OUT_DB, sprintf("%s:*", p))) != ""; p = pn)
368                         {
369                                 // start player section
370                                 url_fputs(fh, sprintf("P %s\n", p));
371
372                                 // playerid/index (entity number for this server)
373                                 nn = db_get(PS_GR_OUT_DB, sprintf("%s:_playerid", p));
374                                 if(nn != "") { url_fputs(fh, sprintf("i %s\n", nn)); }
375
376                                 // player name 
377                                 nn = db_get(PS_GR_OUT_DB, sprintf("%s:_netname", p));
378                                 if(nn != "") { url_fputs(fh, sprintf("n %s\n", nn)); }
379
380                                 // team identification number
381                                 if(teamplay)
382                                 {
383                                         tt = db_get(PS_GR_OUT_DB, sprintf("%s:_team", p));
384                                         url_fputs(fh, sprintf("t %s\n", tt));
385                                 }
386
387                                 // output player events
388                                 for(e = PS_GR_OUT_EVL; (en = db_get(PS_GR_OUT_DB, sprintf("*:%s", e))) != ""; e = en)
389                                 {
390                                         float v = stof(db_get(PS_GR_OUT_DB, sprintf("%s:%s", p, e)));
391                                         if(v != 0) { url_fputs(fh, sprintf("e %s %g\n", e, v)); }
392                                 }
393                         }
394                         url_fputs(fh, "\n");
395                         url_fclose(fh);
396                         break;
397                 }
398
399                 // ======================================
400                 // -- INCOMING GAME REPORT INFORMATION --
401                 // ======================================
402                 /* SPECIFICATIONS:
403                  * stuff
404                  */
405                 case URL_READY_CANREAD:
406                 {
407                         // url_fclose is processing, we got a response for writing the data
408                         // this must come from HTTP
409                         print("Got response from player stats server:\n");
410                         while((s = url_fgets(fh))) { print("  ", s, "\n"); }
411                         print("End of response.\n");
412                         url_fclose(fh);
413                         break;
414                 }
415                 
416                 case URL_READY_CLOSED:
417                 {
418                         // url_fclose has finished
419                         print("Player stats written\n");
420                         PlayerStats_GameReport_DelayMapVote = FALSE;
421                         db_close(PS_GR_OUT_DB);
422                         PS_GR_OUT_DB = -1;
423                         break;
424                 }
425                 
426                 case URL_READY_ERROR:
427                 default:
428                 {
429                         print("Player stats writing failed: ", ftos(status), "\n");
430                         PlayerStats_GameReport_DelayMapVote = FALSE;
431                         if(PS_GR_OUT_DB >= 0)
432                         {
433                                 db_close(PS_GR_OUT_DB);
434                                 PS_GR_OUT_DB = -1;
435                         }
436                         break;
437                 }
438         }
439 }
440 #endif // SVQC
441 /*
442 void PlayerInfo_AddPlayer(entity e)
443 {
444         if(playerinfo_db < 0)
445                 return;
446
447         string key;
448         key = sprintf("#%d:*", e.playerid); // TODO: use hashkey instead?
449
450         string p;
451         p = db_get(playerinfo_db, key);
452         if(p == "")
453         {
454                 if(playerinfo_last)
455                 {
456                         db_put(playerinfo_db, key, playerinfo_last);
457                         strunzone(playerinfo_last);
458                 }
459                 else
460                         db_put(playerinfo_db, key, "#");
461                 playerinfo_last = strzone(ftos(e.playerid));
462                 print("  Added player ", ftos(e.playerid), " to playerinfo_db\n");//DEBUG//
463         }
464 }
465
466 void PlayerInfo_AddItem(entity e, string item_id, string val)
467 {
468         if(playerinfo_db < 0)
469                 return;
470
471         string key;
472         key = sprintf("*:%s", item_id);
473
474         string p;
475         p = db_get(playerinfo_db, key);
476         if(p == "")
477         {
478                 if(playerinfo_events_last)
479                 {
480                         db_put(playerinfo_db, key, playerinfo_events_last);
481                         strunzone(playerinfo_events_last);
482                 }
483                 else
484                         db_put(playerinfo_db, key, "#");
485                 playerinfo_events_last = strzone(item_id);
486         }
487
488         key = sprintf("#%d:%s", e.playerid, item_id);
489         db_put(playerinfo_db, key, val);
490         print("  Added item ", key, "=", val, " to playerinfo_db\n");//DEBUG//
491 }
492
493 string PlayerInfo_GetItem(entity e, string item_id)
494 {
495         if(playerinfo_db < 0)
496                 return "";
497
498         string key;
499         key = sprintf("#%d:%s",  e.playerid, item_id);
500         return db_get(playerinfo_db, key);
501 }
502
503 string PlayerInfo_GetItemLocal(string item_id)
504 {
505         entity p = spawn();
506         p.playerid = 0;
507         return PlayerInfo_GetItem(p, item_id);
508 }
509
510 void PlayerInfo_ready(entity fh, entity p, float status)
511 {
512         float n;
513         string s;
514
515         PlayerInfo_AddPlayer(p);
516
517         switch(status)
518         {
519                 case URL_READY_CANWRITE:
520                         print("-- Sending data to player stats server\n");
521                         url_fputs(fh, "V 1\n");
522 #ifdef WATERMARK
523                         url_fputs(fh, sprintf("R %s\n", WATERMARK));
524 #endif
525 #ifdef MENUQC
526                         url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
527                         url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country
528                         url_fputs(fh, sprintf("g %s\n", cvar_string("_menu_prvm_gender"))); // gender
529                         url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
530                         url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
531 #endif
532                         url_fputs(fh, "\n");
533                         url_fclose(fh);
534                         break;
535                 case URL_READY_CANREAD:
536                         print("-- Got response from player stats server:\n");
537                         string gametype = string_null;
538                         while((s = url_fgets(fh)))
539                         {
540                                 print("  ", s, "\n");
541
542                                 string key = "", value = "", data = "";
543
544                                 n = tokenizebyseparator(s, " "); // key (value) data
545                                 if (n == 1)
546                                         continue;
547                                 else if (n == 2)
548                                 {
549                                         key = argv(0);
550                                         data = argv(1);
551                                 }
552                                 else if (n >= 3)
553                                 {
554                                         key = argv(0);
555                                         value = argv(1);
556                                         data = argv(2);
557                                 }
558
559                                 if (data == "")
560                                         continue;
561
562                                 if (key == "#")
563                                         continue;
564                                 else if (key == "V")
565                                         PlayerInfo_AddItem(p, "_version", data);
566                                 else if (key == "R")
567                                         PlayerInfo_AddItem(p, "_release", data);
568                                 else if (key == "T")
569                                         PlayerInfo_AddItem(p, "_time", data);
570                                 else if (key == "S")
571                                         PlayerInfo_AddItem(p, "_statsurl", data);
572                                 else if (key == "P")
573                                         PlayerInfo_AddItem(p, "_hashkey", data);
574                                 else if (key == "n")
575                                         PlayerInfo_AddItem(p, "_playernick", data);
576                                 else if (key == "i")
577                                         PlayerInfo_AddItem(p, "_playerid", data);
578                                 else if (key == "G")
579                                         gametype = data;
580                                 else if (key == "e" && value != "")
581                                 {
582                                         if (gametype == "")
583                                                 PlayerInfo_AddItem(p, value, data);
584                                         else
585                                                 PlayerInfo_AddItem(p, sprintf("%s/%s", gametype, value), data);
586                                 }
587                                 else
588                                         continue;
589                         }
590                         print("-- End of response.\n");
591                         url_fclose(fh);
592                         break;
593                 case URL_READY_CLOSED:
594                         // url_fclose has finished
595                         print("Player stats synchronized with server\n");
596                         break;
597                 case URL_READY_ERROR:
598                 default:
599                         print("Receiving player stats failed: ", ftos(status), "\n");
600                         break;
601         }
602 }
603
604 void PlayerInfo_Init()
605 {
606         playerinfo_db = -1;
607         playerinfo_db = db_create();
608 }
609
610 #ifdef SVQC
611 void PlayerInfo_Basic(entity p)
612 {
613         print("-- Getting basic PlayerInfo for player ",ftos(p.playerid)," (SVQC)\n");
614
615         if(playerinfo_db < 0)
616                 return;
617
618         string uri;
619         uri = autocvar_g_playerinfo_uri;
620         if(uri != "" && p.crypto_idfp != "")
621         {
622                 uri = strcat(uri, "/elo/", uri_escape(p.crypto_idfp));
623                 print("Retrieving playerstats from URL: ", uri, "\n");
624                 url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p);
625         }
626 }
627 #endif
628
629 #ifdef MENUQC
630 void PlayerInfo_Details()
631 {
632         print("-- Getting detailed PlayerInfo for local player (MENUQC)\n");
633
634         if(playerinfo_db < 0)
635                 return;
636
637         string uri;
638         uri = autocvar_g_playerinfo_uri; // FIXME
639         if(uri != "" && crypto_getmyidstatus(0) > 0)
640         {
641                 //uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
642                 uri = strcat(uri, "/player/me");
643                 print("Retrieving playerstats from URL: ", uri, "\n");
644                 url_single_fopen(uri, FILE_APPEND, PlayerInfo_ready, world);
645         }
646 }
647 #endif
648
649 #ifdef CSQC
650 /*
651  * FIXME - crypto_* builtin functions missing in CSQC (csprogsdefs.qc:885)
652 void PlayerInfo_Details()
653 {
654         print("-- Getting detailed PlayerInfo for local player (CSQC)\n");
655
656         if(playerinfo_db < 0)
657                 return;
658
659         string uri;
660         uri = autocvar_g_playerinfo_uri; // FIXME
661         if(uri != "" && crypto_getmyidstatus(0) > 0)
662         {
663                 uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
664                 print("Retrieving playerstats from URL: ", uri, "\n");
665                 url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p);
666         }
667 }
668
669 #endif
670 */