Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand on TRR file control #679

Merged
merged 6 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Deps/TRGE.Coord.dll
Binary file not shown.
Binary file modified Deps/TRGE.Core.dll
Binary file not shown.
26 changes: 22 additions & 4 deletions TRLevelControl/Build/TRModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class TRModelBuilder<T>
private static readonly ushort _tr5ModelPadding = 0xFFEF;

private readonly TRGameVersion _version;
private readonly TRModelDataType _dataType;
private readonly ITRLevelObserver _observer;

private List<TRAnimation> _animations;
Expand All @@ -23,9 +24,10 @@ public class TRModelBuilder<T>
private Dictionary<TRAnimDispatch, short> _dispatchToAnimMap;
private Dictionary<TRAnimDispatch, short> _dispatchFrameBase;

public TRModelBuilder(TRGameVersion version, ITRLevelObserver observer = null)
public TRModelBuilder(TRGameVersion version, TRModelDataType dataType, ITRLevelObserver observer = null)
{
_version = version;
_dataType = dataType;
_observer = observer;
}

Expand Down Expand Up @@ -305,6 +307,19 @@ private TRAnimation BuildAnimation(PlaceholderModel placeholderModel, int animIn
}
}

if (_dataType == TRModelDataType.PDP && _version == TRGameVersion.TR1 && globalAnimIndex < _placeholderAnimations.Count - 1)
{
// TR1 PDP files have an extra frame in Lara's drop-twist-hang animation (46, modern controls).
// Frame start/end mean it's never used in-game, but we capture it for tests.
// Sanity check that the difference is exactly the size of a (TR1) frame.
PlaceholderAnimation nextAnimation = _placeholderAnimations[globalAnimIndex + 1];
int difference = ((int)nextAnimation.FrameOffset / sizeof(short)) - frameIndex;
if (difference == 10 + 2 * placeholderModel.NumMeshes)
{
animation.Frames.Add(BuildFrame(ref frameIndex, placeholderModel.NumMeshes));
}
}

return animation;
}

Expand Down Expand Up @@ -536,7 +551,8 @@ private void DeconstructModel(T type, TRModel model)
{
ID = (uint)(object)type,
Animation = model.Animations.Count == 0 ? TRConsts.NoAnimation : (ushort)_placeholderAnimations.Count,
FrameOffset = _observer == null && model.Animations.Count == 0 ? 0 : (uint)_frames.Count * sizeof(short),
FrameOffset = (_dataType == TRModelDataType.PDP || _observer == null)
&& model.Animations.Count == 0 ? 0 : (uint)_frames.Count * sizeof(short),
NumMeshes = (ushort)model.Meshes.Count,
};
_placeholderModels.Add(placeholderModel);
Expand Down Expand Up @@ -880,11 +896,13 @@ private void WriteModels(TRLevelWriter writer, TRDictionary<T, TRModel> models)
}
}

private static TRAngleMode GetMode(TRAnimFrameRotation rot)
private TRAngleMode GetMode(TRAnimFrameRotation rot)
{
if (rot.X == 0 && rot.Y == 0)
{
return TRAngleMode.Z;
// OG TR2+ levels (and TRR levels) use Z here, PDP uses X. Makes no difference
// in game, but keeps tests happy.
return _dataType == TRModelDataType.PDP && rot.Z == 0 ? TRAngleMode.X : TRAngleMode.Z;
}
if (rot.X == 0 && rot.Z == 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,13 @@ private void WriteMeshData(TRLevelWriter writer)

private void ReadModelData(TRLevelReader reader)
{
TRModelBuilder<TR1Type> builder = new(TRGameVersion.TR1, _observer);
TRModelBuilder<TR1Type> builder = new(TRGameVersion.TR1, TRModelDataType.Level, _observer);
_level.Models = builder.ReadModelData(reader, _meshBuilder);
}

private void WriteModelData(TRLevelWriter writer)
{
TRModelBuilder<TR1Type> builder = new(TRGameVersion.TR1, _observer);
TRModelBuilder<TR1Type> builder = new(TRGameVersion.TR1, TRModelDataType.Level, _observer);
builder.WriteModelData(writer, _level.Models);
}

Expand Down
83 changes: 83 additions & 0 deletions TRLevelControl/Control/TR1/TR1MapControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using TRLevelControl.Model;

namespace TRLevelControl;

public class TR1MapControl : TRMapControlBase<TR1Type, TR1RAlias>
{
protected override TR1Type ReadKey(string key)
=> _keyToTypeMap.ContainsKey(key) ? _keyToTypeMap[key] : default;

protected override string ConvertKey(TR1Type key)
=> _typeToKeyMap[key];

private static readonly Dictionary<string, TR1Type> _keyToTypeMap = new()
{
["DUMMY"] = default,
["BRIDGE_FLAT"] = TR1Type.BridgeFlat,
["BRIDGE_TILT1"] = TR1Type.BridgeTilt1,
["BRIDGE_TILT2"] = TR1Type.BridgeTilt2,
["DART_EMITTER"] = TR1Type.DartEmitter,
["DARTS"] = TR1Type.Dart_H,
["DOOR_TYPE1"] = TR1Type.Door1,
["DOOR_TYPE2"] = TR1Type.Door2,
["DOOR_TYPE3"] = TR1Type.Door3,
["DOOR_TYPE4"] = TR1Type.Door4,
["DOOR_TYPE5"] = TR1Type.Door5,
["DOOR_TYPE6"] = TR1Type.Door6,
["DOOR_TYPE7"] = TR1Type.Door7,
["DOOR_TYPE8"] = TR1Type.Door8,
["FALLING_BLOCK"] = TR1Type.FallingBlock,
["FALLING_CEILING1"] = TR1Type.FallingCeiling1,
["FALLING_CEILING2"] = TR1Type.FallingCeiling2,
["KEY_HOLE1"] = TR1Type.Keyhole1,
["KEY_HOLE2"] = TR1Type.Keyhole2,
["KEY_HOLE3"] = TR1Type.Keyhole3,
["KEY_HOLE4"] = TR1Type.Keyhole4,
["KEY_OPTION1"] = TR1Type.Key1_M_H,
["KEY_OPTION2"] = TR1Type.Key2_M_H,
["KEY_OPTION3"] = TR1Type.Key3_M_H,
["KEY_OPTION4"] = TR1Type.Key4_M_H,
["LARSON"] = TR1Type.Larson,
["LAVA_WEDGE"] = TR1Type.AtlanteanLava,
["LIGHTNING_EMITTER"] = TR1Type.ThorLightning,
["MOVABLE_BLOCK"] = TR1Type.PushBlock1,
["MOVABLE_BLOCK2"] = TR1Type.PushBlock2,
["MOVABLE_BLOCK3"] = TR1Type.PushBlock3,
["MOVABLE_BLOCK4"] = TR1Type.PushBlock4,
["MOVING_BAR"] = TR1Type.Barricade,
["NATLA"] = TR1Type.Natla,
["PASSPORT_OPTION"] = TR1Type.PassportOpen_M_H,
["PENDULUM"] = TR1Type.SwingingBlade,
["PLAYER_1"] = TR1Type.CutsceneActor1,
["PLAYER_2"] = TR1Type.CutsceneActor2,
["PLAYER_3"] = TR1Type.CutsceneActor3,
["PLAYER_4"] = TR1Type.CutsceneActor4,
["PODS"] = TR1Type.AtlanteanEgg,
["PUZZLE_DONE1"] = TR1Type.PuzzleDone1,
["PUZZLE_DONE2"] = TR1Type.PuzzleDone2,
["PUZZLE_DONE3"] = TR1Type.PuzzleDone3,
["PUZZLE_DONE4"] = TR1Type.PuzzleDone4,
["PUZZLE_HOLE1"] = TR1Type.PuzzleHole1,
["PUZZLE_HOLE2"] = TR1Type.PuzzleHole2,
["PUZZLE_HOLE3"] = TR1Type.PuzzleHole3,
["PUZZLE_HOLE4"] = TR1Type.PuzzleHole4,
["PUZZLE_OPTION1"] = TR1Type.Puzzle1_M_H,
["PUZZLE_OPTION2"] = TR1Type.Puzzle2_M_H,
["PUZZLE_OPTION3"] = TR1Type.Puzzle3_M_H,
["PUZZLE_OPTION4"] = TR1Type.Puzzle4_M_H,
["ROLLING_BALL"] = TR1Type.RollingBall,
["SWITCH_TYPE1"] = TR1Type.WallSwitch,
["SWITCH_TYPE2"] = TR1Type.UnderwaterSwitch,
["TEETH_TRAP"] = TR1Type.SlammingDoor,
["TRAPDOOR"] = TR1Type.Trapdoor1,
["TRAPDOOR2"] = TR1Type.Trapdoor2,
["WARRIOR3"] = TR1Type.NonShootingAtlantean_N,
};

private static readonly Dictionary<TR1Type, string> _typeToKeyMap;

static TR1MapControl()
{
_typeToKeyMap = _keyToTypeMap.ToDictionary(e => e.Value, e => e.Key);
}
}
13 changes: 13 additions & 0 deletions TRLevelControl/Control/TR1/TR1PDPControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using TRLevelControl.Build;
using TRLevelControl.Model;

namespace TRLevelControl;

public class TR1PDPControl : TRPDPControlBase<TR1Type>
{
public TR1PDPControl(ITRLevelObserver observer = null)
: base(observer) { }

protected override TRModelBuilder<TR1Type> CreateBuilder()
=> new(TRGameVersion.TR1, TRModelDataType.PDP);
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,13 @@ private void WriteMeshData(TRLevelWriter writer)

private void ReadModelData(TRLevelReader reader)
{
TRModelBuilder<TR2Type> builder = new(TRGameVersion.TR2, _observer);
TRModelBuilder<TR2Type> builder = new(TRGameVersion.TR2, TRModelDataType.Level, _observer);
_level.Models = builder.ReadModelData(reader, _meshBuilder);
}

private void WriteModelData(TRLevelWriter writer)
{
TRModelBuilder<TR2Type> builder = new(TRGameVersion.TR2, _observer);
TRModelBuilder<TR2Type> builder = new(TRGameVersion.TR2, TRModelDataType.Level, _observer);
builder.WriteModelData(writer, _level.Models);
}

Expand Down
119 changes: 119 additions & 0 deletions TRLevelControl/Control/TR2/TR2MapControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using TRLevelControl.Model;

namespace TRLevelControl;

public class TR2MapControl : TRMapControlBase<TR2Type, TR2RAlias>
{
protected override TR2Type ReadKey(string key)
=> _keyToTypeMap.ContainsKey(key) ? _keyToTypeMap[key] : default;

protected override string ConvertKey(TR2Type key)
=> _typeToKeyMap[key];

private static readonly Dictionary<string, TR2Type> _keyToTypeMap = new()
{
["DUMMY"] = default,
["ALARM_SOUND"] = TR2Type.DoorBell_N,
["AVALANCHE"] = TR2Type.BouldersOrSnowballs,
["BANDIT2"] = TR2Type.Mercenary2,
["BANDIT2B"] = TR2Type.Mercenary3,
["BANDIT3"] = TR2Type.Mercenary1,
["BARACUDDA"] = TR2Type.Barracuda,
["BIG_SPIDER"] = TR2Type.GiantSpider,
["BIG_YETI"] = TR2Type.BirdMonster,
["BLADE"] = TR2Type.WallMountedKnifeBlade,
["BRIDGE_FLAT"] = TR2Type.BridgeFlat,
["BRIDGE_TILT1"] = TR2Type.BridgeTilt1,
["BRIDGE_TILT2"] = TR2Type.BridgeTilt2,
["CEILING_SPIKES"] = TR2Type.SpikyCeiling,
["COPTER"] = TR2Type.Helicopter,
["CULT3"] = TR2Type.ShotgunGoon,
["DOOR_TYPE1"] = TR2Type.Door1,
["DOOR_TYPE2"] = TR2Type.Door2,
["DOOR_TYPE3"] = TR2Type.Door3,
["DOOR_TYPE4"] = TR2Type.Door4,
["DOOR_TYPE5"] = TR2Type.Door5,
["DOOR_TYPE6"] = TR2Type.LiftingDoor1,
["DOOR_TYPE7"] = TR2Type.LiftingDoor2,
["DOOR_TYPE8"] = TR2Type.LiftingDoor3,
["DRAW_BRIDGE"] = TR2Type.Drawbridge,
["EAGLE"] = TR2Type.Eagle,
["FALLING_BLOCK"] = TR2Type.FallingBlock,
["FALLING_CEILING1"] = TR2Type.FallingCeilingOrSandbag,
["HARPOON_BOLT"] = TR2Type.HarpoonProjectile_H,
["KEY_HOLE1"] = TR2Type.Keyhole1,
["KEY_HOLE2"] = TR2Type.Keyhole2,
["KEY_HOLE3"] = TR2Type.Keyhole3,
["KEY_HOLE4"] = TR2Type.Keyhole4,
["KEY_OPTION1"] = TR2Type.Key1_M_H,
["KEY_OPTION2"] = TR2Type.Key2_M_H,
["KEY_OPTION3"] = TR2Type.Key3_M_H,
["KEY_OPTION4"] = TR2Type.Key4_M_H,
["KILLER_STATUE"] = TR2Type.StatueWithKnifeBlade,
["MESHSWAP1"] = TR2Type.CutsceneActor1,
["MESHSWAP2"] = TR2Type.CutsceneActor2,
["MINI_COPTER"] = TR2Type.Helicopter2,
["MONK1"] = TR2Type.MonkWithLongStick,
["MONK2"] = TR2Type.MonkWithKnifeStick,
["MOVABLE_BLOCK"] = TR2Type.PushBlock1,
["MOVABLE_BLOCK2"] = TR2Type.PushBlock2,
["MOVABLE_BLOCK3"] = TR2Type.PushBlock3,
["MOVABLE_BLOCK4"] = TR2Type.PushBlock4,
["OILDRUMS"] = TR2Type.RollingStorageDrums,
["PASSPORT_OPTION"] = TR2Type.PassportOpen_M_H,
        ["PENDULUM"] = TR2Type.SandbagOrBallsack,
["PICKUP_OPTION1"] = TR2Type.Quest1_M_H,
["PICKUP_OPTION2"] = TR2Type.Quest2_M_H,
["PLAYER_1"] = TR2Type.CutsceneActor4,
["PLAYER_2"] = TR2Type.CutsceneActor5,
["PLAYER_3"] = TR2Type.CutsceneActor6,
["PLAYER_5"] = TR2Type.CutsceneActor8,
["PLAYER_6"] = TR2Type.CutsceneActor9,
["PLAYER_7"] = TR2Type.CutsceneActor10,
["PLAYER_8"] = TR2Type.CutsceneActor11,
["PLAYER4"] = TR2Type.CutsceneActor7,
["PUSH_SWITCH"] = TR2Type.PushButtonSwitch,
["PUZZLE_DONE1"] = TR2Type.PuzzleDone1,
["PUZZLE_DONE2"] = TR2Type.PuzzleDone2,
["PUZZLE_DONE3"] = TR2Type.PuzzleDone3,
["PUZZLE_DONE4"] = TR2Type.PuzzleDone4,
["PUZZLE_HOLE1"] = TR2Type.PuzzleHole1,
["PUZZLE_HOLE2"] = TR2Type.PuzzleHole2,
["PUZZLE_HOLE3"] = TR2Type.PuzzleHole3,
["PUZZLE_HOLE4"] = TR2Type.PuzzleHole4,
["PUZZLE_OPTION1"] = TR2Type.Puzzle1_M_H,
["PUZZLE_OPTION2"] = TR2Type.Puzzle2_M_H,
["PUZZLE_OPTION3"] = TR2Type.Puzzle3_M_H,
["PUZZLE_OPTION4"] = TR2Type.Puzzle4_M_H,
["ROCKET"] = TR2Type.GrenadeProjectile_H,
["ROLLING_BALL"] = TR2Type.RollingBall,
["SHARK"] = TR2Type.Shark,
["SKIDMAN"] = TR2Type.MercSnowmobDriver,
["SKIDOO"] = TR2Type.RedSnowmobile,
["SMASH_ICE"] = TR2Type.BreakableWindow2,
["SMASH_WINDOW"] = TR2Type.BreakableWindow1,
["SPIDER"] = TR2Type.Spider,
["SPIKE_WALL"] = TR2Type.SpikyWall,
["SPIKES"] = TR2Type.TeethSpikesOrGlassShards,
["SPINNING_BLADE"] = TR2Type.RollingSpindle,
["SPRING_BOARD"] = TR2Type.BouncePad,
["SWING_BOX"] = TR2Type.SwingingBoxOrBall,
["SWITCH_TYPE1"] = TR2Type.WallSwitch,
["SWITCH_TYPE2"] = TR2Type.UnderwaterSwitch,
["TEETH_TRAP"] = TR2Type.SlammingDoor,
["TIGER"] = TR2Type.TigerOrSnowLeopard,
["TRAPDOOR"] = TR2Type.Trapdoor1,
["TRAPDOOR2"] = TR2Type.Trapdoor2,
["WORKER1"] = TR2Type.Gunman1,
["WORKER3"] = TR2Type.StickWieldingGoon1,
["WORKER4"] = TR2Type.StickWieldingGoon2,
["YETI"] = TR2Type.Yeti,
};

private static readonly Dictionary<TR2Type, string> _typeToKeyMap;

static TR2MapControl()
{
_typeToKeyMap = _keyToTypeMap.ToDictionary(e => e.Value, e => e.Key);
}
}
13 changes: 13 additions & 0 deletions TRLevelControl/Control/TR2/TR2PDPControl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using TRLevelControl.Build;
using TRLevelControl.Model;

namespace TRLevelControl;

public class TR2PDPControl : TRPDPControlBase<TR2Type>
{
public TR2PDPControl(ITRLevelObserver observer = null)
: base(observer) { }

protected override TRModelBuilder<TR2Type> CreateBuilder()
=> new(TRGameVersion.TR2, TRModelDataType.PDP);
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,13 @@ private void WriteMeshData(TRLevelWriter writer)

private void ReadModelData(TRLevelReader reader)
{
TRModelBuilder<TR3Type> builder = new(TRGameVersion.TR3, _observer);
TRModelBuilder<TR3Type> builder = new(TRGameVersion.TR3, TRModelDataType.Level, _observer);
_level.Models = builder.ReadModelData(reader, _meshBuilder);
}

private void WriteModelData(TRLevelWriter writer)
{
TRModelBuilder<TR3Type> builder = new(TRGameVersion.TR3, _observer);
TRModelBuilder<TR3Type> builder = new(TRGameVersion.TR3, TRModelDataType.Level, _observer);
builder.WriteModelData(writer, _level.Models);
}

Expand Down
Loading
Loading