private static int findMatchStartRound(FileStream fileStream) { int result = 0; using (var parser = new DemoParser(fileStream)) { parser.ParseHeader(); int roundNumber = 0; parser.MatchStarted += (sender, e) => { if (roundNumber <= 16) { roundNumber = 0; result++; } }; parser.RoundEnd += (object sender, RoundEndedEventArgs e) => { roundNumber++; }; parser.ParseToEnd(); } Console.WriteLine("Match starts at round number " + result); return result; }
public Match Parse(FileInfo fileInfo) { _Match.DemoName = fileInfo.Name; using (var stream = new FileStream(fileInfo.FullName, FileMode.Open)) { var parser = new DemoInfo.DemoParser(stream); parser.MatchStarted += OnMatchStarted; parser.PlayerKilled += OnPlayerKilled; parser.PlayerTeam += OnPlayerTeam; parser.RoundEnd += OnRoundEnd; parser.ParseHeader(); _Match.Map = parser.Map; parser.ParseToEnd(); RemoveSpectators(); } return(_Match); }
public static void GenerateFrags(DemoParser parser) { parser.ParseHeader (); // Make a print on round-start so you can see the actual frags per round. parser.RoundStart += (sender, e) => Console.WriteLine ("New Round, Current Score: T {0} : {1} CT", parser.TScore, parser.CTScore); parser.PlayerKilled += (sender, e) => { if(e.Killer == null) { // The player has murdered himself (falling / own nade / ...) Console.WriteLine("<World><0><None>"); } else { Console.Write("<{0}><{1}><{2}>", e.Killer.Name, e.Killer.SteamID, ShortTeam(e.Killer.Team)); } if(e.Assister == null) { // nothing } else { Console.Write(" + <{0}><{1}><{2}>", e.Assister.Name, e.Assister.SteamID, ShortTeam(e.Assister.Team)); } Console.Write(" [{0}]", e.Weapon.Weapon); if(e.Headshot) { Console.Write("[HS]"); } if(e.PenetratedObjects > 0) { Console.Write("[Wall]"); } Console.Write(" "); Console.Write("<{0}><{1}><{2}>", e.DeathPerson.Name, e.DeathPerson.SteamID, ShortTeam(e.DeathPerson.Team)); Console.WriteLine(); }; parser.ParseToEnd (); }
public string GetMapName() { if (string.IsNullOrEmpty(mapName)) { if (availableOffline) { using (FileStream fs = new FileStream(GetMatchFilePath(), FileMode.Open, FileAccess.Read)) using (DemoInfo.DemoParser dp = new DemoInfo.DemoParser(fs)) { dp.ParseHeader(); mapName = dp.Map; } } else { var lastRound = GetLastRoundStats(); if (lastRound != null) { mapName = ((GameType)lastRound.reservation.game_type).ToString(); } } } return(mapName); }
static List<String> parseDemo(string fileName) { List<String> playerIDs = new List<String>(); using (var fileStream = File.OpenRead(fileName)) { using (var parser = new DemoParser(fileStream)) { //hasMatchStarted framkallar viðvörun um að mögulegt sé //að það verði aldrei notað, með þessu slökkvum við á //þessari viðvörun. #pragma warning disable 0219 bool hasMatchStarted = false; #pragma warning restore 0219 parser.ParseHeader(); parser.MatchStarted += (sender, e) => { hasMatchStarted = true; foreach (var player in parser.PlayingParticipants) { string test = player.SteamID.ToString(); playerIDs.Add(test); } }; //Þetta fyrir neðan er algjörlega ógeðslegt, ég veit. //En er samt nauðsynlegt svo að playerIDs verði fyllt. for (int i = 0; i < 3000; i++) { parser.ParseNextTick(); } } } return playerIDs; }
public static void Main(string[] args) { using (var input = File.OpenRead(args[0])) { var parser = new DemoParser(input); parser.ParseHeader(); parser.WeaponFired += (sender, e) => { Console.WriteLine("Weapon_fire"); Console.WriteLine("{"); Console.WriteLine("velx=" + e.Shooter.Velocity.X); Console.WriteLine("vely=" + e.Shooter.Velocity.Y); Console.WriteLine("weapon=" + e.Weapon.Weapon); Console.WriteLine("playername=" + e.Shooter.Name); Console.WriteLine("playersteamid=" + e.Shooter.SteamID); Console.WriteLine("}"); }; parser.ParseToEnd(); Console.WriteLine("End"); } }
public static Demo ParseDemoHeader(string pathDemoFile) { DemoParser parser = new DemoParser(File.OpenRead(pathDemoFile)); DateTime dateFile = File.GetCreationTime(pathDemoFile); string dateAsString = dateFile.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); Demo demo = new Demo { Name = Path.GetFileName(pathDemoFile), Path = pathDemoFile, Date = dateAsString }; try { parser.ParseHeader(); } catch (InvalidDataException) { // Silently ignore no CSGO demos return null; } DemoHeader header = parser.Header; DateTime dateTime = File.GetCreationTime(pathDemoFile); int seconds = (int)(dateTime.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; demo.Id = header.MapName.Replace("/", "") + seconds + header.SignonLength + header.PlaybackFrames; demo.ClientName = header.ClientName; demo.Hostname = header.ServerName; if (header.PlaybackTicks != 0 && header.PlaybackTime != 0) { demo.ServerTickrate = header.PlaybackTicks / header.PlaybackTime; } if (header.PlaybackFrames != 0 && header.PlaybackTime != 0) { demo.Tickrate = (int)Math.Round((double)header.PlaybackFrames / header.PlaybackTime); } demo.Duration = header.PlaybackTime; demo.MapName = header.MapName; demo.Source = DetermineDemoSource(demo, parser, header); return demo; }
public static void CreateMatchInfo(MatchInfo match, out CDataGCCStrike15_v2_MatchInfo matchInfo, out ExtraMatchStats extraStats, System.Threading.CancellationToken cancelToken) { matchInfo = null; extraStats = null; if (match.availableOffline) { CDataGCCStrike15_v2_MatchInfo tempMatchInfo = new CDataGCCStrike15_v2_MatchInfo(); ExtraMatchStats tempExtraStats = new ExtraMatchStats(); #region File Name Data ulong reservationId = 0; bool reservationIdSpecified = false; string[] fileNameSplits = match.fileName.Split('_'); if (match.fileName.IndexOf("match730_") == 0 && fileNameSplits.Length == 4) { try { reservationId = Convert.ToUInt64(fileNameSplits[1]); reservationIdSpecified = true; WatchableMatchInfo watchablematchinfo = new WatchableMatchInfo(); watchablematchinfo.tv_port = Convert.ToUInt32(fileNameSplits[2]); watchablematchinfo.server_ip = Convert.ToUInt32(fileNameSplits[3]); tempMatchInfo.watchablematchinfo = watchablematchinfo; } catch (Exception) { } } #endregion using (FileStream fileStream = File.Open(match.GetMatchFilePath(), FileMode.Open, FileAccess.Read)) using (DemoInfo.DemoParser dp = new DemoInfo.DemoParser(fileStream)) { #region Data Analysis #region Match Info Variables int matchStartTick = 0; CMsgGCCStrike15_v2_MatchmakingServerRoundStats currentRoundStats = null; Dictionary <uint, int> totalAssists = new Dictionary <uint, int>(); Dictionary <uint, int> totalDeaths = new Dictionary <uint, int>(); Dictionary <uint, int> totalEnemyHeadshots = new Dictionary <uint, int>(); Dictionary <uint, int> totalEnemyKills = new Dictionary <uint, int>(); Dictionary <uint, int> totalKills = new Dictionary <uint, int>(); Dictionary <uint, int> totalMvps = new Dictionary <uint, int>(); Dictionary <uint, int> totalScores = new Dictionary <uint, int>(); List <List <DemoInfo.Player> > playerTeams = new List <List <DemoInfo.Player> >(); //List<uint> accountIds = new List<uint>(); int[] totalTeamScore = new int[2]; #endregion Action <uint> AddPlayerToCurrentRound = (accountId) => { currentRoundStats.reservation.account_ids.Add(accountId); currentRoundStats.assists.Add(0); currentRoundStats.deaths.Add(0); currentRoundStats.enemy_headshots.Add(0); currentRoundStats.enemy_kills.Add(0); currentRoundStats.kills.Add(0); currentRoundStats.mvps.Add(0); currentRoundStats.scores.Add(0); }; Func <uint, int> GetPlayerIndex = (accountId) => { int playerIndex = currentRoundStats.reservation.account_ids.IndexOf(accountId); if (playerIndex < 0) { AddPlayerToCurrentRound(accountId); playerIndex = currentRoundStats.reservation.account_ids.Count - 1; } return(playerIndex); }; EventHandler <DemoInfo.MatchStartedEventArgs> matchStartedHandler = (obj, msea) => { matchStartTick = dp.CurrentTick; foreach (var player in dp.PlayerInformations) { if (player != null && player.SteamID > 0) { uint accountId = new SteamKit2.SteamID((ulong)player.SteamID).AccountID; #region Extra Stats Data tempExtraStats.accountIds.Add(accountId); tempExtraStats.playerNames.Add(player.Name); #endregion var teamToAddTo = playerTeams.Find((team) => team.Exists((teamPlayer) => teamPlayer.Team == player.Team)); if (teamToAddTo == null) { teamToAddTo = new List <DemoInfo.Player>(); playerTeams.Add(teamToAddTo); } teamToAddTo.Add(player); } } }; EventHandler <DemoInfo.RoundStartedEventArgs> roundStartedHandler = (obj, rsea) => { #region Match Info Data currentRoundStats = new CMsgGCCStrike15_v2_MatchmakingServerRoundStats(); tempMatchInfo.roundstatsall.Add(currentRoundStats); currentRoundStats.team_scores.AddRange(totalTeamScore); CMsgGCCStrike15_v2_MatchmakingGC2ServerReserve reservation = new CMsgGCCStrike15_v2_MatchmakingGC2ServerReserve(); currentRoundStats.reservation = reservation; foreach (var player in dp.PlayerInformations) { if (player != null && player.SteamID > 0) { AddPlayerToCurrentRound(new SteamKit2.SteamID((ulong)player.SteamID).AccountID); } } #endregion #region Extra Stats Data tempExtraStats.roundStartTicks.Add(dp.CurrentTick); #endregion }; EventHandler <DemoInfo.PlayerKilledEventArgs> playerKilledHandler = (obj, pkea) => { if (currentRoundStats != null) { if (pkea.Victim?.SteamID > 0) { uint victimAccountId = new SteamKit2.SteamID((ulong)pkea.Victim.SteamID).AccountID; int victimIndex = GetPlayerIndex(victimAccountId); UnityEngine.Debug.Assert(victimIndex > -1, "How do we not have this player yet?? @tick " + dp.CurrentTick + " index: " + victimIndex + " accountId: " + victimAccountId + " name " + pkea.Victim.Name); if (victimIndex > -1) { if (!totalDeaths.ContainsKey(victimAccountId)) { totalDeaths[victimAccountId] = 0; } currentRoundStats.deaths[victimIndex] = ++totalDeaths[victimAccountId]; } } if (pkea.Killer?.SteamID > 0) { uint killerAccountId = new SteamKit2.SteamID((ulong)pkea.Killer.SteamID).AccountID; int killerIndex = GetPlayerIndex(killerAccountId); UnityEngine.Debug.Assert(killerIndex > -1, "How do we not have this player yet?? @tick " + dp.CurrentTick + " index: " + killerIndex + " accountId: " + killerAccountId + " name " + pkea.Killer.Name); if (killerIndex > -1) { if (!totalKills.ContainsKey(killerAccountId)) { totalKills[killerAccountId] = 0; } currentRoundStats.kills[killerIndex] = ++totalKills[killerAccountId]; bool enemyKill = pkea.Victim.TeamID != pkea.Killer.TeamID; if (!totalEnemyKills.ContainsKey(killerAccountId)) { totalEnemyKills[killerAccountId] = 0; } currentRoundStats.enemy_kills[killerIndex] += enemyKill ? ++totalEnemyKills[killerAccountId] : 0; if (!totalEnemyHeadshots.ContainsKey(killerAccountId)) { totalEnemyHeadshots[killerAccountId] = 0; } currentRoundStats.enemy_headshots[killerIndex] += enemyKill && pkea.Headshot ? ++totalEnemyHeadshots[killerAccountId] : 0; } } if (pkea.Assister?.SteamID > 0) { uint assisterAccountId = new SteamKit2.SteamID((ulong)pkea.Assister.SteamID).AccountID; int assisterIndex = GetPlayerIndex(assisterAccountId); UnityEngine.Debug.Assert(assisterIndex > -1, "How do we not have this player yet?? @tick " + dp.CurrentTick + " index: " + assisterIndex + " accountId: " + assisterAccountId + " name " + pkea.Assister.Name); if (assisterIndex > -1) { if (!totalAssists.ContainsKey(assisterAccountId)) { totalAssists[assisterAccountId] = 0; } currentRoundStats.assists[assisterIndex] = ++totalAssists[assisterAccountId]; } } } }; EventHandler <DemoInfo.RoundMVPEventArgs> roundMVPHandler = (obj, rmea) => { if (rmea.Player?.SteamID > 0) { uint playerAccountId = new SteamKit2.SteamID((ulong)rmea.Player.SteamID).AccountID; if (!totalMvps.ContainsKey(playerAccountId)) { totalMvps[playerAccountId] = 0; } int playerIndex = GetPlayerIndex(playerAccountId); UnityEngine.Debug.Assert(playerIndex > -1, "How do we not have this player yet?? @tick " + dp.CurrentTick + " index: " + playerIndex + " accountId: " + playerAccountId + " name " + rmea.Player.Name); if (playerIndex > -1 && playerIndex < currentRoundStats.mvps.Count) { currentRoundStats.mvps[playerIndex] = ++totalMvps[playerAccountId]; } } }; EventHandler <DemoInfo.RoundEndedEventArgs> roundEndedHandler = (obj, reea) => { #region Match Info Data Debug.Assert(currentRoundStats != null, "How can you end a round without starting it!? @tick " + dp.CurrentTick); if (currentRoundStats != null) { if (reea.Winner != DemoInfo.Team.Spectate) { int teamIndex = playerTeams.FindIndex((team) => team[0].Team == reea.Winner); if (teamIndex > -1 && teamIndex < totalTeamScore.Length) { currentRoundStats.team_scores[teamIndex] = ++totalTeamScore[teamIndex]; } } currentRoundStats.match_duration = (int)((dp.CurrentTick - matchStartTick) * dp.TickTime); foreach (var player in dp.PlayerInformations) { if (player != null && player.SteamID > 0) { uint playerAccountId = new SteamKit2.SteamID((ulong)player.SteamID).AccountID; int playerIndex = GetPlayerIndex(playerAccountId); Debug.Assert(playerIndex > -1, "How do we not have this player yet?? @tick " + dp.CurrentTick + " index: " + playerIndex + " accountId: " + playerAccountId + " name " + player.Name); currentRoundStats.scores[playerIndex] = player.AdditionaInformations.Score; } } } #endregion #region Extra Stats Data tempExtraStats.roundEndTicks.Add(dp.CurrentTick); tempExtraStats.roundWinner.Add((int)reea.Winner); #endregion }; #endregion dp.MatchStarted += matchStartedHandler; dp.RoundStart += roundStartedHandler; dp.PlayerKilled += playerKilledHandler; dp.RoundMVP += roundMVPHandler; dp.RoundEnd += roundEndedHandler; dp.ParseHeader(); while (dp.ParseNextTick() && !cancelToken.IsCancellationRequested) { match.infoProgress = dp.CurrentTick / (float)dp.Header.PlaybackFrames; } dp.MatchStarted -= matchStartedHandler; dp.RoundStart -= roundStartedHandler; dp.PlayerKilled -= playerKilledHandler; dp.RoundMVP -= roundMVPHandler; dp.RoundEnd -= roundEndedHandler; #region Last round stats if (reservationIdSpecified) { currentRoundStats.reservationid = reservationId; } currentRoundStats.reservation.game_type = (uint)(GameType)Enum.Parse(typeof(GameType), dp.Map); if (totalTeamScore[0] != totalTeamScore[1]) { var winningTeam = (DemoInfo.Team)currentRoundStats.round_result; currentRoundStats.match_result = (winningTeam == DemoInfo.Team.Terrorist ? 1 : 2); //1 is CT, 2 is T. I do the switching because of team switching at half } else { currentRoundStats.match_result = 0; } #endregion } if (cancelToken.IsCancellationRequested) { tempMatchInfo = null; tempExtraStats = null; } matchInfo = tempMatchInfo; extraStats = tempExtraStats; } }
static void Main(string[] args) { int demosParsed = 0; int errors = 0; String[] directories = Directory.GetDirectories(@"D:\demos\extracted\"); foreach (String directory in directories) { if(directory.Length != 28 || !directory.StartsWith(@"D:\demos\extracted\demo190")) { continue; } foreach(String file in Directory.GetFiles(directory)) { DemoParser parser = new DemoParser(File.OpenRead(file)); try { parser.ParseHeader(); Program program = new Program(parser); parser.ParseToEnd(); demosParsed++; } catch (Exception e) { parser = null; errors++; break; } } if(demosParsed > 100) { break; } } Console.WriteLine("Errors: " + errors); Console.WriteLine("Demos Parsed: " + demosParsed); Console.Read(); }
public static void Analyze(string demoPath) { using (var fileStream = File.OpenRead(demoPath)) { Console.WriteLine("Parsing demo " + demoPath); using (var parser = new DemoParser(fileStream)) { parser.ParseHeader(); string map = parser.Map; Console.WriteLine("Map: " + map); bool hasMatchStarted = false; bool firstBlood = true; int round = 0; Dictionary<Player, int> killsThisRound = new Dictionary<Player, int>(); Dictionary<Player, int> entryFrags = new Dictionary<Player, int>(); Dictionary<Player, int> entryFragAttempts = new Dictionary<Player, int>(); Dictionary<Player, Rating> ratings = new Dictionary<Player, Rating>(); parser.MatchStarted += (sender, e) => { hasMatchStarted = true; }; parser.RoundStart += (sender, e) => { if (!hasMatchStarted) return; firstBlood = true; round++; killsThisRound.Clear(); foreach (var player in parser.PlayingParticipants) { if (!ratings.ContainsKey(player)) { ratings.Add(player, new Rating()); } ratings[player].roundsPlayed++; } }; parser.RoundEnd += (sender, e) => { if (!hasMatchStarted) return; //firstBlood = true; //round++; //killsThisRound.Clear(); foreach (var player in killsThisRound.Keys) { if (!ratings.ContainsKey(player)) { ratings.Add(player,new Rating()); } ratings[player].multipleKillsDictionary[killsThisRound[player]]++; } }; parser.PlayerKilled += (object sender, PlayerKilledEventArgs e) => { if (!hasMatchStarted) return; //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; if(e.Killer.Team == e.Victim.Team) { //killsThisRound[e.Killer]--; } else { killsThisRound[e.Killer]++; } //Remember how many kills each player made this rounds if (!ratings.ContainsKey(e.Victim)) { ratings.Add(e.Victim, new Rating()); } ratings[e.Victim].deaths++; if (firstBlood) { if (!entryFrags.ContainsKey(e.Killer)) entryFrags[e.Killer] = 0; entryFrags[e.Killer]++; if (!entryFragAttempts.ContainsKey(e.Killer)) entryFragAttempts[e.Killer] = 0; entryFragAttempts[e.Killer]++; if (!entryFragAttempts.ContainsKey(e.Victim)) entryFragAttempts[e.Victim] = 0; entryFragAttempts[e.Victim]++; firstBlood = false; } } }; parser.ParseToEnd(); //foreach(var player in killsThisRound.Keys) //{ // if (!entryFragAttempts.ContainsKey(player)) // { // entryFragAttempts[player] = 0; // } // if (!entryFrags.ContainsKey(player)) // { // entryFrags[player] = 0; // } // Console.WriteLine(player.Name + "(" + player.Team + ")" + " entry frags: " + entryFrags[player] + "/" + entryFragAttempts[player]); //} foreach (var player in ratings.Keys) { Console.WriteLine(player.Name + "(" + player.Team + ")" + " rating: " + ratings[player].getRating()); } } } }
public static Demo ParseDemoHeader(string pathDemoFile) { DemoParser parser = new DemoParser(File.OpenRead(pathDemoFile)); DateTime dateFile = File.GetCreationTime(pathDemoFile); Demo demo = new Demo { Name = Path.GetFileName(pathDemoFile), Path = pathDemoFile, Date = dateFile }; try { parser.ParseHeader(); } catch (Exception) { // Silently ignore no CSGO demos or unreadable file return null; } DemoHeader header = parser.Header; DateTime dateTime = File.GetCreationTime(pathDemoFile); int seconds = (int)(dateTime.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; demo.Id = header.MapName.Replace("/", "") + "_" + seconds + header.SignonLength + header.PlaybackFrames; demo.ClientName = header.ClientName; demo.Hostname = header.ServerName; if (header.PlaybackTicks != 0 && header.PlaybackTime != 0) { demo.ServerTickrate = header.PlaybackTicks / header.PlaybackTime; } if (header.PlaybackFrames != 0 && header.PlaybackTime != 0) { demo.Tickrate = (int)Math.Round((double)header.PlaybackFrames / header.PlaybackTime); } demo.Duration = header.PlaybackTime; demo.MapName = header.MapName; demo.Source = DetermineDemoSource(demo, header); // Read .info file to get the real match date string infoFilePath = demo.Path + ".info"; if (File.Exists(infoFilePath)) { using (FileStream file = File.OpenRead(infoFilePath)) { try { CDataGCCStrike15_v2_MatchInfo infoMsg = Serializer.Deserialize<CDataGCCStrike15_v2_MatchInfo>(file); DateTime unixTime = new DateTime(1970, 1, 1, 0, 0, 0, 0); demo.Date = unixTime.AddSeconds(infoMsg.matchtime); } catch (Exception) { // silently ignore old .info files // Maybe add the old message to handle it? } } } return demo; }
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] == "--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 (); } } } }
public static void Main(string[] args) { using (var input = File.OpenRead(args[0])) { var parser = new DemoParser(input); parser.ParseHeader (); #if DEBUG Dictionary<Player, int> failures = new Dictionary<Player, int>(); parser.TickDone += (sender, e) => { //Problem: The HP coming from CCSPlayerEvent are sent 1-4 ticks later //I guess this is because the think()-method of the CCSPlayerResource isn't called //that often. Haven't checked though. foreach(var p in parser.PlayingParticipants) { //Make sure the array is never empty ;) failures[p] = failures.ContainsKey(p) ? failures[p] : 0; if(p.HP == p.AdditionaInformations.ScoreboardHP) failures[p] = 0; else failures[p]++; //omg this is hacky. //Okay, if it's wrong 2 seconds in a row, something's off //Since there should be a tick where it's right, right? //And if there's something off (e.g. two players are swapped) //there will be 2 seconds of ticks where it's wrong //So no problem here :) Debug.Assert( failures[p] < parser.TickRate * 2, string.Format( "The player-HP({0}) of {2} (Clan: {3}) and it's Scoreboard HP ({1}) didn't match for {4} ticks. ", p.HP, p.AdditionaInformations.ScoreboardHP, p.Name, p.AdditionaInformations.Clantag, parser.TickRate * 2 ) ); } }; if (args.Length >= 2) { // progress reporting requested using (var progressFile = File.OpenWrite(args[1])) using (var progressWriter = new StreamWriter(progressFile) { AutoFlush = false }) { int lastPercentage = -1; while (parser.ParseNextTick()) { var newProgress = (int)(parser.ParsingProgess * 100); if (newProgress != lastPercentage) { progressWriter.Write(lastPercentage = newProgress); progressWriter.Flush(); } } } return; } #endif parser.ParseToEnd(); } }
static void parseDemo(FileStream fileStream, string fileName, int matchStartEventNumber) { using (var parser = new DemoParser(fileStream)) { string demoName = Path.GetFileName(fileName); // 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 output_roundResults = fileName + "_roundResults.csv"; string output_killEvents = fileName + "_killEvents.csv"; string output_matchResults = fileName + "_matchResults.csv"; /*string output_smokeNades = fileName + "_smokeNades.csv"; string output_fireNades = fileName + "_fireNades.csv"; string output_flashNades = fileName + "_flashNades.csv"; string output_decoyNades = fileName + "_decoyNades.csv"; string output_heNades = fileName + "_heNades.csv";*/ // and open it. var outputStream_roundResults = new StreamWriter(output_roundResults, false, Encoding.UTF8); var outputStream_killEvents = new StreamWriter(output_killEvents, false, Encoding.UTF8); var outputStream_matchResults = new StreamWriter(output_matchResults, false, Encoding.UTF8); /*var outputStream_smokeNades = new StreamWriter(output_smokeNades, false, Encoding.UTF8); var outputStream_fireNades = new StreamWriter(output_fireNades, false, Encoding.UTF8); var outputStream_flashNades = new StreamWriter(output_flashNades, false, Encoding.UTF8); var outputStream_decoyNades = new StreamWriter(output_decoyNades, false, Encoding.UTF8); var outputStream_heNades = new StreamWriter(output_heNades, false, Encoding.UTF8);*/ //And write a header so you know what is what in the resulting file //outputStream.WriteLine(GenerateCSVHeader()); int ctStartroundMoney = 0, tStartroundMoney = 0, ctEquipValue = 0, tEquipValue = 0, ctSaveAmount = 0, tSaveAmount = 0; int numberOfEventsMatchStarted = 0; int roundNumber = 0; List<String> ctWeapons = new List<String>(); List<String> tWeapons = new List<String>(); float ctWay = 0, tWay = 0; bool teamsSwap = false; int CTScore = 0; int TScore = 0; int defuses = 0; int plants = 0; string firstRoundWinner = ""; string lastRoundWinner = ""; Dictionary<Player, int> killsThisRound = new Dictionary<Player, int>(); // 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) => { numberOfEventsMatchStarted++; }; parser.PlayerKilled += (object sender, PlayerKilledEventArgs e) => { if (numberOfEventsMatchStarted!=matchStartEventNumber) return; //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 try { killsThisRound[e.Killer]++; string killerSteamID = e.Killer.SteamID.ToString(); Vector killerPosition = e.Killer.Position; string killingWeapon = e.Weapon.Weapon.ToString(); bool isHeadshot = e.Headshot; int penetratedObjects = e.PenetratedObjects; string victimSteamID = e.Victim.SteamID.ToString(); Vector victimPosition = e.Victim.Position; PrintKillEvent(parser, outputStream_killEvents, demoName, map, killerSteamID, killerPosition, killingWeapon, isHeadshot, penetratedObjects, victimSteamID, victimPosition); } catch (Exception) { return; } } }; parser.RoundStart += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) 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; ctWeapons = new List<String>(); tWeapons = new List<String>(); killsThisRound.Clear(); }; parser.FreezetimeEnded += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) 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); List<Player> ctPLayers = parser.Participants.Where(a => a.Team == Team.CounterTerrorist).ToList(); tEquipValue = parser.Participants.Where(a => a.Team == Team.Terrorist).Sum(a => a.CurrentEquipmentValue); List<Player> tPLayers = parser.Participants.Where(a => a.Team == Team.Terrorist).ToList(); foreach (Player p in ctPLayers) { foreach (Equipment e2 in p.Weapons) ctWeapons.Add(e2.Weapon.ToString()); ctWeapons.Add("END"); } foreach (Player p in tPLayers) { foreach (Equipment e2 in p.Weapons) tWeapons.Add(e2.Weapon.ToString()); tWeapons.Add("END"); } }; parser.BombPlanted += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) return; plants++; }; /*parser.SmokeNadeStarted += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) return; Vector nadePosition = e.Position; PrintSmokeEvent(parser, outputStream_smokeNades, demoName, map, nadePosition); }; parser.FireNadeStarted += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) return; Vector nadePosition = e.Position; PrintFireNadeEvent(parser, outputStream_fireNades, demoName, map, nadePosition); }; parser.FlashNadeExploded += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) return; Vector nadePosition = e.Position; PrintFlashNadeEvent(parser, outputStream_flashNades, demoName, map, nadePosition); }; parser.DecoyNadeStarted += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) return; Vector nadePosition = e.Position; PrintDecoyNadeEvent(parser, outputStream_decoyNades, demoName, map, nadePosition); }; parser.ExplosiveNadeExploded += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) return; Vector nadePosition = e.Position; PrintHeNadeExplosionEvent(parser, outputStream_heNades, demoName, map, nadePosition); };*/ parser.BombDefused += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) return; defuses++; }; parser.LastRoundHalf += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) return; teamsSwap = true; }; parser.TickDone += (sender, e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) 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 += (object sender, RoundEndedEventArgs e) => { if (numberOfEventsMatchStarted != matchStartEventNumber) { return; } roundNumber++; // 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 :) string winningFaction = e.Winner.ToString(); if (roundNumber == 1) { if (winningFaction.Equals("CounterTerrorist")) { firstRoundWinner = parser.CTClanName; } if (winningFaction.Equals("Terrorist")) { firstRoundWinner = parser.TClanName; } } if (winningFaction.Equals("CounterTerrorist")) { CTScore++; lastRoundWinner = parser.CTClanName; } if (winningFaction.Equals("Terrorist")) { lastRoundWinner = parser.TClanName; TScore++; } PrintRoundResults(parser, outputStream_roundResults, demoName, map, CTScore, TScore, ctStartroundMoney, tStartroundMoney, ctEquipValue, tEquipValue, ctSaveAmount, tSaveAmount, ctWay, tWay, defuses, plants, killsThisRound, winningFaction, ctWeapons, tWeapons); if (teamsSwap) { teamsSwap = false; int tempCTscore = CTScore; int tempTscore = TScore; CTScore = tempTscore; TScore = tempCTscore; } }; //Now let's parse the demo! parser.ParseToEnd(); PrintMatchResults(parser, outputStream_matchResults, demoName, map, parser.CurrentTime, parser.Header.ClientName, parser.Header.NetworkProtocol, parser.Header.Protocol, parser.Header.SignonLength, parser.Header.PlaybackTime, firstRoundWinner, lastRoundWinner); outputStream_roundResults.Close(); outputStream_killEvents.Close(); outputStream_matchResults.Close(); /* outputStream_smokeNades.Close(); outputStream_fireNades.Close(); outputStream_flashNades.Close(); outputStream_decoyNades.Close(); outputStream_heNades.Close();*/ } }
public static void GenerateScoreboards(DemoParser parser) { int i = 0; parser.ParseHeader (); Console.WriteLine ("map: " + parser.Map); int roundEndedCount = 0; parser.RoundEnd += (object sender, RoundEndedEventArgs e) => { // The reason I'm doing this after tick_done is that // entity-updates only come in the same tick as round_end // comes, meaning the score-update might not be transmitted yet // which would be sad - we always want the current score! // so we wait 1 second. roundEndedCount = 1; }; parser.TickDone += (object sender, TickDoneEventArgs e) => { if(roundEndedCount == 0) return; roundEndedCount ++; // Wait twice the tickrate of the demo (~2 seconds) to make sure the // screen has been updated. I *LOVE* demo files :) if(roundEndedCount < parser.TickRate * 2) { return; } roundEndedCount = 0; Console.WriteLine ("------------------------------------------------------------"); Console.WriteLine ("Round {0}, CT: {1}, T: {2}", ++i, parser.CTScore, parser.TScore); Console.WriteLine("Ts\t" + parser.TClanName); Console.WriteLine("Tag\tName\tSteamID\tKills\tDeaths\tAssists\tScore\t"); foreach(var player in parser.PlayingParticipants.Where(a => a.Team == Team.Terrorist)) Console.WriteLine ( "{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", player.AdditionaInformations.Clantag, player.Name, player.SteamID, player.AdditionaInformations.Kills, player.AdditionaInformations.Deaths, player.AdditionaInformations.Assists, player.AdditionaInformations.Score ); Console.WriteLine("CTs\t" + parser.CTClanName); Console.WriteLine("Tag\tName\tSteamID\tKills\tDeaths\tAssists\tScore\t"); foreach(var player in parser.PlayingParticipants.Where(a => a.Team == Team.CounterTerrorist)) Console.WriteLine ( "{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", player.AdditionaInformations.Clantag, player.Name, player.SteamID, player.AdditionaInformations.Kills, player.AdditionaInformations.Deaths, player.AdditionaInformations.Assists, player.AdditionaInformations.Score ); Console.WriteLine (); }; parser.ParseToEnd (); }
static void Main(string[] args) { if (args.Length == 0 || args[0] == "-help") { DisplayHelp(); return; } /* * One argument per demofile, thus we iterate over all of them * > generator.exe demo1.dem demo2.dem */ foreach (var fileName in args) { /* * Initializing DemoParser with fileStream needs to be done * with "using". Orig.Author warned about memoryleaks if we * do not dispose fileStream / DemoParser. */ using (var fileStream = File.OpenRead(fileName)) { Console.WriteLine("Beginning parsing of " +fileName); using (var parser = new DemoParser(fileStream)){ /* * After initialization, always parse demoheader * Create outPutFile&Stream * Create CSVHeader for outPutFile */ parser.ParseHeader(); DateTime timenow = DateTime.Now; string map = parser.Map; string outPutFileName = timenow.ToString("d") + "." + map + ".csv"; var outPutStream = new StreamWriter(outPutFileName); outPutStream.WriteLine(GenerateCSVHeader()); /* * Let's get srated on analyzing demo * According to DemoInfo author, CSGO * is mainly based on events. Therefore * we use binding and event driven programing * to do our bidding. */ bool hasMatchStarted = false; List<Player> ingame = new List<Player>(); Dictionary<Player, List<double>> dicPlayerFiredVelocity = new Dictionary<Player, List<double>>(); parser.MatchStarted += (sender, e) => { hasMatchStarted = true; ingame.AddRange(parser.PlayingParticipants); }; parser.RoundStart += (sender, e) => { if (!hasMatchStarted) { return; } dicPlayerFiredVelocity.Clear(); }; /* * Event WeaponFired has 2 parameters, * Shooter(Player) & Weapon(Weapon) */ parser.WeaponFired += (sender, e) => { if (hasMatchStarted) { if (dicPlayerFiredVelocity.ContainsKey(e.Shooter)) { dicPlayerFiredVelocity[e.Shooter].Add(e.Shooter.Velocity.Absolute); } else { dicPlayerFiredVelocity.Add(e.Shooter, new List<double> { e.Shooter.Velocity.Absolute }); } } }; /* * On roundEnd, print information to * outputstream */ parser.RoundEnd += (sender, e) => { if (!hasMatchStarted) { return; } OutPutStreamRoundResults(parser, outPutStream, dicPlayerFiredVelocity); }; /* * Call this after binding events * Parses demofile to the end. * Remember to close StreamWriter. */ parser.ParseToEnd(); outPutStream.Close(); } } } }
public static void DoStuff(string[] args) { foreach (var fileName in args) { using (var fileStream = File.OpenRead (fileName)) { Console.WriteLine ("Parsing demo " + fileName); using (var parser = new DemoParser (fileStream)) { parser.ParseHeader (); string map = parser.Map, outputFileName = fileName + "." + map + ".csv"; var outputStream = new StreamWriter (outputFileName); 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> (); parser.MatchStarted += (sender, e) => { hasMatchStarted = true; }; parser.PlayerKilled += (object sender, PlayerKilledEventArgs e) => { if(e.Killer != null) { if(!killsThisRound.ContainsKey(e.Killer)) killsThisRound[e.Killer] = 0; killsThisRound[e.Killer]++; } }; parser.RoundStart += (sender, e) => { if(!hasMatchStarted) return; 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); 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); ctWay = 0; tWay = 0; plants = 0; defuses = 0; killsThisRound.Clear(); }; parser.FreezetimeEnded += (sender, e) => { if(!hasMatchStarted) return; 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; foreach(var player in parser.PlayingParticipants) { float currentWay = (float)(player.Velocity.Absolute * parser.TickTime); if(player.Team == Team.CounterTerrorist) ctWay += currentWay; else if(player.Team == Team.Terrorist) tWay += currentWay; } }; parser.RoundEnd += (sender, e) => { if(!hasMatchStarted) return; PrintRoundResults (parser, outputStream, ctStartroundMoney, tStartroundMoney, ctEquipValue, tEquipValue, ctSaveAmount, tSaveAmount, ctWay, tWay, defuses, plants, killsThisRound); }; parser.ParseToEnd (); PrintRoundResults (parser, outputStream, ctStartroundMoney, tStartroundMoney, ctEquipValue, tEquipValue, ctSaveAmount, tSaveAmount, ctWay, tWay, defuses, plants, killsThisRound); outputStream.Close (); } } } }