public static ActorState Deserialize(int maxChannels, List <ActorState> existingActorStates, List <ActorState> frameActorStates, string[] objectIndexToName, IDictionary <string, ClassNetCache> classNetCacheByName, UInt32 versionMajor, UInt32 versionMinor, BitReader br)
        {
            var        startPosition = br.Position;
            ActorState a             = new ActorState();

            try
            {
                var actorId = br.ReadUInt32Max(maxChannels);

                a.Id = actorId;

                if (br.ReadBit())
                {
                    if (br.ReadBit())
                    {
                        a.State    = ActorStateState.New;
                        a.Unknown1 = br.ReadBit();
                        if (versionMajor > 868 || (versionMajor == 868 && versionMinor >= 14))
                        {
                            a.Unknown2 = br.ReadUInt32(); // Always roughly 1/2 the ID. Maybe some sort of parent/child thing? I dunno, doesnt seem to matter yet.
                        }

                        a.TypeId = br.ReadUInt32();

                        a.TypeName       = objectIndexToName[(int)a.TypeId.Value];
                        a._classNetCache = ObjectNameToClassNetCache(a.TypeName, classNetCacheByName);
                        a.ClassName      = objectIndexToName[a._classNetCache.ObjectIndex];

                        if (!ClassHasInitialPosition(a.ClassName))
                        {
#if DEBUG
                            a.KnownBits = br.GetBits(startPosition, br.Position - startPosition);
                            a.Complete  = true;
#endif
                            return(a);
                        }

                        a.Position = Vector3D.Deserialize(br);

                        if (ClassHasRotation(a.ClassName))
                        {
                            a.Rotation = Rotator.Deserialize(br);
                        }
#if DEBUG
                        a.Complete = true;
#endif
                    }
                    else
                    {
                        a.State = ActorStateState.Existing;
                        var oldState = existingActorStates.Where(x => x.Id == a.Id).Single();

                        a.TypeId = oldState.TypeId;

                        a.Properties = new List <ActorStateProperty>();
                        ActorStateProperty lastProp = null;
                        while (br.ReadBit())
                        {
                            lastProp = ActorStateProperty.Deserialize(oldState._classNetCache, oldState.TypeName, objectIndexToName, versionMajor, versionMinor, br);
                            a.Properties.Add(lastProp);

#if DEBUG
                            if (!lastProp.IsComplete)
                            {
                                break;
                            }
#endif
                        }

#if DEBUG
                        a.Complete = lastProp.IsComplete;
                        if (lastProp.Data.Count > 0 && lastProp.Data.Last().ToString() == "FAILED")
                        {
                            a.Failed = true;
                        }
#endif
                        var endPosition = br.Position;
                    }
                }
                else
                {
                    a.State = ActorStateState.Deleted;

                    var actor = existingActorStates.Where(x => x.Id == a.Id).SingleOrDefault();
#if DEBUG
                    a.Complete = true;
#endif
                    var endPosition = br.Position;
                }
#if DEBUG
                if (!a.Complete)
                {
                    // Read a bunch of data so we have something to look at in the logs
                    // Otherwise the logs may not show any data bits for whatever is broken, which is hard to interpret
                    br.ReadBytes(16);
                }

                a.KnownBits = br.GetBits(startPosition, br.Position - startPosition);
#endif
                return(a);
            }
            catch (Exception e)
            {
#if DEBUG
                a.KnownBits   = br.GetBits(startPosition, br.Position - startPosition);
                a.UnknownBits = br.GetBits(br.Position, 100);
                Console.WriteLine(e.ToString());
                a.Failed   = true;
                a.Complete = false;
                return(a);
#else
                throw;
#endif
            }
        }
        public static ActorState Deserialize(int maxChannels, IDictionary <UInt32, ActorState> existingActorStates, List <ActorState> frameActorStates, string[] objectIndexToName, IDictionary <string, ClassNetCache> classNetCacheByName, UInt32 engineVersion, UInt32 licenseeVersion, UInt32 netVersion, BitReader br)
        {
            var        startPosition = br.Position;
            ActorState a             = new ActorState();

            try
            {
                var actorId = br.ReadUInt32Max(maxChannels);

                a.Id = actorId;

                if (br.ReadBit())
                {
                    if (br.ReadBit())
                    {
                        a.State = ActorStateState.New;

                        if (engineVersion > 868 || (engineVersion == 868 && licenseeVersion >= 14))
                        {
                            a.NameId = br.ReadUInt32();
                        }

                        a.Unknown1 = br.ReadBit();
                        a.TypeId   = br.ReadUInt32();

                        var typeName = objectIndexToName[(int)a.TypeId.Value];
                        a._classNetCache = ObjectNameToClassNetCache(typeName, classNetCacheByName);
                        a.ClassId        = a._classNetCache.ObjectIndex;

                        if (!ClassHasInitialPosition(objectIndexToName[a.ClassId.Value]))
                        {
#if DEBUG
                            a.KnownBits = br.GetBits(startPosition, br.Position - startPosition);
                            a.Complete  = true;
#endif
                            return(a);
                        }

                        a.Position = Vector3D.Deserialize(br, netVersion);

                        if (ClassHasRotation(objectIndexToName[a.ClassId.Value]))
                        {
                            a.Rotation = Rotator.Deserialize(br);
                        }
#if DEBUG
                        a.Complete = true;
#endif
                    }
                    else
                    {
                        a.State = ActorStateState.Existing;
                        var oldState = existingActorStates[a.Id];

                        ActorStateProperty lastProp = null;
                        while (br.ReadBit())
                        {
                            lastProp = ActorStateProperty.Deserialize(oldState._classNetCache, objectIndexToName, engineVersion, licenseeVersion, netVersion, br);

                            ActorStateProperty existingProperty = null;
                            if (!a.Properties.TryGetValue(lastProp.PropertyId, out existingProperty))
                            {
                                a.Properties.Add(lastProp.PropertyId, lastProp);
                            }
                            else
                            {
                                // Combine this property's data into the existing property's data.
                                // TODO: If/When concrete property types are created, we should convert to an array type

                                // The "pretty" json serializer methods try hard to avoid serializing extra data.
                                // But, it gets confused by these cases where we have multiple properties with the same name on a single ActorState.
                                // The serializer could consider them all as a single array of values,
                                // but I think it makes more sense to treat them as an array in the parser.
                                // It probably more closely reflects the object in Rocket League that way.

                                // This implementation is a bit of a hack though. But the whole property class is a little hacky...

                                var listProperty = existingProperty as ActorStateListProperty;
                                if (listProperty == null)
                                {
                                    listProperty = new ActorStateListProperty(existingProperty);
                                    a.Properties[listProperty.PropertyId] = listProperty;
                                }

                                listProperty.Add(lastProp);
                            }
                        }

#if DEBUG
                        a.Complete = true;
#endif
                    }
                }
                else
                {
                    a.State = ActorStateState.Deleted;
#if DEBUG
                    a.Complete = true;
#endif
                }
#if DEBUG
                if (!a.Complete)
                {
                    // Read a bunch of data so we have something to look at in the logs
                    // Otherwise the logs may not show any data bits for whatever is broken, which is hard to interpret
                    br.ReadBytes(16);
                }

                a.KnownBits = br.GetBits(startPosition, br.Position - startPosition);
#endif
                return(a);
            }
            catch (Exception e)
            {
#if DEBUG
                a.KnownBits   = br.GetBits(startPosition, br.Position - startPosition);
                a.UnknownBits = br.GetBits(br.Position, 100);
                Console.WriteLine(e.ToString());
                a.Failed   = true;
                a.Complete = false;
                return(a);
#else
                throw;
#endif
            }
        }
        public static ActorState Deserialize(int maxChannels, List<ActorState> existingActorStates, List<ActorState> frameActorStates, string[] objectIndexToName, IDictionary<string, ClassNetCache> classNetCacheByName, UInt32 versionMajor, UInt32 versionMinor, BitReader br)
        {
            var startPosition = br.Position;
            ActorState a = new ActorState();

            try
            {
                var actorId = br.ReadUInt32Max(maxChannels);

                a.Id = actorId;

                if (br.ReadBit())
                {
                    if (br.ReadBit())
                    {
                        a.State = ActorStateState.New;

                        if (versionMajor > 868 || (versionMajor == 868 && versionMinor >= 14))
                        {
                            a.NameId = br.ReadUInt32();
                        }

                        a.Unknown1 = br.ReadBit();
                        a.TypeId = br.ReadUInt32();

                        a.TypeName = objectIndexToName[(int)a.TypeId.Value];
                        a._classNetCache = ObjectNameToClassNetCache(a.TypeName, classNetCacheByName);
                        a.ClassName = objectIndexToName[a._classNetCache.ObjectIndex];

                        if ( !ClassHasInitialPosition(a.ClassName))
                        {
            #if DEBUG
                            a.KnownBits = br.GetBits(startPosition, br.Position - startPosition);
                            a.Complete = true;
            #endif
                            return a;
                        }

                        a.Position = Vector3D.Deserialize(br);

                        if (ClassHasRotation(a.ClassName))
                        {
                            a.Rotation = Rotator.Deserialize(br);
                        }
            #if DEBUG
                        a.Complete = true;
            #endif
                    }
                    else
                    {
                        a.State = ActorStateState.Existing;
                        var oldState = existingActorStates.Where(x => x.Id == a.Id).Single();

                        a.TypeId = oldState.TypeId;

                        a.Properties = new List<ActorStateProperty>();
                        ActorStateProperty lastProp = null;
                        while (br.ReadBit())
                        {
                            lastProp = ActorStateProperty.Deserialize(oldState._classNetCache, oldState.TypeName, objectIndexToName, versionMajor, versionMinor, br);
                            a.Properties.Add(lastProp);

            #if DEBUG
                            if (!lastProp.IsComplete)
                            {
                                break;
                            }
            #endif
                        }

            #if DEBUG
                        a.Complete = lastProp.IsComplete;
                        if (lastProp.Data.Count > 0 && lastProp.Data.Last().ToString() == "FAILED")
                        {
                            a.Failed = true;
                        }
            #endif
                        var endPosition = br.Position;
                    }
                }
                else
                {
                    a.State = ActorStateState.Deleted;

                    var actor = existingActorStates.Where(x => x.Id == a.Id).SingleOrDefault();
            #if DEBUG
                    a.Complete = true;
            #endif
                    var endPosition = br.Position;
                }
            #if DEBUG
                if (!a.Complete)
                {
                    // Read a bunch of data so we have something to look at in the logs
                    // Otherwise the logs may not show any data bits for whatever is broken, which is hard to interpret
                    br.ReadBytes(16);
                }

                a.KnownBits = br.GetBits(startPosition, br.Position - startPosition);
            #endif
                return a;
            }
            catch(Exception e)
            {
            #if DEBUG
                a.KnownBits = br.GetBits(startPosition, br.Position - startPosition);
                a.UnknownBits = br.GetBits(br.Position, 100);
                Console.WriteLine(e.ToString());
                a.Failed = true;
                a.Complete = false;
                return a;
            #else
                throw;
            #endif
            }
        }