]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/playerstats.qc
Merge branch 'master' into Mirio/balance
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / playerstats.qc
index 3667d30d4a44b40445a183a030e1b0449a7b7335..b879b63b09587a8846ec9bf910154b796c4cfab3 100644 (file)
@@ -1,35 +1,18 @@
-#ifdef SVQC
-//float PS_PM_IN_DB;   // playerstats_prematch_in_db      // db for info COLLECTED at the beginning of a match
-float PS_GR_OUT_DB;  // playerstats_gamereport_out_db   // db of info SENT at the end of a match
-//float PS_GR_IN_DB;   // playerstats_gamereport_in_db    // db for info COLLECTED at the end of a match
-//float PS_B_IN_DB;    // playerstats_playerbasic_in_db   // db for info COLLECTED for basic player info (ELO)
-// http://stats.xonotic.org/player/GgXRw6piDtFIbMArMuiAi8JG4tiin8VLjZgsKB60Uds=/elo.txt -- this works, 
-// http://stats.xonotic.org/player/ENkUjf83vKMVZcNm%2F6Ao1EmXEj1apQ6XvdQTxwELvmA%3D/elo.txt -- but this doesn't?!?
-// ENkUjf83vKMVZcNm/6Ao1EmXEj1apQ6XvdQTxwELvmA=
-#endif
-
-#ifdef MENUQC
-float PS_D_IN_DB; // playerstats_playerdetail_in_db  // db for info COLLECTED for detailed player profile display
-// http://stats.xonotic.org/player/me
-#endif
-
-#ifdef SVQC
-//string PS_PM_IN_EVL;   // playerstats_prematch_in_events_last
-string PS_GR_OUT_TL;   // playerstats_gamereport_out_teams_last
-string PS_GR_OUT_PL;   // playerstats_gamereport_out_players_las
-string PS_GR_OUT_EVL;  // playerstats_gamereport_out_events_last
-//string PS_GR_IN_PL;    // playerstats_gamereport_in_players_last
-//string PS_GR_IN_EVL;   // playerstats_gamereport_in_events_last
-//string PS_B_IN_PL;     // playerstats_playerbasic_in_players_last
-//string PS_B_IN_EVL;    // playerstats_playerbasic_in_events_last
-#endif
-
-#ifdef MENUQC
-string PS_D_IN_EVL; // playerstats_playerdetail_in_events_last
+#include "playerstats.qh"
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+    #include "constants.qh"
+    #include "util.qh"
+    #include <common/weapons/_all.qh>
+    #include "../server/anticheat.qh"
+    #include "../server/defs.qh"
+    #include "../server/scores.qh"
+    #include "../server/weapons/accuracy.qh"
 #endif
 
 #ifdef SVQC
-void PlayerStats_Prematch(void)
+void PlayerStats_Prematch()
 {
        //foobar
 }
@@ -45,21 +28,21 @@ void PlayerStats_GameReport_AddPlayer(entity e)
                { s = e.crypto_idfp; }
        else if(IS_BOT_CLIENT(e))
                { 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((s == "") || find(NULL, 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); }
                else
                        { s = sprintf("player#%d", e.playerid); }
        }
-       
+
        e.playerstats_id = strzone(s);
 
        // now add the player to the database
        string key = sprintf("%s:*", e.playerstats_id);
        string p = db_get(PS_GR_OUT_DB, key);
-       
+
        if(p == "")
        {
                if(PS_GR_OUT_PL)
@@ -78,7 +61,7 @@ void PlayerStats_GameReport_AddTeam(float t)
 
        string key = sprintf("%d", t);
        string p = db_get(PS_GR_OUT_DB, key);
-       
+
        if(p == "")
        {
                if(PS_GR_OUT_TL)
@@ -97,7 +80,7 @@ void PlayerStats_GameReport_AddEvent(string event_id)
 
        string key = sprintf("*:%s", event_id);
        string p = db_get(PS_GR_OUT_DB, key);
-       
+
        if(p == "")
        {
                if(PS_GR_OUT_EVL)
@@ -124,24 +107,16 @@ float PlayerStats_GameReport_Event(string prefix, string event_id, float value)
 
 void PlayerStats_GameReport_Accuracy(entity p)
 {
-       entity w;
-       float i;
-
-       for(i = WEP_FIRST; i <= WEP_LAST; ++i)
-       {
-               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]));
-
+       #define ACCMAC(suffix, field) \
+               PS_GR_P_ADDVAL(p, sprintf("acc-%s-%s", it.netname, suffix), p.accuracy.(field[i-1]));
+       FOREACH(Weapons, it != WEP_Null, {
                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
-       }
+       });
+       #undef ACCMAC
 }
 
 void PlayerStats_GameReport_FinalizePlayer(entity p)
@@ -167,6 +142,7 @@ void PlayerStats_GameReport_FinalizePlayer(entity p)
                PS_GR_P_ADDVAL(p, PLAYERSTATS_JOINS, 1);
 
        PlayerStats_GameReport_Accuracy(p);
+       anticheat_report_to_playerstats(p);
 
        if(IS_REAL_CLIENT(p))
        {
@@ -184,54 +160,52 @@ void PlayerStats_GameReport_FinalizePlayer(entity p)
 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)
-       {
+       FOREACH_CLIENT(true, {
                // add personal score rank
-               PS_GR_P_ADDVAL(p, PLAYERSTATS_RANK, p.score_dummyfield);
+               PS_GR_P_ADDVAL(it, PLAYERSTATS_RANK, it.score_dummyfield);
 
                // scoreboard data
-               if(p.scoreboard_pos)
+               if(it.scoreboard_pos)
                {
                        // scoreboard is valid!
-                       PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_VALID, 1);
+                       PS_GR_P_ADDVAL(it, PLAYERSTATS_SCOREBOARD_VALID, 1);
 
                        // add scoreboard position
-                       PS_GR_P_ADDVAL(p, PLAYERSTATS_SCOREBOARD_POS, p.scoreboard_pos);
+                       PS_GR_P_ADDVAL(it, PLAYERSTATS_SCOREBOARD_POS, it.scoreboard_pos);
 
                        // add scoreboard data
-                       PlayerScore_PlayerStats(p);
+                       PlayerScore_PlayerStats(it);
 
                        // 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);
+                               PS_GR_P_ADDVAL(it, PLAYERSTATS_WINS, it.winning);
+                               PS_GR_P_ADDVAL(it, PLAYERSTATS_MATCHES, 1);
                        }
                }
 
                // collect final player information
-               PlayerStats_GameReport_FinalizePlayer(p);
-       }
+               PlayerStats_GameReport_FinalizePlayer(it);
+       });
 
        if(autocvar_g_playerstats_gamereport_uri != "")
        {
-               PlayerStats_GameReport_DelayMapVote = TRUE;
+               PlayerStats_GameReport_DelayMapVote = true;
                url_multi_fopen(
                        autocvar_g_playerstats_gamereport_uri,
                        FILE_APPEND,
                        PlayerStats_GameReport_Handler,
-                       world
+                       NULL
                );
        }
        else
        {
-               PlayerStats_GameReport_DelayMapVote = FALSE;
+               PlayerStats_GameReport_DelayMapVote = false;
                db_close(PS_GR_OUT_DB);
                PS_GR_OUT_DB = -1;
        }
@@ -241,12 +215,11 @@ void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that
 {
        if(autocvar_g_playerstats_gamereport_uri == "") { return; }
 
-       PS_GR_OUT_DB = -1;
        PS_GR_OUT_DB = db_create();
 
        if(PS_GR_OUT_DB >= 0)
        {
-               PlayerStats_GameReport_DelayMapVote = TRUE;
+               PlayerStats_GameReport_DelayMapVote = true;
 
                serverflags |= SERVERFLAG_PLAYERSTATS;
 
@@ -260,17 +233,13 @@ void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that
                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"));
-               }
+               FOREACH(Weapons, it != WEP_Null, {
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", it.netname, "-hit"));
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", it.netname, "-fired"));
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", it.netname, "-cnt-hit"));
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", it.netname, "-cnt-fired"));
+                       PlayerStats_GameReport_AddEvent(strcat("acc-", it.netname, "-frags"));
+               });
 
                PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3);
                PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5);
@@ -282,8 +251,10 @@ void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that
                PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_BOTLIKE);
                PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD);
                PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM);
+
+               anticheat_register_to_playerstats();
        }
-       else { PlayerStats_GameReport_DelayMapVote = FALSE; }
+       else { PlayerStats_GameReport_DelayMapVote = false; }
 }
 
 void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
@@ -379,7 +350,7 @@ void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
                                nn = db_get(PS_GR_OUT_DB, sprintf("%s:_playerid", p));
                                if(nn != "") { url_fputs(fh, sprintf("i %s\n", nn)); }
 
-                               // player name 
+                               // player name
                                nn = db_get(PS_GR_OUT_DB, sprintf("%s:_netname", p));
                                if(nn != "") { url_fputs(fh, sprintf("n %s\n", nn)); }
 
@@ -412,28 +383,31 @@ void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
                {
                        // 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");
+                       LOG_TRACE("Got response from player stats server:");
+                       while((s = url_fgets(fh))) { LOG_TRACE("  ", s); }
+                       LOG_TRACE("End of response.");
                        url_fclose(fh);
                        break;
                }
-               
+
                case URL_READY_CLOSED:
                {
                        // url_fclose has finished
-                       print("Player stats written\n");
-                       PlayerStats_GameReport_DelayMapVote = FALSE;
-                       db_close(PS_GR_OUT_DB);
-                       PS_GR_OUT_DB = -1;
+                       LOG_TRACE("Player stats written");
+                       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_GameReport_DelayMapVote = FALSE;
+                       LOG_INFO("Player stats writing failed: ", ftos(status), "\n");
+                       PlayerStats_GameReport_DelayMapVote = false;
                        if(PS_GR_OUT_DB >= 0)
                        {
                                db_close(PS_GR_OUT_DB);
@@ -444,35 +418,75 @@ void PlayerStats_GameReport_Handler(entity fh, entity pass, float status)
        }
 }
 
-void PlayerStats_PlayerBasic()
+void PlayerStats_PlayerBasic(entity joiningplayer, float newrequest)
 {
-       entity player;
-       //PS_D_IN_DB = -1;
-       //PS_D_IN_DB = db_create();
-
-       //if(PS_D_IN_DB < 0) { return; }
-
-       FOR_EACH_REALCLIENT(player)
+       PlayerScore_Add(joiningplayer, SP_ELO, -1);
+       // http://stats.xonotic.org/player/GgXRw6piDtFIbMArMuiAi8JG4tiin8VLjZgsKB60Uds=/elo.txt
+       if(autocvar_g_playerstats_playerbasic_uri != "")
        {
                string uri = autocvar_g_playerstats_playerbasic_uri;
-               if((uri != "") && (player.crypto_idfp != ""))
-               {
-                       uri = strcat(uri, "/player/", uri_escape(uri_escape(player.crypto_idfp)), "/elo.txt");
-                       print("Retrieving playerstats from URL: ", uri, "\n");
+               if (joiningplayer.crypto_idfp == "") {
+                       PlayerScore_Add(joiningplayer, SP_ELO, -1);
+               } else {
+                       // 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(uri_escape(joiningplayer.crypto_idfp))), "/elo.txt");
+                       LOG_TRACE("Retrieving playerstats from URL: ", uri);
                        url_single_fopen(
                                uri,
                                FILE_APPEND,
                                PlayerStats_PlayerBasic_Handler,
-                               player
+                               joiningplayer
                        );
-               }
 
-               /*p.crypto_idfp != "")
+                       // 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
+       {
+               // server has this disabled, kill the DB and set status to idle
+               PlayerScore_Add(joiningplayer, SP_ELO, -1);
+               if(PS_B_IN_DB >= 0)
                {
-                       uri = strcat(uri, "/elo/", uri_escape(p.crypto_idfp));
-                       print("Retrieving playerstats from URL: ", uri, "\n");
-                       url_single_fopen(uri, FILE_READ, PlayerInfo_ready, p);
-               }*/
+                       db_close(PS_B_IN_DB);
+                       PS_B_IN_DB = -1;
+
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it), it.playerstats_basicstatus = PS_B_STATUS_IDLE);
+               }
+       }
+}
+
+void PlayerStats_PlayerBasic_CheckUpdate(entity joiningplayer)
+{
+       // determine whether we should retrieve playerbasic information again
+
+       LOG_TRACEF("PlayerStats_PlayerBasic_CheckUpdate('%s'): %f",
+               joiningplayer.netname,
+               time
+       );
+
+       // 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)
+               );
        }
 }
 
@@ -482,97 +496,98 @@ void PlayerStats_PlayerBasic_Handler(entity fh, entity p, float status)
        {
                case URL_READY_CANWRITE:
                {
-                       print("-- Sending data to player stats server\n");
+                       LOG_TRACE("-- Sending data to player stats server");
                        /*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("_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
                        */url_fputs(fh, "\n");
                        url_fclose(fh);
-                       break;
+                       return;
                }
-               
+
                case URL_READY_CANREAD:
                {
-                       string s = "";
-                       print("-- Got response from player stats server:\n");
-                       //string gametype = string_null;
-                       while((s = url_fgets(fh)))
-                       {
-                               print("  ", s, "\n");
-                               /*
+                       bool handled = false;
+                       string gt = string_null;
+                       for (string s = ""; (s = url_fgets(fh)); ) {
+                               int n = tokenizebyseparator(s, " "); // key value? data
+                               if (n == 1) continue;
                                string key = "", value = "", data = "";
-
-                               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);
-                               }
-
-                               if (data == "")
-                               continue;
-
-                               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);
+                               if (n == 2) {
+                    key = argv(0);
+                    data = argv(1);
+                               } else if (n >= 3) {
+                    key = argv(0);
+                    value = argv(1);
+                    data = argv(2);
                                }
-                               else
-                               continue;
-                               */
+                switch (key) {
+                    case "V":
+                        // PlayerInfo_AddItem(p, "_version", data);
+                        break;
+                    case "R":
+                        // PlayerInfo_AddItem(p, "_release", data);
+                        break;
+                    case "T":
+                        // PlayerInfo_AddItem(p, "_time", data);
+                        break;
+                    case "S":
+                        // PlayerInfo_AddItem(p, "_statsurl", data);
+                        break;
+                    case "P":
+                        // PlayerInfo_AddItem(p, "_hashkey", data);
+                        break;
+                    case "n":
+                        // PlayerInfo_AddItem(p, "_playernick", data);
+                        break;
+                    case "i":
+                        // PlayerInfo_AddItem(p, "_playerid", data);
+                        // p.xonstat_id = stof(data);
+                        break;
+                    case "G":
+                        gt = data;
+                        break;
+                    case "e":
+                        LOG_TRACE("G: ", gt);
+                        LOG_TRACE("e: ", data);
+                        if (gt == GetGametype()) {
+                            handled = true;
+                            float e = stof(data);
+                            PlayerScore_Add(p, SP_ELO, +1 + e);
+                        }
+                        if (gt == "") {
+                            // PlayerInfo_AddItem(p, value, data);
+                        } else {
+                            // PlayerInfo_AddItem(p, sprintf("%s/%s", gt, value), data);
+                        }
+                        break;
+                }
                        }
-                       print("-- End of response.\n");
                        url_fclose(fh);
+                       if (handled) return;
                        break;
                }
                case URL_READY_CLOSED:
                {
                        // url_fclose has finished
-                       print("Player stats synchronized with server\n");
-                       break;
+                       LOG_INFO("Player stats synchronized with server\n");
+                       return;
                }
-               
+
                case URL_READY_ERROR:
                default:
                {
-                       print("Receiving player stats failed: ", ftos(status), "\n");
+                       LOG_INFO("Receiving player stats failed: ", ftos(status), "\n");
                        break;
                }
        }
+       PlayerScore_Add(p, SP_ELO, -1);
 }
 #endif // SVQC
 
@@ -584,7 +599,7 @@ void PlayerStats_PlayerBasic_Handler(entity fh, entity p, float status)
        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))));
+               LOG_INFO(sprintf("%d:%s:%s\n", i, e, db_get(PS_D_IN_DB, sprintf("#%s", e))));
                ++i;
        }
 #endif
@@ -602,41 +617,71 @@ void PlayerStats_PlayerDetail_AddItem(string event, string data)
                        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);
        }
 
        // now actually set the event data
        db_put(PS_D_IN_DB, sprintf("#%s", event), data);
-       print("Added item ", sprintf("#%s", event), "=", data, " to PS_D_IN_DB\n");
+       LOG_TRACE("Added item ", sprintf("#%s", event), "=", data, " to PS_D_IN_DB");
 }
 
 void PlayerStats_PlayerDetail()
 {
-       //PS_D_IN_DB = -1;
-       //PS_D_IN_DB = db_create();
-
-       if(PS_D_IN_DB < 0)
-       {
-               PS_D_IN_DB = -1;
-               PS_D_IN_DB = db_create();
-       }
-       else
-       {
-               // kill the old db and try again
-               //db_close(PS_D_IN_DB);
-               //PS_D_IN_DB = -1;
-       }
-
+       // http://stats.xonotic.org/player/me
        if((autocvar_g_playerstats_playerdetail_uri != "") && (crypto_getmyidstatus(0) > 0))
        {
+               // 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)));
-               print("Retrieving playerstats from URL: ", autocvar_g_playerstats_playerdetail_uri, "\n");
+               LOG_TRACE("Retrieving playerstats from URL: ", autocvar_g_playerstats_playerdetail_uri);
                url_single_fopen(
                        autocvar_g_playerstats_playerdetail_uri,
                        FILE_APPEND,
                        PlayerStats_PlayerDetail_Handler,
-                       world
+                       NULL
                );
+
+               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)
+               {
+                       db_close(PS_D_IN_DB);
+                       PS_D_IN_DB = -1;
+               }
+
+               PlayerStats_PlayerDetail_Status = PS_D_STATUS_IDLE;
+       }
+}
+
+void PlayerStats_PlayerDetail_CheckUpdate()
+{
+       // determine whether we should retrieve playerdetail information again
+       float gamecount = cvar("cl_matchcount");
+
+       #if 0
+       LOG_INFOF("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;
        }
 }
 
@@ -646,34 +691,33 @@ void PlayerStats_PlayerDetail_Handler(entity fh, entity unused, float status)
        {
                case URL_READY_CANWRITE:
                {
-                       print("-- Sending data to player stats server\n");
+                       LOG_TRACE("PlayerStats_PlayerDetail_Handler(): Sending data to player stats server...");
                        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("_menu_prvm_gender"))); // gender
+                       //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("-- Got response from player stats server:\n");
+                       //print("PlayerStats_PlayerDetail_Handler(): Got response from player stats server:\n");
                        string input = "";
                        string gametype = "overall";
                        while((input = url_fgets(fh)))
                        {
-                               //print(input, "\n");
                                float count = tokenizebyseparator(input, " ");
                                string key = "", event = "", data = "";
 
                                if(argv(0) == "#") { continue; }
-                               
+
                                if(count == 2)
                                {
                                        key = argv(0);
@@ -717,34 +761,56 @@ void PlayerStats_PlayerDetail_Handler(entity fh, entity unused, float status)
                                                }
                                                break;
                                        }
-                                       
-                                       default: print("PlayerStats_PlayerDetail_Handler(): Key went unhandled?\n"); break;
+
+                                       default:
+                                       {
+                                               LOG_INFOF(
+                                                       "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",
+                               LOG_INFO(sprintf(
+                                       "PlayerStats_PlayerDetail_Handler(): "
+                                       "Key '%s', Event '%s', Data '%s'\n",
                                        key,
                                        event,
                                        data
                                ));
                                #endif
                        }
-                       print("-- End of response.\n");
+                       //print("PlayerStats_PlayerDetail_Handler(): End of response.\n");
                        url_fclose(fh);
+                       PlayerStats_PlayerDetail_Status = PS_D_STATUS_RECEIVED;
+                       statslist.getStats(statslist);
                        break;
                }
+
                case URL_READY_CLOSED:
                {
                        // url_fclose has finished
-                       print("Player stats synchronized with server\n");
+                       LOG_INFO("PlayerStats_PlayerDetail_Handler(): Player stats synchronized with server.\n");
                        break;
                }
-               
+
                case URL_READY_ERROR:
                default:
                {
-                       print("Receiving player stats failed: ", ftos(status), "\n");
+                       LOG_INFO("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;
                }
        }
@@ -838,7 +904,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
@@ -916,7 +982,6 @@ void PlayerInfo_ready(entity fh, entity p, float status)
 
 void PlayerInfo_Init()
 {
-       playerinfo_db = -1;
        playerinfo_db = db_create();
 }
 
@@ -954,14 +1019,13 @@ void PlayerInfo_Details()
                //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);
+               url_single_fopen(uri, FILE_APPEND, PlayerInfo_ready, NULL);
        }
 }
 #endif
 
 #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");