From: Jan Behrens Date: Sat, 3 Aug 2013 19:49:44 +0000 (+0200) Subject: moving playerstats code from server to common X-Git-Tag: xonotic-v0.8.0~139^2~1^2~148^2~7 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=8b013d3ca13b022f553ef3b60c22f8a2047c7615;p=xonotic%2Fxonotic-data.pk3dir.git moving playerstats code from server to common --- diff --git a/qcsrc/common/playerstats.qc b/qcsrc/common/playerstats.qc new file mode 100644 index 000000000..b6f795ed8 --- /dev/null +++ b/qcsrc/common/playerstats.qc @@ -0,0 +1,537 @@ +#ifdef SVQC + +float playerstats_db; +string teamstats_last; +string playerstats_last; +string events_last; +.float playerstats_addedglobalinfo; +.string playerstats_id; + +void PlayerStats_Init() // initiated before InitGameplayMode so that scores are added properly +{ + 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); +} + +void PlayerStats_AddPlayer(entity e) +{ + string s; + + if(playerstats_db < 0) + return; + if(e.playerstats_id) + return; + + s = string_null; + 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); + + 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); + else + s = sprintf("player#%d", e.playerid); + } + + e.playerstats_id = strzone(s); + + string key; + key = sprintf("%s:*", e.playerstats_id); + + string p; + p = db_get(playerstats_db, key); + if(p == "") + { + if(playerstats_last) + { + db_put(playerstats_db, key, playerstats_last); + strunzone(playerstats_last); + } + else + db_put(playerstats_db, key, "#"); + playerstats_last = strzone(e.playerstats_id); + } +} + +void PlayerStats_AddTeam(float t) +{ + if(playerstats_db < 0) + return; + + string key; + key = sprintf("%d", t); + + string p; + p = db_get(playerstats_db, key); + if(p == "") + { + if(teamstats_last) + { + db_put(playerstats_db, key, teamstats_last); + strunzone(teamstats_last); + } + else + db_put(playerstats_db, key, "#"); + teamstats_last = strzone(key); + } +} + +void PlayerStats_AddEvent(string event_id) +{ + if(playerstats_db < 0) + return; + + string key; + key = sprintf("*:%s", event_id); + + string p; + p = db_get(playerstats_db, key); + if(p == "") + { + if(events_last) + { + db_put(playerstats_db, key, events_last); + strunzone(events_last); + } + else + db_put(playerstats_db, key, "#"); + events_last = strzone(event_id); + } +} + +float PlayerStats_Event(entity e, string event_id, float value) +{ + if((e.playerstats_id == "") || playerstats_db < 0) + return 0; + + string key; + float val; + key = sprintf("%s:%s", e.playerstats_id, event_id); + val = stof(db_get(playerstats_db, key)); + val += value; + db_put(playerstats_db, key, ftos(val)); + return val; +} + +float PlayerStats_TeamScore(float t, string event_id, float value) +{ + if(playerstats_db < 0) + return 0; + + 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; +} + +/* + format spec: + + A collection of lines of the format SPACE NEWLINE, where + 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 + T: time at which the game ended + 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-: total score of that scoreboard item + scoreboard-: end-of-game score of that scoreboard item (can differ in non-team games) + achievement-: achievement counters (their "count" is usually 1 if nonzero at all) + kills-: number of kills against the indexed player + rank : rank of player + acc--hit: total damage dealt + acc--fired: total damage that all fired projectiles *could* have dealt + acc--cnt-hit: amount of shots that actually hit + acc--cnt-fired: amount of fired shots + acc--frags: amount of frags dealt by weapon + + Response format (not used yet): see https://gist.github.com/4284222 +*/ + +void PlayerStats_ready(entity fh, entity pass, float status) +{ + string t, tn; + string p, pn; + string e, en; + string nn, tt; + string s; + + switch(status) + { + case URL_READY_CANWRITE: + url_fputs(fh, "V 7\n"); +#ifdef WATERMARK + url_fputs(fh, sprintf("R %s\n", WATERMARK)); +#endif + url_fputs(fh, sprintf("T %s.%06d\n", strftime(FALSE, "%s"), floor(random() * 1000000))); + url_fputs(fh, sprintf("G %s\n", GetGametype())); + url_fputs(fh, sprintf("O %s\n", modname)); + url_fputs(fh, sprintf("M %s\n", GetMapname())); + url_fputs(fh, sprintf("I %s\n", matchid)); + url_fputs(fh, sprintf("S %s\n", cvar_string("hostname"))); + 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))); + if(teamplay) + { + for(t = teamstats_last; (tn = db_get(playerstats_db, sprintf("%d", stof(t)))) != ""; t = tn) + { + url_fputs(fh, sprintf("Q team#%s\n", t)); + for(e = events_last; (en = db_get(playerstats_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)); + } + } + } + for(p = playerstats_last; (pn = db_get(playerstats_db, sprintf("%s:*", p))) != ""; p = pn) + { + 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)); + if(teamplay) + { + tt = db_get(playerstats_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) + { + 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)); + } + } + url_fputs(fh, "\n"); + url_fclose(fh); + break; + 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"); + 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; + break; + case URL_READY_ERROR: + default: + print("Player stats writing failed: ", ftos(status), "\n"); + playerstats_waitforme = TRUE; + if(playerstats_db >= 0) + { + db_close(playerstats_db); + playerstats_db = -1; + } + break; + } +} + +//#NO AUTOCVARS START +void PlayerStats_Shutdown() +{ + string uri; + + if(playerstats_db < 0) + return; + + uri = autocvar_g_playerstats_uri; + if(uri != "") + { + playerstats_waitforme = FALSE; + url_multi_fopen(uri, FILE_APPEND, PlayerStats_ready, world); + } + else + { + playerstats_waitforme = TRUE; + db_close(playerstats_db); + playerstats_db = -1; + } +} +//#NO AUTOCVARS END + +void PlayerStats_Accuracy(entity p) +{ + entity a, w; + a = p.accuracy; + float i; + + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + { + w = get_weaponinfo(i); + + 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])); + + 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])); + + PlayerStats_Event(p, strcat("acc-", w.netname, "-frags"), a.(accuracy_frags[i-1])); + } + //backtrace(strcat("adding player stat accuracy for ", p.netname, ".\n")); +} + +void PlayerStats_AddGlobalInfo(entity p) +{ + if(playerstats_db < 0) + return; + if((p.playerstats_id == "") || playerstats_db < 0) + return; + p.playerstats_addedglobalinfo = TRUE; + + // add global info! + if(p.alivetime) + { + PlayerStats_Event(p, PLAYERSTATS_ALIVETIME, time - p.alivetime); + p.alivetime = 0; + } + + 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); + + 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); + + PlayerStats_Accuracy(p); + + if(IS_REAL_CLIENT(p)) + { + if(p.latency_cnt) + { + float latency = (p.latency_sum / p.latency_cnt); + if(latency) { PlayerStats_Event(p, PLAYERSTATS_AVGLATENCY, latency); } + } + } + + strunzone(p.playerstats_id); + p.playerstats_id = string_null; +} + +.float scoreboard_pos; +void PlayerStats_EndMatch(float finished) +{ + entity p; + PlayerScore_Sort(score_dummyfield, 0, 0, 0); + PlayerScore_Sort(scoreboard_pos, 1, 1, 1); + if(teamplay) + PlayerScore_TeamStats(); + FOR_EACH_CLIENT(p) + { + // 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) + { + PlayerStats_Event(p, PLAYERSTATS_WINS, p.winning); + PlayerStats_Event(p, PLAYERSTATS_MATCHES, 1); + } + } +} + + +//// WIP -zykure + +/* + format spec: + + A collection of lines of the format SPACE NEWLINE, where + 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 + T: time at which the game ended + 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-: total score of that scoreboard item + scoreboard-: end-of-game score of that scoreboard item (can differ in non-team games) + achievement-: achievement counters (their "count" is usually 1 if nonzero at all) + kills-: number of kills against the indexed player + rank : rank of player + acc--hit: total damage dealt + acc--fired: total damage that all fired projectiles *could* have dealt + acc--cnt-hit: amount of shots that actually hit + acc--cnt-fired: amount of fired shots + acc--frags: amount of frags dealt by weapon + + Response format (not used yet): see https://gist.github.com/4284222 +*/ +/* +void PlayerStatsEntity_ready(entity fh, entity player_stats, float status) +{ + string s; + string key, value; + + switch(status) + { + case URL_READY_CANREAD: + print("Got response from player stats server:\n"); + while((s = url_fgets(fh))) + { + print(" ", s, "\n"); + + key = substring(s, 0, 1); + value = substring(s, 2, -1); + if (key == "#") + continue; + else if (key == "V") + // version + continue; + else if (key == "P") + player_stats.playerid = stof(value); + else if (key == "n") + player_stats.playernick = value; + } + print("End of response.\n"); + url_fclose(fh); + break; + case URL_READY_CLOSED: + // url_fclose has finished + print("Player stats received from server\n"); + break; + case URL_READY_ERROR: + default: + print("Receiving player stats failed: ", ftos(status), "\n"); + break; + } +} + +void PlayerStats_GetPlayerInfo(entity p) +{ + string uri; + + uri = autocvar_g_playerstats_uri; + if(uri != "") + { + url_single_fopen(strcat(uri, "/"), FILE_READ, PlayerStatsEntity_ready, p.player_stats); + } +} +*/ + +#endif // SVQC diff --git a/qcsrc/common/playerstats.qh b/qcsrc/common/playerstats.qh new file mode 100644 index 000000000..1cc088545 --- /dev/null +++ b/qcsrc/common/playerstats.qh @@ -0,0 +1,64 @@ +#ifdef SVQC + +// time the player was alive and kicking +string PLAYERSTATS_ALIVETIME = "alivetime"; +string PLAYERSTATS_AVGLATENCY = "avglatency"; +string PLAYERSTATS_WINS = "wins"; +string PLAYERSTATS_MATCHES = "matches"; +string PLAYERSTATS_JOINS = "joins"; +string PLAYERSTATS_SCOREBOARD_VALID = "scoreboardvalid"; +string PLAYERSTATS_RANK = "rank"; +string PLAYERSTATS_SCOREBOARD_POS = "scoreboardpos"; + +string PLAYERSTATS_TOTAL = "total-"; +string PLAYERSTATS_SCOREBOARD = "scoreboard-"; + +string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3 = "achievement-kill-spree-3"; +string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5 = "achievement-kill-spree-5"; +string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10 = "achievement-kill-spree-10"; +string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15 = "achievement-kill-spree-15"; +string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20 = "achievement-kill-spree-20"; +string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25 = "achievement-kill-spree-25"; +string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30 = "achievement-kill-spree-30"; +string PLAYERSTATS_ACHIEVEMENT_BOTLIKE = "achievement-botlike"; +string PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD = "achievement-firstblood"; +string PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM = "achievement-firstvictim"; + +// delay map switch until this is set +float playerstats_waitforme; + +// call at initialization +void PlayerStats_Init(); + +// add a new player +void PlayerStats_AddPlayer(entity e); + +// add a new team +void PlayerStats_AddTeam(float t); + +// add a new event +void PlayerStats_AddEvent(string event_id); + +// call on each event to track, or at player disconnect OR match end for "global stuff" +float PlayerStats_Event(entity e, string event_id, float value); + +// add a team score +float PlayerStats_TeamScore(float t, string event_id, float value); + +// call at game over +void PlayerStats_Shutdown(); // send stats to the server + +void PlayerStats_Accuracy(entity p); + +// call this whenever a player leaves +void PlayerStats_AddGlobalInfo(entity p); + +// call this at the end of the match +void PlayerStats_EndMatch(float finished); + + +//// WIP -zykure + +//void PlayerStats_GetPlayerInfo(entity p); + +#endif //SVQC diff --git a/qcsrc/menu/progs.src b/qcsrc/menu/progs.src index 3036278c1..b6ea698cd 100644 --- a/qcsrc/menu/progs.src +++ b/qcsrc/menu/progs.src @@ -11,6 +11,7 @@ config.qh ../warpzonelib/mathlib.qh ../common/util.qh ../common/test.qh +../common/playerstats.qh oo/base.h @@ -39,6 +40,7 @@ oo/implementation.h ../common/util.qc ../common/test.qc +../common/playerstats.qc ../common/command/markup.qc ../common/command/rpn.qc ../common/command/generic.qc diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index 7d3585725..f694b7d1f 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -55,7 +55,7 @@ float team1_score, team2_score, team3_score, team4_score; float maxclients; // flag set on worldspawn so that the code knows if it is dedicated or not -float server_is_dedicated; +float server_is_dedicated; // Fields @@ -473,7 +473,7 @@ void target_voicescript_clear(entity pl); .float target_random; .float trigger_reverse; -// Nexball +// Nexball .entity ballcarried; // Also used for keepaway .float metertime; float g_nexball_meter_period; @@ -514,8 +514,8 @@ string matchid; .float last_pickup; -.float hit_time; -.float typehit_time; +.float hit_time; +.float typehit_time; .float stat_leadlimit; @@ -614,3 +614,12 @@ string modname; #define MISSILE_IS_CONFUSABLE(m) ((m.missile_flags & MIF_GUIDED_CONFUSABLE) ? TRUE : FALSE) #define MISSILE_IS_GUIDED(m) ((m.missile_flags & MIF_GUIDED_ALL) ? TRUE : FALSE) #define MISSILE_IS_TRACKING(m) ((m.missile_flags & MIF_GUIDED_TRACKING) ? TRUE : FALSE) + + +//// + +.entity player_stats; +//.float playerid; +.string playernick; +.float elos; +.float ranks; diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index df0903213..af8067ca0 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -647,16 +647,16 @@ float want_weapon(string cvarprefix, entity weaponinfo, float allguns) d = 0; // weapon is set a few lines later else d = (i == WEP_LASER || i == WEP_SHOTGUN); - + if(g_grappling_hook) // if possible, redirect off-hand hook to on-hand hook d |= (i == WEP_HOOK); if(weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED) // never default mutator blocked guns d = 0; var float t = cvar(strcat(cvarprefix, weaponinfo.netname)); - + //print(strcat("want_weapon: ", weaponinfo.netname, " - d: ", ftos(d), ", t: ", ftos(t), ". \n")); - + // bit order in t: // 1: want or not // 2: is default? @@ -907,7 +907,7 @@ void readlevelcvars(void) // load mutators #define CHECK_MUTATOR_ADD(mut_cvar,mut_name,dependence) \ { if(cvar(mut_cvar) && dependence) { MUTATOR_ADD(mut_name); } } - + CHECK_MUTATOR_ADD("g_dodging", mutator_dodging, 1); CHECK_MUTATOR_ADD("g_spawn_near_teammate", mutator_spawn_near_teammate, 1); CHECK_MUTATOR_ADD("g_physical_items", mutator_physical_items, 1); @@ -920,9 +920,9 @@ void readlevelcvars(void) CHECK_MUTATOR_ADD("g_vampire", mutator_vampire, !cvar("g_minstagib")); CHECK_MUTATOR_ADD("g_superspectate", mutator_superspec, 1); CHECK_MUTATOR_ADD("g_sandbox", sandbox, 1); - + #undef CHECK_MUTATOR_ADD - + if(cvar("sv_allow_fullbright")) serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT; @@ -941,7 +941,7 @@ void readlevelcvars(void) g_bugrigs_speed_ref = cvar("g_bugrigs_speed_ref"); g_bugrigs_speed_pow = cvar("g_bugrigs_speed_pow"); g_bugrigs_steer = cvar("g_bugrigs_steer"); - + g_minstagib = cvar("g_minstagib"); sv_clones = cvar("sv_clones"); @@ -1816,7 +1816,7 @@ string uid2name(string myuid) { db_put(ServerProgsDB, strcat("uid2name", myuid), ""); } } - + if(s == "") s = "^1Unregistered Player"; return s; diff --git a/qcsrc/server/playerstats.qc b/qcsrc/server/playerstats.qc deleted file mode 100644 index 354b521e8..000000000 --- a/qcsrc/server/playerstats.qc +++ /dev/null @@ -1,432 +0,0 @@ -float playerstats_db; -string teamstats_last; -string playerstats_last; -string events_last; -.float playerstats_addedglobalinfo; -.string playerstats_id; - -void PlayerStats_Init() // initiated before InitGameplayMode so that scores are added properly -{ - 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); -} - -void PlayerStats_AddPlayer(entity e) -{ - string s; - - if(playerstats_db < 0) - return; - if(e.playerstats_id) - return; - - s = string_null; - 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); - - 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); - else - s = sprintf("player#%d", e.playerid); - } - - e.playerstats_id = strzone(s); - - string key; - key = sprintf("%s:*", e.playerstats_id); - - string p; - p = db_get(playerstats_db, key); - if(p == "") - { - if(playerstats_last) - { - db_put(playerstats_db, key, playerstats_last); - strunzone(playerstats_last); - } - else - db_put(playerstats_db, key, "#"); - playerstats_last = strzone(e.playerstats_id); - } -} - -void PlayerStats_AddTeam(float t) -{ - if(playerstats_db < 0) - return; - - string key; - key = sprintf("%d", t); - - string p; - p = db_get(playerstats_db, key); - if(p == "") - { - if(teamstats_last) - { - db_put(playerstats_db, key, teamstats_last); - strunzone(teamstats_last); - } - else - db_put(playerstats_db, key, "#"); - teamstats_last = strzone(key); - } -} - -void PlayerStats_AddEvent(string event_id) -{ - if(playerstats_db < 0) - return; - - string key; - key = sprintf("*:%s", event_id); - - string p; - p = db_get(playerstats_db, key); - if(p == "") - { - if(events_last) - { - db_put(playerstats_db, key, events_last); - strunzone(events_last); - } - else - db_put(playerstats_db, key, "#"); - events_last = strzone(event_id); - } -} - -float PlayerStats_Event(entity e, string event_id, float value) -{ - if((e.playerstats_id == "") || playerstats_db < 0) - return 0; - - string key; - float val; - key = sprintf("%s:%s", e.playerstats_id, event_id); - val = stof(db_get(playerstats_db, key)); - val += value; - db_put(playerstats_db, key, ftos(val)); - return val; -} - -float PlayerStats_TeamScore(float t, string event_id, float value) -{ - if(playerstats_db < 0) - return 0; - - 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; -} - -/* - format spec: - - A collection of lines of the format SPACE NEWLINE, where - 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 - T: time at which the game ended - 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-: total score of that scoreboard item - scoreboard-: end-of-game score of that scoreboard item (can differ in non-team games) - achievement-: achievement counters (their "count" is usually 1 if nonzero at all) - kills-: number of kills against the indexed player - rank : rank of player - acc--hit: total damage dealt - acc--fired: total damage that all fired projectiles *could* have dealt - acc--cnt-hit: amount of shots that actually hit - acc--cnt-fired: amount of fired shots - acc--frags: amount of frags dealt by weapon - - Response format (not used yet): see https://gist.github.com/4284222 -*/ - -void PlayerStats_ready(entity fh, entity pass, float status) -{ - string t, tn; - string p, pn; - string e, en; - string nn, tt; - string s; - - switch(status) - { - case URL_READY_CANWRITE: - url_fputs(fh, "V 7\n"); -#ifdef WATERMARK - url_fputs(fh, sprintf("R %s\n", WATERMARK)); -#endif - url_fputs(fh, sprintf("T %s.%06d\n", strftime(FALSE, "%s"), floor(random() * 1000000))); - url_fputs(fh, sprintf("G %s\n", GetGametype())); - url_fputs(fh, sprintf("O %s\n", modname)); - url_fputs(fh, sprintf("M %s\n", GetMapname())); - url_fputs(fh, sprintf("I %s\n", matchid)); - url_fputs(fh, sprintf("S %s\n", cvar_string("hostname"))); - 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))); - if(teamplay) - { - for(t = teamstats_last; (tn = db_get(playerstats_db, sprintf("%d", stof(t)))) != ""; t = tn) - { - url_fputs(fh, sprintf("Q team#%s\n", t)); - for(e = events_last; (en = db_get(playerstats_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)); - } - } - } - for(p = playerstats_last; (pn = db_get(playerstats_db, sprintf("%s:*", p))) != ""; p = pn) - { - 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)); - if(teamplay) - { - tt = db_get(playerstats_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) - { - 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)); - } - } - url_fputs(fh, "\n"); - url_fclose(fh); - break; - 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"); - 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; - break; - case URL_READY_ERROR: - default: - print("Player stats writing failed: ", ftos(status), "\n"); - playerstats_waitforme = TRUE; - if(playerstats_db >= 0) - { - db_close(playerstats_db); - playerstats_db = -1; - } - break; - } -} - -//#NO AUTOCVARS START -void PlayerStats_Shutdown() -{ - string uri; - - if(playerstats_db < 0) - return; - - uri = autocvar_g_playerstats_uri; - if(uri != "") - { - playerstats_waitforme = FALSE; - url_multi_fopen(uri, FILE_APPEND, PlayerStats_ready, world); - } - else - { - playerstats_waitforme = TRUE; - db_close(playerstats_db); - playerstats_db = -1; - } -} -//#NO AUTOCVARS END - -void PlayerStats_Accuracy(entity p) -{ - entity a, w; - a = p.accuracy; - float i; - - for(i = WEP_FIRST; i <= WEP_LAST; ++i) - { - w = get_weaponinfo(i); - - 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])); - - 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])); - - PlayerStats_Event(p, strcat("acc-", w.netname, "-frags"), a.(accuracy_frags[i-1])); - } - //backtrace(strcat("adding player stat accuracy for ", p.netname, ".\n")); -} - -void PlayerStats_AddGlobalInfo(entity p) -{ - if(playerstats_db < 0) - return; - if((p.playerstats_id == "") || playerstats_db < 0) - return; - p.playerstats_addedglobalinfo = TRUE; - - // add global info! - if(p.alivetime) - { - PlayerStats_Event(p, PLAYERSTATS_ALIVETIME, time - p.alivetime); - p.alivetime = 0; - } - - 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); - - 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); - - PlayerStats_Accuracy(p); - - if(IS_REAL_CLIENT(p)) - { - if(p.latency_cnt) - { - float latency = (p.latency_sum / p.latency_cnt); - if(latency) { PlayerStats_Event(p, PLAYERSTATS_AVGLATENCY, latency); } - } - } - - strunzone(p.playerstats_id); - p.playerstats_id = string_null; -} - -.float scoreboard_pos; -void PlayerStats_EndMatch(float finished) -{ - entity p; - PlayerScore_Sort(score_dummyfield, 0, 0, 0); - PlayerScore_Sort(scoreboard_pos, 1, 1, 1); - if(teamplay) - PlayerScore_TeamStats(); - FOR_EACH_CLIENT(p) - { - // 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) - { - PlayerStats_Event(p, PLAYERSTATS_WINS, p.winning); - PlayerStats_Event(p, PLAYERSTATS_MATCHES, 1); - } - } -} diff --git a/qcsrc/server/playerstats.qh b/qcsrc/server/playerstats.qh deleted file mode 100644 index ab28b3a55..000000000 --- a/qcsrc/server/playerstats.qh +++ /dev/null @@ -1,53 +0,0 @@ -// time the player was alive and kicking -string PLAYERSTATS_ALIVETIME = "alivetime"; -string PLAYERSTATS_AVGLATENCY = "avglatency"; -string PLAYERSTATS_WINS = "wins"; -string PLAYERSTATS_MATCHES = "matches"; -string PLAYERSTATS_JOINS = "joins"; -string PLAYERSTATS_SCOREBOARD_VALID = "scoreboardvalid"; -string PLAYERSTATS_RANK = "rank"; -string PLAYERSTATS_SCOREBOARD_POS = "scoreboardpos"; - -string PLAYERSTATS_TOTAL = "total-"; -string PLAYERSTATS_SCOREBOARD = "scoreboard-"; - -string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_3 = "achievement-kill-spree-3"; -string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_5 = "achievement-kill-spree-5"; -string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_10 = "achievement-kill-spree-10"; -string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_15 = "achievement-kill-spree-15"; -string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_20 = "achievement-kill-spree-20"; -string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_25 = "achievement-kill-spree-25"; -string PLAYERSTATS_ACHIEVEMENT_KILL_SPREE_30 = "achievement-kill-spree-30"; -string PLAYERSTATS_ACHIEVEMENT_BOTLIKE = "achievement-botlike"; -string PLAYERSTATS_ACHIEVEMENT_FIRSTBLOOD = "achievement-firstblood"; -string PLAYERSTATS_ACHIEVEMENT_FIRSTVICTIM = "achievement-firstvictim"; - -// delay map switch until this is set -float playerstats_waitforme; - -// call at initialization -void PlayerStats_Init(); - -// add a new player -void PlayerStats_AddPlayer(entity e); - -// add a new team -void PlayerStats_AddTeam(float t); - -// add a new event -void PlayerStats_AddEvent(string event_id); - -// call on each event to track, or at player disconnect OR match end for "global stuff" -float PlayerStats_Event(entity e, string event_id, float value); - -// add a team score -float PlayerStats_TeamScore(float t, string event_id, float value); - -// call at game over -void PlayerStats_Shutdown(); // send stats to the server - -// call this whenever a player leaves -void PlayerStats_AddGlobalInfo(entity p); - -// call this at the end of the match -void PlayerStats_EndMatch(float finished); diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 367e8609d..2997bd251 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -43,7 +43,7 @@ mutators/gamemode_ctf.qh mutators/gamemode_domination.qh mutators/gamemode_keyhunt.qh // TODO fix this mutators/gamemode_keepaway.qh -mutators/gamemode_nexball.qh +mutators/gamemode_nexball.qh mutators/gamemode_lms.qh mutators/mutator_dodging.qh @@ -72,7 +72,7 @@ csqceffects.qc anticheat.qh cheats.qh -playerstats.qh +../common/playerstats.qh portals.qh @@ -219,7 +219,7 @@ playerdemo.qc anticheat.qc cheats.qc -playerstats.qc +../common/playerstats.qc round_handler.qc