diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 4bb8548..03e8af0 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -22,4 +22,4 @@ jobs: - name: Build run: dotnet build --no-restore --configuration Debug - name: Test - run: dotnet test --no-restore --verbosity normal --filter "Category!=RequiresArk&Category!=RequiresSettings" \ No newline at end of file + run: dotnet test --no-restore --verbosity normal --filter "Category!=RequiresSettings&Category!=FishyTests" \ No newline at end of file diff --git a/ExtractingBuffersOfEverything/Program.cs b/ExtractingBuffersOfEverything/Program.cs index f4323c5..ef901db 100644 --- a/ExtractingBuffersOfEverything/Program.cs +++ b/ExtractingBuffersOfEverything/Program.cs @@ -1,25 +1,24 @@ // See https://aka.ms/new-console-template for more information -using System.Net.Mime; -using UWRandomizerEditor; -using UWRandomizerEditor.LEVDotARK; -using static UWRandomizerEditor.Utils; +using UWRandomizerEditor.LEVdotARK; -namespace ExtractingEverything; +namespace ExtractingBuffersOfEverything; public static class Program { public static void Main() { - var path = @"C:\Users\Karl\Desktop\UnderworldStudy\UW\DATA\LEV.ARK"; - var baseBufferPath = @"C:\Users\Karl\Desktop\UnderworldStudy\Buffers"; + var path = + @"C:\Users\Karl\Desktop\UnderworldStudy\Seeing what is being fixed by UltimateEditor\arkcleaned_nodoors_fixed.bin"; + var baseBufferPath = + @"C:\Users\Karl\Desktop\UnderworldStudy\Seeing what is being fixed by UltimateEditor\Cleaned-nodoors-fixed"; Directory.CreateDirectory(baseBufferPath); var Ark = new ArkLoader(path); // Header - Ark.header.SaveBuffer(baseBufferPath, "header.bin"); + UWRandomizerEditor.Utils.StdSaveBuffer(Ark.header, baseBufferPath, "header.bin"); // Blocks int counter_block = 0; @@ -27,8 +26,8 @@ public static void Main() Directory.CreateDirectory(blockPath); foreach (var block in Ark.blocks) { - block.SaveBuffer(blockPath, - $"Block{counter_block}_level{block.LevelNumber}_length{block.TotalBlockLength}.bin"); + UWRandomizerEditor.Utils.StdSaveBuffer(block, blockPath, + $"Block{counter_block}_level{block.LevelNumber}_length{block.Buffer.Length}.bin"); counter_block++; } @@ -42,23 +41,27 @@ public static void Main() var nthTileMapBlockPath = Path.Join(tilemapBlocksPath, $"TileMapBlock{counter_block}"); Directory.CreateDirectory(nthTileMapBlockPath); - block.SaveBuffer(nthTileMapBlockPath, $"TileMapBlock{counter_block}_fullbuffer.bin"); - StdSaveBuffer(block.TileMapBuffer, nthTileMapBlockPath, $"TileMapBuffer{counter_block}_fullbuffer.bin"); - StdSaveBuffer(block.MobileObjectInfoBuffer, nthTileMapBlockPath, - $"MobileObjectInfoBuffer{counter_block}_fullbuffer.bin"); - StdSaveBuffer(block.StaticObjectInfoBuffer, nthTileMapBlockPath, - $"StaticObjectInfoBuffer{counter_block}_fullbuffer.bin"); - StdSaveBuffer(block.FreeListMobileObjectBuffer, nthTileMapBlockPath, - $"FreeListMobileObject{counter_block}_fullbuffer.bin"); - StdSaveBuffer(block.FreeListStaticObjectBuffer, nthTileMapBlockPath, - $"FreeListStaticObject{counter_block}_fullbuffer.bin"); + UWRandomizerEditor.Utils.StdSaveBuffer(block, nthTileMapBlockPath, + $"TileMapBlock{counter_block}_fullbuffer.bin"); + File.WriteAllBytes(Path.Combine(nthTileMapBlockPath, $"TileMapBuffer{counter_block}_fullbuffer.bin"), + block.TileMapBuffer); + File.WriteAllBytes( + Path.Combine(nthTileMapBlockPath, $"MobileObjectInfoBuffer{counter_block}_fullbuffer.bin"), + block.MobileObjectInfoBuffer); + File.WriteAllBytes( + Path.Combine(nthTileMapBlockPath, $"StaticObjectInfoBuffer{counter_block}_fullbuffer.bin"), + block.StaticObjectInfoBuffer); + File.WriteAllBytes(Path.Combine(nthTileMapBlockPath, $"FreeListMobileObject{counter_block}_fullbuffer.bin"), + block.FreeListMobileObjectBuffer); + File.WriteAllBytes(Path.Combine(nthTileMapBlockPath, $"FreeListStaticObject{counter_block}_fullbuffer.bin"), + block.FreeListStaticObjectBuffer); var counter_objects = 0; // Save Mobile Object buffers counter_objects = 0; foreach (var mobileObject in block.MobileObjects) { - mobileObject.SaveBuffer(nthTileMapBlockPath, + UWRandomizerEditor.Utils.StdSaveBuffer(mobileObject, nthTileMapBlockPath, $"MobileObjectIdx{mobileObject.IdxAtObjectArray}_ctr{counter_objects}.bin"); counter_objects++; } @@ -67,7 +70,7 @@ public static void Main() // Doesn't reset to 0. foreach (var staticObject in block.StaticObjects) { - staticObject.SaveBuffer(nthTileMapBlockPath, + UWRandomizerEditor.Utils.StdSaveBuffer(staticObject, nthTileMapBlockPath, $"StaticObjectIdx{staticObject.IdxAtObjectArray}_ctr{counter_objects}.bin"); counter_objects++; } @@ -79,14 +82,14 @@ public static void Main() int MobileDuplicateCounter = 0; // Save free list Mobile objects buffers counter_objects = 0; - foreach (var mobileFreeObject in block.FreeListMobileObject) + foreach (var mobileFreeObject in block.FreeListMobileObjects) { - sw.WriteLine($"Mobile Free Object entry {counter_objects} has value {mobileFreeObject.Entry}"); - StdSaveBuffer(mobileFreeObject.Buffer, nthTileMapBlockPath, + sw.WriteLine($"Mobile Free Object entry {counter_objects} has value {mobileFreeObject.IdxAtArray}"); + UWRandomizerEditor.Utils.StdSaveBuffer(mobileFreeObject, nthTileMapBlockPath, $"mobileFreeObjectIdx{mobileFreeObject.EntryNum}_ctr{counter_objects}.bin"); counter_objects++; MobileDuplicateCounter += - setMobile.Add(mobileFreeObject.Entry) + setMobile.Add(mobileFreeObject.IdxAtArray) ? 0 : 1; // Reminder: Add returns false if element is already present } @@ -94,19 +97,19 @@ public static void Main() var setStatic = new HashSet(); int StaticDuplicateCounter = 0; // Save free list Static objects buffers - foreach (var staticFreeObject in block.FreeListStaticObject) + foreach (var staticFreeObject in block.FreeListStaticObjects) { - sw.WriteLine($"Static Free Object entry {counter_objects} has value {staticFreeObject.Entry}"); - StdSaveBuffer(staticFreeObject.Buffer, nthTileMapBlockPath, + sw.WriteLine($"Static Free Object entry {counter_objects} has value {staticFreeObject.IdxAtArray}"); + UWRandomizerEditor.Utils.StdSaveBuffer(staticFreeObject, nthTileMapBlockPath, $"staticFreeObjectIdx{staticFreeObject.EntryNum}_ctr{counter_objects}.bin"); counter_objects++; StaticDuplicateCounter += - setStatic.Add(staticFreeObject.Entry) + setStatic.Add(staticFreeObject.IdxAtArray) ? 0 : 1; // Reminder: Add returns false if element is already present } - sw.WriteLine($"Summary: Mobile list contains {block.FreeListMobileObject.Length} entries of which" + + sw.WriteLine($"Summary: Mobile list contains {block.FreeListMobileObjects.Length} entries of which" + $" {MobileDuplicateCounter} are duplicates." + $" Indexes present in Mobile List: {string.Join(",", setMobile.OrderBy(x => x))}." ); @@ -114,7 +117,7 @@ public static void Main() allMobileIdxs.ExceptWith(setMobile); sw.WriteLine($"Indexes not present: {string.Join(",", allMobileIdxs)}"); - sw.WriteLine($"Summary: Static list contains {block.FreeListStaticObject.Length} entries of which" + + sw.WriteLine($"Summary: Static list contains {block.FreeListStaticObjects.Length} entries of which" + $" {StaticDuplicateCounter} are duplicates." + $" Indexes present in Mobile List: {string.Join(",", setStatic.OrderBy(x => x))}"); var allStaticIdxs = Enumerable.Range(256, 1024 - 256).ToHashSet(); @@ -126,14 +129,14 @@ public static void Main() counter_objects = 0; foreach (var tile in block.TileInfos) { - tile.SaveBuffer(nthTileMapBlockPath, + UWRandomizerEditor.Utils.StdSaveBuffer(tile, nthTileMapBlockPath, $"TileIdx{counter_objects}Offset{tile.Offset},X{tile.XYPos[0]}Y{tile.XYPos[1]}.bin"); counter_objects++; } counter_block++; - } + #endregion #region TextureMappingBlock @@ -143,7 +146,8 @@ public static void Main() Directory.CreateDirectory(TextureMappingBlocksPath); foreach (var textMapBlock in Ark.TextMapBlocks) { - textMapBlock.SaveBuffer(TextureMappingBlocksPath, $"fullbuffer_{counter_block}.bin"); + UWRandomizerEditor.Utils.StdSaveBuffer(textMapBlock, TextureMappingBlocksPath, + $"fullbuffer_{counter_block}.bin"); counter_block++; } @@ -156,7 +160,8 @@ public static void Main() Directory.CreateDirectory(ObjectAnimationOverlayMapPath); foreach (var objAnimBlock in Ark.ObjAnimBlocks) { - objAnimBlock.SaveBuffer(ObjectAnimationOverlayMapPath, $"fullbuffer_{counter_block}.bin"); + UWRandomizerEditor.Utils.StdSaveBuffer(objAnimBlock, ObjectAnimationOverlayMapPath, + $"fullbuffer_{counter_block}.bin"); counter_block++; } @@ -169,7 +174,7 @@ public static void Main() Directory.CreateDirectory(MapNotesBlockPath); foreach (var mapNotesBlock in Ark.MapNotesBlocks) { - mapNotesBlock.SaveBuffer(MapNotesBlockPath, $"fullbuffer_{counter_block}.bin"); + UWRandomizerEditor.Utils.StdSaveBuffer(mapNotesBlock, MapNotesBlockPath, $"fullbuffer_{counter_block}.bin"); counter_block++; } @@ -182,7 +187,8 @@ public static void Main() Directory.CreateDirectory(AutomapInfosBlockPath); foreach (var automapInfosBlock in Ark.AutomapBlocks) { - automapInfosBlock.SaveBuffer(AutomapInfosBlockPath, $"fullbuffer_{counter_block}.bin"); + UWRandomizerEditor.Utils.StdSaveBuffer(automapInfosBlock, AutomapInfosBlockPath, + $"fullbuffer_{counter_block}.bin"); counter_block++; } diff --git a/RandomizerUnitTests/ArkLoaderTest.cs b/RandomizerUnitTests/ArkLoaderTest.cs deleted file mode 100644 index 83a5e6d..0000000 --- a/RandomizerUnitTests/ArkLoaderTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.IO; -using NUnit.Framework; -using UWRandomizerEditor; -using UWRandomizerEditor.LEVDotARK; - -namespace RandomizerUnitTests; - -class ArkLoaderTest -{ - [Test] - [Category("RequiresArk")] - public void CompareLoadSerialize() - { - var AL = new ArkLoader(Settings.DefaultArkPath); - Assert.True(AL.CompareCurrentArkWithHash()); - AL.arkbuffer = AL.ReconstructBufferFromBlocks(); - string savedpath = AL.SaveBuffer(Path.GetDirectoryName(Settings.DefaultArkPath)); - var AL2 = new ArkLoader(savedpath); - Assert.True(AL2.CompareCurrentArkWithHash()); - - for (int i = 0; i < AL.arkbuffer.Length; i++) - { - if (AL.arkbuffer[i] != AL2.arkbuffer[i]) - { - Console.WriteLine($"Failed LEV.ARK comparison at byte {i}"); - } - } - } - // TODO: Make one here to test the buffer lengths -} diff --git a/RandomizerUnitTests/HeaderTest.cs b/RandomizerUnitTests/HeaderTest.cs deleted file mode 100644 index f58cc27..0000000 --- a/RandomizerUnitTests/HeaderTest.cs +++ /dev/null @@ -1,75 +0,0 @@ -using NUnit.Framework; -using UWRandomizerEditor.LEVDotARK; -using static UWRandomizerEditor.Settings; - -namespace RandomizerUnitTests; - -[Category("RequiresArk")] -public class HeaderTestUW1 -{ - private ArkLoader AL; - private Header header; - static int validEntriesWithInfo = 9 * 3; - static int validEntries = 45; - static int emptyEntries = 90; - static int totalEntries = validEntries + emptyEntries; - - [SetUp] - public void SetUp() - { - AL = new ArkLoader(DefaultArkPath); - Assert.True(AL.CompareCurrentArkWithHash()); // This is supposed to work with pristine UW1 lev.ark - header = AL.header; - } - // 542 - [Test] - public void TestHeaderSize() - { - Assert.True(header.Size == header.buffer.Length); - } - - [Test] - public void TestGetOffsetForBlock() - { - for (int i = 0; i < totalEntries; i++) - { - int offset = header.GetOffsetForBlock(i); - if (i < validEntriesWithInfo) - { - Assert.True(offset > 0); - } - else - { - Assert.True(offset == 0); - } - } - } - - [Test] - public void TestReadOffsets() - { - int[] validEntryOffsets = new[] - { - // Level tilemap... - 542, 32294, 64046, 95798, 127550, 159302, 191054, 222806, 254558, - // Animation overlay - 286310, 286694, 287078, 287462, 287846, 288230, 288614, 288998, 289382, - // Texturemapping - 289766, 289888, 290010, 290132, 290254, 290376, 290498, 290620, 290742, - // automap info - 0, 0, 0, 0, 0, 0, 0, 0, 0, - // map notes - 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - - for (int i = 0; i < validEntries; i++) - { - Assert.True(header.BlockOffsets[i] == validEntryOffsets[i]); - } - - for (int i = validEntries; i < totalEntries; i++) - { - Assert.True(header.BlockOffsets[i] == 0); - } - } -} \ No newline at end of file diff --git a/RandomizerUnitTests/TestGameObjectEquality.cs b/RandomizerUnitTests/TestGameObjectEquality.cs deleted file mode 100644 index c104a91..0000000 --- a/RandomizerUnitTests/TestGameObjectEquality.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using NUnit.Framework; -using UWRandomizerEditor; -using UWRandomizerEditor.LEVDotARK.GameObjects; - -namespace RandomizerUnitTests; - -class TestGameObjectEquality -{ - [Test] - public void TwoEqGameObjects() - { - var obj1 = new GameObject( - objid_flagsField: 0xFFF, - positionField: 0xFFF, - quality_chainField: 0xFFF, - link_specialField: 0xFFF - ); - - var obj1_copy = new GameObject( - objid_flagsField: 0xFFF, - positionField: 0xFFF, - quality_chainField: 0xFFF, - link_specialField: 0xFFF - ); - - Assert.True(obj1.Equals(obj1_copy)); - } - - [Test] - public void TwoDiffGameObjects() - { - var obj1 = new GameObject( - objid_flagsField: 0xFFF, - positionField: 0xFFF, - quality_chainField: 0xFFF, - link_specialField: 0xFFF - ); - - var obj2 = new GameObject( - objid_flagsField: 0xCCC, - positionField: 0xCCC, - quality_chainField: 0xCCC, - link_specialField: 0xCCC - ); - - Assert.False(obj1.Equals(obj2)); - } - - [Test] - public void GOAndQtty() - { - var obj1 = new GameObject( - objid_flagsField: 0xFFF, - positionField: 0xFFF, - quality_chainField: 0xFFF, - link_specialField: 0xFFF - ); - - var obj2 = new QuantityGameObject( - objid_flagsField: 0xFFF, - positionField: 0xFFF, - quality_chainField: 0xFFF, - link_specialField: 0xFFF - ); - - Assert.False(obj1.Equals(obj2)); - } - - [Test] - public void StaticAndMobileObjects() - { - var staticObj = new GameObject( - objid_flagsField: 0xFFF, - positionField: 0xFFF, - quality_chainField: 0xFFF, - link_specialField: 0xFFF - ); - - var mobileObj = new MobileObject(staticObj.Buffer, 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 10); - - Assert.False(staticObj.Equals(mobileObj)); - } -} diff --git a/RandomizerUnitTests/TestTileInfo.cs b/RandomizerUnitTests/TestTileInfo.cs deleted file mode 100644 index b997739..0000000 --- a/RandomizerUnitTests/TestTileInfo.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using NUnit.Framework; -using UWRandomizerEditor.LEVDotARK; -using UWRandomizerEditor.LEVDotARK.GameObjects; -using UWRandomizerEditor; - -namespace RandomizerUnitTests; - -[TestFixture] -public class TestTileInfo -{ - // Let's create one random entry from an int. - // And another from a buffer - private TileInfo tinfo1; - private TileInfo tinfo2; - private TileInfo tinfo3; - - [SetUp] - public void Setup() - { - tinfo1 = new TileInfo(0, 240, 0, 0); - tinfo2 = new TileInfo(0, BitConverter.GetBytes(240), 0, 0); - tinfo3 = new TileInfo(0, BitConverter.GetBytes(241), 0, 0); - } - - [Test] - public void ComparingConstructors() - { - Assert.True(tinfo1.Equals(tinfo2)); - Assert.False(tinfo1.Equals(tinfo3)); - Assert.False(tinfo2.Equals(tinfo3)); - } - - // todo: break up this massive test. Remove hardcoded paths - [Category("RequiresSettings")] - [Test] - public void SavingBufferAndReloading() - { - // Compare the buffers as-is - Assert.True(tinfo1.TileBuffer.SequenceEqual(tinfo2.TileBuffer)); - string tinfo1Path = tinfo1.SaveBuffer(basePath: Settings.DefaultBinaryTestsPath, filename: "buffer_tinfo1"); - string tinfo2Path = tinfo2.SaveBuffer(basePath: Settings.DefaultBinaryTestsPath, filename: "buffer_tinfo2"); - string tinfo3Path = tinfo3.SaveBuffer(basePath: Settings.DefaultBinaryTestsPath, filename: "buffer_tinfo3"); - - // Compare their hashes - SHA256 mySHA256 = SHA256.Create(); - var tinfo1Hash = mySHA256.ComputeHash(tinfo1.TileBuffer); - var tinfo2Hash = mySHA256.ComputeHash(tinfo2.TileBuffer); - var tinfo3Hash = mySHA256.ComputeHash(tinfo3.TileBuffer); - Assert.True(tinfo1Hash.SequenceEqual(tinfo2Hash)); - Assert.False(tinfo1Hash.SequenceEqual(tinfo3Hash)); - Assert.False(tinfo2Hash.SequenceEqual(tinfo3Hash)); - - // Reload the buffers - var tinfo1RelBuffer = LoadTileData(tinfo1Path); - var tinfo2RelBuffer = LoadTileData(tinfo2Path); - var tinfo3RelBuffer = LoadTileData(tinfo3Path); - - // Compare the hashes again - var rectinfo1Hash = mySHA256.ComputeHash(tinfo1RelBuffer); - var rectinfo2Hash = mySHA256.ComputeHash(tinfo2RelBuffer); - var rectinfo3Hash = mySHA256.ComputeHash(tinfo3RelBuffer); - Assert.True(rectinfo1Hash.SequenceEqual(rectinfo2Hash)); - Assert.False(rectinfo2Hash.SequenceEqual(rectinfo3Hash)); - Assert.False(rectinfo2Hash.SequenceEqual(rectinfo3Hash)); - - // Compare the buffers - Assert.True(tinfo1RelBuffer.SequenceEqual(tinfo2RelBuffer)); - Assert.False(tinfo1RelBuffer.SequenceEqual(tinfo3RelBuffer)); - Assert.False(tinfo2RelBuffer.SequenceEqual(tinfo3RelBuffer)); - - // Rebuild the objects - var rebuiltTinfo1 = new TileInfo(0, tinfo1RelBuffer, 0, 0); - var rebuiltTinfo2 = new TileInfo(0, tinfo2RelBuffer, 0, 0); - var rebuiltTinfo3 = new TileInfo(0, tinfo3RelBuffer, 0, 0); - - // Compare their buffers - Assert.True(rebuiltTinfo1.TileBuffer.SequenceEqual(rebuiltTinfo2.TileBuffer)); - Assert.True(rebuiltTinfo1.TileBuffer.SequenceEqual(tinfo1.TileBuffer)); - Assert.True(rebuiltTinfo2.TileBuffer.SequenceEqual(tinfo2.TileBuffer)); - Assert.True(rebuiltTinfo1.TileBuffer.SequenceEqual(tinfo2.TileBuffer)); - Assert.True(rebuiltTinfo2.TileBuffer.SequenceEqual(tinfo1.TileBuffer)); - Assert.True(rebuiltTinfo3.TileBuffer.SequenceEqual(tinfo3.TileBuffer)); - - Assert.False(rebuiltTinfo1.TileBuffer.SequenceEqual(rebuiltTinfo3.TileBuffer)); - Assert.False(rebuiltTinfo2.TileBuffer.SequenceEqual(tinfo3.TileBuffer)); - Assert.False(rebuiltTinfo1.TileBuffer.SequenceEqual(tinfo3.TileBuffer)); - Assert.False(rebuiltTinfo2.TileBuffer.SequenceEqual(tinfo3.TileBuffer)); - - Assert.True(rebuiltTinfo1.Equals(tinfo1)); - Assert.True(rebuiltTinfo1.Equals(tinfo2)); - Assert.True(rebuiltTinfo1.Equals(rebuiltTinfo2)); - Assert.True(rebuiltTinfo2.Equals(rebuiltTinfo1)); - Assert.False(rebuiltTinfo1.Equals(rebuiltTinfo3)); - Assert.False(rebuiltTinfo2.Equals(rebuiltTinfo3)); - } - - public byte[] LoadTileData(string path) - { - return System.IO.File.ReadAllBytes(path); - } - -} diff --git a/RandomizerUnitTests/TestUtils.cs b/RandomizerUnitTests/TestUtils.cs deleted file mode 100644 index 534599c..0000000 --- a/RandomizerUnitTests/TestUtils.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NUnit.Framework; - -using static UWRandomizerEditor.Utils; - -namespace RandomizerUnitTests; - -[TestFixture] -internal class TestSetBits -{ - [Test] - public void TestSetBits1() - { - byte currval = 0b0001; - byte mask = 0b0011; - byte newval = 0b0011; - byte shift = 1; - byte expected = 0b0111; - int calc = SetBits(currval, newval, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - - } - [Test] - public void TestSetBits2() - { - - byte currval = 0b0001; - byte mask = 0b0110; - byte newval = 0b0011; - byte shift = 0; - byte expected = 0b0011; - int calc = SetBits(currval, newval, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - - [Test] - public void TestSetBits3() - { - byte currval = 0b1111; - byte mask = 0b0011; - byte newval = 0b1100; - byte shift = 1; - byte expected = 0b1001; - int calc = SetBits(currval, newval, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - - [Test] - public void TestSetBits4() - { - byte currval = 0b1111; - byte mask = 0b0110; - byte newval = 0b0000; - byte shift = 0; - byte expected = 0b1001; - int calc = SetBits(currval, newval, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - - [Test] - public void TestSetBits5() - { - byte currval = 0b1111_1111; - byte mask = 0b0011_1100; - byte newval = 0b0000_0000; - byte shift = 0; - byte expected = 0b1100_0011; - int calc = SetBits(currval, newval, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - [Test] - public void TestSetBits6() - { - byte currval = 0b1111_1111; - byte mask = 0b0000_1111; - byte newval = 0b0000_0000; - byte shift = 2; - byte expected = 0b1100_0011; - int calc = SetBits(currval, newval, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - - [Test] - public void TestSetBits7() - { - byte currval = 0b1100_1110; - byte mask = 0b0011_1000; - byte newval = 0b1001_0000; - byte shift = 0; - byte expected = 0b1101_0110; - int calc = SetBits(currval, newval, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - [Test] - public void TestSetBits8() - { - byte currval = 0b1111_0000; - byte mask = 0b0000_1111; - byte newval = 0b0000_1010; - byte shift = 4; - byte expected = 0b1010_0000; - int calc = SetBits(currval, newval, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - - -} - -[TestFixture] -internal class TestGetBits -{ - [Test] - public void TestGetBits1() - { - int value = 0b1010; - int mask = 0b0011; - int shift = 0; - int expected = 0b0010; - int calc = GetBits(value, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - [Test] - public void TestGetBits2() - { - int value = 0b0000; - int mask = 0b0011; - int shift = 0; - int expected = 0b0000; - int calc = GetBits(value, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - - [Test] - public void TestGetBits3() - { - int value = 0b1100_1001; - int mask = 0b1111; - int shift = 4; - int expected = 0b1100; - int calc = GetBits(value, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - - [Test] - public void TestGetBits4() - { - int value = 0b1010_0101; - int mask = 0b1111; - int shift = 2; - int expected = 0b1001; - int calc = GetBits(value, mask, shift); - Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); - } - - -} - -[TestFixture] -internal class TestSaveBuffer -{ - private byte[] buffer; - [SetUp] - public void Setup() - { - int[] temp = new[] {0, 1, 2, 3, 4, 5, 6, 7}; - buffer = (from i in temp select (byte) i).ToArray(); // Really necessary?!? - } - - [Test] - public void TestSaveB() - { - string path = Path.GetFullPath("."); - string filename = "TestSaveBuffer.bin"; - string output = StdSaveBuffer(buffer, path, filename); - - byte[] savedStuff = File.ReadAllBytes(output); - for (int i = 0; i < savedStuff.Length; i++) - { - Assert.True(savedStuff[i] == buffer[i]); - } - File.Delete(output); - } -} \ No newline at end of file diff --git a/UWRandomizer/MainWindow.xaml b/UWRandomizer/MainWindow.xaml deleted file mode 100644 index 77fb175..0000000 --- a/UWRandomizer/MainWindow.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/UWRandomizer/MainWindow.xaml.cs b/UWRandomizer/MainWindow.xaml.cs deleted file mode 100644 index 91202b7..0000000 --- a/UWRandomizer/MainWindow.xaml.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - -namespace UWRandomizer -{ - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow : Window - { - public MainWindow() - { - InitializeComponent(); - } - } -} \ No newline at end of file diff --git a/UWRandomizer/ManageDoors.cs b/UWRandomizer/ManageDoors.cs deleted file mode 100644 index 6bab83b..0000000 --- a/UWRandomizer/ManageDoors.cs +++ /dev/null @@ -1,21 +0,0 @@ -using UWRandomizerEditor.LEVDotARK; -using UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; - -namespace UWRandomizer; - -public static partial class RandoTools -{ - public static void RemoveAllDoorReferencesToLocks(ArkLoader arkFile) - { - foreach (var block in arkFile.TileMapObjectsBlocks) - { - foreach (var staticObject in block.StaticObjects) - { - if (staticObject is Door door) - { - door.RemoveLock(); - } - } - } - } -} \ No newline at end of file diff --git a/UWRandomizer/ShuffleItems.cs b/UWRandomizer/ShuffleItems.cs deleted file mode 100644 index d05886f..0000000 --- a/UWRandomizer/ShuffleItems.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Collections.Generic; -using UWRandomizerEditor.LEVDotARK; -using UWRandomizerEditor.LEVDotARK.Blocks; -using UWRandomizerEditor.LEVDotARK.GameObjects; - -namespace UWRandomizer; - -public static class ShuffleItems -{ - static void ShuffleAllLevels(ArkLoader arkFile) - { - foreach (var block in arkFile.TileMapObjectsBlocks) - { - ShuffleItemsInLevel(block); - block.UpdateBuffer(); - } - - arkFile.ReconstructBufferFromBlocks(); - } - - static void ShuffleItemsInLevel(TileMapMasterObjectListBlock block) - { - Stack objectsInLevel = new Stack(); - foreach (var tile in block.TileInfos) - { - foreach (var obj in tile.ObjectChain.PopObjectsThatShouldBeMoved()) - { - objectsInLevel.Push(obj); - } - } - - while (objectsInLevel.Count > 0) - { - int chosenTileIdx = Singletons.RandomInstance.Next(0, block.TileInfos.Length); - TileInfo chosenTile = block.TileInfos[chosenTileIdx]; - if (!IsTileValid(chosenTile, block.LevelNumber)) - continue; - chosenTile.ObjectChain.Add(objectsInLevel.Pop()); - } - - foreach (var tile in block.TileInfos) - { - tile.MoveObjectsToCorrectCorner(); - tile.MoveObjectsToSameZLevel(); - } - - block.UpdateBuffer(); // TODO: necessary? - } - - private static IDictionary LevelTextureIdxOfWater = new Dictionary() - { - {0, 8}, // lvl1 - {1, 8}, // lvl2 - {2, 8}, // lvl3 - {3, 7}, // lvl4 - {4, 8}, // lvl5 - {5, -1}, // lvl6 - {6, 8}, // lvl7 - {7, -1}, // lvl8 - {8, 7}, // lvl9, void - }; - - // TODO: Lvl8 appears to have 2 textures that are lava, right below the fire elementals beside the doors - private static IDictionary LevelTextureIdxOfLava = new Dictionary() - { - {0, -1}, // lvl1 - {1, -1}, // lvl2 - {2, -1}, // lvl3 - {3, -1}, // lvl4 - {4, 6}, // lvl5 - {5, 8}, // lvl6 - {6, -1}, // lvl7 - {7, 7}, // lvl8 - {8, 4}, // lvl9, void - }; - - private static bool IsTileValid(TileInfo tile, int levelNumber) - { - - if ((TileInfo.TileTypes) tile.TileType == TileInfo.TileTypes.solid) - { - return false; - } - - // Can't place items on water, or else they might vanish. TODO: Need to test though! - if ((tile.FloorTextureIdx == LevelTextureIdxOfWater[levelNumber]) | - tile.FloorTextureIdx == LevelTextureIdxOfLava[levelNumber]) - { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/UWRandomizerEditor/Interfaces/IBufferObject.cs b/UWRandomizerEditor/Interfaces/IBufferObject.cs new file mode 100644 index 0000000..424a5b4 --- /dev/null +++ b/UWRandomizerEditor/Interfaces/IBufferObject.cs @@ -0,0 +1,12 @@ +namespace UWRandomizerEditor.Interfaces; + +/// +/// Interface that consolidates the behavior of object that contain Buffers. These objects should have a byte buffer, +/// and a method to reconstruct it, meaning if any property was changed (e.g., Door lock status), then the buffer has to +/// be updated. +/// +public interface IBufferObject +{ + public byte[] Buffer { get; protected set; } + public bool ReconstructBuffer(); +} \ No newline at end of file diff --git a/UWRandomizerEditor/Interfaces/IShouldIMove.cs b/UWRandomizerEditor/Interfaces/IShouldIMove.cs deleted file mode 100644 index 30ee5bf..0000000 --- a/UWRandomizerEditor/Interfaces/IShouldIMove.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UWRandomizerEditor.Interfaces; - -interface IShouldIMove -{ - public bool ShouldBeMoved { get; set; } - -} \ No newline at end of file diff --git a/UWRandomizerEditor/ItemCombinations.cs b/UWRandomizerEditor/ItemCombinations.cs index fb24c2b..055dd9d 100644 --- a/UWRandomizerEditor/ItemCombinations.cs +++ b/UWRandomizerEditor/ItemCombinations.cs @@ -2,14 +2,14 @@ using System.Text.Json; using System.Text.Json.Serialization; using UWRandomizerEditor.Interfaces; -using static UWRandomizerEditor.Utils; + namespace UWRandomizerEditor; -public class CombinationsFile// : ISaveBinary +public class CombinationsFile : IBufferObject { public List Combinations; private string _path; - public byte[] Buffer; + public byte[] Buffer { get; set; } public CombinationsFile(string path) { @@ -34,16 +34,18 @@ private void ProcessCombinations() { Combinations = new List(); // if (BitConverter.ToInt64(Buffer[^6..]) != 0) // Doesn't end in 0s - if (Buffer[^6] != 0 | Buffer[^5] != 0 | Buffer[^4] != 0| Buffer[^3] != 0 | Buffer[^2] != 0 | Buffer[^1] != 0) // Doesn't end in 0s + if (Buffer[^6] != 0 | Buffer[^5] != 0 | Buffer[^4] != 0 | Buffer[^3] != 0 | Buffer[^2] != 0 | + Buffer[^1] != 0) // Doesn't end in 0s { throw new ArithmeticException("Buffer does not end in six bytes of 0s!"); } - + for (int i = 0; i < Buffer.Length - ItemCombination.Size; i += ItemCombination.Size) { byte[] buf = Buffer[i..(i + ItemCombination.Size)]; - Combinations.Add(new ItemCombination(Buffer[i..(i+ItemCombination.Size)])); + Combinations.Add(new ItemCombination(Buffer[i..(i + ItemCombination.Size)])); } + Combinations.Add(new FinalCombination()); } @@ -53,12 +55,12 @@ public string SaveCombinations(string? path) if (File.Exists(path)) Console.WriteLine("Overwriting file"); File.WriteAllBytes(path, Buffer); - + return path; } [MemberNotNull(nameof(Buffer))] - private void UpdateBuffer() + public bool ReconstructBuffer() { Buffer = new byte[Combinations.Count * ItemCombination.Size]; int i = 0; @@ -67,12 +69,14 @@ private void UpdateBuffer() comb.Buffer.CopyTo(Buffer, i * ItemCombination.Size); i++; // Oh how I wish for an `enumerate` in C#. } + + return true; } public void AddCombination(ItemCombination comb) { Combinations.Insert(Combinations.Count - 1, comb); // Inserts before null - UpdateBuffer(); + ReconstructBuffer(); } public void RemoveCombination(int idx) @@ -87,15 +91,16 @@ public void RemoveCombination(int idx) { Console.WriteLine("Invalid bounds"); } + Combinations.RemoveAt(idx); - UpdateBuffer(); + ReconstructBuffer(); } public CombinationsFile(List combinations, string path = "CMB.DAT") { this.Combinations = combinations; this._path = path; - UpdateBuffer(); + ReconstructBuffer(); } /// @@ -109,7 +114,7 @@ public bool CheckEnding() // TODO: this is a bit redundant with the function abo Combinations[^1].Product.itemID == 0; } - + /// /// Checks if at least one item is destroyed upon combination /// @@ -119,16 +124,16 @@ public bool CheckConsistency() // return Combinations.All(cmb => cmb.FirstItem.IsDestroyed | cmb.SecondItem.IsDestroyed); foreach (var cmb in Combinations) { - if (!(cmb.FirstItem.IsDestroyed | cmb.SecondItem.IsDestroyed) & (cmb.FirstItem.itemID != 0 & cmb.SecondItem.itemID != 0 & cmb.Product.itemID != 0)) + if (!(cmb.FirstItem.IsDestroyed | cmb.SecondItem.IsDestroyed) & + (cmb.FirstItem.itemID != 0 & cmb.SecondItem.itemID != 0 & cmb.Product.itemID != 0)) { return false; } } return true; - } - + public void ExportAsJson(string filename) { string json = JsonSerializer.Serialize(Combinations, @@ -139,7 +144,7 @@ public void ExportAsJson(string filename) }); File.WriteAllText(filename, json); } - + public static CombinationsFile ImportFromJson(string filename) { var temp = JsonSerializer.Deserialize>(File.ReadAllText(filename), @@ -149,12 +154,13 @@ public static CombinationsFile ImportFromJson(string filename) PropertyNameCaseInsensitive = true }) ?? throw new InvalidOperationException(); - + var file = new CombinationsFile(temp, "CMB.DAT"); - + if (!file.CheckConsistency()) { - Console.WriteLine("One of the combinations has both items preserved. This won't work. Consider editing to remove that combination"); + Console.WriteLine( + "One of the combinations has both items preserved. This won't work. Consider editing to remove that combination"); } if (!file.CheckEnding()) @@ -164,22 +170,26 @@ public static CombinationsFile ImportFromJson(string filename) Console.WriteLine("Done"); } - file.Combinations[^1] = new FinalCombination(); // Replacing because the Deserializer made it into ItemCombination + file.Combinations[^1] = + new FinalCombination(); // Replacing because the Deserializer made it into ItemCombination return file; } - - } -public class ItemCombination: ISaveBinary +public class ItemCombination : IBufferObject { - [JsonIgnore] - public const int NumOfItemsInCombination = 3; - [JsonIgnore] - public const int Size = NumOfItemsInCombination * ItemDescriptor.size; - [JsonIgnore] - public byte[] Buffer = new byte[NumOfItemsInCombination * ItemDescriptor.size]; + [JsonIgnore] public const int NumOfItemsInCombination = 3; + [JsonIgnore] public const int Size = NumOfItemsInCombination * ItemDescriptor.size; + [JsonIgnore] public byte[] Buffer { get; set; } = new byte[NumOfItemsInCombination * ItemDescriptor.size]; + + public bool ReconstructBuffer() + { + FirstItem.buffer.CopyTo(Buffer, ItemDescriptor.size * 0); + SecondItem.buffer.CopyTo(Buffer, ItemDescriptor.size * 1); + Product.buffer.CopyTo(Buffer, ItemDescriptor.size * 2); + return true; + } public ItemDescriptor FirstItem; public ItemDescriptor SecondItem; @@ -188,7 +198,7 @@ public class ItemCombination: ISaveBinary public ItemCombination(byte[] buffer) // 3 shorts = 6 bytes { Buffer = buffer; - FirstItem = new ItemDescriptor(buffer[(ItemDescriptor.size*0)..(ItemDescriptor.size * 1)]); + FirstItem = new ItemDescriptor(buffer[(ItemDescriptor.size * 0)..(ItemDescriptor.size * 1)]); SecondItem = new ItemDescriptor(buffer[(ItemDescriptor.size * 1)..(ItemDescriptor.size * 2)]); Product = new ItemDescriptor(buffer[(ItemDescriptor.size * 2)..(ItemDescriptor.size * 3)]); } @@ -199,25 +209,15 @@ public ItemCombination(ItemDescriptor firstItem, ItemDescriptor secondItem, Item FirstItem = firstItem; SecondItem = secondItem; Product = product; - - firstItem.buffer.CopyTo(Buffer, ItemDescriptor.size * 0); - secondItem.buffer.CopyTo(Buffer, ItemDescriptor.size * 1); - product.buffer.CopyTo(Buffer, ItemDescriptor.size * 2); - } - - public string SaveBuffer(string? basePath = null, string filename = "") - { - if (basePath is null) - { - basePath = Settings.DefaultBinaryTestsPath; - } - return StdSaveBuffer(Buffer, basePath, filename); + ReconstructBuffer(); } } -public class FinalCombination: ItemCombination +public class FinalCombination : ItemCombination { - public FinalCombination(): base(new FinalEntry(), new FinalEntry(), new FinalEntry()) {} + public FinalCombination() : base(new FinalEntry(), new FinalEntry(), new FinalEntry()) + { + } } /// @@ -225,36 +225,21 @@ public class FinalCombination: ItemCombination /// public class ItemDescriptor { - [JsonIgnore] - public const int size = 2; // In bytes - [JsonIgnore] - public byte[] buffer; - [JsonIgnore] - private ushort entry; + [JsonIgnore] public const int size = 2; // In bytes + [JsonIgnore] public byte[] buffer; + [JsonIgnore] private ushort entry; // TODO is Item ID 9 or 10 bits? public ushort itemID { - get - { - return (ushort) GetBits(entry, 0x3FF, 0); - } - set - { - entry = (ushort) SetBits(entry, value, 0x3FF, 0); - } + get { return (ushort) Utils.GetBits(entry, 0x3FF, 0); } + set { entry = (ushort) Utils.SetBits(entry, value, 0x3FF, 0); } } public bool IsDestroyed { - get - { - return entry >> 15 == 1; - } - set - { - entry = (ushort) SetBits(entry, value? 1:0, 0b1, 15); - } + get { return entry >> 15 == 1; } + set { entry = (ushort) Utils.SetBits(entry, value ? 1 : 0, 0b1, 15); } } [JsonConstructor] @@ -284,16 +269,15 @@ public class FinalEntry : ItemDescriptor { public new byte[] buffer = {0, 0}; private short entry = 0; - [JsonInclude] - public short itemID = 0; - [JsonInclude] - public bool IsDestroyed = false; + [JsonInclude] public short itemID = 0; + [JsonInclude] public bool IsDestroyed = false; [JsonConstructor] - public FinalEntry(short itemID, bool isDestroyed): this() - { } - - public FinalEntry(): base() - { } + public FinalEntry(short itemID, bool isDestroyed) : this() + { + } + public FinalEntry() : base() + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/ArkLoader.cs b/UWRandomizerEditor/LEVDotARK/ArkLoader.cs index b64abe8..42c3fd3 100644 --- a/UWRandomizerEditor/LEVDotARK/ArkLoader.cs +++ b/UWRandomizerEditor/LEVDotARK/ArkLoader.cs @@ -1,13 +1,8 @@ -using System.Diagnostics; -using System.Security.Cryptography; -using UWRandomizerEditor.Interfaces; -using UWRandomizerEditor.LEVDotARK.Blocks; -using static UWRandomizerEditor.Utils; +using UWRandomizerEditor.Interfaces; +using UWRandomizerEditor.LEVdotARK.Blocks; -namespace UWRandomizerEditor.LEVDotARK +namespace UWRandomizerEditor.LEVdotARK { - - /// /// This class loads an lev.ark file and subdivides it into 1) a header and 2) a sequence of blocks. /// @@ -20,20 +15,13 @@ namespace UWRandomizerEditor.LEVDotARK /// * Map Notes block /// * Empty blocks /// - /// A publicly accessible buffer is available and can be saved anytime. Each block can also be updated and, - /// in turn, update the ark buffer. This also contains a Sha256 hash of the original lev.ark. - /// /// - public class ArkLoader: ISaveBinary + public class ArkLoader : IBufferObject { - public const string PristineLevArkSha256Hash = - "87e9a6e5d249df273e1964f48ad910afee6f7e073165c00237dfb9a22ae3a121"; - public const int NumOfLevels = 9; // In UW1 - public byte[] arkbuffer; - public string arkpath; - public byte[] CurrentLevArkSha256Hash = new byte[] {}; + public byte[] Buffer { get; set; } + public string Path; public Header header; public Block[] blocks; @@ -45,38 +33,34 @@ public class ArkLoader: ISaveBinary public enum Sections { - level_tilemap_objlist = 0, - object_anim_overlay_info = 1, - texture_mappings = 2, - automap_infos = 3, - map_notes = 4, - unused = 5, + LevelTilemapObjlist = 0, + ObjectAnimOverlayInfo = 1, + TextureMappings = 2, + AutomapInfos = 3, + MapNotes = 4, + Unused = 5, } IDictionary BlockLengths = new Dictionary() { - {Sections.level_tilemap_objlist, TileMapMasterObjectListBlock.TotalBlockLength}, - {Sections.object_anim_overlay_info, ObjectAnimationOverlayInfoBlock.TotalBlockLength}, - {Sections.texture_mappings, TextureMappingBlock.TotalBlockLength }, - {Sections.automap_infos, 0}, - {Sections.map_notes, 0}, - {Sections.unused, 0 } + {Sections.LevelTilemapObjlist, TileMapMasterObjectListBlock.FixedBlockLength}, + {Sections.ObjectAnimOverlayInfo, ObjectAnimationOverlayInfoBlock.FixedBlockLength}, + {Sections.TextureMappings, TextureMappingBlock.FixedBlockLength}, + {Sections.AutomapInfos, 0}, + {Sections.MapNotes, 0}, + {Sections.Unused, 0} }; - private ArkLoader() : this(Settings.DefaultArkPath) - { - } - /// /// Instantiates a new ArkLoader object using a provided path. /// - /// - public ArkLoader(string arkpath) + /// + public ArkLoader(string path) { - this.arkpath = arkpath; - arkbuffer = LoadArkfile(arkpath); - int headerSize = Header.blockNumSize + Header.blockOffsetSize * Header.NumEntriesFromBuffer(arkbuffer); - header = new Header(arkbuffer[0..headerSize]); + Path = path; + Buffer = LoadArkfile(path); + int headerSize = Header.blockNumSize + Header.blockOffsetSize * Header.NumEntriesFromBuffer(Buffer); + header = new Header(Buffer[0..headerSize]); blocks = new Block[header.NumEntries]; TileMapObjectsBlocks = new TileMapMasterObjectListBlock[NumOfLevels]; @@ -84,8 +68,7 @@ public ArkLoader(string arkpath) TextMapBlocks = new TextureMappingBlock[NumOfLevels]; AutomapBlocks = new AutomapInfosBlock[NumOfLevels]; MapNotesBlocks = new MapNotesBlock[NumOfLevels]; - - CheckEqualityToPristineLevDotArk(); + LoadBlocks(); } @@ -93,7 +76,7 @@ public void LoadBlocks() { int currblocktypecount = 0; int currblocktype = 0; - + // Loop through all entries specified in the header for (short blocknum = 0; blocknum < header.NumEntries; blocknum++) { @@ -101,20 +84,15 @@ public void LoadBlocks() { currblocktype = 5; // Hack } + Block block = GetBlock(blocknum, (Sections) currblocktype, currblocktypecount); // Let's store the blocks this general array blocks[blocknum] = block; - + // And also in its specific array, depending on its type if (block is TileMapMasterObjectListBlock tilemap) { TileMapObjectsBlocks[currblocktypecount] = tilemap; - tilemap.Populate_MobileObjectsFromBuffer(); - tilemap.Populate_StaticObjectsFromBuffer(); - tilemap.Populate_AllGameObjectsFromBuffer(); - tilemap.Populate_FreeListMobileObjectArrFromBuffer(); - tilemap.Populate_FreeListStaticObjectArrFromBuffer(); - tilemap.ExtractInfoFromTileMapBuffer(); } else if (block is ObjectAnimationOverlayInfoBlock obj) { @@ -132,7 +110,7 @@ public void LoadBlocks() { AutomapBlocks[currblocktypecount] = automap; } - + currblocktypecount++; // Resets currblocktypecount when going from one block type to another if (currblocktypecount >= NumOfLevels) // TODO: looks like I could do a % here. Think more @@ -143,58 +121,23 @@ public void LoadBlocks() } } - public byte[] ReconstructBufferFromBlocks() + public bool ReconstructBuffer() { - byte[] tempbuffer = new byte[arkbuffer.Length]; List templist = new List(); - templist.AddRange(header.buffer); - foreach(var block in blocks) - { - templist.AddRange(block.blockbuffer); - } - byte[] concatbuffers = templist.ToArray(); - concatbuffers.CopyTo(tempbuffer, 0); - - return tempbuffer; - } - - public void CheckEqualityToPristineLevDotArk() - { - SHA256 mySHA256 = SHA256.Create(); - this.CurrentLevArkSha256Hash = mySHA256.ComputeHash(this.arkbuffer); - if (!CompareCurrentArkWithHash()) - { - Debug.WriteLine( - "Warning: Current ark file is different from original. Are you editing a save file? If not, report this."); - } - else - { - Debug.WriteLine("Current lev.ark is the same as the original."); - } - } - - public bool CompareCurrentArkWithHash() - { - if (PristineLevArkSha256Hash.Length / 2 != CurrentLevArkSha256Hash.Length) + header.ReconstructBuffer(); + templist.AddRange(header.Buffer); + foreach (var block in blocks) { - return false; - } - - for (int currbyte = 0; currbyte < PristineLevArkSha256Hash.Length / 2; currbyte++) - { - int currbyteStrPos = currbyte * 2; - //byte originalByte = byte.Parse(PristineLevArkSha256Hash[currbyteStrPos..(currbyteStrPos + 2)], ); - byte originalByte = Convert.ToByte(PristineLevArkSha256Hash[currbyteStrPos..(currbyteStrPos + 2)], 16); - byte currentbyte = CurrentLevArkSha256Hash[currbyte]; - if (originalByte != currentbyte) - { - return false; - } + block.ReconstructBuffer(); + templist.AddRange(block.Buffer); } + byte[] concatbuffers = templist.ToArray(); + concatbuffers.CopyTo(Buffer, 0); return true; } + public byte[] GetBlockBuffer(short blockNum, int BlockLength) { if (blockNum > header.NumEntries) @@ -203,15 +146,15 @@ public byte[] GetBlockBuffer(short blockNum, int BlockLength) $" number of blocks {header.NumEntries}"); } - //int blockOffset = BitConverter.ToInt32(arkbuffer[(2 + 4 * blockNum)..(6 + 4 * blockNum)]); + //int blockOffset = BitConverter.ToInt32(Buffer[(2 + 4 * blockNum)..(6 + 4 * blockNum)]); int blockOffset = header.BlockOffsets[blockNum]; - + if (blockOffset == 0) { return Array.Empty(); // TODO: figure out why this is better than new byte[] {} } - byte[] blockBuffer = arkbuffer[blockOffset..(blockOffset + BlockLength)]; + byte[] blockBuffer = Buffer[blockOffset..(blockOffset + BlockLength)]; return blockBuffer; } @@ -225,7 +168,7 @@ public byte[] GetBlockBuffer(short blockNum, int BlockLength) public Block GetBlock(short BlockNum, Sections BlockType, int levelnumber = -1) { int BlockLength; - if ((BlockType == Sections.map_notes) | (BlockType == Sections.automap_infos)) + if ((BlockType == Sections.MapNotes) | (BlockType == Sections.AutomapInfos)) { // TODO: When I'm less tired, check if this will go back in the last block. BlockLength = header.BlockOffsets[BlockNum + 1] - header.BlockOffsets[BlockNum]; @@ -238,17 +181,17 @@ public Block GetBlock(short BlockNum, Sections BlockType, int levelnumber = -1) byte[] buffer = GetBlockBuffer(BlockNum, BlockLength); switch (BlockType) { - case Sections.level_tilemap_objlist: + case Sections.LevelTilemapObjlist: return new TileMapMasterObjectListBlock(buffer, levelnumber); - case Sections.object_anim_overlay_info: + case Sections.ObjectAnimOverlayInfo: return new ObjectAnimationOverlayInfoBlock(buffer, levelnumber); - case Sections.texture_mappings: + case Sections.TextureMappings: return new TextureMappingBlock(buffer, levelnumber); - case Sections.automap_infos: + case Sections.AutomapInfos: return new AutomapInfosBlock(buffer, levelnumber); - case Sections.map_notes: + case Sections.MapNotes: return new MapNotesBlock(buffer, levelnumber); - case Sections.unused: + case Sections.Unused: // Debug.Assert(buffer.Length == 0); // Got the buffer, but should be empty. return new EmptyBlock(); default: @@ -260,23 +203,10 @@ static byte[] LoadArkfile(string? path = null) { if (path is null) { - path = Settings.DefaultArkPath; + throw new FileNotFoundException(); } - return System.IO.File.ReadAllBytes(path); - } - public string SaveBuffer(string? basePath = null, string filename = "") - { - if (basePath is null) - { - basePath = Settings.DefaultArkPath; - } - if (filename.Length == 0) - { - filename = $@"NEWLEV.ARK"; - } - return StdSaveBuffer(arkbuffer, basePath, filename); + return File.ReadAllBytes(path); } - } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Blocks/AutomapInfosBlock.cs b/UWRandomizerEditor/LEVDotARK/Blocks/AutomapInfosBlock.cs index a43c862..5a2e0b8 100644 --- a/UWRandomizerEditor/LEVDotARK/Blocks/AutomapInfosBlock.cs +++ b/UWRandomizerEditor/LEVDotARK/Blocks/AutomapInfosBlock.cs @@ -1,18 +1,21 @@ -namespace UWRandomizerEditor.LEVDotARK.Blocks; -public class AutomapInfosBlock: Block -{ - public static new int TotalBlockLength = 0x1000; +using System.Diagnostics; - public AutomapInfosBlock(byte[] buffer, int levelnumber) - { - // Debug.Assert(buffer.Length == TotalBlockLength); - blockbuffer = buffer; - LevelNumber = levelnumber; - } +namespace UWRandomizerEditor.LEVdotARK.Blocks; + +public class AutomapInfosBlock : Block +{ + public new const int FixedBlockLength = 0; // found out this can be 0 in a new file - public override string SaveBuffer(string? basePath = null, string filename = "") + public override bool ReconstructBuffer() { - return base.SaveBuffer(basePath, filename.Length == 0 ? $@"_AUTOMAP_{LevelNumber}" : filename); + // Since there's no operations at the moment that can change the buffer, this will do nothing. + return true; } + public AutomapInfosBlock(byte[] buffer, int levelnumber) + { + Buffer = new byte[buffer.Length]; + buffer.CopyTo(Buffer, 0); + LevelNumber = levelnumber; + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Blocks/Block.cs b/UWRandomizerEditor/LEVDotARK/Blocks/Block.cs index 7ab89fc..cc4467f 100644 --- a/UWRandomizerEditor/LEVDotARK/Blocks/Block.cs +++ b/UWRandomizerEditor/LEVDotARK/Blocks/Block.cs @@ -1,36 +1,27 @@ using UWRandomizerEditor.Interfaces; -using static UWRandomizerEditor.Utils; -namespace UWRandomizerEditor.LEVDotARK.Blocks +namespace UWRandomizerEditor.LEVdotARK.Blocks { - public abstract class Block: ISaveBinary + public abstract class Block : IBufferObject { - public byte[] blockbuffer; - public int LevelNumber; + public byte[] Buffer { get; set; } = Array.Empty(); - public int TotalBlockLength - { - get - { - return blockbuffer.Length; - } - } + public abstract bool ReconstructBuffer(); + + public int LevelNumber = -1; - protected Block(): this(new byte[] { }, -1){ } + /// If 0, means the block has no fixed length + public const int FixedBlockLength = 0; - public Block(byte[] blockbuffer, int levelNumber) + protected Block() { - this.blockbuffer = blockbuffer; - this.LevelNumber = levelNumber; } - public virtual string SaveBuffer(string? basePath = null, string filename = "") + protected Block(byte[] buffer, int levelNumber) { - if (basePath is null) - { - basePath = Settings.DefaultBinaryTestsPath; - } - return StdSaveBuffer(blockbuffer, basePath, filename); + Buffer = new byte[buffer.Length]; + buffer.CopyTo(Buffer, 0); + LevelNumber = levelNumber; } } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Blocks/EmptyBlock.cs b/UWRandomizerEditor/LEVDotARK/Blocks/EmptyBlock.cs index 89eecb7..7d3c402 100644 --- a/UWRandomizerEditor/LEVDotARK/Blocks/EmptyBlock.cs +++ b/UWRandomizerEditor/LEVDotARK/Blocks/EmptyBlock.cs @@ -1,9 +1,11 @@ -namespace UWRandomizerEditor.LEVDotARK.Blocks +namespace UWRandomizerEditor.LEVdotARK.Blocks { - public class EmptyBlock: Block + public class EmptyBlock : Block { - public static new int TotalBlockLength = 0; - public new byte[] blockbuffer = Array.Empty(); // VS suggestion - public EmptyBlock() { } + public override bool ReconstructBuffer() + { + Buffer = Array.Empty(); + return true; + } } -} +} \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Blocks/MapNotesBlock.cs b/UWRandomizerEditor/LEVDotARK/Blocks/MapNotesBlock.cs index 750d8ad..207758c 100644 --- a/UWRandomizerEditor/LEVDotARK/Blocks/MapNotesBlock.cs +++ b/UWRandomizerEditor/LEVDotARK/Blocks/MapNotesBlock.cs @@ -1,15 +1,22 @@ -namespace UWRandomizerEditor.LEVDotARK.Blocks +namespace UWRandomizerEditor.LEVdotARK.Blocks { /// /// Block that stores map notes. Its length is 0 if there are no block notes /// - public class MapNotesBlock: Block + public class MapNotesBlock : Block { - public MapNotesBlock(byte[] buffer, int levelnumber): base(buffer, levelnumber) - { } - public override string SaveBuffer(string? basePath = null, string filename = "") + public MapNotesBlock(byte[] buffer, int levelnumber) { - return base.SaveBuffer(basePath, filename.Length == 0 ? $@"_MAPNOTES_{LevelNumber}" : filename); + Buffer = new byte[buffer + .Length]; // This is getting the length from the input buffer because the length of this block is variable + buffer.CopyTo(Buffer, 0); + LevelNumber = levelnumber; + } + + public override bool ReconstructBuffer() + { + // Since there's no operations that can change the buffer, this won't do anything. + return true; } } -} +} \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Blocks/ObjectAnimationOverlayInfoBlock.cs b/UWRandomizerEditor/LEVDotARK/Blocks/ObjectAnimationOverlayInfoBlock.cs index 468aa0f..a53fef1 100644 --- a/UWRandomizerEditor/LEVDotARK/Blocks/ObjectAnimationOverlayInfoBlock.cs +++ b/UWRandomizerEditor/LEVDotARK/Blocks/ObjectAnimationOverlayInfoBlock.cs @@ -1,34 +1,40 @@ -namespace UWRandomizerEditor.LEVDotARK.Blocks +namespace UWRandomizerEditor.LEVdotARK.Blocks { - public class ObjectAnimationOverlayInfoBlock: Block + public class ObjectAnimationOverlayInfoBlock : Block { - public static new int TotalBlockLength = 0x0180; + public new const int FixedBlockLength = 0x0180; - /* - This block contains entries with length of 6 bytes with infos about - objects with animation overlay images from "animo.gr". - It always is 0x0180 bytes long which leads to 64 entries. + /* + This block contains entries with length of 6 bytes with infos about + objects with animation overlay images from "animo.gr". + It always is 0x0180 bytes long which leads to 64 entries. + + 0000 Int16 link1 + 0002 Int16 unk2 + 0004 Int8 tile x coordinate + 0005 Int8 tile y coordinate + + link1's most significant 10 bits contain a link into the master object + list, to the object that should get an animation overlay. + */ - 0000 Int16 link1 - 0002 Int16 unk2 - 0004 Int8 tile x coordinate - 0005 Int8 tile y coordinate - - link1's most significant 10 bits contain a link into the master object - list, to the object that should get an animation overlay. - */ - - public ObjectAnimationOverlayInfoBlock(byte[] buffer, int levelnumber) + public override bool ReconstructBuffer() { - // Debug.Assert(buffer.Length == TotalBlockLength); - blockbuffer = buffer; - LevelNumber = levelnumber; + // Since there's no operations that can change this block, this doesn't do anything. + return true; } - public override string SaveBuffer(string? basePath = null, string filename = "") + public ObjectAnimationOverlayInfoBlock(byte[] buffer, int levelnumber) { - return base.SaveBuffer(basePath, filename.Length == 0 ? $@"_ObjAnimOverlayInfo_{LevelNumber}" : filename); - } + if (buffer.Length != FixedBlockLength) + { + throw new ArgumentException( + $"Length of buffer ({buffer.Length}) is incompatible with expected ObjectAnimationOverlayInfo length ({FixedBlockLength})"); + } + Buffer = new byte[FixedBlockLength]; + buffer.CopyTo(Buffer, 0); + LevelNumber = levelnumber; + } } -} +} \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Blocks/TextureMappingBlock.cs b/UWRandomizerEditor/LEVDotARK/Blocks/TextureMappingBlock.cs index 75b60eb..f7c6b73 100644 --- a/UWRandomizerEditor/LEVDotARK/Blocks/TextureMappingBlock.cs +++ b/UWRandomizerEditor/LEVDotARK/Blocks/TextureMappingBlock.cs @@ -1,19 +1,28 @@ -namespace UWRandomizerEditor.LEVDotARK.Blocks +using System.Diagnostics; + +namespace UWRandomizerEditor.LEVdotARK.Blocks { - public class TextureMappingBlock: Block + public sealed class TextureMappingBlock : Block { - public static new int TotalBlockLength = 0x007a; + public new const int FixedBlockLength = 0x007a; + + public override bool ReconstructBuffer() + { + // Since there's no operations that can change this block, this doesn't do anything. + return true; + } public TextureMappingBlock(byte[] buffer, int levelnumber) { - // Debug.Assert(buffer.Length == TotalBlockLength); - blockbuffer = buffer; + if (buffer.Length != FixedBlockLength) + { + throw new ArgumentException( + $"Length of buffer ({buffer.Length}) is incompatible with expected TextureMappingBlock length ({FixedBlockLength})"); + } + + Buffer = new byte[FixedBlockLength]; + buffer.CopyTo(Buffer, 0); LevelNumber = levelnumber; } - - public override string SaveBuffer(string? basePath = null, string filename = "") - { - return base.SaveBuffer(basePath, filename.Length == 0 ? $@"_TEXMAP_{LevelNumber}" : filename); } -} -} +} \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Blocks/TileMapMasterObjectListBlock.cs b/UWRandomizerEditor/LEVDotARK/Blocks/TileMapMasterObjectListBlock.cs index ef26163..2734918 100644 --- a/UWRandomizerEditor/LEVDotARK/Blocks/TileMapMasterObjectListBlock.cs +++ b/UWRandomizerEditor/LEVDotARK/Blocks/TileMapMasterObjectListBlock.cs @@ -1,401 +1,364 @@ -using System.Diagnostics; -using UWRandomizerEditor.LEVDotARK.GameObjects; +using System.Data; +using System.Diagnostics; +using UWRandomizerEditor.LEVdotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; -namespace UWRandomizerEditor.LEVDotARK.Blocks +namespace UWRandomizerEditor.LEVdotARK.Blocks { // TODO: Separate this into TileMap and Object List...? - public class TileMapMasterObjectListBlock : Block + public partial class TileMapMasterObjectListBlock : Block { - // From uw-formats.txt - // offset size description - // 0000 4000 tilemap (64 x 64 x 4 bytes) - public const int TileMapOffset = 0; - public const int TileMapLength = 0x4000; - public const int TileWidth = 64; - public const int TileHeight = 64; - - public const int TileMapEntrySize = 4; - - // 4000 1b00 mobile object information (objects 0000-00ff, 256 x 27 bytes) - public const int MobileObjectInfoOffset = 0x4000; - public const int MobileObjectInfoLength = 0x1b00; - public const int MobileObjectInfoEntrySize = MobileObject.TotalLength; - public const int MobileObjectNum = 256; - - // 5b00 1800 static object information (objects 0100-03ff, 768 x 8 bytes) - public const int StaticObjectInfoOffset = 0x5b00; - public const int StaticObjectInfoLength = 0x1800; - public const int StaticObjectInfoEntrySize = GameObject.TotalLength; - public const int StaticObjectNum = 768; - - // 7300 01fc free list for mobile objects (objects 0002-00ff, 254 x 2 bytes) - public const int FreeListMobileObjectsOffset = 0x7300; - public const int FreeListMobileObjectsLength = 0x01fc; - public const int FreeListMobileObjectsEntrySize = 2; - public const int FreeListMobileObjectsNum = 254; - - // 74fc 0600 free list for static objects (objects 0100-03ff, 768 x 2 bytes) - public const int FreeListStaticObjectsOffset = 0x74fc; - public const int FreeListStaticObjectsLength = 0x0600; - public const int FreeListStaticObjectsEntrySize = 2; - public const int FreeListStaticObjectsNum = 768; - - // 7afc 0104 unknown(260 bytes) - public const int UnknownOffset = 0x7afc; - public const int UnknownLength = 0x104; - public const int UnknownEntrySize = 1; // irrelevant ATM - - // 7c00 0002 - public const int Unknown2Offset = 0x7c00; - public const int Unknown2Length = 2; - public const int Unknown2EntrySize = 2; - - // 7c02 0002 no.entries in mobile free list minus 1 - public const int NumEntriesMobileFreeListAdjOffset = 0x7c02; - public const int NumEntriesMobileFreeListAdjLength = 2; - public const int NumEntriesMobileFreeListAdjEntrySize = 2; - public short NumEntriesInMobileListMinus1 - { - get { return BitConverter.ToInt16(blockbuffer, NumEntriesMobileFreeListAdjOffset); } - set { BitConverter.GetBytes(value).CopyTo(blockbuffer, NumEntriesInMobileListMinus1); } - } - // 7c04 0002 no. entries in static free list minus 1 - public const int NumEntriesStaticFreeListAdjOffset = 0x7c04; - public const int NumEntriesStaticFreeListAdjLength = 2; - public const int NumEntriesStaticFreeListAdjEntrySize = 2; - public short NumEntriesInStaticListMinus1 - { - get { return BitConverter.ToInt16(blockbuffer, NumEntriesStaticFreeListAdjOffset); } - set { BitConverter.GetBytes(value).CopyTo(blockbuffer, NumEntriesStaticFreeListAdjOffset); } - } - // 7c06 0002 0x7775 ('uw') - public const int EndOfBlockConfirmationOffset = 0x7c06; - public const int EndOfBlockConfirmationDataSize = 2; - public const short EndOfBlockConfirmationValue = 0x7775; // "uw" - - public static new int TotalBlockLength = 0x7c08; - public TileInfo[] TileInfos = new TileInfo[TileMapLength / TileMapEntrySize]; public GameObject[] AllGameObjects = new GameObject[MobileObjectNum + StaticObjectNum]; public MobileObject[] MobileObjects = new MobileObject[MobileObjectNum]; public StaticObject[] StaticObjects = new StaticObject[StaticObjectNum]; - - public FreeListObjectEntry[] FreeListMobileObject = new FreeListObjectEntry[FreeListMobileObjectsNum]; - public FreeListObjectEntry[] FreeListStaticObject = new FreeListObjectEntry[FreeListStaticObjectsNum]; - // public int LevelNumber; // for safekeeping, irrelevant to the buffers. + + public FreeListObjectEntry[] FreeListMobileObjects = new FreeListObjectEntry[FreeListMobileObjectsNum]; + public FreeListObjectEntry[] FreeListStaticObjects = new FreeListObjectEntry[FreeListStaticObjectsNum]; + + public ushort FirstFreeSlotInMobileList + { + get { return BitConverter.ToUInt16(Buffer, NumEntriesMobileFreeListAdjOffset); } + set { BitConverter.GetBytes(value).CopyTo(Buffer, NumEntriesMobileFreeListAdjOffset); } + } + + public ushort FirstFreeSlotInStaticList + { + get { return BitConverter.ToUInt16(Buffer, NumEntriesStaticFreeListAdjOffset); } + set { BitConverter.GetBytes(value).CopyTo(Buffer, NumEntriesStaticFreeListAdjOffset); } + } + + public ushort FirstFreeMobileObjectIdx => FreeListMobileObjects[FirstFreeSlotInMobileList].IdxAtArray; + public ushort FirstFreeStaticObjectIdx => FreeListStaticObjects[FirstFreeSlotInStaticList].IdxAtArray; // TODO: Is this +1? // todo: Recheck and make sure the number of entries is correct. - public void UpdateBuffer() + public override bool ReconstructBuffer() { - TileMapBuffer.CopyTo(blockbuffer, TileMapOffset); - MobileObjectInfoBuffer.CopyTo(blockbuffer, MobileObjectInfoOffset); - StaticObjectInfoBuffer.CopyTo(blockbuffer, StaticObjectInfoOffset); - FreeListMobileObjectBuffer.CopyTo(blockbuffer, FreeListMobileObjectsOffset); - FreeListStaticObjectBuffer.CopyTo(blockbuffer, FreeListStaticObjectsOffset); - UnknownBuffer.CopyTo(blockbuffer, UnknownOffset); - Unknown2Buffer.CopyTo(blockbuffer, Unknown2Offset); + if (Buffer.Length != FixedBlockLength) + { + throw new ConstraintException( + $"Somehow the length of TileMapMasterObjectListBlock has the invalid length of {Buffer.Length}"); + } + + ReconstructSubBuffers(); + + TileMapBuffer.CopyTo(Buffer, TileMapOffset); + MobileObjectInfoBuffer.CopyTo(Buffer, MobileObjectInfoOffset); + StaticObjectInfoBuffer.CopyTo(Buffer, StaticObjectInfoOffset); + FreeListMobileObjectBuffer.CopyTo(Buffer, FreeListMobileObjectsOffset); + FreeListStaticObjectBuffer.CopyTo(Buffer, FreeListStaticObjectsOffset); + UnknownBuffer.CopyTo(Buffer, UnknownOffset); + Unknown2Buffer.CopyTo(Buffer, Unknown2Offset); // todo: do I really need these 2? Seems this is always kept updated. - BitConverter.GetBytes(NumEntriesInMobileListMinus1).CopyTo(blockbuffer, NumEntriesMobileFreeListAdjOffset); - BitConverter.GetBytes(NumEntriesInStaticListMinus1).CopyTo(blockbuffer, NumEntriesStaticFreeListAdjOffset); - BitConverter.GetBytes(EndOfBlockConfirmationValue).CopyTo(blockbuffer, EndOfBlockConfirmationOffset); - Debug.Assert(blockbuffer.Length == TotalBlockLength); + BitConverter.GetBytes(FirstFreeSlotInMobileList).CopyTo(Buffer, NumEntriesMobileFreeListAdjOffset); + BitConverter.GetBytes(FirstFreeSlotInStaticList).CopyTo(Buffer, NumEntriesStaticFreeListAdjOffset); + BitConverter.GetBytes(EndOfBlockConfirmationValue).CopyTo(Buffer, EndOfBlockConfirmationOffset); + return true; } - public void UpdateObjectInfoBuffer() + private void ReconstructSubBuffers() { - UpdateMobileObjectInfoBuffer(); - UpdateStaticObjectInfoBuffer(); - UpdateFreeListMobileObjectBuffer(); - UpdateFreeListStaticObjectBuffer(); + ReconstructTileMapBuffer(); + // TODO: These should ideally check the validity of each object and rearrange them so they're ordered + // from the end, and the free lists should accomodate that. + ReconstructMobileObjectInfoBuffer(); + ReconstructStaticObjectInfoBuffer(); + ReconstructFreeListMobileObjectBuffer(); + ReconstructFreeListStaticObjectBuffer(); } - public void UpdateMobileObjectInfoBuffer() + private void ReconstructMobileObjectInfoBuffer() { - // Let's use the object's own index to decide where it goes - foreach (MobileObject mobj in MobileObjects) + // Let's use the object's own index to decide where it goes... + foreach (var mobj in MobileObjects) { - mobj.Buffer.CopyTo(MobileObjectInfoBuffer, mobj.IdxAtObjectArray * MobileObject.TotalLength); + mobj.ReconstructBuffer(); + mobj.Buffer.CopyTo(MobileObjectInfoBuffer, mobj.IdxAtObjectArray * MobileObject.FixedTotalLength); } } - public void UpdateStaticObjectInfoBuffer() + private void ReconstructStaticObjectInfoBuffer() { - foreach (GameObject obj in StaticObjects) + foreach (var obj in StaticObjects) { - obj.Buffer.CopyTo(StaticObjectInfoBuffer, obj.IdxAtObjectArray * GameObject.TotalLength); + obj.ReconstructBuffer(); + // obj.Buffer.CopyTo(StaticObjectInfoBuffer, (obj.IdxAtObjectArray - MobileObjectNum) * StaticObject.FixedTotalLength); + obj.Buffer.CopyTo(StaticObjectInfoBuffer, + (obj.IdxAtObjectArray - MobileObjectNum) * StaticObject.FixedTotalLength); } } - public void UpdateFreeListStaticObjectBuffer() + private void ReconstructFreeListStaticObjectBuffer() { - throw new NotImplementedException();//todo + foreach (var obj in FreeListStaticObjects) + { + obj.ReconstructBuffer(); + obj.Buffer.CopyTo(FreeListStaticObjectBuffer, obj.EntryNum * FreeListObjectEntry.FixedSize); + } + } + + private void ReconstructFreeListMobileObjectBuffer() + { + foreach (var obj in FreeListMobileObjects) + { + obj.ReconstructBuffer(); + obj.Buffer.CopyTo(FreeListMobileObjectBuffer, obj.EntryNum * FreeListObjectEntry.FixedSize); + } } - - public void UpdateFreeListMobileObjectBuffer() + + private void ReconstructTileMapBuffer() { - throw new NotImplementedException();//todo + // Todo: Check that TileInfos is the required length AND there aren't repeat indices. + foreach (TileInfo currInfo in TileInfos) + { + currInfo.ReconstructBuffer(); + currInfo.Buffer.CopyTo(TileMapBuffer, currInfo.Offset); + } } - public void Populate_StaticObjectsFromBuffer() + private void Populate_StaticObjectsFromBuffer() { for (short i = 0; i < StaticObjectNum; i++) { byte[] currbuffer = - StaticObjectInfoBuffer[(i * StaticObject.TotalLength)..((i + 1) * StaticObject.TotalLength)]; - var currobj = (StaticObject) GameObjectFactory.CreateFromBuffer(currbuffer, (short) (i + MobileObjectNum)); - - if (currobj.IdxAtObjectArray < MobileObjectNum) + StaticObjectInfoBuffer[ + (i * StaticObject.FixedTotalLength)..((i + 1) * StaticObject.FixedTotalLength)]; + var currobj = + (StaticObject) GameObjectFactory.CreateFromBuffer(currbuffer, (ushort) (i + MobileObjectNum)); + if (i < FirstFreeStaticObjectIdx - MobileObjectNum + 2) // +2 because of objs 0 and 1 + currobj.Invalid = true; + + if ((currobj.IdxAtObjectArray < MobileObjectNum) & (currobj.Invalid)) { throw new Exception( "Attempted to add a static object to the region of mobile objects. Should not happen!"); } - + StaticObjects[i] = currobj; AllGameObjects[currobj.IdxAtObjectArray] = currobj; } } - + // todo: consider also providing an entry number, for safekeeping - public void Populate_MobileObjectsFromBuffer() + private void Populate_MobileObjectsFromBuffer() { - for (short i = 0; i < MobileObjectNum; i++) + for (ushort i = 0; i < MobileObjectNum; i++) { byte[] currbuffer = - MobileObjectInfoBuffer[(i * MobileObject.TotalLength)..((i + 1) * MobileObject.TotalLength)]; + MobileObjectInfoBuffer[ + (i * MobileObject.FixedTotalLength)..((i + 1) * MobileObject.FixedTotalLength)]; var currobj = (MobileObject) GameObjectFactory.CreateFromBuffer(currbuffer, i); + if (i <= FirstFreeMobileObjectIdx) + currobj.Invalid = true; if (currobj.IdxAtObjectArray >= MobileObjectNum) { throw new Exception( "Attempted to add a static object to the region of mobile objects. Should not happen!"); } - + MobileObjects[i] = currobj; AllGameObjects[currobj.IdxAtObjectArray] = currobj; } } - public void Populate_AllGameObjectsFromBuffer() - { - Populate_MobileObjectsFromBuffer(); - Populate_StaticObjectsFromBuffer(); - } - - public void Populate_FreeListMobileObjectArrFromBuffer() + private void Populate_FreeListMobileObjectArrFromBuffer() { for (int i = 0; i < FreeListMobileObjectsNum; i++) { byte[] currbuffer = - FreeListMobileObjectBuffer[(i * FreeListObjectEntry.EntrySize)..((i + 1) * FreeListObjectEntry.EntrySize)]; + FreeListMobileObjectBuffer[ + (i * FreeListObjectEntry.FixedSize)..((i + 1) * FreeListObjectEntry.FixedSize)]; var currobj = new FreeListObjectEntry(currbuffer, i); - FreeListMobileObject[i] = currobj; + FreeListMobileObjects[i] = currobj; } } - - public void Populate_FreeListStaticObjectArrFromBuffer() + + private void Populate_FreeListStaticObjectArrFromBuffer() { for (int i = 0; i < FreeListStaticObjectsNum; i++) { byte[] currbuffer = - FreeListStaticObjectBuffer[(i * FreeListObjectEntry.EntrySize)..((i + 1) * FreeListObjectEntry.EntrySize)]; + FreeListStaticObjectBuffer[ + (i * FreeListObjectEntry.FixedSize)..((i + 1) * FreeListObjectEntry.FixedSize)]; var currobj = new FreeListObjectEntry(currbuffer, i); - FreeListStaticObject[i] = currobj; + FreeListStaticObjects[i] = currobj; } } - public TileMapMasterObjectListBlock(byte[] blockbuffer, int levelNumber) + public TileMapMasterObjectListBlock(byte[] buffer, int levelNumber) { - // Debug.Assert(blockbuffer.Length == TotalBlockLength); - this.blockbuffer = blockbuffer; - this.LevelNumber = levelNumber; - } + if (buffer.Length != FixedBlockLength) + { + throw new ArgumentException( + $"Length of buffer ({buffer.Length}) is incompatible with expected TextureMappingBlock length ({FixedBlockLength})"); + } - #region information extraction + Buffer = new byte[FixedBlockLength]; + buffer.CopyTo(Buffer, 0); + LevelNumber = levelNumber; + + TileMapBuffer = buffer[TileMapOffset..(TileMapOffset + TileMapLength)]; + MobileObjectInfoBuffer = buffer[MobileObjectInfoOffset..(MobileObjectInfoOffset + MobileObjectInfoLength)]; + StaticObjectInfoBuffer = buffer[StaticObjectInfoOffset..(StaticObjectInfoOffset + StaticObjectInfoLength)]; + FreeListMobileObjectBuffer = + buffer[FreeListMobileObjectsOffset..(FreeListMobileObjectsOffset + FreeListMobileObjectsLength)]; + FreeListStaticObjectBuffer = + buffer[FreeListStaticObjectsOffset..(FreeListStaticObjectsOffset + FreeListStaticObjectsLength)]; + UnknownBuffer = buffer[UnknownOffset..(UnknownOffset + UnknownLength)]; + Unknown2Buffer = buffer[Unknown2Offset..(Unknown2Offset + Unknown2Length)]; + + Populate_FreeListMobileObjectArrFromBuffer(); + Populate_FreeListStaticObjectArrFromBuffer(); + Populate_MobileObjectsFromBuffer(); + Populate_StaticObjectsFromBuffer(); + Populate_TileInfos(); // This requires a complete array of game objects, so it comes last + Populate_Containers(); + } - public void ExtractInfoFromTileMapBuffer() + private void Populate_TileInfos() { - // TileInfo[] tileInfos = new TileInfo[TileMapLength / TileMapEntrySize]; - - for (int i = 0; i < TileMapLength / TileMapEntrySize; i++) + for (uint i = 0; i < TileMapLength / TileMapEntrySize; i++) { - - int offset = i * TileMapEntrySize; + var offset = i * TileMapEntrySize; // Todo: Seems a bit weird to convert and de-convert later. Think better. - int entry = BitConverter.ToInt32(TileMapBuffer, offset); + var entry = BitConverter.ToUInt32(TileMapBuffer, (int) offset); TileInfo currInfo = new TileInfo(i, entry, offset, LevelNumber); TileInfos[i] = currInfo; + currInfo.ObjectChain.PopulateObjectList(AllGameObjects); } } - - public void UpdateTileMapBuffer() + private void Populate_Containers() { - // Todo: Check that TileInfos is the required length AND there aren't repeat indices. - foreach (TileInfo currInfo in TileInfos) + foreach (var gameObject in AllGameObjects) { - currInfo.TileBuffer.CopyTo(TileMapBuffer, currInfo.Offset); + if (gameObject.Invalid) continue; + if (gameObject is Container cont) + { + cont.Contents.PopulateObjectList(AllGameObjects); + } + + if (gameObject is MobileObject mobileObject) + { + mobileObject.Inventory.PopulateObjectList(AllGameObjects); + } } - // byte[] newbuffer = new byte[TileInfos.Length * TileInfo.Size]; - - // Thinking... which approach is better? I stored the offset for a reason... I think the second method can allow for - // more flexibility though. - - //int i = 0; - //foreach (TileInfo currInfo in TileInfos) - //{ - // currInfo.TileBuffer.CopyTo(newbuffer, i * TileInfo.Size); - // i++; - //} - - // foreach (TileInfo currInfo in TileInfos) - // { - // currInfo.TileBuffer.CopyTo(newbuffer, currInfo.Offset); - // } - // return newbuffer; - // return newbuffer; } - #endregion - - - - - // #region properties - // TODO: Verify this implementation! - // public bool IsLengthOfMobileListValid() - // { - // int counter = 0; - // foreach (MobileObject mobj in FreeMobileObject) - // { - // if (mobj.ItemID != 0) - // { - // counter += 1; - // } - // } - // - // if (counter - 1 != NumEntriesInMobileListMinus1) - // { - // return false; - // } - // else - // { - // return true; - // } - // } - // // TODO: Verify this implementation! - // public bool IsLengthOfStaticListValid() - // { - // int counter = 0; - // foreach (GameObject mobj in FreeStaticObject) - // { - // if (mobj.ItemID != 0) - // { - // counter += 1; - // } - // } - // - // if (counter - 1 != NumEntriesInStaticListMinus1) - // { - // return false; - // } - // else - // { - // return true; - // } - // } - // #endregion - - #region buffer definitions + + private byte[] _tileMapBuffer = new byte[TileMapLength]; public byte[] TileMapBuffer { - get { return blockbuffer[TileMapOffset..(TileMapOffset + TileMapLength)]; } + get { return _tileMapBuffer; } set { - Trace.Assert(value.Length == TileMapLength); // better to throw exception? - value.CopyTo(blockbuffer, TileMapOffset); + if (value.Length != TileMapLength) + { + throw new ArgumentException($"Invalid length of TileMapBuffer"); + } + + value.CopyTo(_tileMapBuffer, 0); } } + private byte[] _mobileObjectInfoBuffer = new byte[MobileObjectInfoLength]; + public byte[] MobileObjectInfoBuffer { - get { return blockbuffer[MobileObjectInfoOffset..(MobileObjectInfoOffset + MobileObjectInfoLength)]; } + get { return _mobileObjectInfoBuffer; } set { - Trace.Assert(value.Length == MobileObjectInfoLength); // better to throw exception? - value.CopyTo(blockbuffer, MobileObjectInfoOffset); + if (value.Length != MobileObjectInfoLength) + { + throw new ArgumentException($"Invalid length of MobileObjectInfoBuffer"); + } + + value.CopyTo(_mobileObjectInfoBuffer, 0); } } + private byte[] _staticObjectInfoBuffer = new byte[StaticObjectInfoLength]; + public byte[] StaticObjectInfoBuffer { - get { return blockbuffer[StaticObjectInfoOffset..(StaticObjectInfoOffset + StaticObjectInfoLength)]; } + get { return _staticObjectInfoBuffer; } set { - Trace.Assert(value.Length == StaticObjectInfoLength); // better to throw exception? - value.CopyTo(blockbuffer, StaticObjectInfoOffset); + if (value.Length != StaticObjectInfoLength) + { + throw new ArgumentException($"Invalid length of StaticObjectInfoBuffer"); + } + + value.CopyTo(_staticObjectInfoBuffer, 0); } } + private byte[] _freeListMobileObjectBuffer = new byte[FreeListMobileObjectsLength]; + public byte[] FreeListMobileObjectBuffer { - get - { - return blockbuffer[ - FreeListMobileObjectsOffset..(FreeListMobileObjectsOffset + FreeListMobileObjectsLength)]; - } + get { return _freeListMobileObjectBuffer; } set { - Trace.Assert(value.Length == FreeListMobileObjectsLength); - value.CopyTo(blockbuffer, FreeListMobileObjectsOffset); + if (value.Length != FreeListMobileObjectsLength) + { + throw new ArgumentException($"Invalid length of FreeListMobileObjectBuffer"); + } + + value.CopyTo(_freeListMobileObjectBuffer, 0); } } + private byte[] _freeListStaticObjectBuffer = new byte[FreeListStaticObjectsLength]; + public byte[] FreeListStaticObjectBuffer { - get - { - return blockbuffer[ - FreeListStaticObjectsOffset..(FreeListStaticObjectsOffset + FreeListStaticObjectsLength)]; - } + get { return _freeListStaticObjectBuffer; } set { - Trace.Assert(value.Length == FreeListStaticObjectsLength); - value.CopyTo(blockbuffer, FreeListStaticObjectsOffset); + if (value.Length != FreeListStaticObjectsLength) + { + throw new ArgumentException($"Invalid length of FreeListStaticObjectBuffer"); + } + + value.CopyTo(_freeListStaticObjectBuffer, 0); } } + private byte[] _unknownBuffer = new byte[UnknownLength]; + protected byte[] UnknownBuffer { - get { return blockbuffer[UnknownOffset..(UnknownOffset + UnknownLength)]; } + get { return _unknownBuffer; } set { - Trace.Assert(value.Length == UnknownLength); - value.CopyTo(blockbuffer, UnknownOffset); + if (value.Length != UnknownLength) + { + throw new ArgumentException($"Invalid length"); + } + + value.CopyTo(_unknownBuffer, 0); } } + private byte[] _unknown2Buffer = new byte[Unknown2Length]; + protected byte[] Unknown2Buffer { - get { return blockbuffer[Unknown2Offset..(Unknown2Offset + Unknown2Length)]; } + get { return _unknown2Buffer; } set { - Trace.Assert(value.Length == Unknown2Length); - value.CopyTo(blockbuffer, Unknown2Offset); - } - } - - #endregion + if (value.Length != Unknown2Length) + { + throw new ArgumentException($"Invalid length"); + } - public override string SaveBuffer(string? basePath = null, string filename = "") - { - if (basePath is null) - { - basePath = Settings.DefaultBinaryTestsPath; + value.CopyTo(_unknown2Buffer, 0); } - return base.SaveBuffer(basePath, filename.Length == 0 ? $@"_TileMapObjList_{LevelNumber}" : filename); } } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Blocks/TileMapMasterObjectListBlock_Constants.cs b/UWRandomizerEditor/LEVDotARK/Blocks/TileMapMasterObjectListBlock_Constants.cs new file mode 100644 index 0000000..1171f57 --- /dev/null +++ b/UWRandomizerEditor/LEVDotARK/Blocks/TileMapMasterObjectListBlock_Constants.cs @@ -0,0 +1,73 @@ +using System.Diagnostics; +using UWRandomizerEditor.LEVdotARK.GameObjects; + +namespace UWRandomizerEditor.LEVdotARK.Blocks +{ + // TODO: Separate this into TileMap and Object List...? + public partial class TileMapMasterObjectListBlock : Block + { + // From uw-formats.txt + // offset size description + // 0000 4000 tilemap (64 x 64 x 4 bytes) + public const int TileMapOffset = 0; + public const int TileMapLength = 0x4000; + public const int TileWidth = 64; + public const int TileHeight = 64; + + public const int TileMapEntrySize = 4; + + // 4000 1b00 mobile object information (objects 0000-00ff, 256 x 27 bytes) + public const int MobileObjectInfoOffset = 0x4000; + public const int MobileObjectInfoLength = 0x1b00; + public const int MobileObjectInfoEntrySize = MobileObject.FixedTotalLength; + public const int MobileObjectNum = 256; + + // 5b00 1800 static object information (objects 0100-03ff, 768 x 8 bytes) + public const int StaticObjectInfoOffset = 0x5b00; + public const int StaticObjectInfoLength = 0x1800; + public const int StaticObjectInfoEntrySize = GameObject.FixedTotalLength; + public const int StaticObjectNum = 768; + + // 7300 01fc free list for mobile objects (objects 0002-00ff, 254 x 2 bytes) + public const int FreeListMobileObjectsOffset = 0x7300; + public const int FreeListMobileObjectsLength = 0x01fc; + public const int FreeListMobileObjectsEntrySize = 2; + public const int FreeListMobileObjectsNum = 254; + + // 74fc 0600 free list for static objects (objects 0100-03ff, 768 x 2 bytes) + public const int FreeListStaticObjectsOffset = 0x74fc; + public const int FreeListStaticObjectsLength = 0x0600; + public const int FreeListStaticObjectsEntrySize = 2; + public const int FreeListStaticObjectsNum = 768; + + // 7afc 0104 unknown(260 bytes) + public const int UnknownOffset = 0x7afc; + public const int UnknownLength = 0x104; + public const int UnknownEntrySize = 1; // irrelevant ATM + + // 7c00 0002 + public const int Unknown2Offset = 0x7c00; + public const int Unknown2Length = 2; + public const int Unknown2EntrySize = 2; + + // 7c02 0002 no.entries in mobile free list minus 1 + public const int NumEntriesMobileFreeListAdjOffset = 0x7c02; + public const int NumEntriesMobileFreeListAdjLength = 2; + public const int NumEntriesMobileFreeListAdjEntrySize = 2; + + // 7c04 0002 no. entries in static free list minus 1 + public const int NumEntriesStaticFreeListAdjOffset = 0x7c04; + public const int NumEntriesStaticFreeListAdjLength = 2; + public const int NumEntriesStaticFreeListAdjEntrySize = 2; + + // 7c06 0002 0x7775 ('uw') + public const int EndOfBlockConfirmationOffset = 0x7c06; + + public const int EndOfBlockConfirmationDataSize = 2; + + // Has to be this order, because bit converter was inverting this. "uw" + public const short EndOfBlockConfirmationValue = 0x7577; + + public new const int FixedBlockLength = 0x7c08; + } +} \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/FreeListObjectEntry.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/FreeListObjectEntry.cs index 00cc1d2..d4a5a2f 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/FreeListObjectEntry.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/FreeListObjectEntry.cs @@ -1,40 +1,47 @@ -using UWRandomizerEditor.LEVDotARK.Blocks; +using UWRandomizerEditor.Interfaces; +using UWRandomizerEditor.LEVdotARK.Blocks; -namespace UWRandomizerEditor.LEVDotARK.GameObjects; +namespace UWRandomizerEditor.LEVdotARK.GameObjects; -public class FreeListObjectEntry +public class FreeListObjectEntry : IBufferObject { // FreeListMobileObjectEntrySize is also 4 (short). - public const int EntrySize = TileMapMasterObjectListBlock.FreeListStaticObjectsEntrySize; - public byte[] Buffer = new byte[EntrySize]; - public short Entry; - public int EntryNum; + public const int FixedSize = TileMapMasterObjectListBlock.FreeListStaticObjectsEntrySize; + public byte[] Buffer { get; set; } = new byte[FixedSize]; - public FreeListObjectEntry(byte[] buffer, int EntryNum) + public ushort IdxAtArray { - // Debug.Assert(buffer.Length == TileMapMasterObjectListBlock.FreeListMobileObjectsEntrySize); - this.Buffer = buffer; - this.EntryNum = EntryNum; - UpdateEntry(); + get + { + return BitConverter.ToUInt16(Buffer); + } + set + { + Buffer = new byte[FixedSize]; + BitConverter.GetBytes(value).CopyTo(Buffer, 0); + } } - public FreeListObjectEntry(short Entry, int EntryNum) + public readonly int EntryNum; + + public FreeListObjectEntry(byte[] buffer, int EntryNum) { - this.Entry = Entry; + if (Buffer.Length != FixedSize) + { + throw new ArgumentException($"Can't create Free List Object entry with buffer of size {buffer.Length} (expected {FixedSize})"); + } + Buffer = buffer; this.EntryNum = EntryNum; - UpdateBuffer(); } - public FreeListObjectEntry() // For non-entries. - { } - - public void UpdateBuffer() + public FreeListObjectEntry(ushort idxAtArray, int EntryNum) { - Buffer = BitConverter.GetBytes(Entry); + this.IdxAtArray = idxAtArray; + this.EntryNum = EntryNum; } - public void UpdateEntry() + public bool ReconstructBuffer() { - Entry = BitConverter.ToInt16(Buffer); + return true; } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/GameObject.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/GameObject.cs index 8157669..e6ae4b9 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/GameObject.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/GameObject.cs @@ -1,85 +1,98 @@ +using System.Diagnostics; using UWRandomizerEditor.Interfaces; -using static UWRandomizerEditor.Utils; // TODO: Maybe make a different class of Container objects? // TODO: What about empty entries? And the null entry (id = 0)? // TODO: Add checks that prevent modification if id=0. -namespace UWRandomizerEditor.LEVDotARK.GameObjects +namespace UWRandomizerEditor.LEVdotARK.GameObjects { - public class GameObject: IEquatable, IShouldIMove, ISaveBinary + public abstract class GameObject : IEquatable, IBufferObject { public const int InfoSize = 2; public const int InfoNum = 4; public const int BaseLength = InfoNum * InfoSize; public const int ExtraLength = 0; - public const int TotalLength = BaseLength + ExtraLength; - public short IdxAtObjectArray; + public const int FixedTotalLength = BaseLength + ExtraLength; + public ushort IdxAtObjectArray; - public virtual bool ShouldBeMoved { get; set; } = true; + public bool InContainer { get; set; } = false; - public byte[] Buffer = new byte[TotalLength]; - public short[] GeneralInfo = new short[InfoNum] { 0, 0, 0, 0 }; + public byte[] Buffer { get; set; } + // public short[] GeneralInfo = new short[InfoNum] {0, 0, 0, 0}; - public ushort link_specialField; - protected ushort objid_flagsField; - protected ushort positionField; - protected ushort quality_chainField; + protected ushort LinkSpecial; + protected ushort ObjIdFlags; + protected ushort Position; + protected ushort QualityChain; - protected GameObject() { } + /// + /// Tells whether or not an item has matching itemID and buffer size, i.e., can't have a Sword as a Mobile Object + /// + public bool Invalid { get; set; } = false; + + protected GameObject() + { + Buffer = Array.Empty(); + } - // todo: Rider is complaining about the virtual methods. Check if this will be negatively affected here. - // If not, then just re-implement the methods in this and the derived classes. - public GameObject(byte[] buffer, short idxAtObjArray) + public GameObject(byte[] buffer, ushort idxAtObjArray) { - // Debug.Assert(buffer.Length == TotalLength); - this.Buffer = buffer; - this.IdxAtObjectArray = idxAtObjArray; + if (buffer.Length != FixedTotalLength) + { + throw new ArgumentException( + $"Length of buffer ({buffer.Length}) is incompatible with GameObject length ({FixedTotalLength})"); + } + + Buffer = new byte[FixedTotalLength]; + buffer.CopyTo(Buffer, 0); + IdxAtObjectArray = idxAtObjArray; UpdateEntries(); - // this.objid_flagsField = BitConverter.ToInt16(buffer, 0); - // this.positionField = BitConverter.ToInt16(buffer, 2); - // this.quality_chainField = BitConverter.ToInt16(buffer, 4); - // this.link_specialField = BitConverter.ToInt16(buffer, 6); } - public GameObject(ushort objid_flagsField, ushort positionField, ushort quality_chainField, - ushort link_specialField) + public GameObject(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) { - this.objid_flagsField = objid_flagsField; - this.positionField = positionField; - this.quality_chainField = quality_chainField; - this.link_specialField = link_specialField; - UpdateBuffer(); + ObjIdFlags = objIdFlags; + Position = position; + QualityChain = qualityChain; + LinkSpecial = linkSpecial; + + Buffer = new byte[FixedTotalLength]; + ReconstructBuffer(); } - public void UpdateEntries() + protected virtual void UpdateEntries() { - this.objid_flagsField = BitConverter.ToUInt16(Buffer, 0); - this.positionField = BitConverter.ToUInt16(Buffer, 2); - this.quality_chainField = BitConverter.ToUInt16(Buffer, 4); - this.link_specialField = BitConverter.ToUInt16(Buffer, 6); + ObjIdFlags = BitConverter.ToUInt16(Buffer, 0); + Position = BitConverter.ToUInt16(Buffer, 2); + QualityChain = BitConverter.ToUInt16(Buffer, 4); + LinkSpecial = BitConverter.ToUInt16(Buffer, 6); } - - public void UpdateBuffer() - { // todo: this can be made more elegant. - byte[] tempbuffer = new byte[TotalLength]; - byte[] field1 = BitConverter.GetBytes(this.objid_flagsField); - byte[] field2 = BitConverter.GetBytes(this.positionField); - byte[] field3 = BitConverter.GetBytes(this.quality_chainField); - byte[] field4 = BitConverter.GetBytes(this.link_specialField); - - field1.CopyTo(tempbuffer, 0); - field2.CopyTo(tempbuffer, 2); - field3.CopyTo(tempbuffer, 4); - field4.CopyTo(tempbuffer, 6); - - tempbuffer.CopyTo(Buffer, 0); + + public virtual bool ReconstructBuffer() + { + byte[] field1 = BitConverter.GetBytes(ObjIdFlags); + byte[] field2 = BitConverter.GetBytes(Position); + byte[] field3 = BitConverter.GetBytes(QualityChain); + byte[] field4 = BitConverter.GetBytes(LinkSpecial); + + field1.CopyTo(Buffer, 0); + field2.CopyTo(Buffer, 2); + field3.CopyTo(Buffer, 4); + field4.CopyTo(Buffer, 6); + return true; } #region First Short + public int ItemID { - get { return GetBits(objid_flagsField, 0b111111111, 0); } - set { objid_flagsField = (ushort)SetBits(objid_flagsField, value, 0b111111111, 0); UpdateBuffer(); } + get { return Utils.GetBits(ObjIdFlags, 0b111111111, 0); } + set + { + ObjIdFlags = (ushort) Utils.SetBits(ObjIdFlags, value, 0b111111111, 0); + ReconstructBuffer(); + } } public int Flags @@ -87,83 +100,126 @@ public int Flags get { // todo: error in uw-formats.txt? Check hank's. -- He uses a mask with 3 bits, so I changed here. - // return GetBits(objid_flagsField, 0b1111, 9); - return GetBits(objid_flagsField, 0b111, 9); + return Utils.GetBits(ObjIdFlags, 0b111, 9); + } + set + { + ObjIdFlags = (ushort) Utils.SetBits(ObjIdFlags, value, 0b111, 9); + ReconstructBuffer(); } - set { objid_flagsField = (ushort)SetBits(objid_flagsField, value, 0b1111, 9); UpdateBuffer(); } } public int EnchantFlag { - // get { return Flags & 0b1; } - get { return GetBits(Flags, 0b1, 0); } - set { this.Flags = (short)SetBits(Flags, value, 0b1, 0); } - } // These update "Flags", which updates the buffer, so no need to call it every time. + get { return Utils.GetBits(Flags, 0b1, 0); } + set + { + Flags = (short) Utils.SetBits(Flags, value, 0b1, 0); + ReconstructBuffer(); + } + } - public int Doordir + public int DoorDir { - // get { return (Flags >> 1) & 0b1; } - get { return GetBits(Flags, 0b1, 1); } - set { this.Flags = (short)SetBits(Flags, value, 0b1, 1); } + get { return Utils.GetBits(Flags, 0b1, 1); } + set + { + Flags = (short) Utils.SetBits(Flags, value, 0b1, 1); + ReconstructBuffer(); + } } public int Invis { - // get { return (Flags >> 2) & 0b1; } - get { return GetBits(Flags, 0b1, 2); } - set { this.Flags = (short)SetBits(Flags, value, 0b1, 2); } + get { return Utils.GetBits(Flags, 0b1, 2); } + set + { + this.Flags = (short) Utils.SetBits(Flags, value, 0b1, 2); + ReconstructBuffer(); + } } public int IsQuant { - // get { return (Flags >> 3) & 0b1; } - get { return GetBits(Flags, 0b1, 3); } - set { this.Flags = (short)SetBits(Flags, value, 0b1, 3); } + get { return Utils.GetBits(Flags, 0b1, 3); } + set + { + this.Flags = (short) Utils.SetBits(Flags, value, 0b1, 3); + ReconstructBuffer(); + } } + #endregion #region Second Short + public byte Zpos { - // get { return (byte) (positionField & 0b1111111); } - get { return (byte)GetBits(positionField, 0b1111111, 0); } - set { this.positionField = (ushort)SetBits(positionField, value, 0b1111111, 0); UpdateBuffer(); } + get { return (byte) Utils.GetBits(Position, 0b1111111, 0); } + set + { + Position = (ushort) Utils.SetBits(Position, value, 0b1111111, 0); + ReconstructBuffer(); + } } public byte Heading { - // get { return (byte) ((positionField >> 7) & 0b111); } - get { return (byte)GetBits(positionField, 0b111, 7); } - set { positionField = (ushort)SetBits(positionField, value, 0b111, 7); UpdateBuffer(); } + // get { return (byte) ((position >> 7) & 0b111); } + get { return (byte) Utils.GetBits(Position, 0b111, 7); } + set + { + Position = (ushort) Utils.SetBits(Position, value, 0b111, 7); + ReconstructBuffer(); + } } public byte Ypos { - // get { return (byte) ((positionField >> 10) & 0b111); } - get { return (byte)GetBits(positionField, 0b111, 10); } - set { positionField = (ushort)SetBits(positionField, value, 0b111, 10); UpdateBuffer(); } + // get { return (byte) ((position >> 10) & 0b111); } + get { return (byte) Utils.GetBits(Position, 0b111, 10); } + set + { + Position = (ushort) Utils.SetBits(Position, value, 0b111, 10); + ReconstructBuffer(); + } } public byte Xpos { - // get { return (byte) ((positionField >> 13) & 0b111); } - get { return (byte)GetBits(positionField, 0b111, 13); } - set { positionField = (ushort)SetBits(positionField, value, 0b111, 13); UpdateBuffer(); } + // get { return (byte) ((position >> 13) & 0b111); } + get { return (byte) Utils.GetBits(Position, 0b111, 13); } + set + { + Position = (ushort) Utils.SetBits(Position, value, 0b111, 13); + ReconstructBuffer(); + } } + #endregion + #region Third Short + public byte Quality { - // get { return (byte) (quality_chainField & 0b111111); } - get { return (byte)GetBits(quality_chainField, 0b111111, 0); } - set { quality_chainField = (ushort)SetBits(quality_chainField, value, 0b111111, 0); UpdateBuffer(); } + // get { return (byte) (QualityChain & 0b111111); } + get { return (byte) Utils.GetBits(QualityChain, 0b111111, 0); } + set + { + QualityChain = (ushort) Utils.SetBits(QualityChain, value, 0b111111, 0); + ReconstructBuffer(); + } } - public short next + public ushort next { - // get { return (byte) ((quality_chainField >> 6) & 0b1111111111); } - get { return (short)GetBits(quality_chainField, 0b1111111111, 6); } - set { quality_chainField = (ushort)SetBits(quality_chainField, value, 0b1111111111, 6); UpdateBuffer(); } + // get { return (byte) ((QualityChain >> 6) & 0b1111111111); } + get { return (ushort) Utils.GetBits(QualityChain, 0b1111111111, 6); } + set + { + QualityChain = (ushort) Utils.SetBits(QualityChain, value, 0b1111111111, 6); + ReconstructBuffer(); + } } @@ -171,34 +227,45 @@ public bool IsEndOfList { get { return next == 0; } } + #endregion + #region Short 4 - public byte Owner_or_special + + public byte OwnerOrSpecial { - // get { return (byte) (link_specialField & 0b111111); } - get { return (byte)GetBits(link_specialField, 0b111111, 0); } - set { link_specialField = (ushort)SetBits(link_specialField, value, 0b111111, 0); UpdateBuffer(); } + // get { return (byte) (linkSpecial & 0b111111); } + get { return (byte) Utils.GetBits(LinkSpecial, 0b111111, 0); } + set + { + LinkSpecial = (ushort) Utils.SetBits(LinkSpecial, value, 0b111111, 0); + ReconstructBuffer(); + } } - public short QuantityOrSpecialLinkOrSpecialProperty + public ushort QuantityOrSpecialLinkOrSpecialProperty { - // get { return (byte) ((link_specialField >> 6) & 0b1111111111); } - get { return (byte)GetBits(link_specialField, 0b1111111111, 6); } - set { link_specialField = (ushort)SetBits(link_specialField, value, 0b1111111111, 6); UpdateBuffer(); } + get { return (ushort) Utils.GetBits(LinkSpecial, 0b11_1111_1111, 6); } + set + { + LinkSpecial = (ushort) Utils.SetBits(LinkSpecial, value, 0b11_1111_1111, 6); + ReconstructBuffer(); + } } + #endregion public bool HasOwner { - // get { return Owner_or_special == 0; } + // get { return OwnerOrSpecial == 0; } // todo: which one? get { return ItemOwnerStrIdx == 0; } } public int ItemOwnerStrIdx { - get { return Owner_or_special - 1 + 370; } - set { Owner_or_special = (byte) (value + 1 - 370); } + get { return OwnerOrSpecial - 1 + 370; } + set { OwnerOrSpecial = (byte) (value + 1 - 370); } } // TODO: Revise all these "Is..." functions. Would they be needed by the GameObjectFactory? @@ -206,7 +273,7 @@ public static bool IsTexturedObject(byte[] buffer) { byte firstByte = buffer[0]; int start = 0x160; // TODO: I haven't checked these indices thoroughly! - int end = 0x17f; // TODO: I haven't checked these indices thoroughly! + int end = 0x17f; // TODO: I haven't checked these indices thoroughly! if ((firstByte > start) | (firstByte < end)) return true; return false; @@ -218,6 +285,7 @@ public static bool IsQuantityObject(byte[] buffer) { return false; } + short first2bytes = BitConverter.ToInt16(buffer, 0); bool quantitybit = ((first2bytes >> 15) & 1) == 1 ? true : false; return quantitybit; @@ -229,6 +297,7 @@ public static bool IsSpecialPropertyObject(byte[] buffer) { return false; } + short lastShort = BitConverter.ToInt16(buffer, 6); int quantity = (lastShort >> 6) & 0b1111111111; if ((quantity > 512) & IsQuantityObject(buffer)) @@ -251,6 +320,7 @@ public static bool IsSpecialLinkObject(byte[] buffer) { return false; } + if (!IsQuantityObject(buffer)) { return true; @@ -278,7 +348,7 @@ public virtual bool Equals(GameObject? other) return false; } - if (this.GetType() != other.GetType()) + if (GetType() != other.GetType()) { return false; } @@ -293,14 +363,5 @@ public virtual bool Equals(GameObject? other) return true; } - - public virtual string SaveBuffer(string? basePath = null, string filename = "") - { - if (basePath is null) - { - basePath = Settings.DefaultBinaryTestsPath; - } - return StdSaveBuffer(Buffer, basePath, filename.Length == 0 ? $@"_GameObject_{IdxAtObjectArray}" : filename); - } } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/GameObjectFactory.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/GameObjectFactory.cs index 79ef866..9f2f732 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/GameObjectFactory.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/GameObjectFactory.cs @@ -1,86 +1,104 @@ -using UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; -using static UWRandomizerEditor.Utils; +using UWRandomizerEditor.LEVdotARK.Blocks; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; -namespace UWRandomizerEditor.LEVDotARK.GameObjects; +namespace UWRandomizerEditor.LEVdotARK.GameObjects; public static class GameObjectFactory { - // TODO: I'll likely need to add a reference to "FreeListOfStatic/Mobile objects" - public static GameObject CreateFromBuffer(byte[] buffer, short idxAtArray) + public static GameObject CreateFromBuffer(byte[] buffer, ushort idxAtArray) { + if ((idxAtArray == 0) | (idxAtArray == 1)) // These never hold actual object data. + { + return new MobileObject(buffer, idxAtArray) {Invalid = true}; + } + // Create a StaticObject just to get the ItemID for later. var tempObject = new StaticObject(buffer[0..8], 2); - // Just to get the itemID. I'll use this later to separate StaticObjects into different classes int itemID = tempObject.ItemID; - - if (buffer.Length == MobileObject.TotalLength) + + // Start is always MobileObjects + if (idxAtArray < TileMapMasterObjectListBlock.MobileObjectNum) { - return new MobileObject(buffer, idxAtArray); + if (buffer.Length != MobileObject.FixedTotalLength) + { + throw new InvalidOperationException( + $"Cannot create a Mobile Object with buffer of length {buffer.Length}"); + } + + // Mobile objects can only have itemIDs in this range + if ((itemID >= 0x40) & (itemID <= 0x7f)) + { + return new MobileObject(buffer, idxAtArray); + } + // Outside this range, it's not a valid MobileObject + return new MobileObject(buffer, idxAtArray) {Invalid = true}; } - if (buffer.Length == StaticObject.TotalLength) + // End is always StaticObjects + if (idxAtArray >= TileMapMasterObjectListBlock.MobileObjectNum & + idxAtArray < + (TileMapMasterObjectListBlock.MobileObjectNum + TileMapMasterObjectListBlock.StaticObjectNum)) { - return new StaticObject(buffer, idxAtArray); + if (buffer.Length != StaticObject.FixedTotalLength) + { + throw new InvalidOperationException( + $"Cannot create a Static Object with buffer of length {buffer.Length}"); + } + + if (itemID <= 0x1f) // Weapons and missiles + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0x20 & itemID <= 0x3f) // Armor and clothes + return new StaticObject(buffer, idxAtArray); + // Monsters -- dealt above. Always MobileObject. If it's here, it's invalid + if (itemID >= 0x40 & itemID <= 0x7f) + return new StaticObject(buffer, idxAtArray) {Invalid = true}; + if (itemID >= 0x80 & itemID <= 0x8f) // Containers + return new Container(buffer, idxAtArray); + if (itemID >= 0x90 & itemID <= 0x97) // Light sources + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0x98 & itemID <= 0x9f) // Wands + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0xa0 & itemID <= 0xaf) // Treasure + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0xb0 & itemID <= 0xbf) // Comestible + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0xc0 & itemID <= 0xdf) // Scenery and junk + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0xe0 & itemID <= 0xff) // Runes and bits of key + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0x100 & itemID <= 0x10e) + return new Key(buffer, idxAtArray); + if (itemID == 0x10f) + return new Lock(buffer, idxAtArray); + if (itemID >= 0x110 & itemID <= 0x11f) // quest items + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0x120 & itemID <= 0x12f) // Inventory items, misc stuff + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0x130 & itemID <= 0x13f) // Books and scrolls + return new StaticObject(buffer, idxAtArray); + if (itemID >= 0x140 & itemID <= 0x14f) // Doors + return new Door(buffer, idxAtArray); + if (itemID >= 0x150 & itemID <= 0x15f) // Furniture + return new Furniture(buffer, idxAtArray); + if (itemID >= 0x160 & itemID <= 0x16f) // Pillars, etc + return new Furniture(buffer, idxAtArray); // TODO: should be joined with furniture? + if (itemID >= 0x170 & itemID <= 0x17f) // Switches + return new TexturedGameObject(buffer, idxAtArray); + if (itemID >= 0x180 & itemID <= 0x19f) // Traps + return new Trap(buffer, idxAtArray); + if (itemID >= 0x1a0 & itemID <= 0x1bf) // Triggers + return new Trigger(buffer, idxAtArray); + if (itemID >= 0x1c0 & itemID <= 0x1cf) // Explosions, splats, fountain, silver tree + return new StaticObject(buffer, idxAtArray); + if (itemID > 0x1cf & itemID <= 0x1ff) // Not described in the text + return new StaticObject(buffer, idxAtArray); + + // return new StaticObject(buffer, idxAtArray) {Invalid = true}; + throw new Exception($"Can't create object with itemID {itemID}"); } - - // if (buffer.Length == MobileObject.TotalLength) - // { - // return new MobileObject(buffer, idxAtArray); - // } - // else if (buffer.Length == StaticObject.TotalLength) - // { - // if (itemID <= 0x1f) // Weapons and missiles - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0x20 & itemID <= 0x3f) // Armor and clothes - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0x80 & itemID <= 0x8f) // Containers - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0x90 & itemID <= 0x97) // Light sources - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0x98 & itemID <= 0x9f) // Wands - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0xa0 & itemID <= 0xaf) // Treasure - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0xb0 & itemID <= 0xbf) // Comestible - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0xc0 & itemID <= 0xdf) // Scenery and junk - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0xe0 & itemID <= 0xff) // Runes and bits of key - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0x100 & itemID <= 0x10e) - // return new Key(buffer, idxAtArray); - // if (itemID == 0x10f) - // return new Lock(buffer, idxAtArray); - // if (itemID >= 0x110 & itemID <= 0x11f) // quest items - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0x120 & itemID <= 0x12f) // Inventory items, misc stuff - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0x130 & itemID <= 0x13f) // Books and scrolls - // return new StaticObject(buffer, idxAtArray); - // if (itemID >= 0x140 & itemID <= 0x14f) // Doors - // return new Door(buffer, idxAtArray); - // if (itemID >= 0x150 & itemID <= 0x15f) // Furniture - // return new Furniture(buffer, idxAtArray); - // if (itemID >= 0x160 & itemID <= 0x16f) // Pillars, etc - // return new Furniture(buffer, idxAtArray); // TODO: should be joined with furniture? - // if (itemID >= 0x170 & itemID <= 0x17f) // Switches - // return new TexturedGameObject(buffer, idxAtArray); - // if (itemID >= 0x180 & itemID <= 0x19f) // Traps - // return new Trap(buffer, idxAtArray); - // if (itemID >= 0x1a0 & itemID <= 0x1bf) // Triggers - // return new Trigger(buffer, idxAtArray); - // if (itemID >= 0x1c0 & itemID <= 0x1cf) // Explosions, splats, fountain, silver tree - // return new StaticObject(buffer, idxAtArray) {ShouldBeMoved = false}; - // if (itemID > 0x1cf & itemID <= 0x1ff) // Not described in the text - // return new StaticObject(buffer, idxAtArray); - // - // if (itemID > 0x1ff) - // { - // throw new ArgumentException($"Invalid itemID {itemID} when creating a GameObject"); - // } - // } - throw new ArgumentException($"Invalid buffer length of {buffer.Length} when creating a GameObject"); - } + throw new InvalidOperationException( + $"Object could not be created. Either invalid buffer length ({buffer.Length}) or invalid index {idxAtArray} in GameObjectFactory"); + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/MobileObject.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/MobileObject.cs index 99d6572..d92f0c4 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/MobileObject.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/MobileObject.cs @@ -1,11 +1,12 @@ -using static UWRandomizerEditor.Utils; - -namespace UWRandomizerEditor.LEVDotARK.GameObjects +namespace UWRandomizerEditor.LEVdotARK.GameObjects { + /// + /// Class that describes Mobile Objects (e.g. NPCs). Inheritance should use the parameterless constructor because of the virtual methods to update buffer/entries. + /// public class MobileObject : GameObject { public new const int ExtraLength = 19; - public new const int TotalLength = BaseLength + ExtraLength; + public new const int FixedTotalLength = BaseLength + ExtraLength; protected const int offset1 = 0x8; protected const int offset2 = 0x9; @@ -39,97 +40,139 @@ public class MobileObject : GameObject protected byte byte_NPCHunger; // offset 0019 protected byte byte_NPCwhoami; // offset 001a - public new byte[] Buffer = new byte[TotalLength]; public int ObjectBufferIfx; - public override bool ShouldBeMoved { get; set; } = false; - public int HP { get { return (int) byte1_hp; } - set { byte1_hp = (byte) value; UpdateBuffer();} + set + { + byte1_hp = (byte) value; + ReconstructBuffer(); + } } public int Goal { - get { return GetBits(short_NPCGoalGtarg, 0b1111, 0); } - set { short_NPCGoalGtarg = (short) SetBits(short_NPCGoalGtarg, value, 0b1111, 0); UpdateBuffer();} + get { return Utils.GetBits(short_NPCGoalGtarg, 0b1111, 0); } + set + { + short_NPCGoalGtarg = (short) Utils.SetBits(short_NPCGoalGtarg, value, 0b1111, 0); + ReconstructBuffer(); + } } public int Gtarg { - get { return GetBits(short_NPCGoalGtarg, 0b11111111, 4); } - set { short_NPCGoalGtarg = (short) SetBits(short_NPCGoalGtarg, value, 0b11111111, 4); UpdateBuffer();} + get { return Utils.GetBits(short_NPCGoalGtarg, 0b11111111, 4); } + set + { + short_NPCGoalGtarg = (short) Utils.SetBits(short_NPCGoalGtarg, value, 0b11111111, 4); + ReconstructBuffer(); + } } public int Level { - get { return GetBits(short_NPCLevelTalkedAttitude, 0b111, 0); } - set { short_NPCLevelTalkedAttitude = (short) SetBits(short_NPCLevelTalkedAttitude, value, 0b111, 0); UpdateBuffer();} + get { return Utils.GetBits(short_NPCLevelTalkedAttitude, 0b111, 0); } + set + { + short_NPCLevelTalkedAttitude = (short) Utils.SetBits(short_NPCLevelTalkedAttitude, value, 0b111, 0); + ReconstructBuffer(); + } } public bool TalkedTo { - get { return GetBits(short_NPCLevelTalkedAttitude, 0b1, 13) == 1; } - set { short_NPCLevelTalkedAttitude = (short) SetBits(short_NPCLevelTalkedAttitude, value ? 1:0, 0b1, 13); UpdateBuffer();} + get { return Utils.GetBits(short_NPCLevelTalkedAttitude, 0b1, 13) == 1; } + set + { + short_NPCLevelTalkedAttitude = + (short) Utils.SetBits(short_NPCLevelTalkedAttitude, value ? 1 : 0, 0b1, 13); + ReconstructBuffer(); + } } public int Attitude { - get { return GetBits(short_NPCLevelTalkedAttitude, 0b11, 14); } - set { short_NPCLevelTalkedAttitude = (short) SetBits(short_NPCLevelTalkedAttitude, value, 0b11, 14); UpdateBuffer();} + get { return Utils.GetBits(short_NPCLevelTalkedAttitude, 0b11, 14); } + set + { + short_NPCLevelTalkedAttitude = (short) Utils.SetBits(short_NPCLevelTalkedAttitude, value, 0b11, 14); + ReconstructBuffer(); + } } public int Height { - get { return GetBits(short_NPCheightQM, 0b1111111, 6); } - set { short_NPCheightQM = (short) SetBits(short_NPCheightQM, value, 0b1111111, 6); UpdateBuffer();} + get { return Utils.GetBits(short_NPCheightQM, 0b1111111, 6); } + set + { + short_NPCheightQM = (short) Utils.SetBits(short_NPCheightQM, value, 0b1111111, 6); + ReconstructBuffer(); + } } public int YHome { - get { return GetBits(short_NPChome, 0b111111, 4); } - set { short_NPChome = (short) SetBits(short_NPChome, value, 0b111111, 4); UpdateBuffer();} + get { return Utils.GetBits(short_NPChome, 0b111111, 4); } + set + { + short_NPChome = (short) Utils.SetBits(short_NPChome, value, 0b111111, 4); + ReconstructBuffer(); + } } public int XHome { - get { return GetBits(short_NPChome, 0b111111, 10); } - set { short_NPChome = (short) SetBits(short_NPChome, value, 0b111111, 10); UpdateBuffer();} + get { return Utils.GetBits(short_NPChome, 0b111111, 10); } + set + { + short_NPChome = (short) Utils.SetBits(short_NPChome, value, 0b111111, 10); + ReconstructBuffer(); + } } public int NPCHeading { - get { return GetBits(byte_NPCheading, 0b1111, 0); } - set { byte_NPCheading = (byte) SetBits(byte_NPCheading, value, 0b1111, 0); UpdateBuffer();} + get { return Utils.GetBits(byte_NPCheading, 0b1111, 0); } + set + { + byte_NPCheading = (byte) Utils.SetBits(byte_NPCheading, value, 0b1111, 0); + ReconstructBuffer(); + } } public int Hunger { - get { return GetBits(byte_NPCHunger, 0b111111, 0); } - set { byte_NPCHunger = (byte) SetBits(byte_NPCHunger, value, 0b111111, 0); UpdateBuffer();} + get { return Utils.GetBits(byte_NPCHunger, 0b111111, 0); } + set + { + byte_NPCHunger = (byte) Utils.SetBits(byte_NPCHunger, value, 0b111111, 0); + ReconstructBuffer(); + } } public int whoami { - get { return GetBits(byte_NPCwhoami, 0b11111111, 0); } - set { byte_NPCwhoami = (byte) SetBits(byte_NPCwhoami, value, 0b11111111, 0); UpdateBuffer();} + get { return Utils.GetBits(byte_NPCwhoami, 0b11111111, 0); } + set + { + byte_NPCwhoami = (byte) Utils.SetBits(byte_NPCwhoami, value, 0b11111111, 0); + ReconstructBuffer(); + } } - public new void UpdateBuffer() + // TODO: Create interface, standardize this with Container and TileInfo? + public UWLinkedList Inventory { get; set; } + + public override bool ReconstructBuffer() { - // From base - byte[] tempbuffer = new byte[TotalLength]; - byte[] field1 = BitConverter.GetBytes(this.objid_flagsField); - byte[] field2 = BitConverter.GetBytes(this.positionField); - byte[] field3 = BitConverter.GetBytes(this.quality_chainField); - byte[] field4 = BitConverter.GetBytes(this.link_specialField); - field1.CopyTo(tempbuffer, 0); - field2.CopyTo(tempbuffer, 2); - field3.CopyTo(tempbuffer, 4); - field4.CopyTo(tempbuffer, 6); - tempbuffer.CopyTo(Buffer, 0); - // new ones + // QuantityOrSpecialLinkOrSpecialProperty = (ushort) Inventory.startingIdx; + // Avoiding infinite loop. TODO: think of something more intelligent + LinkSpecial = (ushort) Utils.SetBits(LinkSpecial, Inventory.startingIdx, 0b11_1111_1111, 6); + base.ReconstructBuffer(); + Buffer[offset1] = byte1_hp; Buffer[offset2] = byte2_unk; Buffer[offset3] = byte3_unk; @@ -145,15 +188,12 @@ public int whoami Buffer[offset13] = byte_NPCheading; Buffer[offset14] = byte_NPCHunger; Buffer[offset15] = byte_NPCwhoami; + return true; } - public new void UpdateEntries() + protected override void UpdateEntries() { - // From base - objid_flagsField = BitConverter.ToUInt16(Buffer, 0); - positionField = BitConverter.ToUInt16(Buffer, 2); - quality_chainField = BitConverter.ToUInt16(Buffer, 4); - link_specialField = BitConverter.ToUInt16(Buffer, 6); + base.UpdateEntries(); // New ones byte1_hp = Buffer[offset1]; byte2_unk = Buffer[offset2]; @@ -171,22 +211,26 @@ public int whoami byte_NPCHunger = Buffer[offset14]; byte_NPCwhoami = Buffer[offset15]; } - - public MobileObject(byte[] buffer, short idx) + + // Reminder: don't call base... Need to design this better + public MobileObject(byte[] buffer, ushort idx) { - // Debug.Assert(buffer.Length == TotalLength); - this.Buffer = buffer; - base.Buffer = buffer[..8]; // In case this is cast as a GameObject, it'll preserve the base buffer - this.IdxAtObjectArray = idx; + // Debug.Assert(buffer.Length == FixedTotalLength); + Buffer = new byte[FixedTotalLength]; + buffer.CopyTo(Buffer, 0); + IdxAtObjectArray = idx; UpdateEntries(); + Inventory = new UWLinkedList() {startingIdx = QuantityOrSpecialLinkOrSpecialProperty, RepresentingContainer = true}; } - public MobileObject(byte[] baseBuffer, byte byte1_hp, byte unk2, byte unk3, short NPCGoalGTarg, short NPCLevelTalked, + // Reminder: don't call base... Need to design this better + public MobileObject(byte[] baseBuffer, byte byte1_hp, byte unk2, byte unk3, short NPCGoalGTarg, + short NPCLevelTalked, short NPCheight, byte unk4, byte unk5, byte unk6, - byte unk7, byte unk8, short NPChome, byte heading, byte hunger, byte whoami, short idx) + byte unk7, byte unk8, short NPChome, byte heading, byte hunger, byte whoami, ushort idx) { + Buffer = new byte[FixedTotalLength]; baseBuffer.CopyTo(Buffer, 0); - base.Buffer = baseBuffer; byte[] extra = new byte[ExtraLength] { byte1_hp, @@ -212,18 +256,21 @@ public MobileObject(byte[] baseBuffer, byte byte1_hp, byte unk2, byte unk3, shor extra.CopyTo(Buffer, BaseLength); IdxAtObjectArray = idx; UpdateEntries(); + Inventory = new UWLinkedList() {startingIdx = QuantityOrSpecialLinkOrSpecialProperty}; } - public MobileObject(short short1, short short2, short short3, short short4, byte byte1_hp, byte unk2, byte unk3, short NPCGoalGTarg, short NPCLevelTalked, + + public MobileObject(short short1, short short2, short short3, short short4, byte byte1_hp, byte unk2, byte unk3, + short NPCGoalGTarg, short NPCLevelTalked, short NPCheight, byte unk4, byte unk5, byte unk6, - byte unk7, byte unk8, short NPChome, byte heading, byte hunger, byte whoami, short idx) + byte unk7, byte unk8, short NPChome, byte heading, byte hunger, byte whoami, ushort idx) { byte[] baseBuffer = new byte[BaseLength]; - BitConverter.GetBytes(short1).CopyTo(baseBuffer, 2*0); - BitConverter.GetBytes(short2).CopyTo(baseBuffer, 2*1); - BitConverter.GetBytes(short3).CopyTo(baseBuffer, 2*2); - BitConverter.GetBytes(short4).CopyTo(baseBuffer, 2*3); - base.Buffer = baseBuffer; - + BitConverter.GetBytes(short1).CopyTo(baseBuffer, 2 * 0); + BitConverter.GetBytes(short2).CopyTo(baseBuffer, 2 * 1); + BitConverter.GetBytes(short3).CopyTo(baseBuffer, 2 * 2); + BitConverter.GetBytes(short4).CopyTo(baseBuffer, 2 * 3); + baseBuffer.CopyTo(Buffer, 0); + byte[] extra = new byte[ExtraLength] { byte1_hp, @@ -249,15 +296,14 @@ public MobileObject(short short1, short short2, short short3, short short4, byte extra.CopyTo(Buffer, BaseLength); IdxAtObjectArray = idx; UpdateEntries(); + Inventory = new UWLinkedList() {startingIdx = QuantityOrSpecialLinkOrSpecialProperty, RepresentingContainer = true}; } - public override string SaveBuffer(string? basePath = null, string filename = "") - { - if (basePath is null) + + protected MobileObject() { - basePath = Settings.DefaultBinaryTestsPath; + Buffer = Array.Empty(); + Invalid = true; + Inventory = new UWLinkedList() {startingIdx = 0, RepresentingContainer = true}; } - return StdSaveBuffer(Buffer, basePath, filename.Length == 0 ? $@"_GameObject_{IdxAtObjectArray}" : filename); - } - } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/QuantityGameObject.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/QuantityGameObject.cs index dc3d127..8bd9da6 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/QuantityGameObject.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/QuantityGameObject.cs @@ -1,11 +1,11 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects; +namespace UWRandomizerEditor.LEVdotARK.GameObjects; public class QuantityGameObject : StaticObject { // is_quant is true, quantity < 512 (coins, etc) - public short Quantity + public ushort Quantity { - // get { return (byte) ((link_specialField >> 6) & 0b1111111111); } + // get { return (byte) ((linkSpecial >> 6) & 0b1111111111); } get { return QuantityOrSpecialLinkOrSpecialProperty; } set { @@ -13,17 +13,19 @@ public short Quantity { throw new Exception("Cannot have a Quantity Game Object with quantity > 512"); } - link_specialField = (ushort)Utils.SetBits(link_specialField, value, 0b1111111111, 6); UpdateBuffer(); + + LinkSpecial = (ushort) Utils.SetBits(LinkSpecial, value, 0b1111111111, 6); + ReconstructBuffer(); } } - public QuantityGameObject(byte[] buffer, short idx) : base(buffer, idx) - { } + public QuantityGameObject(byte[] buffer, ushort idx) : base(buffer, idx) + { + } - public QuantityGameObject(ushort objid_flagsField, ushort positionField, - ushort quality_chainField, ushort link_specialField) : base(objid_flagsField, positionField, - quality_chainField, link_specialField) - { } - - + public QuantityGameObject(ushort objIdFlags, ushort position, + ushort qualityChain, ushort linkSpecial) : base(objIdFlags, position, + qualityChain, linkSpecial) + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/SpecialLinkGameObject.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/SpecialLinkGameObject.cs index 8b937bb..89ad116 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/SpecialLinkGameObject.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/SpecialLinkGameObject.cs @@ -1,20 +1,27 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects; +using System.Diagnostics; + +namespace UWRandomizerEditor.LEVdotARK.GameObjects; public class SpecialLinkGameObject : StaticObject { - // is_quant is false. Enchantments, wands, etc - public new readonly int IsQuant = 0; - public short SpecialIdx + public ushort SpecialIdx { get { return QuantityOrSpecialLinkOrSpecialProperty; } - set { link_specialField = (ushort)Utils.SetBits(link_specialField, value, 0b1111111111, 6); UpdateBuffer(); } + set + { + LinkSpecial = (ushort) Utils.SetBits(LinkSpecial, value, 0b1111111111, 6); + ReconstructBuffer(); + } + } + + public SpecialLinkGameObject(byte[] buffer, ushort idx) : base(buffer, idx) + { + Debug.Assert(IsQuant == 0); } - - public SpecialLinkGameObject(byte[] buffer, short idx) : base(buffer, idx) - { } - public SpecialLinkGameObject(ushort objid_flagsField, ushort positionField, ushort quality_chainField, - ushort link_specialField) : base(objid_flagsField, positionField, quality_chainField, link_specialField) - { } - + public SpecialLinkGameObject(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) : base(objIdFlags, position, qualityChain, linkSpecial) + { + Debug.Assert(IsQuant == 0); + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/SpecialPropertyGameObject.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/SpecialPropertyGameObject.cs index 18e4747..7a1a912 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/SpecialPropertyGameObject.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/SpecialPropertyGameObject.cs @@ -1,4 +1,4 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects; +namespace UWRandomizerEditor.LEVdotARK.GameObjects; // is_quant is true, quantity > 512 (special property) public class SpecialPropertyGameObject : StaticObject @@ -8,9 +8,9 @@ public short RawSpecialLink get { return Convert.ToInt16(SpecialLink + 512); } set { SpecialLink = Convert.ToInt16(value + 512); } } + public short SpecialLink { - // get { return (byte) ((link_specialField >> 6) & 0b1111111111); } get { return Convert.ToInt16(QuantityOrSpecialLinkOrSpecialProperty - 512); } set { @@ -18,14 +18,18 @@ public short SpecialLink { throw new Exception("Cannot have a SpecialLink with value < 512"); } - link_specialField = (ushort)Utils.SetBits(link_specialField, value, 0b1111111111, 6); UpdateBuffer(); + + LinkSpecial = (ushort) Utils.SetBits(LinkSpecial, value, 0b1111111111, 6); + ReconstructBuffer(); } } - - public SpecialPropertyGameObject(byte[] buffer, short idx) : base(buffer, idx) - { } - public SpecialPropertyGameObject(ushort objid_flagsField, ushort positionField, ushort quality_chainField, - ushort link_specialField) : base(objid_flagsField, positionField, quality_chainField, link_specialField) - { } + public SpecialPropertyGameObject(byte[] buffer, ushort idx) : base(buffer, idx) + { + } + + public SpecialPropertyGameObject(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) : base(objIdFlags, position, qualityChain, linkSpecial) + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Container.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Container.cs new file mode 100644 index 0000000..55831ea --- /dev/null +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Container.cs @@ -0,0 +1,25 @@ +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; + +public class Container: SpecialLinkGameObject +{ + public Container(byte[] buffer, ushort idx) : base(buffer, idx) + { + Contents = new UWLinkedList() {startingIdx = QuantityOrSpecialLinkOrSpecialProperty, RepresentingContainer = true}; + } + + public Container(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) : base(objIdFlags, position, qualityChain, linkSpecial) + { + Contents = new UWLinkedList() {startingIdx = QuantityOrSpecialLinkOrSpecialProperty, RepresentingContainer = true}; + } + + public UWLinkedList Contents { get; set; } + + public override bool ReconstructBuffer() + { + // Workaround to avoid infinite loop. TODO: Fix this + LinkSpecial = (ushort) Utils.SetBits(LinkSpecial, Contents.startingIdx, 0b11_1111_1111, 6); + base.ReconstructBuffer(); + return base.ReconstructBuffer(); + } +} \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Doors.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Doors.cs index 163f263..25d4bfa 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Doors.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Doors.cs @@ -1,56 +1,64 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; -using static UWRandomizerEditor.Utils; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; -public class Door: SpecialLinkGameObject +public class Door : SpecialLinkGameObject { - public Door(byte[] buffer, short idxAtObjArray): base(buffer, idxAtObjArray) {} - - public override bool ShouldBeMoved { get; set; } = false; - + public Door(byte[] buffer, ushort idxAtObjArray) : base(buffer, idxAtObjArray) + { + } + + // On doors: // Bit 1 of "owner" field is set, door is spiked // sp_link field points to a_lock object (010f), door is locked. - + /// /// Points the "sp_link" field to 0, removing any reference to a lock. /// public void RemoveLock() { - link_specialField = 0; - UpdateBuffer(); + LinkSpecial = 0; + ReconstructBuffer(); } // TODO: add verification /// - /// Checks if sp_link (link_specialField) points to something other than 0. If so, returns the value, which + /// Checks if sp_link (linkSpecial) points to something other than 0. If so, returns the value, which /// is the index of the lock. /// - /// Index of the lock object - public ushort HasLock() + public bool HasLock(out ushort LockIdx) { - if (link_specialField != 0) + LockIdx = LinkSpecial; + return HasLock(); + } + + /// + /// Checks if sp_link (linkSpecial) points to something other than 0. + /// + public bool HasLock() + { + if (LinkSpecial != 0) { - return link_specialField; + return true; } - return link_specialField; + return false; } /// - /// Replaces sp_link (link_specialField) with the value provided. Does not check if it really points to a lock object. + /// Replaces sp_link (linkSpecial) with the value provided. Does not check if it really points to a lock object. /// /// public void AddLock(ushort lock_idx) { // TODO: add some checks - link_specialField = lock_idx; - UpdateBuffer(); + LinkSpecial = lock_idx; + ReconstructBuffer(); } - + // TODO: By "bit 1" does it mean 0b10 or 0b01? I'm assuming it's the former. public bool IsSpiked() { - if (GetBits(Owner_or_special, 0b10, 0) == 1) + if (Utils.GetBits(OwnerOrSpecial, 0b10, 0) == 1) { return true; } @@ -60,27 +68,26 @@ public bool IsSpiked() public void AddSpike() { - SetBits(Owner_or_special, 0b1, 0b10, 0); - UpdateBuffer(); + Utils.SetBits(OwnerOrSpecial, 0b1, 0b10, 0); + ReconstructBuffer(); } public void RemoveSpike() { - SetBits(Owner_or_special, 0b0, 0b10, 0); - UpdateBuffer(); + Utils.SetBits(OwnerOrSpecial, 0b0, 0b10, 0); + ReconstructBuffer(); } public void Unlock(GameObject[] blockGameObjects) { - if (blockGameObjects[link_specialField] is Lock LockObject) + if (blockGameObjects[LinkSpecial] is Lock LockObject) { LockObject.IsLocked = false; - LockObject.UpdateBuffer(); + LockObject.ReconstructBuffer(); } else { throw new InvalidOperationException("Cannot unlock because door isn't pointing to a lock object"); } } - } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedArmor.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedArmor.cs index 254425d..e1f9a64 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedArmor.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedArmor.cs @@ -1,25 +1,28 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; public class EnchantedArmor : SpecialLinkGameObject { public new readonly int EnchantFlag = 1; + public int Enchantment { get { return SpecialIdx - 512; } - set { SpecialIdx = (short) (value + 512); } + set { SpecialIdx = (ushort) (value + 512); } } // Oh boy. This is more complicated. Need to have logic to differentiate between Acc/Dam/Prot/Tough and other spells public int Spell { get { return Enchantment + 256 + 16; } - set { Enchantment = value - 256 - 16; } // todo: these will UpdateBuffer too right? + set { Enchantment = value - 256 - 16; } // todo: these will ReconstructBuffer too right? } - public EnchantedArmor(byte[] buffer, short idx) : base(buffer, idx) - { } + public EnchantedArmor(byte[] buffer, ushort idx) : base(buffer, idx) + { + } - public EnchantedArmor(ushort objid_flagsField, ushort positionField, ushort quality_chainField, - ushort link_specialField) : base(objid_flagsField, positionField, quality_chainField, link_specialField) - { } + public EnchantedArmor(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) : base(objIdFlags, position, qualityChain, linkSpecial) + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedObject.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedObject.cs index 1fd43b2..b3e4209 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedObject.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedObject.cs @@ -1,4 +1,4 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; // Todo: Get enchantment name in strings chunk 5 // Todo: create an enum or something with spell names, and their indices, to use here. @@ -9,7 +9,7 @@ public class EnchantedObject : SpecialLinkGameObject public int Enchantment { get { return SpecialIdx - 512; } - set { SpecialIdx = (short) (value + 512); } + set { SpecialIdx = (ushort) (value + 512); } } public int Spell @@ -31,14 +31,15 @@ public int Spell Enchantment = value; else Enchantment = value - 144; - } // todo: these will UpdateBuffer too right? + } // todo: these will ReconstructBuffer too right? } - public EnchantedObject(byte[] buffer, short idx) : base(buffer, idx) - { } + public EnchantedObject(byte[] buffer, ushort idx) : base(buffer, idx) + { + } - public EnchantedObject(ushort objid_flagsField, ushort positionField, ushort quality_chainField, - ushort link_specialField) : base(objid_flagsField, positionField, quality_chainField, link_specialField) - { } - + public EnchantedObject(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) : base(objIdFlags, position, qualityChain, linkSpecial) + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedWand.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedWand.cs index e621031..04d69da 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedWand.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/EnchantedWand.cs @@ -1,27 +1,24 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; public class EnchantedWand : SpecialLinkGameObject { - public new readonly int EnchantFlag = 1; public int SpellObjectLink { get { return SpecialIdx; } - set { SpecialIdx = (short) value; } + set { SpecialIdx = (ushort) value; } } public GameObject SpellObject; - public EnchantedWand(byte[] buffer, short idx) : base(buffer, idx) + public EnchantedWand(byte[] buffer, ushort idx) : base(buffer, idx) { throw new NotImplementedException(); // TODO: Need to link to SpellObject } - public EnchantedWand(ushort objid_flagsField, ushort positionField, ushort quality_chainField, - ushort link_specialField) : base(objid_flagsField, positionField, quality_chainField, link_specialField) + public EnchantedWand(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) : base(objIdFlags, position, qualityChain, linkSpecial) { throw new NotImplementedException(); } - - } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Furniture.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Furniture.cs index 5914060..550ce2e 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Furniture.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Furniture.cs @@ -1,10 +1,10 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; // Should this inherit from textured game object? -public class Furniture: StaticObject +public class Furniture : StaticObject { - public Furniture(byte[] buffer, short idxAtObjArray) : base(buffer, idxAtObjArray) - { } + public Furniture(byte[] buffer, ushort idxAtObjArray) : base(buffer, idxAtObjArray) + { + } - public override bool ShouldBeMoved { get; set; } = false; } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Key.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Key.cs index a73997a..3ca7107 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Key.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Key.cs @@ -1,27 +1,28 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; -public class Key: StaticObject +public class Key : StaticObject { - public Key(byte[] buffer, short idxAtObjArray) : base(buffer, idxAtObjArray) { } - - public Key(ushort objid_flagsField, ushort positionField, ushort quality_chainField, ushort link_specialField) - : base(objid_flagsField, positionField, quality_chainField, link_specialField) - { } + public Key(byte[] buffer, ushort idxAtObjArray) : base(buffer, idxAtObjArray) + { + } + + public Key(ushort objIdFlags, ushort position, ushort qualityChain, ushort linkSpecial) + : base(objIdFlags, position, qualityChain, linkSpecial) + { + } public Key() : base() - { } + { + } public byte KeyID { - get - { - return Owner_or_special; - } + get { return OwnerOrSpecial; } set { if (value >= 0b1000000) // This should only have 6 bits in length throw new InvalidDataException("Invalid range for key ID. Must fit in 6 bits (<=63)"); - Owner_or_special = value; + OwnerOrSpecial = value; } } diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Lock.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Lock.cs index 0a87215..f7f892c 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Lock.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Lock.cs @@ -1,15 +1,13 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; - -using static UWRandomizerEditor.Utils; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; public class Lock : StaticObject { - public Lock(byte[] buffer, short idxAtObjArray) : base(buffer, idxAtObjArray) + public Lock(byte[] buffer, ushort idxAtObjArray) : base(buffer, idxAtObjArray) { } - public Lock(ushort objid_flagsField, ushort positionField, ushort quality_chainField, ushort link_specialField) - : base(objid_flagsField, positionField, quality_chainField, link_specialField) + public Lock(ushort objIdFlags, ushort position, ushort qualityChain, ushort linkSpecial) + : base(objIdFlags, position, qualityChain, linkSpecial) { } @@ -19,18 +17,18 @@ public Lock() : base() public bool IsLocked { - get { return GetBits(objid_flagsField, 0b1, 9) == 1; } - set { objid_flagsField = (ushort) SetBits(objid_flagsField, value ? 1 : 0, 0b1, 9); } + get { return Utils.GetBits(ObjIdFlags, 0b1, 9) == 1; } + set { ObjIdFlags = (ushort) Utils.SetBits(ObjIdFlags, value ? 1 : 0, 0b1, 9); } } public byte KeyID { - get { return (byte) GetBits(link_specialField, 0b111111, 0); } + get { return (byte) Utils.GetBits(LinkSpecial, 0b111111, 0); } set { if (value >= 0b1000000) // This should only have 6 bits in length throw new InvalidDataException("Invalid range for key ID. Must fit in 6 bits (<=63)"); - link_specialField = (byte) SetBits(link_specialField, value, 0b111111, 0); + LinkSpecial = (byte) Utils.SetBits(LinkSpecial, value, 0b111111, 0); } } diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Trap.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Trap.cs index 2a4fc9a..4c3e59a 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Trap.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Trap.cs @@ -1,8 +1,8 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; -public class Trap: StaticObject +public class Trap : StaticObject { - public override bool ShouldBeMoved { get; set; } = false; - public Trap(byte[] buffer, short idxAtObjArray) : base(buffer, idxAtObjArray) - { } + public Trap(byte[] buffer, ushort idxAtObjArray) : base(buffer, idxAtObjArray) + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Trigger.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Trigger.cs index e9ed06f..16c2433 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Trigger.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Trigger.cs @@ -1,8 +1,9 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; // TODO: special link or special property? -public class Trigger: SpecialLinkGameObject +public class Trigger : SpecialLinkGameObject { - public override bool ShouldBeMoved { get; set; } = false; - public Trigger(byte[] buffer, short IdxAtObjArray) : base(buffer, IdxAtObjArray) { } + public Trigger(byte[] buffer, ushort IdxAtObjArray) : base(buffer, IdxAtObjArray) + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Weapon.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Weapon.cs index 18ef7a3..a915bd2 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Weapon.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/Specifics/Weapon.cs @@ -1,34 +1,28 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +namespace UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; public class EnchantedWeapon : SpecialLinkGameObject { - public new readonly int EnchantFlag = 1; public int Enchantment { get { return SpecialIdx - 512; } - set { SpecialIdx = (short) (value + 512); } + set { SpecialIdx = (ushort) (value + 512); } } // Oh boy. This is more complicated. Need to have logic to differentiate between Acc/Dam/Prot/Tough and other spells public int Spell { - get - { - return Enchantment + 256; - } - set - { - Enchantment = value - 256; - } // todo: these will UpdateBuffer too right? + get { return Enchantment + 256; } + set { Enchantment = value - 256; } // todo: these will ReconstructBuffer too right? } - public EnchantedWeapon(byte[] buffer, short idx) : base(buffer, idx) - { } + public EnchantedWeapon(byte[] buffer, ushort idx) : base(buffer, idx) + { + } - public EnchantedWeapon(ushort objid_flagsField, ushort positionField, ushort quality_chainField, - ushort link_specialField) : base(objid_flagsField, positionField, quality_chainField, link_specialField) - { } - + public EnchantedWeapon(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) : base(objIdFlags, position, qualityChain, linkSpecial) + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/StaticObject.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/StaticObject.cs index fe9d665..926d3b4 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/StaticObject.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/StaticObject.cs @@ -1,14 +1,17 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects; +namespace UWRandomizerEditor.LEVdotARK.GameObjects; -public class StaticObject: GameObject +public class StaticObject : GameObject { - public StaticObject(byte[] buffer, short idxAtObjArray) : base(buffer, idxAtObjArray) { } + public StaticObject(byte[] buffer, ushort idxAtObjArray) : base(buffer, idxAtObjArray) + { + } - public StaticObject(ushort objid_flagsField, ushort positionField, ushort quality_chainField, ushort link_specialField) - : base(objid_flagsField, positionField, quality_chainField, link_specialField) - { } + public StaticObject(ushort objIdFlags, ushort position, ushort qualityChain, ushort linkSpecial) + : base(objIdFlags, position, qualityChain, linkSpecial) + { + } - public StaticObject() : base() + protected StaticObject() : base() { } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/GameObjects/TexturedGameObject.cs b/UWRandomizerEditor/LEVDotARK/GameObjects/TexturedGameObject.cs index 7722548..5b55de7 100644 --- a/UWRandomizerEditor/LEVDotARK/GameObjects/TexturedGameObject.cs +++ b/UWRandomizerEditor/LEVDotARK/GameObjects/TexturedGameObject.cs @@ -1,31 +1,21 @@ -namespace UWRandomizerEditor.LEVDotARK.GameObjects; +using System.Diagnostics; + +namespace UWRandomizerEditor.LEVdotARK.GameObjects; public class TexturedGameObject : StaticObject { - public new bool HasTexture = true; - public new readonly int? Flags = null; - public new readonly int? EnchantFlag = null; - public new readonly int? Doordir = null; - public new readonly int? Invis = null; - public new readonly int? IsQuant = null; - - public override bool ShouldBeMoved { get; set; } = false; - public int TextureNumber { - get { return Utils.GetBits(objid_flagsField, 0b1111111, 9); } - set { objid_flagsField = (ushort)Utils.SetBits(objid_flagsField, value, 0b1111111, 9); } + get { return Utils.GetBits(ObjIdFlags, 0b1111111, 9); } + set { ObjIdFlags = (ushort) Utils.SetBits(ObjIdFlags, value, 0b1111111, 9); } } - public TexturedGameObject(byte[] buffer, short idx) : base(buffer, idx) - { } - - public TexturedGameObject(ushort objid_flagsField, ushort positionField, ushort quality_chainField, - ushort link_specialField) : base(objid_flagsField, positionField, quality_chainField, link_specialField) - { } + public TexturedGameObject(byte[] buffer, ushort idx) : base(buffer, idx) + { + } - // public new void UpdateBuffer() - // { - // base.UpdateBuffer(); // todo: recheck that this is working as intended. - // } + public TexturedGameObject(ushort objIdFlags, ushort position, ushort qualityChain, + ushort linkSpecial) : base(objIdFlags, position, qualityChain, linkSpecial) + { + } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/Header.cs b/UWRandomizerEditor/LEVDotARK/Header.cs index f63af78..3b721e4 100644 --- a/UWRandomizerEditor/LEVDotARK/Header.cs +++ b/UWRandomizerEditor/LEVDotARK/Header.cs @@ -1,54 +1,63 @@ -using UWRandomizerEditor.Interfaces; +using System.Diagnostics.CodeAnalysis; +using UWRandomizerEditor.Interfaces; -namespace UWRandomizerEditor.LEVDotARK; -using static Utils; +namespace UWRandomizerEditor.LEVdotARK; /// /// A small class that contains the header of a lev.ark file. The header is composed of a short with the number of /// entries (blocks) followed by ints with the offsets to the start of each block in lev.ark /// -public class Header: ISaveBinary +public class Header : IBufferObject { - public byte[] buffer; - public static int blockNumSize = 2; // a short + public byte[] Buffer { get; set; } + public static int blockNumSize = 2; // a short public static int blockOffsetSize = 4; // int32 public int[] BlockOffsets; + public bool ReconstructBuffer() + { + // TODO: Ideally, this should get all the blocks... But I'm leaving it like this for now, for simplicity. + // It's very unlikely that I'll change the block sizes in the near future. + return true; + } + public int Size { get { return blockOffsetSize * NumEntries + blockNumSize; } } - public static short NumEntriesFromBuffer(byte[] buffer) + public static ushort NumEntriesFromBuffer(byte[] buffer) { - return BitConverter.ToInt16(buffer, 0); + return BitConverter.ToUInt16(buffer, 0); } - public short NumEntries + public ushort NumEntries { - get { return NumEntriesFromBuffer(buffer); } + get { return NumEntriesFromBuffer(Buffer); } set { if (value <= 0) { throw new ArgumentException("Number of entries has to be greater than 0!"); } + Console.WriteLine("Changing number of entries. Be careful!"); byte[] newbuffer = new byte[value]; BitConverter.GetBytes(value).CopyTo(newbuffer, 0); - buffer.CopyTo(newbuffer, 0); - buffer = newbuffer; + Buffer.CopyTo(newbuffer, 0); + Buffer = newbuffer; } } public int GetOffsetForBlock(int blockNum) { // int blockOffset = BitConverter.ToInt32(buffer[(2 + 4 * blockNum)..(6 + 4 * blockNum)]); - int blockOffset = BitConverter.ToInt32(buffer, (blockNumSize + blockOffsetSize * blockNum)); + int blockOffset = BitConverter.ToInt32(Buffer, (blockNumSize + blockOffsetSize * blockNum)); return blockOffset; } + [MemberNotNull("BlockOffsets")] public void CalculateOffsets() { BlockOffsets = new int[NumEntries]; @@ -60,17 +69,7 @@ public void CalculateOffsets() public Header(byte[] buffer) { - this.buffer = buffer; + Buffer = buffer; CalculateOffsets(); } - - public string SaveBuffer(string basePath = "D:\\Dropbox\\UnderworldStudy\\studies\\LEV.ARK", string filename = "") - { - if (filename.Length == 0) - { - filename = $@"_HEADER"; - } - - return StdSaveBuffer(buffer, basePath, filename); - } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/MasterObjectList.cs b/UWRandomizerEditor/LEVDotARK/MasterObjectList.cs index 6504791..bac3201 100644 --- a/UWRandomizerEditor/LEVDotARK/MasterObjectList.cs +++ b/UWRandomizerEditor/LEVDotARK/MasterObjectList.cs @@ -1,4 +1,4 @@ -namespace UWRandomizerEditor.LEVDotARK +namespace UWRandomizerEditor.LEVdotARK { public class MasterObjectList { diff --git a/UWRandomizerEditor/LEVDotARK/TileInfo.cs b/UWRandomizerEditor/LEVDotARK/TileInfo.cs index 410326f..c6dceb0 100644 --- a/UWRandomizerEditor/LEVDotARK/TileInfo.cs +++ b/UWRandomizerEditor/LEVDotARK/TileInfo.cs @@ -1,135 +1,158 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using UWRandomizerEditor.Interfaces; -using UWRandomizerEditor.LEVDotARK.Blocks; -using UWRandomizerEditor.LEVDotARK.GameObjects; -using static UWRandomizerEditor.Utils; +using UWRandomizerEditor.LEVdotARK.Blocks; +using UWRandomizerEditor.LEVdotARK.GameObjects; -namespace UWRandomizerEditor.LEVDotARK +namespace UWRandomizerEditor.LEVdotARK { - - public class TileInfo: ISaveBinary, IEquatable + public class TileInfo : IBufferObject, IEquatable { - public const int Size = 4; - public enum TileTypes + public const int FixedSize = 4; + + public enum TileTypes: uint { - solid = 0, - open, - diag_se, - diag_sw, - diag_ne, - diag_nw, - slp_n, - slp_s, - slp_e, - slp_w, - } ; + Solid = 0, + Open, + DiagSe, + DiagSw, + DiagNe, + DiagNw, + SlpN, + SlpS, + SlpE, + SlpW, + }; public static readonly IDictionary TileTypeDescriptors = new Dictionary() { - { 0, "#SOLID" }, - { 1, "#OPEN" }, - { 2, "#DIAG_SE" }, - { 3, "#DIAG_SW" }, - { 4, "#DIAG_NE" }, - { 5, "#DIAG_NW" }, - { 6, "#SLP_N" }, - { 7, "#SLP_S" }, - { 8, "#SLP_E" }, - { 9, "#SLP_W" } + {0, "#SOLID"}, + {1, "#OPEN"}, + {2, "#DIAG_SE"}, + {3, "#DIAG_SW"}, + {4, "#DIAG_NE"}, + {5, "#DIAG_NW"}, + {6, "#SLP_N"}, + {7, "#SLP_S"}, + {8, "#SLP_E"}, + {9, "#SLP_W"} }; + public static readonly IDictionary TileCharReplacements = new Dictionary() { - { 0, '#' }, - { 1, '_' }, - { 2, '/' }, - { 3, '\\' }, - { 4, '\\' }, - { 5, '/' }, - { 6, '^' }, - { 7, 'v' }, - { 8, '>' }, - { 9, '<' } + {0, '#'}, + {1, '_'}, + {2, '/'}, + {3, '\\'}, + {4, '\\'}, + {5, '/'}, + {6, '^'}, + {7, 'v'}, + {8, '>'}, + {9, '<'} }; - // Defined in the constructor - private int _entry; - public int Entry - { - get { UpdateEntry(); return _entry; } - set { _entry = value; UpdateBuffer(); } - } - - private byte[] _tileBuffer; - public byte[] TileBuffer + // This is the 4 bytes used to describe a Tile Info. This is useful for bitwise operations that span + // the boundary of 1 byte, which would be a bit cumbersome to do with a byte[] buffer. The byte[] Buffer + // is to be considered "primary", meaning it should always be kept up to date. + private uint BufferAsUInt32 { get { - UpdateBuffer(); // Let's assure the buffer is always updated - return _tileBuffer; + return BitConverter.ToUInt32(Buffer); } - private set + [MemberNotNull("Buffer")] + set { - _tileBuffer = value; + Buffer = BitConverter.GetBytes(value); } } - public int EntryNum { get; set; } - // TODO: Test this + public byte[] Buffer { get; set; } + + public uint EntryNum { get; set; } + public int Offset { - get - { - return EntryNum * Size; - } - } + get { return (int) EntryNum * FixedSize; } + } + public int LevelNum { get; set; } - public int TileType + public uint TileType { - get { return GetBits(Entry, 0b1111, 0); } - set { Entry = SetBits(Entry, value, 0b1111, 0);} + get { return Utils.GetBits(BufferAsUInt32, 0b1111, 0); } + set + { + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b1111, 0); + } } - public int TileHeight + public uint TileHeight { - get { return GetBits(Entry, 0b1111, 4); } - set { Entry = SetBits(Entry, value, 0b1111, 4); UpdateBuffer(); } + get { return Utils.GetBits(BufferAsUInt32, 0b1111, 4); } + set + { + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b1111, 4); + } } - public int Light + public uint Light { - get { return GetBits(Entry, 0b1, 8); } - set { Entry = SetBits(Entry, value, 0b1, 8); UpdateBuffer(); } + get { return Utils.GetBits(BufferAsUInt32, 0b1, 8); } + set + { + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b1, 8); + } } - + // todo: recheck this. - public int Bit9 + public uint Bit9 { - get { return GetBits(Entry, 0b1, 9); } - set { Entry = SetBits(Entry, value, 0b1, 9); UpdateBuffer(); } + get { return Utils.GetBits(BufferAsUInt32, 0b1, 9); } + set + { + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b1, 9); + } } - public int FloorTextureIdx + public uint FloorTextureIdx { - get { return GetBits(Entry, 0b1111, 10); } - set { Entry = SetBits(Entry, value, 0b1111, 10); UpdateBuffer(); } + get { return Utils.GetBits(BufferAsUInt32, 0b1111, 10); } + set + { + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b1111, 10); + } } - public int NoMagic + + public uint NoMagic { - get { return GetBits(Entry, 0b1, 14); } - set { Entry = SetBits(Entry, value, 0b1, 14); UpdateBuffer(); } + get { return Utils.GetBits(BufferAsUInt32, 0b1, 14); } + set + { + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b1, 14); + } } - public int DoorBit + + public uint DoorBit { - get { return GetBits(Entry, 0b1, 15); } - set { Entry = SetBits(Entry, value, 0b1, 15); UpdateBuffer(); } + get { return Utils.GetBits(BufferAsUInt32, 0b1, 15); } + set + { + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b1, 15); + } } - public int WallTextureIdx + + public uint WallTextureIdx { - get { return GetBits(Entry, 0b111111, 16); } - set { Entry = SetBits(Entry, value, 0b111111, 16); UpdateBuffer(); } + get { return Utils.GetBits(BufferAsUInt32, 0b111111, 16); } + set + { + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b111111, 16); + } } - public int FirstObjIdx + + public uint FirstObjIdx { get { @@ -137,54 +160,79 @@ public int FirstObjIdx } set { - Entry = SetBits(Entry, value, 0b1111111111, 22); + BufferAsUInt32 = Utils.SetBits(BufferAsUInt32, value, 0b1111111111, 22); ObjectChain.startingIdx = value; - UpdateBuffer(); + if (GetStartingIndex(Buffer) != value) + throw new Exception("Why isn't the starting index being added correctly?"); } } + private static uint GetStartingIndex(uint bufferAsUInt32) + { + return Utils.GetBits(bufferAsUInt32, 0b1111111111, 22); + } + + private static uint GetStartingIndex(byte[] buffer) + { + uint temp = BitConverter.ToUInt32(buffer); + return GetStartingIndex(temp); + } + public UWLinkedList ObjectChain { get; } - public int[] XYPos + // TODO: Review this + public uint[] XYPos { get { - int row = EntryNum % TileMapMasterObjectListBlock.TileHeight; - int col = EntryNum / TileMapMasterObjectListBlock.TileWidth; - return new int[] {row, col}; + var row = EntryNum % TileMapMasterObjectListBlock.TileHeight; + var col = EntryNum / TileMapMasterObjectListBlock.TileWidth; + return new uint[] {row, col}; } } - [MemberNotNull(nameof(_tileBuffer))] - private void UpdateBuffer() // Modified entry, updates buffer - { - _entry = SetBits(_entry, ObjectChain.startingIdx, 0b1111111111, 22); - _tileBuffer = BitConverter.GetBytes(_entry); - } + public uint XPos => XYPos[0]; + public uint YPos => XYPos[1]; - private void UpdateEntry() // Modified buffer, updates entry + // TODO: I could add more checks here + public bool ReconstructBuffer() { - _entry = BitConverter.ToInt32(_tileBuffer); + if (GetStartingIndex(Buffer) != ObjectChain.startingIdx) + { + Debug.Print("Mismatch between TileInfo firstObjectIndex in buffer and UWLinkedList."); + FirstObjIdx = ObjectChain.startingIdx; + } + return true; } - - public TileInfo(int entrynum, int entry, int offset, int levelNumber) + public TileInfo(uint entrynum, uint bufferAsUInt32, uint offset, int levelNumber) { EntryNum = entrynum; - _entry = entry; + BufferAsUInt32 = bufferAsUInt32; LevelNum = levelNumber; - ObjectChain = new UWLinkedList(); - UpdateBuffer(); + ObjectChain = new UWLinkedList() {startingIdx = GetStartingIndex(bufferAsUInt32), RepresentingContainer = false}; + if (offset != Offset) + { + throw new Exception("Invalid calculation of offset from EntryNum!"); + } } - public TileInfo(int entrynum, byte[] buffer, int offset, int levelNumber) + public TileInfo(uint entrynum, byte[] buffer, uint offset, int levelNumber) { + if (buffer.Length != FixedSize) + { + throw new ArgumentException($"Invalid size of Tile Info Buffer: {buffer.Length}"); + } + EntryNum = entrynum; LevelNum = levelNumber; - _tileBuffer = buffer; - ObjectChain = new UWLinkedList(); - UpdateEntry(); + Buffer = buffer; + ObjectChain = new UWLinkedList() {startingIdx = GetStartingIndex(buffer), RepresentingContainer = false}; + if (offset != Offset) + { + throw new Exception("Invalid calculation of offset from EntryNum!"); + } } // TODO: Check if we need that modification in the height value mentioned in the uw-formats.txt @@ -192,75 +240,62 @@ public void MoveObjectsToSameZLevel() { foreach (GameObject obj in ObjectChain) { - obj.Zpos = (byte) TileHeight; + obj.Zpos = (byte) (TileHeight * 8); + // obj.Zpos = (byte) (TileHeight); } } // TODO: Make the positions randomized among a set of possible values - public void MoveObjectsToCorrectCorner() + public void MoveObjectsToCorrectCorner(Random r) { - Random r = new Random(); // TODO: Make a singleton random instance foreach (var obj in ObjectChain) { switch (TileType) { - case (int) TileTypes.open: - case (int) TileTypes.slp_n: - case (int) TileTypes.slp_e: - case (int) TileTypes.slp_s: - case (int) TileTypes.slp_w: - case (int) TileTypes.solid: + case (int) TileTypes.Open: + case (int) TileTypes.SlpN: + case (int) TileTypes.SlpE: + case (int) TileTypes.SlpS: + case (int) TileTypes.SlpW: + case (int) TileTypes.Solid: + break; + case (int) TileTypes.DiagSe: + { + obj.Xpos = 6; + obj.Ypos = 1; + break; + } + case (int) TileTypes.DiagSw: + { + obj.Xpos = 1; + obj.Ypos = 1; + break; + } + case (int) TileTypes.DiagNe: + { + obj.Xpos = 6; + obj.Ypos = 6; break; - case (int) TileTypes.diag_se: - { - obj.Xpos = 6; - obj.Ypos = 1; - break; - } - case (int) TileTypes.diag_sw: - { - obj.Xpos = 1; - obj.Ypos = 1; - break; - } - case (int) TileTypes.diag_ne: - { - obj.Xpos = 6; - obj.Ypos = 6; - break; - } - case (int) TileTypes.diag_nw: - { - obj.Xpos = 1; - obj.Ypos = 6; - break; - } + } + case (int) TileTypes.DiagNw: + { + obj.Xpos = 1; + obj.Ypos = 6; + break; + } } } } - public string SaveBuffer(string? basePath, string? filename) - { - basePath ??= Settings.DefaultBinaryTestsPath; - filename ??= string.Empty; - if (filename.Length == 0) - { - filename = $@"_TILE_{LevelNum}_{XYPos}_{TileTypeDescriptors[TileType]}"; - } - - return StdSaveBuffer(TileBuffer, basePath, filename); - - } - public bool Equals(TileInfo? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; // I think I shouldn't have to consider the object chain, only the first index and the level num - // I've added the other flags for completeness, because comparing only the "Entry" should be enough to cover them + // I've added the other flags for completeness, because comparing only the "BufferAsUInt32" should be enough to cover them if ( - this.Entry == other.Entry & - this.TileBuffer.SequenceEqual(other.TileBuffer) & + this.BufferAsUInt32 == other.BufferAsUInt32 & + this.Buffer.SequenceEqual(other.Buffer) & this.EntryNum == other.EntryNum & this.LevelNum == other.LevelNum & this.TileType == other.TileType & @@ -270,7 +305,7 @@ public bool Equals(TileInfo? other) this.NoMagic == other.NoMagic & this.DoorBit == other.DoorBit & this.WallTextureIdx == other.WallTextureIdx - ) + ) { return true; } @@ -288,7 +323,7 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return HashCode.Combine(_entry, _tileBuffer, EntryNum, LevelNum, ObjectChain); + return HashCode.Combine(Buffer, EntryNum, LevelNum, ObjectChain); } } } \ No newline at end of file diff --git a/UWRandomizerEditor/LEVDotARK/UWLList.cs b/UWRandomizerEditor/LEVDotARK/UWLList.cs index 7b40ae9..0d99a6d 100644 --- a/UWRandomizerEditor/LEVDotARK/UWLList.cs +++ b/UWRandomizerEditor/LEVDotARK/UWLList.cs @@ -1,14 +1,15 @@ using System.Collections; using System.Data; using System.Diagnostics; -using UWRandomizerEditor.LEVDotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK.GameObjects; -namespace UWRandomizerEditor.LEVDotARK; +namespace UWRandomizerEditor.LEVdotARK; public class UWLinkedList: IList { - private int _startingIdx = 0; - public int startingIdx + private uint _startingIdx = 0; + public bool RepresentingContainer = false; + public uint startingIdx { get { @@ -24,8 +25,11 @@ public int startingIdx { if (objects.Count > 0) { - Debug.WriteLine("Attempting to change the starting index of an initialized UWLinkedList. This will clear the list"); - Clear(); + if (value != _startingIdx) + { + Debug.WriteLine("Attempting to change the starting index of an initialized UWLinkedList. This will clear the list"); + Clear(); + } // Else, don't do anything meaningful. } _startingIdx = value; } @@ -54,6 +58,7 @@ public void Add(GameObject item) objects[^1].next = item.IdxAtObjectArray; } item.next = 0; + item.InContainer = RepresentingContainer; objects.Add(item); } @@ -142,6 +147,7 @@ public void Insert(int index, GameObject item) if (index == 0) { _startingIdx = item.IdxAtObjectArray; + item.InContainer = RepresentingContainer; item.next = objects[0].IdxAtObjectArray; objects.Insert(index, item); } @@ -149,12 +155,14 @@ public void Insert(int index, GameObject item) { objects[^1].next = item.IdxAtObjectArray; item.next = 0; + item.InContainer = RepresentingContainer; objects.Insert(index, item); } else { objects[index - 1].next = item.IdxAtObjectArray; item.next = objects[index].IdxAtObjectArray; + item.InContainer = RepresentingContainer; objects.Insert(index, item); } @@ -265,14 +273,14 @@ public UWLinkedList() /// Creates a UWLinkedList containing the provided list of objects. /// /// - public UWLinkedList(List objectsToBeInTheList, short firstObjectIndex) + public UWLinkedList(List objectsToBeInTheList, ushort firstObjectIndex) { _startingIdx = firstObjectIndex; objects = objectsToBeInTheList; Debug.WriteLineIf(!CheckIntegrity(), "Added list of objects isn't valid!"); } - public UWLinkedList(GameObject[] objectsToBeInTheList, short firstObjectIndex) + public UWLinkedList(GameObject[] objectsToBeInTheList, ushort firstObjectIndex) { _startingIdx = firstObjectIndex; this.objects = objectsToBeInTheList.ToList(); @@ -397,24 +405,6 @@ private void ForceFixIntegrity() } } - public List PopObjectsThatShouldBeMoved() - { - var tempList = new List(); - foreach (var obj in objects) - { - if (obj.ShouldBeMoved) - { - tempList.Add(obj); - } - } - - foreach (var removedObject in tempList) - { - Remove(removedObject); - } - // FixIntegrity(); - return tempList; - } /// /// Fills in the list of objects given a list of all GameObjects present in a level. @@ -430,7 +420,7 @@ public void PopulateObjectList(GameObject[] AllBlockObjects) int safetycounter = 0; int maxcounter = 1024; - int currentIdx = startingIdx; + uint currentIdx = startingIdx; while (currentIdx != 0) { safetycounter++; @@ -440,7 +430,11 @@ public void PopulateObjectList(GameObject[] AllBlockObjects) } GameObject obj = AllBlockObjects[currentIdx]; - // objects.Add(obj); + // TODO: Why are some invalid objects being added? + // if (obj.Invalid) + // { + // throw new Exception("Can't add an invalid object!"); + // } currentIdx = obj.next; Add(obj); } diff --git a/UWRandomizerEditor/LEVDotARK/Viz.cs b/UWRandomizerEditor/LEVDotARK/Viz.cs index 32de63f..3aea007 100644 --- a/UWRandomizerEditor/LEVDotARK/Viz.cs +++ b/UWRandomizerEditor/LEVDotARK/Viz.cs @@ -1,6 +1,6 @@ -using UWRandomizerEditor.LEVDotARK.Blocks; +using UWRandomizerEditor.LEVdotARK.Blocks; -namespace UWRandomizerEditor.LEVDotARK +namespace UWRandomizerEditor.LEVdotARK { // Help visualization /// diff --git a/UWRandomizerEditor/Settings.cs b/UWRandomizerEditor/Settings.cs deleted file mode 100644 index 60a5547..0000000 --- a/UWRandomizerEditor/Settings.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.IO; -using System.Text.Json; - -namespace UWRandomizerEditor; - - -public static class Settings -{ - public static readonly string DefaultArkPath = @"C:\Users\Karl\Desktop\UnderworldStudy\UW\DATA\LEV.ARK"; - public static readonly string DefaultBinaryTestsPath = @"C:\Users\Karl\Desktop\UnderworldStudy\Buffers"; - - static Settings() - { - string homepath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - string jsonpath = Path.Join(homepath, "UWRandomizerSettings.json"); - try - { - string json = File.ReadAllText(jsonpath); - Stt? settings = JsonSerializer.Deserialize(json); - if (settings is null) - { - // DefaultArkPath = "D:\\Dropbox\\UnderworldStudy\\studies\\LEV.ARK"; - // DefaultBinaryTestsPath = "D:\\Dropbox\\UnderworldStudy\\studies\\tests"; - } - else - { - DefaultArkPath = settings.DefaultArkPath; - DefaultBinaryTestsPath = settings.DefaultBinaryTestsPath; - } - } - catch (FileNotFoundException e) - { - #if !DEBUG - Console.WriteLine("UWRandomizerSettings.json file not found!"); - throw; - #else - Console.WriteLine("UWRandomizerSettings.json file not found! If you're testing, ignore."); - #endif - } - - - } - -} - -class Stt -{ - public string DefaultArkPath { get; set; } - public string DefaultBinaryTestsPath { get; set; } -} \ No newline at end of file diff --git a/UWRandomizerEditor/UWRandomizerEditor.csproj b/UWRandomizerEditor/UWRandomizerEditor.csproj index db20c38..72ed777 100644 --- a/UWRandomizerEditor/UWRandomizerEditor.csproj +++ b/UWRandomizerEditor/UWRandomizerEditor.csproj @@ -14,5 +14,11 @@ UWRandomizerEditor + + + Always + + + diff --git a/UWRandomizerEditor/UWRandomizerEditor.dll.config b/UWRandomizerEditor/UWRandomizerEditor.dll.config new file mode 100644 index 0000000..45a82c9 --- /dev/null +++ b/UWRandomizerEditor/UWRandomizerEditor.dll.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UWRandomizerEditor/UWRandomizerSettings.json b/UWRandomizerEditor/UWRandomizerSettings.json deleted file mode 100644 index cfee218..0000000 --- a/UWRandomizerEditor/UWRandomizerSettings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "DefaultBinaryTestsPath": "D:\\Dropbox\\UnderworldStudy\\studies\\tests", - "DefaultArkPath": "D:\\Dropbox\\UnderworldStudy\\studies\\LEV.ARK" -} \ No newline at end of file diff --git a/UWRandomizerEditor/Utils.cs b/UWRandomizerEditor/Utils.cs index fac8ebe..1e8e57b 100644 --- a/UWRandomizerEditor/Utils.cs +++ b/UWRandomizerEditor/Utils.cs @@ -1,4 +1,6 @@ -namespace UWRandomizerEditor +using UWRandomizerEditor.Interfaces; + +namespace UWRandomizerEditor { /// /// Class containing useful small functions to set bits in values, save buffers, etc. @@ -75,9 +77,14 @@ public static class Utils public static int SetBits(int currvalue, int newvalue, int mask, int shift) { return ( - (currvalue & (~(mask << shift))) // Sets the bits to be changed to 0, preserving others - | ((newvalue & mask) << shift) // Moves only the relevant bits of the new value to the relevant position, replaces the target bits - ); + (currvalue & (~(mask << shift))) // Sets the bits to be changed to 0, preserving others + | ((newvalue & mask) << + shift) // Moves only the relevant bits of the new value to the relevant position, replaces the target bits + ); + } + public static uint SetBits(uint currvalue, uint newvalue, int mask, int shift) + { + return (uint) SetBits((int) currvalue, (int) newvalue, mask, shift); } /// @@ -119,21 +126,40 @@ public static int GetBits(int value, int mask, int shift) { return (value >> shift) & mask; } + + public static uint GetBits(uint value, int mask, int shift) + { + return (uint) GetBits((int) value, mask, shift); + } /// /// Saves a specific buffer to a path specified as basepath \ filename. /// - /// Byte array (buffer) - /// Base path to the file (e.g. folder structure) - /// Extra info to add (e.g. name of file) + /// Object that has a "Buffer" property + /// Base path to the file (e.g. folder structure). If not provided, uses the current working directory + /// Extra info to add (e.g. name of file). If not provided, gets a random Guid /// Path to the saved object. If couldn't be saved, returns null - public static string StdSaveBuffer(byte[] buffer, string basepath, string filename) + public static string StdSaveBuffer(IBufferObject obj, string? basepath, string? filename) { + return StdSaveBuffer(obj.Buffer, basepath, filename); + } + + /// + /// Saves a specific buffer to a path specified as basepath \ filename. + /// + /// byte array + /// Base path to the file (e.g. folder structure). If not provided, uses the current working directory + /// Extra info to add (e.g. name of file). If not provided, gets a random Guid + /// Path to the saved object. If couldn't be saved, returns null + public static string StdSaveBuffer(byte[] buffer, string? basepath, string? filename) + { + basepath ??= "."; + filename ??= Guid.NewGuid().ToString(); + string fullpath = Path.Join(basepath, filename); - + File.WriteAllBytes(fullpath, buffer); return fullpath; } - } } \ No newline at end of file diff --git a/UWRandomizerTools/ItemTools.cs b/UWRandomizerTools/ItemTools.cs new file mode 100644 index 0000000..ef1f4aa --- /dev/null +++ b/UWRandomizerTools/ItemTools.cs @@ -0,0 +1,79 @@ +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; + +namespace UWRandomizerTools; + +public class ItemTools +{ + public static List ExtractMovableItems(TileInfo tile, ItemRandomizationSettings settings) + { + return ExtractMovableItems(tile.ObjectChain, settings); + } + + public static List ExtractMovableItems(UWLinkedList list, ItemRandomizationSettings settings) + { + var tempList = new List(); + foreach (var obj in list) + { + if (ShouldBeMoved(obj, settings)) + { + tempList.Add(obj); + } + } + + foreach (var removedObject in tempList) + { + list.Remove(removedObject); + } + + return tempList; + } + + private static bool ShouldBeMoved(GameObject obj, ItemRandomizationSettings settings) + { + // If the object is invalid (idx 0, 1 or in "Free" slot, it should be ignored) + if (obj.Invalid) return false; + + // We have to ignore objects that are in containers + if (obj.InContainer) return false; + + // Then filter by the class + bool res; + try + { + res = settings.MovableRulesByClass[obj.GetType()]; + } + catch (KeyNotFoundException) + { + res = ItemRandomizationSettings.DefaultMovableRuleByClass; + } + + return res; + } +} + +public class ItemRandomizationSettings +{ + public readonly Dictionary MovableRulesByClass = new Dictionary() + { + { typeof(Furniture), false }, + { typeof(Door), false }, + { typeof(Trigger), false }, + { typeof(TexturedGameObject), false }, + { typeof(MobileObject), false }, + { typeof(Lock), false }, + { typeof(Trap), false }, + { typeof(GameObject), false }, + { typeof(StaticObject), true }, + { typeof(QuantityGameObject), true }, + { typeof(EnchantedArmor), true }, + { typeof(EnchantedObject), true }, + { typeof(EnchantedWand), true }, + { typeof(EnchantedWeapon), true }, + { typeof(Key), true }, + { typeof(Container), true}, + }; + + public const bool DefaultMovableRuleByClass = true; +} \ No newline at end of file diff --git a/UWRandomizerTools/ManageDoors.cs b/UWRandomizerTools/ManageDoors.cs new file mode 100644 index 0000000..3669b09 --- /dev/null +++ b/UWRandomizerTools/ManageDoors.cs @@ -0,0 +1,31 @@ +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; + +namespace UWRandomizerTools; + +public static partial class RandoTools +{ + /// + /// Iterates through all GameObjects in a lev.ark file and removes links to lock objects. Reconstructs the buffer. + /// + /// + /// Count of alterations performed + public static int RemoveAllDoorReferencesToLocks(ArkLoader arkFile) + { + int count = 0; + foreach (var block in arkFile.TileMapObjectsBlocks) + { + foreach (var staticObject in block.StaticObjects) + { + if (staticObject is Door door) + { + door.RemoveLock(); + count++; + } + } + } + + arkFile.ReconstructBuffer(); + return count; + } +} \ No newline at end of file diff --git a/UWRandomizerTools/ShuffleItems.cs b/UWRandomizerTools/ShuffleItems.cs new file mode 100644 index 0000000..4a3030b --- /dev/null +++ b/UWRandomizerTools/ShuffleItems.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.Blocks; +using UWRandomizerEditor.LEVdotARK.GameObjects; + +namespace UWRandomizerTools; + +public class ShuffleItems +{ + public static void ShuffleAllLevels(ArkLoader arkFile, Random RandomInstance, ItemRandomizationSettings settings) + { + foreach (var block in arkFile.TileMapObjectsBlocks) + { + ShuffleItemsInLevel(block, RandomInstance, settings); + block.ReconstructBuffer(); + } + + arkFile.ReconstructBuffer(); + } + + public static void ShuffleItemsInLevel(TileMapMasterObjectListBlock block, Random RandomInstance, ItemRandomizationSettings settings) + { + Stack objectsInLevel = new Stack(); + foreach (var tile in block.TileInfos) + { + foreach (var obj in ItemTools.ExtractMovableItems(tile, settings)) + { + objectsInLevel.Push(obj); + } + } + + while (objectsInLevel.Count > 0) + { + int chosenTileIdx = RandomInstance.Next(0, block.TileInfos.Length); + TileInfo chosenTile = block.TileInfos[chosenTileIdx]; + if (!IsTileValid(chosenTile)) + continue; + chosenTile.ObjectChain.Add(objectsInLevel.Pop()); + chosenTile.MoveObjectsToSameZLevel(); + } + + // foreach (var tile in block.TileInfos) + // { + // tile.MoveObjectsToCorrectCorner(); // This shouldn't have to do anything for now + // tile.MoveObjectsToSameZLevel(); + // } + + block.ReconstructBuffer(); + } + + private static readonly IDictionary LevelTextureIdxOfWater = new Dictionary() + { + {0, 8}, // lvl1 + {1, 8}, // lvl2 + {2, 8}, // lvl3 + {3, 7}, // lvl4 + {4, 8}, // lvl5 + {5, -1}, // lvl6 + {6, 8}, // lvl7 + {7, -1}, // lvl8 + {8, 7}, // lvl9, void + }; + + // TODO: Lvl8 appears to have 2 textures that are lava, right below the fire elementals beside the doors + private static readonly IDictionary LevelTextureIdxOfLava = new Dictionary() + { + {0, -1}, // lvl1 + {1, -1}, // lvl2 + {2, -1}, // lvl3 + {3, -1}, // lvl4 + {4, 6}, // lvl5 + {5, 8}, // lvl6 + {6, -1}, // lvl7 + {7, 7}, // lvl8 + {8, 4}, // lvl9, void + }; + + public static bool IsTileValid(TileInfo tile) + { + // Initially, I'm only putting items in open spaces! + // I'll deal with moving them to the appropriate corners later + if ((TileInfo.TileTypes) tile.TileType != TileInfo.TileTypes.Open) + { + return false; + } + + // Preventing items from being placed in the central shaft area + if (tile.LevelNum < 6) // Lvls 1-4 have the shaft aligned in the middle. Lvl 5,6 don't have a shaft + { + if (InRectangle(tile, 30, 33, 33, 30)) + return false; + } + else if (tile.LevelNum == 6) // Lvl 7. Has a weird shape. Divided into rectangles... + { + if ( + InRectangle(tile, 28, 32, 35, 32) | + InRectangle(tile, 28, 33, 35, 33) | + InRectangle(tile, 28, 31, 35, 31) | + InRectangle(tile, 29, 30, 34, 30) | + InRectangle(tile, 30, 29, 33, 29) | + InRectangle(tile, 29, 34, 34, 34) | + InRectangle(tile, 30, 35, 33, 35) + ) + return false; + } + else if (tile.LevelNum == 7) // Lvl 8. Rectangle + Strip + { + if ( + InRectangle(tile, 29, 35, 35, 30) | + InRectangle(tile, 31, 29, 33, 29) + ) + return false; + } + + // Can't place items on water, or else they might vanish. TODO: Need to test though! + if ((tile.FloorTextureIdx == LevelTextureIdxOfWater[tile.LevelNum]) | + tile.FloorTextureIdx == LevelTextureIdxOfLava[tile.LevelNum]) + { + return false; + } + + return true; + } + + private static bool InRectangle(TileInfo tile, + uint topLeftX, uint topLeftY, + uint topRightX, uint bottomLeftY) + { + if ( + (tile.XPos >= topLeftX & tile.XPos <= topRightX) & + (tile.YPos >= bottomLeftY & tile.YPos <= topLeftY) + ) + { + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/UWRandomizer/SwapItems.cs b/UWRandomizerTools/SwapItems.cs similarity index 63% rename from UWRandomizer/SwapItems.cs rename to UWRandomizerTools/SwapItems.cs index af743e1..650f894 100644 --- a/UWRandomizer/SwapItems.cs +++ b/UWRandomizerTools/SwapItems.cs @@ -1,7 +1,7 @@ using System.Diagnostics; -using UWRandomizerEditor.LEVDotARK; +using UWRandomizerEditor.LEVdotARK; -namespace UWRandomizer; +namespace UWRandomizerTools; public static partial class RandoTools { @@ -11,17 +11,17 @@ public static partial class RandoTools /// /// First Tile to replace /// Second tile to replace - static void SwapAllObjectsBetweenTwoTiles(TileInfo Tile1, TileInfo Tile2) + public static void SwapAllObjectsBetweenTwoTiles(TileInfo Tile1, TileInfo Tile2, ItemRandomizationSettings settings, Random r) { - var obj1 = Tile1.ObjectChain.PopObjectsThatShouldBeMoved(); - var obj2 = Tile2.ObjectChain.PopObjectsThatShouldBeMoved(); + var obj1 = ItemTools.ExtractMovableItems(Tile1, settings); + var obj2 = ItemTools.ExtractMovableItems(Tile2, settings); Tile1.ObjectChain.AppendItems(obj2); Tile2.ObjectChain.AppendItems(obj1); - Tile1.MoveObjectsToCorrectCorner(); + Tile1.MoveObjectsToCorrectCorner(r); Tile1.MoveObjectsToSameZLevel(); - Tile2.MoveObjectsToCorrectCorner(); + Tile2.MoveObjectsToCorrectCorner(r); Tile2.MoveObjectsToSameZLevel(); Debug.Assert(Tile1.ObjectChain.CheckIntegrity()); diff --git a/UWRandomizerTools/UWRandomizerTools.csproj b/UWRandomizerTools/UWRandomizerTools.csproj new file mode 100644 index 0000000..49b187f --- /dev/null +++ b/UWRandomizerTools/UWRandomizerTools.csproj @@ -0,0 +1,13 @@ + + + + net6.0-windows + enable + enable + + + + + + + diff --git a/RandomizerUnitTests/TestCompareHankWithThis.cs b/UWRandomizerUnitTests/Editor/CompareGameObjectProperties.cs similarity index 73% rename from RandomizerUnitTests/TestCompareHankWithThis.cs rename to UWRandomizerUnitTests/Editor/CompareGameObjectProperties.cs index 0896002..868d3c6 100644 --- a/RandomizerUnitTests/TestCompareHankWithThis.cs +++ b/UWRandomizerUnitTests/Editor/CompareGameObjectProperties.cs @@ -5,16 +5,23 @@ using System.Reflection; using System.Text.Json; using NUnit.Framework; +using UWRandomizer; using UWRandomizerEditor; -using UWRandomizerEditor.LEVDotARK; -using UWRandomizerEditor.LEVDotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.Blocks; +using UWRandomizerEditor.LEVdotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; +using System.Configuration; namespace RandomizerUnitTests; [TestFixture] -public class TestGameObjectProperties +[Category("PropertyComparisons")] +[Category("FishyTests")] +public class CompareWithHanksEditor { private const int numOfLevels = 9; + // Removing this to temporarily avoid problems with building in github actions. Keeping the same for now. // TODO: Can I put the .json test files without problems? // private Stream[] streamsPristine = new Stream[numOfLevels]; @@ -47,47 +54,33 @@ public enum PossibleLevArkToTest {"ypos", "Ypos"}, {"quality", "Quality"}, {"next", "next"}, - {"owner", "Owner_or_special"}, + {"owner", "OwnerOrSpecial"}, {"link", "QuantityOrSpecialLinkOrSpecialProperty"} }; [OneTimeSetUp] - [Category("RequiresArk")] public void Setup() { for (int blocknum = 0; blocknum < numOfLevels; blocknum++) { - // Jesus this looks ugly. But it's only Loading the jsons into the lists, and the appropriate ArkLoader isntances - // streamsPristine[blocknum] = - // Assembly.GetExecutingAssembly() - // .GetManifestResourceStream( - // $"RandomizerUnitTests.testdata.PristineUW1.Block{blocknum}_objects.json") ?? - // throw new InvalidOperationException(); + // Jesus this looks ugly. But it's only Loading the jsons into the lists, and the appropriate ArkLoader instances streamsPristine[blocknum] = - File.ReadAllText( - @$"C:\Users\Karl\Desktop\UnderworldStudy\UnderworldRandomizer\RandomizerUnitTests\testdata\PristineUW1\Block{blocknum}_objects.json"); + File.ReadAllText(Path.Join(Paths.RUT_TestDataPath, @$"PristineUW1\Block{blocknum}_objects.json")); jsonsPristine.Add(JsonSerializer.Deserialize>>(streamsPristine[blocknum], new JsonSerializerOptions() {AllowTrailingCommas = true}) ?? throw new InvalidOperationException()); - arkPristine = new ArkLoader(Settings.DefaultArkPath); + arkPristine = new ArkLoader(Paths.UW_ArkOriginalPath); - // streamsCleaned[blocknum] = - // Assembly.GetExecutingAssembly() - // .GetManifestResourceStream( - // $"RandomizerUnitTests.testdata.CleanedUW1.Block{blocknum}_objects.json") ?? - // throw new InvalidOperationException(); streamsCleaned[blocknum] = File.ReadAllText( - @$"C:\Users\Karl\Desktop\UnderworldStudy\UnderworldRandomizer\RandomizerUnitTests\testdata\CleanedUW1\Block{blocknum}_objects.json"); + Path.Join(Paths.RUT_TestDataPath, $@"CleanedUW1\Block{blocknum}_objects.json")); jsonsCleaned.Add(JsonSerializer.Deserialize>>( streamsCleaned[blocknum], new JsonSerializerOptions() {AllowTrailingCommas = true}) ?? throw new InvalidOperationException()); - // TODO: De-hardcode this - arkCleaned = new ArkLoader(@"C:\Users\Karl\Desktop\UnderworldStudy\UW - Copy\DATA\LEV.ARK"); + arkCleaned = new ArkLoader(Paths.UW_ArkOriginalPath); } } [Test] - [Category("RequiresArk")] public void TestStaticObjectProperties( [Range(0, numOfLevels - 1, 1)] int blocknum, // Reminder: Range is [from, to], not [from, to[ [Values(PossibleLevArkToTest.pristine, PossibleLevArkToTest.cleaned)] @@ -116,6 +109,7 @@ private void IterateAndCompareAttributesStaticObject(List>> selectArkAndJson(int blo return new Tuple>>(ark, json); } -} - -public class ManualTests -{ - [Test] - public void TestTileInfoComparingToUltimateEditor_manual() - { - var tile = new TileInfo(1609, new byte[] {0x11, 0x20, 0x1E, 0x00}, 6436, 0); - // Some water tile near that island with a lurker nearby - var reference = new TileInfo(1609, 0, 6436, 0); - reference.TileType = (int) TileInfo.TileTypes.open; - reference.TileHeight = 1; - reference.DoorBit = 0; - reference.NoMagic = 0; - reference.FloorTextureIdx = 8; - reference.WallTextureIdx = 30; - reference.FirstObjIdx = 0; - - - Assert.True(tile.Equals(reference)); - } } \ No newline at end of file diff --git a/UWRandomizerUnitTests/Editor/TestArkLoader.cs b/UWRandomizerUnitTests/Editor/TestArkLoader.cs new file mode 100644 index 0000000..9f8a46b --- /dev/null +++ b/UWRandomizerUnitTests/Editor/TestArkLoader.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; + +namespace RandomizerUnitTests; + +[TestFixture] +[Category("RequiresSettings")] +class ArkLoaderTest +{ + /// + /// Tests if the original lev.ark is correctly reconstructed and serialized. + /// + [Test] + public void CompareLoadSerializeOriginal() + { + var myArkLoader = new ArkLoader(Paths.UW_ArkOriginalPath); + Assert.True(Utils.CheckEqualityOfSha256Hash(myArkLoader.Buffer, Utils.OriginalLevArkSha256Hash)); + myArkLoader.ReconstructBuffer(); + Assert.True(Utils.CheckEqualityOfSha256Hash(myArkLoader.Buffer, Utils.OriginalLevArkSha256Hash)); + string savedPath = + UWRandomizerEditor.Utils.StdSaveBuffer(myArkLoader, Paths.BufferTestsPath, "reconstructedOriginalArk.bin"); + + // Reloading and checking if everything was saved correctly. + var myArkLoader2 = new ArkLoader(savedPath); + myArkLoader2.ReconstructBuffer(); + var (differenceCount, differencePositions) = Utils.CompareTwoBuffers(myArkLoader.Buffer, myArkLoader2.Buffer); + Assert.True(differenceCount == 0, "Differences at positions:" + String.Join(",", differencePositions)); + Assert.True(Utils.CheckEqualityOfSha256Hash(myArkLoader2.Buffer, Utils.OriginalLevArkSha256Hash)); + + File.Delete(savedPath); + } + + /// + /// Tests if the "cleaned" lev.ark (opened and closed with UWEditor) is correctly reconstructed and serialized. + /// + [Test] + public void TestReconstructBufferCleaned() + { + var AL = new ArkLoader(Paths.UW_ArkCleanedPath); + var AL_Unreconstructed = new ArkLoader(Paths.UW_ArkCleanedPath); + AL.ReconstructBuffer(); + var (diffs, positions) = Utils.CompareTwoBuffers(AL.Buffer, AL_Unreconstructed.Buffer); + Assert.True(diffs == 0); + string savedpath = + UWRandomizerEditor.Utils.StdSaveBuffer(AL, Paths.BufferTestsPath, "reconstructedCleanedArk.bin"); + + var AL2 = new ArkLoader(savedpath); + AL2.ReconstructBuffer(); + + var (differenceCount, differencePositions) = Utils.CompareTwoBuffers(AL.Buffer, AL2.Buffer); + Assert.True(differenceCount == 0, "Differences at positions:" + String.Join(",", differencePositions)); + } + + /// + /// This will test some items to see if they're in the appropriate positions, heights, correct classes, etc + /// + [Test] + public void TestSpecificObjectProperties() + { + var AL = new ArkLoader(Paths.UW_ArkOriginalPath); + // Testing Lvl1 starting bag + var lvl1 = AL.TileMapObjectsBlocks[0]; + // X=33, Y=3 + // var tile = lvl1.TileInfos[33 * 64 + 3]; + // Console.WriteLine($"{tile.XYPos[0]}, {tile.XYPos[1]}"); + // Assert.True(tile.FirstObjIdx == 942); + + Assert.True(lvl1.AllGameObjects[942] is Container); + Assert.False(lvl1.AllGameObjects[942].InContainer); + Assert.True(lvl1.AllGameObjects[940].InContainer); + Assert.True(lvl1.AllGameObjects[936].InContainer); + Assert.True(lvl1.AllGameObjects[941].InContainer); + Assert.True(lvl1.AllGameObjects[935].InContainer); + Assert.True(lvl1.AllGameObjects[934].InContainer); + Assert.True(lvl1.AllGameObjects[959].InContainer); + + Container cont = (Container) lvl1.AllGameObjects[942]; + Assert.True(cont.Contents.Count == 6); + Assert.True(cont.Contents.RepresentingContainer); + Assert.True(cont.Contents[^1].next == 0); + } +} \ No newline at end of file diff --git a/RandomizerUnitTests/TestFinalEntryItemCombinations.cs b/UWRandomizerUnitTests/Editor/TestFinalEntryItemCombinations.cs similarity index 100% rename from RandomizerUnitTests/TestFinalEntryItemCombinations.cs rename to UWRandomizerUnitTests/Editor/TestFinalEntryItemCombinations.cs diff --git a/UWRandomizerUnitTests/Editor/TestGameObjectEquality.cs b/UWRandomizerUnitTests/Editor/TestGameObjectEquality.cs new file mode 100644 index 0000000..cfb878a --- /dev/null +++ b/UWRandomizerUnitTests/Editor/TestGameObjectEquality.cs @@ -0,0 +1,100 @@ +using System; +using NUnit.Framework; +using UWRandomizerEditor; +using UWRandomizerEditor.LEVdotARK.GameObjects; + +namespace RandomizerUnitTests; + +[TestFixture] +class TestGameObjectEquality +{ + [Test] + public void TwoEqGameObjects() + { + var obj1 = new StaticObject( + objIdFlags: 0xFFF, + position: 0xFFF, + qualityChain: 0xFFF, + linkSpecial: 0xFFF + ); + + var obj1_copy = new StaticObject( + objIdFlags: 0xFFF, + position: 0xFFF, + qualityChain: 0xFFF, + linkSpecial: 0xFFF + ); + + Assert.True(obj1.Equals(obj1_copy)); + } + + [Test] + public void TwoDiffGameObjects() + { + var obj1 = new StaticObject( + objIdFlags: 0xFFF, + position: 0xFFF, + qualityChain: 0xFFF, + linkSpecial: 0xFFF + ); + + var obj2 = new StaticObject( + objIdFlags: 0xCCC, + position: 0xCCC, + qualityChain: 0xCCC, + linkSpecial: 0xCCC + ); + + Assert.False(obj1.Equals(obj2)); + } + + [Test] + public void GOAndQtty() + { + var obj1 = new StaticObject( + objIdFlags: 0xFFF, + position: 0xFFF, + qualityChain: 0xFFF, + linkSpecial: 0xFFF + ); + + var obj2 = new QuantityGameObject( + objIdFlags: 0xFFF, + position: 0xFFF, + qualityChain: 0xFFF, + linkSpecial: 0xFFF + ); + + Assert.False(obj1.Equals(obj2)); + } + + [Test] + public void StaticAndMobileObjects() + { + var staticObj = new StaticObject( + objIdFlags: 0xFFF, + position: 0xFFF, + qualityChain: 0xFFF, + linkSpecial: 0xFFF + ); + + var mobileObj = new MobileObject(staticObj.Buffer, 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 10); + + Assert.False(staticObj.Equals(mobileObj)); + } +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Editor/TestGameObjectGetterSetter.cs b/UWRandomizerUnitTests/Editor/TestGameObjectGetterSetter.cs new file mode 100644 index 0000000..db0d50b --- /dev/null +++ b/UWRandomizerUnitTests/Editor/TestGameObjectGetterSetter.cs @@ -0,0 +1,190 @@ +using System; +using System.Configuration; +using System.Linq; +using NUnit.Framework; +using UWRandomizerEditor.Interfaces; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects; + +namespace RandomizerUnitTests; + +[TestFixture] +[Category("RequiresSettings")] +public class TestObjectGetterSetter +{ + private ArkLoader ark; + [OneTimeSetUp] + public void SetUp() + { + ark = new ArkLoader(Paths.UW_ArkOriginalPath); + } + + private byte[] CreateBufferCopy(IBufferObject obj) + { + byte[] bufferCopy = new byte[obj.Buffer.Length]; + obj.Buffer.CopyTo(bufferCopy, 0); + return bufferCopy; + } + + private void IterAllObjectsAndExecuteAction(Action action) + { + foreach (var tilemap in ark.TileMapObjectsBlocks) + { + foreach (var obj in tilemap.AllGameObjects) + { + var bufferCopy = CreateBufferCopy(obj); + action(obj); + Assert.True(obj.Buffer.SequenceEqual(bufferCopy), + $"Buffers differ:\n" + + $"Original {string.Join(',', obj.Buffer)}\n" + + $"Copy {string.Join(',', bufferCopy)}"); + } + } + } + + [Test] + public void ItemID() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.ItemID; + obj.ItemID = cpy; + }); + } + + [Test] + public void Flags() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.Flags; + obj.Flags = cpy; + }); + } + + [Test] + public void EnchantFlag() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.EnchantFlag; + obj.EnchantFlag = cpy; + }); + } + + [Test] + public void DoorDir() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.DoorDir; + obj.DoorDir = cpy; + }); + } + + [Test] + public void Invis() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.Invis; + obj.Invis = cpy; + }); + } + + [Test] + public void IsQuant() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.IsQuant; + obj.IsQuant = cpy; + }); + } + + [Test] + public void Zpos() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.Zpos; + obj.Zpos = cpy; + }); + } + [Test] + public void Heading() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.Heading; + obj.Heading = cpy; + }); + } + [Test] + public void Ypos() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.Ypos; + obj.Ypos = cpy; + }); + } + [Test] + public void Xpos() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.Xpos; + obj.Xpos = cpy; + }); + } + + [Test] + public void Quality() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.Quality; + obj.Quality = cpy; + }); + } + + [Test] + public void next() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.next; + obj.next = cpy; + }); + } + [Test] + public void OwnerOrSpecial() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.OwnerOrSpecial; + obj.OwnerOrSpecial = cpy; + }); + } + [Test] + public void QuantityOrSpecialLinkOrSpecialProperty() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.QuantityOrSpecialLinkOrSpecialProperty; + obj.QuantityOrSpecialLinkOrSpecialProperty = cpy; + }); + } + [Test] + public void ItemOwnerStrIdx() + { + IterAllObjectsAndExecuteAction((GameObject obj) => + { + var cpy = obj.ItemOwnerStrIdx; + obj.ItemOwnerStrIdx = cpy; + }); + } + + +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Editor/TestHeader.cs b/UWRandomizerUnitTests/Editor/TestHeader.cs new file mode 100644 index 0000000..12d66bc --- /dev/null +++ b/UWRandomizerUnitTests/Editor/TestHeader.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; + +namespace RandomizerUnitTests; + +[Category("RequiresSettings")] +// ReSharper disable once InconsistentNaming +public class HeaderTestUW1 +{ + private ArkLoader _myArkLoader = null!; + private Header _header = null!; + private const int ValidEntriesWithInfo = 9 * 3; + private const int ValidEntries = 45; + private const int EmptyEntries = 90; + private const int TotalEntries = ValidEntries + EmptyEntries; + + [SetUp] + public void SetUp() + { + _myArkLoader = new ArkLoader(Paths.UW_ArkOriginalPath); + Assert.True(Utils.CheckEqualityOfSha256Hash(_myArkLoader.Buffer, Utils.OriginalLevArkSha256Hash)); + _header = _myArkLoader.header; + } + + // 542 + [Test] + public void TestHeaderSize() + { + Assert.True(_header.Size == _header.Buffer.Length); + } + + [Test] + public void TestGetOffsetForBlock() + { + for (int i = 0; i < TotalEntries; i++) + { + int offset = _header.GetOffsetForBlock(i); + if (i < ValidEntriesWithInfo) + { + Assert.True(offset > 0); + } + else + { + Assert.True(offset == 0); + } + } + } + + [Test] + public void TestReadOffsets() + { + int[] validEntryOffsets = new[] + { + // Level tilemap... + 542, 32294, 64046, 95798, 127550, 159302, 191054, 222806, 254558, + // Animation overlay + 286310, 286694, 287078, 287462, 287846, 288230, 288614, 288998, 289382, + // Texture Mapping + 289766, 289888, 290010, 290132, 290254, 290376, 290498, 290620, 290742, + // Automap info + 0, 0, 0, 0, 0, 0, 0, 0, 0, + // map notes + 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + for (int i = 0; i < ValidEntries; i++) + { + Assert.True(_header.BlockOffsets[i] == validEntryOffsets[i]); + } + + for (int i = ValidEntries; i < TotalEntries; i++) + { + Assert.True(_header.BlockOffsets[i] == 0); + } + } +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Editor/TestTileInfo.cs b/UWRandomizerUnitTests/Editor/TestTileInfo.cs new file mode 100644 index 0000000..86f5e57 --- /dev/null +++ b/UWRandomizerUnitTests/Editor/TestTileInfo.cs @@ -0,0 +1,162 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; + +namespace RandomizerUnitTests; + +[TestFixture] +public class TestTileInfo +{ + // Let's create one random entry from an int. + // And another from a buffer + private TileInfo tinfo1; + private TileInfo tinfo2; + private TileInfo tinfo3; + + [SetUp] + public void Setup() + { + tinfo1 = new TileInfo(0, 240, 0, 0); + tinfo2 = new TileInfo(0, BitConverter.GetBytes(240), 0, 0); + tinfo3 = new TileInfo(0, BitConverter.GetBytes(241), 0, 0); + } + + [Test] + public void ComparingConstructors() + { + Assert.True(tinfo1.Equals(tinfo2)); + Assert.False(tinfo1.Equals(tinfo3)); + Assert.False(tinfo2.Equals(tinfo3)); + } + + [Category("RequiresSettings")] + [Test] + public void SavingBufferAndReloading() + { + // Compare the buffers as-is + Assert.True(tinfo1.Buffer.SequenceEqual(tinfo2.Buffer)); + string tinfo1Path = + UWRandomizerEditor.Utils.StdSaveBuffer(tinfo1, Paths.BufferTestsPath, filename: "buffer_tinfo1"); + string tinfo2Path = + UWRandomizerEditor.Utils.StdSaveBuffer(tinfo2, Paths.BufferTestsPath, filename: "buffer_tinfo2"); + string tinfo3Path = + UWRandomizerEditor.Utils.StdSaveBuffer(tinfo3, Paths.BufferTestsPath, filename: "buffer_tinfo3"); + + // Compare their hashes + SHA256 mySHA256 = SHA256.Create(); + var tinfo1Hash = mySHA256.ComputeHash(tinfo1.Buffer); + var tinfo2Hash = mySHA256.ComputeHash(tinfo2.Buffer); + var tinfo3Hash = mySHA256.ComputeHash(tinfo3.Buffer); + Assert.True(tinfo1Hash.SequenceEqual(tinfo2Hash)); + Assert.False(tinfo1Hash.SequenceEqual(tinfo3Hash)); + Assert.False(tinfo2Hash.SequenceEqual(tinfo3Hash)); + + // Reload the buffers + var tinfo1RelBuffer = LoadTileData(tinfo1Path); + var tinfo2RelBuffer = LoadTileData(tinfo2Path); + var tinfo3RelBuffer = LoadTileData(tinfo3Path); + + // Compare the hashes again + var rectinfo1Hash = mySHA256.ComputeHash(tinfo1RelBuffer); + var rectinfo2Hash = mySHA256.ComputeHash(tinfo2RelBuffer); + var rectinfo3Hash = mySHA256.ComputeHash(tinfo3RelBuffer); + Assert.True(rectinfo1Hash.SequenceEqual(rectinfo2Hash)); + Assert.False(rectinfo2Hash.SequenceEqual(rectinfo3Hash)); + Assert.False(rectinfo2Hash.SequenceEqual(rectinfo3Hash)); + + // Compare the buffers + Assert.True(tinfo1RelBuffer.SequenceEqual(tinfo2RelBuffer)); + Assert.False(tinfo1RelBuffer.SequenceEqual(tinfo3RelBuffer)); + Assert.False(tinfo2RelBuffer.SequenceEqual(tinfo3RelBuffer)); + + // Rebuild the objects + var rebuiltTinfo1 = new TileInfo(0, tinfo1RelBuffer, 0, 0); + var rebuiltTinfo2 = new TileInfo(0, tinfo2RelBuffer, 0, 0); + var rebuiltTinfo3 = new TileInfo(0, tinfo3RelBuffer, 0, 0); + + // Compare their buffers + Assert.True(rebuiltTinfo1.Buffer.SequenceEqual(rebuiltTinfo2.Buffer)); + Assert.True(rebuiltTinfo1.Buffer.SequenceEqual(tinfo1.Buffer)); + Assert.True(rebuiltTinfo2.Buffer.SequenceEqual(tinfo2.Buffer)); + Assert.True(rebuiltTinfo1.Buffer.SequenceEqual(tinfo2.Buffer)); + Assert.True(rebuiltTinfo2.Buffer.SequenceEqual(tinfo1.Buffer)); + Assert.True(rebuiltTinfo3.Buffer.SequenceEqual(tinfo3.Buffer)); + + Assert.False(rebuiltTinfo1.Buffer.SequenceEqual(rebuiltTinfo3.Buffer)); + Assert.False(rebuiltTinfo2.Buffer.SequenceEqual(tinfo3.Buffer)); + Assert.False(rebuiltTinfo1.Buffer.SequenceEqual(tinfo3.Buffer)); + Assert.False(rebuiltTinfo2.Buffer.SequenceEqual(tinfo3.Buffer)); + + Assert.True(rebuiltTinfo1.Equals(tinfo1)); + Assert.True(rebuiltTinfo1.Equals(tinfo2)); + Assert.True(rebuiltTinfo1.Equals(rebuiltTinfo2)); + Assert.True(rebuiltTinfo2.Equals(rebuiltTinfo1)); + Assert.False(rebuiltTinfo1.Equals(rebuiltTinfo3)); + Assert.False(rebuiltTinfo2.Equals(rebuiltTinfo3)); + } + + private byte[] LoadTileData(string path) + { + return System.IO.File.ReadAllBytes(path); + } +} + +[TestFixture] +public class TestGettersAndSetters +{ + [Test] + [Category("RequiresSettings")] + public void TestingGettersAndSettersThatModifyBuffer() + { + var ark = new ArkLoader(Paths.UW_ArkOriginalPath); + foreach (var block in ark.TileMapObjectsBlocks) + { + foreach (var tile in block.TileInfos) + { + byte[] tempbuffer = new byte[tile.Buffer.Length]; + tile.Buffer.CopyTo(tempbuffer, 0); + + tile.TileHeight = tile.TileHeight; + tile.TileType = tile.TileType; + tile.Bit9 = tile.Bit9; + tile.Light = tile.Light; + tile.DoorBit = tile.DoorBit; + tile.NoMagic = tile.NoMagic; + tile.FirstObjIdx = tile.FirstObjIdx; + tile.FloorTextureIdx = tile.FloorTextureIdx; + tile.WallTextureIdx = tile.WallTextureIdx; + + Assert.True(tempbuffer.SequenceEqual(tile.Buffer)); + } + } + } + + [Test] + [Category("RequiresSettings")] + public void TestCreatingTileFromSetters() + { + var ark = new ArkLoader(Paths.UW_ArkOriginalPath); + foreach (var block in ark.TileMapObjectsBlocks) + { + foreach (var tile in block.TileInfos) + { + var Tile = new TileInfo(0, new byte[4], 0, 0); + + Tile.TileHeight = tile.TileHeight; + Tile.TileType = tile.TileType; + Tile.Bit9 = tile.Bit9; + Tile.Light = tile.Light; + Tile.DoorBit = tile.DoorBit; + Tile.NoMagic = tile.NoMagic; + Tile.FirstObjIdx = tile.FirstObjIdx; + Tile.FloorTextureIdx = tile.FloorTextureIdx; + Tile.WallTextureIdx = tile.WallTextureIdx; + + Assert.True(Tile.Buffer.SequenceEqual(tile.Buffer)); + } + } + + } +} diff --git a/RandomizerUnitTests/TestUWLinkedList.cs b/UWRandomizerUnitTests/Editor/TestUWLinkedList.cs similarity index 64% rename from RandomizerUnitTests/TestUWLinkedList.cs rename to UWRandomizerUnitTests/Editor/TestUWLinkedList.cs index 127a123..04e18a8 100644 --- a/RandomizerUnitTests/TestUWLinkedList.cs +++ b/UWRandomizerUnitTests/Editor/TestUWLinkedList.cs @@ -4,11 +4,10 @@ using System.Data; using System.Linq; using NUnit.Framework; -using UWRandomizerEditor.LEVDotARK; -using UWRandomizerEditor.LEVDotARK.GameObjects; -using UWRandomizerEditor.LEVDotARK; -using UWRandomizerEditor.LEVDotARK.GameObjects; -using UWRandomizerEditor.LEVDotARK.GameObjects.Specifics; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; +using UWRandomizerTools; namespace RandomizerUnitTests; @@ -18,11 +17,11 @@ public class TestUWLinkedList private List _gameObjects; private UWLinkedList LList1; private UWLinkedList LList2; - + [SetUp] public void Setup() { - var fillerBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 0}; + var fillerBytes = new byte[8]; _gameObjects = new List() { new StaticObject(fillerBytes, 0) {next = 0}, // end TODO: What is the buffer of object 0? @@ -36,8 +35,6 @@ public void Setup() LList1 = new UWLinkedList(); LList2 = new UWLinkedList(); - - } [Test] @@ -51,7 +48,7 @@ public void TestPopulateObjectList() LList1.startingIdx = 3; // This should clear the list Assert.True(LList1.Count == 0); LList1.PopulateObjectList(_gameObjects.ToArray()); - + Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {3, 4, 5})); Assert.True(LList1.Count == 3); @@ -59,14 +56,13 @@ public void TestPopulateObjectList() Assert.True(LList1.Count == 0); LList1.PopulateObjectList(_gameObjects.ToArray()); Assert.True(LList1.Count == 0); - + // Trying to trigger the ContraintException _gameObjects[1].next = 2; _gameObjects[2].next = 1; LList1.Clear(); LList1.startingIdx = 1; Assert.Throws(() => LList1.PopulateObjectList(_gameObjects.ToArray())); - } [Test] @@ -76,7 +72,7 @@ public void TestPop() LList2.startingIdx = 0; LList1.PopulateObjectList(_gameObjects); LList2.PopulateObjectList(_gameObjects); - + // Assert.Throws(() => throw new InvalidOperationException()); Assert.True(LList1.Pop().Equals(_gameObjects[6])); Assert.True(LList1.Pop().Equals(_gameObjects[2])); @@ -84,7 +80,7 @@ public void TestPop() Assert.Throws(() => LList1.Pop()); Assert.Throws(() => LList2.Pop()); } - + [Test] public void TestPopulateObjectList_withList() { @@ -94,159 +90,6 @@ public void TestPopulateObjectList_withList() Assert.True(LList1.Count == 3); } - [Test] - public void TestObjectsThatShouldBeMoved() - { - LList1.startingIdx = 1; - LList2.startingIdx = 3; - - LList1.PopulateObjectList(_gameObjects.ToArray()); - LList2.PopulateObjectList(_gameObjects.ToArray()); - - // LList1 has items in idx 1*,2,6* - // LList2 has items in idx 3,4,5* - // * means it should be moved - var objs1 = LList1.PopObjectsThatShouldBeMoved(); - var objs2 = LList2.PopObjectsThatShouldBeMoved(); - - // Now LList1 should be 2, LList2 should be 3,4 - // objs1 should be 1,6 - // objs2 should be 5 - Assert.True(LList1.Count + objs1.Count == 3); // Should be complementary - Assert.True(objs1[0].Equals(_gameObjects[1])); - Assert.True(LList1[0].Equals(_gameObjects[2])); - Assert.True(objs1[1].Equals(_gameObjects[6])); - - Assert.True(LList2.Count + objs2.Count == 3); - Assert.True(LList2[0].Equals(_gameObjects[3])); - Assert.True(LList2[1].Equals(_gameObjects[4])); - Assert.True(objs2[0].Equals(_gameObjects[5])); - - var empty1 = LList1.PopObjectsThatShouldBeMoved(); - var empty2 = LList2.PopObjectsThatShouldBeMoved(); - Assert.True(empty1.Count == 0); - Assert.True(empty2.Count == 0); - Assert.True(LList1.Count == 1); - Assert.True(LList2.Count == 2); - } - - [Test] - public void TestAppendItems() - { - LList1.startingIdx = 1; - LList2.startingIdx = 3; - - // LList1 has items in idx 1*,2,6* - // LList2 has items in idx 3,4,5* - // * means it should be moved - LList1.PopulateObjectList(_gameObjects.ToArray()); - LList2.PopulateObjectList(_gameObjects.ToArray()); - - // Now LList1 should be 2, LList2 should be 3,4 - // objs1 should be 1,6 - // objs2 should be 5 - var objs1 = LList1.PopObjectsThatShouldBeMoved(); - var objs2 = LList2.PopObjectsThatShouldBeMoved(); - - // LList1 should have 2,5 - // LList2 should have 3,4,1,6 - LList1.AppendItems(objs2.ToArray()); // Changing to ToArray just to cover the test - LList2.AppendItems(objs1.ToArray()); - - Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {2, 5})); - Assert.True(CheckObjectsAtPositions(LList2.ToList(), new List() {3, 4, 1, 6})); - Assert.True(LList1.CheckIntegrity()); - Assert.True(LList2.CheckIntegrity()); - } - - [Test] - public void TestAppendItems_OnEmpty() - { - LList1.startingIdx = 1; - LList2.startingIdx = 0; - - // LList1 has items in idx 1*,2,6* - // LList2 has no items - // * means it should be moved - LList1.PopulateObjectList(_gameObjects.ToArray()); - LList2.PopulateObjectList(_gameObjects.ToArray()); - - // Now LList1 should be 2, LList2 should be empty - // objs1 should be 1,6 - // objs2 should be empty - var objs1 = LList1.PopObjectsThatShouldBeMoved(); - var objs2 = LList2.PopObjectsThatShouldBeMoved(); - - // LList1 should have 2 - // LList2 should have 1,6 - LList1.AppendItems(objs2); - LList2.AppendItems(objs1); - - Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {2})); - Assert.True(CheckObjectsAtPositions(LList2.ToList(), new List() {1, 6})); - Assert.True(LList1.CheckIntegrity()); - Assert.True(LList2.CheckIntegrity()); - } - - [Test] - public void TestPrependItems() - { - LList1.startingIdx = 1; - LList2.startingIdx = 3; - - // LList1 has items in idx 1*,2,6* - // LList2 has items in idx 3,4,5* - // * means it should be moved - LList1.PopulateObjectList(_gameObjects.ToArray()); - LList2.PopulateObjectList(_gameObjects.ToArray()); - - // Now LList1 should be 2, LList2 should be 3,4 - // objs1 should be 1,6 - // objs2 should be 5 - var objs1 = LList1.PopObjectsThatShouldBeMoved(); - var objs2 = LList2.PopObjectsThatShouldBeMoved(); - - // LList1 should have 5,2 - // LList2 should have 1,6,3,4 - LList1.PrependItems(objs2); - LList2.PrependItems(objs1); - - Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {5, 2})); - Assert.True(CheckObjectsAtPositions(LList2.ToList(), new List() {1, 6, 3, 4})); - Assert.False(CheckObjectsAtPositions(LList1.ToList(), new List() {2, 5})); - Assert.False(CheckObjectsAtPositions(LList2.ToList(), new List() {3, 4, 1, 6})); - Assert.True(LList1.CheckIntegrity()); - Assert.True(LList2.CheckIntegrity()); - } - - [Test] - public void TestPrependItems_OnEmptyLList() - { - LList1.startingIdx = 1; - LList2.startingIdx = 0; - - // LList1 has items in idx 1*,2,6* - // LList2 has no items - // * means it should be moved - LList1.PopulateObjectList(_gameObjects.ToArray()); - LList2.PopulateObjectList(_gameObjects.ToArray()); - - // Now LList1 should be 2, LList2 should be empty - // objs1 should be 1,6 - // objs2 should be empty - var objs1 = LList1.PopObjectsThatShouldBeMoved(); - var objs2 = LList2.PopObjectsThatShouldBeMoved(); - - // LList1 should have 2 - // LList2 should have 1,6 - LList1.PrependItems(objs2); - LList2.PrependItems(objs1); - - Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {2})); - Assert.True(CheckObjectsAtPositions(LList2.ToList(), new List() {1, 6})); - Assert.True(LList1.CheckIntegrity()); - Assert.True(LList2.CheckIntegrity()); - } [Test] public void TestRemoveAt_BackToFront() @@ -276,23 +119,22 @@ public void TestRemoveAt_BackToFront() Assert.Throws(() => LList1.RemoveAt(0)); Assert.True(LList1.startingIdx == 0); Assert.True(LList1.CheckIntegrity()); - } - - [Test] - public void TestRemoveAt_FrontToBack(){ + [Test] + public void TestRemoveAt_FrontToBack() + { // LList1 has items in idx 1,2,6 LList1.startingIdx = 1; LList1.PopulateObjectList(_gameObjects); - + LList1.RemoveAt(0); // items 2,6 Assert.True(LList1.Count == 2); Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {2, 6})); Assert.Throws(() => LList1.RemoveAt(2)); Assert.True(LList1.startingIdx == LList1[0].IdxAtObjectArray); Assert.True(LList1.CheckIntegrity()); - + LList1.RemoveAt(0); // items 6 Assert.True(LList1.Count == 1); Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {6})); @@ -300,19 +142,19 @@ public void TestRemoveAt_FrontToBack(){ Assert.True(LList1.startingIdx == LList1[0].IdxAtObjectArray); Assert.True(LList1.CheckIntegrity()); } - - [Test] - public void TestRemoveAt_Middle(){ + [Test] + public void TestRemoveAt_Middle() + { // LList1 has items in idx 1,2,6 LList1.startingIdx = 1; LList1.PopulateObjectList(_gameObjects); - + LList1.RemoveAt(1); // items 1,6 Assert.True(LList1.Count == 2); Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {1, 6})); Assert.True(LList1.CheckIntegrity()); - + LList1.RemoveAt(1); // items 1 Assert.True(LList1.Count == 1); Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {1})); @@ -359,19 +201,19 @@ public void TestInsert() { LList1.startingIdx = 1; LList1.PopulateObjectList(_gameObjects); - + LList1.Insert(0, _gameObjects[3]); Assert.True(LList1.Count == 4); Assert.True(LList1.CheckIntegrity()); LList1.RemoveAt(0); Assert.True(LList1.CheckIntegrity()); - + LList1.Insert(3, _gameObjects[3]); Assert.True(LList1.Count == 4); Assert.True(LList1.CheckIntegrity()); LList1.RemoveAt(3); Assert.True(LList1.CheckIntegrity()); - + LList1.Insert(2, _gameObjects[3]); Assert.True(LList1.Count == 4); Assert.True(LList1.CheckIntegrity()); @@ -385,10 +227,12 @@ public void TestGetEnumerator() { LList1.startingIdx = 1; LList1.PopulateObjectList(_gameObjects); - foreach (var foo in LList1) { } + foreach (var foo in LList1) + { + } // todo: How do I get to IEnumerator IEnumerable.GetEnumerator? } - + private bool CheckObjectsAtPositions(List list, List correctIdxs) { @@ -396,6 +240,7 @@ private bool CheckObjectsAtPositions(List list, List correctI { throw new InvalidOperationException("Can't compare two lists of unequal counts!"); } + for (int i = 0; i < list.Count; i++) { if (!_gameObjects[correctIdxs[i]].Equals(list[i])) @@ -414,33 +259,32 @@ public void TestListCheckIntegrity() LList1.startingIdx = 1; LList1.PopulateObjectList(_gameObjects); Assert.True(LList1.CheckIntegrity()); - + // Changing the index of the first object should invalidate the list because it wouldn't be pointing correctly LList1[0].IdxAtObjectArray = 100; // Should be 1 Assert.False(LList1.CheckIntegrity()); LList1[0].IdxAtObjectArray = 1; // Returning to normal Assert.True(LList1.CheckIntegrity()); - + // Changing the index of an object in the middle, or its next attribute, should invalidate the LList LList1[1].IdxAtObjectArray = 100; // Should be 2 Assert.False(LList1.CheckIntegrity()); LList1[1].IdxAtObjectArray = 2; // Returning Assert.True(LList1.CheckIntegrity()); - + LList1[1].next = 100; // Should be 6 Assert.False(LList1.CheckIntegrity()); LList1[1].next = 6; // Returning Assert.True(LList1.CheckIntegrity()); - + // _gameObjects[^1].next = 100; - Assert.False(LList1.CheckIntegrity()); - + Assert.False(LList1.CheckIntegrity()); + // Having a starting index but no objects is invalid LList1.Clear(); LList1.startingIdx = 1; Assert.False(LList1.CheckIntegrity()); - } [Test] @@ -475,11 +319,10 @@ public void TestListCtor() LList1.startingIdx = 1; LList1.PopulateObjectList(_gameObjects); - var LList3 = new UWLinkedList(LList1.ToList(), (short) LList1.startingIdx); - var LList4 = new UWLinkedList(LList1.ToArray(), (short) LList1.startingIdx); + var LList3 = new UWLinkedList(LList1.ToList(), (ushort) LList1.startingIdx); + var LList4 = new UWLinkedList(LList1.ToArray(), (ushort) LList1.startingIdx); Assert.True(LList3.CheckIntegrity()); Assert.True(LList4.CheckIntegrity()); - } [Test] @@ -494,10 +337,10 @@ public void TestSet() Assert.True(LList1.CheckIntegrity()); LList1[1] = _gameObjects[5]; Assert.True(LList1.CheckIntegrity()); - + Assert.Throws(() => LList1[-1] = _gameObjects[2]); Assert.Throws(() => LList1[100] = _gameObjects[2]); - + LList1.Clear(); LList1.Add(_gameObjects[1]); LList1[0] = _gameObjects[2]; @@ -509,7 +352,7 @@ public void TestGet() { LList1.startingIdx = 1; LList1.PopulateObjectList(_gameObjects); - + Assert.True(LList1[0].Equals(_gameObjects[1])); Assert.True(LList1[1].Equals(_gameObjects[2])); Assert.True(LList1[2].Equals(_gameObjects[6])); @@ -532,4 +375,6 @@ public void TestHavingItemOfIdx0() LList1.Add(_gameObjects[0]); Assert.False(LList1.CheckIntegrity()); } + + } \ No newline at end of file diff --git a/UWRandomizerUnitTests/Editor/TestUWLinkedList_Containers.cs b/UWRandomizerUnitTests/Editor/TestUWLinkedList_Containers.cs new file mode 100644 index 0000000..3d1fdd8 --- /dev/null +++ b/UWRandomizerUnitTests/Editor/TestUWLinkedList_Containers.cs @@ -0,0 +1,98 @@ +using System.Linq; +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; + +namespace RandomizerUnitTests.Editor; + +[TestFixture] +public class TestUWLinkedList_Containers +{ + private Container _bag1; + private TileInfo _tile1; + + private Container _bag2; + private GameObject[] _gameObjects; + + /// + /// Contents + /// bag2(5) -> end(0) + /// | + /// -> bag1(4) -> end(0) + /// | + /// -> Key(1) -> EnchantedWeapon(2) -> EnchantedArmor(3) -> end(0) + /// Tile either points to bag1 or bag2 (depends on test) + /// + [SetUp] + public void SetUp() + { + var tempbuffer = new byte[8]; + _bag1 = new Container(tempbuffer, 4) + { + QuantityOrSpecialLinkOrSpecialProperty = 1, + Contents = new UWLinkedList() {startingIdx = 1, RepresentingContainer = true} + }; + _bag2 = new Container(tempbuffer, 5) + { + QuantityOrSpecialLinkOrSpecialProperty = 4, + Contents = new UWLinkedList() {startingIdx = 4, RepresentingContainer = true} + }; // for bag in bag + + _gameObjects = new GameObject[] + { + new StaticObject(tempbuffer, 0), // Empty obj + new Key(tempbuffer, 1) {next = 2}, + new EnchantedWeapon(tempbuffer, 2) {next = 3}, + new EnchantedArmor(tempbuffer, 3) {next = 0}, + _bag1, + _bag2, + new Door(tempbuffer, 6), + }; + _tile1 = new TileInfo(0, 0, 0, 0) {FirstObjIdx = 4}; + } + + [Test] + public void TestAddingToContainer() + { + _bag1.Contents.PopulateObjectList(_gameObjects); + Assert.True(_bag1.Contents.Count == 3); + Assert.True(_bag1.Contents[0].Equals(_gameObjects[1])); + Assert.True(_bag1.Contents[1].Equals(_gameObjects[2])); + Assert.True(_bag1.Contents[2].Equals(_gameObjects[3])); + Assert.False( (from obj in _gameObjects where obj.InContainer select 1).Sum() != 3 ); + Assert.True( (from obj in _gameObjects where obj.InContainer select 1).Sum() == 3 ); + } + + [Test] + public void TestBagInBag() + { + _bag1.Contents.PopulateObjectList(_gameObjects); + _bag2.Contents.PopulateObjectList(_gameObjects); + Assert.True(_bag2.Contents.Count == 1); + Assert.True(_bag2.Contents[0].Equals(_bag1)); + Assert.False( (from obj in _gameObjects where obj.InContainer select 1).Sum() != 4 ); + Assert.True( (from obj in _gameObjects where obj.InContainer select 1).Sum() == 4 ); + } + + [Test] + public void TestContainerInTile() + { + _bag1.Contents.PopulateObjectList(_gameObjects); + _tile1.ObjectChain.PopulateObjectList(_gameObjects); + Assert.False( (from obj in _gameObjects where obj.InContainer select 1).Sum() != 3 ); + Assert.True( (from obj in _gameObjects where obj.InContainer select 1).Sum() == 3 ); + } + + [Test] + public void TestBagInBagInTile() + { + _bag1.Contents.PopulateObjectList(_gameObjects); + _bag2.Contents.PopulateObjectList(_gameObjects); + _tile1.FirstObjIdx = _bag2.IdxAtObjectArray; + _tile1.ObjectChain.PopulateObjectList(_gameObjects); + Assert.False( (from obj in _gameObjects where obj.InContainer select 1).Sum() != 4 ); + Assert.True( (from obj in _gameObjects where obj.InContainer select 1).Sum() == 4 ); + } + +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Editor/TestUtils.cs b/UWRandomizerUnitTests/Editor/TestUtils.cs new file mode 100644 index 0000000..f7aace1 --- /dev/null +++ b/UWRandomizerUnitTests/Editor/TestUtils.cs @@ -0,0 +1,193 @@ +using System; +using System.IO; +using NUnit.Framework; +using UWRandomizerEditor.Interfaces; + +namespace RandomizerUnitTests; + +[TestFixture] +internal class TestSetBits +{ + [Test] + public void TestSetBits1() + { + byte currval = 0b0001; + byte mask = 0b0011; + byte newval = 0b0011; + byte shift = 1; + byte expected = 0b0111; + int calc = UWRandomizerEditor.Utils.SetBits(currval, newval, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestSetBits2() + { + byte currval = 0b0001; + byte mask = 0b0110; + byte newval = 0b0011; + byte shift = 0; + byte expected = 0b0011; + int calc = UWRandomizerEditor.Utils.SetBits(currval, newval, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestSetBits3() + { + byte currval = 0b1111; + byte mask = 0b0011; + byte newval = 0b1100; + byte shift = 1; + byte expected = 0b1001; + int calc = UWRandomizerEditor.Utils.SetBits(currval, newval, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestSetBits4() + { + byte currval = 0b1111; + byte mask = 0b0110; + byte newval = 0b0000; + byte shift = 0; + byte expected = 0b1001; + int calc = UWRandomizerEditor.Utils.SetBits(currval, newval, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestSetBits5() + { + byte currval = 0b1111_1111; + byte mask = 0b0011_1100; + byte newval = 0b0000_0000; + byte shift = 0; + byte expected = 0b1100_0011; + int calc = UWRandomizerEditor.Utils.SetBits(currval, newval, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestSetBits6() + { + byte currval = 0b1111_1111; + byte mask = 0b0000_1111; + byte newval = 0b0000_0000; + byte shift = 2; + byte expected = 0b1100_0011; + int calc = UWRandomizerEditor.Utils.SetBits(currval, newval, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestSetBits7() + { + byte currval = 0b1100_1110; + byte mask = 0b0011_1000; + byte newval = 0b1001_0000; + byte shift = 0; + byte expected = 0b1101_0110; + int calc = UWRandomizerEditor.Utils.SetBits(currval, newval, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestSetBits8() + { + byte currval = 0b1111_0000; + byte mask = 0b0000_1111; + byte newval = 0b0000_1010; + byte shift = 4; + byte expected = 0b1010_0000; + int calc = UWRandomizerEditor.Utils.SetBits(currval, newval, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } +} + +[TestFixture] +internal class TestGetBits +{ + [Test] + public void TestGetBits1() + { + int value = 0b1010; + int mask = 0b0011; + int shift = 0; + int expected = 0b0010; + int calc = UWRandomizerEditor.Utils.GetBits(value, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestGetBits2() + { + int value = 0b0000; + int mask = 0b0011; + int shift = 0; + int expected = 0b0000; + int calc = UWRandomizerEditor.Utils.GetBits(value, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestGetBits3() + { + int value = 0b1100_1001; + int mask = 0b1111; + int shift = 4; + int expected = 0b1100; + int calc = UWRandomizerEditor.Utils.GetBits(value, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } + + [Test] + public void TestGetBits4() + { + int value = 0b1010_0101; + int mask = 0b1111; + int shift = 2; + int expected = 0b1001; + int calc = UWRandomizerEditor.Utils.GetBits(value, mask, shift); + Assert.AreEqual(expected, calc, $"{expected:x}!={calc:x}"); + } +} + +[TestFixture] +internal class TestSaveBuffer +{ + // TODO: Is there a better way of making this? + private class MockBuffer : IBufferObject + { + private Random r = new Random(); + public byte[] Buffer { get; set; } = new byte[256]; + + public bool ReconstructBuffer() + { + return true; + } + + public MockBuffer() + { + r.NextBytes(Buffer); + } + } + + [Test] + public void Test() + { + string path = Path.GetFullPath("."); + string filename = "TestSaveBuffer.bin"; + var mock = new MockBuffer(); + + string outputPath = UWRandomizerEditor.Utils.StdSaveBuffer(mock, path, filename); + byte[] outputBytes = File.ReadAllBytes(outputPath); + + for (int i = 0; i < outputBytes.Length; i++) + { + Assert.True(outputBytes[i] == mock.Buffer[i]); + } + + File.Delete(outputPath); + } +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Editor/TestsManualBuffers.cs b/UWRandomizerUnitTests/Editor/TestsManualBuffers.cs new file mode 100644 index 0000000..5519f54 --- /dev/null +++ b/UWRandomizerUnitTests/Editor/TestsManualBuffers.cs @@ -0,0 +1,44 @@ +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; + +namespace RandomizerUnitTests; + +[TestFixture] +public class ManualTests +{ + [Test] + public void TestTileInfoComparingToUltimateEditor_manual() + { + var tile = new TileInfo(1609, new byte[] {0x11, 0x20, 0x1E, 0x00}, 6436, 0); + // Some water tile near that island with a lurker nearby + var reference = new TileInfo(1609, 0, 6436, 0); + reference.TileType = (int) TileInfo.TileTypes.Open; + reference.TileHeight = 1; + reference.DoorBit = 0; + reference.NoMagic = 0; + reference.FloorTextureIdx = 8; + reference.WallTextureIdx = 30; + reference.FirstObjIdx = 0; + + Assert.True(tile.Equals(reference)); + } + + [Test] + public void TestRemovingLockFromDoor_ManualBuffers() + { + var doorUnlocked = new Door(new byte[] {0x41, 0x01, 0x50, 0x6F, 0x28, 0x00, 0x00, 0x00}, 1012); + var doorLocked = new Door(new byte[] {0x41, 0x01, 0x50, 0x6F, 0x28, 0x00, 0xC0, 0xF8}, 1012); + var doorToModify = new Door(new byte[] {0x41, 0x01, 0x50, 0x6F, 0x28, 0x00, 0xC0, 0xF8}, 1012); + + Assert.False(doorLocked.Equals(doorUnlocked)); + Assert.True(doorLocked.Equals(doorToModify)); + Assert.False(doorToModify.Equals(doorUnlocked)); + + doorToModify.RemoveLock(); + + Assert.False(doorLocked.Equals(doorUnlocked)); + Assert.False(doorLocked.Equals(doorToModify)); + Assert.True(doorToModify.Equals(doorUnlocked)); + } +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Paths.cs b/UWRandomizerUnitTests/Paths.cs new file mode 100644 index 0000000..158f3f2 --- /dev/null +++ b/UWRandomizerUnitTests/Paths.cs @@ -0,0 +1,18 @@ +using System.Configuration; + +namespace RandomizerUnitTests; + +public static class Paths +{ + public static Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + public static string BufferTestsPath = config.AppSettings.Settings["BufferTestsPath"].Value; + public static string BasePath = config.AppSettings.Settings["BasePath"].Value; + public static string UW_OriginalPath = config.AppSettings.Settings["UWOriginalPath"].Value; + public static string UW_CleanedPath = config.AppSettings.Settings["UWCleanedPath"].Value; + public static string UW_ArkOriginalPath = config.AppSettings.Settings["UWArkOriginalPath"].Value; + public static string UW_ArkCleanedPath = config.AppSettings.Settings["UWArkCleanedPath"].Value; + public static string RUT_TestDataPath = config.AppSettings.Settings["RUTTestDataPath"].Value; + + public static string UWArkCleanedNoDoorsPath_Fixed = + config.AppSettings.Settings["UWArkCleanedNoDoorsPath_Fixed"].Value; +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/ReSharperTestRunner.dll.config b/UWRandomizerUnitTests/ReSharperTestRunner.dll.config new file mode 100644 index 0000000..3c23e60 --- /dev/null +++ b/UWRandomizerUnitTests/ReSharperTestRunner.dll.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UWRandomizerUnitTests/Tools/TestIsTileValid.cs b/UWRandomizerUnitTests/Tools/TestIsTileValid.cs new file mode 100644 index 0000000..2dbd859 --- /dev/null +++ b/UWRandomizerUnitTests/Tools/TestIsTileValid.cs @@ -0,0 +1,100 @@ +using System.Linq; +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerTools; + +namespace RandomizerUnitTests; + +[TestFixture] +public class TestIsTileValid +{ + + [Test] + public void TestTilePositionsLvls1To6([Range(0, 5)] int levelNumber) + { + for (uint i = 0; i < 64*64; i++) + { + var Tile = new TileInfo(i, 0, i * TileInfo.FixedSize, levelNumber) {TileType = (uint) TileInfo.TileTypes.Open}; + if (((Tile.XPos>= 30) & (Tile.XPos <= 33) & (Tile.YPos >= 30) & (Tile.YPos <= 33))) + Assert.False(ShuffleItems.IsTileValid(Tile)); + else + Assert.True(ShuffleItems.IsTileValid(Tile)); + } + } + + [Test] + public void TestTilePositionsLvl7() + { + uint[][] invalidPositions = new[] + { + new[] {30u, 35u}, + new[] {31u, 35u}, + new[] {32u, 35u}, + new[] {33u, 35u}, + new[] {30u, 34u}, + new[] {31u, 34u}, + new[] {32u, 34u}, + new[] {33u, 34u}, + new[] {30u, 33u}, + new[] {31u, 33u}, + new[] {32u, 33u}, + new[] {33u, 33u}, + new[] {30u, 32u}, + new[] {31u, 32u}, + new[] {32u, 32u}, + new[] {33u, 32u}, + new[] {30u, 31u}, + new[] {31u, 31u}, + new[] {32u, 31u}, + new[] {33u, 31u}, + new[] {30u, 30u}, + new[] {31u, 30u}, + new[] {32u, 30u}, + new[] {33u, 30u}, + new[] {30u, 29u}, + new[] {31u, 29u}, + new[] {32u, 29u}, + new[] {33u, 29u}, + new[] {29u, 34u}, + new[] {29u, 33u}, + new[] {29u, 32u}, + new[] {29u, 31u}, + new[] {29u, 30u}, + new[] {28u, 33u}, + new[] {28u, 32u}, + new[] {28u, 31u}, + new[] {34u, 34u}, + new[] {34u, 33u}, + new[] {34u, 32u}, + new[] {34u, 31u}, + new[] {34u, 30u}, + new[] {35u, 33u}, + new[] {35u, 32u}, + new[] {35u, 31u} + }; + + for (uint i = 0; i < 64 * 64; i++) + { + var Tile = new TileInfo(i, 0, i * TileInfo.FixedSize, 6) {TileType = (uint) TileInfo.TileTypes.Open}; + if (invalidPositions.Any(x=>x.SequenceEqual(Tile.XYPos))) + Assert.False(ShuffleItems.IsTileValid(Tile)); + else + Assert.True(ShuffleItems.IsTileValid(Tile)); + } + } + + [Test] + public void TestTilePositionsLvl8() + { + for (uint i = 0; i < 64 * 64; i++) + { + var Tile = new TileInfo(i, 0, i * TileInfo.FixedSize, 7) {TileType = (uint) TileInfo.TileTypes.Open}; + if ((Tile.XPos >= 29) & (Tile.XPos <= 35) & (Tile.YPos >= 30) & (Tile.YPos <= 35)) + Assert.False(ShuffleItems.IsTileValid(Tile), $"X:{Tile.XPos} Y:{Tile.YPos}"); + else if (Tile.XPos >= 31 & Tile.XPos <= 33 & Tile.YPos == 29) + Assert.False(ShuffleItems.IsTileValid(Tile), $"X:{Tile.XPos} Y:{Tile.YPos}"); + else + Assert.True(ShuffleItems.IsTileValid(Tile), $"X:{Tile.XPos} Y:{Tile.YPos}"); + } + } +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Tools/TestItemRemoval.cs b/UWRandomizerUnitTests/Tools/TestItemRemoval.cs new file mode 100644 index 0000000..53d3a0e --- /dev/null +++ b/UWRandomizerUnitTests/Tools/TestItemRemoval.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; +using UWRandomizerTools; + +namespace RandomizerUnitTests; + +[Category("RequiresSettings")] +public class TestItemRemoval +{ + private List _gameObjects; + private UWLinkedList LList1; + private UWLinkedList LList2; + private ItemRandomizationSettings settings; + + [SetUp] + public void Setup() + { + settings = new ItemRandomizationSettings(); + var fillerBytes = new byte[8]; + _gameObjects = new List() + { + new StaticObject(fillerBytes, 0) {next = 0}, // end TODO: What is the buffer of object 0? + new QuantityGameObject(fillerBytes, 1) {next = 2}, // 1.1 movable + new Door(fillerBytes, 2) {next = 6}, // 1.2 immovable + new Trap(fillerBytes, 3) {next = 4}, // 2.1 immovable + new TexturedGameObject(fillerBytes, 4) {next = 5}, // 2.2 immovable + new EnchantedArmor(fillerBytes, 5) {next = 0}, // 2.3 (last) movable + new EnchantedWeapon(fillerBytes, 6) {next = 0} // 1.3 (last) movable + }; + + LList1 = new UWLinkedList(); + LList2 = new UWLinkedList(); + } + + [Test] + public void TestRemoveMovableItems() + { + LList1.startingIdx = 1; + LList2.startingIdx = 3; + + LList1.PopulateObjectList(_gameObjects.ToArray()); + LList2.PopulateObjectList(_gameObjects.ToArray()); + + // LList1 has items in idx 1*,2,6* + // LList2 has items in idx 3,4,5* + // * means it should be moved + var objs1 = ItemTools.ExtractMovableItems(LList1, settings); + var objs2 = ItemTools.ExtractMovableItems(LList2, settings); + + // Now LList1 should be 2, LList2 should be 3,4 + // objs1 should be 1,6 + // objs2 should be 5 + Assert.True(LList1.Count + objs1.Count == 3); // Should be complementary + Assert.True(objs1[0].Equals(_gameObjects[1])); + Assert.True(LList1[0].Equals(_gameObjects[2])); + Assert.True(objs1[1].Equals(_gameObjects[6])); + + Assert.True(LList2.Count + objs2.Count == 3); + Assert.True(LList2[0].Equals(_gameObjects[3])); + Assert.True(LList2[1].Equals(_gameObjects[4])); + Assert.True(objs2[0].Equals(_gameObjects[5])); + + var empty1 = ItemTools.ExtractMovableItems(LList1, settings); + var empty2 = ItemTools.ExtractMovableItems(LList2, settings); + Assert.True(empty1.Count == 0); + Assert.True(empty2.Count == 0); + Assert.True(LList1.Count == 1); + Assert.True(LList2.Count == 2); + } + + [Test] + public void TestSwapMovableItems() + { + LList1.startingIdx = 1; + LList2.startingIdx = 3; + + // LList1 has items in idx 1*,2,6* + // LList2 has items in idx 3,4,5* + // * means it should be moved + LList1.PopulateObjectList(_gameObjects.ToArray()); + LList2.PopulateObjectList(_gameObjects.ToArray()); + + // Now LList1 should be 2, LList2 should be 3,4 + // objs1 should be 1,6 + // objs2 should be 5 + var objs1 = ItemTools.ExtractMovableItems(LList1, settings); + var objs2 = ItemTools.ExtractMovableItems(LList2, settings); + + // LList1 should have 2,5 + // LList2 should have 3,4,1,6 + LList1.AppendItems(objs2.ToArray()); // Changing to ToArray just to cover the test + LList2.AppendItems(objs1.ToArray()); + + Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {2, 5})); + Assert.True(CheckObjectsAtPositions(LList2.ToList(), new List() {3, 4, 1, 6})); + Assert.True(LList1.CheckIntegrity()); + Assert.True(LList2.CheckIntegrity()); + } + + [Test] + public void TestAppendItems_OnEmptyLList() + { + LList1.startingIdx = 1; + LList2.startingIdx = 0; + + // LList1 has items in idx 1*,2,6* + // LList2 has no items + // * means it should be moved + LList1.PopulateObjectList(_gameObjects.ToArray()); + LList2.PopulateObjectList(_gameObjects.ToArray()); + + // Now LList1 should be 2, LList2 should be empty + // objs1 should be 1,6 + // objs2 should be empty + var objs1 = ItemTools.ExtractMovableItems(LList1, settings); + var objs2 = ItemTools.ExtractMovableItems(LList2, settings); + + // LList1 should have 2 + // LList2 should have 1,6 + LList1.AppendItems(objs2); + LList2.AppendItems(objs1); + + Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {2})); + Assert.True(CheckObjectsAtPositions(LList2.ToList(), new List() {1, 6})); + Assert.True(LList1.CheckIntegrity()); + Assert.True(LList2.CheckIntegrity()); + } + + [Test] + public void TestSwapItemsByPrepend() + { + LList1.startingIdx = 1; + LList2.startingIdx = 3; + + // LList1 has items in idx 1*,2,6* + // LList2 has items in idx 3,4,5* + // * means it should be moved + LList1.PopulateObjectList(_gameObjects.ToArray()); + LList2.PopulateObjectList(_gameObjects.ToArray()); + + // Now LList1 should be 2, LList2 should be 3,4 + // objs1 should be 1,6 + // objs2 should be 5 + var objs1 = ItemTools.ExtractMovableItems(LList1, settings); + var objs2 = ItemTools.ExtractMovableItems(LList2, settings); + + // LList1 should have 5,2 + // LList2 should have 1,6,3,4 + LList1.PrependItems(objs2); + LList2.PrependItems(objs1); + + Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {5, 2})); + Assert.True(CheckObjectsAtPositions(LList2.ToList(), new List() {1, 6, 3, 4})); + Assert.False(CheckObjectsAtPositions(LList1.ToList(), new List() {2, 5})); + Assert.False(CheckObjectsAtPositions(LList2.ToList(), new List() {3, 4, 1, 6})); + Assert.True(LList1.CheckIntegrity()); + Assert.True(LList2.CheckIntegrity()); + } + + [Test] + public void TestPrependItems_OnEmptyLList() + { + LList1.startingIdx = 1; + LList2.startingIdx = 0; + + // LList1 has items in idx 1*,2,6* + // LList2 has no items + // * means it should be moved + LList1.PopulateObjectList(_gameObjects.ToArray()); + LList2.PopulateObjectList(_gameObjects.ToArray()); + + // Now LList1 should be 2, LList2 should be empty + // objs1 should be 1,6 + // objs2 should be empty + var objs1 = ItemTools.ExtractMovableItems(LList1, settings); + var objs2 = ItemTools.ExtractMovableItems(LList2, settings); + + // LList1 should have 2 + // LList2 should have 1,6 + LList1.PrependItems(objs2); + LList2.PrependItems(objs1); + + Assert.True(CheckObjectsAtPositions(LList1.ToList(), new List() {2})); + Assert.True(CheckObjectsAtPositions(LList2.ToList(), new List() {1, 6})); + Assert.True(LList1.CheckIntegrity()); + Assert.True(LList2.CheckIntegrity()); + } + + [Test] + public void TestCyclesOfRemovalAndAppendingOnTile() + { + var Tile1 = new TileInfo(0, 0, 0, 0); + Tile1.FirstObjIdx = 1; + + // Tile has items in idx 1*,2,6* + // * means it should be moved + Tile1.ObjectChain.PopulateObjectList(_gameObjects); + + // objs1 should be 1,6 + var objs = ItemTools.ExtractMovableItems(Tile1, settings); + + foreach (var obj in objs) + { + Tile1.ObjectChain.Add(obj); + } + // Tile1 should be 2, 1, 6 + + var Tile2 = new TileInfo(0, 0, 0, 0); + Tile2.FirstObjIdx = 2; + Tile2.ObjectChain.PopulateObjectList(_gameObjects); + + objs = ItemTools.ExtractMovableItems(Tile2, settings); + foreach (var obj in objs) + { + Tile2.ObjectChain.Add(obj); + } + + Tile1.ReconstructBuffer(); + Tile2.ReconstructBuffer(); + Assert.True(Tile1.Equals(Tile2)); + + } + + private bool CheckObjectsAtPositions(List list, List correctIdxs) + { + if (list.Count != correctIdxs.Count) + { + throw new InvalidOperationException("Can't compare two lists of unequal counts!"); + } + + for (int i = 0; i < list.Count; i++) + { + if (!_gameObjects[correctIdxs[i]].Equals(list[i])) + { + return false; + } + } + + return true; + } + +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Tools/TestItemRemoval_Containers.cs b/UWRandomizerUnitTests/Tools/TestItemRemoval_Containers.cs new file mode 100644 index 0000000..c4bce63 --- /dev/null +++ b/UWRandomizerUnitTests/Tools/TestItemRemoval_Containers.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; +using UWRandomizerTools; + +namespace RandomizerUnitTests; + +[TestFixture] +public class TestItemRemoval_Containers +{ + private Container _bag1; + private TileInfo _tile1; + private Container _bag2; + private GameObject[] _gameObjects; + + + /// + /// Item sequence: + /// Tile -> bag2(5) -> Door(6) -> Trap(7) -> EnchantedWeapon(8) -> Trigger(10) -> end(0) + /// | (in) + /// -> bag1(4) -> end(0) + /// | (in) + /// -> Key(1) -> EnchantedWeapon(2) -> EnchantedArmor(3) -> end(0) + /// + /// Movables: bag2, EnchantedWeapon + /// + [SetUp] + public void SetUp() + { + var tempbuffer = new byte[8]; + _bag1 = new Container(tempbuffer, 4) + { + QuantityOrSpecialLinkOrSpecialProperty = 1, + Contents = new UWLinkedList() {startingIdx = 1, RepresentingContainer = true} + }; + _bag2 = new Container(tempbuffer, 5) + { + QuantityOrSpecialLinkOrSpecialProperty = 4, + Contents = new UWLinkedList() {startingIdx = 4, RepresentingContainer = true}, + next = 6, + }; // for bag in bag + + _gameObjects = new GameObject[] + { + new StaticObject(tempbuffer, 0), // Empty obj + new Key(tempbuffer, 1) {next = 2}, + new EnchantedWeapon(tempbuffer, 2) {next = 3}, + new EnchantedArmor(tempbuffer, 3) {next = 0}, + _bag1, + _bag2, + new Door(tempbuffer, 6) {next = 7}, + new Trap(tempbuffer, 7) {next = 8}, + new EnchantedWeapon(tempbuffer, 8) {next = 10}, + new TexturedGameObject(tempbuffer, 9) {next = 0}, // Unused on purpose + new Trigger(tempbuffer, 10) {next = 0} + }; + } + + [Test] + public void TestMovables() + { + _tile1 = new TileInfo(0, 0, 0, 0) {FirstObjIdx = 5}; + _tile1.ObjectChain.PopulateObjectList(_gameObjects); + _bag1.Contents.PopulateObjectList(_gameObjects); + _bag2.Contents.PopulateObjectList(_gameObjects); + + List movables = ItemTools.ExtractMovableItems(_tile1, new ItemRandomizationSettings()); + Assert.True(movables[0].Equals(_bag2)); + Assert.True(movables[1].Equals(_gameObjects[8])); + Assert.True( (from gobj in _gameObjects where gobj.InContainer select 1).Sum() == 4 ); + Assert.False( (from gobj in _gameObjects where gobj.InContainer select 1).Sum() != 4 ); + } + +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Tools/TestMovingItemsInLevel.cs b/UWRandomizerUnitTests/Tools/TestMovingItemsInLevel.cs new file mode 100644 index 0000000..cdb6636 --- /dev/null +++ b/UWRandomizerUnitTests/Tools/TestMovingItemsInLevel.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using NUnit.Framework; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.GameObjects; +using UWRandomizerTools; + +namespace RandomizerUnitTests; + +[TestFixture] +public class TestMovingItemsInLevel +{ + + private TileInfo Move10ItemsNearSpawn(ArkLoader arkFile, ItemRandomizationSettings settings) + { + var lvl = arkFile.TileMapObjectsBlocks[0]; + var leftTile = lvl.TileInfos[159]; // X 31 Y 2 + // Assert.True(leftTile.FirstObjIdx == 981); + + Stack objectsInLevel = new Stack(); + int count = 0; + foreach (var tile in lvl.TileInfos) + { + // if (tile.XYPos[0] == 31 & tile.XYPos[1] == 2) continue; + if (count >= 10) + { + break; + } + foreach (var obj in ItemTools.ExtractMovableItems(tile, settings)) + { + // TODO: Change XYPos[0] to XPos + Console.WriteLine($"Extracted obj {obj.IdxAtObjectArray} from Tile {tile.EntryNum} XY {tile.XYPos[0]}:{tile.XYPos[1]}"); + objectsInLevel.Push(obj); + count++; + } + + tile.ReconstructBuffer(); + } + while (objectsInLevel.Count > 0) + { + leftTile.ObjectChain.Add(objectsInLevel.Pop()); + } + + leftTile.MoveObjectsToSameZLevel(); + + leftTile.ReconstructBuffer(); + return leftTile; + } + private TileInfo MoveSpecificItemsNearSpawn(ArkLoader arkFile, ItemRandomizationSettings settings, bool firstPass) + { + var lvl = arkFile.TileMapObjectsBlocks[0]; + var leftTile = lvl.TileInfos[159]; // X 31 Y 2 + Assert.True(leftTile.FirstObjIdx == 981); + + // Let's get some objects from specific tiles first + const int indexTile1 = (0xE86 - 542) / 4; // X26, Y12, bowl (142), axe(0), torch(145) + const int indexTile2 = (0x68E - 542) / 4; // X28, Y4, Jux Rune(241) + const int indexTile3 = (0x29B6 - 542) / 4; // X38, Y39, Plant(206), Bridge(356) -- shouldn't be moved! + + var Tile1 = lvl.TileInfos[indexTile1]; + var Tile2 = lvl.TileInfos[indexTile2]; + var Tile3 = lvl.TileInfos[indexTile3]; + + Assert.True(Tile1.XYPos[0] == 26 && Tile1.XYPos[1] == 12); + Assert.True(Tile2.XYPos[0] == 28 && Tile2.XYPos[1] == 4); + Assert.True(Tile3.XYPos[0] == 38 && Tile3.XYPos[1] == 39); + + if (firstPass) + Assert.True(Tile2.ObjectChain[0].ItemID == 241); + + var objs1 = ItemTools.ExtractMovableItems(Tile1, settings); // Len 3 + var objs2 = ItemTools.ExtractMovableItems(Tile2, settings); // Len 1 + var objs3 = ItemTools.ExtractMovableItems(Tile3, settings); // Len 1 + + if (firstPass) + { + Assert.True(objs1.Count == 3); + Assert.True(objs2.Count == 1); + Assert.True(objs3.Count == 1); + } + + Assert.True(Tile3.ObjectChain.Count == 1); // Bridge + Assert.True(Tile3.ObjectChain[0].next == 0); + Assert.True(Tile3.ObjectChain[0].ItemID == 356); + + List objs = new List(); + objs.AddRange(objs1); + objs.AddRange(objs2); + objs.AddRange(objs3); + + leftTile.ObjectChain.AppendItems(objs); + leftTile.MoveObjectsToSameZLevel(); + + // Needed? + Tile1.ReconstructBuffer(); + Tile2.ReconstructBuffer(); + Tile3.ReconstructBuffer(); + leftTile.ReconstructBuffer(); + + return leftTile; + } + + [Category("RequiresSettings")] + [Test] + public void TestMovingItemsNearSpawn() + { + var arkFile = new ArkLoader(Paths.UW_ArkOriginalPath); + var settings = new ItemRandomizationSettings(); + + var leftTile = MoveSpecificItemsNearSpawn(arkFile, settings, true); + arkFile.ReconstructBuffer(); + var path = UWRandomizerEditor.Utils.StdSaveBuffer(arkFile, Path.GetDirectoryName(arkFile.Path), "mod.ark"); + var newArkFile = new ArkLoader(path); + + Assert.True(newArkFile.TileMapObjectsBlocks[0].TileInfos[159].FirstObjIdx == leftTile.FirstObjIdx); + Assert.True(newArkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain.Count == leftTile.ObjectChain.Count); + Assert.True( + (from obj in newArkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain select obj.IdxAtObjectArray) + .SequenceEqual + (from obj in arkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain select obj.IdxAtObjectArray) + ); + + var leftTile2 = MoveSpecificItemsNearSpawn(newArkFile, settings, false); + + Assert.True(newArkFile.TileMapObjectsBlocks[0].TileInfos[159].FirstObjIdx == leftTile.FirstObjIdx); + Assert.True(newArkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain.Count == leftTile.ObjectChain.Count); + Assert.True( + (from obj in newArkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain select obj.IdxAtObjectArray) + .SequenceEqual + (from obj in arkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain select obj.IdxAtObjectArray) + ); + + } + + [Category("RequiresSettings")] + [Test] + public void TestMoving10ItemsNearSpawn() + { + var arkFile = new ArkLoader(Paths.UW_ArkOriginalPath); + var settings = new ItemRandomizationSettings(); + + var leftTile = Move10ItemsNearSpawn(arkFile, settings); + arkFile.ReconstructBuffer(); + var path = UWRandomizerEditor.Utils.StdSaveBuffer(arkFile, Path.GetDirectoryName(arkFile.Path), "mod.ark"); + var newArkFile = new ArkLoader(path); + + Assert.True(newArkFile.TileMapObjectsBlocks[0].TileInfos[159].FirstObjIdx == leftTile.FirstObjIdx); + Assert.True(newArkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain.Count == leftTile.ObjectChain.Count); + Assert.True( + (from obj in newArkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain select obj.IdxAtObjectArray) + .SequenceEqual + (from obj in arkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain select obj.IdxAtObjectArray) + ); + + var leftTile2 = Move10ItemsNearSpawn(newArkFile, settings); + + // TODO: When moving, this is finding the same tile and inverting the item sequence. + // TODO: also, the blood stain is at the wrong z height. Check + // Assert.True(newArkFile.TileMapObjectsBlocks[0].TileInfos[159].FirstObjIdx == leftTile.FirstObjIdx); + // Assert.True(newArkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain.Count == leftTile.ObjectChain.Count); + Assert.True( + (from obj in newArkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain select obj.IdxAtObjectArray).OrderBy(x=>x) + .SequenceEqual + ((from obj in arkFile.TileMapObjectsBlocks[0].TileInfos[159].ObjectChain select obj.IdxAtObjectArray).OrderBy(x=>x)) + ); + } + +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Tools/TestRemovingLockFromAllDoorsInArk.cs b/UWRandomizerUnitTests/Tools/TestRemovingLockFromAllDoorsInArk.cs new file mode 100644 index 0000000..4916fb4 --- /dev/null +++ b/UWRandomizerUnitTests/Tools/TestRemovingLockFromAllDoorsInArk.cs @@ -0,0 +1,108 @@ +using System; +using System.Configuration; +using System.IO; +using NUnit.Framework; +using UWRandomizer; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.Blocks; +using UWRandomizerEditor.LEVdotARK.GameObjects.Specifics; +using UWRandomizerTools; + +namespace RandomizerUnitTests; + +public class TestRemovingLockFromAllDoorsInArk +{ + [Test] + [Category("RequiresSettings")] + public void TestManualLockRemoval() + { + // var ArkOriginal = new ArkLoader(@"C:\Users\Karl\Desktop\UnderworldStudy\UW\DATA\LEV.ARK"); + // var ArkEditor = new ArkLoader(@"C:\Users\Karl\Desktop\UnderworldStudy\UW - Doors\DATA\LEV.ARK"); + var ArkOriginal = new ArkLoader(Paths.UW_ArkOriginalPath); + var ArkEditor = new ArkLoader(Path.Join(Paths.BasePath, @"UW - Doors\Data\Lev.ark")); + + var doorToUnlock = (Door) ArkOriginal.TileMapObjectsBlocks[0].AllGameObjects[1012]; + var doorUnlockedByEditor = (Door) ArkEditor.TileMapObjectsBlocks[0].AllGameObjects[1012]; + + Assert.True(doorToUnlock.HasLock()); + Assert.False(doorUnlockedByEditor.HasLock()); + Assert.False(doorToUnlock.Equals(doorUnlockedByEditor)); + + doorToUnlock.RemoveLock(); + Assert.False(doorToUnlock.HasLock()); + Assert.True(doorToUnlock.Equals(doorUnlockedByEditor)); + Assert.True(doorToUnlock.Equals(new Door(new byte[] {0x41, 0x01, 0x50, 0x6F, 0x28, 0x00, 0x00, 0x00}, 1012))); + + ArkOriginal.TileMapObjectsBlocks[0].ReconstructBuffer(); + ArkOriginal.ReconstructBuffer(); + var path = UWRandomizerEditor.Utils.StdSaveBuffer(ArkOriginal, Paths.BufferTestsPath, + "ark_withdoor1012unlocked.bin"); + + var ArkModified = new ArkLoader(path); + var doorUnlockedHere = (Door) ArkModified.TileMapObjectsBlocks[0].AllGameObjects[1012]; + Assert.False(doorUnlockedHere.HasLock()); + Assert.True(doorUnlockedHere.Equals(doorUnlockedByEditor)); + Assert.True( + doorUnlockedHere.Equals(new Door(new byte[] {0x41, 0x01, 0x50, 0x6F, 0x28, 0x00, 0x00, 0x00}, 1012))); + } + + // This test isn't working ATM because somehow an object isn't updating its buffer. But checking with UE and UWE, it's fine + [Test] + [Category("RequiresSettings")] + public void TestRemovingAllLocks() + { + // I'm testing here both the original and the "cleaned" version from UltimateEditor + // var ArkOriginal = new ArkLoader(@"C:\Users\Karl\Desktop\UnderworldStudy\UW\DATA\LEV.ARK"); + // var ArkOriginalToModify = new ArkLoader(@"C:\Users\Karl\Desktop\UnderworldStudy\UW\DATA\LEV.ARK"); + var ArkOriginal = new ArkLoader(Paths.UW_ArkOriginalPath); + var ArkOriginalToModify = new ArkLoader(Paths.UW_ArkOriginalPath); + + var ArkCleaned = new ArkLoader(Paths.UW_ArkCleanedPath); + var ArkCleanedToModify = new ArkLoader(Paths.UW_ArkCleanedPath); + + int countOfLocksRemovedOriginal = RandoTools.RemoveAllDoorReferencesToLocks(ArkOriginalToModify); + int countOfLocksRemovedCleaned = RandoTools.RemoveAllDoorReferencesToLocks(ArkCleanedToModify); + + Assert.True(countOfLocksRemovedOriginal > 0); + Assert.True(countOfLocksRemovedCleaned > 0); + + var pathOriginal = UWRandomizerEditor.Utils.StdSaveBuffer(ArkOriginalToModify, + Path.Join(Paths.BufferTestsPath, "RemovingDoors"), + "ark_nodoors.bin"); + var pathCleaned = UWRandomizerEditor.Utils.StdSaveBuffer(ArkCleanedToModify, + Path.Join(Paths.BufferTestsPath, "RemovingDoors"), + "ark_cleaned_nodoors.bin"); + + var ArkOriginalModified = new ArkLoader(pathOriginal); + var ArkCleanedModified = new ArkLoader(pathCleaned); + + // Seeing if the buffers were saved correctly and if the buffers were altered in any way + Assert.AreEqual(Utils.CompareTwoBuffers(ArkOriginalModified.Buffer, ArkOriginalToModify.Buffer).Item1, 0); + Assert.AreEqual(Utils.CompareTwoBuffers(ArkCleanedModified.Buffer, ArkCleanedToModify.Buffer).Item1, 0); + + var (diffCountsOriginal, diffPositionsOriginal) = + Utils.CompareTwoBuffers(ArkOriginal.Buffer, ArkOriginalModified.Buffer); + var (diffCountsCleaned, diffPositionsCleaned) = + Utils.CompareTwoBuffers(ArkCleaned.Buffer, ArkCleanedModified.Buffer); + + Assert.True(diffCountsOriginal > 0); + Assert.True(diffCountsCleaned > 0); + + // It modifies a short (2 bytes), so the number of differences should be at least the number of locks removed, + // or at most 2x the number of locks removed. + // TODO: Dunno why this assumption is wrong... It's currently less than I think it should be. Need to compare the buffers later + Console.WriteLine( + $"Original: Number of modified bytes: {diffCountsOriginal}; Number of modified doors: {countOfLocksRemovedOriginal}. Positions: " + + string.Join(",", diffPositionsOriginal)); + Console.WriteLine( + $"Cleaned: Number of modified bytes: {diffCountsCleaned}; Number of modified doors: {countOfLocksRemovedCleaned}. Positions: " + + string.Join(",", diffPositionsCleaned)); + // Assert.True((diffCountsOriginal >= countOfLocksRemovedOriginal) & + // (diffCountsOriginal <= countOfLocksRemovedOriginal * 2)); + // Assert.True((diffCountsCleaned >= countOfLocksRemovedCleaned) & + // (diffCountsCleaned <= countOfLocksRemovedCleaned * 2)); + + // File.Delete(pathOriginal); + // File.Delete(pathCleaned); + } +} \ No newline at end of file diff --git a/UWRandomizerUnitTests/Tools/TestWhatUltimateEditorFixes.cs b/UWRandomizerUnitTests/Tools/TestWhatUltimateEditorFixes.cs new file mode 100644 index 0000000..e2700f7 --- /dev/null +++ b/UWRandomizerUnitTests/Tools/TestWhatUltimateEditorFixes.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using NUnit.Framework; +using UWRandomizer; +using UWRandomizerEditor.LEVdotARK; +using UWRandomizerEditor.LEVdotARK.Blocks; +using UWRandomizerTools; + +namespace RandomizerUnitTests; + +public class TestWhatUWEditorFixes +{ + [Test] + [Category("RequiresSettings")] + [Category("FishyTests")] + public void DetectDifferencesAfterLoadingInUltimateEditor() + { + var ArkCleaned = new ArkLoader(Paths.UW_ArkCleanedPath); + var ArkCleanedNoDoors = new ArkLoader(Paths.UW_ArkCleanedPath); + var countLocksRemoved = + RandoTools.RemoveAllDoorReferencesToLocks(ArkCleanedNoDoors); // Assumes this is working correctly + + var cleanedNoDoorsPath = + UWRandomizerEditor.Utils.StdSaveBuffer(ArkCleanedNoDoors.Buffer, Paths.BufferTestsPath, + "cleaned_nodoors.ark"); + + // Every time I change the function to remove all locks, this has to be re-generated + while (!File.Exists(Paths.UWArkCleanedNoDoorsPath_Fixed)) + { + Console.WriteLine( + "cleaned_nodoors_fixed.ark in the buffer test folder not found. Please create it then press enter"); + Console.ReadLine(); + } + + var ArkCleanedNoDoorsFixed = new ArkLoader(Paths.UWArkCleanedNoDoorsPath_Fixed); + + var (countDiffs, positionDiffs) = + Utils.CompareTwoBuffers(ArkCleanedNoDoors.Buffer, ArkCleanedNoDoorsFixed.Buffer); + Console.WriteLine(string.Join("\n", positionDiffs)); + Assert.True(countDiffs == 0); + } +} \ No newline at end of file diff --git a/RandomizerUnitTests/RandomizerUnitTests.csproj b/UWRandomizerUnitTests/UWRandomizerUnitTests.csproj similarity index 68% rename from RandomizerUnitTests/RandomizerUnitTests.csproj rename to UWRandomizerUnitTests/UWRandomizerUnitTests.csproj index 64d1d58..95903c2 100644 --- a/RandomizerUnitTests/RandomizerUnitTests.csproj +++ b/UWRandomizerUnitTests/UWRandomizerUnitTests.csproj @@ -5,6 +5,8 @@ enable false + + RandomizerUnitTests @@ -16,6 +18,13 @@ + + + + + + Always + diff --git a/UWRandomizerUnitTests/Utils.cs b/UWRandomizerUnitTests/Utils.cs new file mode 100644 index 0000000..4c4c944 --- /dev/null +++ b/UWRandomizerUnitTests/Utils.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; + +namespace RandomizerUnitTests; + +public class Utils +{ + public const string OriginalLevArkSha256Hash = "87e9a6e5d249df273e1964f48ad910afee6f7e073165c00237dfb9a22ae3a121"; + + /// + /// Compares two buffers. Returns a tuple containing the number of differences, and the offset positions. + /// + /// Reference array + /// Array to compare against reference + /// Tuple with number of differences and offset positions + public static Tuple> CompareTwoBuffers(byte[] reference, byte[] other) + { + if (reference.Length != other.Length) + { + throw new ArgumentException( + $"Arrays of incompatible lengths: reference {reference.Length} != other {other.Length}"); + } + + int diffCount = 0; + List differenceOffsets = new List(); + for (int i = 0; i < reference.Length; i++) + { + if (reference[i] != other[i]) + { + differenceOffsets.Add(i); + diffCount += 1; + } + } + + return new Tuple>(diffCount, differenceOffsets); + } + + public static bool CheckEqualityOfSha256Hash(byte[] Buffer, string hashToCompare) + { + SHA256 mySHA256 = SHA256.Create(); + // Link is converting byte[] into a sequence of hex values, guaranteed to have 2 places (so 7->07) + var bufferHash = string.Join("", mySHA256.ComputeHash(Buffer).Select(x => x.ToString("x2"))); + return bufferHash == hashToCompare; + } + + private class OffsetDescriptor + { + public int Offset; + public string BlockName; + public int BlockNum; + + public OffsetDescriptor(string blockName, int offset, int blockNum) + { + Offset = offset; + BlockName = blockName; + BlockNum = blockNum; + } + } + + public static string DetermineBufferObjectFromAbsoluteOffset_OriginalArk(int offset) + { + // These constants are valid for the original lev.ark of UW1. The offsets come from the header + const int numOfLevels = 9; + + // Header + const int headerOffset = 0; + const int headerBlockPositionsOffset = 4; + const int headerSize = 542; + + // Block sizes + const int tileMapSize = 31752; + const int animInfoSize = 384; + const int textMapSize = 122; + + // Block offsets + const int tileMapsOffset = headerSize; // 542 + const int animInfosOffset = tileMapsOffset + numOfLevels * tileMapSize; // 286310 + const int textMapsOffset = animInfosOffset + numOfLevels * animInfoSize; // 289766 + const int variableWidthBlocksOffset = textMapsOffset + numOfLevels * textMapSize; // 290864 -- upper bound + + // Populating list of offsets and names + List blockOffsetsAndNames = new List(); + blockOffsetsAndNames.Add(new OffsetDescriptor("HeaderBlockCount", headerOffset, 0)); + blockOffsetsAndNames.Add(new OffsetDescriptor("HeaderBlockOffsets", headerBlockPositionsOffset, 0)); + for (int i = 0; i < numOfLevels; i++) + { + blockOffsetsAndNames.Add(new OffsetDescriptor($"TileMap", tileMapsOffset + i * tileMapSize, i)); + } + + for (int i = 0; i < numOfLevels; i++) + { + blockOffsetsAndNames.Add(new OffsetDescriptor($"animInfo", animInfosOffset + i * animInfoSize, i)); + } + + for (int i = 0; i < numOfLevels; i++) + { + blockOffsetsAndNames.Add(new OffsetDescriptor($"textMaps", textMapsOffset + i * textMapSize, i)); + } + + + OffsetDescriptor targetBlock = null; + // Find which block the input offset is referring to + foreach (var descriptor in blockOffsetsAndNames) + { + if ((offset - descriptor.Offset) > 0) // It's always increasing, so when it's <0, we passed the block + { + targetBlock = descriptor; + continue; + } + + break; + } + + if (targetBlock is null) + throw new ArgumentException("Invalid offset"); + + int relativeOffset = offset - targetBlock.Offset; + + // TODO: Make these descriptions a bit better, and calculate where, in each subblock, the byte is (e.g. static weapon 100) + return $"{targetBlock.BlockName}_byte{relativeOffset}"; + } +} \ No newline at end of file diff --git a/UWRandomizer/App.xaml b/UWRandomizerWPF/App.xaml similarity index 100% rename from UWRandomizer/App.xaml rename to UWRandomizerWPF/App.xaml diff --git a/UWRandomizer/App.xaml.cs b/UWRandomizerWPF/App.xaml.cs similarity index 100% rename from UWRandomizer/App.xaml.cs rename to UWRandomizerWPF/App.xaml.cs diff --git a/UWRandomizer/AssemblyInfo.cs b/UWRandomizerWPF/AssemblyInfo.cs similarity index 100% rename from UWRandomizer/AssemblyInfo.cs rename to UWRandomizerWPF/AssemblyInfo.cs diff --git a/UWRandomizerWPF/MainWindow.xaml b/UWRandomizerWPF/MainWindow.xaml new file mode 100644 index 0000000..66facba --- /dev/null +++ b/UWRandomizerWPF/MainWindow.xaml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + +