From d3bc367471b6285764326a237fc8bf65a5e3e88f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 07:45:46 +0000 Subject: [PATCH 1/4] Initial plan From 042e9f5a489b756a1c884d5e2653b2430659147d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 07:53:54 +0000 Subject: [PATCH 2/4] fix: flatten diagonal wall collision floor data Agent-Logs-Url: https://github.com/TombEngine/Tomb-Editor/sessions/acc3a340-27a1-4994-ab9d-5ac59068b078 Co-authored-by: Nickelony <20436882+Nickelony@users.noreply.github.com> --- .../DiagonalWallCollisionShapeTests.cs | 104 ++++++++++++++++++ .../Compilers/TombEngine/FloorData.cs | 77 +++++++++---- 2 files changed, 162 insertions(+), 19 deletions(-) create mode 100644 TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs diff --git a/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs b/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs new file mode 100644 index 0000000000..c0f342f805 --- /dev/null +++ b/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs @@ -0,0 +1,104 @@ +using System.Globalization; +using System.Reflection; +using TombLib.LevelData; +using TombLib.LevelData.Compilers.TombEngine; +using TombLib.LevelData.SectorEnums; +using TombLib.LevelData.SectorStructs; + +namespace TombLib.Test; + +[TestClass] +public class DiagonalWallCollisionShapeTests +{ + private static readonly Type RoomSectorShapeType = typeof(LevelCompilerTombEngine) + .GetNestedType("RoomSectorShape", BindingFlags.NonPublic)!; + + [DataTestMethod] + [DataRow(DiagonalSplit.XnZn, "HeightXpZp", "HeightXnZp", "HeightXpZn")] + [DataRow(DiagonalSplit.XnZp, "HeightXpZn", "HeightXnZn", "HeightXpZp")] + [DataRow(DiagonalSplit.XpZn, "HeightXnZp", "HeightXnZn", "HeightXpZp")] + [DataRow(DiagonalSplit.XpZp, "HeightXnZn", "HeightXnZp", "HeightXpZn")] + public void RoomSectorShape_FlattensDiagonalWallFloorCollision(DiagonalSplit diagonalSplit, string flatHeightField, string firstFlattenedField, string secondFlattenedField) + { + var sector = CreateDiagonalWallSector(diagonalSplit, isFloor: true); + var shape = CreateRoomSectorShape(sector, floor: true); + int flatHeight = GetField(shape, flatHeightField); + + Assert.AreEqual(flatHeight, GetField(shape, firstFlattenedField)); + Assert.AreEqual(flatHeight, GetField(shape, secondFlattenedField)); + Assert.AreEqual(0, GetField(shape, "DiagonalStep")); + } + + [DataTestMethod] + [DataRow(DiagonalSplit.XnZn, "HeightXpZp", "HeightXnZp", "HeightXpZn")] + [DataRow(DiagonalSplit.XnZp, "HeightXpZn", "HeightXnZn", "HeightXpZp")] + [DataRow(DiagonalSplit.XpZn, "HeightXnZp", "HeightXnZn", "HeightXpZp")] + [DataRow(DiagonalSplit.XpZp, "HeightXnZn", "HeightXnZp", "HeightXpZn")] + public void RoomSectorShape_FlattensDiagonalWallCeilingCollision(DiagonalSplit diagonalSplit, string flatHeightField, string firstFlattenedField, string secondFlattenedField) + { + var sector = CreateDiagonalWallSector(diagonalSplit, isFloor: false); + var shape = CreateRoomSectorShape(sector, floor: false); + int flatHeight = GetField(shape, flatHeightField); + + Assert.AreEqual(flatHeight, GetField(shape, firstFlattenedField)); + Assert.AreEqual(flatHeight, GetField(shape, secondFlattenedField)); + Assert.AreEqual(0, GetField(shape, "DiagonalStep")); + } + + [TestMethod] + public void RoomSectorShape_LeavesNonWallDiagonalSplitUntouched() + { + var sector = new Sector(0, 0) + { + Type = SectorType.Floor, + Floor = new SectorSurface + { + DiagonalSplit = DiagonalSplit.XpZn, + XnZn = 28, + XnZp = 4, + XpZn = 16, + XpZp = 40 + } + }; + + var shape = CreateRoomSectorShape(sector, floor: true); + + Assert.AreNotEqual(0, GetField(shape, "DiagonalStep")); + } + + private static Sector CreateDiagonalWallSector(DiagonalSplit diagonalSplit, bool isFloor) + { + var sector = new Sector(0, 0) + { + Type = SectorType.Wall + }; + + var surface = new SectorSurface + { + DiagonalSplit = diagonalSplit, + XnZn = 28, + XnZp = 4, + XpZn = 16, + XpZp = 40 + }; + + if (isFloor) + sector.Floor = surface; + else + sector.Ceiling = surface; + + return sector; + } + + private static object CreateRoomSectorShape(Sector sector, bool floor) + => Activator.CreateInstance(RoomSectorShapeType, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + binder: null, + args: new object[] { sector, floor, Room.RoomConnectionType.NoPortal, sector.IsAnyWall }, + culture: CultureInfo.InvariantCulture)!; + + private static T GetField(object instance, string fieldName) + => (T)RoomSectorShapeType + .GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)! + .GetValue(instance)!; +} diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/FloorData.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/FloorData.cs index edd86306ca..2272b45b57 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/FloorData.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/FloorData.cs @@ -388,22 +388,49 @@ private struct RoomSectorShape public readonly int HeightXpZp; public readonly int DiagonalStep; + private static void FlattenDiagonalWallPart(DiagonalSplit diagonalSplit, ref int heightXnZn, ref int heightXnZp, ref int heightXpZn, ref int heightXpZp) + { + // Diagonal walls only expose the flat triangle for QA / WS collision in TEN. + // The tilted surface on the hidden half is kept as editor-side geometry data as a hack + // until wall collision can use extra probes and represent both triangles independently. + switch (diagonalSplit) + { + case DiagonalSplit.XnZn: + heightXnZp = heightXpZp; + heightXpZn = heightXpZp; + break; + case DiagonalSplit.XnZp: + heightXnZn = heightXpZn; + heightXpZp = heightXpZn; + break; + case DiagonalSplit.XpZn: + heightXnZn = heightXnZp; + heightXpZp = heightXnZp; + break; + case DiagonalSplit.XpZp: + heightXnZp = heightXnZn; + heightXpZn = heightXnZn; + break; + } + } + public RoomSectorShape(Sector sector, bool floor, Room.RoomConnectionType portalType, bool wall) { var surface = floor ? sector.Floor : sector.Ceiling; + int heightXnZn = surface.XnZn; + int heightXpZn = surface.XpZn; + int heightXnZp = surface.XnZp; + int heightXpZp = surface.XpZp; + int diagonalStep; - HeightXnZn = surface.XnZn; - HeightXpZn = surface.XpZn; - HeightXnZp = surface.XnZp; - HeightXpZp = surface.XpZp; SplitDirectionIsXEqualsZ = surface.SplitDirectionIsXEqualsZWithDiagonalSplit; if (sector.HasGhostBlock && sector.GhostBlock.Valid) { - HeightXnZn += floor ? sector.GhostBlock.Floor.XnZn : sector.GhostBlock.Ceiling.XnZn; - HeightXpZn += floor ? sector.GhostBlock.Floor.XpZn : sector.GhostBlock.Ceiling.XpZn; - HeightXnZp += floor ? sector.GhostBlock.Floor.XnZp : sector.GhostBlock.Ceiling.XnZp; - HeightXpZp += floor ? sector.GhostBlock.Floor.XpZp : sector.GhostBlock.Ceiling.XpZp; + heightXnZn += floor ? sector.GhostBlock.Floor.XnZn : sector.GhostBlock.Ceiling.XnZn; + heightXpZn += floor ? sector.GhostBlock.Floor.XpZn : sector.GhostBlock.Ceiling.XpZn; + heightXnZp += floor ? sector.GhostBlock.Floor.XnZp : sector.GhostBlock.Ceiling.XnZp; + heightXpZp += floor ? sector.GhostBlock.Floor.XpZp : sector.GhostBlock.Ceiling.XpZp; } switch (portalType) @@ -443,35 +470,35 @@ public RoomSectorShape(Sector sector, bool floor, Room.RoomConnectionType portal switch (surface.DiagonalSplit) { case DiagonalSplit.None: - DiagonalStep = 0; + diagonalStep = 0; SplitWallFirst = wall; SplitWallSecond = wall; break; case DiagonalSplit.XnZn: - DiagonalStep = surface.XpZp - surface.XnZp; + diagonalStep = surface.XpZp - surface.XnZp; SplitWallFirst = wall; SplitWallSecond = false; break; case DiagonalSplit.XnZp: - DiagonalStep = surface.XpZn - surface.XpZp; + diagonalStep = surface.XpZn - surface.XpZp; SplitWallFirst = wall; SplitWallSecond = false; break; case DiagonalSplit.XpZn: - DiagonalStep = surface.XnZp - surface.XnZn; - HeightXnZn += DiagonalStep; - HeightXpZp += DiagonalStep; - DiagonalStep = -DiagonalStep; + diagonalStep = surface.XnZp - surface.XnZn; + heightXnZn += diagonalStep; + heightXpZp += diagonalStep; + diagonalStep = -diagonalStep; SplitWallFirst = false; SplitWallSecond = wall; break; case DiagonalSplit.XpZp: - DiagonalStep = surface.XnZn - surface.XpZn; - HeightXpZn += DiagonalStep; - HeightXnZp += DiagonalStep; - DiagonalStep = -DiagonalStep; + diagonalStep = surface.XnZn - surface.XpZn; + heightXpZn += diagonalStep; + heightXnZp += diagonalStep; + diagonalStep = -diagonalStep; SplitWallFirst = false; SplitWallSecond = wall; @@ -479,6 +506,18 @@ public RoomSectorShape(Sector sector, bool floor, Room.RoomConnectionType portal default: throw new ArgumentOutOfRangeException(); } + + if (wall && surface.DiagonalSplit != DiagonalSplit.None) + { + FlattenDiagonalWallPart(surface.DiagonalSplit, ref heightXnZn, ref heightXnZp, ref heightXpZn, ref heightXpZp); + diagonalStep = 0; + } + + HeightXnZn = heightXnZn; + HeightXpZn = heightXpZn; + HeightXnZp = heightXnZp; + HeightXpZp = heightXpZp; + DiagonalStep = diagonalStep; } public int Max => Math.Max(Math.Max(HeightXnZn, HeightXnZp), Math.Max(HeightXpZn, HeightXpZp)); From 785332a6328f241d3860470a97cd95607e7b39d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 07:55:18 +0000 Subject: [PATCH 3/4] test: clarify diagonal wall collision assertions Agent-Logs-Url: https://github.com/TombEngine/Tomb-Editor/sessions/acc3a340-27a1-4994-ab9d-5ac59068b078 Co-authored-by: Nickelony <20436882+Nickelony@users.noreply.github.com> --- .../DiagonalWallCollisionShapeTests.cs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs b/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs index c0f342f805..5492357e8e 100644 --- a/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs +++ b/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs @@ -14,14 +14,15 @@ public class DiagonalWallCollisionShapeTests .GetNestedType("RoomSectorShape", BindingFlags.NonPublic)!; [DataTestMethod] - [DataRow(DiagonalSplit.XnZn, "HeightXpZp", "HeightXnZp", "HeightXpZn")] - [DataRow(DiagonalSplit.XnZp, "HeightXpZn", "HeightXnZn", "HeightXpZp")] - [DataRow(DiagonalSplit.XpZn, "HeightXnZp", "HeightXnZn", "HeightXpZp")] - [DataRow(DiagonalSplit.XpZp, "HeightXnZn", "HeightXnZp", "HeightXpZn")] - public void RoomSectorShape_FlattensDiagonalWallFloorCollision(DiagonalSplit diagonalSplit, string flatHeightField, string firstFlattenedField, string secondFlattenedField) + [DataRow(DiagonalSplit.XnZn)] + [DataRow(DiagonalSplit.XnZp)] + [DataRow(DiagonalSplit.XpZn)] + [DataRow(DiagonalSplit.XpZp)] + public void RoomSectorShape_FlattensDiagonalWallFloorCollision(DiagonalSplit diagonalSplit) { var sector = CreateDiagonalWallSector(diagonalSplit, isFloor: true); var shape = CreateRoomSectorShape(sector, floor: true); + var (flatHeightField, firstFlattenedField, secondFlattenedField) = GetFlatTriangleFields(diagonalSplit); int flatHeight = GetField(shape, flatHeightField); Assert.AreEqual(flatHeight, GetField(shape, firstFlattenedField)); @@ -30,14 +31,15 @@ public void RoomSectorShape_FlattensDiagonalWallFloorCollision(DiagonalSplit dia } [DataTestMethod] - [DataRow(DiagonalSplit.XnZn, "HeightXpZp", "HeightXnZp", "HeightXpZn")] - [DataRow(DiagonalSplit.XnZp, "HeightXpZn", "HeightXnZn", "HeightXpZp")] - [DataRow(DiagonalSplit.XpZn, "HeightXnZp", "HeightXnZn", "HeightXpZp")] - [DataRow(DiagonalSplit.XpZp, "HeightXnZn", "HeightXnZp", "HeightXpZn")] - public void RoomSectorShape_FlattensDiagonalWallCeilingCollision(DiagonalSplit diagonalSplit, string flatHeightField, string firstFlattenedField, string secondFlattenedField) + [DataRow(DiagonalSplit.XnZn)] + [DataRow(DiagonalSplit.XnZp)] + [DataRow(DiagonalSplit.XpZn)] + [DataRow(DiagonalSplit.XpZp)] + public void RoomSectorShape_FlattensDiagonalWallCeilingCollision(DiagonalSplit diagonalSplit) { var sector = CreateDiagonalWallSector(diagonalSplit, isFloor: false); var shape = CreateRoomSectorShape(sector, floor: false); + var (flatHeightField, firstFlattenedField, secondFlattenedField) = GetFlatTriangleFields(diagonalSplit); int flatHeight = GetField(shape, flatHeightField); Assert.AreEqual(flatHeight, GetField(shape, firstFlattenedField)); @@ -90,6 +92,16 @@ private static Sector CreateDiagonalWallSector(DiagonalSplit diagonalSplit, bool return sector; } + private static (string FlatHeightField, string FirstFlattenedField, string SecondFlattenedField) GetFlatTriangleFields(DiagonalSplit diagonalSplit) + => diagonalSplit switch + { + DiagonalSplit.XnZn => ("HeightXpZp", "HeightXnZp", "HeightXpZn"), + DiagonalSplit.XnZp => ("HeightXpZn", "HeightXnZn", "HeightXpZp"), + DiagonalSplit.XpZn => ("HeightXnZp", "HeightXnZn", "HeightXpZp"), + DiagonalSplit.XpZp => ("HeightXnZn", "HeightXnZp", "HeightXpZn"), + _ => throw new ArgumentOutOfRangeException(nameof(diagonalSplit)) + }; + private static object CreateRoomSectorShape(Sector sector, bool floor) => Activator.CreateInstance(RoomSectorShapeType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, From 8bf2b2935ce6be5432b58b07e0d515d6f3ae21dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 08:12:25 +0000 Subject: [PATCH 4/4] fix: flatten classic diagonal wall collision Agent-Logs-Url: https://github.com/TombEngine/Tomb-Editor/sessions/42f7e77a-8155-49bf-a37d-1e8232bf44c3 Co-authored-by: Nickelony <20436882+Nickelony@users.noreply.github.com> --- .../DiagonalWallCollisionShapeTests.cs | 107 +++++++++++------- .../TombLib/LevelData/Compilers/FloorData.cs | 77 +++++++++---- 2 files changed, 127 insertions(+), 57 deletions(-) diff --git a/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs b/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs index 5492357e8e..04029c3b96 100644 --- a/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs +++ b/TombLib/TombLib.Test/DiagonalWallCollisionShapeTests.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Reflection; using TombLib.LevelData; +using TombLib.LevelData.Compilers; using TombLib.LevelData.Compilers.TombEngine; using TombLib.LevelData.SectorEnums; using TombLib.LevelData.SectorStructs; @@ -10,7 +11,9 @@ namespace TombLib.Test; [TestClass] public class DiagonalWallCollisionShapeTests { - private static readonly Type RoomSectorShapeType = typeof(LevelCompilerTombEngine) + private static readonly Type TombEngineRoomSectorShapeType = typeof(LevelCompilerTombEngine) + .GetNestedType("RoomSectorShape", BindingFlags.NonPublic)!; + private static readonly Type ClassicRoomSectorShapeType = typeof(LevelCompilerClassicTR) .GetNestedType("RoomSectorShape", BindingFlags.NonPublic)!; [DataTestMethod] @@ -18,16 +21,9 @@ public class DiagonalWallCollisionShapeTests [DataRow(DiagonalSplit.XnZp)] [DataRow(DiagonalSplit.XpZn)] [DataRow(DiagonalSplit.XpZp)] - public void RoomSectorShape_FlattensDiagonalWallFloorCollision(DiagonalSplit diagonalSplit) + public void TombEngineRoomSectorShape_FlattensDiagonalWallFloorCollision(DiagonalSplit diagonalSplit) { - var sector = CreateDiagonalWallSector(diagonalSplit, isFloor: true); - var shape = CreateRoomSectorShape(sector, floor: true); - var (flatHeightField, firstFlattenedField, secondFlattenedField) = GetFlatTriangleFields(diagonalSplit); - int flatHeight = GetField(shape, flatHeightField); - - Assert.AreEqual(flatHeight, GetField(shape, firstFlattenedField)); - Assert.AreEqual(flatHeight, GetField(shape, secondFlattenedField)); - Assert.AreEqual(0, GetField(shape, "DiagonalStep")); + AssertDiagonalWallCollisionIsFlattened(TombEngineRoomSectorShapeType, diagonalSplit, isFloor: true); } [DataTestMethod] @@ -35,37 +31,41 @@ public void RoomSectorShape_FlattensDiagonalWallFloorCollision(DiagonalSplit dia [DataRow(DiagonalSplit.XnZp)] [DataRow(DiagonalSplit.XpZn)] [DataRow(DiagonalSplit.XpZp)] - public void RoomSectorShape_FlattensDiagonalWallCeilingCollision(DiagonalSplit diagonalSplit) + public void TombEngineRoomSectorShape_FlattensDiagonalWallCeilingCollision(DiagonalSplit diagonalSplit) { - var sector = CreateDiagonalWallSector(diagonalSplit, isFloor: false); - var shape = CreateRoomSectorShape(sector, floor: false); - var (flatHeightField, firstFlattenedField, secondFlattenedField) = GetFlatTriangleFields(diagonalSplit); - int flatHeight = GetField(shape, flatHeightField); - - Assert.AreEqual(flatHeight, GetField(shape, firstFlattenedField)); - Assert.AreEqual(flatHeight, GetField(shape, secondFlattenedField)); - Assert.AreEqual(0, GetField(shape, "DiagonalStep")); + AssertDiagonalWallCollisionIsFlattened(TombEngineRoomSectorShapeType, diagonalSplit, isFloor: false); } [TestMethod] - public void RoomSectorShape_LeavesNonWallDiagonalSplitUntouched() + public void TombEngineRoomSectorShape_LeavesNonWallDiagonalSplitUntouched() { - var sector = new Sector(0, 0) - { - Type = SectorType.Floor, - Floor = new SectorSurface - { - DiagonalSplit = DiagonalSplit.XpZn, - XnZn = 28, - XnZp = 4, - XpZn = 16, - XpZp = 40 - } - }; + AssertNonWallDiagonalCollisionIsUntouched(TombEngineRoomSectorShapeType); + } + + [DataTestMethod] + [DataRow(DiagonalSplit.XnZn)] + [DataRow(DiagonalSplit.XnZp)] + [DataRow(DiagonalSplit.XpZn)] + [DataRow(DiagonalSplit.XpZp)] + public void ClassicRoomSectorShape_FlattensDiagonalWallFloorCollision(DiagonalSplit diagonalSplit) + { + AssertDiagonalWallCollisionIsFlattened(ClassicRoomSectorShapeType, diagonalSplit, isFloor: true); + } - var shape = CreateRoomSectorShape(sector, floor: true); + [DataTestMethod] + [DataRow(DiagonalSplit.XnZn)] + [DataRow(DiagonalSplit.XnZp)] + [DataRow(DiagonalSplit.XpZn)] + [DataRow(DiagonalSplit.XpZp)] + public void ClassicRoomSectorShape_FlattensDiagonalWallCeilingCollision(DiagonalSplit diagonalSplit) + { + AssertDiagonalWallCollisionIsFlattened(ClassicRoomSectorShapeType, diagonalSplit, isFloor: false); + } - Assert.AreNotEqual(0, GetField(shape, "DiagonalStep")); + [TestMethod] + public void ClassicRoomSectorShape_LeavesNonWallDiagonalSplitUntouched() + { + AssertNonWallDiagonalCollisionIsUntouched(ClassicRoomSectorShapeType); } private static Sector CreateDiagonalWallSector(DiagonalSplit diagonalSplit, bool isFloor) @@ -102,15 +102,46 @@ private static (string FlatHeightField, string FirstFlattenedField, string Secon _ => throw new ArgumentOutOfRangeException(nameof(diagonalSplit)) }; - private static object CreateRoomSectorShape(Sector sector, bool floor) - => Activator.CreateInstance(RoomSectorShapeType, + private static void AssertDiagonalWallCollisionIsFlattened(Type roomSectorShapeType, DiagonalSplit diagonalSplit, bool isFloor) + { + var sector = CreateDiagonalWallSector(diagonalSplit, isFloor); + var shape = CreateRoomSectorShape(roomSectorShapeType, sector, isFloor); + var (flatHeightField, firstFlattenedField, secondFlattenedField) = GetFlatTriangleFields(diagonalSplit); + int flatHeight = GetField(roomSectorShapeType, shape, flatHeightField); + + Assert.AreEqual(flatHeight, GetField(roomSectorShapeType, shape, firstFlattenedField)); + Assert.AreEqual(flatHeight, GetField(roomSectorShapeType, shape, secondFlattenedField)); + Assert.AreEqual(0, GetField(roomSectorShapeType, shape, "DiagonalStep")); + } + + private static void AssertNonWallDiagonalCollisionIsUntouched(Type roomSectorShapeType) + { + var sector = new Sector(0, 0) + { + Type = SectorType.Floor, + Floor = new SectorSurface + { + DiagonalSplit = DiagonalSplit.XpZn, + XnZn = 28, + XnZp = 4, + XpZn = 16, + XpZp = 40 + } + }; + + var shape = CreateRoomSectorShape(roomSectorShapeType, sector, floor: true); + Assert.AreNotEqual(0, GetField(roomSectorShapeType, shape, "DiagonalStep")); + } + + private static object CreateRoomSectorShape(Type roomSectorShapeType, Sector sector, bool floor) + => Activator.CreateInstance(roomSectorShapeType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, binder: null, args: new object[] { sector, floor, Room.RoomConnectionType.NoPortal, sector.IsAnyWall }, culture: CultureInfo.InvariantCulture)!; - private static T GetField(object instance, string fieldName) - => (T)RoomSectorShapeType + private static T GetField(Type roomSectorShapeType, object instance, string fieldName) + => (T)roomSectorShapeType .GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)! .GetValue(instance)!; } diff --git a/TombLib/TombLib/LevelData/Compilers/FloorData.cs b/TombLib/TombLib/LevelData/Compilers/FloorData.cs index 80d1dde455..ab306be5a7 100644 --- a/TombLib/TombLib/LevelData/Compilers/FloorData.cs +++ b/TombLib/TombLib/LevelData/Compilers/FloorData.cs @@ -807,24 +807,51 @@ private struct RoomSectorShape public readonly int HeightXpZp; public readonly int DiagonalStep; + private static void FlattenDiagonalWallPart(DiagonalSplit diagonalSplit, ref int heightXnZn, ref int heightXnZp, ref int heightXpZn, ref int heightXpZp) + { + // Classic floordata uses the same diagonal wall collision hack as TEN: + // only the exposed flat triangle should contribute to QA / WS collision, + // while the hidden tilted half remains editor-side geometry data for now. + switch (diagonalSplit) + { + case DiagonalSplit.XnZn: + heightXnZp = heightXpZp; + heightXpZn = heightXpZp; + break; + case DiagonalSplit.XnZp: + heightXnZn = heightXpZn; + heightXpZp = heightXpZn; + break; + case DiagonalSplit.XpZn: + heightXnZn = heightXnZp; + heightXpZp = heightXnZp; + break; + case DiagonalSplit.XpZp: + heightXnZp = heightXnZn; + heightXpZn = heightXnZn; + break; + } + } + public RoomSectorShape(Sector sector, bool floor, Room.RoomConnectionType portalType, bool wall) { var surface = floor ? sector.Floor.WorldToClicks() : sector.Ceiling.WorldToClicks(); + int heightXnZn = surface.XnZn; + int heightXpZn = surface.XpZn; + int heightXnZp = surface.XnZp; + int heightXpZp = surface.XpZp; + int diagonalStep; - HeightXnZn = surface.XnZn; - HeightXpZn = surface.XpZn; - HeightXnZp = surface.XnZp; - HeightXpZp = surface.XpZp; SplitDirectionIsXEqualsZ = surface.SplitDirectionIsXEqualsZWithDiagonalSplit; if (sector.HasGhostBlock && sector.GhostBlock.Valid) { var ghostBlockSurface = floor ? sector.GhostBlock.Floor.WorldToClicks() : sector.GhostBlock.Ceiling.WorldToClicks(); - HeightXnZn += ghostBlockSurface.XnZn; - HeightXpZn += ghostBlockSurface.XpZn; - HeightXnZp += ghostBlockSurface.XnZp; - HeightXpZp += ghostBlockSurface.XpZp; + heightXnZn += ghostBlockSurface.XnZn; + heightXpZn += ghostBlockSurface.XpZn; + heightXnZp += ghostBlockSurface.XnZp; + heightXpZp += ghostBlockSurface.XpZp; } switch (portalType) @@ -864,35 +891,35 @@ public RoomSectorShape(Sector sector, bool floor, Room.RoomConnectionType portal switch (surface.DiagonalSplit) { case DiagonalSplit.None: - DiagonalStep = 0; + diagonalStep = 0; SplitWallFirst = wall; SplitWallSecond = wall; break; case DiagonalSplit.XnZn: - DiagonalStep = surface.XpZp - surface.XnZp; + diagonalStep = surface.XpZp - surface.XnZp; SplitWallFirst = wall; SplitWallSecond = false; break; case DiagonalSplit.XnZp: - DiagonalStep = surface.XpZn - surface.XpZp; + diagonalStep = surface.XpZn - surface.XpZp; SplitWallFirst = wall; SplitWallSecond = false; break; case DiagonalSplit.XpZn: - DiagonalStep = surface.XnZp - surface.XnZn; - HeightXnZn += DiagonalStep; - HeightXpZp += DiagonalStep; - DiagonalStep = -DiagonalStep; + diagonalStep = surface.XnZp - surface.XnZn; + heightXnZn += diagonalStep; + heightXpZp += diagonalStep; + diagonalStep = -diagonalStep; SplitWallFirst = false; SplitWallSecond = wall; break; case DiagonalSplit.XpZp: - DiagonalStep = surface.XnZn - surface.XpZn; - HeightXpZn += DiagonalStep; - HeightXnZp += DiagonalStep; - DiagonalStep = -DiagonalStep; + diagonalStep = surface.XnZn - surface.XpZn; + heightXpZn += diagonalStep; + heightXnZp += diagonalStep; + diagonalStep = -diagonalStep; SplitWallFirst = false; SplitWallSecond = wall; @@ -900,6 +927,18 @@ public RoomSectorShape(Sector sector, bool floor, Room.RoomConnectionType portal default: throw new ArgumentOutOfRangeException(); } + + if (wall && surface.DiagonalSplit != DiagonalSplit.None) + { + FlattenDiagonalWallPart(surface.DiagonalSplit, ref heightXnZn, ref heightXnZp, ref heightXpZn, ref heightXpZp); + diagonalStep = 0; + } + + HeightXnZn = heightXnZn; + HeightXpZn = heightXpZn; + HeightXnZp = heightXnZp; + HeightXpZp = heightXpZp; + DiagonalStep = diagonalStep; } public int Max => Math.Max(Math.Max(HeightXnZn, HeightXnZp), Math.Max(HeightXpZn, HeightXpZp));