]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/playerstats.qc
Merge branch 'master' into Mario/qc_updates
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / playerstats.qc
index 4f4a76cd1995f16f1454b91b71a08183b9772f54..5890fb89e17085271e6061684e3792a6bcc0a735 100644 (file)
-#ifdef SVQC
-
-float playerstats_db;
-string teamstats_last;
-string playerstats_last;
-string events_last;
-.float playerstats_addedglobalinfo;
-.string playerstats_id;
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+       #include "../dpdefs/progsdefs.qh"
+    #include "../dpdefs/dpextensions.qh"
+    #include "constants.qh"
+    #include "util.qh"
+    #include "urllib.qh"
+    #include "weapons/weapons.qh"
+    #include "../server/weapons/accuracy.qh"
+    #include "../server/defs.qh"
+    #include "playerstats.qh"
+    #include "../server/scores.qh"
+#endif
 
-void PlayerStats_Init() // initiated before InitGameplayMode so that scores are added properly
+#ifdef SVQC
+void PlayerStats_Prematch(void)
 {
-       string uri;
-       playerstats_db = -1;
-       playerstats_waitforme = TRUE;
-       uri = autocvar_g_playerstats_uri;
-       if(uri == "")
-               return;
-       playerstats_db = db_create();
-       if(playerstats_db >= 0)
-               playerstats_waitforme = FALSE; // must wait for it at match end
-
-       serverflags |= SERVERFLAG_PLAYERSTATS;
-
-       PlayerStats_AddEvent(PLAYERSTATS_ALIVETIME);
-       PlayerStats_AddEvent(PLAYERSTATS_AVGLATENCY);
-       PlayerStats_AddEvent(PLAYERSTATS_WINS);
-       PlayerStats_AddEvent(PLAYERSTATS_MATCHES);
-       PlayerStats_AddEvent(PLAYERSTATS_JOINS);
-       PlayerStats_AddEvent(PLAYERSTATS_SCOREBOARD_VALID);
-       PlayerStats_AddEvent(PLAYERSTATS_SCOREBOARD_POS);
-       PlayerStats_AddEvent(PLAYERSTATS_RANK);
-
-    // accuracy stats
-    entity w;
-    float i;
-    for(i = WEP_FIRST; i <= WEP_LAST; ++i)
-    {
-        w = get_weaponinfo(i);
-
-        PlayerStats_AddEvent(strcat("acc-", w.netname, "-hit"));
-        PlayerStats_AddEvent(strcat("acc-", w.netname, "-fired"));
-
-        PlayerStats_AddEvent(strcat("acc-", w.netname, "-cnt-hit"));
-        PlayerStats_AddEvent(strcat("acc-", w.netname, "-cnt-fired"));
-
-        PlayerStats_AddEvent(strcat("acc-", w.netname, "-frags"));
-    }
-
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_BOTLIKE);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD);
-       PlayerStats_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM);
+       //foobar
 }
 
-void PlayerStats_AddPlayer(entity e)
+void PlayerStats_GameReport_AddPlayer(entity e)
 {
-       string s;
+       if((PS_GR_OUT_DB < 0) || (e.playerstats_id)) { return; }
 
-       if(playerstats_db < 0)
-               return;
-       if(e.playerstats_id)
-               return;
+       // set up player identification
+       string s = string_null;
 
-       s = string_null;
-       if(e.crypto_idfp != "" && e.cvar_cl_allow_uidtracking == 1)
-               s = e.crypto_idfp;
+       if((e.crypto_idfp != "") && (e.cvar_cl_allow_uidtracking == 1))
+               { s = e.crypto_idfp; }
        else if(IS_BOT_CLIENT(e))
-               s = sprintf("bot#%g#%s", skill, e.cleanname);
+               { s = sprintf("bot#%g#%s", skill, e.cleanname); }
 
        if((s == "") || find(world, playerstats_id, s)) // already have one of the ID - next one can't be tracked then!
        {
                if(IS_BOT_CLIENT(e))
-                       s = sprintf("bot#%d", e.playerid);
+                       { s = sprintf("bot#%d", e.playerid); }
                else
-                       s = sprintf("player#%d", e.playerid);
+                       { s = sprintf("player#%d", e.playerid); }
        }
 
        e.playerstats_id = strzone(s);
 
-       string key;
-       key = sprintf("%s:*", e.playerstats_id);
+       // now add the player to the database
+       string key = sprintf("%s:*", e.playerstats_id);
+       string p = db_get(PS_GR_OUT_DB, key);
 
-       string p;
-       p = db_get(playerstats_db, key);
        if(p == "")
        {
-               if(playerstats_last)
+               if(PS_GR_OUT_PL)
                {
-                       db_put(playerstats_db, key, playerstats_last);
-                       strunzone(playerstats_last);
+                       db_put(PS_GR_OUT_DB, key, PS_GR_OUT_PL);
+                       strunzone(PS_GR_OUT_PL);
                }
-               else
-                       db_put(playerstats_db, key, "#");
-               playerstats_last = strzone(e.playerstats_id);
+               else { db_put(PS_GR_OUT_DB, key, "#"); }
+               PS_GR_OUT_PL = strzone(e.playerstats_id);
        }
 }
 
-void PlayerStats_AddTeam(float t)
+void PlayerStats_GameReport_AddTeam(float t)
 {
-       if(playerstats_db < 0)
-               return;
+       if(PS_GR_OUT_DB < 0) { return; }
 
-       string key;
-       key = sprintf("%d", t);
+       string key = sprintf("%d", t);
+       string p = db_get(PS_GR_OUT_DB, key);
 
-       string p;
-       p = db_get(playerstats_db, key);
        if(p == "")
        {
-               if(teamstats_last)
+               if(PS_GR_OUT_TL)
                {
-                       db_put(playerstats_db, key, teamstats_last);
-                       strunzone(teamstats_last);
+                       db_put(PS_GR_OUT_DB, key, PS_GR_OUT_TL);
+                       strunzone(PS_GR_OUT_TL);
                }
-               else
-                       db_put(playerstats_db, key, "#");
-               teamstats_last = strzone(key);
+               else { db_put(PS_GR_OUT_DB, key, "#"); }
+               PS_GR_OUT_TL = strzone(key);
        }
 }
 
-void PlayerStats_AddEvent(string event_id)
+void PlayerStats_GameReport_AddEvent(string event_id)
 {
-       if(playerstats_db < 0)
-               return;
+       if(PS_GR_OUT_DB < 0) { return; }
 
-       string key;
-       key = sprintf("*:%s", event_id);
+       string key = sprintf("*:%s", event_id);
+       string p = db_get(PS_GR_OUT_DB, key);
 
-       string p;
-       p = db_get(playerstats_db, key);
        if(p == "")
        {
-               if(events_last)
+               if(PS_GR_OUT_EVL)
                {
-                       db_put(playerstats_db, key, events_last);
-                       strunzone(events_last);
+                       db_put(PS_GR_OUT_DB, key, PS_GR_OUT_EVL);
+                       strunzone(PS_GR_OUT_EVL);
                }
-               else
-                       db_put(playerstats_db, key, "#");
-               events_last = strzone(event_id);
+               else { db_put(PS_GR_OUT_DB, key, "#"); }
+               PS_GR_OUT_EVL = strzone(event_id);
        }
 }
 
-float PlayerStats_Event(entity e, string event_id, float value)
+// referred to by PS_GR_P_ADDVAL and PS_GR_T_ADDVAL
+float PlayerStats_GameReport_Event(string prefix, string event_id, float value)
 {
-       if((e.playerstats_id == "") || playerstats_db < 0)
-               return 0;
+       if((prefix == "") || PS_GR_OUT_DB < 0) { return 0; }
 
-       string key;
-       float val;
-       key = sprintf("%s:%s", e.playerstats_id, event_id);
-       val = stof(db_get(playerstats_db, key));
+       string key = sprintf("%s:%s", prefix, event_id);
+       float val = stof(db_get(PS_GR_OUT_DB, key));
        val += value;
-       db_put(playerstats_db, key, ftos(val));
+       db_put(PS_GR_OUT_DB, key, ftos(val));
        return val;
 }
 
-float PlayerStats_TeamScore(float t, string event_id, float value)
+void PlayerStats_GameReport_Accuracy(entity p)
 {
-       if(playerstats_db < 0)
-               return 0;
+       int i;
 
-       string key;
-       float val;
-       key = sprintf("team#%d:%s", t, event_id);
-       val = stof(db_get(playerstats_db, key));
-       val += value;
-       db_put(playerstats_db, key, ftos(val));
-       return val;
+       for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+       {
+               entity w = get_weaponinfo(i);
+
+               #define ACCMAC(suffix,field) \
+                       PS_GR_P_ADDVAL(p, sprintf("acc-%s-%s", w.netname, suffix), p.accuracy.(field[i-1]));
+
+               ACCMAC("hit", accuracy_hit)
+               ACCMAC("fired", accuracy_fired)
+               ACCMAC("cnt-hit", accuracy_cnt_hit)
+               ACCMAC("cnt-fired", accuracy_cnt_fired)
+               ACCMAC("frags", accuracy_frags)
+
+               #undef ACCMAC
+       }
 }
 
-/*
-       format spec:
-
-       A collection of lines of the format <key> SPACE <value> NEWLINE, where
-       <key> is always a single character.
-
-       The following keys are defined:
-
-       V: format version (always a fixed number) - this MUST be the first line!
-       #: comment (MUST be ignored by any parser)
-       R: release information on the server
-       G: game type
-       O: mod name (icon request) as in server browser
-       M: map name
-       I: match ID (see "matchid" in g_world.qc
-       S: "hostname" of the server
-       C: number of "unpure" cvar changes
-       U: UDP port number of the server
-       D: duration of the match
-       P: player ID of an existing player; this also sets the owner for all following "n", "e" and "t" lines (lower case!)
-       Q: team number of an existing team (format: team#NN); this also sets the owner for all following "e" lines (lower case!)
-       n: nickname of the player (optional)
-       t: team ID
-       i: player index
-       e: followed by an event name, a space, and the event count/score
-               event names can be:
-                       alivetime: total playing time of the player
-                       avglatency: average network latency compounded throughout the match
-                       wins: number of games won (can only be set if matches is set)
-                       matches: number of matches played to the end (not aborted by map switch)
-                       joins: number of matches joined (always 1 unless player never played during the match)
-                       scoreboardvalid: set to 1 if the player was there at the end of the match
-                       total-<scoreboardname>: total score of that scoreboard item
-                       scoreboard-<scoreboardname>: end-of-game score of that scoreboard item (can differ in non-team games)
-                       achievement-<achievementname>: achievement counters (their "count" is usually 1 if nonzero at all)
-                       kills-<index>: number of kills against the indexed player
-                       rank <number>: rank of player
-                       acc-<weapon netname>-hit: total damage dealt
-                       acc-<weapon netname>-fired: total damage that all fired projectiles *could* have dealt
-                       acc-<weapon netname>-cnt-hit: amount of shots that actually hit
-                       acc-<weapon netname>-cnt-fired: amount of fired shots
-                       acc-<weapon netname>-frags: amount of frags dealt by weapon
-
-       Response format (not used yet): see https://gist.github.com/4284222
-*/
+void PlayerStats_GameReport_FinalizePlayer(entity p)
+{
+       if((p.playerstats_id == "") || PS_GR_OUT_DB < 0) { return; }
+
+       // add global info!
+       if(p.alivetime)
+       {
+               PS_GR_P_ADDVAL(p, PLAYERSTATS_ALIVETIME, time - p.alivetime);
+               p.alivetime = 0;
+       }
+
+       db_put(PS_GR_OUT_DB, sprintf("%s:_playerid", p.playerstats_id), ftos(p.playerid));
+
+       if(p.cvar_cl_allow_uid2name == 1 || IS_BOT_CLIENT(p))
+               db_put(PS_GR_OUT_DB, sprintf("%s:_netname", p.playerstats_id), p.netname);
+
+       if(teamplay)
+               db_put(PS_GR_OUT_DB, sprintf("%s:_team", p.playerstats_id), ftos(p.team));
+
+       if(stof(db_get(PS_GR_OUT_DB, sprintf("%s:%s", p.playerstats_id, PLAYERSTATS_ALIVETIME))) > 0)
+               PS_GR_P_ADDVAL(p, PLAYERSTATS_JOINS, 1);
+
+       PlayerStats_GameReport_Accuracy(p);
+
+       if(IS_REAL_CLIENT(p))
+       {
+               if(p.latency_cnt)
+               {
+                       float latency = (p.latency_sum / p.latency_cnt);
+                       if(latency) { PS_GR_P_ADDVAL(p, PLAYERSTATS_AVGLATENCY, latency); }
+               }
+       }
+
+       strunzone(p.playerstats_id);
+       p.playerstats_id = string_null;
+}
+
+void PlayerStats_GameReport(float finished)
+{
+       if(PS_GR_OUT_DB < 0) { return; }
+
+       PlayerScore_Sort(score_dummyfield, 0, 0, 0);
+       PlayerScore_Sort(scoreboard_pos, 1, 1, 1);
+       if(teamplay) { PlayerScore_TeamStats(); }
+
+       entity p;
+       FOR_EACH_CLIENT(p)
+       {
+               // add personal score rank
+               PS_GR_P_ADDVAL(p, PLAYERSTATS_RANK, p.score_dummyfield);
+
+               // scoreboard data
+               if(p.scoreboard_pos)
+               {
+                       // scoreboard is valid!
+                       PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_VALID, 1);
+
+                       // add scoreboard position
+                       PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_POS, p.scoreboard_pos);
+
+                       // add scoreboard data
+                       PlayerScore_PlayerStats(p);
+
+                       // if the match ended normally, add winning info
+                       if(finished)
+                       {
+                               PS_GR_P_ADDVAL(p, PLAYERSTATS_WINS, p.winning);
+                               PS_GR_P_ADDVAL(p, PLAYERSTATS_MATCHES, 1);
+                       }
+               }
+
+               // collect final player information
+               PlayerStats_GameReport_FinalizePlayer(p);
+       }
+
+       if(autocvar_g_playerstats_gamereport_uri != "")
+       {
+               PlayerStats_GameReport_DelayMapVote = true;
+               url_multi_fopen(
+                       autocvar_g_playerstats_gamereport_uri,
+                       FILE_APPEND,
+                       PlayerStats_GameReport_Handler,
+                       world
+               );
+       }
+       else
+       {
+               PlayerStats_GameReport_DelayMapVote = false;
+               db_close(PS_GR_OUT_DB);
+               PS_GR_OUT_DB = -1;
+       }
+}
+
+void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that scores are added properly
+{
+       if(autocvar_g_playerstats_gamereport_uri == "") { return; }
+
+       PS_GR_OUT_DB = db_create();
+
+       if(PS_GR_OUT_DB >= 0)
+       {
+               PlayerStats_GameReport_DelayMapVote = true;
+
+               serverflags |= SERVERFLAG_PLAYERSTATS;
+
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ALIVETIME);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_AVGLATENCY);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_WINS);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_MATCHES);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_JOINS);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_SCOREBOARD_VALID);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_SCOREBOARD_POS);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_RANK);
+
+               // accuracy stats
+               entity w;
+               float i;
+               for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+               {
+                       w = get_weaponinfo(i);
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-hit"));
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-fired"));
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-cnt-hit"));
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-cnt-fired"));
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", w.netname, "-frags"));
+               }
 
-void PlayerStats_ready(entity fh, entity pass, float status)
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_BOTLIKE);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD);
+               PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM);
+       }
+       else { PlayerStats_GameReport_DelayMapVote = false; }
+}
+
+void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
 {
        string t, tn;
        string p, pn;
@@ -231,11 +279,52 @@ void PlayerStats_ready(entity fh, entity pass, float status)
 
        switch(status)
        {
+               // ======================================
+               // -- OUTGOING GAME REPORT INFORMATION --
+               // ======================================
+               /* SPECIFICATIONS:
+                * V: format version (always a fixed number) - this MUST be the first line!
+                * #: comment (MUST be ignored by any parser)
+                * R: release information on the server
+                * G: game type
+                * O: mod name (icon request) as in server browser
+                * M: map name
+                * I: match ID (see "matchid" in g_world.qc)
+                * S: "hostname" of the server
+                * C: number of "unpure" cvar changes
+                * U: UDP port number of the server
+                * D: duration of the match
+                * L: "ladder" in which the server is participating in
+                * P: player ID of an existing player; this also sets the owner for all following "n", "e" and "t" lines (lower case!)
+                * Q: team number of an existing team (format: team#NN); this also sets the owner for all following "e" lines (lower case!)
+                * i: player index
+                * n: nickname of the player (optional)
+                * t: team ID
+                * e: followed by an event name, a space, and the event count/score
+                *  event names can be:
+                *   alivetime: total playing time of the player
+                *   avglatency: average network latency compounded throughout the match
+                *   wins: number of games won (can only be set if matches is set)
+                *   matches: number of matches played to the end (not aborted by map switch)
+                *   joins: number of matches joined (always 1 unless player never played during the match)
+                *   scoreboardvalid: set to 1 if the player was there at the end of the match
+                *   total-<scoreboardname>: total score of that scoreboard item
+                *   scoreboard-<scoreboardname>: end-of-game score of that scoreboard item (can differ in non-team games)
+                *   achievement-<achievementname>: achievement counters (their "count" is usually 1 if nonzero at all)
+                *   kills-<index>: number of kills against the indexed player
+                *   rank <number>: rank of player
+                *   acc-<weapon netname>-hit: total damage dealt
+                *   acc-<weapon netname>-fired: total damage that all fired projectiles *could* have dealt
+                *   acc-<weapon netname>-cnt-hit: amount of shots that actually hit
+                *   acc-<weapon netname>-cnt-fired: amount of fired shots
+                *   acc-<weapon netname>-frags: amount of frags dealt by weapon
+                */
                case URL_READY_CANWRITE:
-                       url_fputs(fh, "V 8\n");
-#ifdef WATERMARK
+               {
+                       url_fputs(fh, "V 9\n");
+                       #ifdef WATERMARK
                        url_fputs(fh, sprintf("R %s\n", WATERMARK));
-#endif
+                       #endif
                        url_fputs(fh, sprintf("G %s\n", GetGametype()));
                        url_fputs(fh, sprintf("O %s\n", modname));
                        url_fputs(fh, sprintf("M %s\n", GetMapname()));
@@ -244,208 +333,503 @@ void PlayerStats_ready(entity fh, entity pass, float status)
                        url_fputs(fh, sprintf("C %d\n", cvar_purechanges_count));
                        url_fputs(fh, sprintf("U %d\n", cvar("port")));
                        url_fputs(fh, sprintf("D %f\n", max(0, time - game_starttime)));
+                       url_fputs(fh, sprintf("L %s\n", autocvar_g_playerstats_gamereport_ladder));
+
+                       // TEAMS
                        if(teamplay)
                        {
-                               for(t = teamstats_last; (tn = db_get(playerstats_db, sprintf("%d", stof(t)))) != ""; t = tn)
+                               for(t = PS_GR_OUT_TL; (tn = db_get(PS_GR_OUT_DB, sprintf("%d", stof(t)))) != ""; t = tn)
                                {
+                                       // start team section
                                        url_fputs(fh, sprintf("Q team#%s\n", t));
-                                       for(e = events_last; (en = db_get(playerstats_db, sprintf("*:%s", e))) != ""; e = en)
+
+                                       // output team events // todo: does this do unnecessary loops? perhaps we should do a separate "team_events_last" tracker..."
+                                       for(e = PS_GR_OUT_EVL; (en = db_get(PS_GR_OUT_DB, sprintf("*:%s", e))) != ""; e = en)
                                        {
-                                               float v;
-                                               v = stof(db_get(playerstats_db, sprintf("team#%d:%s", stof(t), e)));
-                                               if(v != 0)
-                                                       url_fputs(fh, sprintf("e %s %g\n", e, v));
+                                               float v = stof(db_get(PS_GR_OUT_DB, sprintf("team#%d:%s", stof(t), e)));
+                                               if(v != 0) { url_fputs(fh, sprintf("e %s %g\n", e, v)); }
                                        }
                                }
                        }
-                       for(p = playerstats_last; (pn = db_get(playerstats_db, sprintf("%s:*", p))) != ""; p = pn)
+
+                       // PLAYERS
+                       for(p = PS_GR_OUT_PL; (pn = db_get(PS_GR_OUT_DB, sprintf("%s:*", p))) != ""; p = pn)
                        {
+                               // start player section
                                url_fputs(fh, sprintf("P %s\n", p));
-                               nn = db_get(playerstats_db, sprintf("%s:_playerid", p));
-                               if(nn != "")
-                                       url_fputs(fh, sprintf("i %s\n", nn));
-                               nn = db_get(playerstats_db, sprintf("%s:_netname", p));
-                               if(nn != "")
-                                       url_fputs(fh, sprintf("n %s\n", nn));
+
+                               // playerid/index (entity number for this server)
+                               nn = db_get(PS_GR_OUT_DB, sprintf("%s:_playerid", p));
+                               if(nn != "") { url_fputs(fh, sprintf("i %s\n", nn)); }
+
+                               // player name
+                               nn = db_get(PS_GR_OUT_DB, sprintf("%s:_netname", p));
+                               if(nn != "") { url_fputs(fh, sprintf("n %s\n", nn)); }
+
+                               // team identification number
                                if(teamplay)
                                {
-                                       tt = db_get(playerstats_db, sprintf("%s:_team", p));
+                                       tt = db_get(PS_GR_OUT_DB, sprintf("%s:_team", p));
                                        url_fputs(fh, sprintf("t %s\n", tt));
                                }
-                               for(e = events_last; (en = db_get(playerstats_db, sprintf("*:%s", e))) != ""; e = en)
+
+                               // output player events
+                               for(e = PS_GR_OUT_EVL; (en = db_get(PS_GR_OUT_DB, sprintf("*:%s", e))) != ""; e = en)
                                {
-                                       float v;
-                                       v = stof(db_get(playerstats_db, sprintf("%s:%s", p, e)));
-                                       if(v != 0)
-                                               url_fputs(fh, sprintf("e %s %g\n", e, v));
+                                       float v = stof(db_get(PS_GR_OUT_DB, sprintf("%s:%s", p, e)));
+                                       if(v != 0) { url_fputs(fh, sprintf("e %s %g\n", e, v)); }
                                }
                        }
                        url_fputs(fh, "\n");
                        url_fclose(fh);
                        break;
+               }
+
+               // ======================================
+               // -- INCOMING GAME REPORT INFORMATION --
+               // ======================================
+               /* SPECIFICATIONS:
+                * stuff
+                */
                case URL_READY_CANREAD:
+               {
                        // url_fclose is processing, we got a response for writing the data
                        // this must come from HTTP
-                       print("Got response from player stats server:\n");
-                       while((s = url_fgets(fh)))
-                               print("  ", s, "\n");
-                       print("End of response.\n");
+                       dprint("Got response from player stats server:\n");
+                       while((s = url_fgets(fh))) { dprint("  ", s, "\n"); }
+                       dprint("End of response.\n");
                        url_fclose(fh);
                        break;
+               }
+
                case URL_READY_CLOSED:
+               {
                        // url_fclose has finished
-                       print("Player stats written\n");
-                       playerstats_waitforme = TRUE;
-                       db_close(playerstats_db);
-                       playerstats_db = -1;
+                       dprint("Player stats written\n");
+                       PlayerStats_GameReport_DelayMapVote = false;
+                       if(PS_GR_OUT_DB >= 0)
+                       {
+                               db_close(PS_GR_OUT_DB);
+                               PS_GR_OUT_DB = -1;
+                       }
                        break;
+               }
+
                case URL_READY_ERROR:
                default:
+               {
                        print("Player stats writing failed: ", ftos(status), "\n");
-                       playerstats_waitforme = TRUE;
-                       if(playerstats_db >= 0)
+                       PlayerStats_GameReport_DelayMapVote = false;
+                       if(PS_GR_OUT_DB >= 0)
                        {
-                               db_close(playerstats_db);
-                               playerstats_db = -1;
+                               db_close(PS_GR_OUT_DB);
+                               PS_GR_OUT_DB = -1;
                        }
                        break;
+               }
        }
 }
 
-//#NO AUTOCVARS START
-void PlayerStats_Shutdown()
+void PlayerStats_PlayerBasic(entity joiningplayer, float newrequest)
 {
-       string uri;
-
-       if(playerstats_db < 0)
-               return;
-
-       uri = autocvar_g_playerstats_uri;
-       if(uri != "")
+       // http://stats.xonotic.org/player/GgXRw6piDtFIbMArMuiAi8JG4tiin8VLjZgsKB60Uds=/elo.txt
+       if(autocvar_g_playerstats_playerbasic_uri != "")
        {
-               playerstats_waitforme = FALSE;
-               url_multi_fopen(uri, FILE_APPEND, PlayerStats_ready, world);
+               string uri = autocvar_g_playerstats_playerbasic_uri;
+               if(joiningplayer.crypto_idfp != "")
+               {
+                       // create the database if it doesn't already exist
+                       if(PS_B_IN_DB < 0)
+                               PS_B_IN_DB = db_create();
+
+                       // now request the information
+                       uri = strcat(uri, "/player/", uri_escape(uri_escape(joiningplayer.crypto_idfp)), "/elo.txt");
+                       dprint("Retrieving playerstats from URL: ", uri, "\n");
+                       url_single_fopen(
+                               uri,
+                               FILE_APPEND,
+                               PlayerStats_PlayerBasic_Handler,
+                               joiningplayer
+                       );
+
+                       // set status appropriately // todo: check whether the player info exists in the database previously
+                       if(newrequest)
+                       {
+                               // database still contains useful information, so don't clear it of a useful status
+                               joiningplayer.playerstats_basicstatus = PS_B_STATUS_WAITING;
+                       }
+                       else
+                       {
+                               // database was previously empty or never hit received status for some reason
+                               joiningplayer.playerstats_basicstatus = PS_B_STATUS_UPDATING;
+                       }
+               }
        }
        else
        {
-               playerstats_waitforme = TRUE;
-               db_close(playerstats_db);
-               playerstats_db = -1;
+               // server has this disabled, kill the DB and set status to idle
+               if(PS_B_IN_DB >= 0)
+               {
+                       entity player;
+
+                       db_close(PS_B_IN_DB);
+                       PS_B_IN_DB = -1;
+
+                       FOR_EACH_REALCLIENT(player) { player.playerstats_basicstatus = PS_B_STATUS_IDLE; }
+               }
        }
 }
-//#NO AUTOCVARS END
 
-void PlayerStats_Accuracy(entity p)
+void PlayerStats_PlayerBasic_CheckUpdate(entity joiningplayer)
 {
-    entity a, w;
-    a = p.accuracy;
-    float i;
+       // determine whether we should retrieve playerbasic information again
+
+       #if 0
+       printf("PlayerStats_PlayerBasic_CheckUpdate('%s'): %f\n",
+               joiningplayer.netname,
+               time
+       );
+       #endif
+
+       // TODO: check to see if this playerid is inside the database already somehow...
+       // for now we'll just check the field, but this won't work for players who disconnect and reconnect properly
+       // although maybe we should just submit another request ANYWAY?
+       if(!joiningplayer.playerstats_basicstatus)
+       {
+               PlayerStats_PlayerBasic(
+                       joiningplayer,
+                       (joiningplayer.playerstats_basicstatus == PS_B_STATUS_RECEIVED)
+               );
+       }
+}
 
-    for(i = WEP_FIRST; i <= WEP_LAST; ++i)
-    {
-        w = get_weaponinfo(i);
+void PlayerStats_PlayerBasic_Handler(entity fh, entity p, float status)
+{
+       switch(status)
+       {
+               case URL_READY_CANWRITE:
+               {
+                       dprint("-- Sending data to player stats server\n");
+                       /*url_fputs(fh, "V 1\n");
+                       #ifdef WATERMARK
+                       url_fputs(fh, sprintf("R %s\n", WATERMARK));
+                       #endif
+                       url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
+                       url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country
+                       url_fputs(fh, sprintf("g %s\n", cvar_string("_cl_gender"))); // gender
+                       url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
+                       url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
+                       */url_fputs(fh, "\n");
+                       url_fclose(fh);
+                       break;
+               }
 
-        PlayerStats_Event(p, strcat("acc-", w.netname, "-hit"), a.(accuracy_hit[i-1]));
-        PlayerStats_Event(p, strcat("acc-", w.netname, "-fired"), a.(accuracy_fired[i-1]));
+               case URL_READY_CANREAD:
+               {
+                       string s = "";
+                       dprint("-- Got response from player stats server:\n");
+                       //string gametype = string_null;
+                       while((s = url_fgets(fh)))
+                       {
+                               dprint("  ", s, "\n");
+                               /*
+                               string key = "", value = "", data = "";
 
-        PlayerStats_Event(p, strcat("acc-", w.netname, "-cnt-hit"), a.(accuracy_cnt_hit[i-1]));
-        PlayerStats_Event(p, strcat("acc-", w.netname, "-cnt-fired"), a.(accuracy_cnt_fired[i-1]));
+                               n = tokenizebyseparator(s, " "); // key (value) data
+                               if (n == 1)
+                                       continue;
+                               else if (n == 2)
+                               {
+                               key = argv(0);
+                               data = argv(1);
+                               }
+                               else if (n >= 3)
+                               {
+                                                               key = argv(0);
+                                                               value = argv(1);
+                                                               data = argv(2);
+                               }
 
-        PlayerStats_Event(p, strcat("acc-", w.netname, "-frags"), a.(accuracy_frags[i-1]));
-    }
-    //backtrace(strcat("adding player stat accuracy for ", p.netname, ".\n"));
-}
+                               if (data == "")
+                               continue;
 
-void PlayerStats_AddGlobalInfo(entity p)
-{
-       if(playerstats_db < 0)
-               return;
-       if((p.playerstats_id == "") || playerstats_db < 0)
-               return;
-       p.playerstats_addedglobalinfo = TRUE;
+                               if (key == "#")
+                               continue;
+                               else if (key == "V")
+                               PlayerInfo_AddItem(p, "_version", data);
+                               else if (key == "R")
+                               PlayerInfo_AddItem(p, "_release", data);
+                               else if (key == "T")
+                               PlayerInfo_AddItem(p, "_time", data);
+                               else if (key == "S")
+                               PlayerInfo_AddItem(p, "_statsurl", data);
+                               else if (key == "P")
+                               PlayerInfo_AddItem(p, "_hashkey", data);
+                               else if (key == "n")
+                               PlayerInfo_AddItem(p, "_playernick", data);
+                               else if (key == "i")
+                               PlayerInfo_AddItem(p, "_playerid", data);
+                               else if (key == "G")
+                               gametype = data;
+                               else if (key == "e" && value != "")
+                               {
+                               if (gametype == "")
+                               PlayerInfo_AddItem(p, value, data);
+                               else
+                               PlayerInfo_AddItem(p, sprintf("%s/%s", gametype, value), data);
+                               }
+                               else
+                               continue;
+                               */
+                       }
+                       dprint("-- End of response.\n");
+                       url_fclose(fh);
+                       break;
+               }
+               case URL_READY_CLOSED:
+               {
+                       // url_fclose has finished
+                       print("Player stats synchronized with server\n");
+                       break;
+               }
 
-       // add global info!
-       if(p.alivetime)
-       {
-               PlayerStats_Event(p, PLAYERSTATS_ALIVETIME, time - p.alivetime);
-               p.alivetime = 0;
+               case URL_READY_ERROR:
+               default:
+               {
+                       print("Receiving player stats failed: ", ftos(status), "\n");
+                       break;
+               }
        }
+}
+#endif // SVQC
 
-       db_put(playerstats_db, sprintf("%s:_playerid", p.playerstats_id), ftos(p.playerid));
-
-       if(p.cvar_cl_allow_uid2name == 1 || IS_BOT_CLIENT(p))
-               db_put(playerstats_db, sprintf("%s:_netname", p.playerstats_id), p.netname);
+#ifdef MENUQC
 
-       if(teamplay)
-               db_put(playerstats_db, sprintf("%s:_team", p.playerstats_id), ftos(p.team));
 
-       if(stof(db_get(playerstats_db, sprintf("%d:%s", p.playerstats_id, PLAYERSTATS_ALIVETIME))) > 0)
-               PlayerStats_Event(p, PLAYERSTATS_JOINS, 1);
+#if 0 // reading the entire DB at once
+       string e = "", en = "";
+       float i = 0;
+       for(e = PS_D_IN_EVL; (en = db_get(PS_D_IN_DB, e)) != ""; e = en)
+       {
+               print(sprintf("%d:%s:%s\n", i, e, db_get(PS_D_IN_DB, sprintf("#%s", e))));
+               ++i;
+       }
+#endif
 
-       PlayerStats_Accuracy(p);
+void PlayerStats_PlayerDetail_AddItem(string event, string data)
+{
+       if(PS_D_IN_DB < 0) { return; }
 
-       if(IS_REAL_CLIENT(p))
+       // create a marker for the event so that we can access it later
+       string marker = sprintf("%s", event);
+       if(db_get(PS_D_IN_DB, marker) == "")
        {
-               if(p.latency_cnt)
+               if(PS_D_IN_EVL)
                {
-                       float latency = (p.latency_sum / p.latency_cnt);
-                       if(latency) { PlayerStats_Event(p, PLAYERSTATS_AVGLATENCY, latency); }
+                       db_put(PS_D_IN_DB, marker, PS_D_IN_EVL);
+                       strunzone(PS_D_IN_EVL);
                }
+               else { db_put(PS_D_IN_DB, marker, "#"); }
+               PS_D_IN_EVL = strzone(marker);
        }
 
-       strunzone(p.playerstats_id);
-       p.playerstats_id = string_null;
+       // now actually set the event data
+       db_put(PS_D_IN_DB, sprintf("#%s", event), data);
+       dprint("Added item ", sprintf("#%s", event), "=", data, " to PS_D_IN_DB\n");
 }
 
-.float scoreboard_pos;
-void PlayerStats_EndMatch(float finished)
+void PlayerStats_PlayerDetail(void)
 {
-       entity p;
-       PlayerScore_Sort(score_dummyfield, 0, 0, 0);
-       PlayerScore_Sort(scoreboard_pos, 1, 1, 1);
-       if(teamplay)
-               PlayerScore_TeamStats();
-       FOR_EACH_CLIENT(p)
+       // http://stats.xonotic.org/player/me
+       if((autocvar_g_playerstats_playerdetail_uri != "") && (crypto_getmyidstatus(0) > 0))
        {
-               // add personal score rank
-               PlayerStats_Event(p, PLAYERSTATS_RANK, p.score_dummyfield);
-
-               if(!p.scoreboard_pos)
-                       continue;
-
-               // scoreboard is valid!
-               PlayerStats_Event(p, PLAYERSTATS_SCOREBOARD_VALID, 1);
-
-               // add scoreboard position
-               PlayerStats_Event(p, PLAYERSTATS_SCOREBOARD_POS, p.scoreboard_pos);
-
-               // add scoreboard data
-               PlayerScore_PlayerStats(p);
-
-               // if the match ended normally, add winning info
-               if(finished)
+               // create the database if it doesn't already exist
+               if(PS_D_IN_DB < 0)
+                       PS_D_IN_DB = db_create();
+
+               //uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
+               dprint("Retrieving playerstats from URL: ", autocvar_g_playerstats_playerdetail_uri, "\n");
+               url_single_fopen(
+                       autocvar_g_playerstats_playerdetail_uri,
+                       FILE_APPEND,
+                       PlayerStats_PlayerDetail_Handler,
+                       world
+               );
+
+               PlayerStats_PlayerDetail_Status = PS_D_STATUS_WAITING;
+       }
+       else
+       {
+               // player has this disabled, kill the DB and set status to idle
+               if(PS_D_IN_DB >= 0)
                {
-                       PlayerStats_Event(p, PLAYERSTATS_WINS, p.winning);
-                       PlayerStats_Event(p, PLAYERSTATS_MATCHES, 1);
+                       db_close(PS_D_IN_DB);
+                       PS_D_IN_DB = -1;
                }
+
+               PlayerStats_PlayerDetail_Status = PS_D_STATUS_IDLE;
        }
 }
 
-#endif // SVQC
+void PlayerStats_PlayerDetail_CheckUpdate(void)
+{
+       // determine whether we should retrieve playerdetail information again
+       float gamecount = cvar("cl_matchcount");
+
+       #if 0
+       printf("PlayerStats_PlayerDetail_CheckUpdate(): %f >= %f, %d > %d\n",
+               time,
+               PS_D_NEXTUPDATETIME,
+               PS_D_LASTGAMECOUNT,
+               gamecount
+       );
+       #endif
+
+       if(
+               (time >= PS_D_NEXTUPDATETIME)
+               ||
+               (gamecount > PS_D_LASTGAMECOUNT)
+       )
+       {
+               PlayerStats_PlayerDetail();
+               PS_D_NEXTUPDATETIME = (time + autocvar_g_playerstats_playerdetail_autoupdatetime);
+               PS_D_LASTGAMECOUNT = gamecount;
+       }
+}
 
+void PlayerStats_PlayerDetail_Handler(entity fh, entity unused, float status)
+{
+       switch(status)
+       {
+               case URL_READY_CANWRITE:
+               {
+                       dprint("PlayerStats_PlayerDetail_Handler(): Sending data to player stats server...\n");
+                       url_fputs(fh, "V 1\n");
+                       #ifdef WATERMARK
+                       url_fputs(fh, sprintf("R %s\n", WATERMARK));
+                       #endif
+                       url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
+                       //url_fputs(fh, sprintf("c %s\n", cvar_string("_cl_country"))); // country
+                       //url_fputs(fh, sprintf("g %s\n", cvar_string("_cl_gender"))); // gender
+                       url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
+                       url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
+                       url_fputs(fh, "\n");
+                       url_fclose(fh);
+                       break;
+               }
 
+               case URL_READY_CANREAD:
+               {
+                       //print("PlayerStats_PlayerDetail_Handler(): Got response from player stats server:\n");
+                       string input = "";
+                       string gametype = "overall";
+                       while((input = url_fgets(fh)))
+                       {
+                               float count = tokenizebyseparator(input, " ");
+                               string key = "", event = "", data = "";
 
+                               if(argv(0) == "#") { continue; }
 
-//// WIP -zykure /////////////////////////////////////////////////////
+                               if(count == 2)
+                               {
+                                       key = argv(0);
+                                       data = substring(input, argv_start_index(1), strlen(input) - argv_start_index(1));
+                               }
+                               else if(count >= 3)
+                               {
+                                       key = argv(0);
+                                       event = argv(1);
+                                       data = substring(input, argv_start_index(2), strlen(input) - argv_start_index(2));
+                               }
+                               else { continue; }
 
+                               switch(key)
+                               {
+                                       // general info
+                                       case "V": PlayerStats_PlayerDetail_AddItem("version", data); break;
+                                       case "R": PlayerStats_PlayerDetail_AddItem("release", data); break;
+                                       case "T": PlayerStats_PlayerDetail_AddItem("time", data); break;
+
+                                       // player info
+                                       case "S": PlayerStats_PlayerDetail_AddItem("statsurl", data); break;
+                                       case "P": PlayerStats_PlayerDetail_AddItem("hashkey", data); break;
+                                       case "n": PlayerStats_PlayerDetail_AddItem("playernick", data); break;
+                                       case "i": PlayerStats_PlayerDetail_AddItem("playerid", data); break;
+
+                                       // other/event info
+                                       case "G": gametype = data; break;
+                                       case "e":
+                                       {
+                                               if(event != "" && data != "")
+                                               {
+                                                       PlayerStats_PlayerDetail_AddItem(
+                                                               sprintf(
+                                                                       "%s/%s",
+                                                                       gametype,
+                                                                       event
+                                                               ),
+                                                               data
+                                                       );
+                                               }
+                                               break;
+                                       }
 
+                                       default:
+                                       {
+                                               printf(
+                                                       "PlayerStats_PlayerDetail_Handler(): ERROR: "
+                                                       "Key went unhandled? Is our version outdated?\n"
+                                                       "PlayerStats_PlayerDetail_Handler(): "
+                                                       "Key '%s', Event '%s', Data '%s'\n",
+                                                       key,
+                                                       event,
+                                                       data
+                                               );
+                                               break;
+                                       }
+                               }
 
+                               #if 0
+                               print(sprintf(
+                                       "PlayerStats_PlayerDetail_Handler(): "
+                                       "Key '%s', Event '%s', Data '%s'\n",
+                                       key,
+                                       event,
+                                       data
+                               ));
+                               #endif
+                       }
+                       //print("PlayerStats_PlayerDetail_Handler(): End of response.\n");
+                       url_fclose(fh);
+                       PlayerStats_PlayerDetail_Status = PS_D_STATUS_RECEIVED;
+                       statslist.getStats(statslist);
+                       break;
+               }
 
-float playerinfo_db;
-string playerinfo_last;
-string playerinfo_events_last;
-.float playerid;
+               case URL_READY_CLOSED:
+               {
+                       // url_fclose has finished
+                       print("PlayerStats_PlayerDetail_Handler(): Player stats synchronized with server.\n");
+                       break;
+               }
 
+               case URL_READY_ERROR:
+               default:
+               {
+                       print("PlayerStats_PlayerDetail_Handler(): Receiving player stats failed: ", ftos(status), "\n");
+                       PlayerStats_PlayerDetail_Status = PS_D_STATUS_ERROR;
+                       if(PS_D_IN_DB >= 0)
+                       {
+                               db_close(PS_D_IN_DB);
+                               PS_D_IN_DB = -1;
+                       }
+                       break;
+               }
+       }
+}
+#endif
+
+/*
 void PlayerInfo_AddPlayer(entity e)
 {
        if(playerinfo_db < 0)
@@ -519,7 +903,7 @@ void PlayerInfo_ready(entity fh, entity p, float status)
        float n;
        string s;
 
-       PlayerInfo_AddPlayer(p);
+        PlayerInfo_AddPlayer(p);
 
        switch(status)
        {
@@ -532,7 +916,7 @@ void PlayerInfo_ready(entity fh, entity p, float status)
 #ifdef MENUQC
                         url_fputs(fh, sprintf("l %s\n", cvar_string("_menu_prvm_language"))); // language
                         url_fputs(fh, sprintf("c %s\n", cvar_string("_menu_prvm_country"))); // country
-                        url_fputs(fh, sprintf("g %s\n", cvar_string("_menu_prvm_gender"))); // gender
+                        url_fputs(fh, sprintf("g %s\n", cvar_string("_cl_gender"))); // gender
                         url_fputs(fh, sprintf("n %s\n", cvar_string("_cl_name"))); // name
                         url_fputs(fh, sprintf("m %s %s\n", cvar_string("_cl_playermodel"), cvar_string("_cl_playerskin"))); // model/skin
 #endif
@@ -546,7 +930,7 @@ void PlayerInfo_ready(entity fh, entity p, float status)
                        {
                                print("  ", s, "\n");
 
-                               string key = string_null, value = string_null, data = string_null;
+                               string key = "", value = "", data = "";
 
                                n = tokenizebyseparator(s, " "); // key (value) data
                                if (n == 1)
@@ -556,47 +940,31 @@ void PlayerInfo_ready(entity fh, entity p, float status)
                                        key = argv(0);
                                        data = argv(1);
                                }
-                               else if (n == 3)
+                               else if (n >= 3)
                                {
                                         key = argv(0);
                                         value = argv(1);
                                         data = argv(2);
                                }
-                               else if (n == 4)
-                               {
-                                       key = argv(0);
-                                       value = argv(1);
-                                       data = argv(2);
-                               }
-                               else
-                                       continue;
 
                                if (data == "")
                                        continue;
 
                                if (key == "#")
-                                       // comment
                                        continue;
                                else if (key == "V")
-                                       // version
                                        PlayerInfo_AddItem(p, "_version", data);
                                else if (key == "R")
-                                       // watermark
                                        PlayerInfo_AddItem(p, "_release", data);
                                else if (key == "T")
-                                       // timestamp
                                        PlayerInfo_AddItem(p, "_time", data);
                                else if (key == "S")
-                                       // stats page URL
                                        PlayerInfo_AddItem(p, "_statsurl", data);
                                else if (key == "P")
-                                       // player hashkey
                                        PlayerInfo_AddItem(p, "_hashkey", data);
                                else if (key == "n")
-                                       // player nick
                                        PlayerInfo_AddItem(p, "_playernick", data);
                                else if (key == "i")
-                                       // xonstats id
                                        PlayerInfo_AddItem(p, "_playerid", data);
                                else if (key == "G")
                                        gametype = data;
@@ -626,7 +994,6 @@ void PlayerInfo_ready(entity fh, entity p, float status)
 
 void PlayerInfo_Init()
 {
-       playerinfo_db = -1;
        playerinfo_db = db_create();
 }
 
@@ -639,7 +1006,7 @@ void PlayerInfo_Basic(entity p)
                return;
 
         string uri;
-        uri = playerinfo_uri; // FIXME
+        uri = autocvar_g_playerinfo_uri;
        if(uri != "" && p.crypto_idfp != "")
        {
                uri = strcat(uri, "/elo/", uri_escape(p.crypto_idfp));
@@ -658,10 +1025,11 @@ void PlayerInfo_Details()
                return;
 
         string uri;
-        uri = playerinfo_uri; // FIXME
+        uri = autocvar_g_playerinfo_uri; // FIXME
        if(uri != "" && crypto_getmyidstatus(0) > 0)
        {
-               uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
+               //uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
+                uri = strcat(uri, "/player/me");
                print("Retrieving playerstats from URL: ", uri, "\n");
                url_single_fopen(uri, FILE_APPEND, PlayerInfo_ready, world);
        }
@@ -670,7 +1038,7 @@ void PlayerInfo_Details()
 
 #ifdef CSQC
 /*
- * FIXME - crypto_* builtin functions missing in CSQC (csprogsdefs.qc:885)
+ * FIXME - crypto_* builtin functions missing in CSQC (csprogsdefs.qh:885)
 void PlayerInfo_Details()
 {
         print("-- Getting detailed PlayerInfo for local player (CSQC)\n");
@@ -679,15 +1047,14 @@ void PlayerInfo_Details()
                return;
 
         string uri;
-        uri = playerinfo_uri; // FIXME
+        uri = autocvar_g_playerinfo_uri; // FIXME
        if(uri != "" && crypto_getmyidstatus(0) > 0)
        {
-                entity p = spawn();
-                p.playerid = 0; // TODO: okay to use -1 for local player? or does local player already has an entity in MENUQC?
                uri = strcat(uri, "/player/", uri_escape(crypto_getmyidfp(0)));
                print("Retrieving playerstats from URL: ", uri, "\n");
                url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p);
        }
 }
-*/
+
 #endif
+*/