]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/playerstats.qc
Invert playerstats_waitforme so it's not fucking retarded
[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_AddGlobalInfo(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         entity p;
182         PlayerScore_Sort(score_dummyfield, 0, 0, 0);
183         PlayerScore_Sort(scoreboard_pos, 1, 1, 1);
184         if(teamplay) { PlayerScore_TeamStats(); }
185         FOR_EACH_CLIENT(p)
186         {
187                 // add personal score rank
188                 PS_GR_P_ADDVAL(p, PLAYERSTATS_RANK, p.score_dummyfield);
189
190                 if(!p.scoreboard_pos) { continue; }
191
192                 // scoreboard is valid!
193                 PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_VALID, 1);
194
195                 // add scoreboard position
196                 PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_POS, p.scoreboard_pos);
197
198                 // add scoreboard data
199                 PlayerScore_PlayerStats(p);
200
201                 // if the match ended normally, add winning info
202                 if(finished)
203                 {
204                         PS_GR_P_ADDVAL(p, PLAYERSTATS_WINS, p.winning);
205                         PS_GR_P_ADDVAL(p, PLAYERSTATS_MATCHES, 1);
206                 }
207         }
208 }
209
210 void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that scores are added properly
211 {
212         string uri;
213         PS_GR_OUT_DB = -1;
214         playerstats_waitforme = FALSE;
215         uri = autocvar_g_playerstats_uri;
216         if(uri == "")
217                 return;
218         PS_GR_OUT_DB = db_create();
219         if(PS_GR_OUT_DB >= 0)
220                 playerstats_waitforme = TRUE; // must wait for it at match end
221
222         serverflags |= SERVERFLAG_PLAYERSTATS;
223
224         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ALIVETIME);
225         PlayerStats_GameReport_AddEvent(PLAYERSTATS_AVGLATENCY);
226         PlayerStats_GameReport_AddEvent(PLAYERSTATS_WINS);
227         PlayerStats_GameReport_AddEvent(PLAYERSTATS_MATCHES);
228         PlayerStats_GameReport_AddEvent(PLAYERSTATS_JOINS);
229         PlayerStats_GameReport_AddEvent(PLAYERSTATS_SCOREBOARD_VALID);
230         PlayerStats_GameReport_AddEvent(PLAYERSTATS_SCOREBOARD_POS);
231         PlayerStats_GameReport_AddEvent(PLAYERSTATS_RANK);
232
233     // accuracy stats
234     entity w;
235     float i;
236     for(i = WEP_FIRST; i <= WEP_LAST; ++i)
237     {
238         w = get_weaponinfo(i);
239         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-hit"));
240         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-fired"));
241         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-cnt-hit"));
242         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-cnt-fired"));
243         PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-frags"));
244     }
245
246         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3);
247         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5);
248         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10);
249         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15);
250         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20);
251         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25);
252         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30);
253         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_BOTLIKE);
254         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD);
255         PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM);
256 }
257
258 void PlayerStats_GameReport_Shutdown()
259 {
260         string uri;
261
262         if(PS_GR_OUT_DB < 0) { return; }
263
264         uri = autocvar_g_playerstats_uri;
265         if(uri != "")
266         {
267                 playerstats_waitforme = TRUE;
268                 url_multi_fopen(uri, FILE_APPEND, PlayerStats_GameReport_Handler, world);
269         }
270         else
271         {
272                 playerstats_waitforme = FALSE;
273                 db_close(PS_GR_OUT_DB);
274                 PS_GR_OUT_DB = -1;
275         }
276 }
277
278 void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
279 {
280         string t, tn;
281         string p, pn;
282         string e, en;
283         string nn, tt;
284         string s;
285
286         switch(status)
287         {
288                 // ======================================
289                 // -- OUTGOING GAME REPORT INFORMATION --
290                 // ======================================
291                 /* SPECIFICATIONS:
292                  * V: format version (always a fixed number) - this MUST be the first line!
293                  * #: comment (MUST be ignored by any parser)
294                  * R: release information on the server
295                  * G: game type
296                  * O: mod name (icon request) as in server browser
297                  * M: map name
298                  * I: match ID (see "matchid" in g_world.qc)
299                  * S: "hostname" of the server
300                  * C: number of "unpure" cvar changes
301                  * U: UDP port number of the server
302                  * D: duration of the match
303                  * L: "ladder" in which the server is participating in
304                  * P: player ID of an existing player; this also sets the owner for all following "n", "e" and "t" lines (lower case!)
305                  * Q: team number of an existing team (format: team#NN); this also sets the owner for all following "e" lines (lower case!)
306                  * i: player index
307                  * n: nickname of the player (optional)
308                  * t: team ID
309                  * e: followed by an event name, a space, and the event count/score
310                  *  event names can be:
311                  *   alivetime: total playing time of the player
312                  *   avglatency: average network latency compounded throughout the match
313                  *   wins: number of games won (can only be set if matches is set)
314                  *   matches: number of matches played to the end (not aborted by map switch)
315                  *   joins: number of matches joined (always 1 unless player never played during the match)
316                  *   scoreboardvalid: set to 1 if the player was there at the end of the match
317                  *   total-<scoreboardname>: total score of that scoreboard item
318                  *   scoreboard-<scoreboardname>: end-of-game score of that scoreboard item (can differ in non-team games)
319                  *   achievement-<achievementname>: achievement counters (their "count" is usually 1 if nonzero at all)
320                  *   kills-<index>: number of kills against the indexed player
321                  *   rank <number>: rank of player
322                  *   acc-<weapon netname>-hit: total damage dealt
323                  *   acc-<weapon netname>-fired: total damage that all fired projectiles *could* have dealt
324                  *   acc-<weapon netname>-cnt-hit: amount of shots that actually hit
325                  *   acc-<weapon netname>-cnt-fired: amount of fired shots
326                  *   acc-<weapon netname>-frags: amount of frags dealt by weapon
327                  */
328                 case URL_READY_CANWRITE:
329                 {
330                         url_fputs(fh, "V 9\n");
331                         #ifdef WATERMARK
332                         url_fputs(fh, sprintf("R %s\n", WATERMARK));
333                         #endif
334                         url_fputs(fh, sprintf("G %s\n", GetGametype()));
335                         url_fputs(fh, sprintf("O %s\n", modname));
336                         url_fputs(fh, sprintf("M %s\n", GetMapname()));
337                         url_fputs(fh, sprintf("I %s\n", matchid));
338                         url_fputs(fh, sprintf("S %s\n", cvar_string("hostname")));
339                         url_fputs(fh, sprintf("C %d\n", cvar_purechanges_count));
340                         url_fputs(fh, sprintf("U %d\n", cvar("port")));
341                         url_fputs(fh, sprintf("D %f\n", max(0, time - game_starttime)));
342                         url_fputs(fh, sprintf("L %s\n", autocvar_g_playerstats_ladder));
343
344                         // TEAMS
345                         if(teamplay)
346                         {
347                                 for(t = PS_GR_OUT_TL; (tn = db_get(PS_GR_OUT_DB, sprintf("%d", stof(t)))) != ""; t = tn)
348                                 {
349                                         // start team section
350                                         url_fputs(fh, sprintf("Q team#%s\n", t));
351
352                                         // output team events // todo: does this do unnecessary loops? perhaps we should do a separate "team_events_last" tracker..."
353                                         for(e = PS_GR_OUT_EVL; (en = db_get(PS_GR_OUT_DB, sprintf("*:%s", e))) != ""; e = en)
354                                         {
355                                                 float v = stof(db_get(PS_GR_OUT_DB, sprintf("team#%d:%s", stof(t), e)));
356                                                 if(v != 0) { url_fputs(fh, sprintf("e %s %g\n", e, v)); }
357                                         }
358                                 }
359                         }
360
361                         // PLAYERS
362                         for(p = PS_GR_OUT_PL; (pn = db_get(PS_GR_OUT_DB, sprintf("%s:*", p))) != ""; p = pn)
363                         {
364                                 // start player section
365                                 url_fputs(fh, sprintf("P %s\n", p));
366
367                                 // playerid/index (entity number for this server)
368                                 nn = db_get(PS_GR_OUT_DB, sprintf("%s:_playerid", p));
369                                 if(nn != "") { url_fputs(fh, sprintf("i %s\n", nn)); }
370
371                                 // player name 
372                                 nn = db_get(PS_GR_OUT_DB, sprintf("%s:_netname", p));
373                                 if(nn != "") { url_fputs(fh, sprintf("n %s\n", nn)); }
374
375                                 // team identification number
376                                 if(teamplay)
377                                 {
378                                         tt = db_get(PS_GR_OUT_DB, sprintf("%s:_team", p));
379                                         url_fputs(fh, sprintf("t %s\n", tt));
380                                 }
381
382                                 // output player events
383                                 for(e = PS_GR_OUT_EVL; (en = db_get(PS_GR_OUT_DB, sprintf("*:%s", e))) != ""; e = en)
384                                 {
385                                         float v = stof(db_get(PS_GR_OUT_DB, sprintf("%s:%s", p, e)));
386                                         if(v != 0) { url_fputs(fh, sprintf("e %s %g\n", e, v)); }
387                                 }
388                         }
389                         url_fputs(fh, "\n");
390                         url_fclose(fh);
391                         break;
392                 }
393
394                 // ======================================
395                 // -- INCOMING GAME REPORT INFORMATION --
396                 // ======================================
397                 /* SPECIFICATIONS:
398                  * stuff
399                  */
400                 case URL_READY_CANREAD:
401                 {
402                         // url_fclose is processing, we got a response for writing the data
403                         // this must come from HTTP
404                         print("Got response from player stats server:\n");
405                         while((s = url_fgets(fh))) { print("  ", s, "\n"); }
406                         print("End of response.\n");
407                         url_fclose(fh);
408                         break;
409                 }
410                 
411                 case URL_READY_CLOSED:
412                 {
413                         // url_fclose has finished
414                         print("Player stats written\n");
415                         playerstats_waitforme = FALSE;
416                         db_close(PS_GR_OUT_DB);
417                         PS_GR_OUT_DB = -1;
418                         break;
419                 }
420                 
421                 case URL_READY_ERROR:
422                 default:
423                 {
424                         print("Player stats writing failed: ", ftos(status), "\n");
425                         playerstats_waitforme = FALSE;
426                         if(PS_GR_OUT_DB >= 0)
427                         {
428                                 db_close(PS_GR_OUT_DB);
429                                 PS_GR_OUT_DB = -1;
430                         }
431                         break;
432                 }
433         }
434 }
435 #endif // SVQC
436 /*
437 void PlayerInfo_AddPlayer(entity e)
438 {
439         if(playerinfo_db < 0)
440                 return;
441
442         string key;
443         key = sprintf("#%d:*", e.playerid); // TODO: use hashkey instead?
444
445         string p;
446         p = db_get(playerinfo_db, key);
447         if(p == "")
448         {
449                 if(playerinfo_last)
450                 {
451                         db_put(playerinfo_db, key, playerinfo_last);
452                         strunzone(playerinfo_last);
453                 }
454                 else
455                         db_put(playerinfo_db, key, "#");
456                 playerinfo_last = strzone(ftos(e.playerid));
457                 print("  Added player ", ftos(e.playerid), " to playerinfo_db\n");//DEBUG//
458         }
459 }
460
461 void PlayerInfo_AddItem(entity e, string item_id, string val)
462 {
463         if(playerinfo_db < 0)
464                 return;
465
466         string key;
467         key = sprintf("*:%s", item_id);
468
469         string p;
470         p = db_get(playerinfo_db, key);
471         if(p == "")
472         {
473                 if(playerinfo_events_last)
474                 {
475                         db_put(playerinfo_db, key, playerinfo_events_last);
476                         strunzone(playerinfo_events_last);
477                 }
478                 else
479                         db_put(playerinfo_db, key, "#");
480                 playerinfo_events_last = strzone(item_id);
481         }
482
483         key = sprintf("#%d:%s", e.playerid, item_id);
484         db_put(playerinfo_db, key, val);
485         print("  Added item ", key, "=", val, " to playerinfo_db\n");//DEBUG//
486 }
487
488 string PlayerInfo_GetItem(entity e, string item_id)
489 {
490         if(playerinfo_db < 0)
491                 return "";
492
493         string key;
494         key = sprintf("#%d:%s",  e.playerid, item_id);
495         return db_get(playerinfo_db, key);
496 }
497
498 string PlayerInfo_GetItemLocal(string item_id)
499 {
500         entity p = spawn();
501         p.playerid = 0;
502         return PlayerInfo_GetItem(p, item_id);
503 }
504
505 void PlayerInfo_ready(entity fh, entity p, float status)
506 {
507         float n;
508         string s;
509
510         PlayerInfo_AddPlayer(p);
511
512         switch(status)
513         {
514                 case URL_READY_CANWRITE:
515                         print("-- Sending data to player stats server\n");
516                         url_fputs(fh, "V 1\n");
517 #ifdef WATERMARK
518                         url_fputs(fh, sprintf("R %s\n", WATERMARK));
519 #endif
520 #ifdef MENUQC
521                         url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
522                         url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country
523                         url_fputs(fh, sprintf("g %s\n", cvar_string("_menu_prvm_gender"))); // gender
524                         url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
525                         url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
526 #endif
527                         url_fputs(fh, "\n");
528                         url_fclose(fh);
529                         break;
530                 case URL_READY_CANREAD:
531                         print("-- Got response from player stats server:\n");
532                         string gametype = string_null;
533                         while((s = url_fgets(fh)))
534                         {
535                                 print("  ", s, "\n");
536
537                                 string key = "", value = "", data = "";
538
539                                 n = tokenizebyseparator(s, " "); // key (value) data
540                                 if (n == 1)
541                                         continue;
542                                 else if (n == 2)
543                                 {
544                                         key = argv(0);
545                                         data = argv(1);
546                                 }
547                                 else if (n >= 3)
548                                 {
549                                         key = argv(0);
550                                         value = argv(1);
551                                         data = argv(2);
552                                 }
553
554                                 if (data == "")
555                                         continue;
556
557                                 if (key == "#")
558                                         continue;
559                                 else if (key == "V")
560                                         PlayerInfo_AddItem(p, "_version", data);
561                                 else if (key == "R")
562                                         PlayerInfo_AddItem(p, "_release", data);
563                                 else if (key == "T")
564                                         PlayerInfo_AddItem(p, "_time", data);
565                                 else if (key == "S")
566                                         PlayerInfo_AddItem(p, "_statsurl", data);
567                                 else if (key == "P")
568                                         PlayerInfo_AddItem(p, "_hashkey", data);
569                                 else if (key == "n")
570                                         PlayerInfo_AddItem(p, "_playernick", data);
571                                 else if (key == "i")
572                                         PlayerInfo_AddItem(p, "_playerid", data);
573                                 else if (key == "G")
574                                         gametype = data;
575                                 else if (key == "e" && value != "")
576                                 {
577                                         if (gametype == "")
578                                                 PlayerInfo_AddItem(p, value, data);
579                                         else
580                                                 PlayerInfo_AddItem(p, sprintf("%s/%s", gametype, value), data);
581                                 }
582                                 else
583                                         continue;
584                         }
585                         print("-- End of response.\n");
586                         url_fclose(fh);
587                         break;
588                 case URL_READY_CLOSED:
589                         // url_fclose has finished
590                         print("Player stats synchronized with server\n");
591                         break;
592                 case URL_READY_ERROR:
593                 default:
594                         print("Receiving player stats failed: ", ftos(status), "\n");
595                         break;
596         }
597 }
598
599 void PlayerInfo_Init()
600 {
601         playerinfo_db = -1;
602         playerinfo_db = db_create();
603 }
604
605 #ifdef SVQC
606 void PlayerInfo_Basic(entity p)
607 {
608         print("-- Getting basic PlayerInfo for player ",ftos(p.playerid)," (SVQC)\n");
609
610         if(playerinfo_db < 0)
611                 return;
612
613         string uri;
614         uri = autocvar_g_playerinfo_uri;
615         if(uri != "" && p.crypto_idfp != "")
616         {
617                 uri = strcat(uri, "/elo/", uri_escape(p.crypto_idfp));
618                 print("Retrieving playerstats from URL: ", uri, "\n");
619                 url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p);
620         }
621 }
622 #endif
623
624 #ifdef MENUQC
625 void PlayerInfo_Details()
626 {
627         print("-- Getting detailed PlayerInfo for local player (MENUQC)\n");
628
629         if(playerinfo_db < 0)
630                 return;
631
632         string uri;
633         uri = autocvar_g_playerinfo_uri; // FIXME
634         if(uri != "" && crypto_getmyidstatus(0) > 0)
635         {
636                 //uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
637                 uri = strcat(uri, "/player/me");
638                 print("Retrieving playerstats from URL: ", uri, "\n");
639                 url_single_fopen(uri, FILE_APPEND, PlayerInfo_ready, world);
640         }
641 }
642 #endif
643
644 #ifdef CSQC
645 /*
646  * FIXME - crypto_* builtin functions missing in CSQC (csprogsdefs.qc:885)
647 void PlayerInfo_Details()
648 {
649         print("-- Getting detailed PlayerInfo for local player (CSQC)\n");
650
651         if(playerinfo_db < 0)
652                 return;
653
654         string uri;
655         uri = autocvar_g_playerinfo_uri; // FIXME
656         if(uri != "" && crypto_getmyidstatus(0) > 0)
657         {
658                 uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
659                 print("Retrieving playerstats from URL: ", uri, "\n");
660                 url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p);
661         }
662 }
663
664 #endif
665 */