예제 #1
0
        public void LoadWad(string fileName)
        {
            BaseName = Path.GetFileNameWithoutExtension(fileName);
            BasePath = Path.GetDirectoryName(fileName);
            FileName = fileName;

            logger.Info("Reading wad file: " + fileName);

            // Initialize stream
            using (BinaryReaderEx reader = new BinaryReaderEx(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)))
            {
                // Read wad version
                Version = reader.ReadInt32();
                if (Version != 129 && Version != 130)
                {
                    logger.Error("Wad version  " + Version + " is not supported!");
                    throw new InvalidDataException();
                }

                // Read textures
                uint numTextures = reader.ReadUInt32();
                logger.Info("Wad object textures: " + numTextures);
                for (int i = 0; i < numTextures; i++)
                {
                    wad_object_texture text;
                    reader.ReadBlock(out text);
                    Textures.Add(text);
                }

                uint numTextureBytes = reader.ReadUInt32();
                logger.Info("Wad texture data size: " + numTextureBytes);
                TexturePages = reader.ReadBytes((int)numTextureBytes);

                NumTexturePages = (int)(numTextureBytes / 196608);

                // Read meshes
                uint numMeshPointers = reader.ReadUInt32();
                logger.Info("Wad mesh pointers: " + numMeshPointers);
                for (int i = 0; i < numMeshPointers; i++)
                {
                    Pointers.Add(reader.ReadUInt32());
                    RealPointers.Add(0);
                }

                uint numMeshWords = reader.ReadUInt32();
                uint bytesToRead  = numMeshWords * 2;
                uint bytesRead    = 0;

                logger.Info("Wad mesh data size: " + bytesToRead);
                while (bytesRead < bytesToRead)
                {
                    var startOfMesh = (uint)reader.BaseStream.Position;

                    wad_mesh mesh = new wad_mesh();
                    mesh.Polygons = new List <wad_polygon>();
                    mesh.Vertices = new List <wad_vertex>();
                    mesh.Normals  = new List <wad_vertex>();
                    mesh.Shades   = new List <short>();

                    mesh.SphereX = reader.ReadInt16();
                    mesh.SphereY = reader.ReadInt16();
                    mesh.SphereZ = reader.ReadInt16();
                    mesh.Radius  = reader.ReadUInt16();
                    mesh.Unknown = reader.ReadUInt16();

                    var numVertices = reader.ReadUInt16();
                    mesh.NumVertices = numVertices;

                    var xMin = Int32.MaxValue;
                    var yMin = Int32.MaxValue;
                    var zMin = Int32.MaxValue;
                    var xMax = Int32.MinValue;
                    var yMax = Int32.MinValue;
                    var zMax = Int32.MinValue;

                    for (var i = 0; i < numVertices; i++)
                    {
                        var v = new wad_vertex();
                        v.X = reader.ReadInt16();
                        v.Y = reader.ReadInt16();
                        v.Z = reader.ReadInt16();

                        if (v.X < xMin)
                        {
                            xMin = v.X;
                        }
                        if (-v.Y < yMin)
                        {
                            yMin = -v.Y;
                        }
                        if (v.Z < zMin)
                        {
                            zMin = v.Z;
                        }

                        if (v.X > xMax)
                        {
                            xMax = v.X;
                        }
                        if (-v.Y > yMax)
                        {
                            yMax = -v.Y;
                        }
                        if (v.Z > zMax)
                        {
                            zMax = v.Z;
                        }

                        mesh.Vertices.Add(v);
                    }

                    mesh.Minimum = new Vector3(xMin, yMin, zMin);
                    mesh.Maximum = new Vector3(xMax, yMax, zMax);

                    short numNormals = reader.ReadInt16();
                    mesh.NumNormals = numNormals;
                    if (numNormals > 0)
                    {
                        for (var i = 0; i < numNormals; i++)
                        {
                            var n = new wad_vertex();
                            n.X = reader.ReadInt16();
                            n.Y = reader.ReadInt16();
                            n.Z = reader.ReadInt16();
                            mesh.Normals.Add(n);
                        }
                    }
                    else
                    {
                        for (var i = 0; i < -numNormals; i++)
                        {
                            mesh.Shades.Add(reader.ReadInt16());
                        }
                    }

                    ushort numPolygons = reader.ReadUInt16();
                    mesh.NumPolygons = numPolygons;
                    ushort numQuads = 0;
                    for (var i = 0; i < numPolygons; i++)
                    {
                        var poly = new wad_polygon();
                        poly.Shape = reader.ReadUInt16();
                        poly.V1    = reader.ReadUInt16();
                        poly.V2    = reader.ReadUInt16();
                        poly.V3    = reader.ReadUInt16();
                        if (poly.Shape == 9)
                        {
                            poly.V4 = reader.ReadUInt16();
                        }
                        poly.Texture    = reader.ReadUInt16();
                        poly.Attributes = reader.ReadByte();
                        poly.Unknown    = reader.ReadByte();

                        if (poly.Shape == 9)
                        {
                            numQuads++;
                        }
                        mesh.Polygons.Add(poly);
                    }

                    if (numQuads % 2 != 0)
                    {
                        reader.ReadInt16();
                    }

                    var endPosition = (uint)reader.BaseStream.Position;
                    bytesRead += endPosition - startOfMesh;
                    Meshes.Add(mesh);

                    // Update the real pointers
                    for (int k = 0; k < Pointers.Count; k++)
                    {
                        if (Pointers[k] == bytesRead)
                        {
                            RealPointers[k] = (uint)Meshes.Count;
                        }
                    }
                }

                var numAnimations = reader.ReadUInt32();
                logger.Info("Wad animations: " + numAnimations);
                for (var i = 0; i < numAnimations; i++)
                {
                    var anim = new wad_animation();
                    anim.KeyFrameOffset  = reader.ReadUInt32();
                    anim.FrameDuration   = reader.ReadByte();
                    anim.KeyFrameSize    = reader.ReadByte();
                    anim.StateId         = reader.ReadUInt16();
                    anim.Speed           = reader.ReadInt32();
                    anim.Accel           = reader.ReadInt32();
                    anim.SpeedLateral    = reader.ReadInt32();
                    anim.AccelLateral    = reader.ReadInt32();
                    anim.FrameStart      = reader.ReadUInt16();
                    anim.FrameEnd        = reader.ReadUInt16();
                    anim.NextAnimation   = reader.ReadUInt16();
                    anim.NextFrame       = reader.ReadUInt16();
                    anim.NumStateChanges = reader.ReadUInt16();
                    anim.ChangesIndex    = reader.ReadUInt16();
                    anim.NumCommands     = reader.ReadUInt16();
                    anim.CommandOffset   = reader.ReadUInt16();

                    Animations.Add(anim);
                }

                var numChanges = reader.ReadUInt32();
                logger.Info("Wad animation state changes: " + numChanges);
                for (var i = 0; i < numChanges; i++)
                {
                    var change = new wad_state_change();
                    change.StateId         = reader.ReadUInt16();
                    change.NumDispatches   = reader.ReadUInt16();
                    change.DispatchesIndex = reader.ReadUInt16();
                    Changes.Add(change);
                }

                var numDispatches = reader.ReadUInt32();
                logger.Info("Wad animation dispatches: " + numDispatches);
                for (var i = 0; i < numDispatches; i++)
                {
                    var anim = new wad_anim_dispatch();
                    anim.Low           = reader.ReadInt16();
                    anim.High          = reader.ReadInt16();
                    anim.NextAnimation = reader.ReadInt16();
                    anim.NextFrame     = reader.ReadInt16();
                    Dispatches.Add(anim);
                }

                var numCommands = reader.ReadUInt32();
                logger.Info("Wad animation commands: " + numCommands);
                for (var i = 0; i < numCommands; i++)
                {
                    Commands.Add(reader.ReadInt16());
                }

                var numLinks = reader.ReadUInt32();
                logger.Info("Wad animation links: " + numLinks);
                for (var i = 0; i < numLinks; i++)
                {
                    Links.Add(reader.ReadInt32());
                }

                var numFrames = reader.ReadUInt32();
                logger.Info("Wad animation frames: " + numFrames);
                for (int i = 0; i < numFrames; i++)
                {
                    KeyFrames.Add(reader.ReadInt16());
                }

                var numMoveables = reader.ReadUInt32();
                logger.Info("Wad objects (moveables): " + numMoveables);
                for (var i = 0; i < numMoveables; i++)
                {
                    var moveable = new wad_moveable();
                    moveable.ObjectID       = reader.ReadUInt32();
                    moveable.NumPointers    = reader.ReadUInt16();
                    moveable.PointerIndex   = reader.ReadUInt16();
                    moveable.LinksIndex     = reader.ReadUInt32();
                    moveable.KeyFrameOffset = reader.ReadUInt32();
                    moveable.AnimationIndex = reader.ReadInt16();
                    Moveables.Add(moveable);
                }

                var numStaticMeshes = reader.ReadUInt32();
                logger.Info("Wad static meshes: " + numStaticMeshes);
                for (var i = 0; i < numStaticMeshes; i++)
                {
                    var staticMesh = new wad_static_mesh();
                    staticMesh.ObjectId      = reader.ReadUInt32();
                    staticMesh.PointersIndex = reader.ReadUInt16();
                    staticMesh.VisibilityX1  = reader.ReadInt16();
                    staticMesh.VisibilityX2  = reader.ReadInt16();
                    staticMesh.VisibilityY1  = reader.ReadInt16();
                    staticMesh.VisibilityY2  = reader.ReadInt16();
                    staticMesh.VisibilityZ1  = reader.ReadInt16();
                    staticMesh.VisibilityZ2  = reader.ReadInt16();
                    staticMesh.CollisionX1   = reader.ReadInt16();
                    staticMesh.CollisionX2   = reader.ReadInt16();
                    staticMesh.CollisionY1   = reader.ReadInt16();
                    staticMesh.CollisionY2   = reader.ReadInt16();
                    staticMesh.CollisionZ1   = reader.ReadInt16();
                    staticMesh.CollisionZ2   = reader.ReadInt16();
                    staticMesh.Flags         = reader.ReadUInt16();
                    Statics.Add(staticMesh);
                }

                reader.Close();
                logger.Info("Wad loaded successfully.");
            }

            // Read sprites
            logger.Info("Reading sprites (swd file) associated with wad.");
            using (var readerSprites = new BinaryReaderEx(new FileStream(BasePath + Path.DirectorySeparatorChar + BaseName + ".swd",
                                                                         FileMode.Open, FileAccess.Read, FileShare.Read)))
            {
                // Version
                readerSprites.ReadUInt32();

                var numSpritesTextures = readerSprites.ReadUInt32();
                logger.Info("Sprites: " + numSpritesTextures);

                //Sprite texture array
                for (var i = 0; i < numSpritesTextures; i++)
                {
                    var buffer = readerSprites.ReadBytes(16);

                    var spriteTexture = new wad_sprite_texture
                    {
                        Tile       = buffer[2],
                        X          = buffer[0],
                        Y          = buffer[1],
                        Width      = buffer[5],
                        Height     = buffer[7],
                        LeftSide   = buffer[0],
                        TopSide    = buffer[1],
                        RightSide  = (short)(buffer[0] + buffer[5] + 1),
                        BottomSide = (short)(buffer[1] + buffer[7] + 1)
                    };

                    SpriteTextures.Add(spriteTexture);
                }

                // Sprites size
                var spriteDataSize = readerSprites.ReadInt32();
                SpriteData = readerSprites.ReadBytes(spriteDataSize);

                var numSequences = readerSprites.ReadUInt32();
                logger.Info("Sprite sequences: " + numSequences);

                // Sprite sequences
                for (var i = 0; i < numSequences; i++)
                {
                    var sequence = new wad_sprite_sequence();
                    sequence.ObjectID       = readerSprites.ReadInt32();
                    sequence.NegativeLength = readerSprites.ReadInt16();
                    sequence.Offset         = readerSprites.ReadInt16();
                    SpriteSequences.Add(sequence);
                }
            }

            // Read WAS
            using (var reader = new StreamReader(File.OpenRead(BasePath + Path.DirectorySeparatorChar + BaseName + ".was")))
            {
                while (!reader.EndOfStream)
                {
                    LegacyNames.Add(reader.ReadLine().Split(':')[0].Replace(" ", "").Replace("EXTRA0", "EXTRA"));
                }
            }
        }
예제 #2
0
        internal static WadMoveable ConvertTr4MoveableToWadMoveable(Wad2 wad, Tr4Wad oldWad, int moveableIndex,
                                                                    Dictionary <int, WadTexture> textures)
        {
            wad_moveable oldMoveable = oldWad.Moveables[moveableIndex];
            var          newId       = new WadMoveableId(oldMoveable.ObjectID);

            // A workaround to find out duplicated item IDs produced by StrPix unpatched for v130 wads.
            // In such case, a legacy name is used to guess real item ID, if this fails - broken item is filtered out.
            if (wad.Moveables.ContainsKey(newId))
            {
                var message = "Duplicated moveable ID " + oldMoveable.ObjectID + " was identified while loading " + oldWad.BaseName + ". ";
                if (oldWad.LegacyNames.Count - oldWad.Statics.Count < moveableIndex)
                {
                    logger.Warn(message + "Can't restore real ID by name, name table is too short. Ignoring moveable.");
                    return(null);
                }

                bool isMoveable;
                var  guessedId = TrCatalog.GetItemIndex(TRVersion.Game.TR4, oldWad.LegacyNames[moveableIndex], out isMoveable);

                if (isMoveable && guessedId.HasValue)
                {
                    newId = new WadMoveableId(guessedId.Value);
                    if (wad.Moveables.ContainsKey(newId))
                    {
                        logger.Warn(message + "Can't restore real ID by name, name table is inconsistent. Ignoring moveable.");
                        return(null);
                    }
                    else
                    {
                        logger.Warn(message + "Successfully restored real ID by name.");
                    }
                }
                else
                {
                    logger.Warn(message + "Can't find provided name in catalog. Ignoring moveable.");
                    return(null);
                }
            }

            WadMoveable newMoveable = new WadMoveable(newId);
            var         frameBases  = new Dictionary <WadAnimation, ushort[]>();

            // Load meshes
            var meshes = new List <WadMesh>();

            for (int j = 0; j < oldMoveable.NumPointers; j++)
            {
                meshes.Add(ConvertTr4MeshToWadMesh(wad, oldWad, textures,
                                                   oldWad.Meshes[(int)oldWad.RealPointers[oldMoveable.PointerIndex + j]],
                                                   (int)oldMoveable.ObjectID));
            }

            // Build the skeleton
            var root = new WadBone();

            root.Name        = "bone_0_root";
            root.Parent      = null;
            root.Translation = Vector3.Zero;
            root.Mesh        = meshes[0];

            newMoveable.Bones.Add(root);

            for (int j = 0; j < oldMoveable.NumPointers - 1; j++)
            {
                WadBone bone = new WadBone();
                bone.Name        = "bone_" + (j + 1).ToString();
                bone.Parent      = null;
                bone.Translation = Vector3.Zero;
                bone.Mesh        = meshes[j + 1];
                newMoveable.Bones.Add(bone);
            }

            for (int mi = 0; mi < (oldMoveable.NumPointers - 1); mi++)
            {
                int j = mi + 1;

                var opcode = (WadLinkOpcode)oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4)];
                int linkX  = oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 1];
                int linkY  = -oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 2];
                int linkZ  = oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 3];

                newMoveable.Bones[j].OpCode      = opcode;
                newMoveable.Bones[j].Translation = new Vector3(linkX, linkY, linkZ);
            }

            // Convert animations
            int numAnimations = 0;
            int nextMoveable  = oldWad.GetNextMoveableWithAnimations(moveableIndex);

            if (nextMoveable == -1)
            {
                numAnimations = oldWad.Animations.Count - oldMoveable.AnimationIndex;
            }
            else
            {
                numAnimations = oldWad.Moveables[nextMoveable].AnimationIndex - oldMoveable.AnimationIndex;
            }

            for (int j = 0; j < numAnimations; j++)
            {
                if (oldMoveable.AnimationIndex == -1)
                {
                    break;
                }

                wad_animation oldAnimation = oldWad.Animations[j + oldMoveable.AnimationIndex];
                WadAnimation  newAnimation = new WadAnimation();
                newAnimation.StateId       = oldAnimation.StateId;
                newAnimation.FrameRate     = oldAnimation.FrameDuration;
                newAnimation.NextAnimation = (ushort)(oldAnimation.NextAnimation - oldMoveable.AnimationIndex);
                newAnimation.NextFrame     = oldAnimation.NextFrame;
                newAnimation.Name          = TrCatalog.GetAnimationName(TRVersion.Game.TR4, oldMoveable.ObjectID, (uint)j);

                // Fix wadmerger/wad format bug with inverted frame start/end on single-frame anims
                ushort newFrameStart = oldAnimation.FrameStart < oldAnimation.FrameEnd ? oldAnimation.FrameStart : oldAnimation.FrameEnd;
                ushort newFrameEnd   = oldAnimation.FrameStart < oldAnimation.FrameEnd ? oldAnimation.FrameEnd : newFrameStart;
                newAnimation.EndFrame = (ushort)(newFrameEnd - newFrameStart);

                for (int k = 0; k < oldAnimation.NumStateChanges; k++)
                {
                    WadStateChange   sc    = new WadStateChange();
                    wad_state_change wadSc = oldWad.Changes[(int)oldAnimation.ChangesIndex + k];
                    sc.StateId = (ushort)wadSc.StateId;

                    for (int n = 0; n < wadSc.NumDispatches; n++)
                    {
                        WadAnimDispatch   ad    = new WadAnimDispatch();
                        wad_anim_dispatch wadAd = oldWad.Dispatches[(int)wadSc.DispatchesIndex + n];

                        ad.InFrame       = (ushort)(wadAd.Low - newFrameStart);
                        ad.OutFrame      = (ushort)(wadAd.High - newFrameStart);
                        ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.AnimationIndex) % numAnimations);
                        ad.NextFrame     = (ushort)wadAd.NextFrame;

                        sc.Dispatches.Add(ad);
                    }

                    newAnimation.StateChanges.Add(sc);
                }

                if (oldAnimation.NumCommands < oldWad.Commands.Count)
                {
                    int lastCommand = oldAnimation.CommandOffset;

                    for (int k = 0; k < oldAnimation.NumCommands; k++)
                    {
                        short commandType = oldWad.Commands[lastCommand];

                        WadAnimCommand command = new WadAnimCommand();
                        command.Type = (WadAnimCommandType)commandType;
                        switch (command.Type)
                        {
                        case WadAnimCommandType.SetPosition:
                            command.Parameter1 = (short)oldWad.Commands[lastCommand + 1];
                            command.Parameter2 = (short)oldWad.Commands[lastCommand + 2];
                            command.Parameter3 = (short)oldWad.Commands[lastCommand + 3];
                            lastCommand       += 4;
                            break;

                        case WadAnimCommandType.SetJumpDistance:
                            command.Parameter1 = (short)oldWad.Commands[lastCommand + 1];
                            command.Parameter2 = (short)oldWad.Commands[lastCommand + 2];
                            lastCommand       += 3;
                            break;

                        case WadAnimCommandType.EmptyHands:
                        case WadAnimCommandType.KillEntity:
                            lastCommand += 1;
                            break;

                        case WadAnimCommandType.PlaySound:
                        case WadAnimCommandType.FlipEffect:
                            command.Parameter1 = (short)(oldWad.Commands[lastCommand + 1] - newFrameStart);
                            command.Parameter2 = (short)oldWad.Commands[lastCommand + 2];

                            // For single-frame anims, clamp frame number to first frame (another fix for WM/wad format range inversion bug)
                            if (newAnimation.EndFrame == 0 && command.Parameter1 > 0)
                            {
                                command.Parameter1 = 0;
                            }

                            lastCommand += 3;
                            break;

                        default:     // Ignore invalid anim commands (see for example karnak.wad)
                            logger.Warn("Invalid anim command " + commandType);
                            goto ExitForLoop;
                        }

                        newAnimation.AnimCommands.Add(command);
                    }
ExitForLoop:
                    ;
                }

                int  frames    = (int)oldAnimation.KeyFrameOffset / 2;
                uint numFrames = 0;
                if (oldAnimation.KeyFrameSize != 0)
                {
                    if ((j + oldMoveable.AnimationIndex) == (oldWad.Animations.Count - 1))
                    {
                        numFrames = ((uint)(2 * oldWad.KeyFrames.Count) - oldAnimation.KeyFrameOffset) / (uint)(2 * oldAnimation.KeyFrameSize);
                    }
                    else
                    {
                        numFrames = (oldWad.Animations[oldMoveable.AnimationIndex + j + 1].KeyFrameOffset - oldAnimation.KeyFrameOffset) / (uint)(2 * oldAnimation.KeyFrameSize);
                    }
                }

                for (int f = 0; f < numFrames; f++)
                {
                    WadKeyFrame frame        = new WadKeyFrame();
                    int         startOfFrame = frames;

                    frame.BoundingBox = new BoundingBox(new Vector3(oldWad.KeyFrames[frames],
                                                                    -oldWad.KeyFrames[frames + 2],
                                                                    oldWad.KeyFrames[frames + 4]),
                                                        new Vector3(oldWad.KeyFrames[frames + 1],
                                                                    -oldWad.KeyFrames[frames + 3],
                                                                    oldWad.KeyFrames[frames + 5]));
                    frames += 6;

                    frame.Offset = new Vector3(oldWad.KeyFrames[frames],
                                               (short)(-oldWad.KeyFrames[frames + 1]),
                                               oldWad.KeyFrames[frames + 2]);
                    frames += 3;

                    for (int n = 0; n < oldMoveable.NumPointers; n++)
                    {
                        frame.Angles.Add(WadKeyFrameRotation.FromTrAngle(ref frames, oldWad.KeyFrames, false, true));
                    }
                    if ((frames - startOfFrame) < oldAnimation.KeyFrameSize)
                    {
                        frames += ((int)oldAnimation.KeyFrameSize - (frames - startOfFrame));
                    }

                    newAnimation.KeyFrames.Add(frame);
                }

                // New velocities
                float acceleration = oldAnimation.Accel / 65536.0f;
                newAnimation.StartVelocity = oldAnimation.Speed / 65536.0f;

                float lateralAcceleration = oldAnimation.AccelLateral / 65536.0f;
                newAnimation.StartLateralVelocity = oldAnimation.SpeedLateral / 65536.0f;

                if (newAnimation.KeyFrames.Count != 0 && newAnimation.FrameRate != 0)
                {
                    newAnimation.EndVelocity = newAnimation.StartVelocity + acceleration *
                                               (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate;
                    newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity + lateralAcceleration *
                                                      (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate;
                }
                else
                {
                    // Basic foolproofness for potentially broken animations
                    newAnimation.EndVelocity        = newAnimation.StartVelocity;
                    newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity;
                }

                // Deduce real maximum frame number, based on interpolation and keyframes.
                // We need to refer this value in NextFrame-related fixes (below) because of epic WadMerger bug,
                // which incorrectly calculates NextFrame and "steals" last frame from every custom animation.
                ushort maxFrameCount = (ushort)((newAnimation.FrameRate == 1 || numFrames <= 2) ? numFrames : ((numFrames - 1) * newAnimation.FrameRate) + 1);

                // Also correct animation out-point
                if (newAnimation.EndFrame >= maxFrameCount)
                {
                    newAnimation.EndFrame = (ushort)(maxFrameCount - 1);
                }

                frameBases.Add(newAnimation, new ushort[] { newFrameStart, (ushort)(maxFrameCount - 1) });
                newMoveable.Animations.Add(newAnimation);
            }

            for (int i = 0; i < newMoveable.Animations.Count; i++)
            {
                var animation = newMoveable.Animations[i];

                if (animation.KeyFrames.Count == 0)
                {
                    animation.EndFrame = 0;
                }

                // HACK: this fixes some invalid NextAnimations values
                animation.NextAnimation %= (ushort)newMoveable.Animations.Count;

                newMoveable.Animations[i] = animation;
            }

            for (int i = 0; i < newMoveable.Animations.Count; i++)
            {
                var animation = newMoveable.Animations[i];

                // HACK: this fixes some invalid NextFrame values
                if (frameBases[newMoveable.Animations[animation.NextAnimation]][0] != 0)
                {
                    animation.NextFrame -= frameBases[newMoveable.Animations[animation.NextAnimation]][0];
                    if (animation.NextFrame > frameBases[newMoveable.Animations[animation.NextAnimation]][1])
                    {
                        animation.NextFrame = frameBases[newMoveable.Animations[animation.NextAnimation]][1];
                    }
                }

                foreach (var stateChange in animation.StateChanges)
                {
                    for (int j = 0; j < stateChange.Dispatches.Count; ++j)
                    {
                        WadAnimDispatch animDispatch = stateChange.Dispatches[j];

                        // HACK: Probably WadMerger's bug
                        if (animDispatch.NextAnimation > 32767)
                        {
                            animDispatch.NextAnimation = 0;
                            animDispatch.NextFrame     = 0;
                            continue;
                        }

                        if (frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0] != 0)
                        {
                            // HACK: In some cases dispatches have invalid NextFrame.
                            // From tests it seems that's ok to make NextFrame equal to max frame number.
                            animDispatch.NextFrame -= frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0];
                            if (animDispatch.NextFrame > frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1])
                            {
                                animDispatch.NextFrame = frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1];
                            }
                        }
                        stateChange.Dispatches[j] = animDispatch;
                    }
                }
            }

            wad.Moveables.Add(newMoveable.Id, newMoveable);
            return(newMoveable);
        }