/**
         * Commands are packed into CmdType + Params, but these are just
         * translated straight back into normal TRAnimCommands.
         */
        private void UnpackAnimCommands(PackedAnimation packedAnimation)
        {
            if (packedAnimation.Commands.Count == 0)
            {
                return;
            }

            List <TRAnimCommand> levelAnimCommands = Level.AnimCommands.ToList();

            packedAnimation.Animation.AnimCommand = (ushort)levelAnimCommands.Count;
            foreach (PackedAnimationCommand cmd in packedAnimation.Commands.Values)
            {
                levelAnimCommands.Add(new TRAnimCommand {
                    Value = (short)cmd.Command
                });
                foreach (short param in cmd.Params)
                {
                    levelAnimCommands.Add(new TRAnimCommand {
                        Value = param
                    });
                }
            }

            // Save back to the level
            Level.AnimCommands    = levelAnimCommands.ToArray();
            Level.NumAnimCommands = (uint)levelAnimCommands.Count;
        }
        private void PackAnimSounds(PackedAnimation packedAnimation)
        {
            foreach (PackedAnimationCommand cmd in packedAnimation.Commands.Values)
            {
                if (cmd.Command == TR2AnimCommand.PlaySound)
                {
                    int   soundMapIndex     = cmd.Params[1] & 0x3fff;
                    short soundDetailsIndex = Level.SoundMap[soundMapIndex];
                    packedAnimation.Sound.SoundMapIndices[soundMapIndex] = soundDetailsIndex;
                    if (soundDetailsIndex != -1)
                    {
                        TRSoundDetails soundDetails = Level.SoundDetails[soundDetailsIndex];
                        packedAnimation.Sound.SoundDetails[soundDetailsIndex] = soundDetails;

                        uint[] sampleIndices = new uint[soundDetails.NumSounds];
                        for (int i = 0; i < soundDetails.NumSounds; i++)
                        {
                            sampleIndices[i] = Level.SampleIndices[(ushort)(soundDetails.Sample + i)];
                        }

                        packedAnimation.Sound.SampleIndices[soundDetails.Sample] = sampleIndices;
                    }
                }
            }
        }
        private int UnpackAnimation(PackedAnimation animation)
        {
            List <TRAnimation> levelAnimations = Level.Animations.ToList();

            levelAnimations.Add(animation.Animation);
            Level.Animations = levelAnimations.ToArray();
            Level.NumAnimations++;

            return(levelAnimations.Count - 1);
        }
        public override void Import()
        {
            Dictionary <int, PackedAnimation> animations = Definition.Animations;
            bool firstAnimationConfigured  = false;
            Dictionary <int, int> indexMap = new Dictionary <int, int>();

            foreach (int oldAnimationIndex in animations.Keys)
            {
                PackedAnimation packedAnimation = animations[oldAnimationIndex];
                UnpackStateChanges(packedAnimation);
                UnpackAnimSounds(packedAnimation);
                UnpackAnimCommands(packedAnimation);

                int newAnimationIndex = UnpackAnimation(packedAnimation);
                indexMap[oldAnimationIndex] = newAnimationIndex;

                if (!firstAnimationConfigured)
                {
                    Definition.Model.Animation = (ushort)newAnimationIndex;
                    firstAnimationConfigured   = true;
                }
            }

            // Re-map the NextAnimations of each of the animation and dispatches
            // now we know the indices of each of the newly inserted animations.
            //List<short> missingAnimations = new List<short>();
            foreach (PackedAnimation packedAnimation in animations.Values)
            {
                packedAnimation.Animation.NextAnimation = (ushort)indexMap[packedAnimation.Animation.NextAnimation];
                foreach (TRAnimDispatch dispatch in packedAnimation.AnimationDispatches.Values)
                {
                    if (indexMap.ContainsKey(dispatch.NextAnimation))
                    {
                        dispatch.NextAnimation = (short)indexMap[dispatch.NextAnimation];
                    }
                    else
                    {
                        // I think this happens for such things as returning to a default stance e.g.
                        // after dismounting the Snowmobile, so it will be different in every level (e.g.
                        // Lara default animations).
                    }
                }
            }

            // Inserting SampleIndices will break the game unless they are sorted numerically
            // so handle this outwith the main animation insertion loop for ease.
            ResortSoundIndices();

            ImportAnimationFrames();
        }
        private void PackStateChanges(PackedAnimation packedAnimation, TRAnimation animation)
        {
            for (int stateChangeIndex = 0; stateChangeIndex < animation.NumStateChanges; stateChangeIndex++)
            {
                TRStateChange stateChange = Level.StateChanges[animation.StateChangeOffset + stateChangeIndex];
                packedAnimation.StateChanges.Add(stateChange);

                int dispatchOffset = stateChange.AnimDispatch;
                for (int i = 0; i < stateChange.NumAnimDispatches; i++, dispatchOffset++)
                {
                    if (!packedAnimation.AnimationDispatches.ContainsKey(dispatchOffset))
                    {
                        TRAnimDispatch dispatch = Level.AnimDispatches[dispatchOffset];
                        packedAnimation.AnimationDispatches[dispatchOffset] = dispatch;
                    }
                }
            }
        }
        private void UnpackStateChanges(PackedAnimation packedAnimation)
        {
            if (packedAnimation.Animation.NumStateChanges == 0)
            {
                if (packedAnimation.AnimationDispatches.Count != 0)
                {
                    throw new Exception();
                }
                return;
            }

            // Import the AnimDispatches first, noting their new indices
            List <TRAnimDispatch> animDispatches = Level.AnimDispatches.ToList();
            Dictionary <int, int> indexMap       = new Dictionary <int, int>();

            foreach (int oldDispatchIndex in packedAnimation.AnimationDispatches.Keys)
            {
                TRAnimDispatch dispatch = packedAnimation.AnimationDispatches[oldDispatchIndex];
                indexMap[oldDispatchIndex] = animDispatches.Count;
                animDispatches.Add(dispatch);
                // The dispatch's NextAnimation will need to be remapped, but this is handled in Import above
                // once all animations are in place.
            }

            // The animation's StateChangeOffset will be the current length of level StateChanges
            List <TRStateChange> stateChanges = Level.StateChanges.ToList();

            packedAnimation.Animation.StateChangeOffset = (ushort)stateChanges.Count;

            // Import Each state change, re-mapping AnimDispatch to new index
            foreach (TRStateChange stateChange in packedAnimation.StateChanges)
            {
                stateChange.AnimDispatch = (ushort)indexMap[stateChange.AnimDispatch];
                stateChanges.Add(stateChange);
            }

            // Save back to the level
            Level.AnimDispatches    = animDispatches.ToArray();
            Level.NumAnimDispatches = (uint)animDispatches.Count;

            Level.StateChanges    = stateChanges.ToArray();
            Level.NumStateChanges = (uint)stateChanges.Count;
        }
        /**
         * SampleIndices has to remain in numerical order, but rather than dealing with it after inserting
         * each new sample, ImportAnimations will handle reorganising the list and remapping SoundDetails
         * as necessary.
         */
        private void UnpackAnimSounds(PackedAnimation packedAnimation)
        {
            _soundUnpacker.Unpack(packedAnimation.Sound, Level, false);
            IReadOnlyDictionary <int, int> soundIndexMap = _soundUnpacker.SoundIndexMap;

            // Change the Params[1] value of each PlaySound AnimCommand to point to the
            // new index in SoundMap.
            foreach (PackedAnimationCommand cmd in packedAnimation.Commands.Values)
            {
                if (cmd.Command == TR2AnimCommand.PlaySound)
                {
                    int oldSoundMapIndex = cmd.Params[1] & 0x3fff;
                    int newSoundMapIndex = soundIndexMap[oldSoundMapIndex];

                    int param = cmd.Params[1] & ~oldSoundMapIndex;
                    param        |= newSoundMapIndex;
                    cmd.Params[1] = (short)param;
                }
            }
        }
        private void PackAnimCommands(PackedAnimation packedAnimation, TRAnimation animation)
        {
            int cmdOffset = animation.AnimCommand;

            for (int i = 0; i < animation.NumAnimCommands; i++)
            {
                int           cmdIndex = cmdOffset++;
                TRAnimCommand cmd      = Level.AnimCommands[cmdIndex];

                int paramCount;
                switch ((TR2AnimCommand)cmd.Value)
                {
                case TR2AnimCommand.SetPosition:
                    paramCount = 3;
                    break;

                case TR2AnimCommand.JumpDistance:
                case TR2AnimCommand.PlaySound:
                case TR2AnimCommand.FlipEffect:
                    paramCount = 2;
                    break;

                default:
                    paramCount = 0;
                    break;
                }

                short[] paramArr = new short[paramCount];
                for (int j = 0; j < paramCount; j++)
                {
                    paramArr[j] = Level.AnimCommands[cmdOffset++].Value;
                }

                packedAnimation.Commands[cmdIndex] = new PackedAnimationCommand
                {
                    Command = (TR2AnimCommand)cmd.Value,
                    Params  = paramArr
                };
            }
        }
        public override void Export()
        {
            Definition.Animations = new Dictionary <int, PackedAnimation>();

            int endAnimation = GetModelAnimationCount(Level, Definition.Model) + Definition.Model.Animation;

            for (int animationIndex = Definition.Model.Animation; animationIndex < endAnimation; animationIndex++)
            {
                TRAnimation     animation       = Level.Animations[animationIndex];
                PackedAnimation packedAnimation = new PackedAnimation
                {
                    Animation = animation,
                };
                Definition.Animations[animationIndex] = packedAnimation;

                PackStateChanges(packedAnimation, animation);
                PackAnimCommands(packedAnimation, animation);
                PackAnimSounds(packedAnimation);
            }

            ExportAnimationFrames();
        }