Esempio n. 1
0
        /// <summary>
        /// Returns a bool stating whether the criteria for this award is met for a givin player
        /// </summary>
        /// <param name="Pid">The player ID</param>
        /// <param name="Level">The award level if the criteria is met</param>
        /// <returns></returns>
        public bool CriteriaMet(int Pid, StatsDatabase Driver, ref int Level)
        {
            // Get the award count (or level for badges) for this award
            string Query = "SELECT COALESCE(max(level), 0) FROM awards WHERE id=@P0 AND awd=@P1";
            int AwardCount = Convert.ToInt32(Driver.ExecuteScalar(Query, Pid, AwardId));
            bool IsRibbon = (AwardId > 3000000);

            // Can only recieve ribbons once in a lifetime, so return false if we have it already
            if (IsRibbon && AwardCount > 0)
                return false;

            // Medals and Badges can receive multiple times (Badges are award level, not count)
            if (!IsRibbon)
                Level = AwardCount + 1;

            // Loop through each criteria and see if we have met the criteria
            foreach (AwardCriteria Criteria in Criterias)
            {
                // Build the query. We always use a count() or sum() to return a sortof bool.
                string Where = Criteria.Where.Replace("###", Level.ToString());
                Query = String.Format("SELECT {0} FROM {1} WHERE id={2} AND {3}", Criteria.Field, Criteria.Table, Pid, Where);

                // If we dont meet the expected result, the criteria is unmet, no use continuing
                if (Convert.ToInt32(Driver.ExecuteScalar(Query)) < Criteria.ExpectedResult)
                    return false;
            }

            return true;
        }
        public override void HandleRequest()
        {
            int Pid;

            // make sure we have a valid player ID
            if (!Request.QueryString.ContainsKey("pid")
                || !Int32.TryParse(Request.QueryString["pid"], out Pid)
                || !Request.QueryString.ContainsKey("nick"))
            {
                Response.WriteResponseStart(false);
                Response.WriteHeaderLine("asof", "err");
                Response.WriteDataLine(DateTime.UtcNow.ToUnixTimestamp(), "Invalid Syntax!");
                Response.Send();
                return;
            }

            // NOTE: The HttpServer will handle the DbConnectException
            using (Database = new StatsDatabase())
            {
                // Fetch Player
                string PlayerNick = Request.QueryString["nick"].Replace("%20", " ");
                string CC = (Request.QueryString.ContainsKey("cid")) ? Client.Request.QueryString["cid"] : "";
                if (Database.ExecuteScalar<int>("SELECT COUNT(*) FROM player WHERE id=@P0 OR name=@P1", Pid, PlayerNick) > 0)
                {
                    Response.WriteResponseStart(false);
                    Response.WriteFreeformLine("Player already Exists!");
                    Response.Send();
                    return;
                }

                // Create Player
                Database.Execute(
                    "INSERT INTO player(id, name, country, joined, isbot) VALUES(@P0, @P1, @P2, @P3, 0)",
                    Pid, PlayerNick, CC, DateTime.UtcNow.ToUnixTimestamp()
                );

                // Confirm
                Response.WriteResponseStart();
                Response.WriteFreeformLine("Player Created Successfully!");
                Response.Send();
            }
        }
        /// <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);
        }
        public override void HandleRequest(MvcRoute Route)
        {
            // Get our POST'ed parameters
            Dictionary<string, string> postParams = Client.Request.GetFormUrlEncodedPostVars();

            // If we have a search value, run it
            if (postParams.ContainsKey("searchvalue"))
            {
                // NOTE: The HttpServer will handle the DbConnectException
                using (StatsDatabase Database = new StatsDatabase())
                {
                    Model.SearchValue = postParams["searchvalue"].Replace("+", " ");
                    List<Dictionary<string, object>> Rows;

                    // Do processing
                    if (Validator.IsNumeric(Model.SearchValue))
                    {
                        if (Validator.IsValidPID(Model.SearchValue))
                        {
                            // If player PID exists, redirect there
                            bool exists = Database.ExecuteScalar<bool>("SELECT COUNT(id) FROM player WHERE id=@P0", Model.SearchValue);
                            if (exists)
                            {
                                Client.Response.Redirect("/bf2stats/player/" + Model.SearchValue);
                                return;
                            }
                        }

                        Rows = Database.Query(
                            "SELECT id, name, score, time, country, rank, lastonline, kills, deaths FROM player WHERE id LIKE @P0 LIMIT 50",
                            "%" + Model.SearchValue + "%"
                        );
                    }
                    else
                    {
                        // Check to see if player with this name exists
                        Rows = Database.Query("SELECT id FROM player WHERE name=@P0 LIMIT 1", Model.SearchValue);
                        if (Rows.Count > 0)
                        {
                            Client.Response.Redirect("/bf2stats/player/" + Rows[0]["id"]);
                            return;
                        }

                        Rows = Database.Query(
                            "SELECT id, name, score, time, country, rank, lastonline, kills, deaths FROM player WHERE name LIKE @P0 LIMIT 50",
                            Model.SearchValue
                        );
                    }

                    // Loop through each result, and process
                    foreach (Dictionary<string, object> Row in Rows)
                    {
                        // DO Kill Death Ratio
                        double Kills = Int32.Parse(Row["kills"].ToString());
                        double Deaths = Int32.Parse(Row["deaths"].ToString());
                        double Kdr = (Deaths > 0) ? Math.Round(Kills / Deaths, 3) : Kills;

                        // Get Score Per Min
                        double Score = Int32.Parse(Row["score"].ToString());
                        double Mins = Int32.Parse(Row["time"].ToString()) / 60;
                        double SPM = (Mins > 0) ? Math.Round(Score / Mins, 4) : Score;

                        // Add Result
                        Model.SearchResults.Add(new PlayerResult
                        {
                            Pid = Int32.Parse(Row["id"].ToString()),
                            Name = Row["name"].ToString(),
                            Score = (int)Score,
                            Rank = Int32.Parse(Row["rank"].ToString()),
                            TimePlayed = FormatTime(Int32.Parse(Row["time"].ToString())),
                            LastOnline = FormatDate(Int32.Parse(Row["lastonline"].ToString())),
                            Country = Row["country"].ToString().ToUpperInvariant(),
                            Kdr = Kdr,
                            Spm = SPM
                        });
                    }
                }
            }

            // Send response
            base.SendTemplateResponse("search", typeof(SearchModel), Model);
        }
Esempio n. 5
0
        /// <summary>
        /// Start the ASP listener, and Connects to the stats database
        /// </summary>
        public static void Start()
        {
            // Can't start if we are already running!
            if (IsRunning) return;

            // === Try to connect to the database
            using (StatsDatabase Database = new StatsDatabase())  
            {
                if (!Database.TablesExist)
                {
                    string message = "In order to use the Private Stats feature of this program, we need to setup a database. "
                        + "You may choose to do this later by clicking \"Cancel\". Would you like to setup the database now?";
                    DialogResult R = MessageBox.Show(message, "Stats Database Setup", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                    if (R == DialogResult.Yes)
                        SetupManager.ShowDatabaseSetupForm(DatabaseMode.Stats, MainForm.Instance);

                    // Call the stopped event to Re-enable the main form's buttons
                    Stopped(null, EventArgs.Empty);
                    return;
                }

                // Initialize the stats manager
                StatsManager.Load(Database);

                // Drop the SQLite ip2nation country tables (old table versions)
                if (Database.DatabaseEngine == DatabaseEngine.Sqlite)
                {
                    string query = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='ip2nation'";
                    if (Database.ExecuteScalar<bool>(query)) // 0 count converts to false
                    {
                        Database.Execute("DROP TABLE IF EXISTS 'ip2nation';");
                        Database.Execute("DROP TABLE IF EXISTS 'ip2nationcountries';");
                        Database.Execute("VACUUM;");
                    }
                }
            }

            // === Compile our templates
            string path = Path.Combine(Program.RootPath, "Web", "Bf2Stats", "Views");
            foreach (string file in Directory.EnumerateFiles(path, "*.cshtml"))
            {
                // If this template file is loaded already, then skip
                string fileName = Path.GetFileName(file);
                if (Engine.Razor.IsTemplateCached(fileName, ModelType))
                    continue;

                // Open the file, and compile it
                try
                {
                    using (FileStream stream = File.OpenRead(file))
                    using (StreamReader reader = new StreamReader(stream))
                        Engine.Razor.Compile(reader.ReadToEnd(), fileName, ModelType);
                }
                catch (TemplateCompilationException e)
                {
                    // Show the Exception form so the user can view
                    DialogResult Res = ExceptionForm.ShowTemplateError(e, file);

                    // If the user clicked "Quit", we stop
                    if (Res == DialogResult.Abort) return;
                }
            }
            

            // === Load XML stats and awards files
            Bf2StatsData.Load();
            BackendAwardData.BuildAwardData();

            // Start the Listener and accept new connections
            try
            {
                Listener.Start();
                Listener.BeginGetContext(HandleRequest, Listener);
            }
            catch (ObjectDisposedException)
            {
                // If we are disposed (happens when port 80 was in use already before, and we tried to start)
                // Then we need to start over with a new Listener
                CreateHttpListener();
                Listener.Start();
                Listener.BeginGetContext(HandleRequest, Listener);
            }
                
            // Fire Startup Event
            Started(null, EventArgs.Empty);
        }
        public override void HandleRequest()
        {
            // Get player ID
            if (Request.QueryString.ContainsKey("pid"))
                Int32.TryParse(Request.QueryString["pid"], out Pid);

            // Prepare Output
            Response.WriteResponseStart();
            Response.WriteHeaderLine("pid", "nick", "asof");

            // Our ourput changes based on the selected Unlocks config setting
            switch (Program.Config.ASP_UnlocksMode)
            {
                // Player Based - Unlocks are earned
                case 0:
                    // NOTE: The HttpServer will handle the DbConnectException
                    using (Database = new StatsDatabase())
                    {
                        // Make sure the player exists
                        Rows = Database.Query("SELECT name, score, rank, availunlocks, usedunlocks FROM player WHERE id=@P0", Pid);
                        if (Rows.Count == 0)
                            goto case 2; // No Unlocks

                        // Start Output
                        Response.WriteDataLine(Pid, Rows[0]["name"].ToString().Trim(), DateTime.UtcNow.ToUnixTimestamp());

                        // Get total number of unlocks player is allowed to have givin his rank, and bonus unlocks
                        Rank = Int32.Parse(Rows[0]["rank"].ToString());
                        int HasUsed = Int32.Parse(Rows[0]["usedunlocks"].ToString());
                        int Available = Int32.Parse(Rows[0]["availunlocks"].ToString());
                        int Earned = GetBonusUnlocks();

                        // Determine total unlocks available, based on what he has earned, minus what he has used already
                        int Used = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM unlocks WHERE id=@P0 AND state='s'", Pid);
                        Earned -= Used;

                        // Update database if the database is off
                        if (Earned != Available || HasUsed != Used)
                            Database.Execute("UPDATE player SET availunlocks=@P0, usedunlocks=@P1 WHERE id=@P2", Earned, Used, Pid);

                        // Output more
                        Response.WriteHeaderLine("enlisted", "officer");
                        Response.WriteDataLine(Earned, 0);
                        Response.WriteHeaderLine("id", "state");

                        // Add each unlock's state
                        Rows = Database.Query("SELECT kit, state FROM unlocks WHERE id=@P0 ORDER BY kit ASC", Pid);
                        if (Rows.Count == 0)
                        {
                            // Create Player Unlock Data
                            StringBuilder Query = new StringBuilder("INSERT INTO unlocks VALUES ", 350);

                            // Normal unlocks
                            for (int i = 11; i < 100; i += 11)
                            {
                                // 88 and above are Special Forces unlocks, and wont display at all if the base unlocks are not earned
                                if (i < 78)
                                    Response.WriteDataLine(i, "n");
                                Query.AppendFormat("({0}, {1}, 'n'), ", Pid, i);
                            }

                            // Sf Unlocks, Dont display these because thats how Gamespy does it
                            for (int i = 111; i < 556; i += 111)
                            {
                                Query.AppendFormat("({0}, {1}, 'n')", Pid, i);
                                if (i != 555)
                                    Query.Append(", ");
                            }

                            // Do Insert
                            Database.Execute(Query.ToString());
                        }
                        else
                        {
                            Dictionary<string, bool> Unlocks = new Dictionary<string, bool>(7);
                            foreach (Dictionary<string, object> Unlock in Rows)
                            {
                                // Add unlock to output if its a base unlock
                                int Id = Int32.Parse(Unlock["kit"].ToString());
                                if (Id < 78)
                                    Response.WriteDataLine(Unlock["kit"], Unlock["state"]);

                                // Add Unlock to list
                                Unlocks.Add(Unlock["kit"].ToString(), (Unlock["state"].ToString() == "s"));
                            }

                            // Add SF Unlocks... We need the base class unlock unlocked first
                            CheckUnlock(88, 22, Unlocks);
                            CheckUnlock(99, 33, Unlocks);
                            CheckUnlock(111, 44, Unlocks);
                            CheckUnlock(222, 55, Unlocks);
                            CheckUnlock(333, 66, Unlocks);
                            CheckUnlock(444, 11, Unlocks);
                            CheckUnlock(555, 77, Unlocks);
                        }
                    }
                    break;

                // All Unlocked
                case 1:
                    Response.WriteDataLine(Pid, "All_Unlocks", DateTime.UtcNow.ToUnixTimestamp());
                    Response.WriteHeaderLine("enlisted", "officer");
                    Response.WriteDataLine(0, 0);
                    Response.WriteHeaderLine("id", "state");
                    for (int i = 11; i < 100; i += 11)
                        Response.WriteDataLine(i, "s");
                    for (int i = 111; i < 556; i += 111)
                        Response.WriteDataLine(i, "s");
                    break;

                // Unlocks Disabled
                case 2:
                default:
                    Response.WriteDataLine(Pid, "No_Unlocks", DateTime.UtcNow.ToUnixTimestamp());
                    Response.WriteHeaderLine("enlisted", "officer");
                    Response.WriteDataLine(0, 0);
                    Response.WriteHeaderLine("id", "state");
                    for (int i = 11; i < 78; i += 11)
                        Response.WriteDataLine(i, "n");
                    break;
            }

            // Send Response
            Response.Send();
        }
Esempio n. 7
0
        /// <summary>
        /// Initializes a new Snapshot, with the specified Date it was Posted
        /// </summary>
        /// <param name="Snapshot">The snapshot source</param>
        public Snapshot(string Snapshot, IPAddress ServerIp = null)
        {
            // Set some internal variables
            this.ServerIp = ServerIp ?? IPAddress.Loopback;
            this.Players = new List<Player>();
            this.DataString = Snapshot.Trim();
            string[] Data = DataString.Split('\\');
            Snapshot = null;

            // Check for invalid snapshot string. All snapshots have at least 36 data pairs,
            // and has an Even number of data sectors.
            if (Data.Length < 36 || Data.Length % 2 != 0)
                throw new InvalidDataException("Snapshot does not contain at least 36 elements, or contains an odd number of elements");

            // Assign server name and prefix
            this.ServerPrefix = Data[0];
            this.ServerName = Data[1];

            // Determine if we are central update. the "cdb_update" variable must be the LAST sector in snapshot
            int Mode;
            if (Data[Data.Length - 2] == "cdb_update" && Int32.TryParse(Data[Data.Length - 1], out Mode) && Mode.InRange(1, 2))
            {
                this.SnapshotMode = (Mode == 2) ? SnapshotMode.Minimal : SnapshotMode.FullSync;
            }

            // Setup our data dictionary's. We use NiceDictionary so we can easily determine missing keys in the log file
            NiceDictionary<string, string> StandardData = new NiceDictionary<string, string>(16);
            NiceDictionary<string, string> PlayerData = new NiceDictionary<string, string>();
            Dictionary<int, int> KillData = new Dictionary<int, int>();

            // Wrap parsing Key/Value snapshot data in a try block!
            try
            {
                // Convert our standard data into key => value pairs
                for (int i = 2; i < Data.Length; i += 2)
                {
                    // Format: "DataKey_PlayerIndex". PlayerIndex is NOT the Player Id
                    string[] Parts = Data[i].Split('_');
                    if (Parts.Length == 1)
                    {
                        // Add to the Standard keys
                        StandardData.Add(Data[i], Data[i + 1]);

                        // Are we at the End of File? If so stop here
                        if (Parts[0] == "EOF")
                        {
                            // Make sure to save that last players stats!!
                            if (PlayerData.Count != 0)
                            {
                                AddPlayer(new Player(PlayerData, KillData));
                                PlayerData = null;
                                KillData = null;
                            }
                            break;
                        }
                    }

                    // If the item key is "pID", then we have a new player record
                    else if (Parts[0] == "pID")
                    {
                        // If we have data, complete this player and start anew
                        if (PlayerData.Count != 0)
                        {
                            AddPlayer(new Player(PlayerData, KillData));
                            PlayerData.Clear();
                            KillData.Clear();
                        }

                        // Add new PID
                        PlayerData.Add(Parts[0], Data[i + 1]);
                    }
                    else if (Parts[0] == "mvks") // Skip mvks... kill data only needs processed once (mvns)
                        continue;
                    else if (Parts[0] == "mvns") // Player kill data
                        KillData.Add(Int32.Parse(Data[i + 1]), Int32.Parse(Data[i + 3]));
                    else
                        PlayerData.Add(Parts[0], Data[i + 1]);
                }
            }
            catch (Exception e)
            {
                throw new InvalidDataException("Error assigning Key => value pairs. See InnerException", e);
            }

            // Make sure we have a completed snapshot
            if (!StandardData.ContainsKey("EOF"))
                throw new InvalidDataException("No End of File element was found, Snapshot assumed to be incomplete.");

            // Try and set internal GameResult variables
            try
            {
                // Server data
                this.ServerPort = Int32.Parse(StandardData["gameport"]);
                this.QueryPort = Int32.Parse(StandardData["queryport"]);

                // Map Data
                this.MapName = StandardData["mapname"];
                this.MapId = Int32.Parse(StandardData["mapid"]);
                this.RoundStartTime = (int)Convert.ToDouble(StandardData["mapstart"], CultureInfo.InvariantCulture.NumberFormat);
                this.RoundEndTime = (int)Convert.ToDouble(StandardData["mapend"], CultureInfo.InvariantCulture.NumberFormat);

                // Misc Data
                this.GameMode = Int32.Parse(StandardData["gm"]);
                this.Mod = StandardData["v"]; // bf2 mod replaced the version key, since we dont care the version anyways
                this.PlayersConnected = Int32.Parse(StandardData["pc"]);

                // Army Data... There is no RWA key if there was no winner...
                this.WinningTeam = Int32.Parse(StandardData["win"]); // Temp
                this.WinningArmyId = (StandardData.ContainsKey("rwa")) ? Int32.Parse(StandardData["rwa"]) : -1;
                this.Team1ArmyId = Int32.Parse(StandardData["ra1"]);
                this.Team1Tickets = Int32.Parse(StandardData["rs1"]);
                this.Team2ArmyId = Int32.Parse(StandardData["ra2"]);
                this.Team2Tickets = Int32.Parse(StandardData["rs2"]);
            }
            catch (Exception e)
            {
                throw new InvalidDataException("Error assigning GameResult variables. See InnerException", e);
            }

            // Wrap this in a try-catch block, because we want to be able to view GameResult
            // data, even if the database is offline
            try
            {
                // Dispose when done!
                using (StatsDatabase Driver = new StatsDatabase())
                {
                    // Check for custom map, with no ID (Not defined in Constants.py)
                    if (this.MapId == 99)
                    {
                        // Check for existing map data
                        var Rows = Driver.Query("SELECT id FROM mapinfo WHERE name=@P0", this.MapName);
                        if (Rows.Count == 0)
                        {
                            // Create new MapId. Id's 700 - 1000 are reserved for unknown maps in the Constants.py file
                            // There should never be more then 300 unknown map id's, considering 1001 is the start of KNOWN
                            // Custom mod map id's. If we are at 1000 now, then we are in trouble :S
                            this.MapId = Driver.ExecuteScalar<int>("SELECT COALESCE(MAX(id), 699) FROM mapinfo WHERE id BETWEEN 700 AND 999") + 1;
                            if (this.MapId == 1000)
                                throw new Exception("Maximum unknown custom mapid has been reached. Please add this map's mapid to the Constants.py");

                            // Insert map data, so we dont lose this mapid we generated
                            Driver.Execute("INSERT INTO mapinfo(id, name, custom) VALUES (@P0, @P1, @P2)", this.MapId, this.MapName, 1);
                        }
                        else
                            this.MapId = Int32.Parse(Rows[0]["id"].ToString());
                    }

                    // Set whether or not this data is already been processed. The OR condition is because i goofed in early updates
                    // and set the timestamp to the RoundStart instead of the RoundEnd like i should have
                    this.IsProcessed = Driver.ExecuteScalar<int>(
                        "SELECT COUNT(*) FROM round_history WHERE mapid=@P0 AND time=@P1 AND (timestamp=@P2 OR timestamp=@P3)",
                        this.MapId, this.RoundTime.Seconds, this.RoundEndTime, this.RoundStartTime
                    ) > 0;
                }
            }
            catch(DbConnectException)
            {
                this.IsProcessed = false;
            }

            // Indicate whether we are a custom map
            this.IsCustomMap = (this.MapId >= 700 || this.MapId == 99);
        }
Esempio n. 8
0
        /// <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;
                }
            }
        }