diff --git a/TLM/CSModLib.Test/CSModLib.Test.csproj b/TLM/CSModLib.Test/CSModLib.Test.csproj new file mode 100644 index 000000000..aa436a36f --- /dev/null +++ b/TLM/CSModLib.Test/CSModLib.Test.csproj @@ -0,0 +1,109 @@ + + + + + + Debug + AnyCPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577} + Library + Properties + CSModLib.Test + CSModLib.Test + v4.8 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + ~/Library/Application Support/Steam/ + $(ProgramFiles)\Steam + $(Registry:HKEY_CURRENT_USER\Software\Valve\Steam@SteamPath) + $(SteamPath)\steamapps\common\Cities_Skylines + $(CSPath)\Cities_Data\Managed + ..\dependencies + $(MSBuildExtensionsPath64)\..\Unity\ + ..\Unity\ + + + + $(MangedDLLPath)\Assembly-CSharp.dll + True + + + $(MangedDLLPath)\ColossalManaged.dll + True + + + $(MangedDLLPath)\ICities.dll + True + + + $(MangedDLLPath)\UnityEngine.dll + True + + + $(MangedDLLPath)\UnityEngine.Networking.dll + True + + + $(MangedDLLPath)\UnityEngine.UI.dll + True + + + ..\packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + + + + + + + + + {5d3b2f48-3f16-424d-8c87-d59cdf420ea9} + CSModLib + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/TLM/CSModLib.Test/GameObjects/ExtNetInfoTest.cs b/TLM/CSModLib.Test/GameObjects/ExtNetInfoTest.cs new file mode 100644 index 000000000..b0a053eca --- /dev/null +++ b/TLM/CSModLib.Test/GameObjects/ExtNetInfoTest.cs @@ -0,0 +1,692 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Linq; +using CSModLib.GameObjects; +using static CSModLib.GameObjects.ExtNetInfo; + +namespace CSModLib.Test.GameObjects { + [TestClass] + public class ExtNetInfoTest { + + [TestMethod] + public void TestTwoLane() { + + var lanes = new[] { + PedestrianLane(-6.5f, 3f), + PedestrianLane(6.5f, 3f), + ParkingLane(-4f, true), + ParkingLane(4f, false), + CarLane(-1.5f, true), + CarLane(1.5f, false), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.TwoWay, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 2, 4, 5, 3, 1); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(2, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward, 4); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.OuterForward, 5); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.None); + VerifyLane(extNetInfo, 1, ExtLaneFlags.None); + VerifyLane(extNetInfo, 2, ExtLaneFlags.None); + VerifyLane(extNetInfo, 3, ExtLaneFlags.None); + VerifyLane(extNetInfo, 4, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 5, ExtLaneFlags.OuterForward); + } + + [TestMethod] + public void TestTwoLaneOneWay() { + + var lanes = new[] { + PedestrianLane(-6.5f, 3f), + PedestrianLane(6.5f, 3f), + ParkingLane(-4f, false), + ParkingLane(4f, false), + CarLane(-1.5f, false), + CarLane(1.5f, false), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.OneWay, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 2, 4, 5, 3, 1); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(1, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterForward, 4, 5); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.None); + VerifyLane(extNetInfo, 1, ExtLaneFlags.None); + VerifyLane(extNetInfo, 2, ExtLaneFlags.None); + VerifyLane(extNetInfo, 3, ExtLaneFlags.None); + VerifyLane(extNetInfo, 4, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 5, ExtLaneFlags.OuterForward); + } + + [TestMethod] + public void TestFourLane() { + + var lanes = new[] { + PedestrianLane(-13.5f, 5f), + PedestrianLane(13.5f, 5f), + ParkingLane(-10f, true), + ParkingLane(10f, false), + CarLane(-7.5f, true), + CarLane(7.5f, false), + CarLane(-4.5f, true), + CarLane(4.5f, false), + RaisedMedianLane(0f), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.TwoWay, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 2, 4, 6, 8, 7, 5, 3, 1); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(2, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward, 4, 6); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.OuterForward, 7, 5); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.None); + VerifyLane(extNetInfo, 1, ExtLaneFlags.None); + VerifyLane(extNetInfo, 2, ExtLaneFlags.None); + VerifyLane(extNetInfo, 3, ExtLaneFlags.None); + VerifyLane(extNetInfo, 4, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 5, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 6, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 7, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 8, ExtLaneFlags.None); + } + + [TestMethod] + public void TestFourLaneInverted() { + + var lanes = new[] { + PedestrianLane(-13.5f, 5f), + PedestrianLane(13.5f, 5f), + ParkingLane(-10f, false), + ParkingLane(10f, true), + CarLane(-7.5f, false), + CarLane(7.5f, true), + CarLane(-4.5f, false), + CarLane(4.5f, true), + RaisedMedianLane(0f), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Inverted, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 2, 4, 6, 8, 7, 5, 3, 1); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(2, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterForward, 4, 6); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.OuterBackward, 7, 5); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.None); + VerifyLane(extNetInfo, 1, ExtLaneFlags.None); + VerifyLane(extNetInfo, 2, ExtLaneFlags.None); + VerifyLane(extNetInfo, 3, ExtLaneFlags.None); + VerifyLane(extNetInfo, 4, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 5, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 6, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 7, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 8, ExtLaneFlags.None); + } + + [TestMethod] + public void TestCFI() { + + var lanes = new[] { + CarLane(0f, true), + ThruCarLane(3f, false), + ThruCarLane(6f, false), + ThruCarLane(9f, true), + ThruCarLane(12f, true), + CarLane(15f, false), + CarLane(18f, false), + CarLane(21f, false), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4, 5, 6, 7); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(4, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward, 0); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.DisplacedInnerForward | ExtLaneFlags.AllowCFI, 1, 2); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.DisplacedInnerBackward | ExtLaneFlags.AllowCFI, 3, 4); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.OuterForward, 5, 6, 7); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 1, ExtLaneFlags.DisplacedInnerForward | ExtLaneFlags.AllowCFI); + VerifyLane(extNetInfo, 2, ExtLaneFlags.DisplacedInnerForward | ExtLaneFlags.AllowCFI); + VerifyLane(extNetInfo, 3, ExtLaneFlags.DisplacedInnerBackward | ExtLaneFlags.AllowCFI); + VerifyLane(extNetInfo, 4, ExtLaneFlags.DisplacedInnerBackward | ExtLaneFlags.AllowCFI); + VerifyLane(extNetInfo, 5, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 6, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 7, ExtLaneFlags.OuterForward); + } + + [TestMethod] + public void TestUncontrolledDisplaced() { + + var lanes = new[] { + CarLane(0f, true), + CarLane(3f, false), + CarLane(6f, true), + CarLane(9f, false), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(4, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward, 0); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.DisplacedInnerForward | ExtLaneFlags.ForbidControlledLanes, 1); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.DisplacedInnerBackward | ExtLaneFlags.ForbidControlledLanes, 2); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.OuterForward, 3); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 1, ExtLaneFlags.DisplacedInnerForward | ExtLaneFlags.ForbidControlledLanes); + VerifyLane(extNetInfo, 2, ExtLaneFlags.DisplacedInnerBackward | ExtLaneFlags.ForbidControlledLanes); + VerifyLane(extNetInfo, 3, ExtLaneFlags.OuterForward); + } + + [TestMethod] + public void TestForwardTurnaround() { + + var lanes = new[] { + CarLane(0f, false), + CarLane(3f, true), + CarLane(6f, true), + CarLane(9f, false), + CarLane(12f, false), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(3, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.DisplacedOuterForward, 0); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.OuterBackward, 1, 2); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.OuterForward, 3, 4); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.DisplacedOuterForward); + VerifyLane(extNetInfo, 1, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 2, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 3, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 4, ExtLaneFlags.OuterForward); + } + + [TestMethod] + public void TestBackwardTurnaround() { + + var lanes = new[] { + CarLane(0f, true), + CarLane(3f, true), + CarLane(6f, false), + CarLane(9f, false), + CarLane(12f, true), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(3, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward, 0, 1); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.OuterForward, 2, 3); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.DisplacedOuterBackward, 4); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 1, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 2, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 3, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 4, ExtLaneFlags.DisplacedOuterBackward); + } + + [TestMethod] + public void TestDualTurnaround() { + + var lanes = new[] { + CarLane(0f, false), + CarLane(3f, true), + CarLane(6f, true), + CarLane(9f, false), + CarLane(12f, false), + CarLane(15f, true), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4, 5); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(4, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.DisplacedOuterForward, 0); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.OuterBackward, 1, 2); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.OuterForward, 3, 4); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.DisplacedOuterBackward, 5); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.DisplacedOuterForward); + VerifyLane(extNetInfo, 1, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 2, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 3, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 4, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 5, ExtLaneFlags.DisplacedOuterBackward); + } + + + [TestMethod] + public void TestDoubleDualTurnaround() { + + var lanes = new[] { + CarLane(0f, false), + CarLane(3f, false), + CarLane(6f, true), + CarLane(9f, true), + CarLane(12f, false), + CarLane(15f, false), + CarLane(18f, true), + CarLane(21f, true), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4, 5, 6, 7); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(4, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.DisplacedOuterForward, 0, 1); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.OuterBackward, 2, 3); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.OuterForward, 4, 5); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.DisplacedOuterBackward, 6, 7); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.DisplacedOuterForward); + VerifyLane(extNetInfo, 1, ExtLaneFlags.DisplacedOuterForward); + VerifyLane(extNetInfo, 2, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 3, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 4, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 5, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 6, ExtLaneFlags.DisplacedOuterBackward); + VerifyLane(extNetInfo, 7, ExtLaneFlags.DisplacedOuterBackward); + } + + [TestMethod] + public void TestInnerDisplacedBusLanes() { + + var lanes = new[] { + CarLane(0f, true), + CarLane(3f, true), + ThruBusLane(6f, false), + ThruBusLane(9f, false), + PedestrianLane(13.5f, 6f), + ThruBusLane(18f, true), + ThruBusLane(21f, true), + CarLane(24f, false), + CarLane(27f, false), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4, 5, 6, 7, 8); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(4, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward, 0, 1); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.DisplacedInnerForward, 2, 3); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.DisplacedInnerBackward, 5, 6); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.OuterForward, 7, 8); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 1, ExtLaneFlags.OuterBackward); + VerifyLane(extNetInfo, 2, ExtLaneFlags.DisplacedInnerForward); + VerifyLane(extNetInfo, 3, ExtLaneFlags.DisplacedInnerForward); + VerifyLane(extNetInfo, 4, ExtLaneFlags.None); + VerifyLane(extNetInfo, 5, ExtLaneFlags.DisplacedInnerBackward); + VerifyLane(extNetInfo, 6, ExtLaneFlags.DisplacedInnerBackward); + VerifyLane(extNetInfo, 7, ExtLaneFlags.OuterForward); + VerifyLane(extNetInfo, 8, ExtLaneFlags.OuterForward); + } + + [TestMethod] + public void TestFourCarriageway() { + + var lanes = new[] { + PedestrianLane(0f, 3f), + ParkingLane(3f, true, 3f), + CarLane(6f, true), + PedestrianLane(9f, 3f), + ThruCarLane(12f, true), + ThruCarLane(15f, true), + RaisedMedianLane(18f), + ThruCarLane(21f, false), + ThruCarLane(24f, false), + PedestrianLane(27f, 3f), + CarLane(30f, false), + ParkingLane(33f, false, 3f), + PedestrianLane(36f, 3f), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.TwoWay, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(4, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane, 2); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane, 4, 5); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane, 7, 8); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane, 10); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.None); + VerifyLane(extNetInfo, 1, ExtLaneFlags.None); + VerifyLane(extNetInfo, 2, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 3, ExtLaneFlags.None); + VerifyLane(extNetInfo, 4, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 5, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 6, ExtLaneFlags.None); + VerifyLane(extNetInfo, 7, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 8, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 9, ExtLaneFlags.None); + VerifyLane(extNetInfo, 10, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 11, ExtLaneFlags.None); + VerifyLane(extNetInfo, 12, ExtLaneFlags.None); + } + + [TestMethod] + public void TestThreeCarriageway() { + + var lanes = new[] { + PedestrianLane(0f, 3f), + ParkingLane(3f, true, 3f), + CarLane(6f, true), + PedestrianLane(9f, 3f), + ThruCarLane(12f, true), + ThruCarLane(15f, true), + ThruCarLane(18f, false), + ThruCarLane(21f, false), + PedestrianLane(24f, 3f), + CarLane(27f, false), + ParkingLane(30f, false, 3f), + PedestrianLane(33f, 3f), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.TwoWay, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(4, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane, 2); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane, 4, 5); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane, 6, 7); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane, 9); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.None); + VerifyLane(extNetInfo, 1, ExtLaneFlags.None); + VerifyLane(extNetInfo, 2, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 3, ExtLaneFlags.None); + VerifyLane(extNetInfo, 4, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 5, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 6, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 7, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 8, ExtLaneFlags.None); + VerifyLane(extNetInfo, 9, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 10, ExtLaneFlags.None); + VerifyLane(extNetInfo, 11, ExtLaneFlags.None); + } + + [TestMethod] + public void TestSixCarriageway() { + + var lanes = new[] { + CarLane(0f, true), + CarLane(3f, true), + RaisedMedianLane(6f), + ThruCarLane(9f, true), + ThruCarLane(12f, true), + ThruBusLane(15f, false), + ThruBusLane(18f, false), + PedestrianLane(22.5f, 6f), + ThruBusLane(27f, true), + ThruBusLane(30f, true), + ThruCarLane(33f, false), + ThruCarLane(36f, false), + RaisedMedianLane(39f), + CarLane(42f, false), + CarLane(45f, false), + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(6, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane, 0, 1); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane, 3, 4); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.DisplacedInnerForward, 5, 6); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.DisplacedInnerBackward, 8, 9); + VerifyGroup(extNetInfo, 4, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane, 10, 11); + VerifyGroup(extNetInfo, 5, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane, 13, 14); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 1, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 2, ExtLaneFlags.None); + VerifyLane(extNetInfo, 3, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 4, ExtLaneFlags.InnerBackward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 5, ExtLaneFlags.DisplacedInnerForward); + VerifyLane(extNetInfo, 6, ExtLaneFlags.DisplacedInnerForward); + VerifyLane(extNetInfo, 7, ExtLaneFlags.None); + VerifyLane(extNetInfo, 8, ExtLaneFlags.DisplacedInnerBackward); + VerifyLane(extNetInfo, 9, ExtLaneFlags.DisplacedInnerBackward); + VerifyLane(extNetInfo, 11, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 10, ExtLaneFlags.InnerForward | ExtLaneFlags.AllowExpressLane); + VerifyLane(extNetInfo, 12, ExtLaneFlags.None); + VerifyLane(extNetInfo, 13, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 14, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane); + } + + [TestMethod] + public void TestLargeCargoRoad() { + + var lanes = new[] { + FlushMedianLane(-16.4f), // 0 + FlushMedianLane(16.4f), // 1 + FlushMedianLane(5.5f), // 2 + PedestrianLane(-27f, 2f), // 3 + FlushMedianLane(-5.5f), // 4 + PedestrianLane(27f, 2f), // 5 + CarLane(-11.1f, true), // 6 + CarLane(11.1f, false), // 7 + CarLane(-22.5f, true), // 8 + CarLane(22.5f, false), // 9 + CarLane(8.1f, true), // 10 + CarLane(-8.1f, false), // 11 + }; + + var extNetInfo = new ExtNetInfo(lanes); + + Assert.AreEqual(LaneConfiguration.Complex, extNetInfo.m_roadLaneConfiguration); + Assert.AreEqual(lanes.Length, extNetInfo.m_extLanes.Length); + VerifySequence(extNetInfo.m_sortedLanes, 3, 8, 0, 6, 11, 4, 2, 10, 7, 1, 9, 5); + + VerifyAggregations(extNetInfo); + + Assert.AreEqual(6, extNetInfo.m_laneGroups.Length); + VerifyGroup(extNetInfo, 0, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane, 8); + VerifyGroup(extNetInfo, 1, ExtLaneFlags.InnerBackward | ExtLaneFlags.ForbidControlledLanes, 6); + VerifyGroup(extNetInfo, 2, ExtLaneFlags.DisplacedInnerForward | ExtLaneFlags.ForbidControlledLanes, 11); + VerifyGroup(extNetInfo, 3, ExtLaneFlags.DisplacedInnerBackward | ExtLaneFlags.ForbidControlledLanes, 10); + VerifyGroup(extNetInfo, 4, ExtLaneFlags.InnerForward | ExtLaneFlags.ForbidControlledLanes, 7); + VerifyGroup(extNetInfo, 5, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane, 9); + + VerifyLane(extNetInfo, 0, ExtLaneFlags.None); + VerifyLane(extNetInfo, 1, ExtLaneFlags.None); + VerifyLane(extNetInfo, 2, ExtLaneFlags.None); + VerifyLane(extNetInfo, 3, ExtLaneFlags.None); + VerifyLane(extNetInfo, 4, ExtLaneFlags.None); + VerifyLane(extNetInfo, 5, ExtLaneFlags.None); + VerifyLane(extNetInfo, 6, ExtLaneFlags.InnerBackward | ExtLaneFlags.ForbidControlledLanes); + VerifyLane(extNetInfo, 7, ExtLaneFlags.InnerForward | ExtLaneFlags.ForbidControlledLanes); + VerifyLane(extNetInfo, 8, ExtLaneFlags.OuterBackward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 9, ExtLaneFlags.OuterForward | ExtLaneFlags.AllowServiceLane); + VerifyLane(extNetInfo, 10, ExtLaneFlags.DisplacedInnerBackward | ExtLaneFlags.ForbidControlledLanes); + VerifyLane(extNetInfo, 11, ExtLaneFlags.DisplacedInnerForward | ExtLaneFlags.ForbidControlledLanes); + } + + private void VerifyAggregations(ExtNetInfo extNetInfo) { + + var expectedForwardFlags = extNetInfo.m_extLanes.Select(l => l.m_extFlags).Where(f => (f & ExtLaneFlags.ForwardGroup) != 0).DefaultIfEmpty().Aggregate((x, y) => x | y); + var expectedBackwardFlags = extNetInfo.m_extLanes.Select(l => l.m_extFlags).Where(f => (f & ExtLaneFlags.BackwardGroup) != 0).DefaultIfEmpty().Aggregate((x, y) => x | y); + var expectedFlags = extNetInfo.m_extLanes.Select(l => l.m_extFlags).DefaultIfEmpty().Aggregate((x, y) => x | y); + + Assert.AreEqual(expectedForwardFlags, extNetInfo.m_forwardExtLaneFlags); + Assert.AreEqual(expectedBackwardFlags, extNetInfo.m_backwardExtLaneFlags); + Assert.AreEqual(expectedFlags, extNetInfo.m_extLaneFlags); + } + + private void VerifyGroup(ExtNetInfo extNetInfo, int groupIndex, ExtLaneFlags laneFlags, params int[] sortedLanes) { + Assert.AreEqual(laneFlags, extNetInfo.m_laneGroups[groupIndex].m_extLaneFlags); + VerifySequence(extNetInfo.m_laneGroups[groupIndex].m_sortedLanes, sortedLanes); + } + + private void VerifyLane(ExtNetInfo extNetInfo, int laneIndex, ExtLaneFlags flags) { + Assert.AreEqual(flags, extNetInfo.m_extLanes[laneIndex].m_extFlags); + } + + private void VerifySequence(int[] actual, params int[] expected) { + CollectionAssert.AreEqual(expected, actual); + } + + private NetInfo.Lane PedestrianLane(float position, float width) => + new NetInfo.Lane { + m_position = position, + m_width = width, + m_verticalOffset = 0f, + m_laneType = NetInfo.LaneType.Pedestrian, + m_direction = NetInfo.Direction.Both, + }; + + private NetInfo.Lane ParkingLane(float position, bool backward, float width = 2f) => + new NetInfo.Lane { + m_position = position, + m_width = width, + m_verticalOffset = -.3f, + m_laneType = NetInfo.LaneType.Parking, + m_vehicleType = VehicleInfo.VehicleType.Car, + m_direction = backward ? NetInfo.Direction.Backward : NetInfo.Direction.Forward, + }; + + private NetInfo.Lane CarLane(float position, bool backward) => + new NetInfo.Lane { + m_position = position, + m_width = 3f, + m_verticalOffset = -.3f, + m_laneType = NetInfo.LaneType.Vehicle, + m_vehicleType = VehicleInfo.VehicleType.Car, + m_direction = backward ? NetInfo.Direction.Backward : NetInfo.Direction.Forward, + m_allowConnect = true, + }; + + private NetInfo.Lane ThruCarLane(float position, bool backward) => + new NetInfo.Lane { + m_position = position, + m_width = 3f, + m_verticalOffset = -.3f, + m_laneType = NetInfo.LaneType.Vehicle, + m_vehicleType = VehicleInfo.VehicleType.Car, + m_direction = backward ? NetInfo.Direction.Backward : NetInfo.Direction.Forward, + m_allowConnect = false, + }; + + private NetInfo.Lane BusLane(float position, bool backward) => + new NetInfo.Lane { + m_position = position, + m_width = 3f, + m_verticalOffset = -.3f, + m_laneType = NetInfo.LaneType.TransportVehicle, + m_vehicleType = VehicleInfo.VehicleType.Car, + m_direction = backward ? NetInfo.Direction.Backward : NetInfo.Direction.Forward, + m_allowConnect = true, + }; + + private NetInfo.Lane ThruBusLane(float position, bool backward) => + new NetInfo.Lane { + m_position = position, + m_width = 3f, + m_verticalOffset = -.3f, + m_laneType = NetInfo.LaneType.TransportVehicle, + m_vehicleType = VehicleInfo.VehicleType.Car, + m_direction = backward ? NetInfo.Direction.Backward : NetInfo.Direction.Forward, + m_allowConnect = false, + }; + + private NetInfo.Lane RaisedMedianLane(float position, float width = 0f) => + new NetInfo.Lane { + m_position = position, + m_width = width, + m_verticalOffset = 0f, + m_direction = NetInfo.Direction.Both, + }; + + private NetInfo.Lane FlushMedianLane(float position, float width = 0f) => + new NetInfo.Lane { + m_position = position, + m_width = width, + m_verticalOffset = -.3f, + m_direction = NetInfo.Direction.Both, + }; + } +} diff --git a/TLM/CSModLib.Test/Properties/AssemblyInfo.cs b/TLM/CSModLib.Test/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c4d5fb26c --- /dev/null +++ b/TLM/CSModLib.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("CSModLib.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CSModLib.Test")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("41f0631c-3ca5-4a0f-9fe8-938897b2f577")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TLM/CSModLib.Test/packages.config b/TLM/CSModLib.Test/packages.config new file mode 100644 index 000000000..d35ea08e3 --- /dev/null +++ b/TLM/CSModLib.Test/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/TLM/CSModLib/CSModLib.csproj b/TLM/CSModLib/CSModLib.csproj new file mode 100644 index 000000000..4b9d3e655 --- /dev/null +++ b/TLM/CSModLib/CSModLib.csproj @@ -0,0 +1,94 @@ + + + + + Debug + AnyCPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9} + Library + Properties + CSModLib + CSModLib + v3.5 + 512 + latest + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + ~/Library/Application Support/Steam/ + $(ProgramFiles)\Steam + $(Registry:HKEY_CURRENT_USER\Software\Valve\Steam@SteamPath) + $(SteamPath)\steamapps\common\Cities_Skylines + $(CSPath)\Cities_Data\Managed + ..\dependencies + $(MSBuildExtensionsPath64)\..\Unity\ + ..\Unity\ + + + + $(MangedDLLPath)\Assembly-CSharp.dll + False + + + $(MangedDLLPath)\ColossalManaged.dll + False + + + $(MangedDLLPath)\ICities.dll + False + + + $(MangedDLLPath)\UnityEngine.dll + False + + + $(MangedDLLPath)\UnityEngine.Networking.dll + False + + + $(MangedDLLPath)\UnityEngine.UI.dll + False + + + ..\packages\Portable.ConcurrentDictionary.1.0.4\lib\net35\Portable.ConcurrentDictionary.dll + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TLM/CSModLib/Core/ReferenceEqualityComparer.cs b/TLM/CSModLib/Core/ReferenceEqualityComparer.cs new file mode 100644 index 000000000..9814ceded --- /dev/null +++ b/TLM/CSModLib/Core/ReferenceEqualityComparer.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace CSModLib.Core { + internal sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer { + + public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer(); + + private ReferenceEqualityComparer() { } + + public bool Equals(T x, T y) => ReferenceEquals(x, y); + + public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj); + + bool IEqualityComparer.Equals(object x, object y) => ReferenceEquals(x, y); + + int IEqualityComparer.GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); + } +} \ No newline at end of file diff --git a/TLM/CSModLib/GameObjects/ExtNetInfo.LaneInspector.cs b/TLM/CSModLib/GameObjects/ExtNetInfo.LaneInspector.cs new file mode 100644 index 000000000..2826b850e --- /dev/null +++ b/TLM/CSModLib/GameObjects/ExtNetInfo.LaneInspector.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CSModLib.GameObjects { + partial class ExtNetInfo { + + private class LaneInspector { + + private readonly NetInfo.Lane[] lanes; + private readonly ExtNetInfo extNetInfo; + + public LaneInspector(ExtNetInfo extNetInfo, NetInfo.Lane[] lanes) { + this.lanes = lanes; + this.extNetInfo = extNetInfo; + } + + private int GetDirectionalSortKey(ExtLaneFlags flags) { + switch (flags & LaneGroupDirection) { + case ExtLaneFlags.BackwardGroup: + return 1; + default: + return 2; + case ExtLaneFlags.ForwardGroup: + return 3; + } + } + + // TODO: Boundary conditions on sorting need work + public void CalculateSortedLanes() { + extNetInfo.m_sortedLanes = extNetInfo.m_extLanes.Select((extInfo, index) => index) + .OrderBy(i => lanes[i].m_position) + .ThenBy(i => GetDirectionalSortKey(extNetInfo.m_extLanes[i].m_extFlags)) + .ToArray(); + } + + public LaneConfiguration GetLaneConfiguration() { + + var minForward = float.MaxValue; + var maxForward = float.MinValue; + var minBackward = float.MaxValue; + var maxBackward = float.MinValue; + + for (int i = 0; i < lanes.Length; i++) { + var flags = extNetInfo.m_extLanes[i].m_extFlags; + if ((flags & ExtLaneFlags.ForwardGroup) != 0) { + float position = lanes[i].m_position; + minForward = Math.Min(minForward, (float)position); + maxForward = Math.Max(maxForward, (float)position); + + } else if ((flags & ExtLaneFlags.BackwardGroup) != 0) { + float position = lanes[i].m_position; + minBackward = Math.Min(minBackward, (float)position); + maxBackward = Math.Max(maxBackward, (float)position); + } + } + + if (minBackward == float.MaxValue) + return minForward == float.MaxValue ? LaneConfiguration.Undefined : LaneConfiguration.OneWay; + else if (minForward == float.MaxValue) + return LaneConfiguration.InvertedOneWay; + else if (maxForward < minBackward || (minForward < minBackward && maxForward == minBackward)) + return LaneConfiguration.Inverted; + else if (minForward < maxBackward) + return LaneConfiguration.Complex; + else + return LaneConfiguration.TwoWay; + } + + public void FindDisplacedOuter(ExtLaneFlags workingDirection, ref int lastOuterDisplacedIndex) { + + bool backward = workingDirection == ExtLaneFlags.BackwardGroup; + + int scanStart = backward ? lanes.Length - 1 : 0; + int scanEnd = backward ? -1 : lanes.Length; + int step = backward ? -1 : 1; + + for (int sortedIndex = scanStart; sortedIndex != scanEnd; sortedIndex += step) { + ExtLaneInfo extLane = extNetInfo.m_extLanes[extNetInfo.m_sortedLanes[sortedIndex]]; + var direction = extLane.m_extFlags & LaneGroupDirection; + if ((direction & workingDirection) != 0) { + extLane.m_extFlags |= ExtLaneFlags.DisplacedOuter; + lastOuterDisplacedIndex = sortedIndex; + } else if (direction != 0) { + break; + } + } + } + + public void FindOuterAndDisplacedInner(ExtLaneFlags workingDirection) { + + bool backward = workingDirection == ExtLaneFlags.BackwardGroup; + + var oppositeDirection = backward ? ExtLaneFlags.ForwardGroup : ExtLaneFlags.BackwardGroup; + int scanStart = backward ? 0 : lanes.Length - 1; + int scanEnd = backward ? lanes.Length : -1; + int step = backward ? 1 : -1; + + int sortedIndex; + + // skip opposite direction DisplacedOuter + for (sortedIndex = scanStart; sortedIndex != scanEnd; sortedIndex += step) { + if ((extNetInfo.m_extLanes[extNetInfo.m_sortedLanes[sortedIndex]].m_extFlags & workingDirection) != 0) + break; + } + + // scan for Outer (may convert some to Inner later) + for (; sortedIndex != scanEnd; sortedIndex += step) { + var extLane = extNetInfo.m_extLanes[extNetInfo.m_sortedLanes[sortedIndex]]; + var direction = extLane.m_extFlags & LaneGroupDirection; + if ((direction & workingDirection) != 0) + extLane.m_extFlags |= ExtLaneFlags.Outer; + if ((direction & oppositeDirection) != 0) + break; + } + + // skip opposite direction + for (; sortedIndex != scanEnd; sortedIndex += step) { + if ((extNetInfo.m_extLanes[extNetInfo.m_sortedLanes[sortedIndex]].m_extFlags & workingDirection) != 0) + break; + } + + // scan for DisplacedInner|ForwardGroup + for (; sortedIndex != scanEnd; sortedIndex += step) { + int laneIndex = extNetInfo.m_sortedLanes[sortedIndex]; + var extLane = extNetInfo.m_extLanes[laneIndex]; + if ((extLane.m_extFlags & workingDirection) != 0) { + + // if we find DisplacedOuter lanes, we've reached the other side and we're done + if ((extLane.m_extFlags & ExtLaneFlags.DisplacedOuter) != 0) + break; + + var lane = lanes[laneIndex]; + extLane.m_extFlags |= ExtLaneFlags.DisplacedInner; + if (lane.m_allowConnect) + extLane.m_extFlags |= ExtLaneFlags.ForbidControlledLanes; + else if (lane.m_laneType == NetInfo.LaneType.Vehicle) + extLane.m_extFlags |= ExtLaneFlags.AllowCFI; + } + } + } + + public void FindInner(ExtLaneFlags workingDirection) { + + bool backward = workingDirection == ExtLaneFlags.BackwardGroup; + + var oppositeDirection = backward ? ExtLaneFlags.ForwardGroup : ExtLaneFlags.BackwardGroup; + int scanStart = backward ? 0 : lanes.Length - 1; + int scanEnd = backward ? lanes.Length : -1; + int step = backward ? 1 : -1; + + // The position multiplier causes lane positions to appear to this logic in ascending order + // regardless of the direction in which we are evaluating them. This keeps the spacial + // calculations simple on median detection. + int positionMultiplier = backward ? 1 : -1; + + int sortedIndex; + + // advance to first Outer + + float laneWidth = float.MaxValue; + float laneEdge = float.MaxValue; + float laneElevation = float.MaxValue; + float medianEdge = float.MaxValue; + float medianElevation = float.MinValue; + + int firstOuterLane; + int firstInnerLane = -1; + + for (firstOuterLane = scanStart; firstOuterLane != scanEnd; firstOuterLane += step) { + + var laneIndex = extNetInfo.m_sortedLanes[firstOuterLane]; + if ((extNetInfo.m_extLanes[laneIndex].m_extFlags & workingDirection) != 0) { + var lane = lanes[laneIndex]; + laneWidth = lane.m_width; + laneEdge = lane.m_position * positionMultiplier + laneWidth / 2; + laneElevation = lane.m_verticalOffset; + break; + } + } + + // scan for median + + for (sortedIndex = firstOuterLane + step; sortedIndex != scanEnd; sortedIndex += step) { + var laneIndex = extNetInfo.m_sortedLanes[sortedIndex]; + var extLane = extNetInfo.m_extLanes[laneIndex]; + var lane = lanes[laneIndex]; + var position = lane.m_position * positionMultiplier; + if ((extLane.m_extFlags & workingDirection) != 0) { + + if (((position - lane.m_width / 2) >= medianEdge + && lane.m_verticalOffset < medianElevation + && medianElevation - lane.m_verticalOffset < 3f) + || (position - lane.m_width - laneWidth) >= laneEdge) { + firstInnerLane = sortedIndex; + break; + } + + medianEdge = float.MaxValue; + medianElevation = float.MinValue; + laneWidth = lane.m_width; + laneEdge = position + laneWidth / 2; + laneElevation = lane.m_verticalOffset; + } else if ((extLane.m_extFlags & oppositeDirection) != 0) { + break; + } else { + if (!lane.IsRoadLane() && lane.m_verticalOffset > laneElevation && lane.m_verticalOffset - laneElevation < 3f) { + if ((position - lane.m_width / 2) >= laneEdge) { + medianEdge = Math.Min(medianEdge, position + lane.m_width / 2); + medianElevation = Math.Max(medianElevation, lane.m_verticalOffset); + } + } + } + } + + if (firstInnerLane >= 0) { + // apply AllowServiceLane + + for (sortedIndex = scanStart; sortedIndex != firstInnerLane; sortedIndex += step) { + var laneIndex = extNetInfo.m_sortedLanes[sortedIndex]; + var extLane = extNetInfo.m_extLanes[laneIndex]; + if ((extLane.m_extFlags & (ExtLaneFlags.Outer | workingDirection)) == (ExtLaneFlags.Outer | workingDirection) + && lanes[laneIndex].IsCarLane()) { + extLane.m_extFlags |= ExtLaneFlags.AllowServiceLane; + } + } + + // apply Inner + + for (; sortedIndex != scanEnd; sortedIndex += step) { + var laneIndex = extNetInfo.m_sortedLanes[sortedIndex]; + var extLane = extNetInfo.m_extLanes[laneIndex]; + if ((extLane.m_extFlags & (ExtLaneFlags.Outer | workingDirection)) == (ExtLaneFlags.Outer | workingDirection)) { + extLane.m_extFlags &= ~ExtLaneFlags.Outer; + extLane.m_extFlags |= ExtLaneFlags.Inner; + var lane = lanes[laneIndex]; + if (lane.m_allowConnect) + extLane.m_extFlags |= ExtLaneFlags.ForbidControlledLanes; + else if (lane.IsCarLane()) + extLane.m_extFlags |= ExtLaneFlags.AllowExpressLane; + } + } + } + } + } + } +} diff --git a/TLM/CSModLib/GameObjects/ExtNetInfo.cs b/TLM/CSModLib/GameObjects/ExtNetInfo.cs new file mode 100644 index 000000000..1923cf73e --- /dev/null +++ b/TLM/CSModLib/GameObjects/ExtNetInfo.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CSModLib.GameObjects { + public partial class ExtNetInfo : ExtPrefabInfo { + + [Flags] + public enum ExtLaneFlags { + + None = 0, + + /// + /// Lanes outside a median (if any) that divides lanes going the same direction. + /// In the absence of such a median, these are simply lanes that are not displaced. + /// + Outer = 1 << 0, + + /// + /// Lanes inside a median that divides lanes going the same direction. + /// + Inner = 1 << 1, + + /// + /// Displaced lanes that are between lanes going the opposite direction. + /// + DisplacedInner = 1 << 2, + + /// + /// Displaced lanes that are located on the far side of the road from what is normal for their direction. + /// + DisplacedOuter = 1 << 3, + + /// + /// Lanes in a group whose prevailing direction is forward. + /// + ForwardGroup = 1 << 4, + + /// + /// Lanes in a group whose prevailing direction is backward. + /// + BackwardGroup = 1 << 5, + + /// + /// Lanes that may be treated as service lanes in controlled lane routing. + /// + AllowServiceLane = 1 << 6, + + /// + /// Lanes that may be treated as express lanes in controlled lane routing. + /// + AllowExpressLane = 1 << 7, + + /// + /// Lanes that may be treated as displaced far turn lanes in controlled lane routing. + /// + AllowCFI = 1 << 8, + + /// + /// Lanes whose properties cause controlled lane routing to be disabled for the segment. + /// + ForbidControlledLanes = 1 << 9, + + OuterForward = Outer | ForwardGroup, + InnerForward = Inner | ForwardGroup, + DisplacedInnerForward = DisplacedInner | ForwardGroup, + DisplacedOuterForward = DisplacedOuter | ForwardGroup, + + OuterBackward = Outer | BackwardGroup, + InnerBackward = Inner | BackwardGroup, + DisplacedInnerBackward = DisplacedInner | BackwardGroup, + DisplacedOuterBackward = DisplacedOuter | BackwardGroup, + } + + /// + /// Mask to obtain the lane grouping key. + /// + public const ExtLaneFlags LaneGroupingKey = + ExtLaneFlags.Outer | ExtLaneFlags.Inner + | ExtLaneFlags.DisplacedInner | ExtLaneFlags.DisplacedOuter + | ExtLaneFlags.ForwardGroup | ExtLaneFlags.BackwardGroup; + + /// + /// Mask to test a segment for the presence of service lanes. + /// + public const ExtLaneFlags ServiceLaneRule = ExtLaneFlags.AllowServiceLane | ExtLaneFlags.ForbidControlledLanes; + + /// + /// Mask to test a segment for the presence of express lanes. + /// + public const ExtLaneFlags ExpressLaneRule = ExtLaneFlags.AllowExpressLane | ExtLaneFlags.ForbidControlledLanes; + + /// + /// Mask to test a segment for the presence of displaced far turn lanes. + /// + public const ExtLaneFlags CFIRule = ExtLaneFlags.AllowCFI | ExtLaneFlags.ForbidControlledLanes; + + /// + /// Mask to test the prevailing direction of a lane group. + /// + public const ExtLaneFlags LaneGroupDirection = ExtLaneFlags.ForwardGroup | ExtLaneFlags.BackwardGroup; + + /// + /// Mask to test a segment for the presence of any displaced lanes. + /// + public const ExtLaneFlags DisplacedLanes = ExtLaneFlags.DisplacedInner | ExtLaneFlags.DisplacedOuter; + + /// + /// Extended lane prefab data, directly corresponding to . + /// + public ExtLaneInfo[] m_extLanes; + + /// + /// Lane group prefab data. + /// + public LaneGroupInfo[] m_laneGroups; + + /// + /// Lane indices sorted by position, then by direction. + /// + public int[] m_sortedLanes; + + /// + /// Aggregate lane flags for forward groups. + /// + public ExtLaneFlags m_forwardExtLaneFlags; + + /// + /// Aggregate lane flags for backward groups. + /// + public ExtLaneFlags m_backwardExtLaneFlags; + + /// + /// Aggregate of all lane flags. + /// + public ExtLaneFlags m_extLaneFlags; + + public LaneConfiguration m_roadLaneConfiguration; + + public ExtNetInfo(NetInfo info) + : this(info.m_lanes) { + } + + public enum LaneConfiguration { + Undefined = 0, + TwoWay = 1 << 0, + OneWay = 1 << 1, + Inverted = 1 << 2, + Complex = 1 << 3, + + InvertedOneWay = OneWay | Inverted, + } + + private const LaneConfiguration GeneralTwoWayConfiguration = LaneConfiguration.TwoWay | LaneConfiguration.Complex; + + public ExtNetInfo(NetInfo.Lane[] lanes) { + + m_extLanes = lanes.Select(l => new ExtLaneInfo(l)).ToArray(); + + var inspector = new LaneInspector(this, lanes); + + inspector.CalculateSortedLanes(); + + m_roadLaneConfiguration = inspector.GetLaneConfiguration(); + + var lastDisplacedOuterBackward = lanes.Length; + var lastDisplacedOuterForward = -1; + + if (m_roadLaneConfiguration != LaneConfiguration.Complex) { + for (int i = 0; i < m_extLanes.Length; i++) { + if (lanes[i].IsRoadLane()) + m_extLanes[i].m_extFlags |= ExtLaneFlags.Outer; + } + } else { + + inspector.FindDisplacedOuter(ExtLaneFlags.ForwardGroup, ref lastDisplacedOuterForward); + inspector.FindDisplacedOuter(ExtLaneFlags.BackwardGroup, ref lastDisplacedOuterBackward); + + inspector.FindOuterAndDisplacedInner(ExtLaneFlags.ForwardGroup); + inspector.FindOuterAndDisplacedInner(ExtLaneFlags.BackwardGroup); + } + + if ((m_roadLaneConfiguration & GeneralTwoWayConfiguration) != 0) { + + inspector.FindInner(ExtLaneFlags.ForwardGroup); + inspector.FindInner(ExtLaneFlags.BackwardGroup); + } + + m_laneGroups = Enumerable.Range(0, lanes.Length) + .Select(index => new { index, lane = lanes[index], extLane = m_extLanes[index] }) + .GroupBy(l => l.extLane.m_extFlags & LaneGroupingKey) + .Where(g => g.Key != 0) + .Select(g => new LaneGroupInfo { + m_extLaneFlags = g.Select(l => l.extLane.m_extFlags).Aggregate((ExtLaneFlags x, ExtLaneFlags y) => x | y), + m_sortedLanes = g.OrderBy(g => g.lane.m_position).Select(g => g.index).ToArray(), + }) + .OrderBy(lg => lanes[lg.m_sortedLanes[0]].m_position) + .ToArray(); + + m_forwardExtLaneFlags = m_extLanes.Select(l => l.m_extFlags).Where(f => (f & ExtLaneFlags.ForwardGroup) != 0).DefaultIfEmpty().Aggregate((x, y) => x | y); + m_backwardExtLaneFlags = m_extLanes.Select(l => l.m_extFlags).Where(f => (f & ExtLaneFlags.BackwardGroup) != 0).DefaultIfEmpty().Aggregate((x, y) => x | y); + + m_extLaneFlags = m_forwardExtLaneFlags | m_backwardExtLaneFlags; + } + + public class ExtLaneInfo { + + public ExtLaneFlags m_extFlags; + + public ExtLaneInfo(NetInfo.Lane lane) { + if (lane.IsRoadLane()) { + + switch (lane.m_direction) { + case NetInfo.Direction.Forward: + case NetInfo.Direction.AvoidBackward: + m_extFlags |= ExtLaneFlags.ForwardGroup; + break; + + case NetInfo.Direction.Backward: + case NetInfo.Direction.AvoidForward: + m_extFlags |= ExtLaneFlags.BackwardGroup; + break; + + case NetInfo.Direction.Both: + case NetInfo.Direction.AvoidBoth: + m_extFlags |= ExtLaneFlags.ForwardGroup | ExtLaneFlags.BackwardGroup; + break; + } + } + } + } + + public class LaneGroupInfo { + + /// + /// Indices of the lanes in this group, sorted by position. + /// + public int[] m_sortedLanes; + + /// + /// Aggregate lane flags. + /// + public ExtLaneFlags m_extLaneFlags; + } + } +} diff --git a/TLM/CSModLib/GameObjects/ExtPrefabInfo.cs b/TLM/CSModLib/GameObjects/ExtPrefabInfo.cs new file mode 100644 index 000000000..c88297800 --- /dev/null +++ b/TLM/CSModLib/GameObjects/ExtPrefabInfo.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Concurrent; +using CSModLib.Core; + +namespace CSModLib.GameObjects { + public abstract class ExtPrefabInfo + where TInfo : PrefabInfo + where TExtInfo : ExtPrefabInfo + { + private static readonly ConcurrentDictionary extInfos; + + static ExtPrefabInfo() { + extInfos = new ConcurrentDictionary(ReferenceEqualityComparer.Instance); + } + + public static TExtInfo GetInstance(TInfo info) { + return extInfos.GetOrAdd(info, i => (TExtInfo)Activator.CreateInstance(typeof(TExtInfo), i)); + } + } +} diff --git a/TLM/CSModLib/GameObjects/FlagExtensions.cs b/TLM/CSModLib/GameObjects/FlagExtensions.cs new file mode 100644 index 000000000..109248971 --- /dev/null +++ b/TLM/CSModLib/GameObjects/FlagExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CSModLib.GameObjects { + public static class FlagExtensions { + + public static bool IsFlagSet(this NetInfo.LaneType laneType, NetInfo.LaneType flag) + => (laneType & flag) != 0; + + public static bool IsFlagSet(this VehicleInfo.VehicleType vehicleType, VehicleInfo.VehicleType flag) + => (vehicleType & flag) != 0; + } +} diff --git a/TLM/CSModLib/GameObjects/NetInfoExtensions.cs b/TLM/CSModLib/GameObjects/NetInfoExtensions.cs new file mode 100644 index 000000000..f293cb4d0 --- /dev/null +++ b/TLM/CSModLib/GameObjects/NetInfoExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CSModLib.GameObjects { + public static class NetInfoExtensions { + + public static bool IsRoadLane(this NetInfo.Lane lane) => + lane.m_laneType.IsFlagSet(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle) + && lane.m_vehicleType.IsFlagSet(VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Trolleybus); + + public static bool IsCarLane(this NetInfo.Lane lane) => + lane.m_laneType.IsFlagSet(NetInfo.LaneType.Vehicle) + && lane.m_vehicleType.IsFlagSet(VehicleInfo.VehicleType.Car); + } +} diff --git a/TLM/CSModLib/Properties/AssemblyInfo.cs b/TLM/CSModLib/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..532a7281e --- /dev/null +++ b/TLM/CSModLib/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CSModLib")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CSModLib")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5d3b2f48-3f16-424d-8c87-d59cdf420ea9")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TLM/CSModLib/packages.config b/TLM/CSModLib/packages.config new file mode 100644 index 000000000..b3303b846 --- /dev/null +++ b/TLM/CSModLib/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 69d877070..230b9cd93 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -1,4 +1,4 @@ - + @@ -107,6 +107,9 @@ ..\libs\MoveItIntegration.dll + + ..\packages\Portable.ConcurrentDictionary.1.0.4\lib\net35\Portable.ConcurrentDictionary.dll + $(MangedDLLPath)\System.Core.dll @@ -882,6 +885,10 @@ + + {5d3b2f48-3f16-424d-8c87-d59cdf420ea9} + CSModLib + {f8759084-df5b-4a54-b73c-824640a8fa3f} CSUtil.CameraControl @@ -1270,6 +1277,8 @@ xcopy /y "$(TargetDir)CSUtil.Commons.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)CitiesHarmony.API.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)MoveItIntegration.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)UnifiedUILib.dll" "%25DEPLOYDIR%25" +xcopy /y "$(TargetDir)CSModLib.dll" "%25DEPLOYDIR%25" +xcopy /y "$(TargetDir)Portable.ConcurrentDictionary.dll" "%25DEPLOYDIR%25" rem To avoid double hot reload, TrafficManager.dll must be replaced last and fast. rem Once TrafficManager.dll is re-loaded, all other dlls will be reloaded as well diff --git a/TLM/TLM/packages.config b/TLM/TLM/packages.config index 0604303d0..49e5d6c4e 100644 --- a/TLM/TLM/packages.config +++ b/TLM/TLM/packages.config @@ -3,6 +3,7 @@ + diff --git a/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj b/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj index c50ac9dc4..887948442 100644 --- a/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj +++ b/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj @@ -1,4 +1,4 @@ - + Debug diff --git a/TLM/TMPE.sln b/TLM/TMPE.sln index 0a18b4fcc..44b914964 100644 --- a/TLM/TMPE.sln +++ b/TLM/TMPE.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29503.13 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32519.379 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TLM", "TLM\TLM.csproj", "{7422AE58-8B0A-401C-9404-F4A438EFFE10}" ProjectSection(ProjectDependencies) = postProject @@ -27,6 +27,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.API", "TMPE.API\TMPE.A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{A38B1D43-3557-4D42-973C-90C7F5E10C02}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSModLib", "CSModLib\CSModLib.csproj", "{5D3B2F48-3F16-424D-8C87-D59CDF420EA9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSModLib.Test", "CSModLib.Test\CSModLib.Test.csproj", "{41F0631C-3CA5-4A0F-9FE8-938897B2F577}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Benchmark|Any CPU = Benchmark|Any CPU @@ -121,6 +125,30 @@ Global {A38B1D43-3557-4D42-973C-90C7F5E10C02}.Release TEST|Any CPU.Build.0 = Release|Any CPU {A38B1D43-3557-4D42-973C-90C7F5E10C02}.Release|Any CPU.ActiveCfg = Release|Any CPU {A38B1D43-3557-4D42-973C-90C7F5E10C02}.Release|Any CPU.Build.0 = Release|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.Benchmark|Any CPU.ActiveCfg = Debug|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.Benchmark|Any CPU.Build.0 = Debug|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.PF2_Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.PF2_Debug|Any CPU.Build.0 = Debug|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.Release TEST|Any CPU.ActiveCfg = Release|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.Release TEST|Any CPU.Build.0 = Release|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D3B2F48-3F16-424D-8C87-D59CDF420EA9}.Release|Any CPU.Build.0 = Release|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.Benchmark|Any CPU.ActiveCfg = Debug|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.Benchmark|Any CPU.Build.0 = Debug|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.PF2_Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.PF2_Debug|Any CPU.Build.0 = Debug|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.Release TEST|Any CPU.ActiveCfg = Release|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.Release TEST|Any CPU.Build.0 = Release|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41F0631C-3CA5-4A0F-9FE8-938897B2F577}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE