diff --git a/Heroes.ReplayParser.ConsoleApplication/Program.cs b/Heroes.ReplayParser.ConsoleApplication/Program.cs index fe937c1..abaf5f8 100644 --- a/Heroes.ReplayParser.ConsoleApplication/Program.cs +++ b/Heroes.ReplayParser.ConsoleApplication/Program.cs @@ -1,10 +1,9 @@ -using System.IO; -using System; -using System.Collections.Generic; +using System; +using System.IO; using System.Linq; using Heroes.ReplayParser; -namespace ConsoleApplication1 +namespace ConsoleApplication { class Program { @@ -15,13 +14,11 @@ static void Main(string[] args) // Attempt to parse the replay // Ignore errors can be set to true if you want to attempt to parse currently unsupported replays, such as 'VS AI' or 'PTR Region' replays - var replayParseResult = DataParser.ParseReplay(randomReplayFileName, ignoreErrors: false, deleteFile: false); + var (replayParseResult, replay) = DataParser.ParseReplay(randomReplayFileName, ignoreErrors: false, deleteFile: false); // If successful, the Replay object now has all currently available information - if (replayParseResult.Item1 == DataParser.ReplayParseResult.Success) + if (replayParseResult == DataParser.ReplayParseResult.Success) { - var replay = replayParseResult.Item2; - Console.WriteLine("Replay Build: " + replay.ReplayBuild); Console.WriteLine("Map: " + replay.Map); foreach (var player in replay.Players.OrderByDescending(i => i.IsWinner)) @@ -30,7 +27,7 @@ static void Main(string[] args) Console.WriteLine("Press Any Key to Close"); } else - Console.WriteLine("Failed to Parse Replay: " + replayParseResult.Item1); + Console.WriteLine("Failed to Parse Replay: " + replayParseResult); Console.Read(); } diff --git a/Heroes.ReplayParser/BitReader.cs b/Heroes.ReplayParser/BitReader.cs index b6321a3..5c1941f 100644 --- a/Heroes.ReplayParser/BitReader.cs +++ b/Heroes.ReplayParser/BitReader.cs @@ -1,9 +1,9 @@ -namespace Heroes.ReplayParser.Streams -{ - using System; - using System.IO; - using System.Text; +using System; +using System.IO; +using System.Text; +namespace Heroes.ReplayParser +{ /// /// A basic little-endian bitstream reader. /// @@ -31,13 +31,7 @@ public BitReader(Stream stream) /// /// Gets a value indicating whether the end of stream has been reached. /// - public bool EndOfStream - { - get - { - return (this.Cursor >> 3) == this.stream.Length; - } - } + public bool EndOfStream => (this.Cursor >> 3) == this.stream.Length; /// /// Reads up to 32 bits from the stream, returning them as a uint. diff --git a/Heroes.ReplayParser/DataParser.cs b/Heroes.ReplayParser/DataParser.cs index 327e079..e33251f 100644 --- a/Heroes.ReplayParser/DataParser.cs +++ b/Heroes.ReplayParser/DataParser.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Heroes.ReplayParser.MPQFiles; +using MpqHeader = Heroes.ReplayParser.MPQFiles.MpqHeader; namespace Heroes.ReplayParser { diff --git a/Heroes.ReplayParser/MPQFiles/MpqHeader.cs b/Heroes.ReplayParser/MPQFiles/MpqHeader.cs index 00f9771..0817276 100644 --- a/Heroes.ReplayParser/MPQFiles/MpqHeader.cs +++ b/Heroes.ReplayParser/MPQFiles/MpqHeader.cs @@ -1,8 +1,8 @@ -namespace Heroes.ReplayParser -{ - using System; - using System.IO; +using System; +using System.IO; +namespace Heroes.ReplayParser.MPQFiles +{ /// Parses the header at the beginning of the MPQ file structure. public static class MpqHeader { @@ -37,7 +37,8 @@ private static void ParseHeader(Replay replay, BinaryReader reader) // [0] = Blob, "Heroes of the Storm replay 11" - Strange backward arrow before 11 as well. I don't think the '11' will change, as I believe it was also always '11' in Starcraft 2 replays. - replay.ReplayVersion = string.Format("{0}.{1}.{2}.{3}", headerStructure.dictionary[1].dictionary[1].vInt.Value, headerStructure.dictionary[1].dictionary[2].vInt.Value, headerStructure.dictionary[1].dictionary[3].vInt.Value, headerStructure.dictionary[1].dictionary[4].vInt.Value); + replay.ReplayVersion = + $"{headerStructure.dictionary[1].dictionary[1].vInt.Value}.{headerStructure.dictionary[1].dictionary[2].vInt.Value}.{headerStructure.dictionary[1].dictionary[3].vInt.Value}.{headerStructure.dictionary[1].dictionary[4].vInt.Value}"; replay.ReplayBuild = (int)headerStructure.dictionary[1].dictionary[4].vInt.Value; diff --git a/Heroes.ReplayParser/MPQFiles/ReplayAttributeEvents.cs b/Heroes.ReplayParser/MPQFiles/ReplayAttributeEvents.cs index 20a2745..f70d15e 100644 --- a/Heroes.ReplayParser/MPQFiles/ReplayAttributeEvents.cs +++ b/Heroes.ReplayParser/MPQFiles/ReplayAttributeEvents.cs @@ -1,11 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; -namespace Heroes.ReplayParser +namespace Heroes.ReplayParser.MPQFiles { - using System.Collections.Generic; - public static class ReplayAttributeEvents { public const string FileName = "replay.attributes.events"; @@ -16,7 +15,7 @@ public static void Parse(Replay replay, byte[] buffer) var attributes = new ReplayAttribute[BitConverter.ToInt32(buffer, headerSize)]; - var initialOffset = 4 + headerSize; + const int initialOffset = 4 + headerSize; for (var i = 0; i < attributes.Length; i++) { @@ -68,15 +67,21 @@ private static void ApplyAttributes(Replay replay, ReplayAttribute[] Attributes) if (type == "comp" || type == "humn") replay.PlayersWithOpenSlots[attribute.PlayerId - 1] = replay.Players[attribute.PlayerId - replayPlayersWithOpenSlotsIndex]; - if (type == "comp") - replay.PlayersWithOpenSlots[attribute.PlayerId - 1].PlayerType = PlayerType.Computer; - else if (type == "humn") - replay.PlayersWithOpenSlots[attribute.PlayerId - 1].PlayerType = PlayerType.Human; - else if (type == "open") + switch (type) + { + case "comp": + replay.PlayersWithOpenSlots[attribute.PlayerId - 1].PlayerType = PlayerType.Computer; + break; + case "humn": + replay.PlayersWithOpenSlots[attribute.PlayerId - 1].PlayerType = PlayerType.Human; + break; // Less than 10 players in a Custom game - replayPlayersWithOpenSlotsIndex++; - else - throw new Exception("Unexpected value for PlayerType"); + case "open": + replayPlayersWithOpenSlotsIndex++; + break; + default: + throw new Exception("Unexpected value for PlayerType"); + } break; } diff --git a/Heroes.ReplayParser/MPQFiles/ReplayDetails.cs b/Heroes.ReplayParser/MPQFiles/ReplayDetails.cs index fcd02f2..84860ba 100644 --- a/Heroes.ReplayParser/MPQFiles/ReplayDetails.cs +++ b/Heroes.ReplayParser/MPQFiles/ReplayDetails.cs @@ -1,10 +1,9 @@ -using System.Linq; +using System; +using System.IO; +using System.Linq; -namespace Heroes.ReplayParser +namespace Heroes.ReplayParser.MPQFiles { - using System; - using System.IO; - public static class ReplayDetails { public const string FileName = "replay.details"; @@ -51,13 +50,18 @@ public static void Parse(Replay replay, byte[] buffer, bool ignoreErrors = false replay.Timestamp = DateTime.FromFileTimeUtc(replayDetailsStructure.dictionary[5].vInt.Value); // m_timeUTC - // There was a bug during the below builds where timestamps were buggy for the Mac build of Heroes of the Storm - // The replay, as well as viewing these replays in the game client, showed years such as 1970, 1999, etc - // I couldn't find a way to get the correct timestamp, so I am just estimating based on when these builds were live - if (replay.ReplayBuild == 34053 && replay.Timestamp < new DateTime(2015, 2, 8)) - replay.Timestamp = new DateTime(2015, 2, 13); - else if (replay.ReplayBuild == 34190 && replay.Timestamp < new DateTime(2015, 2, 15)) - replay.Timestamp = new DateTime(2015, 2, 20); + switch (replay.ReplayBuild) + { + // There was a bug during the below builds where timestamps were buggy for the Mac build of Heroes of the Storm + // The replay, as well as viewing these replays in the game client, showed years such as 1970, 1999, etc + // I couldn't find a way to get the correct timestamp, so I am just estimating based on when these builds were live + case 34053 when replay.Timestamp < new DateTime(2015, 2, 8): + replay.Timestamp = new DateTime(2015, 2, 13); + break; + case 34190 when replay.Timestamp < new DateTime(2015, 2, 15): + replay.Timestamp = new DateTime(2015, 2, 20); + break; + } // [6] - m_timeLocalOffset - For Windows replays, this is Utc offset. For Mac replays, this is actually the entire Local Timestamp // [7] - m_description - Empty String diff --git a/Heroes.ReplayParser/MPQFiles/ReplayGameEvents.cs b/Heroes.ReplayParser/MPQFiles/ReplayGameEvents.cs index baed4eb..a3b4a35 100644 --- a/Heroes.ReplayParser/MPQFiles/ReplayGameEvents.cs +++ b/Heroes.ReplayParser/MPQFiles/ReplayGameEvents.cs @@ -1,12 +1,10 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; -namespace Heroes.ReplayParser +namespace Heroes.ReplayParser.MPQFiles { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - public class ReplayGameEvents { public const string FileName = "replay.game.events"; @@ -20,7 +18,7 @@ public static List Parse(byte[] buffer, Player[] clientList, int repl var ticksElapsed = 0; using (var stream = new MemoryStream(buffer)) { - var bitReader = new Streams.BitReader(stream); + var bitReader = new BitReader(stream); while (!bitReader.EndOfStream) { var gameEvent = new GameEvent(); @@ -453,10 +451,7 @@ public static List Parse(byte[] buffer, Player[] clientList, int repl break; case GameEventType.CGameUserLeaveEvent: // m_leaveReason - if(replayBuild >= 55929) - bitReader.Read(5); - else - bitReader.Read(4); + bitReader.Read(replayBuild >= 55929 ? 5 : 4); break; case GameEventType.CGameUserJoinEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[5] }; @@ -527,12 +522,12 @@ public class GameEvent public Player player = null; public bool isGlobal = false; public int ticksElapsed; - public TimeSpan TimeSpan { get { return new TimeSpan(0, 0, (int)(ticksElapsed / 16.0)); } } + public TimeSpan TimeSpan => new TimeSpan(0, 0, (int)(ticksElapsed / 16.0)); public TrackerEventStructure data = null; public override string ToString() { - return data != null ? data.ToString() : null; + return data?.ToString(); } } diff --git a/Heroes.ReplayParser/MPQFiles/ReplayInitData.cs b/Heroes.ReplayParser/MPQFiles/ReplayInitData.cs index 0b955bc..d66bac9 100644 --- a/Heroes.ReplayParser/MPQFiles/ReplayInitData.cs +++ b/Heroes.ReplayParser/MPQFiles/ReplayInitData.cs @@ -1,10 +1,10 @@ -namespace Heroes.ReplayParser +using System; +using System.IO; +using System.Linq; +using System.Text; + +namespace Heroes.ReplayParser.MPQFiles { - using Heroes.ReplayParser.Streams; - using System; - using System.IO; - using System.Linq; - using System.Text; /// Parses the replay.Initdata file in the replay file. public class ReplayInitData { @@ -142,10 +142,7 @@ public static void Parse(Replay replay, byte[] buffer) reader.Read(8); // + 1 = Max Races // Max Controls - if (replay.ReplayBuild < 59279) - reader.Read(8); - else - reader.Read(4); + reader.Read(replay.ReplayBuild < 59279 ? 8 : 4); replay.MapSize = new Point { X = (int)reader.Read(8), Y = (int)reader.Read(8) }; if (replay.MapSize.Y == 1) @@ -171,10 +168,7 @@ public static void Parse(Replay replay, byte[] buffer) reader.ReadBitArray(reader.Read(6)); // m_allowedDifficulty // m_allowedControls - if (replay.ReplayBuild < 59279) - reader.ReadBitArray(reader.Read(8)); - else - reader.ReadBitArray(reader.Read(4)); + reader.ReadBitArray(replay.ReplayBuild < 59279 ? reader.Read(8) : reader.Read(4)); reader.ReadBitArray(reader.Read(2)); // m_allowedObserveTypes reader.ReadBitArray(reader.Read(7)); // m_allowedAIBuilds @@ -222,7 +216,7 @@ public static void Parse(Replay replay, byte[] buffer) reader.Read(32); // m_logoIndex - string heroId = Encoding.ASCII.GetString(reader.ReadBlobPrecededWithLength(9)); // m_hero + var heroId = Encoding.ASCII.GetString(reader.ReadBlobPrecededWithLength(9)); // m_hero var skinAndSkinTint = Encoding.ASCII.GetString(reader.ReadBlobPrecededWithLength(9)); // m_skin if (skinAndSkinTint == "") @@ -296,19 +290,19 @@ public static void Parse(Replay replay, byte[] buffer) if (replay.ReplayVersionMajor >= 2) { - string banner = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_banner + var banner = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_banner if (!string.IsNullOrEmpty(banner) && userID.HasValue) replay.ClientListByUserID[userID.Value].Banner = banner; - string spray = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_spray + var spray = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_spray if (!string.IsNullOrEmpty(spray) && userID.HasValue) replay.ClientListByUserID[userID.Value].Spray = spray; - string announcer = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_announcerPack + var announcer = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_announcerPack if (!string.IsNullOrEmpty(announcer) && userID.HasValue) replay.ClientListByUserID[userID.Value].AnnouncerPack = announcer; - string voiceLine = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_voiceLine + var voiceLine = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_voiceLine if (!string.IsNullOrEmpty(voiceLine) && userID.HasValue) replay.ClientListByUserID[userID.Value].VoiceLine = voiceLine; @@ -318,8 +312,8 @@ public static void Parse(Replay replay, byte[] buffer) var heroMasteryTiersLength = reader.Read(10); for (var j = 0; j < heroMasteryTiersLength; j++) { - string heroAttributeName = new string(BitConverter.GetBytes(reader.Read(32)).Select(k => (char)k).Reverse().ToArray()); // m_hero - int tier = (int)reader.Read(8); // m_tier + var heroAttributeName = new string(BitConverter.GetBytes(reader.Read(32)).Select(k => (char)k).Reverse().ToArray()); // m_hero + var tier = (int)reader.Read(8); // m_tier if (userID.HasValue) { diff --git a/Heroes.ReplayParser/MPQFiles/ReplayMessageEvents.cs b/Heroes.ReplayParser/MPQFiles/ReplayMessageEvents.cs index 25be1ef..b8704bb 100644 --- a/Heroes.ReplayParser/MPQFiles/ReplayMessageEvents.cs +++ b/Heroes.ReplayParser/MPQFiles/ReplayMessageEvents.cs @@ -1,9 +1,9 @@ -namespace Heroes.ReplayParser -{ - using System; - using System.IO; - using System.Text; +using System; +using System.IO; +using System.Text; +namespace Heroes.ReplayParser.MPQFiles +{ public class ReplayMessageEvents { public const string FileName = "replay.message.events"; @@ -20,7 +20,7 @@ public static void Parse(Replay replay, byte[] buffer) var ticksElapsed = 0; using (var stream = new MemoryStream(buffer)) { - var bitReader = new Streams.BitReader(stream); + var bitReader = new BitReader(stream); while (!bitReader.EndOfStream) { @@ -41,23 +41,24 @@ public static void Parse(Replay replay, byte[] buffer) { case MessageEventType.SChatMessage: { - ChatMessage chatMessage = new ChatMessage(); - - chatMessage.MessageTarget = (MessageTarget)bitReader.Read(3); // m_recipient (the target) - chatMessage.Message = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(11)); // m_string - + var chatMessage = new ChatMessage + { + MessageTarget = (MessageTarget) bitReader.Read(3), // m_recipient (the target) + Message = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(11)) // m_string + }; + message.ChatMessage = chatMessage; replay.Messages.Add(message); break; } case MessageEventType.SPingMessage: { - PingMessage pingMessage = new PingMessage(); - - pingMessage.MessageTarget = (MessageTarget)bitReader.Read(3); // m_recipient (the target) - - pingMessage.XCoordinate = bitReader.ReadInt32() - (-2147483648); // m_point x - pingMessage.YCoordinate = bitReader.ReadInt32() - (-2147483648); // m_point y + var pingMessage = new PingMessage + { + MessageTarget = (MessageTarget) bitReader.Read(3), // m_recipient (the target) + XCoordinate = bitReader.ReadInt32() - (-2147483648), // m_point x + YCoordinate = bitReader.ReadInt32() - (-2147483648) // m_point y + }; message.PingMessage = pingMessage; replay.Messages.Add(message); @@ -81,9 +82,7 @@ public static void Parse(Replay replay, byte[] buffer) } case MessageEventType.SPlayerAnnounceMessage: { - PlayerAnnounceMessage announceMessage = new PlayerAnnounceMessage(); - - announceMessage.AnnouncementType = (AnnouncementType)bitReader.Read(2); + var announceMessage = new PlayerAnnounceMessage {AnnouncementType = (AnnouncementType) bitReader.Read(2)}; switch (announceMessage.AnnouncementType) { @@ -93,10 +92,11 @@ public static void Parse(Replay replay, byte[] buffer) } case AnnouncementType.Ability: { - AbilityAnnouncment ability = new AbilityAnnouncment(); - ability.AbilityLink = bitReader.ReadInt16(); // m_abilLink - ability.AbilityIndex = (int)bitReader.Read(5); // m_abilCmdIndex - ability.ButtonLink = bitReader.ReadInt16(); // m_buttonLink + var ability = new AbilityAnnouncment { + AbilityLink = bitReader.ReadInt16(), // m_abilLink + AbilityIndex = (int) bitReader.Read(5), // m_abilCmdIndex + ButtonLink = bitReader.ReadInt16() // m_buttonLink + }; announceMessage.AbilityAnnouncement = ability; break; @@ -110,8 +110,7 @@ public static void Parse(Replay replay, byte[] buffer) } case AnnouncementType.Vitals: { - VitalAnnouncment vital = new VitalAnnouncment(); - vital.VitalType = (VitalType)(bitReader.ReadInt16() - (-32768)); + var vital = new VitalAnnouncment {VitalType = (VitalType) (bitReader.ReadInt16() - (-32768))}; announceMessage.VitalAnnouncement = vital; break; diff --git a/Heroes.ReplayParser/MPQFiles/ReplayResumableEvents.cs b/Heroes.ReplayParser/MPQFiles/ReplayResumableEvents.cs index dad4154..c58d4a5 100644 --- a/Heroes.ReplayParser/MPQFiles/ReplayResumableEvents.cs +++ b/Heroes.ReplayParser/MPQFiles/ReplayResumableEvents.cs @@ -1,11 +1,9 @@ -using System.Linq; +using System; +using System.IO; +using System.Text; -namespace Heroes.ReplayParser +namespace Heroes.ReplayParser.MPQFiles { - using System; - using System.IO; - using System.Text; - public static class ReplayResumableEvents { public const string FileName = "replay.resumable.events"; diff --git a/Heroes.ReplayParser/MPQFiles/ReplayServerBattlelobby.cs b/Heroes.ReplayParser/MPQFiles/ReplayServerBattlelobby.cs index e5c8e4a..1b445cb 100644 --- a/Heroes.ReplayParser/MPQFiles/ReplayServerBattlelobby.cs +++ b/Heroes.ReplayParser/MPQFiles/ReplayServerBattlelobby.cs @@ -1,13 +1,12 @@ -namespace Heroes.ReplayParser +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Heroes.ReplayParser.MPQFiles { - using System.IO; - using Streams; - using System; - using System.Text; - using System.Collections.Generic; - using System.Text.RegularExpressions; - using System.Linq; - /// Parses the replay.server.battlelobby file in the replay file. public class ReplayServerBattlelobby { @@ -65,7 +64,7 @@ public static void Parse(Replay replay, byte[] buffer) } } - internal static void DetailedParse(BitReader bitReader, Replay replay, int s2mArrayLength) + private static void DetailedParse(BitReader bitReader, Replay replay, int s2mArrayLength) { bitReader.AlignToByte(); for (; ; ) @@ -91,19 +90,16 @@ internal static void DetailedParse(BitReader bitReader, Replay replay, int s2mAr // Player collections - starting with HOTS 2.0 (live build 52860) // strings gone starting with build (ptr) 55929 // -------------------------------------------------------------- - List playerCollection = new List(); + var playerCollection = new List(); - int collectionSize = 0; + var collectionSize = 0; - if (replay.ReplayBuild >= 48027) - collectionSize = bitReader.ReadInt16(); - else - collectionSize = bitReader.ReadInt32(); + collectionSize = replay.ReplayBuild >= 48027 ? bitReader.ReadInt16() : bitReader.ReadInt32(); if (collectionSize > 8000) throw new DetailedParsedException("collectionSize is an unusually large number"); - for (int i = 0; i < collectionSize; i++) + for (var i = 0; i < collectionSize; i++) { if (replay.ReplayBuild >= 55929) bitReader.ReadBytes(8); // most likey an identifier for the item; first six bytes are 0x00 @@ -115,9 +111,9 @@ internal static void DetailedParse(BitReader bitReader, Replay replay, int s2mAr if (bitReader.ReadInt32() != collectionSize) throw new DetailedParsedException("skinArrayLength not equal"); - for (int i = 0; i < collectionSize; i++) + for (var i = 0; i < collectionSize; i++) { - for (int j = 0; j < 16; j++) // 16 is total player slots + for (var j = 0; j < 16; j++) // 16 is total player slots { bitReader.ReadByte(); @@ -163,7 +159,7 @@ internal static void DetailedParse(BitReader bitReader, Replay replay, int s2mAr return; } - for (int player = 0; player < replay.ClientListByUserID.Length; player++) + for (var player = 0; player < replay.ClientListByUserID.Length; player++) { if (replay.ClientListByUserID[player] == null) break; @@ -194,11 +190,11 @@ internal static void DetailedParse(BitReader bitReader, Replay replay, int s2mAr // repeat of the collection section above if (replay.ReplayBuild >= 51609) { - int size = (int)bitReader.Read(12); // 3 bytes + var size = (int)bitReader.Read(12); // 3 bytes if (size == collectionSize) { - int bytesSize = collectionSize / 8; - int bitsSize = collectionSize % 8; + var bytesSize = collectionSize / 8; + var bitsSize = collectionSize % 8; bitReader.ReadBytes(bytesSize); bitReader.Read(bitsSize); @@ -265,12 +261,9 @@ internal static void DetailedParse(BitReader bitReader, Replay replay, int s2mAr // used for builds <= 47479 and 47903 private static void ExtendedBattleTagParsingOld(Replay replay, BitReader bitReader) { - bool changed47479 = false; - - if (replay.ReplayBuild == 47479 && DetectBattleTagChangeBuild47479(replay, bitReader)) - changed47479 = true; + bool changed47479 = replay.ReplayBuild == 47479 && DetectBattleTagChangeBuild47479(replay, bitReader); - for (int i = 0; i < replay.ClientListByUserID.Length; i++) + for (var i = 0; i < replay.ClientListByUserID.Length; i++) { if (replay.ClientListByUserID[i] == null) break; @@ -391,7 +384,7 @@ private static bool DetectBattleTagChangeBuild47479(Replay replay, BitReader bit if (replay.ReplayBuild != 47479) return false; - bool changed = false; + var changed = false; var offset = bitReader.ReadByte(); bitReader.ReadString(2); // T: @@ -399,7 +392,7 @@ private static bool DetectBattleTagChangeBuild47479(Replay replay, BitReader bit bitReader.ReadBytes(6); - for (int i = 0; i < 3; i++) + for (var i = 0; i < 3; i++) { if (bitReader.Read(8) != 0) changed = true; @@ -477,7 +470,7 @@ private static void GetBattleTags(Replay replay, BitReader reader) } } - public static byte[] ReadSpecialBlob(BitReader bitReader, int numBitsForLength) + private static byte[] ReadSpecialBlob(BitReader bitReader, int numBitsForLength) { var stringLength = bitReader.Read(numBitsForLength); bitReader.AlignToByte(); diff --git a/Heroes.ReplayParser/MPQFiles/ReplayTrackerEvents.cs b/Heroes.ReplayParser/MPQFiles/ReplayTrackerEvents.cs index fec9ed0..de8ab5f 100644 --- a/Heroes.ReplayParser/MPQFiles/ReplayTrackerEvents.cs +++ b/Heroes.ReplayParser/MPQFiles/ReplayTrackerEvents.cs @@ -1,12 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; -namespace Heroes.ReplayParser +namespace Heroes.ReplayParser.MPQFiles { - using System; - using System.IO; - using System.Text; - /// /// Parses the replay.tracker.events file in the MPQ Archive /// @@ -122,7 +121,7 @@ public class TrackerEventStructure public TrackerEventStructure[] array = null; public Dictionary dictionary = null; public byte[] blob = null; - public string blobText { get { return blob != null ? Encoding.UTF8.GetString(blob) : null; } } + public string blobText => blob != null ? Encoding.UTF8.GetString(blob) : null; public int? choiceFlag = null; public TrackerEventStructure choiceData = null; public TrackerEventStructure optionalData = null; @@ -195,7 +194,7 @@ public override string ToString() { case 0x00: // array return array != null - ? '[' + string.Join(", ", array.Select(i => i != null ? i.ToString() : null)) + ']' + ? '[' + string.Join(", ", array.Select(i => i?.ToString())) + ']' : null; case 0x01: // bitarray, weird alignment requirements, hasn't been used yet throw new NotImplementedException(); @@ -204,11 +203,9 @@ public override string ToString() case 0x03: // choice return "Choice: Flag: " + choiceFlag + ", Data: " + choiceData; case 0x04: // optional - return optionalData != null - ? optionalData.ToString() - : null; + return optionalData?.ToString(); case 0x05: // struct - return '{' + string.Join(", ", dictionary.Values.Select(i => i != null ? i.ToString() : null)) + '}'; + return '{' + string.Join(", ", dictionary.Values.Select(i => i?.ToString())) + '}'; case 0x06: // u8 case 0x07: // u32 case 0x08: // u64 diff --git a/Heroes.ReplayParser/Message.cs b/Heroes.ReplayParser/Message.cs index 48575ed..ce11e0d 100644 --- a/Heroes.ReplayParser/Message.cs +++ b/Heroes.ReplayParser/Message.cs @@ -1,5 +1,5 @@ using System; -using static Heroes.ReplayParser.ReplayMessageEvents; +using Heroes.ReplayParser.MPQFiles; namespace Heroes.ReplayParser { @@ -8,10 +8,10 @@ public class Message public Player MessageSender { get; set; } public int PlayerIndex { get; set; } public TimeSpan Timestamp { get; set; } - public MessageEventType MessageEventType { get; set; } - public ChatMessage ChatMessage { get; set; } - public PingMessage PingMessage { get; set; } - public PlayerAnnounceMessage PlayerAnnounceMessage { get; set; } + public ReplayMessageEvents.MessageEventType MessageEventType { get; set; } + public ReplayMessageEvents.ChatMessage ChatMessage { get; set; } + public ReplayMessageEvents.PingMessage PingMessage { get; set; } + public ReplayMessageEvents.PlayerAnnounceMessage PlayerAnnounceMessage { get; set; } public override string ToString() { diff --git a/Heroes.ReplayParser/Replay.cs b/Heroes.ReplayParser/Replay.cs index 62ead27..f9f08ab 100644 --- a/Heroes.ReplayParser/Replay.cs +++ b/Heroes.ReplayParser/Replay.cs @@ -1,4 +1,6 @@ -namespace Heroes.ReplayParser +using Heroes.ReplayParser.MPQFiles; + +namespace Heroes.ReplayParser { using System; using System.Collections.Generic; @@ -96,7 +98,7 @@ public class PeriodicXPBreakdown public int StructureXP { get; set; } public int HeroXP { get; set; } public int TrickleXP { get; set; } - public int TotalXP { get { return this.MinionXP + this.CreepXP + this.StructureXP + this.HeroXP + this.TrickleXP; } } + public int TotalXP => this.MinionXP + this.CreepXP + this.StructureXP + this.HeroXP + this.TrickleXP; } public class TeamObjective diff --git a/Heroes.ReplayParser/Statistics.cs b/Heroes.ReplayParser/Statistics.cs index 27d176f..0c52c0c 100644 --- a/Heroes.ReplayParser/Statistics.cs +++ b/Heroes.ReplayParser/Statistics.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Heroes.ReplayParser.MPQFiles; namespace Heroes.ReplayParser { @@ -31,9 +32,9 @@ public static void Parse(Replay replay) replay.TeamObjectives[(ownerChangeEvent != null ? ownerChangeEvent.PlayerNewOwner : vehicleUnit.PlayerControlledBy).Team].Add(new TeamObjective { Player = ownerChangeEvent != null ? ownerChangeEvent.PlayerNewOwner : vehicleUnit.PlayerControlledBy, - TimeSpan = ownerChangeEvent != null ? ownerChangeEvent.TimeSpanOwnerChanged : vehicleUnit.TimeSpanAcquired.Value, + TimeSpan = ownerChangeEvent?.TimeSpanOwnerChanged ?? vehicleUnit.TimeSpanAcquired.Value, TeamObjectiveType = vehicleUnit.Name == "VehiclePlantHorror" ? TeamObjectiveType.GardenOfTerrorGardenTerrorActivatedWithGardenTerrorDurationSeconds : TeamObjectiveType.DragonShireDragonKnightActivatedWithDragonDurationSeconds, - Value = (int) ((vehicleUnit.TimeSpanDied ?? replay.ReplayLength) - (ownerChangeEvent != null ? ownerChangeEvent.TimeSpanOwnerChanged : vehicleUnit.TimeSpanAcquired.Value)).TotalSeconds }); + Value = (int) ((vehicleUnit.TimeSpanDied ?? replay.ReplayLength) - (ownerChangeEvent?.TimeSpanOwnerChanged ?? vehicleUnit.TimeSpanAcquired.Value)).TotalSeconds }); } // Braxis Holdout Zerg Strength @@ -69,7 +70,7 @@ public static void Parse(Replay replay) teamZergUnitCount[zergUnits[i].Team.Value]++; // Check to see if the objective was not completed before the game ended - if (!teamZergUnitCount.Any(j => j == zergSpawnNumberToStrength.Count - 1)) + if (teamZergUnitCount.All(j => j != zergSpawnNumberToStrength.Count - 1)) break; } @@ -399,7 +400,7 @@ public static void Parse(Replay replay) if (trackerEvent.Data.dictionary[2].optionalData.array[2].dictionary[1].vInt.Value == 0) // Not sure why, but sometimes 'TeamID' = 0. I've seen it 3 times in about ~60 Sky Temple games break; - var recentSkyTempleShotsFiredTeamObjective = replay.TeamObjectives[trackerEvent.Data.dictionary[2].optionalData.array[2].dictionary[1].vInt.Value - 1].Where(i => i.TeamObjectiveType == TeamObjectiveType.SkyTempleShotsFiredWithSkyTempleShotsDamage && i.TimeSpan > trackerEvent.TimeSpan.Add(TimeSpan.FromSeconds(-130))).SingleOrDefault(); + var recentSkyTempleShotsFiredTeamObjective = replay.TeamObjectives[trackerEvent.Data.dictionary[2].optionalData.array[2].dictionary[1].vInt.Value - 1].SingleOrDefault(i => i.TeamObjectiveType == TeamObjectiveType.SkyTempleShotsFiredWithSkyTempleShotsDamage && i.TimeSpan > trackerEvent.TimeSpan.Add(TimeSpan.FromSeconds(-130))); if (recentSkyTempleShotsFiredTeamObjective != null) recentSkyTempleShotsFiredTeamObjective.Value += (int) trackerEvent.Data.dictionary[3].optionalData.array[0].dictionary[1].vInt.Value; diff --git a/Heroes.ReplayParser/Unit.cs b/Heroes.ReplayParser/Unit.cs index 1b1dfd8..9e659c9 100644 --- a/Heroes.ReplayParser/Unit.cs +++ b/Heroes.ReplayParser/Unit.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Heroes.ReplayParser.MPQFiles; namespace Heroes.ReplayParser { @@ -515,11 +516,18 @@ public static void ParseUnitData(Replay replay) activeHeroUnits[newUnit.PlayerControlledBy].Positions.Add(new Position { TimeSpan = newUnit.TimeSpanBorn, Point = newUnit.PointBorn }); else if (isCheckingForAbathurLocusts) { - // For Abathur locusts, we need to make sure they aren't spawning from a locust nest (Level 20 talent) - if (newUnit.Name == "AbathurLocustNest") - isCheckingForAbathurLocusts = false; - else if (newUnit.Name == "AbathurLocustNormal" || newUnit.Name == "AbathurLocustAssaultStrain" || newUnit.Name == "AbathurLocustBombardStrain") - activeHeroUnits[newUnit.PlayerControlledBy].Positions.Add(new Position { TimeSpan = newUnit.TimeSpanBorn, Point = newUnit.PointBorn }); + switch (newUnit.Name) + { + // For Abathur locusts, we need to make sure they aren't spawning from a locust nest (Level 20 talent) + case "AbathurLocustNest": + isCheckingForAbathurLocusts = false; + break; + case "AbathurLocustNormal": + case "AbathurLocustAssaultStrain": + case "AbathurLocustBombardStrain": + activeHeroUnits[newUnit.PlayerControlledBy].Positions.Add(new Position { TimeSpan = newUnit.TimeSpanBorn, Point = newUnit.PointBorn }); + break; + } } } break; @@ -701,14 +709,14 @@ public static void ParseUnitData(Replay replay) // Group minion units by lane var currentIndex = 0; - var minionUnitsPerWave = 7; + const int minionUnitsPerWave = 7; while (currentIndex < minionUnits.Length) - for (var i = 0; i < unitsPerLaneTemp.Length; i++) + foreach (var unitPerLaneTemp in unitsPerLaneTemp) for (var j = 0; j < minionUnitsPerWave; j++) { if (currentIndex == minionUnits.Length) break; - unitsPerLaneTemp[i].Add(minionUnits[currentIndex++]); + unitPerLaneTemp.Add(minionUnits[currentIndex++]); // CatapultMinions don't seem to spawn exactly with their minion wave, which is strange // For now I will leave them out of this, which means they may appear to travel through walls @@ -726,10 +734,7 @@ public static void ParseUnitData(Replay replay) // For each lane, take the forts in that lane, and see if the minions in that lane walked beyond this var currentLaneUnitsToAdjust = unitsPerLane[i].Where(j => j.Positions.Any() || j.TimeSpanDied.HasValue); var currentLaneWaypoints = minionWayPoints.Skip(numberOfStructureTiers * i).Take(numberOfStructureTiers); - if (team == 0) - currentLaneWaypoints = currentLaneWaypoints.OrderBy(j => j.X); - else - currentLaneWaypoints = currentLaneWaypoints.OrderByDescending(j => j.X); + currentLaneWaypoints = team == 0 ? currentLaneWaypoints.OrderBy(j => j.X) : currentLaneWaypoints.OrderByDescending(j => j.X); foreach (var laneUnit in currentLaneUnitsToAdjust) {