/// <summary> /// Link the various nodes to each other. Do some initial processing on the path, like finding linking TVNs /// and determining whether junctions are facing or not. /// </summary> /// <param name="patFile">Patfile object containing the various unprocessed Track Path Nodes</param> /// <param name="Nodes">The list of as-of-yet unlinked processed path nodes</param> static private void LinkNodes(PathFile patFile, List <TrainpathNode> Nodes) { // Connect the various nodes to each other for (int i = 0; i < Nodes.Count; i++) { TrainpathNode node = Nodes[i]; TrPathNode tpn = patFile.TrPathNodes[i]; // find TvnIndex to next main node. if (tpn.HasNextMainNode) { node.NextMainNode = Nodes[(int)tpn.nextMainNode]; node.NextMainNode.PrevNode = node; node.NextMainTvnIndex = node.FindTvnIndex(node.NextMainNode); } // find TvnIndex to next siding node if (tpn.HasNextSidingNode) { node.NextSidingNode = Nodes[(int)tpn.nextSidingNode]; if (node.NextSidingNode.PrevNode == null) { node.NextSidingNode.PrevNode = node; } node.NextSidingTvnIndex = node.FindTvnIndex(node.NextSidingNode); } if (node.NextMainNode != null && node.NextSidingNode != null) { node.NodeType = TrainpathNodeType.SidingStart; } } }
// Possible interpretation (as found on internet, by krausyao) // TrPathNode ( AAAABBBB mainIdx passingIdx pdpIdx ) // AAAA wait time seconds in hexidecimal // BBBB (Also hexidecimal, so 16 bits) // Bit 0 - connected pdp-entry references a reversal-point (1/x1) // Bit 1 - waiting point (2/x2) // Bit 2 - intermediate point between switches (4/x4) // Bit 3 - 'other exit' is used (8/x8) // Bit 4 - 'optional Route' active (16/x10) // // But the interpretation below is a bit more complicated. // TODO. Since this interpretation belongs to the PATfile itself, // in principle it would be more logical to have it in PATfile.cs. But this leads to too much code duplication private void InterpretPathNodeFlags(TrPathNode tpn, TrackPDP pdp, bool isTimetableMode) { if ((tpn.pathFlags & 03) == 0) { return; } // bit 0 and/or bit 1 is set. if ((tpn.pathFlags & 01) != 0) { // if bit 0 is set: reversal Type = AIPathNodeType.Reverse; } else { // bit 0 is not set, but bit 1 is set:waiting point Type = AIPathNodeType.Stop; //<CSComment> tests showed me that value 9 in pdp is generated when the waiting point (or also //a path start or end point) are dragged within the path editor of the MSTS activity editor; the points are still valid; // however, as a contradictory case of the past has been reported, the check is skipped only when the enhanced compatibility flag is on; if (pdp.IsInvalid && isTimetableMode) // not a valid point { Type = AIPathNodeType.Invalid; } } WaitTimeS = (int)((tpn.pathFlags >> 16) & 0xffff); // get the AAAA part. // computations for absolute wait times are made within AITrain.cs /* if (WaitTimeS >= 30000 && WaitTimeS < 40000) * { * // real wait time. * // waitTimeS (in decimal notation) = 3HHMM (hours and minuts) * int hour = (WaitTimeS / 100) % 100; * int minute = WaitTimeS % 100; * WaitUntil = 60 * (minute + 60 * hour); * WaitTimeS = 0; * }*/ // computations are made within AITrain.cs /* else if (WaitTimeS >= 40000 && WaitTimeS < 60000) * { * // Uncouple if a wait=stop point * // waitTimeS (in decimal notation) = 4NNSS (uncouple NN cars, wait SS seconds) * // or 5NNSS (uncouple NN cars, keep rear, wait SS seconds) * NCars = (WaitTimeS / 100) % 100; * if (WaitTimeS >= 50000) * NCars = -NCars; * WaitTimeS %= 100; * if (Type == AIPathNodeType.Stop) * Type = AIPathNodeType.Uncouple; * } * else if (WaitTimeS >= 60000) // this is old and should be removed/reused * { * // waitTimes = 6xSSS with waitTime SSS seconds. * WaitTimeS %= 1000; * } */ }
/// <summary> /// Draw the actual path coded in the PATfile (for a number of nodes that can be extended or reduced) /// </summary> /// <param name="drawArea">Area to draw upon</param> public void Draw(DrawArea drawArea) { //draw actual path currentMainNodeIndex = 0; // starting point int currentSidingNodeIndex = -1; // we start without siding path for (int i = 0; i < Math.Min(patFile.TrPathNodes.Count - 1, numberToDraw); i++) { // If we have a current siding track, we draw it step to the next main line first. if (currentSidingNodeIndex > 0) { //while tracking a siding, it has its own main node int nextNodeIndexOnSiding = (int)patFile.TrPathNodes[currentSidingNodeIndex].nextSidingNode; if (nextNodeIndexOnSiding > 0) // because also this path can run off at the end { TrPathNode curNode = patFile.TrPathNodes[currentSidingNodeIndex]; WorldLocation curLoc = GetPdpLocation(patFile.TrackPDPs[(int)curNode.fromPDP]); TrPathNode nextNode = patFile.TrPathNodes[nextNodeIndexOnSiding]; WorldLocation nextLoc = GetPdpLocation(patFile.TrackPDPs[(int)nextNode.fromPDP]); drawArea.DrawLine(1, DrawColors.colorsPathSiding.TrackStraight, curLoc, nextLoc); } currentSidingNodeIndex = nextNodeIndexOnSiding; } TrPathNode curMainNode = patFile.TrPathNodes[currentMainNodeIndex]; WorldLocation curMainLoc = GetPdpLocation(patFile.TrackPDPs[(int)curMainNode.fromPDP]); // from this main line point to the next siding node. // If there is a next siding node, we also reset the currentSidingNodeIndex // but probably it is not allowed to have siding int nextSidingNodeIndex = (int)curMainNode.nextSidingNode; if (nextSidingNodeIndex >= 0) { // draw the start of a siding path TrPathNode nextNode = patFile.TrPathNodes[nextSidingNodeIndex]; WorldLocation nextLoc = GetPdpLocation(patFile.TrackPDPs[(int)nextNode.fromPDP]); drawArea.DrawLine(1, DrawColors.colorsPathSiding.TrackStraight, curMainLoc, nextLoc); currentSidingNodeIndex = nextSidingNodeIndex; } // From this main line point to the next int nextMainNodeIndex = (int)curMainNode.nextMainNode; if (nextMainNodeIndex >= 0) { TrPathNode nextNode = patFile.TrPathNodes[nextMainNodeIndex]; WorldLocation nextLoc = GetPdpLocation(patFile.TrackPDPs[(int)nextNode.fromPDP]); drawArea.DrawLine(1, DrawColors.colorsPathMain.TrackStraight, curMainLoc, nextLoc); currentMainNodeIndex = nextMainNodeIndex; } } }
public bool IsVisited; // true if the train has visited this node /// <summary> /// Creates a single AIPathNode and initializes everything that do not depend on other nodes. /// The AIPath constructor will initialize the rest. /// </summary> public AIPathNode(TrPathNode tpn, TrackPDP pdp, TrackDB trackDB, bool isTimetableMode) { ID = (int)tpn.fromPDP; InterpretPathNodeFlags(tpn, pdp, isTimetableMode); Location = new WorldLocation(pdp.TileX, pdp.TileZ, pdp.X, pdp.Y, pdp.Z); if (pdp.IsJunction) { JunctionIndex = FindJunctionOrEndIndex(Location, trackDB, true); } }
/// <summary> /// Sort of constructor. But it creates the right sub-class /// </summary> /// <returns>A sub-class object properly initialized</returns> public static TrainpathNode CreatePathNode(TrPathNode tpn, TrackPDP pdp, TrackDB trackDB, TrackSectionsFile tsectionDat) { if (pdp.IsJunction) { // we do not use tpn: this means we do not interpret the flags return(new TrainpathJunctionNode(pdp, trackDB, tsectionDat)); } else { return(new TrainpathVectorNode(tpn, pdp, trackDB, tsectionDat)); } }
/// <summary> /// Add information of the basic MSTS PATfile /// </summary> /// <param name="trackViewer"></param> private void AddPATfileStatus(TrackViewer trackViewer) { if (Properties.Settings.Default.statusShowPATfile && (trackViewer.DrawPATfile != null)) { TrPathNode curNode = trackViewer.DrawPATfile.CurrentNode; TrackPDP curPDP = trackViewer.DrawPATfile.CurrentPdp; statusAdditional.Text += string.Format(System.Globalization.CultureInfo.CurrentCulture, " {7}: {3}, {4} [{1} {2}] [{5} {6}] <{0}>", curNode.pathFlags, (int)curNode.nextMainNode, (int)curNode.nextSidingNode, curPDP.X, curPDP.Z, curPDP.junctionFlag, curPDP.invalidFlag, trackViewer.DrawPATfile.FileName); } }
/// <summary> /// Constructor based on PAT file information. /// </summary> /// <param name="tpn">TrPathNode from .pat file</param> /// <param name="pdp">TrackPDP from .pat file</param> /// <param name="trackDB"></param> /// <param name="tsectionDat"></param> public TrainpathVectorNode(TrPathNode tpn, TrackPDP pdp, TrackDB trackDB, TrackSectionsFile tsectionDat) : base(pdp, trackDB, tsectionDat) { try { Traveller traveller = new Traveller(tsectionDat, trackDB.TrackNodes, this.Location); CopyDataFromTraveller(traveller); } catch { SetBroken(NodeStatus.NotOnTrack); } ForwardOriented = true; // only initial setting InterpretPathNodeFlags(tpn); }
// Flag Intepretation // (No flag interpretation for junction nodes) // Possible interpretation (as found on internet, by krausyao) // TrPathNode ( AAAABBBB mainIdx passingIdx pdpIdx ) // AAAA wait time seconds in hexidecimal // BBBB (Also hexidecimal, so 16 bits) // Bit 0 - connected pdp-entry references a reversal-point (1/x1) // Bit 1 - waiting point (2/x2) // Bit 2 - intermediate point between switches (4/x4) // Bit 3 - 'other exit' is used (8/x8) // Bit 4 - 'optional Route' active (16/x10) // // But the interpretation below is a bit more complicated. // Since this interpretation belongs to the PATfile itself, // in principle it would be more logical to have it in PATfile.cs. But this leads to too much code duplication private void InterpretPathNodeFlags(TrPathNode tpn) { if ((tpn.pathFlags & 03) == 0) { return; } // bit 0 and/or bit 1 is set. if ((tpn.pathFlags & 01) != 0) { // if bit 0 is set: reversal NodeType = TrainpathNodeType.Reverse; } else { // bit 0 is not set, but bit 1 is set:waiting point NodeType = TrainpathNodeType.Stop; } WaitTimeS = (int)((tpn.pathFlags >> 16) & 0xffff); // get the AAAA part. }
public AIPath(TDBFile TDB, TSectionDatFile tsectiondat, string filePath) #endif { PathFile patFile = new PathFile(filePath); pathName = patFile.Name; TrackDB = TDB.TrackDB; TSectionDat = tsectiondat; #if ACTIVITY_EDITOR orRouteConfig = orRouteConf; #endif bool fatalerror = false; if (patFile.TrPathNodes.Count <= 0) { fatalerror = true; Nodes = null; return; } foreach (TrPathNode tpn in patFile.TrPathNodes) { Nodes.Add(new AIPathNode(tpn, patFile.TrackPDPs[(int)tpn.fromPDP], TrackDB, isTimetableMode)); } FirstNode = Nodes[0]; //LastVisitedNode = FirstNode; // Connect the various nodes to each other for (int i = 0; i < Nodes.Count; i++) { AIPathNode node = Nodes[i]; node.Index = i; TrPathNode tpn = patFile.TrPathNodes[i]; // find TVNindex to next main node. if (tpn.HasNextMainNode) { node.NextMainNode = Nodes[(int)tpn.nextMainNode]; node.NextMainTVNIndex = node.FindTVNIndex(node.NextMainNode, TDB, tsectiondat); if (node.JunctionIndex >= 0) { node.IsFacingPoint = TestFacingPoint(node.JunctionIndex, node.NextMainTVNIndex); } if (node.NextMainTVNIndex < 0) { node.NextMainNode = null; Trace.TraceWarning("Cannot find main track for node {1} in path {0}", filePath, i); fatalerror = true; } } // find TVNindex to next siding node if (tpn.HasNextSidingNode) { node.NextSidingNode = Nodes[(int)tpn.nextSidingNode]; node.NextSidingTVNIndex = node.FindTVNIndex(node.NextSidingNode, TDB, tsectiondat); if (node.JunctionIndex >= 0) { node.IsFacingPoint = TestFacingPoint(node.JunctionIndex, node.NextSidingTVNIndex); } if (node.NextSidingTVNIndex < 0) { node.NextSidingNode = null; Trace.TraceWarning("Cannot find siding track for node {1} in path {0}", filePath, i); fatalerror = true; } } if (node.NextMainNode != null && node.NextSidingNode != null) { node.Type = AIPathNodeType.SidingStart; } } FindSidingEnds(); if (fatalerror) { Nodes = null; // invalid path - do not return any nodes } }