diff --git a/TRLevelControl/Build/TRModelBuilder.cs b/TRLevelControl/Build/TRModelBuilder.cs index 86aee054b..985f52186 100644 --- a/TRLevelControl/Build/TRModelBuilder.cs +++ b/TRLevelControl/Build/TRModelBuilder.cs @@ -93,7 +93,7 @@ private void ReadAnimations(TRLevelReader reader) placeholder.FrameOffset = reader.ReadUInt32(); animation.FrameRate = reader.ReadByte(); - animation.FrameSize = reader.ReadByte(); + placeholder.FrameSize = reader.ReadByte(); animation.StateID = reader.ReadUInt16(); animation.Speed = reader.ReadFixed32(); animation.Accel = reader.ReadFixed32(); @@ -113,9 +113,6 @@ private void ReadAnimations(TRLevelReader reader) placeholder.ChangeOffset = reader.ReadUInt16(); placeholder.NumAnimCommands = reader.ReadUInt16(); placeholder.AnimCommand = reader.ReadUInt16(); - - animation.Changes = new(); - animation.Commands = new(); } } @@ -223,16 +220,13 @@ private TRModel BuildModel(PlaceholderModel placeholder) ID = placeholder.ID, NumMeshes = placeholder.NumMeshes, StartingMesh = placeholder.StartingMesh, - MeshTree = placeholder.MeshTree, - FrameOffset = placeholder.FrameOffset, - Animation = placeholder.Animation, }; // Everything has a dummy mesh tree, so load one less than the mesh count int treePointer = (int)placeholder.MeshTree / sizeof(int); - for (int j = 0; j < placeholder.NumMeshes - 1; j++) + for (int i = 0; i < placeholder.NumMeshes - 1; i++) { - model.MeshTrees.Add(_trees[treePointer + j]); + model.MeshTrees.Add(_trees[treePointer + i]); } for (int i = 0; i < placeholder.AnimCount; i++) @@ -269,12 +263,49 @@ private TRAnimation BuildAnimation(PlaceholderModel placeholderModel, int animIn animation.FrameEnd -= animation.FrameStart; animation.FrameStart = 0; - int offset = (int)placeholderAnimation.FrameOffset / sizeof(short); - int nextOffset = globalAnimIndex == _animations.Count - 1 - ? _frames.Count - : (int)_placeholderAnimations[globalAnimIndex + 1].FrameOffset / sizeof(short); + animation.Frames = new(); + int frameIndex = (int)placeholderAnimation.FrameOffset / sizeof(short); + uint numAnimFrames = 0; + + if (placeholderAnimation.FrameSize == 0) + { + if (_version == TRGameVersion.TR1) + { + numAnimFrames = (uint)(Math.Ceiling((animation.FrameEnd - animation.FrameStart) / (float)animation.FrameRate) + 1); + } + } + else + { + uint nextOffset = globalAnimIndex == _animations.Count - 1 + ? (uint)(_frames.Count * sizeof(short)) + : _placeholderAnimations[globalAnimIndex + 1].FrameOffset; + + numAnimFrames = (nextOffset - placeholderAnimation.FrameOffset) / (uint)(sizeof(short) * placeholderAnimation.FrameSize); + if (numAnimFrames == 0) + { + // TR4 Lara anim 63 for example. Allow it to be observed, it will be zeroed on write. + _observer?.OnEmptyAnimFramesRead(globalAnimIndex, placeholderAnimation.FrameSize); + } + } - animation.Frames = _frames.GetRange(offset, nextOffset - offset); + for (int i = 0; i < numAnimFrames; i++) + { + int frameIndexStart = frameIndex; + animation.Frames.Add(BuildFrame(ref frameIndex, placeholderModel.NumMeshes)); + + if (_version != TRGameVersion.TR1) + { + // All frames in an animation are aligned to the size of the largest one in TR2+. In addition, TR5 has frames aligned to 2 shorts, + // so we need to ensure the final frame is also padded. The padding is random, so it can be observed. Zeroed on write. + int padding = Math.Max(0, placeholderAnimation.FrameSize - frameIndex + frameIndexStart); + if (_version == TRGameVersion.TR5 && padding == 0 && _frames.Count % 2 == 0 && frameIndex == _frames.Count - 1) + { + padding = 1; + } + _observer?.OnFramePaddingRead(globalAnimIndex, i, _frames.GetRange(frameIndex, padding)); + frameIndex += padding; + } + } return animation; } @@ -338,35 +369,69 @@ private List BuildCommands(int parentAnimIndex) } else { - for (int i = 0; i < placeholderAnimation.NumAnimCommands; i++) + for (int i = 0; i < placeholderAnimation.NumAnimCommands && offset < _commands.Count; i++) { - TRAnimCommand command = new() - { - Type = (TRAnimCommandType)_commands[offset++] - }; - switch (command.Type) + TRAnimCommand command; + TRAnimCommandType type = (TRAnimCommandType)_commands[offset++]; + switch (type) { case TRAnimCommandType.SetPosition: - command.Params = new() + command = new TRSetPositionCommand { - _commands[offset++], // X - _commands[offset++], // Y - _commands[offset++], // Z + X = _commands[offset++], + Y = _commands[offset++], + Z = _commands[offset++], }; break; case TRAnimCommandType.JumpDistance: - command.Params = new() + command = new TRJumpDistanceCommand { - _commands[offset++], // VerticalSpeed - _commands[offset++], // HorizontalSpeed + VerticalSpeed = _commands[offset++], + HorizontalSpeed = _commands[offset++], }; break; + case TRAnimCommandType.EmptyHands: + command = new TREmptyHandsCommand(); + break; + case TRAnimCommandType.Kill: + command = new TRKillCommand(); + break; case TRAnimCommandType.PlaySound: + short sfxFrame = (short)(_commands[offset++] - animation.FrameStart); + short sfxID = _commands[offset++]; + command = new TRSFXCommand + { + FrameNumber = sfxFrame, + SoundID = (short)(sfxID & 0x3FFF), + Environment = _version == TRGameVersion.TR1 ? TRSFXEnvironment.Any : (TRSFXEnvironment)(sfxID & 0xC000) + }; + break; case TRAnimCommandType.FlipEffect: - command.Params = new() + short fxFrame = (short)(_commands[offset++] - animation.FrameStart); + short fxID = _commands[offset++]; + if (_version > TRGameVersion.TR2 && (fxID & 0x3FFF) == (int)TR3FX.Footprint) { - (short)(_commands[offset++] - animation.FrameStart), // Frame number, make it relative - _commands[offset++], // (S)FX ID + command = new TRFootprintCommand + { + FrameNumber = fxFrame, + Foot = (TRFootprint)(fxID & 0xC000), + }; + } + else + { + command = new TRFXCommand + { + FrameNumber = fxFrame, + EffectID = fxID, + }; + } + break; + default: + // Karnak and Citadel Gate have these. Easier to store than observe as + // index tracking gets far too complex. + command = new TRNullCommand + { + Value = (short)type, }; break; } @@ -383,6 +448,73 @@ private List BuildCommands(int parentAnimIndex) return animCommands; } + private TRAnimFrame BuildFrame(ref int frameIndex, int numRotations) + { + TRAnimFrame frame = new() + { + Bounds = new() + { + MinX = _frames[frameIndex++], + MaxX = _frames[frameIndex++], + MinY = _frames[frameIndex++], + MaxY = _frames[frameIndex++], + MinZ = _frames[frameIndex++], + MaxZ = _frames[frameIndex++], + }, + OffsetX = _frames[frameIndex++], + OffsetY = _frames[frameIndex++], + OffsetZ = _frames[frameIndex++], + }; + + // TR1 stores the mesh count, but this always matches the model mesh count + if (_version == TRGameVersion.TR1) + { + frameIndex++; + } + + frame.Rotations = new(); + for (int i = 0; i < numRotations; i++) + { + TRAnimFrameRotation rot = new(); + frame.Rotations.Add(rot); + + short rot0, rot1; + TRAngleMode rotMode = TRAngleMode.All; + if (_version == TRGameVersion.TR1) + { + // Reversed words + rot1 = _frames[frameIndex++]; + rot0 = _frames[frameIndex++]; + } + else + { + rot0 = _frames[frameIndex++]; + rotMode = (TRAngleMode)(rot0 & 0xC000); + rot1 = rotMode == TRAngleMode.All + ? _frames[frameIndex++] + : GetSingleRotation(rot0); + } + + switch (rotMode) + { + case TRAngleMode.X: + rot.X = rot1; + break; + case TRAngleMode.Y: + rot.Y = rot1; + break; + case TRAngleMode.Z: + rot.Z = rot1; + break; + default: + UnpackRotation(rot, rot0, rot1); + break; + } + } + + return frame; + } + private void TestTR5Changes(List models) { if (_observer == null || _version != TRGameVersion.TR5) @@ -428,8 +560,8 @@ private void DeconstructModel(TRModel model) { frameBase += (ushort)(animation.FrameEnd + 1); } - _frames.AddRange(animation.Frames); + DeconstructFrames(placeholderAnimation, animation); DeconstructCommands(placeholderAnimation, animation); placeholderAnimation.ChangeOffset = (ushort)_placeholderChanges.Count; @@ -454,26 +586,148 @@ private void DeconstructModel(TRModel model) } } + private void DeconstructFrames(PlaceholderAnimation placeholderAnimation, TRAnimation animation) + { + List> unpaddedFrames = new(); + int longestSet = 0; + foreach (TRAnimFrame frame in animation.Frames) + { + List frameSet = Flatten(frame); + unpaddedFrames.Add(frameSet); + longestSet = Math.Max(longestSet, frameSet.Count); + } + + if (_version != TRGameVersion.TR1) + { + placeholderAnimation.FrameSize = _observer?.GetEmptyAnimFrameSize(_placeholderAnimations.Count - 1) + ?? (byte)longestSet; + } + + for (int i = 0; i < unpaddedFrames.Count; i++) + { + List frameSet = unpaddedFrames[i]; + if (_version != TRGameVersion.TR1) + { + // Pad so that all frames are the same size as the largest one. + frameSet.AddRange(_observer?.GetFramePadding(_placeholderAnimations.Count - 1, i) + ?? Enumerable.Repeat((short)0, longestSet - frameSet.Count)); + } + _frames.AddRange(frameSet); + } + } + + public List Flatten(TRAnimFrame frame) + { + List frames = new() + { + frame.Bounds.MinX, + frame.Bounds.MaxX, + frame.Bounds.MinY, + frame.Bounds.MaxY, + frame.Bounds.MinZ, + frame.Bounds.MaxZ, + frame.OffsetX, + frame.OffsetY, + frame.OffsetZ, + }; + + if (_version == TRGameVersion.TR1) + { + frames.Add((short)frame.Rotations.Count); + } + + foreach (TRAnimFrameRotation rot in frame.Rotations) + { + int rotX = rot.X & 0x03FF; + int rotY = rot.Y & 0x03FF; + int rotZ = rot.Z & 0x03FF; + + if (_version == TRGameVersion.TR1) + { + // Reverse order + frames.Add(PackYZRotation(rotY, rotZ)); + frames.Add(PackXYRotation(rotX, rotY)); + } + else + { + TRAngleMode mode = GetMode(rot); + switch (mode) + { + case TRAngleMode.X: + frames.Add(MaskSingleRotation(rot.X, mode)); + break; + case TRAngleMode.Y: + frames.Add(MaskSingleRotation(rot.Y, mode)); + break; + case TRAngleMode.Z: + frames.Add(MaskSingleRotation(rot.Z, mode)); + break; + default: + frames.Add(PackXYRotation(rotX, rotY)); + frames.Add(PackYZRotation(rotY, rotZ)); + break; + } + } + } + + return frames; + } + private void DeconstructCommands(PlaceholderAnimation placeholderAnimation, TRAnimation animation) { // NumAnimCommands may have been wrong on read, so test observers can restore. placeholderAnimation.AnimCommand = (ushort)_commands.Count; - placeholderAnimation.NumAnimCommands = _observer?.GetNumAnimCommands(_placeholderAnimations.Count - 1) ?? (ushort)animation.Commands.Count; + ushort commandCount = 0; foreach (TRAnimCommand cmd in animation.Commands) { - _commands.Add((short)cmd.Type); - if (cmd.Type == TRAnimCommandType.PlaySound || cmd.Type == TRAnimCommandType.FlipEffect) + List values = new(); + bool validCommand = true; + + switch (cmd) { - Debug.Assert(cmd.Params.Count == 2); - _commands.Add((short)(cmd.Params[0] + placeholderAnimation.RelFrameStart)); - _commands.Add(cmd.Params[1]); + case TRSetPositionCommand setCmd: + values.Add(setCmd.X); + values.Add(setCmd.Y); + values.Add(setCmd.Z); + break; + case TRJumpDistanceCommand jumpCmd: + values.Add(jumpCmd.VerticalSpeed); + values.Add(jumpCmd.HorizontalSpeed); + break; + case TRFXCommand flipCmd: + values.Add((short)(flipCmd.FrameNumber + placeholderAnimation.RelFrameStart)); + values.Add(flipCmd.EffectID); + break; + case TRFootprintCommand footCmd: + if (validCommand = _version >= TRGameVersion.TR3) + { + values.Add((short)(footCmd.FrameNumber + placeholderAnimation.RelFrameStart)); + values.Add((short)((int)TR3FX.Footprint | (int)footCmd.Foot)); + } + break; + case TRSFXCommand sfxCmd: + { + short soundID = sfxCmd.SoundID; + if (_version > TRGameVersion.TR1) + { + soundID = (short)((int)soundID | (int)sfxCmd.Environment); + } + values.Add((short)(sfxCmd.FrameNumber + placeholderAnimation.RelFrameStart)); + values.Add(soundID); + break; + } } - else + + if (validCommand) { - _commands.AddRange(cmd.Params); + _commands.Add(cmd is TRNullCommand nullCmd ? nullCmd.Value : (short)cmd.Type); + _commands.AddRange(values); + commandCount++; } } + + placeholderAnimation.NumAnimCommands = _observer?.GetNumAnimCommands(_placeholderAnimations.Count - 1) ?? commandCount; } private void RestoreTR5Extras() @@ -523,7 +777,7 @@ private void WriteAnimations(TRLevelWriter writer, List models) writer.Write(placeholderAnimation.FrameOffset); writer.Write(animation.FrameRate); - writer.Write(animation.FrameSize); + writer.Write(placeholderAnimation.FrameSize); writer.Write(animation.StateID); writer.Write(animation.Speed); writer.Write(animation.Accel); @@ -627,6 +881,60 @@ private void WriteModels(TRLevelWriter writer, List models) } } + private static TRAngleMode GetMode(TRAnimFrameRotation rot) + { + if (rot.X == 0 && rot.Y == 0) + { + return TRAngleMode.Z; + } + if (rot.X == 0 && rot.Z == 0) + { + return TRAngleMode.Y; + } + if (rot.Y == 0 && rot.Z == 0) + { + return TRAngleMode.X; + } + return TRAngleMode.All; + } + + private static void UnpackRotation(TRAnimFrameRotation rot, short rot0, short rot1) + { + rot.X = (short)((rot0 & 0x3FF0) >> 4); + rot.Y = (short)(((rot0 & 0x000F) << 6) | ((rot1 & 0xFC00) >> 10)); + rot.Z = (short)(rot1 & 0x03FF); + } + + private static short PackXYRotation(int x, int y) + { + return (short)((x << 4) | ((y & 0x0FC0) >> 6)); + } + + private static short PackYZRotation(int y, int z) + { + return (short)(((y & 0x003F) << 10) | (z & 0x03FF)); + } + + private short GetSingleRotation(int angle) + { + if (_version < TRGameVersion.TR4) + { + return (short)(angle & 0x03FF); + } + + return (short)(angle & 0x0FFF); + } + + private short MaskSingleRotation(int angle, TRAngleMode mode) + { + if (_version < TRGameVersion.TR4) + { + return (short)((angle & 0x03FF) | (int)mode); + } + + return (short)((angle & 0x0FFF) | (int)mode); + } + // Information we need for building, but do not want to retain. class PlaceholderModel { @@ -641,6 +949,7 @@ class PlaceholderModel class PlaceholderAnimation { + public byte FrameSize { get; set; } public uint FrameOffset { get; set; } public ushort RelFrameStart { get; set; } public ushort NumStateChanges { get; set; } diff --git a/TRLevelControl/ITRLevelObserver.cs b/TRLevelControl/ITRLevelObserver.cs index 0e700bde9..990699412 100644 --- a/TRLevelControl/ITRLevelObserver.cs +++ b/TRLevelControl/ITRLevelObserver.cs @@ -18,6 +18,10 @@ public interface ITRLevelObserver short? GetAnimCommandPadding(); void OnUnusedStateChangeRead(Tuple padding); Tuple GetUnusedStateChange(); + void OnEmptyAnimFramesRead(int animIndex, byte frameSize); + byte? GetEmptyAnimFrameSize(int animIndex); + void OnFramePaddingRead(int animIndex, int frameIndex, List values); + List GetFramePadding(int animIndex, int frameIndex); void OnSampleIndicesRead(uint[] sampleIndices); IEnumerable GetSampleIndices(); } diff --git a/TRLevelControl/Model/Base/Enums/TR1FX.cs b/TRLevelControl/Model/Base/Enums/TR1FX.cs new file mode 100644 index 000000000..7ebaafc5f --- /dev/null +++ b/TRLevelControl/Model/Base/Enums/TR1FX.cs @@ -0,0 +1,22 @@ +namespace TRLevelControl.Model; + +public enum TR1FX : short +{ + Turn180 = 0, + FloorShake = 1, + LaraNormal = 2, + LaraBubbles = 3, + EndLevel = 4, + Earthquake = 5, + Flood = 6, + RaisingBlock = 7, + StairsToSlope = 8, + Sand = 9, + PowerUp = 10, + Explosion = 11, + LaraHandsFree = 12, + FlipMap = 13, + DrawRightGun = 14, + Chainblock = 15, + Flicker = 16, +} diff --git a/TRLevelControl/Model/Base/TRAnimCommand.cs b/TRLevelControl/Model/Base/TRAnimCommand.cs index 3518b90fe..c8c2d164b 100644 --- a/TRLevelControl/Model/Base/TRAnimCommand.cs +++ b/TRLevelControl/Model/Base/TRAnimCommand.cs @@ -1,18 +1,10 @@ namespace TRLevelControl.Model; -public class TRAnimCommand : ICloneable +public abstract class TRAnimCommand : ICloneable { - public TRAnimCommandType Type { get; set; } - public List Params { get; set; } = new(); + public abstract TRAnimCommandType Type { get; } - public TRAnimCommand Clone() - { - return new() - { - Type = Type, - Params = new(Params), - }; - } + public abstract TRAnimCommand Clone(); object ICloneable.Clone() => Clone(); diff --git a/TRLevelControl/Model/Base/TRAnimFrame.cs b/TRLevelControl/Model/Base/TRAnimFrame.cs index 0eac051e1..190a36331 100644 --- a/TRLevelControl/Model/Base/TRAnimFrame.cs +++ b/TRLevelControl/Model/Base/TRAnimFrame.cs @@ -1,38 +1,25 @@ -using TRLevelControl.Serialization; +namespace TRLevelControl.Model; -namespace TRLevelControl.Model; - -public class TRAnimFrame : ISerializableCompact +public class TRAnimFrame : ICloneable { - public TRBoundingBox Box { get; set; } - + public TRBoundingBox Bounds { get; set; } public short OffsetX { get; set; } - public short OffsetY { get; set; } - public short OffsetZ { get; set; } + public List Rotations { get; set; } - public short NumValues { get; set; } - - public ushort[] AngleSets { get; set; } - - public byte[] Serialize() + public TRAnimFrame Clone() { - using MemoryStream stream = new(); - using (BinaryWriter writer = new(stream)) + return new() { - writer.Write(Box.Serialize()); - writer.Write(OffsetX); - writer.Write(OffsetY); - writer.Write(OffsetZ); - writer.Write(NumValues); - - foreach (ushort val in AngleSets) - { - writer.Write(val); - } - } - - return stream.ToArray(); + Bounds = Bounds.Clone(), + OffsetX = OffsetX, + OffsetY = OffsetY, + OffsetZ = OffsetZ, + Rotations = new(Rotations.Select(r => r.Clone())) + }; } + + object ICloneable.Clone() + => Clone(); } diff --git a/TRLevelControl/Model/Base/TRAnimation.cs b/TRLevelControl/Model/Base/TRAnimation.cs index 9c2c8084f..d74431808 100644 --- a/TRLevelControl/Model/Base/TRAnimation.cs +++ b/TRLevelControl/Model/Base/TRAnimation.cs @@ -3,7 +3,6 @@ public class TRAnimation : ICloneable { public byte FrameRate { get; set; } - public byte FrameSize { get; set; } public ushort StateID { get; set; } public FixedFloat32 Speed { get; set; } public FixedFloat32 Accel { get; set; } @@ -15,7 +14,7 @@ public class TRAnimation : ICloneable public ushort NextFrame { get; set; } public List Changes { get; set; } = new(); public List Commands { get; set; } = new(); - public List Frames { get; set; } = new(); + public List Frames { get; set; } = new(); public TRAnimation Clone() { @@ -25,14 +24,13 @@ public TRAnimation Clone() FrameRate = FrameRate, FrameStart = FrameStart, FrameEnd = FrameEnd, - FrameSize = FrameSize, Speed = Speed, Accel = Accel, SpeedLateral = SpeedLateral, AccelLateral = AccelLateral, NextAnimation = NextAnimation, NextFrame = NextFrame, - Frames = new(Frames), + Frames = new(Frames.Select(f => f.Clone())), Changes = new(Changes.Select(c => c.Clone())), Commands = new(Commands.Select(c => c.Clone())) }; diff --git a/TRLevelControl/Model/Base/TRModel.cs b/TRLevelControl/Model/Base/TRModel.cs index 3ee4a57df..9980086f0 100644 --- a/TRLevelControl/Model/Base/TRModel.cs +++ b/TRLevelControl/Model/Base/TRModel.cs @@ -1,8 +1,6 @@ -using System.Text; +namespace TRLevelControl.Model; -namespace TRLevelControl.Model; - -public class TRModel +public class TRModel : ICloneable { public List Animations { get; set; } = new(); public List MeshTrees { get; set; } = new(); @@ -12,23 +10,18 @@ public class TRModel public ushort StartingMesh { get; set; } - public uint MeshTree { get; set; } - - public uint FrameOffset { get; set; } - - public ushort Animation { get; set; } - - public override string ToString() + public TRModel Clone() { - StringBuilder sb = new(base.ToString()); - - sb.Append(" ID: " + ID); - sb.Append(" NumMeshes: " + NumMeshes); - sb.Append(" StartingMesh: " + StartingMesh); - sb.Append(" MeshTree: " + MeshTree); - sb.Append(" FrameOffset: " + FrameOffset); - sb.Append(" Animation: " + Animation); - - return sb.ToString(); + return new() + { + Animations = new(Animations.Select(a => a.Clone())), + MeshTrees = new(MeshTrees.Select(m => m.Clone())), + ID = ID, + NumMeshes = NumMeshes, + StartingMesh = StartingMesh, + }; } + + object ICloneable.Clone() + => Clone(); } diff --git a/TRLevelControl/Model/Common/AnimCommands/TREmptyHandsCommand.cs b/TRLevelControl/Model/Common/AnimCommands/TREmptyHandsCommand.cs new file mode 100644 index 000000000..9e80518f2 --- /dev/null +++ b/TRLevelControl/Model/Common/AnimCommands/TREmptyHandsCommand.cs @@ -0,0 +1,9 @@ +namespace TRLevelControl.Model; + +public class TREmptyHandsCommand : TRAnimCommand +{ + public override TRAnimCommandType Type => TRAnimCommandType.EmptyHands; + + public override TRAnimCommand Clone() + => (TREmptyHandsCommand)MemberwiseClone(); +} diff --git a/TRLevelControl/Model/Common/AnimCommands/TRFXCommand.cs b/TRLevelControl/Model/Common/AnimCommands/TRFXCommand.cs new file mode 100644 index 000000000..9e2c50e39 --- /dev/null +++ b/TRLevelControl/Model/Common/AnimCommands/TRFXCommand.cs @@ -0,0 +1,11 @@ +namespace TRLevelControl.Model; + +public class TRFXCommand : TRAnimCommand +{ + public override TRAnimCommandType Type => TRAnimCommandType.FlipEffect; + public short FrameNumber { get; set; } + public short EffectID { get; set; } + + public override TRAnimCommand Clone() + => (TRFXCommand)MemberwiseClone(); +} diff --git a/TRLevelControl/Model/Common/AnimCommands/TRFootprintCommand.cs b/TRLevelControl/Model/Common/AnimCommands/TRFootprintCommand.cs new file mode 100644 index 000000000..bc8733749 --- /dev/null +++ b/TRLevelControl/Model/Common/AnimCommands/TRFootprintCommand.cs @@ -0,0 +1,11 @@ +namespace TRLevelControl.Model; + +public class TRFootprintCommand : TRAnimCommand +{ + public override TRAnimCommandType Type => TRAnimCommandType.FlipEffect; + public short FrameNumber { get; set; } + public TRFootprint Foot { get; set; } + + public override TRAnimCommand Clone() + => (TRFootprintCommand)MemberwiseClone(); +} diff --git a/TRLevelControl/Model/Common/AnimCommands/TRJumpDistanceCommand.cs b/TRLevelControl/Model/Common/AnimCommands/TRJumpDistanceCommand.cs new file mode 100644 index 000000000..f96c4eefa --- /dev/null +++ b/TRLevelControl/Model/Common/AnimCommands/TRJumpDistanceCommand.cs @@ -0,0 +1,11 @@ +namespace TRLevelControl.Model; + +public class TRJumpDistanceCommand : TRAnimCommand +{ + public override TRAnimCommandType Type => TRAnimCommandType.JumpDistance; + public short VerticalSpeed { get; set; } + public short HorizontalSpeed { get; set; } + + public override TRAnimCommand Clone() + => (TRJumpDistanceCommand)MemberwiseClone(); +} diff --git a/TRLevelControl/Model/Common/AnimCommands/TRKillCommand.cs b/TRLevelControl/Model/Common/AnimCommands/TRKillCommand.cs new file mode 100644 index 000000000..1cdcd9e3e --- /dev/null +++ b/TRLevelControl/Model/Common/AnimCommands/TRKillCommand.cs @@ -0,0 +1,9 @@ +namespace TRLevelControl.Model; + +public class TRKillCommand : TRAnimCommand +{ + public override TRAnimCommandType Type => TRAnimCommandType.Kill; + + public override TRAnimCommand Clone() + => (TRKillCommand)MemberwiseClone(); +} diff --git a/TRLevelControl/Model/Common/AnimCommands/TRNullCommand.cs b/TRLevelControl/Model/Common/AnimCommands/TRNullCommand.cs new file mode 100644 index 000000000..0465e9d12 --- /dev/null +++ b/TRLevelControl/Model/Common/AnimCommands/TRNullCommand.cs @@ -0,0 +1,10 @@ +namespace TRLevelControl.Model; + +public class TRNullCommand : TRAnimCommand +{ + public override TRAnimCommandType Type => TRAnimCommandType.Null; + public short Value { get; set; } + + public override TRAnimCommand Clone() + => (TRNullCommand)MemberwiseClone(); +} diff --git a/TRLevelControl/Model/Common/AnimCommands/TRSFXCommand.cs b/TRLevelControl/Model/Common/AnimCommands/TRSFXCommand.cs new file mode 100644 index 000000000..f69ee53da --- /dev/null +++ b/TRLevelControl/Model/Common/AnimCommands/TRSFXCommand.cs @@ -0,0 +1,12 @@ +namespace TRLevelControl.Model; + +public class TRSFXCommand : TRAnimCommand +{ + public override TRAnimCommandType Type => TRAnimCommandType.PlaySound; + public TRSFXEnvironment Environment { get; set; } + public short FrameNumber { get; set; } + public short SoundID { get; set; } + + public override TRAnimCommand Clone() + => (TRSFXCommand)MemberwiseClone(); +} diff --git a/TRLevelControl/Model/Common/AnimCommands/TRSetPositionCommand.cs b/TRLevelControl/Model/Common/AnimCommands/TRSetPositionCommand.cs new file mode 100644 index 000000000..6e4ace088 --- /dev/null +++ b/TRLevelControl/Model/Common/AnimCommands/TRSetPositionCommand.cs @@ -0,0 +1,12 @@ +namespace TRLevelControl.Model; + +public class TRSetPositionCommand : TRAnimCommand +{ + public override TRAnimCommandType Type => TRAnimCommandType.SetPosition; + public short X { get; set; } + public short Y { get; set; } + public short Z { get; set; } + + public override TRAnimCommand Clone() + => (TRSetPositionCommand)MemberwiseClone(); +} diff --git a/TRLevelControl/Model/Common/Enums/TRAngleMode.cs b/TRLevelControl/Model/Common/Enums/TRAngleMode.cs new file mode 100644 index 000000000..9577e0fa3 --- /dev/null +++ b/TRLevelControl/Model/Common/Enums/TRAngleMode.cs @@ -0,0 +1,9 @@ +namespace TRLevelControl.Model; + +public enum TRAngleMode +{ + All = 0, + X = 0x4000, + Y = 0x8000, + Z = 0xC000, +} diff --git a/TRLevelControl/Model/Common/Enums/TRFootprint.cs b/TRLevelControl/Model/Common/Enums/TRFootprint.cs new file mode 100644 index 000000000..37269a17f --- /dev/null +++ b/TRLevelControl/Model/Common/Enums/TRFootprint.cs @@ -0,0 +1,7 @@ +namespace TRLevelControl.Model; + +public enum TRFootprint +{ + Left = 0x4000, + Right = 0x8000, +} diff --git a/TRLevelControl/Model/Common/Enums/TRSFXEnvironment.cs b/TRLevelControl/Model/Common/Enums/TRSFXEnvironment.cs new file mode 100644 index 000000000..3be5016d6 --- /dev/null +++ b/TRLevelControl/Model/Common/Enums/TRSFXEnvironment.cs @@ -0,0 +1,8 @@ +namespace TRLevelControl.Model; + +public enum TRSFXEnvironment +{ + Any = 0, + Land = 0x4000, + Water = 0x8000, +} diff --git a/TRLevelControl/Model/Common/TRAnimFrameRotation.cs b/TRLevelControl/Model/Common/TRAnimFrameRotation.cs new file mode 100644 index 000000000..7b0482704 --- /dev/null +++ b/TRLevelControl/Model/Common/TRAnimFrameRotation.cs @@ -0,0 +1,14 @@ +namespace TRLevelControl.Model; + +public class TRAnimFrameRotation : ICloneable +{ + public short X { get; set; } + public short Y { get; set; } + public short Z { get; set; } + + public TRAnimFrameRotation Clone() + => (TRAnimFrameRotation)MemberwiseClone(); + + object ICloneable.Clone() + => Clone(); +} diff --git a/TRLevelControl/Model/TR2/Enums/TR2FX.cs b/TRLevelControl/Model/TR2/Enums/TR2FX.cs new file mode 100644 index 000000000..9b35900f6 --- /dev/null +++ b/TRLevelControl/Model/TR2/Enums/TR2FX.cs @@ -0,0 +1,35 @@ +namespace TRLevelControl.Model; + +public enum TR2FX : short +{ + Turn180 = 0, + FloorShake = 1, + LaraNormal = 2, + LaraBubbles = 3, + EndLevel = 4, + Flood = 5, + Chandelier = 6, + Rumble = 7, + Pistons = 8, + Curtains = 9, + SetChange = 10, + Explosion = 11, + LaraHandsFree = 12, + FlipMap = 13, + DrawRightGun = 14, + DrawLeftGun = 15, + MeshSwap1 = 18, + MeshSwap2 = 19, + MeshSwap3 = 20, + HideItem = 21, + ShowItem = 22, + DynamicLightsOn = 23, + DynamicLightsOff = 24, + Statue = 25, + ResetHair = 26, + Boiler = 27, + AssaultReset = 28, + AssaultStop = 29, + AssaultStart = 30, + AssaultEnd = 31, +} diff --git a/TRLevelControl/Model/TR3/Enums/TR3FX.cs b/TRLevelControl/Model/TR3/Enums/TR3FX.cs new file mode 100644 index 000000000..c898c9827 --- /dev/null +++ b/TRLevelControl/Model/TR3/Enums/TR3FX.cs @@ -0,0 +1,56 @@ +namespace TRLevelControl.Model; + +public enum TR3FX : short +{ + Turn180 = 0, + FloorShake = 1, + LaraNormal = 2, + LaraBubbles = 3, + EndLevel = 4, + Flood = 5, + Rumble = 7, + Explosion = 11, + LaraHandsFree = 12, + FlipMap = 13, + DrawRightGun = 14, + DrawLeftGun = 15, + ShootRightGun = 16, + ShootLeftGun = 17, + MeshSwap1 = 18, + MeshSwap2 = 19, + MeshSwap3 = 20, + HideItem = 21, + ShowItem = 22, + DynamicLightsOn = 23, + DynamicLightsOff = 24, + ResetHair = 26, + AssaultReset = 28, + AssaultStart = 30, + AssaultEnd = 31, + Footprint = 32, + AssaultPenalty8 = 33, + RacetrackStart = 34, + RacetrackReset = 35, + RacetrackEnd = 36, + AssaultPenalty30 = 37, + GymHint1 = 38, + GymHint2 = 39, + GymHint3 = 40, + GymHint4 = 41, + GymHint5 = 42, + GymHint6 = 43, + GymHint7 = 44, + GymHint8 = 45, + GymHint9 = 46, + GymHint10 = 47, + GymHint11 = 48, + GymHint12 = 49, + GymHint13 = 50, + GymHint14 = 51, + GymHint15 = 52, + GymHint16 = 53, + GymHint17 = 54, + GymHint18 = 55, + GymHint19 = 56, + GymHintReset = 57, +} diff --git a/TRLevelControl/Model/TR4/Enums/TR4FX.cs b/TRLevelControl/Model/TR4/Enums/TR4FX.cs new file mode 100644 index 000000000..425fc6dc9 --- /dev/null +++ b/TRLevelControl/Model/TR4/Enums/TR4FX.cs @@ -0,0 +1,36 @@ +namespace TRLevelControl.Model; + +public enum TR4FX : short +{ + Turn180 = 0, + FloorShake = 1, + LaraNormal = 2, + LaraBubbles = 3, + EndLevel = 4, + ActivateCamera = 5, + ActivateKey = 6, + Rumble = 7, + SwapCrowbar = 8, + TimerField = 10, + Explosion = 11, + LaraHandsFree = 12, + DrawRightGun = 14, + DrawLeftGun = 15, + ShootRightGun = 16, + ShootLeftGun = 17, + MeshSwap1 = 18, + MeshSwap2 = 19, + MeshSwap3 = 20, + HideItem = 21, + ShowItem = 22, + ResetHair = 26, + SetFog = 28, + GhostTrap = 29, + LaraLocation = 30, + ClearScarabs = 31, + Footprint = 32, + PourSwapOn = 43, + PourSwapOff = 44, + LaraLocationPad = 45, + KillActiveBaddies = 46, +} diff --git a/TRLevelControl/Model/TR5/Enums/TR5FX.cs b/TRLevelControl/Model/TR5/Enums/TR5FX.cs new file mode 100644 index 000000000..3f468db4c --- /dev/null +++ b/TRLevelControl/Model/TR5/Enums/TR5FX.cs @@ -0,0 +1,41 @@ +namespace TRLevelControl.Model; + +public enum TR5FX : short +{ + Turn180 = 0, + FloorShake = 1, + LaraNormal = 2, + LaraBubbles = 3, + EndLevel = 4, + ActivateCamera = 5, + ActivateKey = 6, + Rumble = 7, + SwapCrowbar = 8, + TimerField = 10, + Explosion = 11, + LaraHandsFree = 12, + ShootRightGun = 16, + ShootLeftGun = 17, + HideItem = 21, + ShowItem = 22, + ResetHair = 26, + SetFog = 28, + LaraLocation = 30, + ResetTest = 31, + Footprint = 32, + ClearSpiders = 33, + LaraLocationPad = 45, + KillActiveBaddies = 46, + TutorialHint1 = 47, + TutorialHint2 = 48, + TutorialHint3 = 49, + TutorialHint4 = 50, + TutorialHint5 = 51, + TutorialHint6 = 52, + TutorialHint7 = 53, + TutorialHint8 = 54, + TutorialHint9 = 55, + TutorialHint10 = 56, + TutorialHint11 = 57, + TutorialHint12 = 58, +} diff --git a/TRLevelControlTests/Base/Observers/ObserverBase.cs b/TRLevelControlTests/Base/Observers/ObserverBase.cs index dcef01508..65eab060d 100644 --- a/TRLevelControlTests/Base/Observers/ObserverBase.cs +++ b/TRLevelControlTests/Base/Observers/ObserverBase.cs @@ -53,6 +53,18 @@ public virtual void OnUnusedStateChangeRead(Tuple padding) public virtual Tuple GetUnusedStateChange() => null; + public virtual void OnEmptyAnimFramesRead(int animIndex, byte frameSize) + { } + + public virtual byte? GetEmptyAnimFrameSize(int animIndex) + => null; + + public virtual void OnFramePaddingRead(int animIndex, int frameIndex, List values) + { } + + public virtual List GetFramePadding(int animIndex, int frameIndex) + => null; + public virtual void OnSampleIndicesRead(uint[] sampleIndices) { } diff --git a/TRLevelControlTests/Base/Observers/TR2Observer.cs b/TRLevelControlTests/Base/Observers/TR2Observer.cs index 5954b2ac6..519f66633 100644 --- a/TRLevelControlTests/Base/Observers/TR2Observer.cs +++ b/TRLevelControlTests/Base/Observers/TR2Observer.cs @@ -2,15 +2,24 @@ public class TR2Observer : TR1Observer { - private readonly Dictionary> _animLinks = new(); + private readonly Dictionary>> _framePadding = new(); - public override void OnBadAnimLinkRead(int animIndex, ushort animLink, ushort frameLink) + public override void OnFramePaddingRead(int animIndex, int frameIndex, List values) { - _animLinks[animIndex] = new(animLink, frameLink); + if (!_framePadding.ContainsKey(animIndex)) + { + _framePadding[animIndex] = new(); + } + _framePadding[animIndex][frameIndex] = values; } - public override Tuple GetAnimLink(int animIndex) + public override List GetFramePadding(int animIndex, int frameIndex) { - return _animLinks.ContainsKey(animIndex) ? _animLinks[animIndex] : null; + if (_framePadding.ContainsKey(animIndex) + && _framePadding[animIndex].ContainsKey(frameIndex)) + { + return _framePadding[animIndex][frameIndex]; + } + return null; } } diff --git a/TRLevelControlTests/Base/Observers/TR3Observer.cs b/TRLevelControlTests/Base/Observers/TR3Observer.cs new file mode 100644 index 000000000..0a761d074 --- /dev/null +++ b/TRLevelControlTests/Base/Observers/TR3Observer.cs @@ -0,0 +1,16 @@ +namespace TRLevelControlTests; + +public class TR3Observer : TR2Observer +{ + private readonly Dictionary> _animLinks = new(); + + public override void OnBadAnimLinkRead(int animIndex, ushort animLink, ushort frameLink) + { + _animLinks[animIndex] = new(animLink, frameLink); + } + + public override Tuple GetAnimLink(int animIndex) + { + return _animLinks.ContainsKey(animIndex) ? _animLinks[animIndex] : null; + } +} diff --git a/TRLevelControlTests/Base/Observers/TR4Observer.cs b/TRLevelControlTests/Base/Observers/TR4Observer.cs index b23849de6..3930a589a 100644 --- a/TRLevelControlTests/Base/Observers/TR4Observer.cs +++ b/TRLevelControlTests/Base/Observers/TR4Observer.cs @@ -3,13 +3,14 @@ namespace TRLevelControlTests; -public class TR4Observer : TR2Observer +public class TR4Observer : TR3Observer { private readonly Dictionary _inflatedReads = new(); private readonly Dictionary _inflatedWrites = new(); private readonly Dictionary> _meshPadding = new(); private readonly Dictionary _badAnimCmdCounts = new(); + private readonly Dictionary _emptyAnimFrameSizes = new(); private uint[] _sampleIndices; @@ -84,6 +85,16 @@ public override void OnBadAnimCommandRead(int animIndex, ushort numAnimCommands) return _badAnimCmdCounts.ContainsKey(animIndex) ? _badAnimCmdCounts[animIndex] : null; } + public override void OnEmptyAnimFramesRead(int animIndex, byte frameSize) + { + _emptyAnimFrameSizes[animIndex] = frameSize; + } + + public override byte? GetEmptyAnimFrameSize(int animIndex) + { + return _emptyAnimFrameSizes.ContainsKey(animIndex) ? _emptyAnimFrameSizes[animIndex] : null; + } + public override void OnSampleIndicesRead(uint[] sampleIndices) { _sampleIndices = sampleIndices; diff --git a/TRLevelControlTests/Base/TR45LevelSummary.cs b/TRLevelControlTests/Base/TR45LevelSummary.cs deleted file mode 100644 index 4a516809e..000000000 --- a/TRLevelControlTests/Base/TR45LevelSummary.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace TRLevelControlTests; - -public class TR45LevelSummary -{ - public uint LevelChunkUncompressedSize { get; set; } - public uint Tex32ChunkUncompressedSize { get; set; } - public uint Tex16ChunkUncompressedSize { get; set; } - public uint Tex32MChunkUncompressedSize { get; set; } -} diff --git a/TRLevelControlTests/Base/TestBase.cs b/TRLevelControlTests/Base/TestBase.cs index c7f33df66..6c25f9396 100644 --- a/TRLevelControlTests/Base/TestBase.cs +++ b/TRLevelControlTests/Base/TestBase.cs @@ -105,7 +105,7 @@ public static void ReadWriteLevel(string levelName, TRGameVersion version) control2.Write(level2, outputStream); break; case TRGameVersion.TR3: - observer = new TR2Observer(); + observer = new TR3Observer(); TR3LevelControl control3 = new(observer); TR3Level level3 = control3.Read(pathI); control3.Write(level3, outputStream); diff --git a/TRModelTransporter/Handlers/ModelTransportHandler.cs b/TRModelTransporter/Handlers/ModelTransportHandler.cs index be31cda63..bf4cff431 100644 --- a/TRModelTransporter/Handlers/ModelTransportHandler.cs +++ b/TRModelTransporter/Handlers/ModelTransportHandler.cs @@ -38,7 +38,7 @@ public static void Import(TR1Level level, TR1ModelDefinition definition, Diction if (!definition.HasGraphics) { // The original mesh data may still be needed so don't overwrite - definition.Model.MeshTree = level.Models[i].MeshTree; + definition.Model.MeshTrees = level.Models[i].MeshTrees; definition.Model.NumMeshes = level.Models[i].NumMeshes; definition.Model.StartingMesh = level.Models[i].StartingMesh; } @@ -103,8 +103,7 @@ public static void Import(TR3Level level, TR3ModelDefinition definition, Diction // at 0, so because these don't change per skin, we just replace the meshes and frames here. level.Models[i].NumMeshes = definition.Model.NumMeshes; level.Models[i].StartingMesh = definition.Model.StartingMesh; - level.Models[i].MeshTree = definition.Model.MeshTree; - level.Models[i].FrameOffset = definition.Model.FrameOffset; + level.Models[i].MeshTrees = definition.Model.MeshTrees; } } @@ -121,7 +120,7 @@ private static void ReplaceLaraDependants(List models, TRModel lara, IE TRModel dependentModel = models.Find(m => m.ID == dependant); if (dependentModel != null) { - dependentModel.MeshTree = lara.MeshTree; + dependentModel.MeshTrees = lara.MeshTrees; dependentModel.StartingMesh = lara.StartingMesh; } } diff --git a/TRModelTransporter/Model/Animations/TR1PackedAnimation.cs b/TRModelTransporter/Model/Animations/TR1PackedAnimation.cs deleted file mode 100644 index 75a93d7be..000000000 --- a/TRModelTransporter/Model/Animations/TR1PackedAnimation.cs +++ /dev/null @@ -1,18 +0,0 @@ -using TRLevelControl.Model; - -namespace TRModelTransporter.Model.Animations; - -public class TR1PackedAnimation -{ - public TRAnimation Animation { get; set; } - public Dictionary AnimationDispatches { get; set; } - public Dictionary Commands { get; set; } - public List StateChanges { get; set; } - - public TR1PackedAnimation() - { - AnimationDispatches = new Dictionary(); - Commands = new Dictionary(); - StateChanges = new List(); - } -} diff --git a/TRModelTransporter/Model/Animations/TR1PackedAnimationCommand.cs b/TRModelTransporter/Model/Animations/TR1PackedAnimationCommand.cs deleted file mode 100644 index e3c0c309c..000000000 --- a/TRModelTransporter/Model/Animations/TR1PackedAnimationCommand.cs +++ /dev/null @@ -1,9 +0,0 @@ -using TRLevelControl.Model; - -namespace TRModelTransporter.Model.Animations; - -public class TR1PackedAnimationCommand -{ - public TRAnimCommandType Command { get; set; } - public short[] Params { get; set; } -} diff --git a/TRModelTransporter/Model/Animations/TR2PackedAnimation.cs b/TRModelTransporter/Model/Animations/TR2PackedAnimation.cs deleted file mode 100644 index cc829e56a..000000000 --- a/TRModelTransporter/Model/Animations/TR2PackedAnimation.cs +++ /dev/null @@ -1,19 +0,0 @@ -using TRLevelControl.Model; - -namespace TRModelTransporter.Model.Animations; - -public class TR2PackedAnimation -{ - public TRAnimation Animation { get; set; } - public Dictionary AnimationDispatches { get; set; } - public Dictionary Commands { get; set; } - - public List StateChanges { get; set; } - - public TR2PackedAnimation() - { - AnimationDispatches = new Dictionary(); - Commands = new Dictionary(); - StateChanges = new List(); - } -} diff --git a/TRModelTransporter/Model/Animations/TR3PackedAnimation.cs b/TRModelTransporter/Model/Animations/TR3PackedAnimation.cs deleted file mode 100644 index 842b8c4d1..000000000 --- a/TRModelTransporter/Model/Animations/TR3PackedAnimation.cs +++ /dev/null @@ -1,19 +0,0 @@ -using TRLevelControl.Model; - -namespace TRModelTransporter.Model.Animations; - -public class TR3PackedAnimation -{ - public TRAnimation Animation { get; set; } - public Dictionary AnimationDispatches { get; set; } - public Dictionary Commands { get; set; } - - public List StateChanges { get; set; } - - public TR3PackedAnimation() - { - AnimationDispatches = new Dictionary(); - Commands = new Dictionary(); - StateChanges = new List(); - } -} diff --git a/TRModelTransporter/Model/Definitions/TR1ModelDefinition.cs b/TRModelTransporter/Model/Definitions/TR1ModelDefinition.cs index 8b6e1833e..d804635c5 100644 --- a/TRModelTransporter/Model/Definitions/TR1ModelDefinition.cs +++ b/TRModelTransporter/Model/Definitions/TR1ModelDefinition.cs @@ -1,17 +1,13 @@ using TRLevelControl.Model; -using TRModelTransporter.Model.Animations; namespace TRModelTransporter.Model.Definitions; public class TR1ModelDefinition : AbstractTRModelDefinition { public override TR1Type Entity => (TR1Type)Model.ID; - public Dictionary Animations { get; set; } - public ushort[] AnimationFrames { get; set; } public TRCinematicFrame[] CinematicFrames { get; set; } public Dictionary Colours { get; set; } public List Meshes { get; set; } - public TRMeshTreeNode[] MeshTrees { get; set; } public TRModel Model { get; set; } public SortedDictionary SoundEffects { get; set; } diff --git a/TRModelTransporter/Model/Definitions/TR2ModelDefinition.cs b/TRModelTransporter/Model/Definitions/TR2ModelDefinition.cs index 45b62ecd5..a33cb7ed0 100644 --- a/TRModelTransporter/Model/Definitions/TR2ModelDefinition.cs +++ b/TRModelTransporter/Model/Definitions/TR2ModelDefinition.cs @@ -1,17 +1,13 @@ using TRLevelControl.Model; -using TRModelTransporter.Model.Animations; namespace TRModelTransporter.Model.Definitions; public class TR2ModelDefinition : AbstractTRModelDefinition { public override TR2Type Entity => (TR2Type)Model.ID; - public Dictionary Animations { get; set; } - public ushort[] AnimationFrames { get; set; } public TRCinematicFrame[] CinematicFrames { get; set; } public Dictionary Colours { get; set; } public List Meshes { get; set; } - public TRMeshTreeNode[] MeshTrees { get; set; } public TRModel Model { get; set; } public SortedDictionary SoundEffects { get; set; } diff --git a/TRModelTransporter/Model/Definitions/TR3ModelDefinition.cs b/TRModelTransporter/Model/Definitions/TR3ModelDefinition.cs index 1170ca3ce..117d34b9c 100644 --- a/TRModelTransporter/Model/Definitions/TR3ModelDefinition.cs +++ b/TRModelTransporter/Model/Definitions/TR3ModelDefinition.cs @@ -1,17 +1,13 @@ using TRLevelControl.Model; -using TRModelTransporter.Model.Animations; namespace TRModelTransporter.Model.Definitions; public class TR3ModelDefinition : AbstractTRModelDefinition { public override TR3Type Entity => (TR3Type)Model.ID; - public Dictionary Animations { get; set; } - public ushort[] AnimationFrames { get; set; } public TRCinematicFrame[] CinematicFrames { get; set; } public Dictionary Colours { get; set; } public List Meshes { get; set; } - public TRMeshTreeNode[] MeshTrees { get; set; } public TRModel Model { get; set; } public SortedDictionary SoundEffects { get; set; } diff --git a/TRModelTransporter/Transport/TR1/TR1ModelExporter.cs b/TRModelTransporter/Transport/TR1/TR1ModelExporter.cs index 318997627..3e9363c70 100644 --- a/TRModelTransporter/Transport/TR1/TR1ModelExporter.cs +++ b/TRModelTransporter/Transport/TR1/TR1ModelExporter.cs @@ -101,14 +101,10 @@ public static void AmendPierreGunshot(TR1Level level) TRAnimation anim = model.Animations[10]; // On the 2nd frame, play SFX 44 (magnums) - anim.Commands.Add(new() + anim.Commands.Add(new TRSFXCommand { - Type = TRAnimCommandType.PlaySound, - Params = new() - { - 1, - (short)TR1SFX.LaraMagnums, - } + FrameNumber = 1, + SoundID = (short)TR1SFX.LaraMagnums, }); } @@ -119,14 +115,10 @@ public static void AmendPierreDeath(TR1Level level) TRAnimation anim = model.Animations[12]; // On the 61st frame, play SFX 159 (death) - anim.Commands.Add(new() + anim.Commands.Add(new TRSFXCommand { - Type = TRAnimCommandType.PlaySound, - Params = new() - { - 60, - (short)TR1SFX.PierreDeath, - } + FrameNumber = 60, + SoundID = (short)TR1SFX.PierreDeath, }); } @@ -137,14 +129,10 @@ public static void AmendLarsonDeath(TR1Level level) TRAnimation anim = model.Animations[15]; // On the 2nd frame, play SFX 158 (death) - anim.Commands.Add(new() + anim.Commands.Add(new TRSFXCommand { - Type = TRAnimCommandType.PlaySound, - Params = new() - { - 1, - (short)TR1SFX.LarsonDeath, - } + FrameNumber = 1, + SoundID = (short)TR1SFX.LarsonDeath, }); } @@ -154,7 +142,7 @@ public static void AmendSkaterBoyDeath(TR1Level level) // Get his death animation TRAnimation anim = model.Animations[13]; // Play the death sound on the 2nd frame (doesn't work on the 1st, which is OG). - anim.Commands[2].Params[0]++; + (anim.Commands[2] as TRSFXCommand).FrameNumber++; } public static void AmendNatlaDeath(TR1Level level) @@ -164,14 +152,10 @@ public static void AmendNatlaDeath(TR1Level level) TRAnimation anim = model.Animations[13]; // On the 5th frame, play SFX 160 (death) - anim.Commands.Add(new() + anim.Commands.Add(new TRSFXCommand { - Type = TRAnimCommandType.PlaySound, - Params = new() - { - 4, - (short)TR1SFX.NatlaDeath, - } + FrameNumber = 4, + SoundID = (short)TR1SFX.NatlaDeath, }); } @@ -192,14 +176,9 @@ public static void AddMovingBlockSFX(TR1Level level) TRAnimation anim = model.Animations[i]; // On the 1st frame, play SFX 162 - anim.Commands.Add(new() + anim.Commands.Add(new TRSFXCommand { - Type = TRAnimCommandType.PlaySound, - Params = new() - { - 0, - (short)TR1SFX.TrapdoorClose, - } + SoundID = (short)TR1SFX.TrapdoorClose, }); } } diff --git a/TRRandomizerCore/Processors/TR3/TR3SequenceProcessor.cs b/TRRandomizerCore/Processors/TR3/TR3SequenceProcessor.cs index 35b17f9a3..3318dba98 100644 --- a/TRRandomizerCore/Processors/TR3/TR3SequenceProcessor.cs +++ b/TRRandomizerCore/Processors/TR3/TR3SequenceProcessor.cs @@ -276,15 +276,9 @@ private void AmendWillardBoss(TR3CombinedLevel level) { TR3Type replacement = _artefactAssignment[artefact]; TRModel artefactModel = level.Data.Models.Find(m => m.ID == (uint)artefact); - level.Data.Models.Add(new() - { - Animation = artefactModel.Animation, - FrameOffset = artefactModel.FrameOffset, - ID = (uint)replacement, - MeshTree = artefactModel.MeshTree, - NumMeshes = artefactModel.NumMeshes, - StartingMesh = artefactModel.StartingMesh - }); + TRModel replacementModel = artefactModel.Clone(); + replacementModel.ID = (uint)replacement; + level.Data.Models.Add(replacementModel); level.Data.Entities .FindAll(e => e.TypeID == artefact) diff --git a/TRRandomizerCore/Randomizers/TR1/TR1EnemyRandomizer.cs b/TRRandomizerCore/Randomizers/TR1/TR1EnemyRandomizer.cs index 7c339bf67..3efaad9d4 100644 --- a/TRRandomizerCore/Randomizers/TR1/TR1EnemyRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR1/TR1EnemyRandomizer.cs @@ -943,16 +943,9 @@ private void AmendAtlanteanModels(TR1CombinedLevel level, EnemyRandomizationColl TRModel nonShooter = level.Data.Models.Find(m => m.ID == (uint)TR1Type.NonShootingAtlantean_N); if (shooter == null && nonShooter != null) { - level.Data.Models.Add(new() - { - ID = (uint)TR1Type.ShootingAtlantean_N, - Animation = nonShooter.Animation, - FrameOffset = nonShooter.FrameOffset, - MeshTree = nonShooter.MeshTree, - NumMeshes = nonShooter.NumMeshes, - StartingMesh = nonShooter.StartingMesh - }); - + shooter = nonShooter.Clone(); + shooter.ID = (uint)TR1Type.ShootingAtlantean_N; + level.Data.Models.Add(shooter); enemies.Available.Add(TR1Type.ShootingAtlantean_N); } } diff --git a/TRRandomizerCore/Randomizers/TR1/TR1OutfitRandomizer.cs b/TRRandomizerCore/Randomizers/TR1/TR1OutfitRandomizer.cs index ce839123c..4af746f43 100644 --- a/TRRandomizerCore/Randomizers/TR1/TR1OutfitRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR1/TR1OutfitRandomizer.cs @@ -730,8 +730,7 @@ private bool ImportGymOutfit(TR1CombinedLevel level) if (existingModel != null) { TRModel newModel = level.Data.Models.Find(m => m.ID == (uint)TR1Type.LaraMiscAnim_H); - newModel.Animation = existingModel.Animation; - newModel.FrameOffset = existingModel.FrameOffset; + newModel.Animations = existingModel.Animations; } return true; diff --git a/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs b/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs index d9087572e..e2e3df087 100644 --- a/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR2/TR2EnemyRandomizer.cs @@ -500,15 +500,9 @@ private static void DisguiseEntity(TR2CombinedLevel level, TR2Type guiser, TR2Ty { // We have to keep the original model for the boss, so in // this instance we just clone the model for the guiser - level.Data.Models.Add(new() - { - Animation = disguiseAsModel.Animation, - FrameOffset = disguiseAsModel.FrameOffset, - ID = (uint)guiser, - MeshTree = disguiseAsModel.MeshTree, - NumMeshes = disguiseAsModel.NumMeshes, - StartingMesh = disguiseAsModel.StartingMesh - }); + TRModel guiserModel = disguiseAsModel.Clone(); + guiserModel.ID = (uint)guiser; + level.Data.Models.Add(guiserModel); } else { @@ -936,7 +930,7 @@ private void RandomizeEnemyMeshes(TR2CombinedLevel level, EnemyRandomizationColl foreach (TR2Type enemyType in laraClones) { TRModel enemyModel = level.Data.Models.Find(m => m.ID == (uint)enemyType); - enemyModel.MeshTree = laraModel.MeshTree; + enemyModel.MeshTrees = laraModel.MeshTrees; enemyModel.StartingMesh = laraModel.StartingMesh; enemyModel.NumMeshes = laraModel.NumMeshes; } @@ -952,9 +946,8 @@ private void RandomizeEnemyMeshes(TR2CombinedLevel level, EnemyRandomizationColl // Make Marco look and behave like Winston, until Lara gets too close TRModel marcoModel = level.Data.Models.Find(m => m.ID == (uint)TR2Type.MarcoBartoli); TRModel winnieModel = level.Data.Models.Find(m => m.ID == (uint)TR2Type.Winston); - marcoModel.Animation = winnieModel.Animation; - marcoModel.FrameOffset = winnieModel.FrameOffset; - marcoModel.MeshTree = winnieModel.MeshTree; + marcoModel.Animations = winnieModel.Animations; + marcoModel.MeshTrees = winnieModel.MeshTrees; marcoModel.StartingMesh = winnieModel.StartingMesh; marcoModel.NumMeshes = winnieModel.NumMeshes; } diff --git a/TRRandomizerCore/Randomizers/TR2/TR2OutfitRandomizer.cs b/TRRandomizerCore/Randomizers/TR2/TR2OutfitRandomizer.cs index af84f0bc5..0970a496b 100644 --- a/TRRandomizerCore/Randomizers/TR2/TR2OutfitRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR2/TR2OutfitRandomizer.cs @@ -230,7 +230,7 @@ protected override void ProcessImpl() private bool Import(TR2CombinedLevel level, TR2Type lara) { TRModel laraModel = level.Data.Models.Find(m => m.ID == (uint)TR2Type.Lara); - List laraClones = level.Data.Models.FindAll(m => m.MeshTree == laraModel.MeshTree && m != laraModel); + List laraClones = level.Data.Models.FindAll(m => m.MeshTrees.FirstOrDefault() == laraModel.MeshTrees.FirstOrDefault() && m != laraModel); if (lara == TR2Type.LaraInvisible) { @@ -278,7 +278,7 @@ private bool Import(TR2CombinedLevel level, TR2Type lara) laraModel = level.Data.Models.Find(m => m.ID == (uint)TR2Type.Lara); foreach (TRModel model in laraClones) { - model.MeshTree = laraModel.MeshTree; + model.MeshTrees = laraModel.MeshTrees; model.StartingMesh = laraModel.StartingMesh; model.NumMeshes = laraModel.NumMeshes; } @@ -421,7 +421,7 @@ private void AdjustOutfit(TR2CombinedLevel level, TR2Type lara) TRModel actorLara = level.Data.Models.Find(m => m.ID == (short)TR2Type.CutsceneActor3); TRModel realLara = level.Data.Models.Find(m => m.ID == (short)TR2Type.Lara); - actorLara.MeshTree = realLara.MeshTree; + actorLara.MeshTrees = realLara.MeshTrees; actorLara.NumMeshes = realLara.NumMeshes; actorLara.StartingMesh = realLara.StartingMesh; } diff --git a/TRRandomizerCore/Randomizers/TR3/TR3SecretRandomizer.cs b/TRRandomizerCore/Randomizers/TR3/TR3SecretRandomizer.cs index 2f3b99705..9b9eec4e0 100644 --- a/TRRandomizerCore/Randomizers/TR3/TR3SecretRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR3/TR3SecretRandomizer.cs @@ -465,15 +465,9 @@ private void AddDamageControl(TR3CombinedLevel level, List pickupTypes, // We have a free slot, so duplicate a model TR3Type baseArtefact = pickupTypes[_generator.Next(0, pickupTypes.Count)]; TRModel artefactMenuModel = level.Data.Models.Find(m => m.ID == (uint)artefacts[baseArtefact]); - level.Data.Models.Add(new() - { - Animation = artefactMenuModel.Animation, - FrameOffset = artefactMenuModel.FrameOffset, - ID = (uint)availableMenuType, - MeshTree = artefactMenuModel.MeshTree, - NumMeshes = artefactMenuModel.NumMeshes, - StartingMesh = artefactMenuModel.StartingMesh - }); + TRModel availableModel = artefactMenuModel.Clone(); + availableModel.ID = (uint)availableMenuType; + level.Data.Models.Add(availableModel); // Add a script name - pull from GamestringRando once translations completed SetPuzzleTypeName(level, availablePickupType, "Infinite Medi Packs"); @@ -839,15 +833,9 @@ protected override void ProcessImpl() // to duplicate the models instead of replacing them, otherwise the carried-over // artefacts from previous levels are invisible. TRModel menuModel = level.Data.Models.Find(m => m.ID == (uint)artefactMenuType); - level.Data.Models.Add(new() - { - Animation = menuModel.Animation, - FrameOffset = menuModel.FrameOffset, - ID = (uint)puzzleMenuType, - MeshTree = menuModel.MeshTree, - NumMeshes = menuModel.NumMeshes, - StartingMesh = menuModel.StartingMesh - }); + TRModel newModel = menuModel.Clone(); + newModel.ID = (uint)puzzleMenuType; + level.Data.Models.Add(newModel); // Remove this puzzle type from the available pool allocation.AvailablePickupModels.RemoveAt(0);