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