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