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