public Snapshot(string Snapshot, IPAddress ServerIp = null)
{
// Set some internal variables
this.ServerIp = ServerIp ?? IPAddress.Loopback;
this.Players = new List<Player>();
this.DataString = Snapshot.Trim();
string[] Data = DataString.Split('\\');
Snapshot = null;
// Check for invalid snapshot string. All snapshots have at least 36 data pairs,
// and has an Even number of data sectors.
if (Data.Length < 36 || Data.Length % 2 != 0)
throw new InvalidDataException("Snapshot does not contain at least 36 elements, or contains an odd number of elements");
// Assign server name and prefix
this.ServerPrefix = Data[0];
this.ServerName = Data[1];
// Determine if we are central update. the "cdb_update" variable must be the LAST sector in snapshot
int Mode;
if (Data[Data.Length - 2] == "cdb_update" && Int32.TryParse(Data[Data.Length - 1], out Mode) && Mode.InRange(1, 2))
{
this.SnapshotMode = (Mode == 2) ? SnapshotMode.Minimal : SnapshotMode.FullSync;
}
// Setup our data dictionary's. We use NiceDictionary so we can easily determine missing keys in the log file
NiceDictionary<string, string> StandardData = new NiceDictionary<string, string>(16);
NiceDictionary<string, string> PlayerData = new NiceDictionary<string, string>();
Dictionary<int, int> KillData = new Dictionary<int, int>();
// Wrap parsing Key/Value snapshot data in a try block!
try
{
// Convert our standard data into key => value pairs
for (int i = 2; i < Data.Length; i += 2)
{
// Format: "DataKey_PlayerIndex". PlayerIndex is NOT the Player Id
string[] Parts = Data[i].Split('_');
if (Parts.Length == 1)
{
// Add to the Standard keys
StandardData.Add(Data[i], Data[i + 1]);
// Are we at the End of File? If so stop here
if (Parts[0] == "EOF")
{
// Make sure to save that last players stats!!
if (PlayerData.Count != 0)
{
AddPlayer(new Player(PlayerData, KillData));
PlayerData = null;
KillData = null;
}
break;
}
}
// If the item key is "pID", then we have a new player record
else if (Parts[0] == "pID")
{
// If we have data, complete this player and start anew
if (PlayerData.Count != 0)
{
AddPlayer(new Player(PlayerData, KillData));
PlayerData.Clear();
KillData.Clear();
}
// Add new PID
PlayerData.Add(Parts[0], Data[i + 1]);
}
else if (Parts[0] == "mvks") // Skip mvks... kill data only needs processed once (mvns)
continue;
else if (Parts[0] == "mvns") // Player kill data
KillData.Add(Int32.Parse(Data[i + 1]), Int32.Parse(Data[i + 3]));
else
PlayerData.Add(Parts[0], Data[i + 1]);
}
}
catch (Exception e)
{
throw new InvalidDataException("Error assigning Key => value pairs. See InnerException", e);
}
// Make sure we have a completed snapshot
if (!StandardData.ContainsKey("EOF"))
throw new InvalidDataException("No End of File element was found, Snapshot assumed to be incomplete.");
// Try and set internal GameResult variables
try
{
// Server data
this.ServerPort = Int32.Parse(StandardData["gameport"]);
this.QueryPort = Int32.Parse(StandardData["queryport"]);
// Map Data
this.MapName = StandardData["mapname"];
this.MapId = Int32.Parse(StandardData["mapid"]);
this.RoundStartTime = (int)Convert.ToDouble(StandardData["mapstart"], CultureInfo.InvariantCulture.NumberFormat);
this.RoundEndTime = (int)Convert.ToDouble(StandardData["mapend"], CultureInfo.InvariantCulture.NumberFormat);
// Misc Data
this.GameMode = Int32.Parse(StandardData["gm"]);
this.Mod = StandardData["v"]; // bf2 mod replaced the version key, since we dont care the version anyways
this.PlayersConnected = Int32.Parse(StandardData["pc"]);
// Army Data... There is no RWA key if there was no winner...
this.WinningTeam = Int32.Parse(StandardData["win"]); // Temp
this.WinningArmyId = (StandardData.ContainsKey("rwa")) ? Int32.Parse(StandardData["rwa"]) : -1;
this.Team1ArmyId = Int32.Parse(StandardData["ra1"]);
this.Team1Tickets = Int32.Parse(StandardData["rs1"]);
this.Team2ArmyId = Int32.Parse(StandardData["ra2"]);
this.Team2Tickets = Int32.Parse(StandardData["rs2"]);
}
catch (Exception e)
{
throw new InvalidDataException("Error assigning GameResult variables. See InnerException", e);
}
// Wrap this in a try-catch block, because we want to be able to view GameResult
// data, even if the database is offline
try
{
// Dispose when done!
using (StatsDatabase Driver = new StatsDatabase())
{
// Check for custom map, with no ID (Not defined in Constants.py)
if (this.MapId == 99)
{
// Check for existing map data
var Rows = Driver.Query("SELECT id FROM mapinfo WHERE name=@P0", this.MapName);
if (Rows.Count == 0)
{
// Create new MapId. Id's 700 - 1000 are reserved for unknown maps in the Constants.py file
// There should never be more then 300 unknown map id's, considering 1001 is the start of KNOWN
// Custom mod map id's. If we are at 1000 now, then we are in trouble :S
this.MapId = Driver.ExecuteScalar<int>("SELECT COALESCE(MAX(id), 699) FROM mapinfo WHERE id BETWEEN 700 AND 999") + 1;
if (this.MapId == 1000)
throw new Exception("Maximum unknown custom mapid has been reached. Please add this map's mapid to the Constants.py");
// Insert map data, so we dont lose this mapid we generated
Driver.Execute("INSERT INTO mapinfo(id, name, custom) VALUES (@P0, @P1, @P2)", this.MapId, this.MapName, 1);
}
else
this.MapId = Int32.Parse(Rows[0]["id"].ToString());
}
// Set whether or not this data is already been processed. The OR condition is because i goofed in early updates
// and set the timestamp to the RoundStart instead of the RoundEnd like i should have
this.IsProcessed = Driver.ExecuteScalar<int>(
"SELECT COUNT(*) FROM round_history WHERE mapid=@P0 AND time=@P1 AND (timestamp=@P2 OR timestamp=@P3)",
this.MapId, this.RoundTime.Seconds, this.RoundEndTime, this.RoundStartTime
) > 0;
}
}
catch(DbConnectException)
{
this.IsProcessed = false;
}
// Indicate whether we are a custom map
this.IsCustomMap = (this.MapId >= 700 || this.MapId == 99);
}