Provides an object interface that can properly put together an Insert Query string.
All parameters in the WHERE and HAVING statements will be escaped by the underlaying DbCommand object, making the Execute*() methods SQL injection safe.
        /// <summary>
        /// Adds a server's posted snapshot into the Snapshot Processing Queue, which
        /// will process the snapshot as soon as possible. This method is Non-Blocking.
        /// </summary>
        /// <remarks>
        /// Any errors that occur during the actual import of the data will be
        /// logged inside the StatsDebug log
        /// </remarks>
        /// <param name="Data">The snapshot data provided by the server.</param>
        /// <param name="ServerAddress">The IP address of the server.</param>
        /// <exception cref="UnauthorizedAccessException">
        ///     Thrown if the Server IP is not authorized to post game data to this server
        /// </exception>
        /// <exception cref="InvalidDataException">
        ///     Thrown if the provided Snapshot data is not valid, and cannot be processed
        /// </exception>
        public static void QueueServerSnapshot(string Data, IPAddress ServerAddress)
        {
            // Make sure the server is authorized
            if (!IsAuthorizedGameServer(ServerAddress))
                throw new UnauthorizedAccessException("Un-Authorised Gameserver (Ip: " + ServerAddress + ")");

            // Create the Snapshot Object
            Snapshot Snap = new Snapshot(Data, ServerAddress);

            // Update this server in the Database
            using (StatsDatabase Database = new StatsDatabase())
            {
                // Try and grab the ID of this server
                int id = Database.ExecuteScalar<int>(
                    "SELECT COALESCE(id, -1), COUNT(id) FROM servers WHERE ip=@P0 AND port=@P1",
                    ServerAddress, Snap.ServerPort
                );

                // New server?
                if (id < 0)
                {
                    InsertQueryBuilder builder = new InsertQueryBuilder(Database);
                    builder.SetTable("servers");
                    builder.SetField("ip", ServerAddress);
                    builder.SetField("port", Snap.ServerPort);
                    builder.SetField("prefix", Snap.ServerPrefix);
                    builder.SetField("name", Snap.ServerName);
                    builder.SetField("queryport", Snap.QueryPort);
                    builder.SetField("lastupdate", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
                    builder.Execute();
                }
                else // existing
                {
                    UpdateQueryBuilder builder = new UpdateQueryBuilder(Database);
                    builder.SetTable("servers");
                    builder.SetField("prefix", Snap.ServerPrefix);
                    builder.SetField("name", Snap.ServerName);
                    builder.SetField("queryport", Snap.QueryPort);
                    builder.SetField("lastupdate", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
                    builder.AddWhere("id", Comparison.Equals, id);
                    builder.Execute();
                }
            }

            // Add snapshot to Queue
            SnapshotQueue.Enqueue(Snap);
        }
        /// <summary>
        /// Imports a Player XML Sheet from the specified path
        /// </summary>
        /// <param name="XmlPath">The full path to the XML file</param>
        public static void ImportPlayerXml(string XmlPath)
        {
            // Connect to database first!
            using (StatsDatabase Driver = new StatsDatabase())
            {
                // Load elements
                XDocument Doc = XDocument.Load(new FileStream(XmlPath, FileMode.Open, FileAccess.Read));
                XElement Info = Doc.Root.Element("Info");
                XElement TableData = Doc.Root.Element("TableData");

                // Make sure player doesnt already exist
                int Pid = Int32.Parse(Info.Element("Pid").Value);
                if (Driver.PlayerExists(Pid))
                    throw new Exception(String.Format("Player with PID {0} already exists!", Pid));

                // Begin Transaction
                using (DbTransaction Transaction = Driver.BeginTransaction())
                {
                    try
                    {
                        // Loop through tables
                        foreach (XElement Table in TableData.Elements())
                        {
                            // Loop through Rows
                            foreach (XElement Row in Table.Elements())
                            {
                                InsertQueryBuilder QueryBuilder = new InsertQueryBuilder(Table.Name.LocalName, Driver);
                                foreach (XElement Col in Row.Elements())
                                {
                                    if (Col.Name.LocalName == "name")
                                        QueryBuilder.SetField(Col.Name.LocalName, Col.Value.UnescapeXML());
                                    else
                                        QueryBuilder.SetField(Col.Name.LocalName, Col.Value);
                                }

                                QueryBuilder.Execute();
                            }
                        }

                        // Commit Transaction
                        Transaction.Commit();
                    }
                    catch
                    {
                        Transaction.Rollback();
                        throw;
                    }
                }
            }
        }
Beispiel #3
0
        /// <summary>
        /// Processes the snapshot data, inserted and updating player data.
        /// </summary>
        /// <exception cref="InvalidDataException">Thrown if the snapshot data is invalid</exception>
        public void Process()
        {
            // Make sure we are valid, or throw exception!
            if (!IsValidSnapshot)
                throw new InvalidDataException("Invalid Snapshot data!");

            // Begin Logging
            Log(String.Format("Begin Processing ({0})...", MapName), LogLevel.Notice);
            if (IsCustomMap)
                Log(String.Format("Custom Map ({0})...", MapId), LogLevel.Notice);
            else
                Log(String.Format("Standard Map ({0})...", MapId), LogLevel.Notice);

            Log("Found " + PlayerData.Count + " Player(s)...", LogLevel.Notice);

            // Make sure we meet the minimum player requirement
            if (PlayerData.Count < MainForm.Config.ASP_MinRoundPlayers)
            {
                Log("Minimum round Player count does not meet the ASP requirement... Aborting", LogLevel.Warning);
                return;
            }

            // Start a timer!
            Stopwatch Clock = new Stopwatch();
            Clock.Start();

            // Setup some variables
            List<Dictionary<string, object>> Rows;
            InsertQueryBuilder InsertQuery;
            UpdateQueryBuilder UpdateQuery;
            WhereClause Where;

            // Temporary Map Data (For Round history and Map info tables)
            int MapScore = 0;
            int MapKills = 0;
            int MapDeaths = 0;
            int Team1Players = 0;
            int Team2Players = 0;
            int Team1PlayersEnd = 0;
            int Team2PlayersEnd = 0;

            // MySQL could throw a packet size error here, so we need will increase it!
            if (Driver.DatabaseEngine == DatabaseEngine.Mysql)
                Driver.Execute("SET GLOBAL max_allowed_packet=51200");

            // Begin Transaction
            DbTransaction Transaction = Driver.BeginTransaction();

            // To prevent half complete snapshots due to exceptions,
            // Put the whole thing in a try block, and rollback on error
            try
            {
                // Loop through each player, and process them
                int PlayerPosition = 0;
                foreach (Dictionary<string, string> Player in PlayerData)
                {
                    // Parse some player data
                    int Pid = Int32.Parse(Player["pID"]);
                    int Time = Int32.Parse(Player["ctime"]);
                    int SqlTime = Int32.Parse(Player["tsl"]);
                    int SqmTime = Int32.Parse(Player["tsm"]);
                    int LwTime = Int32.Parse(Player["tlw"]);
                    int Army = Int32.Parse(Player["a"]);
                    int RoundScore = Int32.Parse(Player["rs"]);
                    int CurRank = Int32.Parse(Player["rank"]);
                    int Kills = Int32.Parse(Player["kills"]);
                    int Deaths = Int32.Parse(Player["deaths"]);
                    int Ks = Int32.Parse(Player["ks"]);
                    int Ds = Int32.Parse(Player["ds"]);
                    bool IsAi = (Int32.Parse(Player["ai"]) != 0);
                    bool CompletedRound = (Int32.Parse(Player["c"]) == 1);
                    bool OnWinningTeam = (Int32.Parse(Player["t"]) == WinningTeam);
                    IPAddress PlayerIp = IPAddress.Loopback;

                    // Player meets min round time or are we ignoring AI?
                    if ((Time < MainForm.Config.ASP_MinRoundTime) || (MainForm.Config.ASP_IgnoreAI && IsAi))
                        continue;

                    // Add map data
                    MapScore += RoundScore;
                    MapKills += Kills;
                    MapDeaths += Deaths;

                    // Fix N/A Ip address
                    if (Player["ip"] == "N/A")
                        Player["ip"] = "127.0.0.1";

                    // Sometimes Squad times are negative.. idk why, but we need to fix that here
                    if (SqlTime < 0) SqlTime = 0;
                    if (SqmTime < 0) SqmTime = 0;
                    if (LwTime < 0) LwTime = 0;

                    // Log
                    Log(String.Format("Processing Player ({0})", Pid), LogLevel.Notice);

                    // Fetch the player
                    string Query;
                    Rows = Driver.Query("SELECT COUNT(id) AS count FROM player WHERE id=@P0", Pid);
                    if (int.Parse(Rows[0]["count"].ToString()) == 0)
                    {
                        // === New Player === //

                        // Log
                        Log(String.Format("Adding NEW Player ({0})", Pid), LogLevel.Notice);

                        // Get playres country code
                        IPAddress.TryParse(Player["ip"], out PlayerIp);
                        string CC = GetCountryCode(PlayerIp);

                        // Build insert data
                        InsertQuery = new InsertQueryBuilder("player", Driver);
                        InsertQuery.SetField("id", Pid);
                        InsertQuery.SetField("name", Player["name"]);
                        InsertQuery.SetField("country", CC);
                        InsertQuery.SetField("time", Time);
                        InsertQuery.SetField("rounds", Player["c"]);
                        InsertQuery.SetField("ip", Player["ip"]);
                        InsertQuery.SetField("score", Player["rs"]);
                        InsertQuery.SetField("cmdscore", Player["cs"]);
                        InsertQuery.SetField("skillscore", Player["ss"]);
                        InsertQuery.SetField("teamscore", Player["ts"]);
                        InsertQuery.SetField("kills", Player["kills"]);
                        InsertQuery.SetField("deaths", Player["deaths"]);
                        InsertQuery.SetField("captures", Player["cpc"]);
                        InsertQuery.SetField("captureassists", Player["cpa"]);
                        InsertQuery.SetField("defends", Player["cpd"]);
                        InsertQuery.SetField("damageassists", Player["ka"]);
                        InsertQuery.SetField("heals", Player["he"]);
                        InsertQuery.SetField("revives", Player["rev"]);
                        InsertQuery.SetField("ammos", Player["rsp"]);
                        InsertQuery.SetField("repairs", Player["rep"]);
                        InsertQuery.SetField("targetassists", Player["tre"]);
                        InsertQuery.SetField("driverspecials", Player["drs"]);
                        InsertQuery.SetField("teamkills", Player["tmkl"]);
                        InsertQuery.SetField("teamdamage", Player["tmdg"]);
                        InsertQuery.SetField("teamvehicledamage", Player["tmvd"]);
                        InsertQuery.SetField("suicides", Player["su"]);
                        InsertQuery.SetField("killstreak", Player["ks"]);
                        InsertQuery.SetField("deathstreak", Player["ds"]);
                        InsertQuery.SetField("rank", Player["rank"]);
                        InsertQuery.SetField("banned", Player["ban"]);
                        InsertQuery.SetField("kicked", Player["kck"]);
                        InsertQuery.SetField("cmdtime", Player["tco"]);
                        InsertQuery.SetField("sqltime", SqlTime);
                        InsertQuery.SetField("sqmtime", SqmTime);
                        InsertQuery.SetField("lwtime", LwTime);
                        InsertQuery.SetField("wins", OnWinningTeam);
                        InsertQuery.SetField("losses", !OnWinningTeam);
                        InsertQuery.SetField("availunlocks", 0);
                        InsertQuery.SetField("usedunlocks", 0);
                        InsertQuery.SetField("joined", TimeStamp);
                        InsertQuery.SetField("rndscore", Player["rs"]);
                        InsertQuery.SetField("lastonline", MapEnd);
                        InsertQuery.SetField("mode0", ((GameMode == 0) ? 1 : 0));
                        InsertQuery.SetField("mode1", ((GameMode == 1) ? 1 : 0));
                        InsertQuery.SetField("mode2", ((GameMode == 2) ? 1 : 0));
                        InsertQuery.SetField("isbot", Player["ai"]);

                        // Insert Player Data
                        InsertQuery.Execute();

                        // Create Player Unlock Data
                        Query = "INSERT INTO unlocks VALUES ";
                        for (int i = 11; i < 100; i += 11)
                            Query += String.Format("({0}, {1}, 'n'), ", Pid, i);
                        for (int i = 111; i < 556; i += 111)
                            Query += String.Format("({0}, {1}, 'n'), ", Pid, i);
                        Driver.Execute(Query.TrimEnd(new char[] { ',', ' ' }));
                    }
                    else
                    {
                        // Existing Player

                        // Log
                        Log(String.Format("Updating EXISTING Player ({0})", Pid), LogLevel.Notice);

                        // Fetch Player
                        Rows = Driver.Query("SELECT ip, country, rank, killstreak, deathstreak, rndscore FROM player WHERE id=@P0", Pid);
                        Dictionary<string, object> DataRow = Rows[0];

                        // Setup vars
                        string CC = DataRow["country"].ToString();
                        int DbRank = Int32.Parse(DataRow["rank"].ToString());

                        // Update country if the ip has changed
                        IPAddress.TryParse(Player["ip"], out PlayerIp);
                        if (DataRow["ip"].ToString() != Player["ip"])
                            CC = GetCountryCode(PlayerIp);

                        // Verify/Correct Rank
                        if (MainForm.Config.ASP_StatsRankCheck)
                        {
                            // Fail-safe in-case rank data was not obtained and reset to '0' in-game.
                            if (DbRank > CurRank)
                            {
                                Player["rank"] = DbRank.ToString();
                                DebugLog.Write("Rank Correction ({0}), Using database rank ({1})", Pid, DbRank);
                            }
                        }

                        // Calcuate best killstreak/deathstreak
                        int KillStreak = Int32.Parse(DataRow["killstreak"].ToString());
                        int DeathStreak = Int32.Parse(DataRow["deathstreak"].ToString());
                        if (Ks > KillStreak) KillStreak = Ks;
                        if (Ds > DeathStreak) DeathStreak = Ds;

                        // Calculate Best Round Score
                        int Brs = Int32.Parse(DataRow["rndscore"].ToString());
                        if (RoundScore > Brs) Brs = RoundScore;

                        // Calculate rank change
                        int chng = 0;
                        int decr = 0;
                        if (DbRank != CurRank)
                        {
                            if (CurRank > DbRank)
                                chng = 1;
                            else
                                decr = 1;
                        }

                        // Update Player Data
                        UpdateQuery = new UpdateQueryBuilder("player", Driver);
                        UpdateQuery.SetField("country", CC);
                        UpdateQuery.SetField("time", Time, ValueMode.Add);
                        UpdateQuery.SetField("rounds", Player["c"], ValueMode.Add);
                        UpdateQuery.SetField("ip", Player["ip"]);
                        UpdateQuery.SetField("score", Player["rs"], ValueMode.Add);
                        UpdateQuery.SetField("cmdscore", Player["cs"], ValueMode.Add);
                        UpdateQuery.SetField("skillscore", Player["ss"], ValueMode.Add);
                        UpdateQuery.SetField("teamscore", Player["ts"], ValueMode.Add);
                        UpdateQuery.SetField("kills", Player["kills"], ValueMode.Add);
                        UpdateQuery.SetField("deaths", Player["deaths"], ValueMode.Add);
                        UpdateQuery.SetField("captures", Player["cpc"], ValueMode.Add);
                        UpdateQuery.SetField("captureassists", Player["cpa"], ValueMode.Add);
                        UpdateQuery.SetField("defends", Player["cpd"], ValueMode.Add);
                        UpdateQuery.SetField("damageassists", Player["ks"], ValueMode.Add);
                        UpdateQuery.SetField("heals", Player["he"], ValueMode.Add);
                        UpdateQuery.SetField("revives", Player["rev"], ValueMode.Add);
                        UpdateQuery.SetField("ammos", Player["rsp"], ValueMode.Add);
                        UpdateQuery.SetField("repairs", Player["rep"], ValueMode.Add);
                        UpdateQuery.SetField("targetassists", Player["tre"], ValueMode.Add);
                        UpdateQuery.SetField("driverspecials", Player["drs"], ValueMode.Add);
                        UpdateQuery.SetField("teamkills", Player["tmkl"], ValueMode.Add);
                        UpdateQuery.SetField("teamdamage", Player["tmdg"], ValueMode.Add);
                        UpdateQuery.SetField("teamvehicledamage", Player["tmvd"], ValueMode.Add);
                        UpdateQuery.SetField("suicides", Player["su"], ValueMode.Add);
                        UpdateQuery.SetField("Killstreak", KillStreak, ValueMode.Set);
                        UpdateQuery.SetField("deathstreak", DeathStreak, ValueMode.Set);
                        UpdateQuery.SetField("rank", CurRank, ValueMode.Set);
                        UpdateQuery.SetField("banned", Player["ban"], ValueMode.Add);
                        UpdateQuery.SetField("kicked", Player["kck"], ValueMode.Add);
                        UpdateQuery.SetField("cmdtime", Player["tco"], ValueMode.Add);
                        UpdateQuery.SetField("sqltime", SqlTime, ValueMode.Add);
                        UpdateQuery.SetField("sqmtime", SqmTime, ValueMode.Add);
                        UpdateQuery.SetField("lwtime", LwTime, ValueMode.Add);
                        UpdateQuery.SetField("wins", ((OnWinningTeam) ? 1 : 0), ValueMode.Add);
                        UpdateQuery.SetField("losses", ((!OnWinningTeam) ? 1 : 0), ValueMode.Add);
                        UpdateQuery.SetField("rndscore", Brs, ValueMode.Set);
                        UpdateQuery.SetField("lastonline", TimeStamp, ValueMode.Set);
                        UpdateQuery.SetField("mode0", ((GameMode == 0) ? 1 : 0), ValueMode.Add);
                        UpdateQuery.SetField("mode1", ((GameMode == 1) ? 1 : 0), ValueMode.Add);
                        UpdateQuery.SetField("mode2", ((GameMode == 2) ? 1 : 0), ValueMode.Add);
                        UpdateQuery.SetField("chng", chng, ValueMode.Set);
                        UpdateQuery.SetField("decr", decr, ValueMode.Set);
                        UpdateQuery.SetField("isbot", Player["ai"], ValueMode.Set);
                        UpdateQuery.AddWhere("id", Comparison.Equals, Pid);
                        UpdateQuery.Execute();
                    }

                    // ********************************
                    // Insert Player history.
                    // ********************************
                    Driver.Execute(
                        "INSERT INTO player_history VALUES(@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8, @P9)",
                        Pid, TimeStamp, Time, RoundScore, Player["cs"], Player["ss"], Player["ts"],
                        Kills, Deaths, CurRank
                    );

                    // ********************************
                    // Process Player Army Data
                    // ********************************
                    Log(String.Format("Processing Army Data ({0})", Pid), LogLevel.Notice);

                    // DO team counts
                    if(Army == Team1Army)
                    {
                        Team1Players++;
                        if (CompletedRound) // Completed round?
                            Team1PlayersEnd++;
                    }
                    else
                    {
                        Team2Players++;
                        if (CompletedRound) // Completed round?
                            Team2PlayersEnd++;
                    }

                    // Update player army times
                    Rows = Driver.Query("SELECT * FROM army WHERE id=" + Pid);
                    if (Rows.Count == 0)
                    {
                        InsertQuery = new InsertQueryBuilder("army", Driver);
                        InsertQuery.SetField("id", Pid);
                        InsertQuery.SetField("time0", Player["ta0"]);
                        InsertQuery.SetField("time1", Player["ta1"]);
                        InsertQuery.SetField("time2", Player["ta2"]);
                        InsertQuery.SetField("time3", Player["ta3"]);
                        InsertQuery.SetField("time4", Player["ta4"]);
                        InsertQuery.SetField("time5", Player["ta5"]);
                        InsertQuery.SetField("time6", Player["ta6"]);
                        InsertQuery.SetField("time7", Player["ta7"]);
                        InsertQuery.SetField("time8", Player["ta8"]);
                        InsertQuery.SetField("time9", Player["ta9"]);
                        InsertQuery.SetField("time10", Player["ta10"]);
                        InsertQuery.SetField("time11", Player["ta11"]);
                        InsertQuery.SetField("time12", Player["ta12"]);
                        InsertQuery.SetField("time13", Player["ta13"]);

                        // Make sure we arent playing an unsupported army
                        if (Army < 14)
                        {
                            InsertQuery.SetField("win" + Army, ((OnWinningTeam) ? 1 : 0));
                            InsertQuery.SetField("loss" + Army, ((!OnWinningTeam) ? 1 : 0));
                            InsertQuery.SetField("score" + Army, Player["rs"]);
                            InsertQuery.SetField("best" + Army, Player["rs"]);
                            InsertQuery.SetField("worst" + Army, Player["rs"]);
                        }

                        InsertQuery.Execute();
                    }
                    else
                    {
                        UpdateQuery = new UpdateQueryBuilder("army", Driver);
                        UpdateQuery.AddWhere("id", Comparison.Equals, Pid);
                        UpdateQuery.SetField("time0", Player["ta0"], ValueMode.Add);
                        UpdateQuery.SetField("time1", Player["ta1"], ValueMode.Add);
                        UpdateQuery.SetField("time2", Player["ta2"], ValueMode.Add);
                        UpdateQuery.SetField("time3", Player["ta3"], ValueMode.Add);
                        UpdateQuery.SetField("time4", Player["ta4"], ValueMode.Add);
                        UpdateQuery.SetField("time5", Player["ta5"], ValueMode.Add);
                        UpdateQuery.SetField("time6", Player["ta6"], ValueMode.Add);
                        UpdateQuery.SetField("time7", Player["ta7"], ValueMode.Add);
                        UpdateQuery.SetField("time8", Player["ta8"], ValueMode.Add);
                        UpdateQuery.SetField("time9", Player["ta9"], ValueMode.Add);
                        UpdateQuery.SetField("time10", Player["ta10"], ValueMode.Add);
                        UpdateQuery.SetField("time11", Player["ta11"], ValueMode.Add);
                        UpdateQuery.SetField("time12", Player["ta12"], ValueMode.Add);
                        UpdateQuery.SetField("time13", Player["ta13"], ValueMode.Add);

                        // Prevent database errors with custom army IDs
                        if (Army < 14)
                        {
                            string Best = (Int32.Parse(Rows[0]["best" + Army].ToString()) > RoundScore)
                                ? Rows[0]["best" + Army].ToString()
                                : Player["rs"];
                            string Worst = (Int32.Parse(Rows[0]["worst" + Army].ToString()) > RoundScore)
                                ? Rows[0]["worst" + Army].ToString()
                                : Player["rs"];

                            UpdateQuery.SetField("win" + Army, OnWinningTeam, ValueMode.Add);
                            UpdateQuery.SetField("loss" + Army, !OnWinningTeam, ValueMode.Add);
                            UpdateQuery.SetField("score" + Army, Player["rs"], ValueMode.Add);
                            UpdateQuery.SetField("best" + Army, Best, ValueMode.Set);
                            UpdateQuery.SetField("worst" + Army, Worst, ValueMode.Set);
                        }

                        UpdateQuery.Execute();
                    }

                    // ********************************
                    // Process Player Kills
                    // ********************************
                    Log(String.Format("Processing Kills Data ({0})", Pid), LogLevel.Notice);

                    foreach (KeyValuePair<string, string> Kill in KillData[PlayerPosition])
                    {
                        string Victim = Kill.Key;
                        int KillCount = Int32.Parse(Kill.Value);
                        Rows = Driver.Query("SELECT count FROM kills WHERE attacker=@P0 AND victim=@P1", Pid, Victim);
                        if (Rows.Count == 0)
                        {
                            InsertQuery = new InsertQueryBuilder("kills", Driver);
                            InsertQuery.SetField("attacker", Pid);
                            InsertQuery.SetField("victim", Victim);
                            InsertQuery.SetField("count", KillCount);
                            InsertQuery.Execute();
                        }
                        else
                        {
                            UpdateQuery = new UpdateQueryBuilder("kills", Driver);
                            UpdateQuery.SetField("count", KillCount, ValueMode.Add);
                            Where = UpdateQuery.AddWhere("attacker", Comparison.Equals, Pid);
                            Where.AddClause(LogicOperator.And, "victim", Comparison.Equals, Victim);
                            UpdateQuery.Execute();
                        }
                    }

                    // ********************************
                    // Process Player Kit Data
                    // ********************************
                    Log(String.Format("Processing Kit Data ({0})", Pid), LogLevel.Notice);

                    Rows = Driver.Query("SELECT time0 FROM kits WHERE id=" + Pid);
                    if (Rows.Count == 0)
                    {
                        InsertQuery = new InsertQueryBuilder("kits", Driver);
                        InsertQuery.SetField("id", Pid);
                        for (int i = 0; i < 7; i++)
                        {
                            InsertQuery.SetField("time" + i, Player["tk" + i]);
                            InsertQuery.SetField("kills" + i, Player["kk" + i]);
                            InsertQuery.SetField("deaths" + i, Player["dk" + i]);
                        }
                        InsertQuery.Execute();
                    }
                    else
                    {
                        UpdateQuery = new UpdateQueryBuilder("kits", Driver);
                        UpdateQuery.AddWhere("id", Comparison.Equals, Pid);
                        for (int i = 0; i < 7; i++)
                        {
                            UpdateQuery.SetField("time" + i, Player["tk" + i], ValueMode.Add);
                            UpdateQuery.SetField("kills" + i, Player["kk" + i], ValueMode.Add);
                            UpdateQuery.SetField("deaths" + i, Player["dk" + i], ValueMode.Add);
                        }
                        UpdateQuery.Execute();
                    }

                    // ********************************
                    // Process Player Vehicle Data
                    // ********************************
                    Log(String.Format("Processing Vehicle Data ({0})", Pid), LogLevel.Notice);

                    Rows = Driver.Query("SELECT time0 FROM vehicles WHERE id=" + Pid);
                    if (Rows.Count == 0)
                    {
                        InsertQuery = new InsertQueryBuilder("vehicles", Driver);
                        InsertQuery.SetField("id", Pid);
                        for (int i = 0; i < 7; i++)
                        {
                            InsertQuery.SetField("time" + i, Player["tv" + i]);
                            InsertQuery.SetField("kills" + i, Player["kv" + i]);
                            InsertQuery.SetField("deaths" + i, Player["bv" + i]);
                            InsertQuery.SetField("rk" + i, Player["kvr" + i]);
                        }
                        InsertQuery.SetField("timepara", Player["tvp"]);
                        InsertQuery.Execute();
                    }
                    else
                    {
                        UpdateQuery = new UpdateQueryBuilder("vehicles", Driver);
                        UpdateQuery.AddWhere("id", Comparison.Equals, Pid);
                        for (int i = 0; i < 7; i++)
                        {
                            UpdateQuery.SetField("time" + i, Player["tv" + i], ValueMode.Add);
                            UpdateQuery.SetField("kills" + i, Player["kv" + i], ValueMode.Add);
                            UpdateQuery.SetField("deaths" + i, Player["bv" + i], ValueMode.Add);
                            UpdateQuery.SetField("rk" + i, Player["kvr" + i], ValueMode.Add);
                        }
                        UpdateQuery.SetField("timepara", Player["tvp"], ValueMode.Add);
                        UpdateQuery.Execute();
                    }

                    // ********************************
                    // Process Player Weapon Data
                    // ********************************
                    Log(String.Format("Processing Weapon Data ({0})", Pid), LogLevel.Notice);

                    Rows = Driver.Query("SELECT time0 FROM weapons WHERE id=" + Pid);
                    if (Rows.Count == 0)
                    {
                        // Prepare Query
                        InsertQuery = new InsertQueryBuilder("weapons", Driver);
                        InsertQuery.SetField("id", Pid);

                        // Basic Weapon Data
                        for (int i = 0; i < 9; i++)
                        {
                            InsertQuery.SetField("time" + i, Player["tw" + i]);
                            InsertQuery.SetField("kills" + i, Player["kw" + i]);
                            InsertQuery.SetField("deaths" + i, Player["bw" + i]);
                            InsertQuery.SetField("fired" + i, Player["sw" + i]);
                            InsertQuery.SetField("hit" + i, Player["hw" + i]);
                        }

                        // Knife Data
                        InsertQuery.SetField("knifetime", Player["te0"]);
                        InsertQuery.SetField("knifekills", Player["ke0"]);
                        InsertQuery.SetField("knifedeaths", Player["be0"]);
                        InsertQuery.SetField("knifefired", Player["se0"]);
                        InsertQuery.SetField("knifehit", Player["he0"]);

                        // C4 Data
                        InsertQuery.SetField("c4time", Player["te1"]);
                        InsertQuery.SetField("c4kills", Player["ke1"]);
                        InsertQuery.SetField("c4deaths", Player["be1"]);
                        InsertQuery.SetField("c4fired", Player["se1"]);
                        InsertQuery.SetField("c4hit", Player["he1"]);

                        // Handgrenade
                        InsertQuery.SetField("handgrenadetime", Player["te3"]);
                        InsertQuery.SetField("handgrenadekills", Player["ke3"]);
                        InsertQuery.SetField("handgrenadedeaths", Player["be3"]);
                        InsertQuery.SetField("handgrenadefired", Player["se3"]);
                        InsertQuery.SetField("handgrenadehit", Player["he3"]);

                        // Claymore
                        InsertQuery.SetField("claymoretime", Player["te2"]);
                        InsertQuery.SetField("claymorekills", Player["ke2"]);
                        InsertQuery.SetField("claymoredeaths", Player["be2"]);
                        InsertQuery.SetField("claymorefired", Player["se2"]);
                        InsertQuery.SetField("claymorehit", Player["he2"]);

                        // Shockpad
                        InsertQuery.SetField("shockpadtime", Player["te4"]);
                        InsertQuery.SetField("shockpadkills", Player["ke4"]);
                        InsertQuery.SetField("shockpaddeaths", Player["be4"]);
                        InsertQuery.SetField("shockpadfired", Player["se4"]);
                        InsertQuery.SetField("shockpadhit", Player["he4"]);

                        // At Mine
                        InsertQuery.SetField("atminetime", Player["te5"]);
                        InsertQuery.SetField("atminekills", Player["ke5"]);
                        InsertQuery.SetField("atminedeaths", Player["be5"]);
                        InsertQuery.SetField("atminefired", Player["se5"]);
                        InsertQuery.SetField("atminehit", Player["he5"]);

                        // Tactical
                        InsertQuery.SetField("tacticaltime", Player["te6"]);
                        InsertQuery.SetField("tacticaldeployed", Player["de6"]);

                        // Grappling Hook
                        InsertQuery.SetField("grapplinghooktime", Player["te7"]);
                        InsertQuery.SetField("grapplinghookdeployed", Player["de7"]);
                        InsertQuery.SetField("grapplinghookdeaths", Player["be9"]);

                        // Zipline
                        InsertQuery.SetField("ziplinetime", Player["te8"]);
                        InsertQuery.SetField("ziplinedeployed", Player["de8"]);
                        InsertQuery.SetField("ziplinedeaths", Player["be8"]);

                        // Do Query
                        InsertQuery.Execute();
                    }
                    else
                    {
                        // Prepare Query
                        UpdateQuery = new UpdateQueryBuilder("weapons", Driver);
                        UpdateQuery.AddWhere("id", Comparison.Equals, Pid);

                        // Basic Weapon Data
                        for (int i = 0; i < 9; i++)
                        {
                            UpdateQuery.SetField("time" + i, Player["tw" + i], ValueMode.Add);
                            UpdateQuery.SetField("kills" + i, Player["kw" + i], ValueMode.Add);
                            UpdateQuery.SetField("deaths" + i, Player["bw" + i], ValueMode.Add);
                            UpdateQuery.SetField("fired" + i, Player["sw" + i], ValueMode.Add);
                            UpdateQuery.SetField("hit" + i, Player["hw" + i], ValueMode.Add);
                        }

                        // Knife Data
                        UpdateQuery.SetField("knifetime", Player["te0"], ValueMode.Add);
                        UpdateQuery.SetField("knifekills", Player["ke0"], ValueMode.Add);
                        UpdateQuery.SetField("knifedeaths", Player["be0"], ValueMode.Add);
                        UpdateQuery.SetField("knifefired", Player["se0"], ValueMode.Add);
                        UpdateQuery.SetField("knifehit", Player["he0"], ValueMode.Add);

                        // C4 Data
                        UpdateQuery.SetField("c4time", Player["te1"], ValueMode.Add);
                        UpdateQuery.SetField("c4kills", Player["ke1"], ValueMode.Add);
                        UpdateQuery.SetField("c4deaths", Player["be1"], ValueMode.Add);
                        UpdateQuery.SetField("c4fired", Player["se1"], ValueMode.Add);
                        UpdateQuery.SetField("c4hit", Player["he1"], ValueMode.Add);

                        // Handgrenade
                        UpdateQuery.SetField("handgrenadetime", Player["te3"], ValueMode.Add);
                        UpdateQuery.SetField("handgrenadekills", Player["ke3"], ValueMode.Add);
                        UpdateQuery.SetField("handgrenadedeaths", Player["be3"], ValueMode.Add);
                        UpdateQuery.SetField("handgrenadefired", Player["se3"], ValueMode.Add);
                        UpdateQuery.SetField("handgrenadehit", Player["he3"], ValueMode.Add);

                        // Claymore
                        UpdateQuery.SetField("claymoretime", Player["te2"], ValueMode.Add);
                        UpdateQuery.SetField("claymorekills", Player["ke2"], ValueMode.Add);
                        UpdateQuery.SetField("claymoredeaths", Player["be2"], ValueMode.Add);
                        UpdateQuery.SetField("claymorefired", Player["se2"], ValueMode.Add);
                        UpdateQuery.SetField("claymorehit", Player["he2"], ValueMode.Add);

                        // Shockpad
                        UpdateQuery.SetField("shockpadtime", Player["te4"], ValueMode.Add);
                        UpdateQuery.SetField("shockpadkills", Player["ke4"], ValueMode.Add);
                        UpdateQuery.SetField("shockpaddeaths", Player["be4"], ValueMode.Add);
                        UpdateQuery.SetField("shockpadfired", Player["se4"], ValueMode.Add);
                        UpdateQuery.SetField("shockpadhit", Player["he4"], ValueMode.Add);

                        // At Mine
                        UpdateQuery.SetField("atminetime", Player["te5"], ValueMode.Add);
                        UpdateQuery.SetField("atminekills", Player["ke5"], ValueMode.Add);
                        UpdateQuery.SetField("atminedeaths", Player["be5"], ValueMode.Add);
                        UpdateQuery.SetField("atminefired", Player["se5"], ValueMode.Add);
                        UpdateQuery.SetField("atminehit", Player["he5"], ValueMode.Add);

                        // Tactical
                        UpdateQuery.SetField("tacticaltime", Player["te6"], ValueMode.Add);
                        UpdateQuery.SetField("tacticaldeployed", Player["de6"], ValueMode.Add);

                        // Grappling Hook
                        UpdateQuery.SetField("grapplinghooktime", Player["te7"], ValueMode.Add);
                        UpdateQuery.SetField("grapplinghookdeployed", Player["de7"], ValueMode.Add);
                        UpdateQuery.SetField("grapplinghookdeaths", Player["be9"], ValueMode.Add);

                        // Zipline
                        UpdateQuery.SetField("ziplinetime", Player["te8"], ValueMode.Add);
                        UpdateQuery.SetField("ziplinedeployed", Player["de8"], ValueMode.Add);
                        UpdateQuery.SetField("ziplinedeaths", Player["be8"], ValueMode.Add);

                        // Do Query
                        UpdateQuery.Execute();
                    }

                    // ********************************
                    // Process Player Map Data
                    // ********************************
                    Log(String.Format("Processing Map Data ({0})", Pid), LogLevel.Notice);

                    Rows = Driver.Query("SELECT best, worst FROM maps WHERE id=@P0 AND mapid=@P1", Pid, MapId);
                    if (Rows.Count == 0)
                    {
                        // Prepare Query
                        InsertQuery = new InsertQueryBuilder("maps", Driver);
                        InsertQuery.SetField("id", Pid);
                        InsertQuery.SetField("mapid", MapId);
                        InsertQuery.SetField("time", Time);
                        InsertQuery.SetField("win", ((OnWinningTeam) ? 1 : 0));
                        InsertQuery.SetField("loss", ((!OnWinningTeam) ? 1 : 0));
                        InsertQuery.SetField("best", RoundScore);
                        InsertQuery.SetField("worst", RoundScore);
                        InsertQuery.Execute();
                    }
                    else
                    {
                        // Get best and worst round scores
                        string Best = ((Int32.Parse(Rows[0]["best"].ToString()) > RoundScore) ? Rows[0]["best"].ToString() : RoundScore.ToString());
                        string Worst = ((Int32.Parse(Rows[0]["worst"].ToString()) > RoundScore) ? Rows[0]["worst"].ToString() : RoundScore.ToString());

                        // Prepare Query
                        UpdateQuery = new UpdateQueryBuilder("maps", Driver);
                        Where = UpdateQuery.AddWhere("id", Comparison.Equals, Pid);
                        Where.AddClause(LogicOperator.And, "mapid", Comparison.Equals, MapId);
                        UpdateQuery.SetField("time", Time, ValueMode.Add);
                        UpdateQuery.SetField("win", ((OnWinningTeam) ? 1 : 0), ValueMode.Add);
                        UpdateQuery.SetField("loss", ((!OnWinningTeam) ? 1 : 0), ValueMode.Add);
                        UpdateQuery.SetField("best", Best, ValueMode.Add);
                        UpdateQuery.SetField("worst", Worst, ValueMode.Add);
                        UpdateQuery.Execute();
                    }

                    // ********************************
                    // Process Player Awards Data
                    // ********************************
                    Log(String.Format("Processing Award Data ({0})", Pid), LogLevel.Notice);

                    // Do we require round completion for award processing?
                    if (CompletedRound || !MainForm.Config.ASP_AwardsReqComplete)
                    {
                        // Get our list of awards we earned in the round
                        Dictionary<int, int> Awards = GetRoundAwards(Pid, Player);
                        foreach (KeyValuePair<int, int> Award in Awards)
                        {
                            int First = 0;
                            int AwardId = Award.Key;
                            int Level = Award.Value;

                            // If isMedal
                            if (AwardId > 2000000 && AwardId < 3000000)
                                Query = String.Format("SELECT level FROM awards WHERE id={0} AND awd={1}", Pid, AwardId);
                            else
                                Query = String.Format("SELECT level FROM awards WHERE id={0} AND awd={1} AND level={2}", Pid, AwardId, Level);

                            // Check for prior awarding of award
                            Rows = Driver.Query(Query);
                            if (Rows.Count == 0)
                            {
                                // Medals
                                if (AwardId > 2000000 && AwardId < 3000000)
                                    First = TimeStamp;

                                // Badges
                                else if(AwardId < 2000000)
                                {
                                    // Need to do extra work for Badges as more than one badge per round may have been awarded
                                    for (int j = 1; j < Level; j++)
                                    {
                                        Rows = Driver.Query("SELECT level FROM awards WHERE id=@P0 AND awd=@P1 AND level=@P2", Pid, AwardId, j);
                                        if (Rows.Count == 0)
                                        {
                                            // Prepare Query
                                            InsertQuery = new InsertQueryBuilder("awards", Driver);
                                            InsertQuery.SetField("id", Pid);
                                            InsertQuery.SetField("awd", AwardId);
                                            InsertQuery.SetField("level", j);
                                            InsertQuery.SetField("earned", (TimeStamp - 5) + j);
                                            InsertQuery.SetField("first", First);
                                            InsertQuery.Execute();
                                        }
                                    }
                                }

                                // Add the players award
                                InsertQuery = new InsertQueryBuilder("awards", Driver);
                                InsertQuery.SetField("id", Pid);
                                InsertQuery.SetField("awd", AwardId);
                                InsertQuery.SetField("level", Level);
                                InsertQuery.SetField("earned", TimeStamp);
                                InsertQuery.SetField("first", First);
                                InsertQuery.Execute();

                            }
                            else
                            {
                                // Player has recived this award prior //

                                // If award if a medal (Because ribbons and badges are only awarded once ever!)
                                if (AwardId > 2000000 && AwardId < 3000000)
                                {
                                    // Prepare Query
                                    UpdateQuery = new UpdateQueryBuilder("awards", Driver);
                                    Where = UpdateQuery.AddWhere("id", Comparison.Equals, Pid);
                                    Where.AddClause(LogicOperator.And, "awd", Comparison.Equals, AwardId);
                                    UpdateQuery.SetField("level", 1, ValueMode.Add);
                                    UpdateQuery.SetField("earned", TimeStamp, ValueMode.Set);
                                    UpdateQuery.Execute();
                                }
                            }

                            // Add best round count if player earned best round medal
                            if (OnWinningTeam && AwardId == 2051907)
                            {
                                // Prepare Query
                                UpdateQuery = new UpdateQueryBuilder("army", Driver);
                                UpdateQuery.AddWhere("id", Comparison.Equals, Pid);
                                UpdateQuery.SetField("brnd" + Army, 1, ValueMode.Add);
                                UpdateQuery.Execute();
                            }

                        } // End Foreach Award
                    }

                    // Increment player position
                    PlayerPosition++;
                } // End Foreach Player

                // Commit the transaction
                try
                {
                    Transaction.Commit();
                }
                catch (Exception E)
                {
                    try {
                        Transaction.Rollback();
                    }
                    catch { }

                    // Log error
                    Log("An error occured while commiting player changes: " + E.Message, LogLevel.Error);
                    return;
                }
            }
            catch(Exception E)
            {
                Log("An error occured while updating player stats: " + E.Message, LogLevel.Error);
                Transaction.Rollback();
            }

            // ********************************
            // Process ServerInfo
            // ********************************
            //Log("Processing Game Server", LogLevel.Notice);

            // ********************************
            // Process MapInfo
            // ********************************
            Log(String.Format("Processing Map Info ({0}:{1})", MapName, MapId), LogLevel.Notice);

            TimeSpan Timer = new TimeSpan(Convert.ToInt64(MapEnd - MapStart));
            Rows = Driver.Query("SELECT COUNT(id) AS count FROM mapinfo WHERE id=" + MapId);
            if(Int32.Parse(Rows[0]["count"].ToString()) == 0)
            {
                // Prepare Query
                InsertQuery = new InsertQueryBuilder("mapinfo", Driver);
                InsertQuery.SetField("id", MapId);
                InsertQuery.SetField("name", MapName);
                InsertQuery.SetField("score", MapScore);
                InsertQuery.SetField("time", Timer.Seconds);
                InsertQuery.SetField("times", 1);
                InsertQuery.SetField("kills", MapKills);
                InsertQuery.SetField("deaths", MapDeaths);
                InsertQuery.SetField("custom", (IsCustomMap) ? 1 : 0);
                InsertQuery.Execute();
            }
            else
            {
                UpdateQuery = new UpdateQueryBuilder("mapinfo", Driver);
                UpdateQuery.AddWhere("id", Comparison.Equals, MapId);
                UpdateQuery.SetField("score", MapScore, ValueMode.Add);
                UpdateQuery.SetField("time", Timer.Seconds, ValueMode.Add);
                UpdateQuery.SetField("times", 1, ValueMode.Add);
                UpdateQuery.SetField("kills", MapKills, ValueMode.Add);
                UpdateQuery.SetField("deaths", MapDeaths, ValueMode.Add);
                UpdateQuery.Execute();
            }

            // ********************************
            // Process RoundInfo
            // ********************************
            Log("Processing Round Info", LogLevel.Notice);
            InsertQuery = new InsertQueryBuilder("round_history", Driver);
            InsertQuery.SetField("timestamp", MapStart);
            InsertQuery.SetField("mapid", MapId);
            InsertQuery.SetField("time", Timer.Seconds);
            InsertQuery.SetField("team1", Team1Army);
            InsertQuery.SetField("team2", Team2Army);
            InsertQuery.SetField("tickets1", Team1Tickets);
            InsertQuery.SetField("tickets2", Team2Tickets);
            InsertQuery.SetField("pids1", Team1Players);
            InsertQuery.SetField("pids1_end", Team1PlayersEnd);
            InsertQuery.SetField("pids2", Team2Players);
            InsertQuery.SetField("pids2_end", Team2PlayersEnd);
            InsertQuery.Execute();

            // ********************************
            // Process Smoc And General Ranks
            // ********************************
            if (MainForm.Config.ASP_SmocCheck) SmocCheck();
            if (MainForm.Config.ASP_GeneralCheck) GenCheck();

            // Call our Finished Event
            Timer = new TimeSpan(Clock.ElapsedTicks);
            Log(String.Format("Snapshot ({0}) processed in {1} milliseconds", MapName, Timer.Milliseconds), LogLevel.Info);
            SnapshotProccessed();
        }
        /// <summary>
        /// Imports a players stats from the official gamespy ASP.
        /// This method is to be used in a background worker
        /// </summary>
        public void ImportEaStats(object sender, DoWorkEventArgs e)
        {
            // Setup variables
            BackgroundWorker Worker = (BackgroundWorker)sender;
            int Pid = Int32.Parse(e.Argument.ToString());

            // Make sure redirects are disabled
            if (MainForm.RedirectsEnabled)
                throw new Exception("Cant import player when Gamespy redirects are active");

            // Make sure the player doesnt exist!
            if(PlayerExists(Pid))
                throw new Exception(String.Format("Player with PID {0} already exists!", Pid));

            // Build variables
            Uri GsUrl;
            WebRequest Request;
            HttpWebResponse Response;
            List<string[]> PlayerLines;
            List<string[]> AwardLines;
            List<string[]> MapLines;
            InsertQueryBuilder Query;

            // Create Request
            string Url = String.Format(
                "getplayerinfo.aspx?pid={0}&info=per*,cmb*,twsc,cpcp,cacp,dfcp,kila,heal,rviv,rsup,rpar,tgte,dkas,dsab,cdsc,rank,cmsc,kick,kill,deth,suic,"
                + "ospm,klpm,klpr,dtpr,bksk,wdsk,bbrs,tcdr,ban,dtpm,lbtl,osaa,vrk,tsql,tsqm,tlwf,mvks,vmks,mvn*,vmr*,fkit,fmap,fveh,fwea,wtm-,wkl-,wdt-,"
                + "wac-,wkd-,vtm-,vkl-,vdt-,vkd-,vkr-,atm-,awn-,alo-,abr-,ktm-,kkl-,kdt-,kkd-",
                Pid);
            Worker.ReportProgress(1, "Requesting Player Stats");
            GsUrl = new Uri("http://bf2web.gamespy.com/ASP/" + Url);
            Request = WebRequest.Create(GsUrl);

            // Get response
            Response = (HttpWebResponse)Request.GetResponse();
            if (Response.StatusCode != HttpStatusCode.OK)
                throw new Exception("Unable to connect to the Gamespy ASP Webservice!");

            // Read response data
            Worker.ReportProgress(2, "Parsing Stats Response");
            PlayerLines = new List<string[]>();
            using (StreamReader Reader = new StreamReader(Response.GetResponseStream()))
                while(!Reader.EndOfStream)
                    PlayerLines.Add(Reader.ReadLine().Split('\t'));

            // Does the player exist?
            if (PlayerLines[0][0] != "O")
                throw new Exception("Player does not exist on the gamespy servers!");

            // Fetch player mapinfo
            Worker.ReportProgress(3, "Requesting Player Map Data");
            GsUrl = new Uri(String.Format("http://bf2web.gamespy.com/ASP/getplayerinfo.aspx?pid={0}&info=mtm-,mwn-,mls-", Pid));
            Request = WebRequest.Create(GsUrl);

            // Get response
            Response = (HttpWebResponse)Request.GetResponse();
            if (Response.StatusCode != HttpStatusCode.OK)
                throw new Exception("Unable to connect to the Gamespy ASP Webservice!");

            // Read response data
            Worker.ReportProgress(4, "Parsing Map Data Response");
            MapLines = new List<string[]>();
            using (StreamReader Reader = new StreamReader(Response.GetResponseStream()))
                while(!Reader.EndOfStream)
                    MapLines.Add(Reader.ReadLine().Split('\t'));

            // Fetch player awards
            Worker.ReportProgress(5, "Requesting Player Awards");
            GsUrl = new Uri(String.Format("http://bf2web.gamespy.com/ASP/getawardsinfo.aspx?pid={0}", Pid));
            Request = WebRequest.Create(GsUrl);

            // Get response
            Response = (HttpWebResponse)Request.GetResponse();
            if (Response.StatusCode != HttpStatusCode.OK)
                throw new Exception("Unable to connect to the Gamespy ASP Webservice!");

            // Read response data
            Worker.ReportProgress(6, "Parsing Player Awards Response");
            AwardLines = new List<string[]>();
            using (StreamReader Reader = new StreamReader(Response.GetResponseStream()))
                while(!Reader.EndOfStream)
                    AwardLines.Add(Reader.ReadLine().Split('\t'));

            // === Process Player Info === //

            // Parse Player Data
            Dictionary<string, string> PlayerInfo = StatsParser.ParseHeaderData(PlayerLines[3], PlayerLines[4]);
            int Rounds = Int32.Parse(PlayerInfo["mode0"]) + Int32.Parse(PlayerInfo["mode1"]) + Int32.Parse(PlayerInfo["mode2"]);

            // Begin database transaction
            DbTransaction Transaction = BeginTransaction();

            // Wrap all DB inserts into a try block so we can rollback on error
            try
            {
                // Insert Player Data
                Worker.ReportProgress(7, "Inserting Player Data Into Table: player");
                Query = new InsertQueryBuilder("player", this);
                Query.SetField("id", Pid);
                Query.SetField("name", " " + PlayerInfo["nick"].Trim()); // Online accounts always start with space in bf2stats
                Query.SetField("country", "xx");
                Query.SetField("time", PlayerInfo["time"]);
                Query.SetField("rounds", Rounds);
                Query.SetField("ip", "127.0.0.1");
                Query.SetField("score", PlayerInfo["scor"]);
                Query.SetField("cmdscore", PlayerInfo["cdsc"]);
                Query.SetField("skillscore", PlayerInfo["cmsc"]);
                Query.SetField("teamscore", PlayerInfo["twsc"]);
                Query.SetField("kills", PlayerInfo["kill"]);
                Query.SetField("deaths", PlayerInfo["deth"]);
                Query.SetField("captures", PlayerInfo["cpcp"]);
                Query.SetField("captureassists", PlayerInfo["cacp"]);
                Query.SetField("defends", PlayerInfo["dfcp"]);
                Query.SetField("damageassists", PlayerInfo["kila"]);
                Query.SetField("heals", PlayerInfo["heal"]);
                Query.SetField("revives", PlayerInfo["rviv"]);
                Query.SetField("ammos", PlayerInfo["rsup"]);
                Query.SetField("repairs", PlayerInfo["rpar"]);
                Query.SetField("driverspecials", PlayerInfo["dsab"]);
                Query.SetField("suicides", PlayerInfo["suic"]);
                Query.SetField("killstreak", PlayerInfo["bksk"]);
                Query.SetField("deathstreak", PlayerInfo["wdsk"]);
                Query.SetField("rank", PlayerInfo["rank"]);
                Query.SetField("banned", PlayerInfo["ban"]);
                Query.SetField("kicked", PlayerInfo["kick"]);
                Query.SetField("cmdtime", PlayerInfo["tcdr"]);
                Query.SetField("sqltime", PlayerInfo["tsql"]);
                Query.SetField("sqmtime", PlayerInfo["tsqm"]);
                Query.SetField("lwtime", PlayerInfo["tlwf"]);
                Query.SetField("wins", PlayerInfo["wins"]);
                Query.SetField("losses", PlayerInfo["loss"]);
                Query.SetField("joined", PlayerInfo["jond"]);
                Query.SetField("rndscore", PlayerInfo["bbrs"]);
                Query.SetField("lastonline", PlayerInfo["lbtl"]);
                Query.SetField("mode0", PlayerInfo["mode0"]);
                Query.SetField("mode1", PlayerInfo["mode1"]);
                Query.SetField("mode2", PlayerInfo["mode2"]);
                Query.Execute();

                // Insert Army Data
                Worker.ReportProgress(8, "Inserting Player Data Into Table: army");
                Query = new InsertQueryBuilder("army", this);
                Query.SetField("id", Pid);
                for (int i = 0; i < 10; i++)
                {
                    Query.SetField("time" + i, PlayerInfo["atm-" + i]);
                    Query.SetField("win" + i, PlayerInfo["awn-" + i]);
                    Query.SetField("loss" + i, PlayerInfo["alo-" + i]);
                    Query.SetField("best" + i, PlayerInfo["abr-" + i]);
                }
                Query.Execute();

                // Insert Kit Data
                Worker.ReportProgress(9, "Inserting Player Data Into Table: kits");
                Query = new InsertQueryBuilder("kits", this);
                Query.SetField("id", Pid);
                for (int i = 0; i < 7; i++)
                {
                    Query.SetField("time" + i, PlayerInfo["ktm-" + i]);
                    Query.SetField("kills" + i, PlayerInfo["kkl-" + i]);
                    Query.SetField("deaths" + i, PlayerInfo["kdt-" + i]);
                }
                Query.Execute();

                // Insert Vehicle Data
                Worker.ReportProgress(10, "Inserting Player Data Into Table: vehicles");
                Query = new InsertQueryBuilder("vehicles", this);
                Query.SetField("id", Pid);
                Query.SetField("timepara", 0);
                for (int i = 0; i < 7; i++)
                {
                    Query.SetField("time" + i, PlayerInfo["vtm-" + i]);
                    Query.SetField("kills" + i, PlayerInfo["vkl-" + i]);
                    Query.SetField("deaths" + i, PlayerInfo["vdt-" + i]);
                    Query.SetField("rk" + i, PlayerInfo["vkr-" + i]);
                }
                Query.Execute();

                // Insert Weapon Data
                Worker.ReportProgress(11, "Inserting Player Data Into Table: weapons");
                Query = new InsertQueryBuilder("weapons", this);
                Query.SetField("id", Pid);
                for (int i = 0; i < 9; i++)
                {
                    Query.SetField("time" + i, PlayerInfo["wtm-" + i]);
                    Query.SetField("kills" + i, PlayerInfo["wkl-" + i]);
                    Query.SetField("deaths" + i, PlayerInfo["wdt-" + i]);
                }

                // Knife
                Query.SetField("knifetime", PlayerInfo["wtm-9"]);
                Query.SetField("knifekills", PlayerInfo["wkl-9"]);
                Query.SetField("knifedeaths", PlayerInfo["wdt-9"]);
                // Shockpad
                Query.SetField("shockpadtime", PlayerInfo["wtm-10"]);
                Query.SetField("shockpadkills", PlayerInfo["wkl-10"]);
                Query.SetField("shockpaddeaths", PlayerInfo["wdt-10"]);
                // Claymore
                Query.SetField("claymoretime", PlayerInfo["wtm-11"]);
                Query.SetField("claymorekills", PlayerInfo["wkl-11"]);
                Query.SetField("claymoredeaths", PlayerInfo["wdt-11"]);
                // Handgrenade
                Query.SetField("handgrenadetime", PlayerInfo["wtm-12"]);
                Query.SetField("handgrenadekills", PlayerInfo["wkl-12"]);
                Query.SetField("handgrenadedeaths", PlayerInfo["wdt-12"]);
                // SF Weapn Data
                Query.SetField("tacticaldeployed", PlayerInfo["de-6"]);
                Query.SetField("grapplinghookdeployed", PlayerInfo["de-7"]);
                Query.SetField("ziplinedeployed", PlayerInfo["de-8"]);

                Query.Execute();

                // === Process Awards Data === //
                Worker.ReportProgress(12, "Inserting Player Awards");
                List<Dictionary<string, string>> Awards = StatsParser.ParseAwards(AwardLines);
                foreach (Dictionary<string, string> Award in Awards)
                {
                    Query = new InsertQueryBuilder("awards", this);
                    Query.SetField("id", Pid);
                    Query.SetField("awd", Award["id"]);
                    Query.SetField("level", Award["level"]);
                    Query.SetField("earned", Award["when"]);
                    Query.SetField("first", Award["first"]);
                    Query.Execute();
                }

                // === Process Map Data === //
                Worker.ReportProgress(13, "Inserting Player Map Data");
                PlayerInfo = StatsParser.ParseHeaderData(MapLines[3], MapLines[4]);
                int[] Maps = new int[] {
                    0, 1, 2, 3, 4, 5, 6, 100, 101, 102, 103, 105,
                    601, 300, 301, 302, 303, 304, 305, 306, 307,
                    10, 11, 110, 200, 201, 202, 12
                };
                foreach (int MapId in Maps)
                {
                    if (PlayerInfo.ContainsKey("mtm-" + MapId))
                    {
                        Query = new InsertQueryBuilder("maps", this);
                        Query.SetField("id", Pid);
                        Query.SetField("mapid", MapId);
                        Query.SetField("time", PlayerInfo["mtm-" + MapId]);
                        Query.SetField("win", PlayerInfo["mwn-" + MapId]);
                        Query.SetField("loss", PlayerInfo["mls-" + MapId]);
                        Query.SetField("best", 0);
                        Query.SetField("worst", 0);
                        Query.Execute();
                    }
                }

                // Commit transaction
                Transaction.Commit();
            }
            catch (Exception)
            {
                Transaction.Rollback();
                throw;
            }
            finally
            {
                // Dispose dispose the transaction
                Transaction.Dispose();
            }
        }
        /// <summary>
        /// Processes the snapshot data, inserted and updating player data in the gamespy database
        /// </summary>
        /// <exception cref="InvalidDataException">Thrown if the snapshot data is invalid</exception>
        public void ProcessData()
        {
            // Make sure we are processing the same data again
            if (this.IsProcessed)
                throw new Exception("Round data has already been processed!");

            // Begin Logging
            Log(String.Format("Begin Processing ({0})...", this.MapName), LogLevel.Notice);
            Log(String.Format((this.IsCustomMap) ? "Custom Map ({0})..." : "Standard Map ({0})...", this.MapId), LogLevel.Notice);
            Log("Found " + this.Players.Count + " Player(s)...", LogLevel.Notice);

            // Make sure we meet the minimum player requirement
            if (this.Players.Count < Program.Config.ASP_MinRoundPlayers)
            {
                Log("Minimum round Player count does not meet the ASP requirement... Aborting", LogLevel.Warning);
                throw new Exception("Minimum round Player count does not meet the ASP requirement");
            }

            // CDB update
            if (this.SnapshotMode == SnapshotMode.Minimal)
                Log("Snapshot mode set to CentralDatabase.Minimal. Rank and Award data will be ingnored", LogLevel.Notice);

            // Setup some variables
            Stopwatch Clock = Stopwatch.StartNew();
            List<Dictionary<string, object>> Rows;
            InsertQueryBuilder InsertQuery;
            UpdateQueryBuilder UpdateQuery;
            WhereClause Where;

            // Start the timer and Begin Transaction
            using (StatsDatabase Driver = new StatsDatabase())
            using (DbTransaction Transaction = Driver.BeginTransaction())
            {
                // To prevent half complete snapshots due to exceptions,
                // Put the whole thing in a try block, and rollback on error
                try
                {
                    // Loop through each player, and process them
                    foreach (Player Player in this.Players)
                    {
                        // Player meets min round time or are we ignoring AI?
                        if ((Player.RoundTime < Program.Config.ASP_MinRoundTime) || (Program.Config.ASP_IgnoreAI && Player.IsAI))
                            continue;

                        // Parse some player data
                        bool OnWinningTeam = (Player.Team == WinningTeam);
                        string CountryCode = Ip2nation.GetCountryCode(Player.IpAddress);
                        int Best, Worst;

                        // Log
                        Log(String.Format("Processing Player ({0})", Player.Pid), LogLevel.Notice);

                        // Fetch the player
                        Rows = Driver.Query("SELECT ip, country, rank, killstreak, deathstreak, rndscore FROM player WHERE id=@P0", Player.Pid);
                        if (Rows.Count == 0)
                        {
                            // === New Player === //
                            Log(String.Format("Adding NEW Player ({0})", Player.Pid), LogLevel.Notice);

                            // We dont save rank data on CentralDatabase.Minimal
                            if (this.SnapshotMode == SnapshotMode.Minimal)
                                Player.SetRank(0);

                            // Build insert data
                            InsertQuery = new InsertQueryBuilder("player", Driver);
                            InsertQuery.SetField("id", Player.Pid);
                            InsertQuery.SetField("name", Player.Name);
                            InsertQuery.SetField("country", CountryCode);
                            InsertQuery.SetField("time", Player.RoundTime);
                            InsertQuery.SetField("rounds", Player.CompletedRound);
                            InsertQuery.SetField("ip", Player.IpAddress);
                            InsertQuery.SetField("score", Player.RoundScore);
                            InsertQuery.SetField("cmdscore", Player.CommandScore);
                            InsertQuery.SetField("skillscore", Player.SkillScore);
                            InsertQuery.SetField("teamscore", Player.TeamScore);
                            InsertQuery.SetField("kills", Player.Stats.Kills);
                            InsertQuery.SetField("deaths", Player.Stats.Deaths);
                            InsertQuery.SetField("captures", Player.Stats.FlagCaptures);
                            InsertQuery.SetField("captureassists", Player.Stats.FlagCaptureAssists);
                            InsertQuery.SetField("defends", Player.Stats.FlagDefends);
                            InsertQuery.SetField("damageassists", Player.Stats.DamageAssists);
                            InsertQuery.SetField("heals", Player.Stats.Heals);
                            InsertQuery.SetField("revives", Player.Stats.Revives);
                            InsertQuery.SetField("ammos", Player.Stats.Ammos);
                            InsertQuery.SetField("repairs", Player.Stats.Repairs);
                            InsertQuery.SetField("targetassists", Player.Stats.TargetAssists);
                            InsertQuery.SetField("driverspecials", Player.Stats.DriverSpecials);
                            InsertQuery.SetField("teamkills", Player.Stats.TeamKills);
                            InsertQuery.SetField("teamdamage", Player.Stats.TeamDamage);
                            InsertQuery.SetField("teamvehicledamage", Player.Stats.TeamVehicleDamage);
                            InsertQuery.SetField("suicides", Player.Stats.Suicides);
                            InsertQuery.SetField("killstreak", Player.Stats.KillStreak);
                            InsertQuery.SetField("deathstreak", Player.Stats.DeathStreak);
                            InsertQuery.SetField("rank", Player.Rank);
                            InsertQuery.SetField("banned", Player.TimesBanned);
                            InsertQuery.SetField("kicked", Player.TimesKicked);
                            InsertQuery.SetField("cmdtime", Player.CmdTime);
                            InsertQuery.SetField("sqltime", Player.SqlTime);
                            InsertQuery.SetField("sqmtime", Player.SqmTime);
                            InsertQuery.SetField("lwtime", Player.LwTime);
                            InsertQuery.SetField("wins", OnWinningTeam);
                            InsertQuery.SetField("losses", !OnWinningTeam);
                            InsertQuery.SetField("availunlocks", 0);
                            InsertQuery.SetField("usedunlocks", 0);
                            InsertQuery.SetField("joined", this.RoundEndTime);
                            InsertQuery.SetField("rndscore", Player.RoundScore);
                            InsertQuery.SetField("lastonline", RoundEndTime);
                            InsertQuery.SetField("mode0", (GameMode == 0));
                            InsertQuery.SetField("mode1", (GameMode == 1));
                            InsertQuery.SetField("mode2", (GameMode == 2));
                            InsertQuery.SetField("isbot", Player.IsAI);

                            // Insert Player Data
                            InsertQuery.Execute();

                            // Double Check Unlocks
                            if (!Player.IsAI && Driver.ExecuteScalar<int>("SELECT COUNT(*) FROM unlocks WHERE id=@P0", Player.Pid) == 0)
                            {
                                // Create New Player Unlock Data
                                StringBuilder Q = new StringBuilder("INSERT INTO unlocks VALUES ", 350);

                                // Normal unlocks
                                for (int i = 11; i < 100; i += 11)
                                    Q.AppendFormat("({0}, {1}, 'n'), ", Player.Pid, i);

                                // Sf Unlocks
                                for (int i = 111; i < 556; i += 111)
                                {
                                    Q.AppendFormat("({0}, {1}, 'n')", Player.Pid, i);
                                    if (i != 555) Q.Append(", ");
                                }

                                // Execute query
                                Driver.Execute(Q.ToString());
                            }
                        }
                        else
                        {
                            // Existing Player
                            Log(String.Format("Updating EXISTING Player ({0})", Player.Pid), LogLevel.Notice);

                            // If rank is lower then the database rank, and the players old rank is not a special rank
                            // then we will be correct the rank here. We do this because sometimes stats are interupted
                            // while being fetched in the python (yay single threading!), and the players stats are reset
                            // during that round.
                            int DbRank = Int32.Parse(Rows[0]["rank"].ToString());
                            if (this.SnapshotMode == SnapshotMode.Minimal)
                            {
                                // On CDB mode, always use database rank
                                Player.SetRank(DbRank);
                            }
                            else if (DbRank > Player.Rank && DbRank != 11 && DbRank != 21)
                            {
                                // Fail-safe in-case rank data was not obtained and reset to '0' in-game.
                                Player.SetRank(DbRank);
                                DebugLog.Write("Rank Correction ({0}), Using database rank ({1})", Player.Pid, DbRank);
                            }

                            // Calcuate best killstreak/deathstreak
                            int KillStreak = Int32.Parse(Rows[0]["killstreak"].ToString());
                            int DeathStreak = Int32.Parse(Rows[0]["deathstreak"].ToString());
                            if (Player.Stats.KillStreak > KillStreak) KillStreak = Player.Stats.KillStreak;
                            if (Player.Stats.DeathStreak > DeathStreak) DeathStreak = Player.Stats.DeathStreak;

                            // Calculate Best Round Score
                            int Brs = Int32.Parse(Rows[0]["rndscore"].ToString());
                            if (Player.RoundScore > Brs) Brs = Player.RoundScore;

                            // Update Player Data
                            UpdateQuery = new UpdateQueryBuilder("player", Driver);
                            UpdateQuery.SetField("country", CountryCode, ValueMode.Set);
                            UpdateQuery.SetField("time", Player.RoundTime, ValueMode.Add);
                            UpdateQuery.SetField("rounds", Player.CompletedRound, ValueMode.Add);
                            UpdateQuery.SetField("ip", Player.IpAddress, ValueMode.Set);
                            UpdateQuery.SetField("score", Player.RoundScore, ValueMode.Add);
                            UpdateQuery.SetField("cmdscore", Player.CommandScore, ValueMode.Add);
                            UpdateQuery.SetField("skillscore", Player.SkillScore, ValueMode.Add);
                            UpdateQuery.SetField("teamscore", Player.TeamScore, ValueMode.Add);
                            UpdateQuery.SetField("kills", Player.Stats.Kills, ValueMode.Add);
                            UpdateQuery.SetField("deaths", Player.Stats.Deaths, ValueMode.Add);
                            UpdateQuery.SetField("captures", Player.Stats.FlagCaptures, ValueMode.Add);
                            UpdateQuery.SetField("captureassists", Player.Stats.FlagCaptureAssists, ValueMode.Add);
                            UpdateQuery.SetField("defends", Player.Stats.FlagDefends, ValueMode.Add);
                            UpdateQuery.SetField("damageassists", Player.Stats.DamageAssists, ValueMode.Add);
                            UpdateQuery.SetField("heals", Player.Stats.Heals, ValueMode.Add);
                            UpdateQuery.SetField("revives", Player.Stats.Revives, ValueMode.Add);
                            UpdateQuery.SetField("ammos", Player.Stats.Ammos, ValueMode.Add);
                            UpdateQuery.SetField("repairs", Player.Stats.Repairs, ValueMode.Add);
                            UpdateQuery.SetField("targetassists", Player.Stats.TargetAssists, ValueMode.Add);
                            UpdateQuery.SetField("driverspecials", Player.Stats.DriverSpecials, ValueMode.Add);
                            UpdateQuery.SetField("teamkills", Player.Stats.TeamKills, ValueMode.Add);
                            UpdateQuery.SetField("teamdamage", Player.Stats.TeamDamage, ValueMode.Add);
                            UpdateQuery.SetField("teamvehicledamage", Player.Stats.TeamVehicleDamage, ValueMode.Add);
                            UpdateQuery.SetField("suicides", Player.Stats.Suicides, ValueMode.Add);
                            UpdateQuery.SetField("Killstreak", KillStreak, ValueMode.Set);
                            UpdateQuery.SetField("deathstreak", DeathStreak, ValueMode.Set);
                            UpdateQuery.SetField("rank", Player.Rank, ValueMode.Set);
                            UpdateQuery.SetField("banned", Player.TimesBanned, ValueMode.Add);
                            UpdateQuery.SetField("kicked", Player.TimesKicked, ValueMode.Add);
                            UpdateQuery.SetField("cmdtime", Player.CmdTime, ValueMode.Add);
                            UpdateQuery.SetField("sqltime", Player.SqlTime, ValueMode.Add);
                            UpdateQuery.SetField("sqmtime", Player.SqmTime, ValueMode.Add);
                            UpdateQuery.SetField("lwtime", Player.LwTime, ValueMode.Add);
                            UpdateQuery.SetField("wins", OnWinningTeam, ValueMode.Add);
                            UpdateQuery.SetField("losses", !OnWinningTeam, ValueMode.Add);
                            UpdateQuery.SetField("rndscore", Brs, ValueMode.Set);
                            UpdateQuery.SetField("lastonline", this.RoundEndTime, ValueMode.Set);
                            UpdateQuery.SetField("mode0", (GameMode == 0), ValueMode.Add);
                            UpdateQuery.SetField("mode1", (GameMode == 1), ValueMode.Add);
                            UpdateQuery.SetField("mode2", (GameMode == 2), ValueMode.Add);
                            UpdateQuery.SetField("chng", (Player.Rank > DbRank), ValueMode.Set);
                            UpdateQuery.SetField("decr", (Player.Rank < DbRank), ValueMode.Set);
                            UpdateQuery.SetField("isbot", Player.IsAI, ValueMode.Set);
                            UpdateQuery.AddWhere("id", Comparison.Equals, Player.Pid);
                            UpdateQuery.Execute();
                        }

                        // ********************************
                        // Insert Player history.
                        // ********************************
                        Driver.Execute(
                            "INSERT INTO player_history VALUES(@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8, @P9)",
                            Player.Pid, this.RoundEndTime, Player.RoundTime, Player.RoundScore, Player.CommandScore,
                            Player.SkillScore, Player.TeamScore, Player.Stats.Kills, Player.Stats.Deaths, Player.Rank
                        );

                        // ********************************
                        // Process Player Army Data
                        // ********************************
                        Log(String.Format("Processing Army Data ({0})", Player.Pid), LogLevel.Notice);

                        // Update player army times
                        Rows = Driver.Query("SELECT * FROM army WHERE id=" + Player.Pid);
                        if (Rows.Count == 0)
                        {
                            // Build query
                            InsertQuery = new InsertQueryBuilder("army", Driver);
                            InsertQuery.SetField("id", Player.Pid);
                            for (int i = 0; i < 14; i++)
                                InsertQuery.SetField("time" + i, Player.TimeAsArmy[i]);

                            // Make sure we arent playing an unsupported army
                            if (Player.ArmyId < 14)
                            {
                                InsertQuery.SetField("win" + Player.ArmyId, OnWinningTeam);
                                InsertQuery.SetField("loss" + Player.ArmyId, !OnWinningTeam);
                                InsertQuery.SetField("score" + Player.ArmyId, Player.RoundScore);
                                InsertQuery.SetField("best" + Player.ArmyId, Player.RoundScore);
                                InsertQuery.SetField("worst" + Player.ArmyId, Player.RoundScore);
                            }

                            InsertQuery.Execute();
                        }
                        else
                        {
                            // Build query
                            UpdateQuery = new UpdateQueryBuilder("army", Driver);
                            UpdateQuery.AddWhere("id", Comparison.Equals, Player.Pid);
                            for (int i = 0; i < 14; i++)
                                UpdateQuery.SetField("time" + i, Player.TimeAsArmy[i], ValueMode.Add);

                            // Prevent database errors with custom army IDs
                            if (Player.ArmyId < 14)
                            {
                                Best = Int32.Parse(Rows[0]["best" + Player.ArmyId].ToString());
                                Worst = Int32.Parse(Rows[0]["worst" + Player.ArmyId].ToString());
                                if (Player.RoundScore > Best) Best = Player.RoundScore;
                                if (Player.RoundScore < Worst) Worst = Player.RoundScore;

                                UpdateQuery.SetField("win" + Player.ArmyId, OnWinningTeam, ValueMode.Add);
                                UpdateQuery.SetField("loss" + Player.ArmyId, !OnWinningTeam, ValueMode.Add);
                                UpdateQuery.SetField("score" + Player.ArmyId, Player.RoundScore, ValueMode.Add);
                                UpdateQuery.SetField("best" + Player.ArmyId, Best, ValueMode.Set);
                                UpdateQuery.SetField("worst" + Player.ArmyId, Worst, ValueMode.Set);
                            }

                            UpdateQuery.Execute();
                        }

                        // ********************************
                        // Process Player Kills
                        // ********************************
                        Log(String.Format("Processing Kills Data ({0})", Player.Pid), LogLevel.Notice);
                        foreach (KeyValuePair<int, int> Kill in Player.Victims)
                        {
                            // Kill: VictimPid => KillCount
                            if (Driver.ExecuteScalar<int>("SELECT COUNT(*) FROM kills WHERE attacker=@P0 AND victim=@P1", Player.Pid, Kill.Key) == 0)
                            {
                                InsertQuery = new InsertQueryBuilder("kills", Driver);
                                InsertQuery.SetField("attacker", Player.Pid);
                                InsertQuery.SetField("victim", Kill.Key);
                                InsertQuery.SetField("count", Kill.Value);
                                InsertQuery.Execute();
                            }
                            else
                            {
                                UpdateQuery = new UpdateQueryBuilder("kills", Driver);
                                UpdateQuery.SetField("count", Kill.Value, ValueMode.Add);
                                Where = UpdateQuery.AddWhere("attacker", Comparison.Equals, Player.Pid);
                                Where.AddClause(LogicOperator.And, "victim", Comparison.Equals, Kill.Key);
                                UpdateQuery.Execute();
                            }
                        }

                        // ********************************
                        // Process Player Kit Data
                        // ********************************
                        Log(String.Format("Processing Kit Data ({0})", Player.Pid), LogLevel.Notice);

                        // Check for existing player data
                        if (Driver.ExecuteScalar<int>("SELECT COUNT(*) FROM kits WHERE id=" + Player.Pid) == 0)
                        {
                            InsertQuery = new InsertQueryBuilder("kits", Driver);
                            InsertQuery.SetField("id", Player.Pid);
                            for (int i = 0; i < 7; i++)
                            {
                                InsertQuery.SetField("time" + i, Player.KitData[i].Time);
                                InsertQuery.SetField("kills" + i, Player.KitData[i].Kills);
                                InsertQuery.SetField("deaths" + i, Player.KitData[i].Deaths);
                            }
                            InsertQuery.Execute();
                        }
                        else
                        {
                            UpdateQuery = new UpdateQueryBuilder("kits", Driver);
                            UpdateQuery.AddWhere("id", Comparison.Equals, Player.Pid);
                            for (int i = 0; i < 7; i++)
                            {
                                UpdateQuery.SetField("time" + i, Player.KitData[i].Time, ValueMode.Add);
                                UpdateQuery.SetField("kills" + i, Player.KitData[i].Kills, ValueMode.Add);
                                UpdateQuery.SetField("deaths" + i, Player.KitData[i].Deaths, ValueMode.Add);
                            }
                            UpdateQuery.Execute();
                        }

                        // ********************************
                        // Process Player Vehicle Data
                        // ********************************
                        Log(String.Format("Processing Vehicle Data ({0})", Player.Pid), LogLevel.Notice);

                        // Check for existing player data
                        if (Driver.ExecuteScalar<int>("SELECT COUNT(*) FROM vehicles WHERE id=" + Player.Pid) == 0)
                        {
                            InsertQuery = new InsertQueryBuilder("vehicles", Driver);
                            InsertQuery.SetField("id", Player.Pid);
                            for (int i = 0; i < 7; i++)
                            {
                                InsertQuery.SetField("time" + i, Player.VehicleData[i].Time);
                                InsertQuery.SetField("kills" + i, Player.VehicleData[i].Kills);
                                InsertQuery.SetField("deaths" + i, Player.VehicleData[i].Deaths);
                                InsertQuery.SetField("rk" + i, Player.VehicleData[i].RoadKills);
                            }
                            InsertQuery.SetField("timepara", Player.VehicleData[7].Time);
                            InsertQuery.Execute();
                        }
                        else
                        {
                            UpdateQuery = new UpdateQueryBuilder("vehicles", Driver);
                            UpdateQuery.AddWhere("id", Comparison.Equals, Player.Pid);
                            for (int i = 0; i < 7; i++)
                            {
                                UpdateQuery.SetField("time" + i, Player.VehicleData[i].Time, ValueMode.Add);
                                UpdateQuery.SetField("kills" + i, Player.VehicleData[i].Kills, ValueMode.Add);
                                UpdateQuery.SetField("deaths" + i, Player.VehicleData[i].Deaths, ValueMode.Add);
                                UpdateQuery.SetField("rk" + i, Player.VehicleData[i].RoadKills, ValueMode.Add);
                            }
                            UpdateQuery.SetField("timepara", Player.VehicleData[7].Time, ValueMode.Add);
                            UpdateQuery.Execute();
                        }

                        // ********************************
                        // Process Player Weapon Data
                        // ********************************
                        Log(String.Format("Processing Weapon Data ({0})", Player.Pid), LogLevel.Notice);

                        // Check for existing player data
                        if (Driver.ExecuteScalar<int>("SELECT COUNT(*) FROM weapons WHERE id=" + Player.Pid) == 0)
                        {
                            // Prepare Query
                            InsertQuery = new InsertQueryBuilder("weapons", Driver);
                            InsertQuery.SetField("id", Player.Pid);

                            // Basic Weapon Data
                            for (int i = 0; i < 15; i++)
                            {
                                if (i < 9)
                                {
                                    InsertQuery.SetField("time" + i, Player.WeaponData[i].Time);
                                    InsertQuery.SetField("kills" + i, Player.WeaponData[i].Kills);
                                    InsertQuery.SetField("deaths" + i, Player.WeaponData[i].Deaths);
                                    InsertQuery.SetField("fired" + i, Player.WeaponData[i].Fired);
                                    InsertQuery.SetField("hit" + i, Player.WeaponData[i].Hits);
                                }
                                else
                                {
                                    string Pfx = GetWeaponTblPrefix(i);
                                    InsertQuery.SetField(Pfx + "time", Player.WeaponData[i].Time);
                                    InsertQuery.SetField(Pfx + "kills", Player.WeaponData[i].Kills);
                                    InsertQuery.SetField(Pfx + "deaths", Player.WeaponData[i].Deaths);
                                    InsertQuery.SetField(Pfx + "fired", Player.WeaponData[i].Fired);
                                    InsertQuery.SetField(Pfx + "hit", Player.WeaponData[i].Hits);
                                }
                            }

                            // Tactical
                            InsertQuery.SetField("tacticaltime", Player.WeaponData[15].Time);
                            InsertQuery.SetField("tacticaldeployed", Player.WeaponData[15].Deployed);

                            // Grappling Hook
                            InsertQuery.SetField("grapplinghooktime", Player.WeaponData[16].Time);
                            InsertQuery.SetField("grapplinghookdeployed", Player.WeaponData[16].Deployed);
                            InsertQuery.SetField("grapplinghookdeaths", Player.WeaponData[16].Deaths);

                            // Zipline
                            InsertQuery.SetField("ziplinetime", Player.WeaponData[17].Time);
                            InsertQuery.SetField("ziplinedeployed", Player.WeaponData[17].Deployed);
                            InsertQuery.SetField("ziplinedeaths", Player.WeaponData[17].Deaths);

                            // Do Query
                            InsertQuery.Execute();
                        }
                        else
                        {
                            // Prepare Query
                            UpdateQuery = new UpdateQueryBuilder("weapons", Driver);
                            UpdateQuery.AddWhere("id", Comparison.Equals, Player.Pid);

                            // Basic Weapon Data
                            for (int i = 0; i < 15; i++)
                            {
                                if (i < 9)
                                {
                                    UpdateQuery.SetField("time" + i, Player.WeaponData[i].Time, ValueMode.Add);
                                    UpdateQuery.SetField("kills" + i, Player.WeaponData[i].Kills, ValueMode.Add);
                                    UpdateQuery.SetField("deaths" + i, Player.WeaponData[i].Deaths, ValueMode.Add);
                                    UpdateQuery.SetField("fired" + i, Player.WeaponData[i].Fired, ValueMode.Add);
                                    UpdateQuery.SetField("hit" + i, Player.WeaponData[i].Hits, ValueMode.Add);
                                }
                                else
                                {
                                    string Pfx = GetWeaponTblPrefix(i);
                                    UpdateQuery.SetField(Pfx + "time", Player.WeaponData[i].Time, ValueMode.Add);
                                    UpdateQuery.SetField(Pfx + "kills", Player.WeaponData[i].Kills, ValueMode.Add);
                                    UpdateQuery.SetField(Pfx + "deaths", Player.WeaponData[i].Deaths, ValueMode.Add);
                                    UpdateQuery.SetField(Pfx + "fired", Player.WeaponData[i].Fired, ValueMode.Add);
                                    UpdateQuery.SetField(Pfx + "hit", Player.WeaponData[i].Hits, ValueMode.Add);
                                }
                            }

                            // Tactical
                            UpdateQuery.SetField("tacticaltime", Player.WeaponData[15].Time, ValueMode.Add);
                            UpdateQuery.SetField("tacticaldeployed", Player.WeaponData[15].Deployed, ValueMode.Add);

                            // Grappling Hook
                            UpdateQuery.SetField("grapplinghooktime", Player.WeaponData[16].Time, ValueMode.Add);
                            UpdateQuery.SetField("grapplinghookdeployed", Player.WeaponData[16].Deployed, ValueMode.Add);
                            UpdateQuery.SetField("grapplinghookdeaths", Player.WeaponData[16].Deaths, ValueMode.Add);

                            // Zipline
                            UpdateQuery.SetField("ziplinetime", Player.WeaponData[17].Time, ValueMode.Add);
                            UpdateQuery.SetField("ziplinedeployed", Player.WeaponData[17].Deployed, ValueMode.Add);
                            UpdateQuery.SetField("ziplinedeaths", Player.WeaponData[17].Deaths, ValueMode.Add);

                            // Do Query
                            UpdateQuery.Execute();
                        }

                        // ********************************
                        // Process Player Map Data
                        // ********************************
                        Log(String.Format("Processing Map Data ({0})", Player.Pid), LogLevel.Notice);

                        Rows = Driver.Query("SELECT best, worst FROM maps WHERE id=@P0 AND mapid=@P1", Player.Pid, MapId);
                        if (Rows.Count == 0)
                        {
                            // Prepare Query
                            InsertQuery = new InsertQueryBuilder("maps", Driver);
                            InsertQuery.SetField("id", Player.Pid);
                            InsertQuery.SetField("mapid", this.MapId);
                            InsertQuery.SetField("time", Player.RoundTime);
                            InsertQuery.SetField("win", OnWinningTeam);
                            InsertQuery.SetField("loss", !OnWinningTeam);
                            InsertQuery.SetField("best", Player.RoundScore);
                            InsertQuery.SetField("worst", Player.RoundScore);
                            InsertQuery.Execute();
                        }
                        else
                        {
                            // Get best and worst round scores
                            Best = Int32.Parse(Rows[0]["best"].ToString());
                            Worst = Int32.Parse(Rows[0]["worst"].ToString());
                            if (Player.RoundScore > Best) Best = Player.RoundScore;
                            if (Player.RoundScore < Worst) Worst = Player.RoundScore;

                            // Prepare Query
                            UpdateQuery = new UpdateQueryBuilder("maps", Driver);
                            Where = UpdateQuery.AddWhere("id", Comparison.Equals, Player.Pid);
                            Where.AddClause(LogicOperator.And, "mapid", Comparison.Equals, this.MapId);
                            UpdateQuery.SetField("time", Player.RoundTime, ValueMode.Add);
                            UpdateQuery.SetField("win", OnWinningTeam, ValueMode.Add);
                            UpdateQuery.SetField("loss", !OnWinningTeam, ValueMode.Add);
                            UpdateQuery.SetField("best", Best, ValueMode.Set);
                            UpdateQuery.SetField("worst", Worst, ValueMode.Set);
                            UpdateQuery.Execute();
                        }

                        // Quit here on central database Min mode, since award data isnt allowed
                        if (this.SnapshotMode == SnapshotMode.Minimal) continue;

                        // ********************************
                        // Process Player Awards Data
                        // ********************************
                        Log(String.Format("Processing Award Data ({0})", Player.Pid), LogLevel.Notice);

                        // Do we require round completion for award processing?
                        if (Player.CompletedRound || !Program.Config.ASP_AwardsReqComplete)
                        {
                            // Prepare query
                            InsertQuery = new InsertQueryBuilder("awards", Driver);

                            // Add Backend awards too
                            foreach (BackendAward Award in BackendAwardData.BackendAwards)
                            {
                                int Level = 1;
                                if (Award.CriteriaMet(Player.Pid, Driver, ref Level))
                                    Player.EarnedAwards.Add(Award.AwardId, Level);
                            }

                            // Now we loop though each players earned award, and store them in the database
                            foreach (KeyValuePair<int, int> Award in Player.EarnedAwards)
                            {
                                // Get our award type. Award.Key is the ID, Award.Value is the level (or count)
                                bool IsMedal = Award.Key.InRange(2000000, 3000000);
                                bool IsBadge = (Award.Key < 2000000);

                                // Build our query
                                string Query = "SELECT COUNT(*) FROM awards WHERE id=@P0 AND awd=@P1";
                                if (IsBadge)
                                    Query += " AND level=" + Award.Value.ToString();

                                // Check for prior awarding of award
                                if (Driver.ExecuteScalar<int>(Query, Player.Pid, Award.Key) == 0)
                                {
                                    // Need to do extra work for Badges as more than one badge level may have been awarded.
                                    // The snapshot will only post the highest awarded level of a badge, so here we award
                                    // the lower level badges if the player does not have them.
                                    if (IsBadge)
                                    {
                                        // Check all prior badge levels, and make sure the player has them
                                        for (int j = 1; j < Award.Value; j++)
                                        {
                                            Query = "SELECT COUNT(*) FROM awards WHERE id=@P0 AND awd=@P1 AND level=@P2";
                                            if (Driver.ExecuteScalar<int>(Query, Player.Pid, Award.Key, j) == 0)
                                            {
                                                // Prepare Query
                                                InsertQuery.SetField("id", Player.Pid);
                                                InsertQuery.SetField("awd", Award.Key);
                                                InsertQuery.SetField("level", j);
                                                InsertQuery.SetField("earned", (this.RoundEndTime - 5) + j);
                                                InsertQuery.SetField("first", 0);
                                                InsertQuery.Execute();
                                            }
                                        }
                                    }

                                    // Add the players award
                                    InsertQuery.SetField("id", Player.Pid);
                                    InsertQuery.SetField("awd", Award.Key);
                                    InsertQuery.SetField("level", Award.Value);
                                    InsertQuery.SetField("earned", this.RoundEndTime);
                                    InsertQuery.SetField("first", ((IsMedal) ? this.RoundEndTime : 0));
                                    InsertQuery.Execute();

                                }
                                else // === Player has recived this award prior === //
                                {
                                    // Only update medals because ribbons and badges are only awarded once ever!
                                    if (IsMedal)
                                    {
                                        // Prepare Query
                                        UpdateQuery = new UpdateQueryBuilder("awards", Driver);
                                        Where = UpdateQuery.AddWhere("id", Comparison.Equals, Player.Pid);
                                        Where.AddClause(LogicOperator.And, "awd", Comparison.Equals, Award.Key);
                                        UpdateQuery.SetField("level", 1, ValueMode.Add);
                                        UpdateQuery.SetField("earned", this.RoundEndTime, ValueMode.Set);
                                        UpdateQuery.Execute();
                                    }
                                }

                                // Add best round count if player earned best round medal
                                if (Award.Key == 2051907 && Player.ArmyId < 14)
                                {
                                    // Prepare Query
                                    UpdateQuery = new UpdateQueryBuilder("army", Driver);
                                    UpdateQuery.AddWhere("id", Comparison.Equals, Player.Pid);
                                    UpdateQuery.SetField("brnd" + Player.ArmyId, 1, ValueMode.Add);
                                    UpdateQuery.Execute();
                                }

                            } // End Foreach Award
                        } // End Award Processing
                    } // End Foreach Player

                    // ********************************
                    // Process ServerInfo
                    // ********************************
                    //Log("Processing Game Server", LogLevel.Notice);

                    // ********************************
                    // Process MapInfo
                    // ********************************
                    Log(String.Format("Processing Map Info ({0}:{1})", this.MapName, this.MapId), LogLevel.Notice);
                    if (Driver.ExecuteScalar<int>("SELECT COUNT(*) FROM mapinfo WHERE id=" + this.MapId) == 0)
                    {
                        // Prepare Query
                        InsertQuery = new InsertQueryBuilder("mapinfo", Driver);
                        InsertQuery.SetField("id", this.MapId);
                        InsertQuery.SetField("name", this.MapName);
                        InsertQuery.SetField("score", this.MapScore);
                        InsertQuery.SetField("time", this.RoundTime.Seconds);
                        InsertQuery.SetField("times", 1);
                        InsertQuery.SetField("kills", this.MapKills);
                        InsertQuery.SetField("deaths", this.MapDeaths);
                        InsertQuery.SetField("custom", (this.IsCustomMap) ? 1 : 0);
                        InsertQuery.Execute();
                    }
                    else
                    {
                        UpdateQuery = new UpdateQueryBuilder("mapinfo", Driver);
                        UpdateQuery.AddWhere("id", Comparison.Equals, this.MapId);
                        UpdateQuery.SetField("score", this.MapScore, ValueMode.Add);
                        UpdateQuery.SetField("time", this.RoundTime.Seconds, ValueMode.Add);
                        UpdateQuery.SetField("times", 1, ValueMode.Add);
                        UpdateQuery.SetField("kills", this.MapKills, ValueMode.Add);
                        UpdateQuery.SetField("deaths", this.MapDeaths, ValueMode.Add);
                        UpdateQuery.Execute();
                    }

                    // ********************************
                    // Process RoundInfo
                    // ********************************
                    Log("Processing Round Info", LogLevel.Notice);
                    InsertQuery = new InsertQueryBuilder("round_history", Driver);
                    InsertQuery.SetField("timestamp", this.RoundEndTime);
                    InsertQuery.SetField("mapid", this.MapId);
                    InsertQuery.SetField("time", this.RoundTime.Seconds);
                    InsertQuery.SetField("team1", this.Team1ArmyId);
                    InsertQuery.SetField("team2", this.Team2ArmyId);
                    InsertQuery.SetField("tickets1", this.Team1Tickets);
                    InsertQuery.SetField("tickets2", this.Team2Tickets);
                    InsertQuery.SetField("pids1", this.Team1Players);
                    InsertQuery.SetField("pids1_end", this.Team1PlayersEnd);
                    InsertQuery.SetField("pids2", this.Team2Players);
                    InsertQuery.SetField("pids2_end", this.Team2PlayersEnd);
                    InsertQuery.Execute();

                    // ********************************
                    // Process Smoc And General Ranks
                    // ********************************
                    if (Program.Config.ASP_SmocCheck) SmocCheck(Driver);
                    if (Program.Config.ASP_GeneralCheck) GenCheck(Driver);

                    // ********************************
                    // Commit the Transaction and Log
                    // ********************************
                    Transaction.Commit();
                    Clock.Stop();

                    // Log in the stats debug log, and call it
                    this.IsProcessed = true;
                    string logText = String.Format(
                        "Snapshot ({0}) processed in {1} milliseconds [{2} Queries]",
                        this.MapName, Clock.Elapsed.Milliseconds, Driver.NumQueries
                    );
                    Log(logText, LogLevel.Info);
                }
                catch (Exception E)
                {
                    Log("An error occured while updating player stats: " + E.Message, LogLevel.Error);
                    ExceptionHandler.GenerateExceptionLog(E);
                    Transaction.Rollback();
                    throw;
                }
            }
        }