X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fcommon%2Fplayerstats.qc;h=e46e12fa1eeb6ac7053fda8b6365599e2f18bebd;hb=a16bfaa11ae87fe6fdab9e6c4504e2c5528ea595;hp=91ca32dc1115e23ac27ef03f06e176eafc4a1624;hpb=51dba7df6a5545e6e102c86c33f325b7fe151727;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/common/playerstats.qc b/qcsrc/common/playerstats.qc index 91ca32dc1..e46e12fa1 100644 --- a/qcsrc/common/playerstats.qc +++ b/qcsrc/common/playerstats.qc @@ -1,29 +1,16 @@ -#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 -#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_las -//string PS_B_IN_EVL; // playerstats_playerbasic_in_events_last -#endif - -#ifdef MENUQC -//string PS_D_IN_EVL; // playerstats_playerdetail_in_events_last +#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/all.qh" + #include "../server/weapons/accuracy.qh" + #include "../server/defs.qh" + #include "playerstats.qh" + #include "../server/scores.qh" #endif #ifdef SVQC @@ -43,7 +30,7 @@ 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(IS_BOT_CLIENT(e)) @@ -51,13 +38,13 @@ void PlayerStats_GameReport_AddPlayer(entity e) 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) @@ -76,7 +63,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) @@ -95,7 +82,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) @@ -122,20 +109,23 @@ float PlayerStats_GameReport_Event(string prefix, string event_id, float value) void PlayerStats_GameReport_Accuracy(entity p) { - entity w; - float i; - - #define PAC p.accuracy - for(i = WEP_FIRST; i <= WEP_LAST; ++i) - { - w = get_weaponinfo(i); - PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-hit"), PAC.(accuracy_hit[i-1])); - PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-fired"), PAC.(accuracy_fired[i-1])); - PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-cnt-hit"), PAC.(accuracy_cnt_hit[i-1])); - PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-cnt-fired"), PAC.(accuracy_cnt_fired[i-1])); - PS_GR_P_ADDVAL(p, strcat("acc-", w.netname, "-frags"), PAC.(accuracy_frags[i-1])); - } - #undef PAC + int i; + + 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 + } } void PlayerStats_GameReport_FinalizePlayer(entity p) @@ -175,10 +165,10 @@ void PlayerStats_GameReport_FinalizePlayer(entity p) p.playerstats_id = string_null; } -void PlayerStats_GameReport_EndMatch(float finished) +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(); } @@ -215,7 +205,7 @@ void PlayerStats_GameReport_EndMatch(float finished) if(autocvar_g_playerstats_gamereport_uri != "") { - PlayerStats_GameReport_DelayMapVote = TRUE; + PlayerStats_GameReport_DelayMapVote = true; url_multi_fopen( autocvar_g_playerstats_gamereport_uri, FILE_APPEND, @@ -225,7 +215,7 @@ void PlayerStats_GameReport_EndMatch(float finished) } else { - PlayerStats_GameReport_DelayMapVote = FALSE; + PlayerStats_GameReport_DelayMapVote = false; db_close(PS_GR_OUT_DB); PS_GR_OUT_DB = -1; } @@ -235,12 +225,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; @@ -277,7 +266,7 @@ void PlayerStats_GameReport_Init() // initiated before InitGameplayMode so that PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD); PlayerStats_GameReport_AddEvent(PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM); } - else { PlayerStats_GameReport_DelayMapVote = FALSE; } + else { PlayerStats_GameReport_DelayMapVote = false; } } void PlayerStats_GameReport_Handler(entity fh, entity pass, float status) @@ -373,7 +362,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)); } @@ -406,28 +395,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"); + 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_GameReport_DelayMapVote = FALSE; - db_close(PS_GR_OUT_DB); - PS_GR_OUT_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_GameReport_DelayMapVote = FALSE; + PlayerStats_GameReport_DelayMapVote = false; if(PS_GR_OUT_DB >= 0) { db_close(PS_GR_OUT_DB); @@ -437,38 +429,109 @@ void PlayerStats_GameReport_Handler(entity fh, entity pass, float status) } } } -#endif // SVQC -#ifdef MENUQC -void PlayerStats_PlayerDetail_Handler(entity fh, entity p, float status) +void PlayerStats_PlayerBasic(entity joiningplayer, float newrequest) +{ + // http://stats.xonotic.org/player/GgXRw6piDtFIbMArMuiAi8JG4tiin8VLjZgsKB60Uds=/elo.txt + if(autocvar_g_playerstats_playerbasic_uri != "") + { + 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 + { + // 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; } + } + } +} + +void PlayerStats_PlayerBasic_CheckUpdate(entity joiningplayer) +{ + // 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) + ); + } +} + +void PlayerStats_PlayerBasic_Handler(entity fh, entity p, float status) { switch(status) { case URL_READY_CANWRITE: { - print("-- Sending data to player stats server\n"); + 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("_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; } - + case URL_READY_CANREAD: { string s = ""; - print("-- Got response from player stats server:\n"); + dprint("-- Got response from player stats server:\n"); //string gametype = string_null; while((s = url_fgets(fh))) { - print(" ", s, "\n"); + dprint(" ", s, "\n"); /* string key = "", value = "", data = ""; @@ -519,7 +582,7 @@ void PlayerStats_PlayerDetail_Handler(entity fh, entity p, float status) continue; */ } - print("-- End of response.\n"); + dprint("-- End of response.\n"); url_fclose(fh); break; } @@ -529,7 +592,7 @@ void PlayerStats_PlayerDetail_Handler(entity fh, entity p, float status) print("Player stats synchronized with server\n"); break; } - + case URL_READY_ERROR: default: { @@ -538,24 +601,230 @@ void PlayerStats_PlayerDetail_Handler(entity fh, entity p, float status) } } } +#endif // SVQC + +#ifdef MENUQC + -void PlayerStats_PlayerDetail() +#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 + +void PlayerStats_PlayerDetail_AddItem(string event, string data) { - //PS_D_IN_DB = -1; - //PS_D_IN_DB = db_create(); + if(PS_D_IN_DB < 0) { return; } + + // 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(PS_D_IN_EVL) + { + 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); + } - //if(PS_D_IN_DB < 0) { return; } + // 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"); +} +void PlayerStats_PlayerDetail(void) +{ + // 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"); + 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) + { + db_close(PS_D_IN_DB); + PS_D_IN_DB = -1; + } + + PlayerStats_PlayerDetail_Status = PS_D_STATUS_IDLE; + } +} + +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; } + + 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; + } + + 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 @@ -647,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 @@ -725,7 +994,6 @@ void PlayerInfo_ready(entity fh, entity p, float status) void PlayerInfo_Init() { - playerinfo_db = -1; playerinfo_db = db_create(); } @@ -770,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");