nsane Limits V0.8/R2: Player Count Tracker
Version V0.8/R2: R1 fully tested, but R2 update not yet compiled or tested.
This example shows how to track the number of players in the server over time. It's useful to determining if a particular map is very popular and good at attracting players, or unpopular and causes a lot of quitting.
At debug level 3, the plugin console will get posted with every change to map level/round or number of players. At level 2 or lower, only the change to map level/round is posted. Example output at level 3 (other Thread lines deleted for clarity):
[23:03:53] [Insane Limits] Thread(enforcer): [PCT]: Seine/CQ64/2/2, 32/32, +1 net change. Round (min-max) 21-32
[23:04:13] [Insane Limits] Thread(enforcer): Checking limits for 32 players in list
...
[23:09:20] [Insane Limits] Thread(enforcer): Checking limits for 32 players in list
[23:09:21] [Insane Limits] Thread(enforcer): [PCT]: Seine/CQ64/2/2, 30/32, -2 net change. Round (min-max) 30-30
[23:09:41] [Insane Limits] Thread(enforcer): Checking limits for 29 players in list
[23:09:43] [Insane Limits] Thread(enforcer): [PCT]: Seine/CQ64/2/2, 23/32, -7 net change. Round (min-max) 23-30
[23:10:03] [Insane Limits] Thread(enforcer): Checking limits for 22 players in list
[23:10:05] [Insane Limits] Thread(enforcer): [PCT]: Karkand/CQ64/1/2, 21/32, -2 net change. Round (min-max) 21-30
[23:10:13] [Insane Limits] round start detected
...
[23:10:25] [Insane Limits] Thread(enforcer): Checking limits for 18 players in list
[23:10:27] [Insane Limits] Thread(enforcer): [PCT]: Karkand/CQ64/1/2, 18/32, -3 net change. Round (min-max) 18-30
...
[23:10:47] [Insane Limits] Thread(enforcer): Checking limits for 20 players in list
[23:10:48] [Insane Limits] Thread(enforcer): [PCT]: Karkand/CQ64/1/2, 20/32, +2 net change. Round (min-max) 18-30
...
[23:11:52] [Insane Limits] Thread(enforcer): Checking limits for 21 players in list
[23:11:54] [Insane Limits] Thread(enforcer): [PCT]: Karkand/CQ64/1/2, 21/32, +1 net change. Round (min-max) 18-30
Optionally, the example will also log comma separated values (CSV) to a log file. The default location of the log file is procon/Logs/%ip%_%port%/YYYYMMDD_pct.csv, where %ip% and %port% are the IP address and port of your RCON connection, and YYYYMMDD are the year, month and date of the log file. For example, if your IP is 129.0.0.1 and your port is 47000 and the date was Feb 4, 2012, the path would be: procon/Logs/129.0.0.1_47000/20120204_pct.csv. The logging interval is approximately 1 minute if there is no change in the player count, or, every OnInterval period in which there is a change.
Sample CSV:
Code:
23:03:53,Seine,CQ64,2,2,32,32,1,21,32
23:04:58,Seine,CQ64,2,2,32,32,0,21,32
23:06:04,Seine,CQ64,2,2,32,32,0,21,32
23:07:09,Seine,CQ64,2,2,32,32,0,21,32
23:08:15,Seine,CQ64,2,2,32,32,0,21,32
23:09:21,Seine,CQ64,2,2,30,32,-2,30,30
23:09:43,Seine,CQ64,2,2,23,32,-7,23,30
23:10:05,Karkand,CQ64,1,2,21,32,-2,21,30
23:10:27,Karkand,CQ64,1,2,18,32,-3,18,30
23:10:48,Karkand,CQ64,1,2,20,32,2,18,30
23:11:54,Karkand,CQ64,1,2,21,32,1,18,30
The columns are: Time, Map, Mode, Round, Of Rounds, Player Count, Max Players, Net Change, Min Players For Round, Max Players For Round.
Version 0.8p3: Set limit to evaluate OnIntervalServer, set the interval to 31, set Action to None
Set first_check to this Code:
Code:
/* Version: V0.8/R2 */
/* Track count of players by map/mode name */
bool logToFile = true; // Set to false to disable logging
String kMaxPlayers = "PCT_max"; // PCT = Player Count Tracker
String kMinPlayers = "PCT_min";
String kPlayers = "PCT_players";
String kTime = "PCT_time";
String kMapRound = "PCT_map_round";
if (!server.Data.issetInt(kPlayers)) {
server.Data.setInt(kPlayers, 0);
}
if (!server.RoundData.issetInt(kMaxPlayers)) {
server.RoundData.setInt(kMaxPlayers, 0);
}
if (!server.RoundData.issetInt(kMinPlayers)) {
server.RoundData.setInt(kMinPlayers, 64);
}
if (!server.RoundData.issetObject(kTime)) {
server.RoundData.setObject(kTime, DateTime.Now);
}
if (!server.RoundData.issetString(kMapRound)) {
server.RoundData.setString(kMapRound, server.MapFileName + server.CurrentRound.ToString());
}
int level = 2;
try {
level = Convert.ToInt32(plugin.getPluginVarValue("debug_level"));
} catch (Exception e) {}
/* Check for change */
int lastPlayers = server.Data.getInt(kPlayers);
int currentPlayers = server.PlayerCount;
if (lastPlayers == currentPlayers) {
// No change, skip update
if (level >= 6) plugin.ConsoleWrite("^b[PCT]^n: skipping " + lastPlayers + "->" + currentPlayers);
if (!logToFile) return false;
// Even if we are logging, skip if less than a minute has passed
DateTime lastTime = (DateTime)server.RoundData.getObject(kTime);
TimeSpan span = DateTime.Now.Subtract(lastTime);
if (span.TotalSeconds < 60) return false;
}
/* Calculate net change */
int maxPlayers = server.RoundData.getInt(kMaxPlayers);
int minPlayers = server.RoundData.getInt(kMinPlayers);
int netChange = currentPlayers - lastPlayers;
if (maxPlayers < currentPlayers) {
server.RoundData.setInt(kMaxPlayers, currentPlayers);
maxPlayers = currentPlayers;
}
if (minPlayers > currentPlayers) {
server.RoundData.setInt(kMinPlayers, currentPlayers);
minPlayers = currentPlayers;
}
server.Data.setInt(kPlayers, currentPlayers);
server.RoundData.setObject(kTime, DateTime.Now);
/* Log changes */
/* BF3 friendly map names, including B2K */
Dictionary<String, String> maps = new Dictionary<String, String>();
maps.Add("MP_001", "Bazaar");
maps.Add("MP_003", "Teheran");
maps.Add("MP_007", "Caspian");
maps.Add("MP_011", "Seine");
maps.Add("MP_012", "Firestorm");
maps.Add("MP_013", "Damavand");
maps.Add("MP_017", "Canals");
maps.Add("MP_018", "Kharg");
maps.Add("MP_Subway", "Metro");
maps.Add("XP1_001", "Karkand");
maps.Add("XP1_002", "Oman");
maps.Add("XP1_003", "Sharqi");
maps.Add("XP1_004", "Wake");
maps.Add("XP2_Factory", "Factory");
maps.Add("XP2_Office", "Office");
maps.Add("XP2_Palace", "Palace");
maps.Add("XP2_Skybar", "Skybar");
/* BF3 friendly game modes, including B2K */
Dictionary<String, String> modes = new Dictionary<String, String>();
modes.Add("ConquestLarge0", "CQ64");
modes.Add("ConquestSmall0", "CQ");
modes.Add("ConquestSmall1", "CQA");
modes.Add("RushLarge0", "Rush");
modes.Add("SquadRush0", "SQRush");
modes.Add("SquadDeathMatch0", "SQDM");
modes.Add("TeamDeathMatch0", "TDM");
modes.Add("Domination0", "CQD");
modes.Add("GunMaster0", "GM");
modes.Add("TeamDeathMatchC0", "TDMC");
String mapName = (maps.ContainsKey(server.MapFileName)) ? maps[server.MapFileName] : server.MapFileName;
String modeName = (modes.ContainsKey(server.Gamemode)) ? modes[server.Gamemode] : server.Gamemode;
String color = (netChange >= 0) ? "^2+" : "^8";
String time = DateTime.Now.ToString("HH:mm:ss");
String currMapRound = server.MapFileName + server.CurrentRound.ToString();
// Example console line:
// [PCT]: Metro/CQ64/1/2, 47/64, -3 net change. Round (min-max) 33-60
// Log to console if map/round changed, or if debugging and player count changed
if (!server.RoundData.getString(kMapRound).Equals(currMapRound) || (level >= 3 && lastPlayers != currentPlayers)) {
plugin.ConsoleWrite("^b[PCT]^n: ^4" + mapName + "/" + modeName + "/" + (server.CurrentRound+1) + "/" + server.TotalRounds + "^0, " + currentPlayers + "/" + server.MaxPlayers + ", ^b" + color + netChange + "^0^n net change. Round (min-max) " + minPlayers + "-" + maxPlayers);
server.RoundData.setString(kMapRound, currMapRound);
}
// Example log line:
// 23:11:54,Karkand,CQ64,1,2,21,32,1,18,30
if (logToFile) {
String logName = plugin.R("Logs/%server_host%_%server_port%/") + DateTime.Now.ToString("yyyyMMdd") + "_pct.csv";
String line = time + "," + mapName + "," + modeName + "," + (server.CurrentRound+1) + "," + server.TotalRounds + "," + currentPlayers + "," + server.MaxPlayers + "," + netChange + "," + minPlayers + "," + maxPlayers;
plugin.Log(logName, line);
}
return false;
Leave second_check Disabled
To disable CSV logging, change the value of logToFile from true to false.
REVISIONS