public static void Main(string[] args) { // First, check wether the user needs assistance: if (args.Length == 0 || args [0] == "--help") { PrintHelp(); return; } if (args[0] == "--paths") { bool generatePNGs = false; bool generateCSV = false; bool writePathsToDB = true; switch (args[1]) { case "--png": generatePNGs = true; break; case "--nopng": generatePNGs = false; break; default: generatePNGs = false; break; } switch (args[2]) { case "--csv": generateCSV = true; break; case "--nocsv": generateCSV = false; break; default: generateCSV = false; break; } for (int i = 3; i < args.Length; i++) { using (var fileStream = File.OpenRead(args[i])) { using (var parser = new DemoParser(fileStream)) { PathGenerator.GeneratePath(parser, generatePNGs, generateCSV, writePathsToDB, Path.GetFileName(fileStream.Name)); } } } return; } if (args[0] == "--analyze") { using (var fileStream = File.OpenRead(args[1])) { PathAnalyzer.AnalyzePaths(Path.GetFileName(fileStream.Name), fileStream); } return; } if (args[0] == "--stratmeta") { StratMetaGenerator.GenerateStratMeta(int.Parse(args[1])); return; } if (args [0] == "--scoreboard") { using (var fileStream = File.OpenRead(args[1])) { using (var parser = new DemoParser(fileStream)) { ScoreboardGenerator.GenerateScoreboards(parser); } } return; } if (args [0] == "--frags") { using (var fileStream = File.OpenRead(args[1])) { using (var parser = new DemoParser(fileStream)) { FragGenerator.GenerateFrags(parser); } } return; } // Every argument is a file, so let's iterate over all the arguments // So you can call this program like // > StatisticsGenerator.exe hello.dem bye.dem // It'll generate the statistics. foreach (var fileName in args) { // Okay, first we need to initalize a demo-parser // It takes a stream, so we simply open with a filestream using (var fileStream = File.OpenRead(fileName)) { // By using "using" we make sure that the fileStream is properly disposed // the same goes for the DemoParser which NEEDS to be disposed (else it'll // leak memory and kittens will die. Console.WriteLine("Parsing demo " + fileName); using (var parser = new DemoParser(fileStream)) { // So now we've initialized a demo-parser. // let's parse the head of the demo-file to get which map the match is on! // this is always the first step you need to do. parser.ParseHeader(); // and now, do some magic: grab the match! string map = parser.Map; // And now, generate the filename of the resulting file string outputFileName = fileName + "." + map + ".csv"; // and open it. var outputStream = new StreamWriter(outputFileName); //And write a header so you know what is what in the resulting file outputStream.WriteLine(GenerateCSVHeader()); // Cool! Now let's get started generating the analysis-data. //Let's just declare some stuff we need to remember // Here we'll save how far a player has travelled each round. // Here we remember wheter the match has started yet. bool hasMatchStarted = false; int ctStartroundMoney = 0, tStartroundMoney = 0, ctEquipValue = 0, tEquipValue = 0, ctSaveAmount = 0, tSaveAmount = 0; float ctWay = 0, tWay = 0; int defuses = 0; int plants = 0; Dictionary <Player, int> killsThisRound = new Dictionary <Player, int> (); List <Player> ingame = new List <Player> (); // Since most of the parsing is done via "Events" in CS:GO, we need to use them. // So you bind to events in C# as well. // AFTER we have bound the events, we start the parser! parser.MatchStarted += (sender, e) => { hasMatchStarted = true; //Okay let's output who's really in this game! Console.WriteLine("Participants: "); Console.WriteLine(" Terrorits \"{0}\": ", parser.CTClanName); foreach (var player in parser.PlayingParticipants.Where(a => a.Team == Team.Terrorist)) { Console.WriteLine(" {0} {1} (Steamid: {2})", player.AdditionaInformations.Clantag, player.Name, player.SteamID); } Console.WriteLine(" Counter-Terrorits \"{0}\": ", parser.TClanName); foreach (var player in parser.PlayingParticipants.Where(a => a.Team == Team.CounterTerrorist)) { Console.WriteLine(" {0} {1} (Steamid: {2})", player.AdditionaInformations.Clantag, player.Name, player.SteamID); } // Okay, problem: At the end of the demo // a player might have already left the game, // so we need to store some information // about the players before they left :) ingame.AddRange(parser.PlayingParticipants); }; parser.PlayerKilled += (object sender, PlayerKilledEventArgs e) => { //the killer is null if you're killed by the world - eg. by falling if (e.Killer != null) { if (!killsThisRound.ContainsKey(e.Killer)) { killsThisRound[e.Killer] = 0; } //Remember how many kills each player made this rounds killsThisRound[e.Killer]++; } }; parser.RoundStart += (sender, e) => { if (!hasMatchStarted) { return; } //How much money had each team at the start of the round? ctStartroundMoney = parser.Participants.Where(a => a.Team == Team.CounterTerrorist).Sum(a => a.Money); tStartroundMoney = parser.Participants.Where(a => a.Team == Team.Terrorist).Sum(a => a.Money); //And how much they did they save from the last round? ctSaveAmount = parser.Participants.Where(a => a.Team == Team.CounterTerrorist && a.IsAlive).Sum(a => a.CurrentEquipmentValue); tSaveAmount = parser.Participants.Where(a => a.Team == Team.Terrorist && a.IsAlive).Sum(a => a.CurrentEquipmentValue); //And let's reset those statistics ctWay = 0; tWay = 0; plants = 0; defuses = 0; killsThisRound.Clear(); }; parser.FreezetimeEnded += (sender, e) => { if (!hasMatchStarted) { return; } // At the end of the freezetime (when players can start walking) // calculate the equipment value of each team! ctEquipValue = parser.Participants.Where(a => a.Team == Team.CounterTerrorist).Sum(a => a.CurrentEquipmentValue); tEquipValue = parser.Participants.Where(a => a.Team == Team.Terrorist).Sum(a => a.CurrentEquipmentValue); }; parser.BombPlanted += (sender, e) => { if (!hasMatchStarted) { return; } plants++; }; parser.BombDefused += (sender, e) => { if (!hasMatchStarted) { return; } defuses++; }; parser.TickDone += (sender, e) => { if (!hasMatchStarted) { return; } // Okay, let's measure how far each team travelled. // As you might know from school the amount walked // by a player is the sum of it's velocities foreach (var player in parser.PlayingParticipants) { // We multiply it by the time of one tick // Since the velocity is given in // ingame-units per second float currentWay = (float)(player.Velocity.Absolute * parser.TickTime); // This is just an example of what kind of stuff you can do // with this parser. // Of course you could find out who makes the most footsteps, and find out // which player ninjas the most - just to give you an example if (player.Team == Team.CounterTerrorist) { ctWay += currentWay; } else if (player.Team == Team.Terrorist) { tWay += currentWay; } } }; //So now lets do some fancy output parser.RoundEnd += (sender, e) => { if (!hasMatchStarted) { return; } // We do this in a method-call since we'd else need to duplicate code // The much parameters are there because I simply extracted a method // Sorry for this - you should be able to read it anywys :) PrintRoundResults(parser, outputStream, ctStartroundMoney, tStartroundMoney, ctEquipValue, tEquipValue, ctSaveAmount, tSaveAmount, ctWay, tWay, defuses, plants, killsThisRound); }; //Now let's parse the demo! parser.ParseToEnd(); //And output the result of the last round again. PrintRoundResults(parser, outputStream, ctStartroundMoney, tStartroundMoney, ctEquipValue, tEquipValue, ctSaveAmount, tSaveAmount, ctWay, tWay, defuses, plants, killsThisRound); //Lets just display an end-game-scoreboard! Console.WriteLine("Finished! Results: "); Console.WriteLine(" Terrorits \"{0}\": ", parser.CTClanName); foreach (var player in ingame.Where(a => a.Team == Team.Terrorist)) { Console.WriteLine( " {0} {1} (Steamid: {2}): K: {3}, D: {4}, A: {5}", player.AdditionaInformations.Clantag, player.Name, player.SteamID, player.AdditionaInformations.Kills, player.AdditionaInformations.Deaths, player.AdditionaInformations.Assists ); } Console.WriteLine(" Counter-Terrorits \"{0}\": ", parser.TClanName); foreach (var player in ingame.Where(a => a.Team == Team.CounterTerrorist)) { Console.WriteLine( " {0} {1} (Steamid: {2}): K: {3}, D: {4}, A: {5}", player.AdditionaInformations.Clantag, player.Name, player.SteamID, player.AdditionaInformations.Kills, player.AdditionaInformations.Deaths, player.AdditionaInformations.Assists ); } outputStream.Close(); } } } }
static public void AnalyzePaths(string filename, FileStream fileStream) { long matchID = 0; string mapName = ""; List <ingameRound> allRoundsList = new List <ingameRound>(); List <ingameRound> matchRoundsList = new List <ingameRound>(); List <ingameRound> existingStratRoundsList = new List <ingameRound>(); List <ingamePathpoint> allPathpointsList = new List <ingamePathpoint>(); MySqlConnection sqlConnection = null; MySqlDataReader sqlReader = null; //Setup connection with the MySQL Server////////////////////////////////////// try { string connectionString = "SERVER=localhost;DATABASE=demostatistics;UID=demostatistics;PASSWORD=1ax4X7M4;"; sqlConnection = new MySqlConnection(connectionString); sqlConnection.Open(); Console.WriteLine(getTimeStamp() + "Connected to MySQL Server"); } catch (MySqlException er) { Console.WriteLine(getTimeStamp() + "Error: {0}", er.ToString()); } ////////////////////////////////////////////////////////////////////////////// //Preloading info for analysis./////////////////////////////////////////////// //Get match based on the demo filename string matchSelectQuery = "SELECT id,mapName FROM `match` WHERE filename='" + filename + "'"; MySqlCommand matchSelectionCommand = new MySqlCommand(matchSelectQuery, sqlConnection); sqlReader = matchSelectionCommand.ExecuteReader(); while (sqlReader.Read()) { //For each match matchID = sqlReader.GetInt64("id"); mapName = sqlReader.GetString("mapName"); } sqlReader.Close(); //If match is not found in database launch PathGenerator if (matchID == 0) { Console.WriteLine(getTimeStamp() + "Match not found in DB... Running PathGenerator..."); Console.WriteLine(); Console.WriteLine(); Console.WriteLine(); using (var parser = new DemoParser(fileStream)) { PathGenerator.GeneratePath(parser, false, false, true, filename); } sqlReader = matchSelectionCommand.ExecuteReader(); while (sqlReader.Read()) { //For each match matchID = sqlReader.GetInt64("id"); mapName = sqlReader.GetString("mapName"); } sqlReader.Close(); //System.Environment.Exit(1); } Console.WriteLine(getTimeStamp() + filename + " is being analyzed"); //Prepare statement to get all viable rounds from database Console.Write(getTimeStamp() + "Loading all rounds in DB... "); string allRoundsSelectionQuery = "SELECT id,roundType,stratFound FROM `round` WHERE bombPlanted=1 OR enemyTeamWiped=1 AND mapName='" + mapName + "'"; MySqlCommand allRoundsSelectionCommand = new MySqlCommand(allRoundsSelectionQuery, sqlConnection); sqlReader = allRoundsSelectionCommand.ExecuteReader(); //Load each round which meets the query while (sqlReader.Read()) { allRoundsList.Add(new ingameRound(sqlReader.GetInt64("id"), sqlReader.GetString("roundType"), sqlReader.GetBoolean("stratFound"))); } sqlReader.Close(); Console.WriteLine(allRoundsList.Count + " entries - Done"); //Prepare statement to get viable rounds from match Console.Write(getTimeStamp() + "Loading rounds from Demo-File... "); string matchRoundSelectionQuery = "SELECT id,roundType,stratFound FROM `round` WHERE matchID=" + matchID + " AND bombPlanted=1 OR enemyTeamWiped=1 AND stratFound=0"; MySqlCommand matchRoundSelectionCommand = new MySqlCommand(matchRoundSelectionQuery, sqlConnection); sqlReader = matchRoundSelectionCommand.ExecuteReader(); while (sqlReader.Read()) { matchRoundsList.Add(new ingameRound(sqlReader.GetInt64("id"), sqlReader.GetString("roundType"), sqlReader.GetBoolean("stratFound"))); } sqlReader.Close(); Console.WriteLine(matchRoundsList.Count + " entries - Done"); //Prepare statement to get viable rounds from existing strats Console.Write(getTimeStamp() + "Loading rounds from existing strats... "); string existingStratsRoundSelectionQuery = "SELECT stratID,roundID,roundType FROM `stratrounds`"; MySqlCommand existingStratsRoundSelectionCommand = new MySqlCommand(existingStratsRoundSelectionQuery, sqlConnection); sqlReader = existingStratsRoundSelectionCommand.ExecuteReader(); while (sqlReader.Read()) { existingStratRoundsList.Add(new ingameRound(sqlReader.GetInt64("roundID"), sqlReader.GetString("roundType"), true)); //existingStratRoundsList.Find(n => n.roundID == sqlReader.GetInt64("roundID")).stratID = sqlReader.GetInt64("stratID"); foreach (var existingRound in existingStratRoundsList.Where(n => n.roundID == sqlReader.GetInt64("roundID"))) { existingRound.stratID = sqlReader.GetInt64("stratID"); } } sqlReader.Close(); Console.WriteLine(existingStratRoundsList.Count + " entries - Done"); //Prepare statement to get all viable pathpoints from database Console.Write(getTimeStamp() + "Loading all pathpoints in DB... "); foreach (var round in allRoundsList) { string pathpointsSelectionQuery = "SELECT roundID,X,Y,Z FROM `pathpoints` WHERE roundID=" + round.roundID; MySqlCommand pathpointsSelectionCommand = new MySqlCommand(pathpointsSelectionQuery, sqlConnection); sqlReader = pathpointsSelectionCommand.ExecuteReader(); while (sqlReader.Read()) { allPathpointsList.Add(new ingamePathpoint(sqlReader.GetInt64("roundID"), sqlReader.GetInt32("X"), sqlReader.GetInt32("Y"), sqlReader.GetInt32("Z"))); } sqlReader.Close(); } Console.WriteLine(allPathpointsList.Count + " entries - Done"); ///////////////////////////////////////////////////////////////////////////// //Analysis Loop - ITS HAPPENING!!! Console.WriteLine(); Console.WriteLine(); Console.WriteLine(getTimeStamp() + "Starting analysis loop"); foreach (var round in matchRoundsList) { //Load all pathpoints for this round in the demo file List <ingamePathpoint> demoPathpointList = new List <ingamePathpoint>(); Console.Write(getTimeStamp() + "Loading pathpoints for round " + round.roundID + "... "); string demoPathpointsSelectionQuery = "SELECT roundID,X,Y,Z FROM `pathpoints` WHERE roundID=" + round.roundID; MySqlCommand demoPathpointsSelectionCommand = new MySqlCommand(demoPathpointsSelectionQuery, sqlConnection); sqlReader = demoPathpointsSelectionCommand.ExecuteReader(); while (sqlReader.Read()) { demoPathpointList.Add(new ingamePathpoint(sqlReader.GetInt64("roundID"), sqlReader.GetInt32("X"), sqlReader.GetInt32("Y"), sqlReader.GetInt32("Z"))); } sqlReader.Close(); Console.WriteLine(demoPathpointList.Count + " entries - Done"); bool matchingRoundInDB = false; //List which contains all rounds which are similar to this round from the Demo List <ingameRound> demoRoundSimilarRounds = new List <ingameRound>(); //Add the round from the demo itself to the list demoRoundSimilarRounds.Add(round); Parallel.ForEach(allRoundsList, dbRound => { int counter = 0; int successCounter = 0; foreach (var demoPathPoint in demoPathpointList) { ingamePathpoint roundedDemoPathPoint = new ingamePathpoint(demoPathPoint.roundID, (((int)Math.Round((int)demoPathPoint.X / 30.0)) * 30), (((int)Math.Round((int)demoPathPoint.Y / 30.0)) * 30), (((int)Math.Round((int)demoPathPoint.Z / 30.0)) * 30)); foreach (var dbPathPoint in allPathpointsList.Where(n => n.roundID != demoPathPoint.roundID && n.roundID == dbRound.roundID)) { ingamePathpoint roundedDbPathPoint = new ingamePathpoint(dbPathPoint.roundID, (((int)Math.Round((int)dbPathPoint.X / 30.0)) * 30), (((int)Math.Round((int)dbPathPoint.Y / 30.0)) * 30), (((int)Math.Round((int)dbPathPoint.Z / 30.0)) * 30)); counter++; double distance = Math.Sqrt(MyPow((roundedDbPathPoint.X - roundedDemoPathPoint.X), 2) + MyPow((roundedDbPathPoint.Y - roundedDemoPathPoint.Y), 2) + MyPow((roundedDbPathPoint.Z - roundedDemoPathPoint.Z), 2)); if (distance == 0) { successCounter++; } } } if (counter != 0) { //Calculate percentage of pathpoints from the DB which are close to the pathpoints from the demo float successPercentage = ((float)successCounter / (float)counter) * 100; if (successPercentage >= 1.7) { //Found a round which seems to be the same strat like the the round from the demo Console.WriteLine("Success round " + dbRound.roundID + ": " + successPercentage.ToString(".0###") + "%"); //Add the round from the database which seems similar to the list. demoRoundSimilarRounds.Add(dbRound); matchingRoundInDB = true; } } }); //If a match has been found if (matchingRoundInDB) { long existingStratID = -1; foreach (var similarRound in demoRoundSimilarRounds.Where(n => n.stratFound == true)) { foreach (var stratRound in existingStratRoundsList.Where(n => n.roundID == similarRound.roundID)) { Console.WriteLine("The matching round " + similarRound.roundID + " is already in the strat " + stratRound.stratID); existingStratID = stratRound.stratID; } } if (existingStratID != -1) { int newRoundCounter = 0; string addRoundToExistingStratQuery = "INSERT INTO `stratrounds` (stratID,roundID,roundType) VALUES "; foreach (var similarRound in demoRoundSimilarRounds.Where(n => n.stratFound == false)) { Console.WriteLine("Adding round " + similarRound.roundID + " to the existing strat " + existingStratID); newRoundCounter++; similarRound.stratFound = true; similarRound.stratID = existingStratID; existingStratRoundsList.Add(similarRound); foreach (var dbRound in allRoundsList.Where(n => n.roundID == similarRound.roundID)) { dbRound.stratFound = true; dbRound.stratID = existingStratID; } foreach (var matchRound in matchRoundsList.Where(n => n.roundID == similarRound.roundID)) { matchRound.stratFound = true; matchRound.stratID = existingStratID; } //Write stratFound into DB MySqlCommand setStratFoundForRound = new MySqlCommand("UPDATE `round` SET stratFound=1 WHERE id=" + similarRound.roundID, sqlConnection); setStratFoundForRound.ExecuteNonQuery(); addRoundToExistingStratQuery += "(" + existingStratID + "," + similarRound.roundID + ",'" + similarRound.roundType + "'),"; } if (newRoundCounter >= 1) { addRoundToExistingStratQuery = addRoundToExistingStratQuery.Remove(addRoundToExistingStratQuery.Length - 1); addRoundToExistingStratQuery += ";"; MySqlCommand addRoundToExistingStrat = new MySqlCommand(addRoundToExistingStratQuery, sqlConnection); addRoundToExistingStrat.ExecuteNonQuery(); Console.WriteLine("Added all rounds to the existing strat " + existingStratID); } } else if (existingStratID == -1) { //Create new strat in DB MySqlCommand createNewStratForRounds = new MySqlCommand("INSERT INTO `strats` (mapName,stratType) VALUES ('" + mapName + "','" + round.roundType + "')", sqlConnection); createNewStratForRounds.ExecuteNonQuery(); Console.WriteLine("Created new strat " + createNewStratForRounds.LastInsertedId); string addRoundToNewStratQuery = "INSERT INTO `stratrounds` (stratID,roundID,roundType) VALUES "; string updateRoundsQuery = "UPDATE `round` SET stratFound=1 WHERE"; foreach (var similarRound in demoRoundSimilarRounds) { Console.WriteLine("Adding round " + similarRound.roundID + " to the new strat " + createNewStratForRounds.LastInsertedId); similarRound.stratFound = true; similarRound.stratID = createNewStratForRounds.LastInsertedId; existingStratRoundsList.Add(similarRound); foreach (var dbRound in allRoundsList.Where(n => n.roundID == similarRound.roundID)) { dbRound.stratFound = true; dbRound.stratID = createNewStratForRounds.LastInsertedId; } foreach (var matchRound in matchRoundsList.Where(n => n.roundID == similarRound.roundID)) { matchRound.stratFound = true; matchRound.stratID = createNewStratForRounds.LastInsertedId; } addRoundToNewStratQuery += "(" + createNewStratForRounds.LastInsertedId + "," + similarRound.roundID + ",'" + similarRound.roundType + "'),"; updateRoundsQuery += " id=" + similarRound.roundID + " OR"; } addRoundToNewStratQuery = addRoundToNewStratQuery.Remove(addRoundToNewStratQuery.Length - 1); addRoundToNewStratQuery += ";"; MySqlCommand insertRoundToStrat = new MySqlCommand(addRoundToNewStratQuery, sqlConnection); insertRoundToStrat.ExecuteNonQuery(); updateRoundsQuery = updateRoundsQuery.Remove(updateRoundsQuery.Length - 3); updateRoundsQuery += ";"; MySqlCommand updateRounds = new MySqlCommand(updateRoundsQuery, sqlConnection); updateRounds.ExecuteNonQuery(); Console.WriteLine("Added all rounds to the new strat " + createNewStratForRounds.LastInsertedId); } } } }