Skip to content

Commit

Permalink
LostArtefacts#304 Cross-game Preparations
Browse files Browse the repository at this point in the history
Some preparations for cross-game level support.
- Zones/ZoneGroups implemented for TR1 as per TR2&3. Unit test added for adding to zones.
- Ability to set the number of sounds in TRSoundDetails.
- Ability to find tile segments from texture indices in texture packer.
- Ability to add faces to meshes in MeshEditor.
- Several general models made available for import (bridges, keys, some traps etc).
- Fixes some texture deduplication issues in GW.
  • Loading branch information
lahm86 committed Apr 25, 2022
1 parent be54ade commit 4cd4494
Show file tree
Hide file tree
Showing 70 changed files with 352 additions and 41 deletions.
88 changes: 88 additions & 0 deletions TRLevelReader/Helpers/TR1BoxUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TRLevelReader.Model;
using TRLevelReader.Model.Base.Enums;

namespace TRLevelReader.Helpers
{
public static class TR1BoxUtilities
{
public static void DuplicateZone(TRLevel level, int boxIndex)
{
TRZoneGroup zoneGroup = level.Zones[boxIndex];
List<TRZoneGroup> zones = level.Zones.ToList();
zones.Add(new TRZoneGroup
{
NormalZone = zoneGroup.NormalZone.Clone(),
AlternateZone = zoneGroup.AlternateZone.Clone()
});
level.Zones = zones.ToArray();
}

public static TRZoneGroup[] ReadZones(uint numBoxes, ushort[] zoneData)
{
// Initialise the zone groups - one for every box.
TRZoneGroup[] zones = new TRZoneGroup[numBoxes];
for (int i = 0; i < zones.Length; i++)
{
zones[i] = new TRZoneGroup
{
NormalZone = new TRZone(),
AlternateZone = new TRZone()
};
}

// Build the zones, mapping the multidimensional ushort structures into the corresponding
// zone object values.
IEnumerable<FlipStatus> flipValues = Enum.GetValues(typeof(FlipStatus)).Cast<FlipStatus>();
IEnumerable<TRZones> zoneValues = Enum.GetValues(typeof(TRZones)).Cast<TRZones>();

int valueIndex = 0;
foreach (FlipStatus flip in flipValues)
{
foreach (TRZones zone in zoneValues)
{
for (int box = 0; box < zones.Length; box++)
{
zones[box][flip].GroundZones[zone] = zoneData[valueIndex++];
}
}

for (int box = 0; box < zones.Length; box++)
{
zones[box][flip].FlyZone = zoneData[valueIndex++];
}
}

return zones;
}

public static ushort[] FlattenZones(TRZoneGroup[] zoneGroups)
{
// Convert the zone objects back into a flat ushort list.
IEnumerable<FlipStatus> flipValues = Enum.GetValues(typeof(FlipStatus)).Cast<FlipStatus>();
IEnumerable<TRZones> zoneValues = Enum.GetValues(typeof(TRZones)).Cast<TRZones>();

List<ushort> zones = new List<ushort>();

foreach (FlipStatus flip in flipValues)
{
foreach (TRZones zone in zoneValues)
{
for (int box = 0; box < zoneGroups.Length; box++)
{
zones.Add(zoneGroups[box][flip].GroundZones[zone]);
}
}

for (int box = 0; box < zoneGroups.Length; box++)
{
zones.Add(zoneGroups[box][flip].FlyZone);
}
}

return zones.ToArray();
}
}
}
8 changes: 8 additions & 0 deletions TRLevelReader/Model/Base/Enums/TRZones.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace TRLevelReader.Model.Base.Enums
{
public enum TRZones
{
Zone1 = 0,
Zone2 = 1
}
}
17 changes: 3 additions & 14 deletions TRLevelReader/Model/Base/TRLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TRLevelReader.Helpers;
using TRLevelReader.Serialization;

namespace TRLevelReader.Model
Expand Down Expand Up @@ -220,19 +221,7 @@ public class TRLevel : BaseTRLevel, ISerializableCompact
/// </summary>
public ushort[] Overlaps { get; set; }

public ushort[] Zones { get; set; }

public ushort[] GroundZone { get; set; }

public ushort[] GroundZone2 { get; set; }

public ushort[] FlyZone { get; set; }

public ushort[] GroundZoneAlt { get; set; }

public ushort[] GroundZoneAlt2 { get; set; }

public ushort[] FlyZoneAlt { get; set; }
public TRZoneGroup[] Zones { get; set; }

/// <summary>
/// 4 bytes
Expand Down Expand Up @@ -371,7 +360,7 @@ public byte[] Serialize()
foreach (TRBox box in Boxes) { writer.Write(box.Serialize()); }
writer.Write(NumOverlaps);
foreach (ushort overlap in Overlaps) { writer.Write(overlap); }
foreach (ushort zone in Zones) { writer.Write(zone); }
foreach (ushort zone in TR1BoxUtilities.FlattenZones(Zones)) { writer.Write(zone); }
writer.Write(NumAnimatedTextures);
writer.Write((ushort)AnimatedTextures.Length);
foreach (TRAnimatedTexture texture in AnimatedTextures) { writer.Write(texture.Serialize()); }
Expand Down
14 changes: 13 additions & 1 deletion TRLevelReader/Model/Base/TRSoundDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,19 @@ public class TRSoundDetails : ISerializableCompact
public ushort Chance { get; set; }

public ushort Characteristics { get; set; }
public int NumSounds => (Characteristics & 0x00FC) >> 2; // get bits 2-7

public int NumSounds
{
get
{
return (Characteristics & 0x00FC) >> 2; // get bits 2-7
}
set
{
Characteristics = (ushort)(Characteristics & ~(Characteristics & 0x00FC));
Characteristics |= (ushort)(value << 2);
}
}

public override string ToString()
{
Expand Down
49 changes: 28 additions & 21 deletions TRLevelReader/Model/Base/TRZone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,50 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using TRLevelReader.Model.Base.Enums;
using TRLevelReader.Serialization;

namespace TRLevelReader.Model
{
public class TRZone : ISerializableCompact
public class TRZone : ISerializableCompact, ICloneable
{
public ushort GroundZone1Normal { get; set; }

public ushort GroundZone2Normal { get; set; }

public ushort FlyZoneNormal { get; set; }

public ushort GroundZone1Alternate { get; set; }
public Dictionary<TRZones, ushort> GroundZones { get; set; }
public ushort FlyZone { get; set; }

public ushort GroundZone2Alternate { get; set; }

public ushort FlyZoneAlternate { get; set; }
public TRZone()
{
GroundZones = new Dictionary<TRZones, ushort>();
}

public byte[] Serialize()
{
using (MemoryStream stream = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(GroundZone1Normal);
writer.Write(GroundZone2Normal);
writer.Write(FlyZoneNormal);
writer.Write(GroundZone1Alternate);
writer.Write(GroundZone2Alternate);
writer.Write(FlyZoneAlternate);
foreach (ushort zone in GroundZones.Values)
{
writer.Write(zone);
}
writer.Write(FlyZone);
}

return stream.ToArray();
}
}

public TRZone Clone()
{
return new TRZone
{
GroundZones = GroundZones.ToDictionary(e => e.Key, e => e.Value),
FlyZone = FlyZone
};
}

object ICloneable.Clone()
{
return Clone();
}
}
}
}
25 changes: 25 additions & 0 deletions TRLevelReader/Model/Base/TRZoneGroup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using TRLevelReader.Model.Base.Enums;

namespace TRLevelReader.Model
{
public class TRZoneGroup : Dictionary<FlipStatus, TRZone>
{
/// <summary>
/// Zone values when flipmap is off.
/// </summary>
public TRZone NormalZone
{
get => this[FlipStatus.Off];
set => this[FlipStatus.Off] = value;
}
/// <summary>
/// Zone values when flipmap is on.
/// </summary>
public TRZone AlternateZone
{
get => this[FlipStatus.On];
set => this[FlipStatus.On] = value;
}
}
}
9 changes: 5 additions & 4 deletions TRLevelReader/TRLevelReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TRLevelReader.Helpers;
using TRLevelReader.Model;

namespace TRLevelReader
Expand Down Expand Up @@ -284,12 +285,12 @@ public TRLevel ReadLevel(string Filename)
level.Overlaps[i] = reader.ReadUInt16();
}

level.Zones = new ushort[6 * level.NumBoxes];

for (int i = 0; i < level.Zones.Count(); i++)
ushort[] zoneData = new ushort[level.NumBoxes * 6];
for (int i = 0; i < zoneData.Length; i++)
{
level.Zones[i] = reader.ReadUInt16();
zoneData[i] = reader.ReadUInt16();
}
level.Zones = TR1BoxUtilities.ReadZones(level.NumBoxes, zoneData);

//Animated Textures - the data stores the total number of ushorts to read (NumAnimatedTextures)
//followed by a ushort to describe the number of actual texture group objects.
Expand Down
51 changes: 51 additions & 0 deletions TRLevelReaderUnitTests/TRLevel_UnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using TRFDControl.FDEntryTypes;
using TRFDControl.Utilities;
using System.Linq;
using TRLevelReader.Model.Base.Enums;
using TRLevelReader.Helpers;

namespace TRLevelReaderUnitTests
{
Expand Down Expand Up @@ -386,5 +388,54 @@ public void Floordata_ReadWrite_DefaultTest()
TR1LevelWriter writer = new TR1LevelWriter();
writer.WriteLevelToFile(lvl, "level10c_fdata.phd");
}

[TestMethod]
public void ModifyZonesTest()
{
TR1LevelReader reader = new TR1LevelReader();
TRLevel lvl = reader.ReadLevel("level1.phd");

// For every box, store the current zone. We use the serialized form
// for comparison.
Dictionary<int, byte[]> flipOffZones = new Dictionary<int, byte[]>();
Dictionary<int, byte[]> flipOnZones = new Dictionary<int, byte[]>();
for (int i = 0; i < lvl.NumBoxes; i++)
{
flipOffZones[i] = lvl.Zones[i][FlipStatus.Off].Serialize();
flipOnZones[i] = lvl.Zones[i][FlipStatus.On].Serialize();
}

// Add a new box
List<TRBox> boxes = lvl.Boxes.ToList();
boxes.Add(boxes[0]);
lvl.Boxes = boxes.ToArray();
lvl.NumBoxes++;

// Add a new zone for the box and store its serialized form for comparison
int newBoxIndex = (int)(lvl.NumBoxes - 1);
TR1BoxUtilities.DuplicateZone(lvl, 0);
flipOffZones[newBoxIndex] = lvl.Zones[newBoxIndex][FlipStatus.Off].Serialize();
flipOnZones[newBoxIndex] = lvl.Zones[newBoxIndex][FlipStatus.On].Serialize();

// Verify the number of zone ushorts matches what's expected for the box count
Assert.AreEqual(TR1BoxUtilities.FlattenZones(lvl.Zones).Length, (int)(6 * lvl.NumBoxes));

// Write and re-read the level
new TR1LevelWriter().WriteLevelToFile(lvl, "TEST.phd");
lvl = reader.ReadLevel("TEST.phd");

// Capture all of the zones again. Make sure the addition of the zone above didn't
// affect any of the others and that the addition itself matches after IO.
for (int i = 0; i < lvl.NumBoxes; i++)
{
byte[] flipOff = lvl.Zones[i][FlipStatus.Off].Serialize();
Assert.IsTrue(flipOffZones.ContainsKey(i));
CollectionAssert.AreEqual(flipOffZones[i], flipOff);

byte[] flipOn = lvl.Zones[i][FlipStatus.On].Serialize();
Assert.IsTrue(flipOnZones.ContainsKey(i));
CollectionAssert.AreEqual(flipOnZones[i], flipOn);
}
}
}
}
8 changes: 7 additions & 1 deletion TRModelTransporter/Data/TR2DefaultDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,11 @@ public IEnumerable<int> GetIgnorableTextureIndices(TR2Entities entity)
[TR2Entities.RedSnowmobile]
= new List<TR2Entities> { TR2Entities.SnowmobileWake_S_H },
[TR2Entities.XianGuardSword]
= new List<TR2Entities> { TR2Entities.XianGuardSparkles_S_H }
= new List<TR2Entities> { TR2Entities.XianGuardSparkles_S_H },
[TR2Entities.WaterfallMist_N]
= new List<TR2Entities> { TR2Entities.WaterRipples_S_H },
[TR2Entities.Key2_M_H]
= new List<TR2Entities> { TR2Entities.Key2_S_P }
};

private static readonly List<TR2Entities> _cinematicEntities = new List<TR2Entities>
Expand Down Expand Up @@ -373,6 +377,8 @@ public IEnumerable<int> GetIgnorableTextureIndices(TR2Entities entity)
{
[TR2Entities.LaraMiscAnim_H]
= new List<int>(), // empty list indicates to ignore everything
[TR2Entities.WaterfallMist_N]
= new List<int> { 0, 1, 2, 3, 4, 5, 6, 11, 15, 20, 22 },
[TR2Entities.LaraSnowmobAnim_H]
= new List<int> { 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 20, 21, 23 },
[TR2Entities.SnowmobileBelt]
Expand Down
Loading

0 comments on commit 4cd4494

Please sign in to comment.