Building roads into rail nodes

Not sure if this is the same problem that people have reported about being able to “connect” roads with rails of train stations, but I suspect it is. In some situations it is possible to build a road node onto a rail node, but it is not possible the other way around. Here is a screenshot of what that looks like right before placement:

The mouse pointer in the middle of the straight stretch of rail with the road right next to it. There’s a rail node right there. The issue does not occur on rail edges, only on nodes. A little further south you can see another stretch of road that completely bugged out. The game really doesn’t like to be in this state.

some exceptions

Exception: Wasn’t able to add network edge to node 56167
at NetEdge.SetEndNode (NetNode endNode, System.Boolean updateShapeFully) [0x00044] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at SplitGhostEdge.SetEndNode (NetNode endNode, System.Boolean updateShape) [0x00018] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at NetworkConstructionManager.CustomUpdate () [0x00a66] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at WorldScripts.UpdateFrameUnity () [0x00107] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at WorldOrchestrator.UpdateFrameUnity (System.Boolean debug) [0x00061] in <7d318cbc46c64bc5a4513c30a14a52a7>:0
at GameOrchestrator.Update () [0x00055] in <7d318cbc46c64bc5a4513c30a14a52a7>:0

Exception: Road.GetLaneBezierDefNonCached invalid road(1): NetEdge, Intersection, , EdgeBezierControl, EdgeBezierControl
at Road.GetLaneBezierDefNonCached (System.Single laneOffset) [0x000eb] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at Road.GetLaneBezierDef (System.Int32 laneOffset) [0x00010] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at (wrapper synchronized) Road.GetLaneBezierDef(int)
at Lane.GetBezier () [0x00000] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at Lane.GetCarStartPosition () [0x0001c] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at Lane.UpdateTotalSpace () [0x00000] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at Road.UpdateShape (System.Boolean fromSave) [0x0001b] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at NetEdge.UpdateShape (System.Boolean updateDoesWork, System.Boolean fromSave) [0x00000] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at NetworkConstructionManager.HandleLeftMouseClick () [0x0049b] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0
at BaYoNeTTe.ClickHandlerPatch.Prefix (ClickHandler __instance, System.Collections.Generic.List`1[T] ___pointerListeners) [0x00116] in :0
at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.ClickHandler.HandleLeftMouseClick_Patch1(ClickHandler)
at ClickHandler.Update () [0x00014] in <1c5fa9f42fd24bd2b3d0143a8c6acc23>:0

I haven’t actually tried to fix this with a patch yet, but I stumbled across something in the code that made me try to cause this issue on purpose, and that’s why I am posting here in the first place.

I think there’s a bug in NetworkConstructionManager.CanBuildIntoNode(). The issue is that when building roads (currentNetworkType == NetworkType.Road) the method will always return true, while it should really check && node.railNode == null or something like that in addition, to make sure roads cannot be built into rails. The rails do have a check like that so building rails into road nodes is not possible.

	public bool CanBuildIntoNode(NetNode node, string edgeName)
	{
		if (currentNetworkType == NetworkType.Pipe)
		{
			return edgeName == node.pipeNode.pipesCached[0].pipeName;
		}
		if (currentNetworkType == NetworkType.Road || splitGhostEdge == null)
		{
			return true;
		}
		if (currentNetworkType == NetworkType.Rail && node.railNode == null)
		{
			return false;
		}
		Vector2 xZ = node.railNode.bezierDirection.GetXZ();
		float offsetLength;
		Vector2 to = EdgeBezierControl.GetCircleDir(directionA: (splitGhostEdge.GetStartNode().GetEdges().Count <= 1) ? (node.GetEdgePosition() - splitGhostEdge.GetStartNode().GetEdgePosition()).GetXZ() : splitGhostEdge.GetStartNode().railNode.bezierDirection.GetXZ(), pointA: splitGhostEdge.GetStartNode().GetEdgePosition().GetXZ(), pointB: node.GetEdgePosition().GetXZ(), visualizationY: -1f, offsetLength: out offsetLength);
		return Vector2.Angle(xZ, to) < 90f;
	}

I had some time to test it and indeed the problem could be fixed by changing the method CanBuildIntoNode() mentioned above. No exception and no weird placements anymore, or so it seems. I am not sure if I caught all cases though, as the whole network construction business is quite complex and I haven’t even waded through all of its code yet.

This is my harmony-patched version. I only changed the conditions in the middle a little. The rest is the same, although I refactored slightly to aid readability for me.

[HarmonyPatch("CanBuildIntoNode")]
[HarmonyPrefix]
public static bool CanBuildIntoNode(ref bool __result, NetNode node, string edgeName, NetworkType ___currentNetworkType, SplitGhostEdge ___splitGhostEdge)
{
    if (___currentNetworkType == NetworkType.Pipe)
    {
        __result = (edgeName == node.pipeNode.pipesCached[0].pipeName);
        return false;
    }
    if (___splitGhostEdge == null)
    {
        __result = true;
        return false;
    }
    if (___currentNetworkType == NetworkType.Road)
    {
        __result = (node.networkType == NetworkType.Road);
        return false;
    }
    if (___currentNetworkType == NetworkType.Rail && node.railNode == null)
    {
        __result = false;
        return false;
    }
    Vector2 toDirection = node.railNode.bezierDirection.GetXZ();
    Vector2 directionA;
    if (___splitGhostEdge.GetStartNode().GetEdges().Count <= 1)
    {
        directionA = (node.GetEdgePosition() - ___splitGhostEdge.GetStartNode().GetEdgePosition()).GetXZ();
    }
    else
    {
        directionA = ___splitGhostEdge.GetStartNode().railNode.bezierDirection.GetXZ();
    }
    Vector2 fromDirection = EdgeBezierControl.GetCircleDir(directionA:     directionA,
                                                           pointA:         ___splitGhostEdge.GetStartNode().GetEdgePosition().GetXZ(),
                                                           pointB:         node.GetEdgePosition().GetXZ(),
                                                           visualizationY: -1f,
                                                           offsetLength:   out float offsetLength);
    __result = Vector2.Angle(toDirection, fromDirection) < 90f;
    return false;
}

In particular I am unsure about the role that splitGhostEdge plays in this method and when it would be null.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.