2020using System ;
2121using System . Collections . Generic ;
2222using System . IO ;
23+ using System . Diagnostics ;
2324using System . Linq ;
2425using System . Windows . Forms ;
26+ using LibGit2Sharp ;
2527using Microsoft . Xna . Framework ;
2628using Microsoft . Xna . Framework . Graphics ;
2729using Orts . Simulation ;
3032using ORTS . Common ;
3133using ORTS . Common . Input ;
3234using ORTS . Settings ;
35+ using static System . Windows . Forms . VisualStyles . VisualStyleElement ;
36+ using Orts . Formats . Msts ;
37+ using SharpFont ;
38+ using static Orts . Viewer3D . WebServices . TrainCarOperationsWebpage . OperationsSend ;
39+ using SharpDX . Direct3D9 ;
3340
3441namespace Orts . Viewer3D . Popups
3542{
@@ -62,6 +69,14 @@ public class HelpWindow : Window
6269 StreamWriter wDbfEval ; //Debrief eval
6370 public static float DbfEvalDistanceTravelled = 0 ; //Debrief eval
6471
72+ // for Train Info tab
73+ private Train LastPlayerTrain = null ;
74+ private int LastPlayerTrainCarCount = 0 ;
75+ private static Texture2D TrainInfoSpriteSheet = null ;
76+ //private struct CarInfo { public float MassKg; public bool IsEngine; }
77+ //private CarInfo[] CarMass = null; // TODO: Does not need to persist beyond method. Use List<T>(size) instead.
78+ private class CarInfo { public readonly float MassKg ; public readonly bool IsEngine ; public CarInfo ( float mass , bool isEng ) { MassKg = mass ; IsEngine = isEng ; } }
79+
6580 List < TabData > Tabs = new List < TabData > ( ) ;
6681 int ActiveTab ;
6782
@@ -74,7 +89,7 @@ public void LogSeparator(int nCols)
7489 }
7590
7691 public HelpWindow ( WindowManager owner )
77- : base ( owner , Window . DecorationSize . X + owner . TextFontDefault . Height * 37 , Window . DecorationSize . Y + owner . TextFontDefault . Height * 24 , Viewer . Catalog . GetString ( "Help" ) )
92+ : base ( owner , Window . DecorationSize . X + owner . TextFontDefault . Height * 42 , Window . DecorationSize . Y + owner . TextFontDefault . Height * 24 , Viewer . Catalog . GetString ( "Help" ) )
7893 {
7994 Tabs . Add ( new TabData ( Tab . KeyboardShortcuts , Viewer . Catalog . GetString ( "Key Commands" ) , ( cl ) =>
8095 {
@@ -576,6 +591,22 @@ owner.Viewer.Simulator.PlayerLocomotive is MSTSLocomotive &&
576591 scrollbox . Add ( new TextFlow ( scrollbox . RemainingWidth , ( ( MSTSLocomotive ) owner . Viewer . Simulator . PlayerLocomotive ) . EngineOperatingProcedures ) ) ;
577592 }
578593 } ) ) ;
594+ Tabs . Add ( new TabData ( Tab . TrainInfo , Viewer . Catalog . GetString ( "Train Info" ) , ( cl ) =>
595+ {
596+ var labelWidth = cl . TextHeight * 11 ;
597+ var scrollbox = cl . AddLayoutScrollboxVertical ( cl . RemainingWidth ) ;
598+ if ( Owner . Viewer . PlayerTrain != null )
599+ {
600+ string name = Owner . Viewer . PlayerTrain . Name ;
601+ if ( ! String . IsNullOrEmpty ( Owner . Viewer . Simulator . conFileName ) )
602+ name += " (" + Viewer . Catalog . GetString ( "created from" ) + " " + Path . GetFileName ( Owner . Viewer . Simulator . conFileName ) + ")" ;
603+ var line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
604+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Name:" ) , LabelAlignment . Left ) ) ;
605+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , name , LabelAlignment . Left ) ) ;
606+
607+ if ( Owner . Viewer . PlayerTrain . Cars != null ) { AddAggregatedTrainInfo ( Owner . Viewer . PlayerTrain , scrollbox , labelWidth ) ; }
608+ }
609+ } ) ) ;
579610 }
580611
581612 private void ReportEvaluation ( WindowManager owner , ControlLayout cl , TrainCar locomotive , int nmissedstation , string labeltext , int noverspeedcoupling , int dbfstationstopsremaining , Train playerTrain , int colWidth , Label indicator , bool lcurvespeeddependent , bool lbreakcouplers , int ndbfEvalTaskAccomplished )
@@ -1152,6 +1183,198 @@ private void writeline()
11521183 if ( ! lDebriefEvalFile ) wDbfEval . WriteLine ( ) ;
11531184 }
11541185
1186+ /// <summary>
1187+ /// Add aggregated info to the Train Info layout.
1188+ /// Loops through the train cars and aggregates the info.
1189+ /// </summary>
1190+ private void AddAggregatedTrainInfo ( Train playerTrain , ControlLayout scrollbox , int labelWidth )
1191+ {
1192+ string numEngines = "" ; // using "+" between DPU sets
1193+ int numCars = 0 ;
1194+ int numAxles = 0 ;
1195+ var massKg = playerTrain . MassKg ;
1196+ string sectionMass = "" ; // using " + " between wagon sets
1197+ var lengthM = playerTrain . Length ;
1198+ var maxSpeedMps = playerTrain . TrainMaxSpeedMpS ;
1199+ float totPowerW = 0f ;
1200+ string sectionTractiveForce = "" ; // using " + " between DPU sets
1201+ float maxBrakeForceN = 0f ;
1202+ float lowestCouplerStrengthN = 9.999e8f ; // impossible high force
1203+ float lowestDerailForceN = 9.999e8f ; // impossible high force
1204+
1205+ int engCount = 0 ; string countSeparator = "" ; // when set, indicates that subsequent engines are in a separate block
1206+ float engForceN = 0f ; string forceSeparator = "" ; // when set, indicates that subsequent engines are in a separate block
1207+ float wagMassKg = 0f ; string massSeparator = "" ; // when set, indicates that subsequent engines are in a separate block
1208+ int numOperativeBrakes = 0 ;
1209+ bool isMetric = false ; bool isUK = false ; // isImperial* does not seem to be used in simulation
1210+
1211+ if ( TrainInfoSpriteSheet == null ) { TrainInfoSpriteSheet = SharedTextureManager . Get ( Owner . Viewer . RenderProcess . GraphicsDevice , Path . Combine ( Owner . Viewer . ContentPath , "TrainInfoSprites.png" ) ) ; }
1212+ const int spriteWidth = 6 ; const int spriteHeight = 26 ;
1213+ var carInfoList = new List < CarInfo > ( playerTrain . Cars . Count ) ;
1214+
1215+ foreach ( var car in playerTrain . Cars )
1216+ {
1217+ // ignore (legacy) EOT
1218+ if ( car . WagonType == TrainCar . WagonTypes . EOT || car . CarLengthM < 1.1f ) { continue ; }
1219+
1220+ var wag = car is MSTSWagon ? ( MSTSWagon ) car : null ;
1221+ var eng = car is MSTSLocomotive ? ( MSTSLocomotive ) car : null ;
1222+
1223+ var isEng = ( car . WagonType == TrainCar . WagonTypes . Engine && eng != null && eng . MaxForceN > 25000 ) ; // count legacy driving trailers as wagons
1224+
1225+ if ( car . IsMetric ) { isMetric = true ; } ; if ( car . IsUK ) { isUK = true ; }
1226+
1227+ if ( isEng )
1228+ {
1229+ engCount ++ ;
1230+ numAxles += eng . LocoNumDrvAxles + eng . GetWagonNumAxles ( ) ;
1231+ totPowerW += eng . MaxPowerW ;
1232+ engForceN += eng . MaxForceN ;
1233+
1234+ // hanlde transition from wagons to engines
1235+ if ( wagMassKg > 0 )
1236+ {
1237+ sectionMass += massSeparator + FormatStrings . FormatLargeMass ( wagMassKg , isMetric , isUK ) ;
1238+ wagMassKg = 0f ; massSeparator = " + " ;
1239+ }
1240+ }
1241+ else if ( wag != null )
1242+ {
1243+ numCars ++ ;
1244+ numAxles += wag . GetWagonNumAxles ( ) ;
1245+ wagMassKg += wag . MassKG ;
1246+
1247+ // handle transition from engines to wagons
1248+ if ( engCount > 0 || ( engCount == 0 && numCars == 0 ) )
1249+ {
1250+ numEngines += countSeparator + engCount . ToString ( ) ;
1251+ engCount = 0 ; countSeparator = "+" ;
1252+ sectionTractiveForce += forceSeparator + FormatStrings . FormatForce ( engForceN , isMetric ) ;
1253+ engForceN = 0f ; forceSeparator = " + " ;
1254+ }
1255+ }
1256+
1257+ // wag and eng
1258+ if ( wag != null )
1259+ {
1260+ maxBrakeForceN += wag . MaxBrakeForceN ;
1261+ var couplerStrength = GetMinCouplerStrenght ( wag ) ;
1262+ if ( couplerStrength < lowestCouplerStrengthN ) { lowestCouplerStrengthN = couplerStrength ; }
1263+ var derailForce = GetDerailForce ( wag ) ;
1264+ if ( derailForce < lowestDerailForceN ) { lowestDerailForceN = derailForce ; }
1265+ if ( wag . MaxBrakeForceN > 0 ) { numOperativeBrakes ++ ; }
1266+
1267+ carInfoList . Add ( new CarInfo ( wag . MassKG , isEng ) ) ;
1268+ }
1269+ }
1270+
1271+ if ( engCount > 0 ) { numEngines = numEngines + countSeparator + engCount . ToString ( ) ; }
1272+ if ( String . IsNullOrEmpty ( numEngines ) ) { numEngines = "0" ; }
1273+ if ( engForceN > 0 ) { sectionTractiveForce += forceSeparator + FormatStrings . FormatForce ( engForceN , isMetric ) ; }
1274+ if ( wagMassKg > 0 ) { sectionMass += massSeparator + FormatStrings . FormatLargeMass ( wagMassKg , isMetric , isUK ) ; }
1275+
1276+ var line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1277+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Number of Engines:" ) , LabelAlignment . Left ) ) ;
1278+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , numEngines , LabelAlignment . Left ) ) ;
1279+
1280+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1281+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Number of Cars:" ) , LabelAlignment . Left ) ) ;
1282+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , numCars . ToString ( ) , LabelAlignment . Left ) ) ;
1283+
1284+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1285+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Number of Axles:" ) , LabelAlignment . Left ) ) ;
1286+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , numAxles . ToString ( ) , LabelAlignment . Left ) ) ;
1287+
1288+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1289+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Total Weight:" ) , LabelAlignment . Left ) ) ;
1290+ string massValue = FormatStrings . FormatLargeMass ( massKg , isMetric , isUK ) + " (" + sectionMass + ")" ;
1291+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , massValue , LabelAlignment . Left ) ) ;
1292+
1293+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1294+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Total Length:" ) , LabelAlignment . Left ) ) ;
1295+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , FormatStrings . FormatShortDistanceDisplay ( lengthM , isMetric ) , LabelAlignment . Left ) ) ;
1296+
1297+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1298+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Maximum Speed:" ) , LabelAlignment . Left ) ) ;
1299+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , FormatStrings . FormatSpeedLimit ( maxSpeedMps , isMetric ) , LabelAlignment . Left ) ) ;
1300+
1301+ scrollbox . AddHorizontalSeparator ( ) ;
1302+
1303+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1304+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Total Power:" ) , LabelAlignment . Left ) ) ;
1305+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , FormatStrings . FormatPower ( totPowerW , isMetric , false , false ) , LabelAlignment . Left ) ) ;
1306+
1307+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1308+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Max Tractive Effort:" ) , LabelAlignment . Left ) ) ;
1309+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , sectionTractiveForce , LabelAlignment . Left ) ) ;
1310+
1311+ if ( ! isMetric )
1312+ {
1313+ float hpt = massKg > 0f ? W . ToHp ( totPowerW ) / Kg . ToTUS ( massKg ) : 0f ;
1314+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1315+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Horespower per Ton:" ) , LabelAlignment . Left ) ) ;
1316+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , string . Format ( "{0:0.0}" , hpt ) , LabelAlignment . Left ) ) ;
1317+
1318+ float tpob = numOperativeBrakes > 0 ? Kg . ToTUS ( massKg ) / numOperativeBrakes : 0 ;
1319+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1320+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Tons per Operative Brake:" ) , LabelAlignment . Left ) ) ;
1321+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , string . Format ( "{0:0}" , tpob ) , LabelAlignment . Left ) ) ;
1322+ }
1323+
1324+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1325+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Lowest Coupler Strength:" ) , LabelAlignment . Left ) ) ;
1326+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , FormatStrings . FormatForce ( lowestCouplerStrengthN , isMetric ) , LabelAlignment . Left ) ) ;
1327+
1328+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1329+ line . Add ( new Label ( labelWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Lowest Derail Force:" ) , LabelAlignment . Left ) ) ;
1330+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , FormatStrings . FormatForce ( lowestDerailForceN , isMetric ) , LabelAlignment . Left ) ) ;
1331+
1332+ scrollbox . AddHorizontalSeparator ( ) ;
1333+
1334+ // weight graph
1335+ line = scrollbox . AddLayoutHorizontalLineOfText ( ) ;
1336+ line . Add ( new Label ( line . RemainingWidth , line . RemainingHeight , Viewer . Catalog . GetString ( "Car Weight (front on left):" ) , LabelAlignment . Left ) ) ;
1337+ scrollbox . AddSpace ( scrollbox . RemainingWidth , 2 ) ;
1338+ var hscrollbox = scrollbox . AddLayoutScrollboxHorizontal ( SystemInformation . HorizontalScrollBarHeight + spriteHeight + 2 ) ;
1339+ var vbox = hscrollbox . AddLayoutVertical ( carInfoList . Count * spriteWidth + 2 ) ;
1340+ var weightbox = vbox . AddLayoutHorizontal ( spriteHeight + 2 ) ;
1341+ foreach ( var car in carInfoList )
1342+ {
1343+ int spriteIdx = ( int ) Math . Floor ( car . MassKg / 15000f ) ;
1344+ if ( spriteIdx < 0 ) { spriteIdx = 0 ; } else if ( spriteIdx > 11 ) { spriteIdx = 11 ; }
1345+ var image = new Image ( spriteWidth , spriteHeight ) ;
1346+ if ( car . IsEngine ) { image . Source = new Rectangle ( spriteIdx * spriteWidth , 0 , spriteWidth , spriteHeight ) ; }
1347+ else { image . Source = new Rectangle ( spriteIdx * spriteWidth , spriteHeight , spriteWidth , spriteHeight ) ; }
1348+ image . Texture = TrainInfoSpriteSheet ;
1349+ weightbox . Add ( image ) ;
1350+ }
1351+ }
1352+
1353+ /// <summary>
1354+ /// Get the lowest coupler strength for a car.
1355+ /// </summary>
1356+ private float GetMinCouplerStrenght ( MSTSWagon wag )
1357+ {
1358+ float couplerStrength = 1e10f ; // default from TrainCar.GetCouplerBreak2N()
1359+ if ( wag . Couplers . Count > 1 && wag . Couplers [ 1 ] . Break2N < couplerStrength ) { couplerStrength = wag . Couplers [ 1 ] . Break2N ; }
1360+ else if ( wag . Couplers . Count > 0 && wag . Couplers [ 0 ] . Break2N < couplerStrength ) { couplerStrength = wag . Couplers [ 0 ] . Break2N ; }
1361+ if ( couplerStrength < 99f ) { couplerStrength = 1e10f ; } // use default if near zero
1362+ return couplerStrength ;
1363+ }
1364+
1365+ /// <summary>
1366+ /// Calculate the lowest force it takes to derail the car (on a curve).
1367+ /// It is equivalent to the vertical force at the wheel.
1368+ /// </summary>
1369+ private float GetDerailForce ( MSTSWagon wag )
1370+ {
1371+ // see TotalWagonVerticalDerailForceN in TrainCar.cs
1372+ float derailForce = 2.0e5f ; // 45k lbf on wheel
1373+ int numWheels = wag . GetWagonNumAxles ( ) * 2 ;
1374+ if ( numWheels > 2 ) { derailForce = wag . MassKG / numWheels * wag . GetGravitationalAccelerationMpS2 ( ) ; }
1375+ return derailForce ;
1376+ }
1377+
11551378 public override void TabAction ( )
11561379 {
11571380 ActiveTab = ( ActiveTab + 1 ) % Tabs . Count ;
@@ -1195,6 +1418,7 @@ enum Tab
11951418 ActivityEvaluation ,
11961419 TimetableBriefing ,
11971420 LocomotiveProcedures ,
1421+ TrainInfo ,
11981422 }
11991423
12001424 class TabData
@@ -1253,6 +1477,14 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull)
12531477 Layout ( ) ;
12541478 }
12551479 }
1480+ else if ( Tabs [ ActiveTab ] . Tab == Tab . TrainInfo && Owner . Viewer . PlayerTrain != null )
1481+ {
1482+ if ( LastPlayerTrain == null || Owner . Viewer . PlayerTrain != LastPlayerTrain || LastPlayerTrainCarCount != Owner . Viewer . PlayerTrain . Cars . Count )
1483+ {
1484+ LastPlayerTrain = Owner . Viewer . PlayerTrain ; LastPlayerTrainCarCount = Owner . Viewer . PlayerTrain . Cars . Count ;
1485+ Layout ( ) ;
1486+ }
1487+ }
12561488 if ( this . ActivityUpdated == true ) //true value is set in ActivityWindow.cs
12571489 {
12581490 this . ActivityUpdated = false ;
0 commit comments