public void IcePalace_ReadTest() { TR2LevelReader reader = new TR2LevelReader(); TR2Level lvl = reader.ReadLevel("icecave.tr2"); byte[] lvlAsBytes = File.ReadAllBytes("icecave.tr2"); //Does our view of the level match byte for byte? CollectionAssert.AreEqual(lvlAsBytes, lvl.Serialize(), "Read does not match byte for byte"); TR2LevelWriter writer = new TR2LevelWriter(); writer.WriteLevelToFile(lvl, "TEST.tr2"); byte[] copyAsBytes = File.ReadAllBytes("TEST.tr2"); //Does our saved copy match the original? CollectionAssert.AreEqual(lvlAsBytes, copyAsBytes, "Write does not match byte for byte"); }
public void CalculateDependencies(TR2Level level, TR2Entities entity) { using (TexturePacker packer = new TexturePacker(level)) { Dictionary <TexturedTile, List <TexturedTileSegment> > entitySegments = packer.GetModelSegments(entity); foreach (TRModel model in level.Models) { TR2Entities otherEntity = (TR2Entities)model.ID; if (entity == otherEntity) { continue; } Dictionary <TexturedTile, List <TexturedTileSegment> > modelSegments = packer.GetModelSegments(otherEntity); foreach (TexturedTile tile in entitySegments.Keys) { if (modelSegments.ContainsKey(tile)) { List <TexturedTileSegment> matches = entitySegments[tile].FindAll(s1 => modelSegments[tile].Any(s2 => s1 == s2)); foreach (TexturedTileSegment matchedSegment in matches) { TextureDependency dependency = GetDependency(tile.Index, matchedSegment.Bounds); if (dependency == null) { dependency = new TextureDependency { TileIndex = tile.Index, Bounds = matchedSegment.Bounds }; Dependencies.Add(dependency); } dependency.AddEntity(entity); dependency.AddEntity(otherEntity); System.Diagnostics.Debug.WriteLine("\t\t" + otherEntity + ", " + tile.Index + ": " + matchedSegment.Bounds); } } } } } }
/// <summary> /// Duplicates the data from one mesh to another and ensures that the contents /// of MeshPointers remains consistent with respect to the mesh lengths. /// </summary> public static void DuplicateMesh(TR2Level level, TRMesh originalMesh, TRMesh replacementMesh) { int oldLength = originalMesh.Serialize().Length; originalMesh.Centre = replacementMesh.Centre; originalMesh.CollRadius = replacementMesh.CollRadius; originalMesh.ColouredRectangles = replacementMesh.ColouredRectangles; originalMesh.ColouredTriangles = replacementMesh.ColouredTriangles; originalMesh.Lights = replacementMesh.Lights; originalMesh.Normals = replacementMesh.Normals; originalMesh.NumColouredRectangles = replacementMesh.NumColouredRectangles; originalMesh.NumColouredTriangles = replacementMesh.NumColouredTriangles; originalMesh.NumNormals = replacementMesh.NumNormals; originalMesh.NumTexturedRectangles = replacementMesh.NumTexturedRectangles; originalMesh.NumTexturedTriangles = replacementMesh.NumTexturedTriangles; originalMesh.NumVertices = replacementMesh.NumVertices; originalMesh.TexturedRectangles = replacementMesh.TexturedRectangles; originalMesh.TexturedTriangles = replacementMesh.TexturedTriangles; originalMesh.Vertices = replacementMesh.Vertices; // The length will have changed so all pointers above the original one will need adjusting int lengthDiff = originalMesh.Serialize().Length - oldLength; List <uint> pointers = level.MeshPointers.ToList(); int pointerIndex = pointers.IndexOf(originalMesh.Pointer); for (int i = pointerIndex + 1; i < pointers.Count; i++) { if (pointers[i] > 0) { int newPointer = (int)pointers[i] + lengthDiff; pointers[i] = (uint)newPointer; } } level.MeshPointers = pointers.ToArray(); int numMeshData = (int)level.NumMeshData + lengthDiff / 2; level.NumMeshData = (uint)numMeshData; }
public TexturePacker(TR2Level level, ITextureClassifier classifier = null) { TileWidth = 256; TileHeight = 256; MaximumTiles = 16; Options = new PackingOptions { FillMode = PackingFillMode.Vertical, OrderMode = PackingOrderMode.Height, Order = PackingOrder.Descending, GroupMode = PackingGroupMode.None, StartMethod = PackingStartMethod.EndTile }; Level = level; _levelClassifier = classifier == null ? string.Empty : classifier.GetClassification(); _allTextures = new List <AbstractIndexedTRTexture>(); if (Level != null) { _allTextures.AddRange(LoadObjectTextures()); _allTextures.AddRange(LoadSpriteTextures()); _allTextures.Sort(new TRTextureReverseAreaComparer()); for (int i = 0; i < Level.NumImages; i++) { TexturedTile tile = AddTile(); tile.BitmapGraphics = GetTileBitmap(i); tile.AllowOverlapping = true; // Allow initially for the likes of Opera House - see tile 3 [128, 128] } foreach (AbstractIndexedTRTexture texture in _allTextures) { _tiles[texture.Atlas].AddTexture(texture); } } }
private int GetModelAnimationCount(TR2Level level, TRModel model) { TRModel nextModel = model; int modelIndex = level.Models.ToList().IndexOf(model) + 1; while (modelIndex < level.NumModels) { nextModel = level.Models[modelIndex++]; if (nextModel.Animation != ushort.MaxValue) { break; } } ushort nextStartAnimation = nextModel.Animation; if (model == nextModel) { nextStartAnimation = (ushort)level.NumAnimations; } return(model.Animation == ushort.MaxValue ? 0 : nextStartAnimation - model.Animation); }
public void FloorData_AppendFDActionListItemCamTest() { //Read Dragons Lair data TR2LevelReader reader = new TR2LevelReader(); TR2Level lvl = reader.ReadLevel("xian.tr2"); //Parse the floordata using FDControl FDControl fdataReader = new FDControl(); fdataReader.ParseFromLevel(lvl); //Add a music action to the trigger at index 6010 //This has a CamAction in its TrigList so this tests //that the Continue flag is correctly set FDTriggerEntry trigger = fdataReader.Entries[6010][1] as FDTriggerEntry; Assert.AreEqual(trigger.TrigActionList.Count, 2); Assert.IsNotNull(trigger.TrigActionList[1].CamAction); Assert.IsFalse(trigger.TrigActionList[1].CamAction.Continue); trigger.TrigActionList.Add(new FDActionListItem { TrigAction = FDTrigAction.PlaySoundtrack, Parameter = 40 }); //Write the data back fdataReader.WriteToLevel(lvl); //Check the CamAction has been updated Assert.AreEqual(trigger.TrigActionList.Count, 3); Assert.IsNotNull(trigger.TrigActionList[1].CamAction); Assert.IsTrue(trigger.TrigActionList[1].CamAction.Continue); //Check the music trigger has Continue set to false Assert.IsFalse(trigger.TrigActionList[2].Continue); }
private void RandomizeSecretTracks(TR2Level level, FDControl floorData) { // Generate new triggers for secrets to allow different sounds for each one List <TRAudioTrack> secretTracks = _tracks[TRAudioCategory.Secret]; Dictionary <int, TR2Entity> secrets = GetSecretItems(level); foreach (int entityIndex in secrets.Keys) { TR2Entity secret = secrets[entityIndex]; TRRoomSector sector = FDUtilities.GetRoomSector(secret.X, secret.Y, secret.Z, secret.Room, level, floorData); if (sector.FDIndex == 0) { // The secret is positioned on a tile that currently has no FD, so create it floorData.CreateFloorData(sector); } List <FDEntry> entries = floorData.Entries[sector.FDIndex]; FDTriggerEntry existingTriggerEntry = entries.Find(e => e is FDTriggerEntry) as FDTriggerEntry; bool existingEntityPickup = false; if (existingTriggerEntry != null) { if (existingTriggerEntry.TrigType == FDTrigType.Pickup && existingTriggerEntry.TrigActionList[0].Parameter == entityIndex) { // GW gold secret (default location) already has a pickup trigger to spawn the // TRex (or whatever enemy) so we'll just append to that item list here existingEntityPickup = true; } else { // There is already a non-pickup trigger for this sector so while nothing is wrong with // adding a pickup trigger, the game ignores it. So in this instance, the sound that // plays will just be whatever is set in the script. continue; } } // Generate a new music action FDActionListItem musicAction = new FDActionListItem { TrigAction = FDTrigAction.PlaySoundtrack, Parameter = secretTracks[_generator.Next(0, secretTracks.Count)].ID }; // For GW default gold, just append it if (existingEntityPickup) { existingTriggerEntry.TrigActionList.Add(musicAction); } else { entries.Add(new FDTriggerEntry { // The values here are replicated from Trigger#112 (in trview) in GW. // The first action list must be the entity being picked up and so // remaining action list items are actioned on pick up. Setup = new FDSetup { Value = 1028 }, TrigSetup = new FDTrigSetup { Value = 15872 }, TrigActionList = new List <FDActionListItem> { new FDActionListItem { TrigAction = FDTrigAction.Object, Parameter = (ushort)entityIndex }, musicAction } }); } } }
public void FloorData_InsertFDTest() { //Read Dragons Lair data TR2LevelReader reader = new TR2LevelReader(); TR2Level lvl = reader.ReadLevel("xian.tr2"); //Parse the floordata using FDControl FDControl fdataReader = new FDControl(); fdataReader.ParseFromLevel(lvl); //Find a sector that currently has no floor data int room, roomSector = -1; for (room = 0; room < lvl.NumRooms; room++) { roomSector = lvl.Rooms[room].SectorList.ToList().FindIndex(s => s.FDIndex == 0); if (roomSector != -1) { break; } } if (roomSector == -1) { Assert.Fail("Could not locate a Room Sector that does not have floor data associated with it."); } TRRoomSector sector = lvl.Rooms[room].SectorList[roomSector]; // Create a slot in the FD for this sector fdataReader.CreateFloorData(sector); Assert.AreNotEqual(sector.FDIndex, 0, "Sector does not have FD allocated."); // Add a music trigger fdataReader.Entries[sector.FDIndex].Add(new FDTriggerEntry { Setup = new FDSetup(FDFunctions.Trigger), TrigSetup = new FDTrigSetup(), TrigActionList = new List <FDActionListItem> { new FDActionListItem { TrigAction = FDTrigAction.PlaySoundtrack, Parameter = 40 } } }); //Write the data back fdataReader.WriteToLevel(lvl); //Save it and read it back in TR2LevelWriter writer = new TR2LevelWriter(); writer.WriteLevelToFile(lvl, "TEST.tr2"); lvl = reader.ReadLevel("TEST.tr2"); //Reassign the sector sector = lvl.Rooms[room].SectorList[roomSector]; fdataReader = new FDControl(); fdataReader.ParseFromLevel(lvl); //Ensure the sector still has FD associated with it Assert.AreNotEqual(sector.FDIndex, 0, "Sector no longer has FD after write/read."); //Verify there is one entry for this sector Assert.AreEqual(fdataReader.Entries[sector.FDIndex].Count, 1); //Verify the trigger we added matches what we expect FDEntry entry = fdataReader.Entries[sector.FDIndex][0]; Assert.IsTrue(entry is FDTriggerEntry); FDTriggerEntry triggerEntry = entry as FDTriggerEntry; Assert.IsTrue(triggerEntry.Setup.Function == (byte)FDFunctions.Trigger); Assert.IsTrue(triggerEntry.TrigActionList.Count == 1); Assert.IsTrue(triggerEntry.TrigActionList[0].TrigAction == FDTrigAction.PlaySoundtrack); Assert.IsTrue(triggerEntry.TrigActionList[0].Parameter == 40); }
public void FloorData_InsertRemoveFDEntryTest() { //Read Dragons Lair data TR2LevelReader reader = new TR2LevelReader(); TR2Level lvl = reader.ReadLevel("xian.tr2"); //Store the original floordata from the level ushort[] originalFData = new ushort[lvl.NumFloorData]; Array.Copy(lvl.FloorData, originalFData, lvl.NumFloorData); //Parse the floordata using FDControl FDControl fdataReader = new FDControl(); fdataReader.ParseFromLevel(lvl); //Verify index 9 has one entry and that it's currently //set as EndData for this index Assert.AreEqual(fdataReader.Entries[9].Count, 1); Assert.IsTrue(fdataReader.Entries[9][0].Setup.EndData); //Verify the next index is currently 9 + the entry's length List <int> indices = fdataReader.Entries.Keys.ToList(); int nextIndex = 9 + fdataReader.Entries[9][0].Flatten().Length; Assert.AreEqual(nextIndex, indices[indices.IndexOf(9) + 1]); //Add a music trigger to index 9 fdataReader.Entries[9].Add(new FDTriggerEntry { Setup = new FDSetup(FDFunctions.Trigger), TrigSetup = new FDTrigSetup(), TrigActionList = new List <FDActionListItem> { new FDActionListItem { TrigAction = FDTrigAction.PlaySoundtrack, Parameter = 40 } } }); //Write the data back fdataReader.WriteToLevel(lvl); //Verify index 9 has two entries, that its first entry //does not have EndData set, but that its second does Assert.AreEqual(fdataReader.Entries[9].Count, 2); Assert.IsFalse(fdataReader.Entries[9][0].Setup.EndData); Assert.IsTrue(fdataReader.Entries[9][1].Setup.EndData); //Verify the next index is now 9 + both the entry's lengths //Bear in mind the underlying dictionary's keys have changed indices = fdataReader.Entries.Keys.ToList(); nextIndex = 9 + fdataReader.Entries[9][0].Flatten().Length + fdataReader.Entries[9][1].Flatten().Length; Assert.AreEqual(nextIndex, indices[indices.IndexOf(9) + 1]); //Remove the new entry fdataReader.Entries[9].RemoveAt(1); //Write the data back fdataReader.WriteToLevel(lvl); //Verify index 9 again has one entry and that it's again //set as EndData for this index Assert.AreEqual(fdataReader.Entries[9].Count, 1); Assert.IsTrue(fdataReader.Entries[9][0].Setup.EndData); //Verify the next index is again 9 + the entry's length indices = fdataReader.Entries.Keys.ToList(); nextIndex = 9 + fdataReader.Entries[9][0].Flatten().Length; Assert.AreEqual(nextIndex, indices[indices.IndexOf(9) + 1]); //Finally compare to make sure the original fdata was written back. CollectionAssert.AreEqual(originalFData, lvl.FloorData, "Floordata does not match"); Assert.AreEqual((uint)lvl.FloorData.Length, lvl.NumFloorData); }
public void FloorData_ReadWriteTest() { //Read Dragons Lair data TR2LevelReader reader = new TR2LevelReader(); TR2Level lvl = reader.ReadLevel("xian.tr2"); //Store the original floordata from the level ushort[] originalFData = new ushort[lvl.NumFloorData]; Array.Copy(lvl.FloorData, originalFData, lvl.NumFloorData); //Parse the floordata using FDControl and re-write the parsed data back FDControl fdataReader = new FDControl(); fdataReader.ParseFromLevel(lvl); fdataReader.WriteToLevel(lvl); //Store the new floordata written back by FDControl ushort[] newFData = lvl.FloorData; //Compare to make sure the original fdata was written back. CollectionAssert.AreEqual(originalFData, newFData, "Floordata does not match"); Assert.AreEqual((uint)newFData.Length, lvl.NumFloorData); //Now modify an entry, and ensure it is different to the original data. FDPortalEntry portal = fdataReader.Entries[3][0] as FDPortalEntry; portal.Room = 42; fdataReader.WriteToLevel(lvl); //Test. FDIndex 3 of Dragon's Lair is a portal to room 3, which is being modified to Room 42. //Data should be: //New - [3] = 0x8001 and [4] = 0x002A //Get ref to new data newFData = lvl.FloorData; Assert.AreEqual(newFData[3], (ushort)0x8001); Assert.AreEqual(newFData[4], (ushort)0x002A); //Compare to make sure the modified fdata was written back. CollectionAssert.AreNotEqual(originalFData, newFData, "Floordata matches, change unsuccessful"); Assert.AreEqual((uint)newFData.Length, lvl.NumFloorData); //Test pattern/type matching example for fdata. bool isPortal = false; foreach (KeyValuePair <int, List <FDEntry> > sector in fdataReader.Entries) { foreach (FDEntry entry in sector.Value) { switch (entry) { case FDClimbEntry climbEntry: break; case FDKillLaraEntry killEntry: break; case FDPortalEntry portalEntry: isPortal = true; break; case FDSlantEntry slantEntry: break; case FDTriggerEntry triggerEntry: break; } } } Assert.IsTrue(isPortal); }
public void FloorData_ReadWriteOneShotTest() { //Read GW data TR2LevelReader reader = new TR2LevelReader(); TR2Level lvl = reader.ReadLevel("wall.tr2"); //Parse the floordata using FDControl FDControl fdataReader = new FDControl(); fdataReader.ParseFromLevel(lvl); //Get all triggers for entity ID 18 List <FDTriggerEntry> triggers = FDUtilities.GetEntityTriggers(fdataReader, 18); //There should be 3 Assert.AreEqual(triggers.Count, 3); //Verify none of the triggers has OneShot set foreach (FDTriggerEntry trigger in triggers) { Assert.IsFalse(trigger.TrigSetup.OneShot); } //Set OneShot on each trigger foreach (FDTriggerEntry trigger in triggers) { trigger.TrigSetup.SetOneShot(); } fdataReader.WriteToLevel(lvl); //Save it and read it back in TR2LevelWriter writer = new TR2LevelWriter(); writer.WriteLevelToFile(lvl, "TEST.tr2"); lvl = reader.ReadLevel("TEST.tr2"); fdataReader = new FDControl(); fdataReader.ParseFromLevel(lvl); //Get the triggers again afresh triggers = FDUtilities.GetEntityTriggers(fdataReader, 18); //Verify that they now have OneShot set foreach (FDTriggerEntry trigger in triggers) { Assert.IsTrue(trigger.TrigSetup.OneShot); } //Switch it off again foreach (FDTriggerEntry trigger in triggers) { trigger.TrigSetup.ClearOneShot(); } fdataReader.WriteToLevel(lvl); //Save it and read it back in writer.WriteLevelToFile(lvl, "TEST.tr2"); lvl = reader.ReadLevel("TEST.tr2"); fdataReader = new FDControl(); fdataReader.ParseFromLevel(lvl); //Get the triggers again afresh triggers = FDUtilities.GetEntityTriggers(fdataReader, 18); //Verify that they now once again do not have OneShot set foreach (FDTriggerEntry trigger in triggers) { Assert.IsFalse(trigger.TrigSetup.OneShot); } }
public void WriteToLevel(TR2Level lvl) { List <ushort> data = new List <ushort> { lvl.FloorData[0] // Index 0 is always dummy }; Dictionary <int, int> entryLengths = new Dictionary <int, int>(); foreach (KeyValuePair <int, List <FDEntry> > entry in Entries) { //Get the list of functions per sector List <FDEntry> functions = entry.Value; //Get the initial sector index into fdata int index = entry.Key; //Track the total length of the entry int entryLength = 0; for (int i = 0; i < functions.Count; i++) { FDEntry function = functions[i]; // Ensure EndData is set on the last function in the list only function.Setup.EndData = i == functions.Count - 1; //Convert function to ushort array ushort[] fdata = function.Flatten(); data.AddRange(fdata); //Store how many shorts there are entryLength += fdata.Length; } // Map the current index to its FD length entryLengths.Add(index, entryLength); } // Recalculate the indices based on the length of the floor data for each. // This allows for new entries to be added (or their entries added to) or removed. Dictionary <int, int> newIndices = new Dictionary <int, int>(); int newIndex = 1; // Always start at 1 as 0 is dummy data foreach (int index in entryLengths.Keys) { newIndices.Add(index, newIndex); // The next index will be this index plus the number of ushorts against it in the FD newIndex += entryLengths[index]; } // Update each TRRoomSector by repointing its FDIndex value to the newly calculated value. SortedDictionary <int, List <FDEntry> > _updatedEntries = new SortedDictionary <int, List <FDEntry> >(); foreach (TR2Room room in lvl.Rooms) { foreach (TRRoomSector sector in room.SectorList) { ushort index = sector.FDIndex; if (newIndices.ContainsKey(index)) { sector.FDIndex = (ushort)newIndices[index]; // Map the list of entries against the new index _updatedEntries.Add(sector.FDIndex, _entries[index]); } } } // Update the raw floor data in the level lvl.FloorData = data.ToArray(); lvl.NumFloorData = (uint)data.Count; // Update the stored values in case of further changes _entries = _updatedEntries; }
public static TRMesh GetModelFirstMesh(TR2Level level, TRModel model) { return(GetMesh(level, model.StartingMesh)); }
// Given a precompiled dictionary of old texture index to new, this will ensure that // all Meshes, RoomData and AnimatedTextures point to the new correct index. public static void ReindexTextures(this TR2Level level, Dictionary <int, int> indexMap, bool defaultToOriginal = true) { if (indexMap.Count == 0) { return; } foreach (TRMesh mesh in level.Meshes) { foreach (TRFace4 rect in mesh.TexturedRectangles) { rect.Texture = ConvertTextureReference(rect.Texture, indexMap, defaultToOriginal); } foreach (TRFace3 tri in mesh.TexturedTriangles) { tri.Texture = ConvertTextureReference(tri.Texture, indexMap, defaultToOriginal); } } foreach (TR2Room room in level.Rooms) { foreach (TRFace4 rect in room.RoomData.Rectangles) { rect.Texture = ConvertTextureReference(rect.Texture, indexMap, defaultToOriginal); } foreach (TRFace3 tri in room.RoomData.Triangles) { tri.Texture = ConvertTextureReference(tri.Texture, indexMap, defaultToOriginal); } } // #137 Ensure animated textures are reindexed too (these are just groups of texture indices) // They have to remain unique it seems, otherwise the animation speed is too fast, so while we // have removed the duplicated textures, we can re-add duplicate texture objects while there is // enough space in that array. List <TRObjectTexture> textures = level.ObjectTextures.ToList(); foreach (TRAnimatedTexture anim in level.AnimatedTextures) { for (int i = 0; i < anim.Textures.Length; i++) { anim.Textures[i] = ConvertTextureReference(anim.Textures[i], indexMap, defaultToOriginal); } ushort previousIndex = anim.Textures[0]; for (int i = 1; i < anim.Textures.Length; i++) { if (anim.Textures[i] == previousIndex && textures.Count < 2048) { textures.Add(textures[anim.Textures[i]]); anim.Textures[i] = (ushort)(textures.Count - 1); } previousIndex = anim.Textures[i]; } } if (textures.Count > level.NumObjectTextures) { level.ObjectTextures = textures.ToArray(); level.NumObjectTextures = (uint)textures.Count; } }
public TR2Level ReadLevel(string Filename) { if (!Filename.ToUpper().Contains("TR2")) { throw new NotImplementedException("File reader only supports TR2 levels"); } TR2Level level = new TR2Level(); reader = new BinaryReader(File.Open(Filename, FileMode.Open)); //Version level.Version = reader.ReadUInt32(); if (level.Version != TR2VersionHeader) { throw new NotImplementedException("File reader only suppors TR2 levels"); } //Colour palettes and textures level.Palette = PopulateColourPalette(reader.ReadBytes((int)MAX_PALETTE_SIZE * 3)); level.Palette16 = PopulateColourPalette16(reader.ReadBytes((int)MAX_PALETTE_SIZE * 4)); level.NumImages = reader.ReadUInt32(); level.Images8 = new TRTexImage8[level.NumImages]; level.Images16 = new TRTexImage16[level.NumImages]; //Initialize the texture arrays for (int i = 0; i < level.NumImages; i++) { level.Images8[i] = new TRTexImage8(); level.Images16[i] = new TRTexImage16(); } //For each texture8 there are 256 * 256 bytes (65536) we can just do a straight byte read for (int i = 0; i < level.NumImages; i++) { level.Images8[i].Pixels = reader.ReadBytes(256 * 256); } //For each texture16 there are 256 * 256 * 2 bytes (131072) for (int i = 0; i < level.NumImages; i++) { level.Images16[i].Pixels = new ushort[256 * 256]; for (int j = 0; j < level.Images16[i].Pixels.Count(); j++) { level.Images16[i].Pixels[j] = reader.ReadUInt16(); } } //Rooms level.Unused = reader.ReadUInt32(); level.NumRooms = reader.ReadUInt16(); level.Rooms = new TR2Room[level.NumRooms]; for (int i = 0; i < level.NumRooms; i++) { TR2Room room = new TR2Room(); //Grab info room.Info = new TRRoomInfo { X = reader.ReadInt32(), Z = reader.ReadInt32(), YBottom = reader.ReadInt32(), YTop = reader.ReadInt32() }; //Grab data room.NumDataWords = reader.ReadUInt32(); room.Data = new ushort[room.NumDataWords]; for (int j = 0; j < room.NumDataWords; j++) { room.Data[j] = reader.ReadUInt16(); } //Store what we just read room.RoomData = ConvertToRoomData(room); //Portals room.NumPortals = reader.ReadUInt16(); room.Portals = new TRRoomPortal[room.NumPortals]; for (int j = 0; j < room.NumPortals; j++) { room.Portals[j] = TR2FileReadUtilities.ReadRoomPortal(reader); } //Sectors room.NumZSectors = reader.ReadUInt16(); room.NumXSectors = reader.ReadUInt16(); room.SectorList = new TRRoomSector[room.NumXSectors * room.NumZSectors]; for (int j = 0; j < (room.NumXSectors * room.NumZSectors); j++) { room.SectorList[j] = TR2FileReadUtilities.ReadRoomSector(reader); } //Lighting room.AmbientIntensity = reader.ReadInt16(); room.AmbientIntensity2 = reader.ReadInt16(); room.LightMode = reader.ReadInt16(); room.NumLights = reader.ReadUInt16(); room.Lights = new TR2RoomLight[room.NumLights]; for (int j = 0; j < room.NumLights; j++) { room.Lights[j] = TR2FileReadUtilities.ReadRoomLight(reader); } //Static meshes room.NumStaticMeshes = reader.ReadUInt16(); room.StaticMeshes = new TR2RoomStaticMesh[room.NumStaticMeshes]; for (int j = 0; j < room.NumStaticMeshes; j++) { room.StaticMeshes[j] = TR2FileReadUtilities.ReadRoomStaticMesh(reader); } room.AlternateRoom = reader.ReadInt16(); room.Flags = reader.ReadInt16(); level.Rooms[i] = room; } //Floordata level.NumFloorData = reader.ReadUInt32(); level.FloorData = new ushort[level.NumFloorData]; for (int i = 0; i < level.NumFloorData; i++) { level.FloorData[i] = reader.ReadUInt16(); } //Mesh Data //This tells us how much mesh data (# of words/uint16s) coming up //just like the rooms previously. level.NumMeshData = reader.ReadUInt32(); level.RawMeshData = new ushort[level.NumMeshData]; for (int i = 0; i < level.NumMeshData; i++) { level.RawMeshData[i] = reader.ReadUInt16(); } //Mesh Pointers level.NumMeshPointers = reader.ReadUInt32(); level.MeshPointers = new uint[level.NumMeshPointers]; for (int i = 0; i < level.NumMeshPointers; i++) { level.MeshPointers[i] = reader.ReadUInt32(); } //Mesh Construction //level.Meshes = ConstructMeshData(level.NumMeshData, level.NumMeshPointers, level.RawMeshData); //Animations level.NumAnimations = reader.ReadUInt32(); level.Animations = new TRAnimation[level.NumAnimations]; for (int i = 0; i < level.NumAnimations; i++) { level.Animations[i] = TR2FileReadUtilities.ReadAnimation(reader); } //State Changes level.NumStateChanges = reader.ReadUInt32(); level.StateChanges = new TRStateChange[level.NumStateChanges]; for (int i = 0; i < level.NumStateChanges; i++) { level.StateChanges[i] = TR2FileReadUtilities.ReadStateChange(reader); } //Animation Dispatches level.NumAnimDispatches = reader.ReadUInt32(); level.AnimDispatches = new TRAnimDispatch[level.NumAnimDispatches]; for (int i = 0; i < level.NumAnimDispatches; i++) { level.AnimDispatches[i] = TR2FileReadUtilities.ReadAnimDispatch(reader); } //Animation Commands level.NumAnimCommands = reader.ReadUInt32(); level.AnimCommands = new TRAnimCommand[level.NumAnimCommands]; for (int i = 0; i < level.NumAnimCommands; i++) { level.AnimCommands[i] = TR2FileReadUtilities.ReadAnimCommand(reader); } //Mesh Trees level.NumMeshTrees = reader.ReadUInt32(); level.NumMeshTrees /= 4; level.MeshTrees = new TRMeshTreeNode[level.NumMeshTrees]; for (int i = 0; i < level.NumMeshTrees; i++) { level.MeshTrees[i] = TR2FileReadUtilities.ReadMeshTreeNode(reader); } //Frames level.NumFrames = reader.ReadUInt32(); level.Frames = new ushort[level.NumFrames]; for (int i = 0; i < level.NumFrames; i++) { level.Frames[i] = reader.ReadUInt16(); } //Models level.NumModels = reader.ReadUInt32(); level.Models = new TRModel[level.NumModels]; for (int i = 0; i < level.NumModels; i++) { level.Models[i] = TR2FileReadUtilities.ReadModel(reader); } //Static Meshes level.NumStaticMeshes = reader.ReadUInt32(); level.StaticMeshes = new TRStaticMesh[level.NumStaticMeshes]; for (int i = 0; i < level.NumStaticMeshes; i++) { level.StaticMeshes[i] = TR2FileReadUtilities.ReadStaticMesh(reader); } //Object Textures level.NumObjectTextures = reader.ReadUInt32(); level.ObjectTextures = new TRObjectTexture[level.NumObjectTextures]; for (int i = 0; i < level.NumObjectTextures; i++) { level.ObjectTextures[i] = TR2FileReadUtilities.ReadObjectTexture(reader); } //Sprite Textures level.NumSpriteTextures = reader.ReadUInt32(); level.SpriteTextures = new TRSpriteTexture[level.NumSpriteTextures]; for (int i = 0; i < level.NumSpriteTextures; i++) { level.SpriteTextures[i] = TR2FileReadUtilities.ReadSpriteTexture(reader); } //Sprite Sequences level.NumSpriteSequences = reader.ReadUInt32(); level.SpriteSequences = new TRSpriteSequence[level.NumSpriteSequences]; for (int i = 0; i < level.NumSpriteSequences; i++) { level.SpriteSequences[i] = TR2FileReadUtilities.ReadSpriteSequence(reader); } //Cameras level.NumCameras = reader.ReadUInt32(); level.Cameras = new TRCamera[level.NumCameras]; for (int i = 0; i < level.NumCameras; i++) { level.Cameras[i] = TR2FileReadUtilities.ReadCamera(reader); } //Sound Sources level.NumSoundSources = reader.ReadUInt32(); level.SoundSources = new TRSoundSource[level.NumSoundSources]; for (int i = 0; i < level.NumSoundSources; i++) { level.SoundSources[i] = TR2FileReadUtilities.ReadSoundSource(reader); } //Boxes level.NumBoxes = reader.ReadUInt32(); level.Boxes = new TR2Box[level.NumBoxes]; for (int i = 0; i < level.NumBoxes; i++) { level.Boxes[i] = TR2FileReadUtilities.ReadBox(reader); } //Overlaps & Zones level.NumOverlaps = reader.ReadUInt32(); level.Overlaps = new ushort[level.NumOverlaps]; level.Zones = new short[10 * level.NumBoxes]; for (int i = 0; i < level.NumOverlaps; i++) { level.Overlaps[i] = reader.ReadUInt16(); } for (int i = 0; i < level.Zones.Count(); i++) { level.Zones[i] = reader.ReadInt16(); } //Animated Textures level.NumAnimatedTextures = reader.ReadUInt32(); level.AnimatedTextures = new ushort[level.NumAnimatedTextures]; for (int i = 0; i < level.NumAnimatedTextures; i++) { level.AnimatedTextures[i] = reader.ReadUInt16(); } //Entities level.NumEntities = reader.ReadUInt32(); level.Entities = new TR2Entity[level.NumEntities]; for (int i = 0; i < level.NumEntities; i++) { level.Entities[i] = TR2FileReadUtilities.ReadEntity(reader); } //Light Map - 32 * 256 = 8192 bytes level.LightMap = new byte[32 * 256]; for (int i = 0; i < level.LightMap.Count(); i++) { level.LightMap[i] = reader.ReadByte(); } //Cinematic Frames level.NumCinematicFrames = reader.ReadUInt16(); level.CinematicFrames = new TRCinematicFrame[level.NumCinematicFrames]; for (int i = 0; i < level.NumCinematicFrames; i++) { level.CinematicFrames[i] = TR2FileReadUtilities.ReadCinematicFrame(reader); } //Demo Data level.NumDemoData = reader.ReadUInt16(); level.DemoData = new byte[level.NumDemoData]; for (int i = 0; i < level.NumDemoData; i++) { level.DemoData[i] = reader.ReadByte(); } //Sound Map (370 shorts = 740 bytes) & Sound Details level.SoundMap = new short[370]; for (int i = 0; i < level.SoundMap.Count(); i++) { level.SoundMap[i] = reader.ReadInt16(); } level.NumSoundDetails = reader.ReadUInt32(); level.SoundDetails = new TRSoundDetails[level.NumSoundDetails]; for (int i = 0; i < level.NumSoundDetails; i++) { level.SoundDetails[i] = TR2FileReadUtilities.ReadSoundDetails(reader); } //Samples level.NumSampleIndices = reader.ReadUInt32(); level.SampleIndices = new uint[level.NumSampleIndices]; for (int i = 0; i < level.NumSampleIndices; i++) { level.SampleIndices[i] = reader.ReadUInt32(); } Debug.Assert(reader.BaseStream.Position == reader.BaseStream.Length); reader.Close(); return(level); }
public HtmlTileBuilder(TR2Level level) { _level = level; }
public static int GetFreeObjectTextureCount(this TR2Level level) { return(2048 - (int)level.NumObjectTextures + level.GetInvalidObjectTextureIndices().Count); }
public static TextureLevelMapping Get(TR2Level level, string mappingFilePrefix, TextureDatabase database, Dictionary <StaticTextureSource, List <StaticTextureTarget> > predefinedMapping = null, List <TR2Entities> entitiesToIgnore = null) { string mapFile = Path.Combine(@"Resources\Textures\Mapping\", mappingFilePrefix + "-Textures.json"); if (!File.Exists(mapFile)) { return(null); } Dictionary <DynamicTextureSource, DynamicTextureTarget> dynamicMapping = new Dictionary <DynamicTextureSource, DynamicTextureTarget>(); Dictionary <StaticTextureSource, List <StaticTextureTarget> > staticMapping = new Dictionary <StaticTextureSource, List <StaticTextureTarget> >(); Dictionary <StaticTextureSource, Dictionary <int, List <LandmarkTextureTarget> > > landmarkMapping = new Dictionary <StaticTextureSource, Dictionary <int, List <LandmarkTextureTarget> > >(); Color skyBoxColour = _defaultSkyBox; Dictionary <string, object> rootMapping = JsonConvert.DeserializeObject <Dictionary <string, object> >(File.ReadAllText(mapFile)); // Read the dynamic mapping - this holds object and sprite texture indices for the level to which we will apply an HSB operation if (rootMapping.ContainsKey("Dynamic")) { SortedDictionary <string, Dictionary <string, object> > mapping = JsonConvert.DeserializeObject <SortedDictionary <string, Dictionary <string, object> > >(rootMapping["Dynamic"].ToString()); foreach (string sourceName in mapping.Keys) { DynamicTextureSource source = database.GetDynamicSource(sourceName); DynamicTextureTarget target = new DynamicTextureTarget { DefaultTileTargets = JsonConvert.DeserializeObject <Dictionary <int, List <Rectangle> > >(mapping[sourceName]["Default"].ToString()) }; if (mapping[sourceName].ContainsKey("Optional")) { target.OptionalTileTargets = JsonConvert.DeserializeObject <Dictionary <TextureCategory, Dictionary <int, List <Rectangle> > > >(mapping[sourceName]["Optional"].ToString()); } dynamicMapping[source] = target; } } // The static mapping contains basic texture segment source to tile target locations if (rootMapping.ContainsKey("Static")) { SortedDictionary <string, object> mapping = JsonConvert.DeserializeObject <SortedDictionary <string, object> >(rootMapping["Static"].ToString()); foreach (string sourceName in mapping.Keys) { staticMapping[database.GetStaticSource(sourceName)] = JsonConvert.DeserializeObject <List <StaticTextureTarget> >(mapping[sourceName].ToString()); } } // Landmark mapping links static sources to room number -> rectangle/triangle indices if (rootMapping.ContainsKey("Landmarks")) { Dictionary <string, Dictionary <int, List <LandmarkTextureTarget> > > mapping = JsonConvert.DeserializeObject <Dictionary <string, Dictionary <int, List <LandmarkTextureTarget> > > >(rootMapping["Landmarks"].ToString()); foreach (string sourceName in mapping.Keys) { landmarkMapping[database.GetStaticSource(sourceName)] = mapping[sourceName]; } } // If a level has had textures removed externally, but the JSON file has static // imports ready for it, we need to make sure they are ignored. if (entitiesToIgnore != null) { List <StaticTextureSource> sources = new List <StaticTextureSource>(staticMapping.Keys); for (int i = 0; i < sources.Count; i++) { StaticTextureSource source = sources[i]; if (source.TextureEntities != null) { foreach (TR2Entities entity in source.TextureEntities) { if (entitiesToIgnore.Contains(entity)) { staticMapping.Remove(source); break; } } } } } // Allows for dynamic mapping to be targeted at levels e.g. when importing non-native // models that are otherwise undefined in the default level JSON data. // This should be done after removing ignored entity textures, for the likes of when // Lara is being replaced. if (predefinedMapping != null) { foreach (StaticTextureSource source in predefinedMapping.Keys) { staticMapping[source] = predefinedMapping[source]; } } // Add global sources, unless they are already defined. These tend to be sprite sequences // so they will be mapped per GenerateSpriteSequenceTargets, but there is also scope to // define global targets if relevant. foreach (StaticTextureSource source in database.GlobalGrouping.Sources.Keys) { if (!staticMapping.ContainsKey(source)) { staticMapping[source] = new List <StaticTextureTarget>(database.GlobalGrouping.Sources[source]); } } // Apply grouping to what has been selected as source elements List <TextureGrouping> staticGrouping = database.GlobalGrouping.GetGrouping(staticMapping.Keys); return(new TextureLevelMapping(level) { DynamicMapping = dynamicMapping, StaticMapping = staticMapping, StaticGrouping = staticGrouping, LandmarkMapping = landmarkMapping, DefaultSkyBox = skyBoxColour }); }
private TextureLevelMapping(TR2Level level) { _level = level; _tileMap = new Dictionary <int, BitmapGraphics>(); _committed = false; }
public void SaveLevel(TR2Level level, string name) { string fullPath = Path.Combine(BasePath, name); _writer.WriteLevelToFile(level, fullPath); }
protected void LoadLevelInstance(TR23ScriptedLevel scriptedLevel) { _scriptedLevelInstance = scriptedLevel; _levelInstance = LoadLevel(scriptedLevel.LevelFileBaseName); }
public void ParseFromLevel(TR2Level lvl) { _entries = new SortedDictionary <int, List <FDEntry> >(); foreach (TR2Room room in lvl.Rooms) { foreach (TRRoomSector sector in room.SectorList) { //Index into FData is FDIndex ushort index = sector.FDIndex; //Index 0 is a dummy if (index == 0) { continue; } //List of floordata functions for the sector List <FDEntry> floordataFunctions = new List <FDEntry>(); while (true) { FDSetup data = new FDSetup() { Value = lvl.FloorData[index] }; switch ((FDFunctions)data.Function) { case FDFunctions.PortalSector: FDPortalEntry portal = new FDPortalEntry() { Setup = new FDSetup() { Value = lvl.FloorData[index] }, Room = lvl.FloorData[++index] }; floordataFunctions.Add(portal); break; case FDFunctions.FloorSlant: FDSlantEntry floorSlant = new FDSlantEntry() { Setup = new FDSetup() { Value = lvl.FloorData[index] }, SlantValue = lvl.FloorData[++index], Type = FDSlantEntryType.FloorSlant }; floordataFunctions.Add(floorSlant); break; case FDFunctions.CeilingSlant: FDSlantEntry ceilingSlant = new FDSlantEntry() { Setup = new FDSetup() { Value = lvl.FloorData[index] }, SlantValue = lvl.FloorData[++index], Type = FDSlantEntryType.CeilingSlant }; floordataFunctions.Add(ceilingSlant); break; case FDFunctions.Trigger: FDTriggerEntry trig = new FDTriggerEntry() { Setup = new FDSetup() { Value = lvl.FloorData[index] }, TrigSetup = new FDTrigSetup() { Value = lvl.FloorData[++index] } }; if (trig.TrigType == FDTrigType.Switch || trig.TrigType == FDTrigType.Key) { //First entry in action list is reference to switch/key entity for switch/key types. trig.SwitchOrKeyRef = lvl.FloorData[++index]; } //We don't know if there are any more yet. bool continueFDParse; //Parse trigactions do { //New trigger action FDActionListItem action = new FDActionListItem() { Value = lvl.FloorData[++index] }; continueFDParse = action.Continue; if (action.TrigAction == FDTrigAction.Camera) { //Camera trig actions have a special extra uint16... FDCameraAction camAction = new FDCameraAction() { Value = lvl.FloorData[++index] }; //store associated camera action action.CamAction = camAction; //Is there more? continueFDParse = camAction.Continue; } //add action trig.TrigActionList.Add(action); } while (index < lvl.NumFloorData && continueFDParse); floordataFunctions.Add(trig); break; case FDFunctions.KillLara: FDKillLaraEntry kill = new FDKillLaraEntry() { Setup = new FDSetup() { Value = lvl.FloorData[index] } }; floordataFunctions.Add(kill); break; case FDFunctions.ClimbableWalls: FDClimbEntry climb = new FDClimbEntry() { Setup = new FDSetup() { Value = lvl.FloorData[index] } }; floordataFunctions.Add(climb); break; case FDFunctions.FloorTriangulationNWSE_Solid: //TR3 Only - Ignore for now... break; case FDFunctions.FloorTriangulationNESW_Solid: //TR3 Only - Ignore for now... break; case FDFunctions.CeilingTriangulationNW_Solid: //TR3 Only - Ignore for now... break; case FDFunctions.CeilingTriangulationNE_Solid: //TR3 Only - Ignore for now... break; case FDFunctions.FloorTriangulationNWSE_SW: //TR3 Only - Ignore for now... break; case FDFunctions.FloorTriangulationNWSE_NE: //TR3 Only - Ignore for now... break; case FDFunctions.FloorTriangulationNESW_SW: //TR3 Only - Ignore for now... break; case FDFunctions.FloorTriangulationNESW_NW: //TR3 Only - Ignore for now... break; case FDFunctions.CeilingTriangulationNW_SW: //TR3 Only - Ignore for now... break; case FDFunctions.CeilingTriangulationNW_NE: //TR3 Only - Ignore for now... break; case FDFunctions.CeilingTriangulationNE_NW: //TR3 Only - Ignore for now... break; case FDFunctions.CeilingTriangulationNE_SE: //TR3 Only - Ignore for now... break; case FDFunctions.Monkeyswing: //TR3 Only - Ignore for now... break; case FDFunctions.DeferredTriggeringOrMinecartRotateLeft: //TR3 Only - Ignore for now... break; case FDFunctions.MechBeetleOrMinecartRotateRight: //TR3 Only - Ignore for now... break; default: break; } if (data.EndData) { //End data (from what I understand) means there is no further functions for this sector. //E.G. Sector 52 on Xian has a slant function and portal function. EndData is not set on //slant function, but is on portal function as there are no further functions. break; } else { //There are further functions for this sector - continue parsing. index++; } } //Store the sector index and all of its associated functions _entries.Add(sector.FDIndex, floordataFunctions); } } }