diff --git a/Source/Documentation/Manual/driving.rst b/Source/Documentation/Manual/driving.rst index 7e234d7563..9f3ef9ac54 100644 --- a/Source/Documentation/Manual/driving.rst +++ b/Source/Documentation/Manual/driving.rst @@ -676,20 +676,17 @@ Dispatcher Window ================= The dispatcher window is a very useful tool to monitor and control train -operation. The :ref:`Dispatcher window ` option -must be selected. - -The dispatcher window is actually created by pressing ````. The -window is created in a minimized state, so to display it in front of the OR -window you must click on ```` and select the dispatcher window -icon, or click on one of the OR icons in the taskbar. If you are running OR -in full-screen mode, you must also have the :ref:`Fast full screen Alt+Tab -` option selected to have both the OR and the -dispatcher windows displayed at the same time. After the dispatcher window -has been selected with ````, successive Alt_Tabs will toggle -between the OR window and the dispatcher window. +operation. operation. To use it, select the :ref:`Dispatcher window +` option before you start the route. + +Open the dispatcher window by pressing ````. You can toggle +between the OR window and the dispatcher window by pressing ```` . + +If you have unchecked the option ``Windowed``, then the game window opens +full screen and the dispatch window opens out of sight behind it. You can +prevent this by checking the option Fast full-screen alt-tab in Options > Video. -The dispatcher window is resizable and can also be maximized, e.g. on a +The dispatcher window is not resizable but can be maximized, e.g. on a second display. You can define the level of zoom either by changing the value within the ``Res`` box or by using the mouse wheel. You can pan through the route by moving the mouse while pressing the left button. You diff --git a/Source/Documentation/Manual/operation.rst b/Source/Documentation/Manual/operation.rst index f6cbc344d4..d7b76bf787 100644 --- a/Source/Documentation/Manual/operation.rst +++ b/Source/Documentation/Manual/operation.rst @@ -1800,6 +1800,10 @@ On the other hand, a horn blow waiting point may be positioned just after a normal WP (thus achieving the effect that the train blows the horn when it restarts). +If the lead locomotive of the AI train has parameter DoesHornTriggerBell +set to 1 in the .eng file, the bell is played for further 30 seconds after +the end of the horn blow. + To implement this feature it is not necessary to proceed as described in the first three paragraphs of this chapter. It is enough to insert the waiting points within the paths with either the MSTS AE or through TrackViewer. @@ -1824,6 +1828,10 @@ the route with TrackViewer allows identification of the true level crossings. If a horn blow is also desired for a *simple* road crossing, the feature *AI Train Horn Blow* described above must be used. +If the lead locomotive of the AI train has parameter DoesHornTriggerBell +set to 1 in the .eng file, the bell is played for further 30 seconds after +the end of the horn blow. + .. _operation-event-triggered-by-ai-train: Location Event triggered by AI Train diff --git a/Source/Menu/Options.Designer.cs b/Source/Menu/Options.Designer.cs index 89e92e9291..2d75d4909c 100644 --- a/Source/Menu/Options.Designer.cs +++ b/Source/Menu/Options.Designer.cs @@ -41,6 +41,7 @@ private void InitializeComponent() this.checkViewDispatcher = new System.Windows.Forms.CheckBox(); this.tabOptions = new System.Windows.Forms.TabControl(); this.tabPageGeneral = new System.Windows.Forms.TabPage(); + this.checkEnableWebServer = new System.Windows.Forms.CheckBox(); this.checkSpeedControl = new System.Windows.Forms.CheckBox(); this.checkDisableTCSScripts = new System.Windows.Forms.CheckBox(); this.labelOtherUnits = new System.Windows.Forms.Label(); @@ -187,6 +188,8 @@ private void InitializeComponent() this.ElevationText = new System.Windows.Forms.Label(); this.checkPreferDDSTexture = new System.Windows.Forms.CheckBox(); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.numericWebServerPort = new System.Windows.Forms.NumericUpDown(); + this.label28 = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.numericBrakePipeChargingRate)).BeginInit(); this.tabOptions.SuspendLayout(); this.tabPageGeneral.SuspendLayout(); @@ -227,6 +230,7 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.numericSuperElevationGauge)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numericSuperElevationMinLen)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.numericUseSuperElevation)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericWebServerPort)).BeginInit(); this.SuspendLayout(); // // buttonOK @@ -347,6 +351,9 @@ private void InitializeComponent() // // tabPageGeneral // + this.tabPageGeneral.Controls.Add(this.label28); + this.tabPageGeneral.Controls.Add(this.numericWebServerPort); + this.tabPageGeneral.Controls.Add(this.checkEnableWebServer); this.tabPageGeneral.Controls.Add(this.checkSpeedControl); this.tabPageGeneral.Controls.Add(this.checkDisableTCSScripts); this.tabPageGeneral.Controls.Add(this.labelOtherUnits); @@ -372,6 +379,16 @@ private void InitializeComponent() this.tabPageGeneral.Text = "General"; this.tabPageGeneral.UseVisualStyleBackColor = true; // + // checkEnableWebServer + // + this.checkEnableWebServer.AutoSize = true; + this.checkEnableWebServer.Location = new System.Drawing.Point(8, 315); + this.checkEnableWebServer.Name = "checkEnableWebServer"; + this.checkEnableWebServer.Size = new System.Drawing.Size(116, 17); + this.checkEnableWebServer.TabIndex = 15; + this.checkEnableWebServer.Text = "Enable WebServer"; + this.checkEnableWebServer.UseVisualStyleBackColor = true; + // // checkSpeedControl // this.checkSpeedControl.AutoSize = true; @@ -2241,6 +2258,37 @@ private void InitializeComponent() this.checkPreferDDSTexture.Text = "Load DDS textures in preference to ACE"; this.checkPreferDDSTexture.UseVisualStyleBackColor = true; // + // numericWebServerPort + // + this.numericWebServerPort.Location = new System.Drawing.Point(8, 338); + this.numericWebServerPort.Maximum = new decimal(new int[] { + 65534, + 0, + 0, + 0}); + this.numericWebServerPort.Minimum = new decimal(new int[] { + 1025, + 0, + 0, + 0}); + this.numericWebServerPort.Name = "numericWebServerPort"; + this.numericWebServerPort.Size = new System.Drawing.Size(70, 20); + this.numericWebServerPort.TabIndex = 16; + this.numericWebServerPort.Value = new decimal(new int[] { + 1025, + 0, + 0, + 0}); + // + // label28 + // + this.label28.AutoSize = true; + this.label28.Location = new System.Drawing.Point(89, 340); + this.label28.Name = "label28"; + this.label28.Size = new System.Drawing.Size(66, 13); + this.label28.TabIndex = 17; + this.label28.Text = "Port Number"; + // // OptionsForm // this.AcceptButton = this.buttonOK; @@ -2308,6 +2356,7 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.numericSuperElevationGauge)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numericSuperElevationMinLen)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.numericUseSuperElevation)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.numericWebServerPort)).EndInit(); this.ResumeLayout(false); } @@ -2470,5 +2519,8 @@ private void InitializeComponent() private System.Windows.Forms.NumericUpDown numericActWeatherRandomizationLevel; private System.Windows.Forms.Label label26; private System.Windows.Forms.CheckBox checkShadowAllShapes; + private System.Windows.Forms.CheckBox checkEnableWebServer; + private System.Windows.Forms.NumericUpDown numericWebServerPort; + private System.Windows.Forms.Label label28; } } diff --git a/Source/Menu/Options.cs b/Source/Menu/Options.cs index 03c80f0b6b..3608b2eeef 100644 --- a/Source/Menu/Options.cs +++ b/Source/Menu/Options.cs @@ -148,9 +148,11 @@ public OptionsForm(UserSettings settings, UpdateManager updateManager, bool init comboPressureUnit.Text = Settings.PressureUnit; comboBoxOtherUnits.Text = settings.Units; checkDisableTCSScripts.Checked = Settings.DisableTCSScripts; - + checkEnableWebServer.Checked = Settings.WebServer; + numericWebServerPort.Value = Settings.WebServerPort; // Audio tab + checkMSTSBINSound.Checked = Settings.MSTSBINSound; numericSoundVolumePercent.Value = Settings.SoundVolumePercent; numericSoundDetailLevel.Value = Settings.SoundDetailLevel; @@ -436,6 +438,7 @@ void buttonOK_Click(object sender, EventArgs e) Settings.PressureUnit = comboPressureUnit.SelectedValue.ToString(); Settings.Units = comboBoxOtherUnits.SelectedValue.ToString(); Settings.DisableTCSScripts = checkDisableTCSScripts.Checked; + Settings.WebServer = checkEnableWebServer.Checked; // Audio tab Settings.MSTSBINSound = checkMSTSBINSound.Checked; @@ -739,5 +742,6 @@ private void checkPerformanceTuner_Click(object sender, EventArgs e) numericPerformanceTunerTarget.Enabled = checkPerformanceTuner.Checked; labelPerformanceTunerTarget.Enabled = checkPerformanceTuner.Checked; } + } } \ No newline at end of file diff --git a/Source/Menu/Options.resx b/Source/Menu/Options.resx index 7b9a6ebb22..443fce5375 100644 --- a/Source/Menu/Options.resx +++ b/Source/Menu/Options.resx @@ -112,18 +112,18 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + 17, 17 - + 114, 17 - + 17, 17 \ No newline at end of file diff --git a/Source/ORTS.Settings/UserSettings.cs b/Source/ORTS.Settings/UserSettings.cs index b8efe06895..129d78d0d1 100644 --- a/Source/ORTS.Settings/UserSettings.cs +++ b/Source/ORTS.Settings/UserSettings.cs @@ -112,6 +112,12 @@ public enum Menu_SelectionIndex public int Multiplayer_Port { get; set; } // General settings: + + [Default(false)] + public bool WebServer { get; set; } + [Default(2150)] + public int WebServerPort { get; set; } + [Default(false)] public bool Alerter { get; set; } [Default(true)] diff --git a/Source/Orts.Simulation/Simulation/AIs/AIAuxAction.cs b/Source/Orts.Simulation/Simulation/AIs/AIAuxAction.cs index 29e865415f..7068ae53a3 100644 --- a/Source/Orts.Simulation/Simulation/AIs/AIAuxAction.cs +++ b/Source/Orts.Simulation/Simulation/AIs/AIAuxAction.cs @@ -144,16 +144,44 @@ public void Save(BinaryWriter outf, int currentClock) { if (ThisTrain is AITrain && ((aiTrain.MovementState == AITrain.AI_MOVEMENT_STATE.HANDLE_ACTION && aiTrain.nextActionInfo != null && - aiTrain.nextActionInfo.NextAction == AIActionItem.AI_ACTION_TYPE.AUX_ACTION && aiTrain.nextActionInfo != null && aiTrain.nextActionInfo is AuxActionWPItem) + aiTrain.nextActionInfo.NextAction == AIActionItem.AI_ACTION_TYPE.AUX_ACTION && aiTrain.nextActionInfo is AuxActionWPItem) || ( aiTrain.AuxActionsContain.SpecAuxActions.Count > 0 && aiTrain.AuxActionsContain.SpecAuxActions[0] is AIActionWPRef && (aiTrain.AuxActionsContain.SpecAuxActions[0] as AIActionWPRef).keepIt != null && (aiTrain.AuxActionsContain.SpecAuxActions[0] as AIActionWPRef).keepIt.currentMvmtState == AITrain.AI_MOVEMENT_STATE.HANDLE_ACTION))) // WP is running { - int remainingDelay; - if (aiTrain.nextActionInfo != null && aiTrain.nextActionInfo is AuxActionWPItem) remainingDelay = ((AuxActionWPItem)aiTrain.nextActionInfo).ActualDepart - currentClock; - else remainingDelay = ((AIActionWPRef)SpecAuxActions[0]).keepIt.ActualDepart - currentClock; - ((AIActionWPRef)SpecAuxActions[0]).SetDelay(remainingDelay); + // Do nothing if it is an absolute WP + if (!(aiTrain.AuxActionsContain.SpecAuxActions.Count > 0 && aiTrain.AuxActionsContain.SpecAuxActions[0] is AIActionWPRef && + (aiTrain.AuxActionsContain.SpecAuxActions[0] as AIActionWPRef).Delay >= 30000 && (aiTrain.AuxActionsContain.SpecAuxActions[0] as AIActionWPRef).Delay < 40000)) + { + int remainingDelay; + if (aiTrain.nextActionInfo != null && aiTrain.nextActionInfo is AuxActionWPItem) remainingDelay = ((AuxActionWPItem)aiTrain.nextActionInfo).ActualDepart - currentClock; + else remainingDelay = ((AIActionWPRef)SpecAuxActions[0]).keepIt.ActualDepart - currentClock; + ((AIActionWPRef)SpecAuxActions[0]).SetDelay(remainingDelay); + } + } + } + // check for horn actions + if (ThisTrain is AITrain && aiTrain.AuxActionsContain.specRequiredActions.Count > 0) + { + foreach (AITrain.DistanceTravelledItem specRequiredAction in aiTrain.AuxActionsContain.specRequiredActions) + { + if (specRequiredAction is AuxActionHornItem) + { + if (SpecAuxActions.Count > 0) + { + foreach (AuxActionRef specAuxAction in SpecAuxActions) + { + if (specAuxAction is AIActionHornRef) + { + (specAuxAction as AIActionHornRef).Delay = (specRequiredAction as AuxActionHornItem).ActualDepart - currentClock; + break; + } + } + break; + } + else break; + } } } foreach (var action in SpecAuxActions) @@ -1901,6 +1929,7 @@ public class AuxActionHornItem : AuxActionItem int Delay; [JsonIgnore] public int ActualDepart; + private const int BellPlayTime = 30; //================================================================================================// @@ -1977,6 +2006,15 @@ public override AITrain.AI_MOVEMENT_STATE InitAction(Train thisTrain, int presen Processing = true; int correctedTime = presentTime; ActualDepart = correctedTime + Delay; + if (!Triggered) + { +#if WITH_PATH_DEBUG + File.AppendAllText(@"C:\temp\checkpath.txt", "Do Horn for AITRain " + thisTrain.Number + " , mvt state " + movementState.ToString() + " at " + presentTime + "\n"); +#endif + TrainCar locomotive = thisTrain.FindLeadLocomotive(); + ((MSTSLocomotive)locomotive).ManualHorn = true; + Triggered = true; + } return AITrain.AI_MOVEMENT_STATE.HANDLE_ACTION; } @@ -1984,29 +2022,27 @@ public override AITrain.AI_MOVEMENT_STATE HandleAction(Train thisTrain, int pres { if (ActualDepart >= presentTime) { - if (!Triggered) - { -#if WITH_PATH_DEBUG - File.AppendAllText(@"C:\temp\checkpath.txt", "Do Horn for AITRain " + thisTrain.Number + " , mvt state " + movementState.ToString() + " at " + presentTime + "\n"); -#endif - TrainCar locomotive = thisTrain.FindLeadLocomotive(); - ((MSTSLocomotive)locomotive).ManualHorn = true; - Triggered = true; - } movementState = AITrain.AI_MOVEMENT_STATE.HANDLE_ACTION; } else { - thisTrain.AuxActionsContain.Remove(this); - + TrainCar locomotive = thisTrain.FindLeadLocomotive(); if (Triggered) { #if WITH_PATH_DEBUG File.AppendAllText(@"C:\temp\checkpath.txt", "Stop Horn for AITRain " + thisTrain.Number + " : mvt state " + movementState.ToString() + " at " + presentTime + "\n"); #endif - TrainCar locomotive = thisTrain.FindLeadLocomotive(); ((MSTSLocomotive)locomotive).ManualHorn = false; + Triggered = false; } + if (((MSTSLocomotive)locomotive).DoesHornTriggerBell && ActualDepart + BellPlayTime >= presentTime) + { + movementState = AITrain.AI_MOVEMENT_STATE.HANDLE_ACTION; + return movementState; + } + else if (((MSTSLocomotive)locomotive).DoesHornTriggerBell && ActualDepart + BellPlayTime < presentTime) + ((MSTSLocomotive)locomotive).BellState = MSTSLocomotive.SoundState.Stopped; + thisTrain.AuxActionsContain.Remove(this); return currentMvmtState; // Restore previous MovementState } return movementState; diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs index dc5f5280b0..dc2e8f952b 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs @@ -1403,6 +1403,7 @@ public override void Update(float elapsedClockSeconds) ApplyDirectionToMotiveForce(); + // Update dynamic brake force if (DynamicBrakePercent > 0 && DynamicBrakeForceCurves != null && AbsSpeedMpS > 0) { float f = DynamicBrakeForceCurves.Get(.01f * DynamicBrakePercent, AbsSpeedMpS); @@ -1415,7 +1416,8 @@ public override void Update(float elapsedClockSeconds) DynamicBrakeForceN = 0f; } } -// else if (DynamicBrakePercent == -1) DynamicBrakeForceN = 0; + else + DynamicBrakeForceN = 0; // Set dynamic brake force to zero if in Notch 0 position UpdateFrictionCoefficient(elapsedClockSeconds); // Find the current coefficient of friction depending upon the weather @@ -3796,6 +3798,10 @@ public virtual float GetDataOf(CabViewControl cvc) data = this.FilteredMotiveForceN; else data = this.LocomotiveAxle.AxleForceN; + if (DynamicBrakePercent > 0) + { + data = DynamicBrakeForceN; + } data = Math.Abs(data); switch (cvc.Units) { diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index a5024d7739..d4d7b0c3cc 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -1511,8 +1511,9 @@ public virtual string GetDebugStatus() AcceptMUSignals ? Simulator.Catalog.GetString("Yes") : Simulator.Catalog.GetString("No"), ThrottlePercent, String.Format("{0}{1}", FormatStrings.FormatSpeedDisplay(SpeedMpS, IsMetric), WheelSlip ? "!!!" : ""), - FormatStrings.FormatPower(MotiveForceN * SpeedMpS, IsMetric, false, false), - String.Format("{0}{1}", FormatStrings.FormatForce(MotiveForceN, IsMetric), CouplerExceedBreakLimit ? "???" : "")); + // For Locomotive HUD display shows "forward" motive power (& force) as a positive value, braking power (& force) will be shown as negative values. + FormatStrings.FormatPower((MotiveForceN - DynamicBrakeForceN) * SpeedMpS, IsMetric, false, false), + String.Format("{0}{1}", FormatStrings.FormatForce((MotiveForceN - DynamicBrakeForceN), IsMetric), CouplerExceedBreakLimit ? "???" : "")); } public virtual string GetTrainBrakeStatus() { return null; } public virtual string GetEngineBrakeStatus() { return null; } diff --git a/Source/RunActivity/Content/Web/HUD.html b/Source/RunActivity/Content/Web/HUD.html new file mode 100644 index 0000000000..48fdb20c99 --- /dev/null +++ b/Source/RunActivity/Content/Web/HUD.html @@ -0,0 +1,52 @@ + + + + OPEN RAILS - Heads Up Display - Common Page + + + + + + + +

Open Rails - Heads Up Display

+
+

+
+
+ + + + + + diff --git a/Source/RunActivity/Content/Web/css/hud.css b/Source/RunActivity/Content/Web/css/hud.css new file mode 100644 index 0000000000..2839e1b3af --- /dev/null +++ b/Source/RunActivity/Content/Web/css/hud.css @@ -0,0 +1,26 @@ +ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #333; +} +li { + float: left; + border-right:1px solid #bbb; +} +li:last-child { + border-right: none; +} +li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} +li a:hover:not(.active) { + background-color: #111; +} + + diff --git a/Source/RunActivity/Content/Web/css/sample.css b/Source/RunActivity/Content/Web/css/sample.css new file mode 100644 index 0000000000..2839e1b3af --- /dev/null +++ b/Source/RunActivity/Content/Web/css/sample.css @@ -0,0 +1,26 @@ +ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #333; +} +li { + float: left; + border-right:1px solid #bbb; +} +li:last-child { + border-right: none; +} +li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} +li a:hover:not(.active) { + background-color: #111; +} + + diff --git a/Source/RunActivity/Content/Web/css/trackmonitor.css b/Source/RunActivity/Content/Web/css/trackmonitor.css new file mode 100644 index 0000000000..2839e1b3af --- /dev/null +++ b/Source/RunActivity/Content/Web/css/trackmonitor.css @@ -0,0 +1,26 @@ +ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #333; +} +li { + float: left; + border-right:1px solid #bbb; +} +li:last-child { + border-right: none; +} +li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} +li a:hover:not(.active) { + background-color: #111; +} + + diff --git a/Source/RunActivity/Content/Web/images/or_logo.png b/Source/RunActivity/Content/Web/images/or_logo.png new file mode 100644 index 0000000000..4023551997 Binary files /dev/null and b/Source/RunActivity/Content/Web/images/or_logo.png differ diff --git a/Source/RunActivity/Content/Web/images/waverley.jpg b/Source/RunActivity/Content/Web/images/waverley.jpg new file mode 100644 index 0000000000..eb13ed22c7 Binary files /dev/null and b/Source/RunActivity/Content/Web/images/waverley.jpg differ diff --git a/Source/RunActivity/Content/Web/images/zig-zag.jpg b/Source/RunActivity/Content/Web/images/zig-zag.jpg new file mode 100644 index 0000000000..6aa99c447f Binary files /dev/null and b/Source/RunActivity/Content/Web/images/zig-zag.jpg differ diff --git a/Source/RunActivity/Content/Web/index - ApiSample.html b/Source/RunActivity/Content/Web/index - ApiSample.html new file mode 100644 index 0000000000..9e297fa70d --- /dev/null +++ b/Source/RunActivity/Content/Web/index - ApiSample.html @@ -0,0 +1,38 @@ + + + + OPEN RAILS - Sample API + + + + + +

Open Rails Web Server - Api Sample

+ + + Sample String Data: +

+ Sample Integer Data: +

+ Date Data: +

+ + String Array Members: +
+
+
+
+

+ + Embedded Class:
+ Embedded String Data: +

+ Embedded Integer Data: +

+ + + + + + + diff --git a/Source/RunActivity/Content/Web/index.html b/Source/RunActivity/Content/Web/index.html new file mode 100644 index 0000000000..ac7c2106b2 --- /dev/null +++ b/Source/RunActivity/Content/Web/index.html @@ -0,0 +1,18 @@ + + + + OPEN RAILS - TrainInfo API + + + + + +

Open Rails Web Server - Api TrainInfo

+ +

+ + + + + + diff --git a/Source/RunActivity/Content/Web/js/ApiSample.js b/Source/RunActivity/Content/Web/js/ApiSample.js new file mode 100644 index 0000000000..284449a278 --- /dev/null +++ b/Source/RunActivity/Content/Web/js/ApiSample.js @@ -0,0 +1,27 @@ +var PageNo = 0; +var hr = new XMLHttpRequest; + +function ApiSample() { + hr.open("POST", "/API/APISAMPLE", true); + hr.send("pageno=" + PageNo); + hr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var obj = JSON.parse(hr.responseText); + strData.innerHTML = obj.strData; + intData.innerHTML = obj.intData; + dateData.innerHTML = obj.dateData; + + arrayData = obj.strArrayData; + + arrayMember0.innerHTML = obj.strArrayData[0]; + arrayMember1.innerHTML = obj.strArrayData[1]; + arrayMember2.innerHTML = obj.strArrayData[2]; + arrayMember3.innerHTML = obj.strArrayData[3]; + arrayMember4.innerHTML = obj.strArrayData[4]; + + embeddedStr.innerHTML = obj.embedded.Str; + embeddedNumb.innerHTML = obj.embedded.Numb; + + } + } +} diff --git a/Source/RunActivity/Content/Web/js/ApiTrainInfo.js b/Source/RunActivity/Content/Web/js/ApiTrainInfo.js new file mode 100644 index 0000000000..5b91bfd1d7 --- /dev/null +++ b/Source/RunActivity/Content/Web/js/ApiTrainInfo.js @@ -0,0 +1,15 @@ +var PageNo = 0; +var hr = new XMLHttpRequest; + +function ApiTrainInfo() { + hr.open("POST", "/API/TRAININFO", true); + hr.send("pageno=" + PageNo); + hr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var obj = JSON.parse(hr.responseText); + + strTrainInfoData.innerHTML = obj.allowedSpeedMps; + + } + } +} \ No newline at end of file diff --git a/Source/RunActivity/Content/Web/js/hud.js b/Source/RunActivity/Content/Web/js/hud.js new file mode 100644 index 0000000000..b13effccb9 --- /dev/null +++ b/Source/RunActivity/Content/Web/js/hud.js @@ -0,0 +1,55 @@ +var PageNo = 0; +var hr = new XMLHttpRequest; + + +function HeadsUp () { + hr.open("POST", "/API/HUD", true); + hr.send("pageno="+PageNo); + hr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var obj = JSON.parse(hr.responseText); + var Rows = obj.commonTable.nRows; + var Cols = obj.commonTable.nCols; + Str = ""; + var next = 0; + for (var row = 0; row < obj.commonTable.nRows; ++row) { + Str += ""; + for (var col=0; col < obj.commonTable.nCols; ++col) { + if (obj.commonTable.values[next] == null) { + Str += ""; + } + else { + Str += ""; + } + ++next; + } + Str += ""; + } + Str += "
" + obj.commonTable.values[next] + "
"; + HUDCommon.innerHTML = Str; + + if (obj.nTables == 2) { + var Rows = obj.extraTable.nRows; + var Cols = obj.extraTable.nCols; + next = 0; + Str = ""; + for (var row = 0; row < obj.extraTable.nRows; ++row) { + Str += ""; + for (var col=0; col < obj.extraTable.nCols; ++col) { + if (obj.extraTable.values[next] == null) { + Str += ""; + } + else { + Str += ""; + } + ++next; + } + Str += ""; + } + Str += "
" + obj.extraTable.values[next] + "
"; + HUDExtra.innerHTML = Str; + } + } + } +} + diff --git a/Source/RunActivity/Content/Web/js/trackmonitor.js b/Source/RunActivity/Content/Web/js/trackmonitor.js new file mode 100644 index 0000000000..337a8aca0c --- /dev/null +++ b/Source/RunActivity/Content/Web/js/trackmonitor.js @@ -0,0 +1,16 @@ +var PageNo = 0; +var hr = new XMLHttpRequest; + + +function TrackMonitor () { + hr.open("POST", "/API/TRACKMONITOR", true); + hr.send("pageno=" + PageNo); + hr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var obj = JSON.parse(hr.responseText); + var Str = obj.str; + common.innerHTML = Str; + } + } +} + diff --git a/Source/RunActivity/Program.cs b/Source/RunActivity/Program.cs index ee9cace713..4da52d4682 100644 --- a/Source/RunActivity/Program.cs +++ b/Source/RunActivity/Program.cs @@ -54,6 +54,8 @@ static void Main(string[] args) var options = args.Where(a => a.StartsWith("-") || a.StartsWith("/")).Select(a => a.Substring(1)); var settings = new UserSettings(options); + //settings.WebServer = true; + //enables loading of dll for specific architecture(32 or 64bit) from distinct folders, useful when both versions require same name (as for OpenAL32.dll) string path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Native"); path = Path.Combine(path, (Environment.Is64BitProcess) ? "X64" : "X86"); diff --git a/Source/RunActivity/RunActivity.csproj b/Source/RunActivity/RunActivity.csproj index bda7aa977c..fb3ff6beb8 100644 --- a/Source/RunActivity/RunActivity.csproj +++ b/Source/RunActivity/RunActivity.csproj @@ -148,6 +148,7 @@ + @@ -173,6 +174,7 @@ + @@ -323,6 +325,19 @@ PreserveNewest + + + + + + + + + + + + + diff --git a/Source/RunActivity/Viewer3D/Cameras.cs b/Source/RunActivity/Viewer3D/Cameras.cs index 88898c9bce..e509442084 100644 --- a/Source/RunActivity/Viewer3D/Cameras.cs +++ b/Source/RunActivity/Viewer3D/Cameras.cs @@ -1775,22 +1775,32 @@ protected override void SetCameraCar(TrainCar car) base.SetCameraCar(car); // Settings are held so that when switching back from another camera, view is not reset. // View is only reset on move to a different car and/or viewpoint or "Ctl + 8". - if (car.CarID != prevcar || ActViewPoint != prevViewPoint) - { - prevcar = car.CarID; - prevViewPoint = ActViewPoint; - viewPointLocation = attachedCar.PassengerViewpoints[ActViewPoint].Location; - viewPointRotationXRadians = attachedCar.PassengerViewpoints[ActViewPoint].RotationXRadians; - viewPointRotationYRadians = attachedCar.PassengerViewpoints[ActViewPoint].RotationYRadians; - RotationXRadians = viewPointRotationXRadians; - RotationYRadians = viewPointRotationYRadians; - attachedLocation = viewPointLocation; - StartViewPointLocation = viewPointLocation; - StartViewPointRotationXRadians = viewPointRotationXRadians; - StartViewPointRotationYRadians = viewPointRotationYRadians; + if (car.CarID != prevcar) + { + ActViewPoint = 0; + ResetViewPoint(car); + } + else if (ActViewPoint != prevViewPoint) + { + ResetViewPoint(car); } } + protected void ResetViewPoint (TrainCar car) + { + prevcar = car.CarID; + prevViewPoint = ActViewPoint; + viewPointLocation = attachedCar.PassengerViewpoints[ActViewPoint].Location; + viewPointRotationXRadians = attachedCar.PassengerViewpoints[ActViewPoint].RotationXRadians; + viewPointRotationYRadians = attachedCar.PassengerViewpoints[ActViewPoint].RotationYRadians; + RotationXRadians = viewPointRotationXRadians; + RotationYRadians = viewPointRotationYRadians; + attachedLocation = viewPointLocation; + StartViewPointLocation = viewPointLocation; + StartViewPointRotationXRadians = viewPointRotationXRadians; + StartViewPointRotationYRadians = viewPointRotationYRadians; + } + public override void HandleUserInput(ElapsedTime elapsedTime) { base.HandleUserInput(elapsedTime); diff --git a/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs b/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs index a5b55d0dae..55769e1be2 100644 --- a/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs @@ -264,6 +264,19 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) } } + // ========================================================================================================================================== + // Method to construct the various Heads Up Display pages for use by the WebServer + // Replaces the Prepare Frame Method + // djr - 20171221 + // ========================================================================================================================================== + public TableData PrepareTable(int PageNo) + { + var table = new TableData() { Cells = new string[1, 1] }; + + TextPages[PageNo](table); + return (table); + } + public override void Draw(SpriteBatch spriteBatch) { // Completely customise the rendering of the HUD - don't call base.Draw(spriteBatch). @@ -307,7 +320,22 @@ public override void Draw(SpriteBatch spriteBatch) } #region Table handling - sealed class TableData + + + // ========================================================================================================================================== + // Class used to construct table for display of Heads Up Display pages + // Original Code has been altered making the class public for use by the WebServer + // djr - 20171221 + // ========================================================================================================================================== + //sealed class TableData + //{ + // public string[,] Cells; + // public int CurrentRow; + // public int CurrentLabelColumn; + // public int CurrentValueColumn; + //} + + public sealed class TableData { public string[,] Cells; public int CurrentRow; diff --git a/Source/RunActivity/Viewer3D/Processes/Game.cs b/Source/RunActivity/Viewer3D/Processes/Game.cs index c2b433ca84..8399e27a1b 100644 --- a/Source/RunActivity/Viewer3D/Processes/Game.cs +++ b/Source/RunActivity/Viewer3D/Processes/Game.cs @@ -66,11 +66,18 @@ public class Game : Microsoft.Xna.Framework.Game /// public LoaderProcess LoaderProcess { get; private set; } + + /// /// Exposes access to the for the game. /// public SoundProcess SoundProcess { get; private set; } + /// + /// Exposes access to the for the game. + /// + public WebServerProcess WebServerProcess { get; private set; } + /// /// Gets the current , if there is one, or null. /// @@ -92,6 +99,7 @@ public Game(UserSettings settings) UpdaterProcess = new UpdaterProcess(this); LoaderProcess = new LoaderProcess(this); SoundProcess = new SoundProcess(this); + WebServerProcess = new WebServerProcess(this); States = new Stack(); } @@ -99,6 +107,7 @@ public Game(UserSettings settings) protected override void BeginRun() { // At this point, GraphicsDevice is initialized and set up. + WebServerProcess.Start(); SoundProcess.Start(); LoaderProcess.Start(); UpdaterProcess.Start(); @@ -151,6 +160,8 @@ protected override void EndRun() UpdaterProcess.Stop(); LoaderProcess.Stop(); SoundProcess.Stop(); + // WebServerProcess.Stop(); Again + WebServerProcess.Stop(); } [ThreadName("Render")] diff --git a/Source/RunActivity/Viewer3D/Processes/GameStateViewer3D.cs b/Source/RunActivity/Viewer3D/Processes/GameStateViewer3D.cs index d4c7844d66..da996c4cb5 100644 --- a/Source/RunActivity/Viewer3D/Processes/GameStateViewer3D.cs +++ b/Source/RunActivity/Viewer3D/Processes/GameStateViewer3D.cs @@ -55,6 +55,12 @@ internal override void BeginRender(RenderFrame frame) if (MPManager.IsMultiPlayer() || Game.Settings.ViewDispatcher) { Program.DebugViewer = new DispatchViewer(Viewer.Simulator, Viewer); + + //Trick to bring window to the front, even though invisible, so that it appears immediately when Ctrl+9 is pressed + Program.DebugViewer.WindowState = System.Windows.Forms.FormWindowState.Minimized; + Program.DebugViewer.Show(); + Program.DebugViewer.WindowState = System.Windows.Forms.FormWindowState.Normal; + Program.DebugViewer.Hide(); Viewer.DebugViewerEnabled = false; } @@ -130,7 +136,7 @@ internal override void Load() internal override void Dispose() { Viewer.Terminate(); - if (MPManager.Server != null) + if (MPManager.Server != null) MPManager.Server.Stop(); if (MPManager.Client != null) MPManager.Client.Stop(); diff --git a/Source/RunActivity/Viewer3D/Processes/WebServerProcess.cs b/Source/RunActivity/Viewer3D/Processes/WebServerProcess.cs new file mode 100644 index 0000000000..0810a249ca --- /dev/null +++ b/Source/RunActivity/Viewer3D/Processes/WebServerProcess.cs @@ -0,0 +1,102 @@ +// COPYRIGHT 2009, 2010, 2011, 2012, 2013, 2014 by the Open Rails project. +// +// This file is part of Open Rails. +// +// Open Rails is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Open Rails is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Open Rails. If not, see . + +// This file is the responsibility of the 3D & Environment Team. + + +using System; +using System.Net.Sockets; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Threading; +using Orts.Viewer3D; +using Orts.Viewer3D.WebServices; +using ORTS.Common; +using ORTS.Settings; +using Orts.Processes; + +namespace Orts.Viewer3D.Processes +{ + public class WebServerProcess + { + public readonly Profiler Profiler = new Profiler("WebServer"); + readonly ProcessState State = new ProcessState("WebServer"); + readonly Game Game; + readonly Thread Thread; + private bool ThreadActive = false; + WebServer webServer; + + public WebServerProcess(Game game) + { + Game = game; + + Thread = new Thread(WebServerThread); + if (game.Settings.WebServer) + { + ThreadActive = true; + } + } + + public void Start() + { + if (ThreadActive) + { + Thread.Start(); + } + } + + public void Stop() + { + if (ThreadActive) + { + webServer.stop(); + State.SignalTerminate(); + Thread.Abort(); + } + } + public bool Finished + { + get + { + return State.Finished; + } + } + + public void WaitTillFinished() + { + State.WaitTillFinished(); + } + + [ThreadName("WebServer")] + void WebServerThread() + { + Profiler.SetThread(); + Game.SetThreadLanguage(); + int port = Game.Settings.WebServerPort; + + var myWebContentPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName( + System.Windows.Forms.Application.ExecutablePath),"Content\\Web"); + + // 127.0.0.1 is a dummy, IPAddress.Any in WebServer.cs to accept any address + // on the local Lan + webServer = new WebServer("127.0.0.1", port, 1, myWebContentPath); + webServer.Run(); + } + } +} diff --git a/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs b/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs index 1459583d67..28ff342434 100644 --- a/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs +++ b/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs @@ -2124,6 +2124,7 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) var digital = Control as CVCDigital; Num = Locomotive.GetDataOf(Control); + if (digital.MinValue < digital.MaxValue) Num = MathHelper.Clamp(Num, (float)digital.MinValue, (float)digital.MaxValue); if (Math.Abs(Num) < digital.AccuracySwitch) Format = Format2; else @@ -2782,7 +2783,7 @@ public void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) Matrix m = XNAMatrix * mx; // TODO: Make this use AddAutoPrimitive instead. - frame.AddPrimitive(this.shapePrimitive.Material, this.shapePrimitive, RenderPrimitiveGroup.World, ref m, ShapeFlags.None); + frame.AddPrimitive(this.shapePrimitive.Material, this.shapePrimitive, RenderPrimitiveGroup.Interior, ref m, ShapeFlags.None); } internal void Mark() @@ -2998,7 +2999,7 @@ public void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) Matrix m = XNAMatrix * mx; // TODO: Make this use AddAutoPrimitive instead. - frame.AddPrimitive(this.shapePrimitive.Material, this.shapePrimitive, RenderPrimitiveGroup.World, ref m, ShapeFlags.None); + frame.AddPrimitive(this.shapePrimitive.Material, this.shapePrimitive, RenderPrimitiveGroup.Interior, ref m, ShapeFlags.None); } internal void Mark() diff --git a/Source/RunActivity/Viewer3D/Shapes.cs b/Source/RunActivity/Viewer3D/Shapes.cs index 87999e21f3..c85e582ae2 100644 --- a/Source/RunActivity/Viewer3D/Shapes.cs +++ b/Source/RunActivity/Viewer3D/Shapes.cs @@ -255,7 +255,7 @@ public StaticTrackShape(Viewer viewer, string path, WorldPosition position) /// public class PoseableShape : StaticShape { - static Dictionary SeenShapeAnimationError = new Dictionary(); + protected static Dictionary SeenShapeAnimationError = new Dictionary(); public Matrix[] XNAMatrices = new Matrix[0]; // the positions of the subobjects @@ -628,6 +628,12 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime) // TODO: Make this use AddAutoPrimitive instead. frame.AddPrimitive(this.shapePrimitive.Material, this.shapePrimitive, RenderPrimitiveGroup.World, ref xnaXfmWrtCamTile, ShapeFlags.None); + // if there is no animation, that's normal and so no animation missing error is displayed + if (SharedShape.Animations == null || SharedShape.Animations.Count == 0) + { + if (!SeenShapeAnimationError.ContainsKey(SharedShape.FilePath)) + SeenShapeAnimationError[SharedShape.FilePath] = true; + } // Update the pose for (int iMatrix = 0; iMatrix < SharedShape.Matrices.Length; ++iMatrix) AnimateMatrix(iMatrix, AnimationKey); diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/HUD.html b/Source/RunActivity/Viewer3D/WebServices/Web/HUD.html new file mode 100644 index 0000000000..48fdb20c99 --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/HUD.html @@ -0,0 +1,52 @@ + + + + OPEN RAILS - Heads Up Display - Common Page + + + + + + + +

Open Rails - Heads Up Display

+
+

+
+
+ + + + + + diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/css/hud.css b/Source/RunActivity/Viewer3D/WebServices/Web/css/hud.css new file mode 100644 index 0000000000..2839e1b3af --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/css/hud.css @@ -0,0 +1,26 @@ +ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #333; +} +li { + float: left; + border-right:1px solid #bbb; +} +li:last-child { + border-right: none; +} +li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} +li a:hover:not(.active) { + background-color: #111; +} + + diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/css/sample.css b/Source/RunActivity/Viewer3D/WebServices/Web/css/sample.css new file mode 100644 index 0000000000..2839e1b3af --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/css/sample.css @@ -0,0 +1,26 @@ +ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #333; +} +li { + float: left; + border-right:1px solid #bbb; +} +li:last-child { + border-right: none; +} +li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} +li a:hover:not(.active) { + background-color: #111; +} + + diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/css/trackmonitor.css b/Source/RunActivity/Viewer3D/WebServices/Web/css/trackmonitor.css new file mode 100644 index 0000000000..2839e1b3af --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/css/trackmonitor.css @@ -0,0 +1,26 @@ +ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #333; +} +li { + float: left; + border-right:1px solid #bbb; +} +li:last-child { + border-right: none; +} +li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} +li a:hover:not(.active) { + background-color: #111; +} + + diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/images/or_logo.png b/Source/RunActivity/Viewer3D/WebServices/Web/images/or_logo.png new file mode 100644 index 0000000000..4023551997 Binary files /dev/null and b/Source/RunActivity/Viewer3D/WebServices/Web/images/or_logo.png differ diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/images/waverley.jpg b/Source/RunActivity/Viewer3D/WebServices/Web/images/waverley.jpg new file mode 100644 index 0000000000..eb13ed22c7 Binary files /dev/null and b/Source/RunActivity/Viewer3D/WebServices/Web/images/waverley.jpg differ diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/images/zig-zag.jpg b/Source/RunActivity/Viewer3D/WebServices/Web/images/zig-zag.jpg new file mode 100644 index 0000000000..6aa99c447f Binary files /dev/null and b/Source/RunActivity/Viewer3D/WebServices/Web/images/zig-zag.jpg differ diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/index - ApiSample.html b/Source/RunActivity/Viewer3D/WebServices/Web/index - ApiSample.html new file mode 100644 index 0000000000..9e297fa70d --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/index - ApiSample.html @@ -0,0 +1,38 @@ + + + + OPEN RAILS - Sample API + + + + + +

Open Rails Web Server - Api Sample

+ + + Sample String Data: +

+ Sample Integer Data: +

+ Date Data: +

+ + String Array Members: +
+
+
+
+

+ + Embedded Class:
+ Embedded String Data: +

+ Embedded Integer Data: +

+ + + + + + + diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/index.html b/Source/RunActivity/Viewer3D/WebServices/Web/index.html new file mode 100644 index 0000000000..ac7c2106b2 --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/index.html @@ -0,0 +1,18 @@ + + + + OPEN RAILS - TrainInfo API + + + + + +

Open Rails Web Server - Api TrainInfo

+ +

+ + + + + + diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/js/ApiSample.js b/Source/RunActivity/Viewer3D/WebServices/Web/js/ApiSample.js new file mode 100644 index 0000000000..284449a278 --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/js/ApiSample.js @@ -0,0 +1,27 @@ +var PageNo = 0; +var hr = new XMLHttpRequest; + +function ApiSample() { + hr.open("POST", "/API/APISAMPLE", true); + hr.send("pageno=" + PageNo); + hr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var obj = JSON.parse(hr.responseText); + strData.innerHTML = obj.strData; + intData.innerHTML = obj.intData; + dateData.innerHTML = obj.dateData; + + arrayData = obj.strArrayData; + + arrayMember0.innerHTML = obj.strArrayData[0]; + arrayMember1.innerHTML = obj.strArrayData[1]; + arrayMember2.innerHTML = obj.strArrayData[2]; + arrayMember3.innerHTML = obj.strArrayData[3]; + arrayMember4.innerHTML = obj.strArrayData[4]; + + embeddedStr.innerHTML = obj.embedded.Str; + embeddedNumb.innerHTML = obj.embedded.Numb; + + } + } +} diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/js/ApiTrainInfo.js b/Source/RunActivity/Viewer3D/WebServices/Web/js/ApiTrainInfo.js new file mode 100644 index 0000000000..5b91bfd1d7 --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/js/ApiTrainInfo.js @@ -0,0 +1,15 @@ +var PageNo = 0; +var hr = new XMLHttpRequest; + +function ApiTrainInfo() { + hr.open("POST", "/API/TRAININFO", true); + hr.send("pageno=" + PageNo); + hr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var obj = JSON.parse(hr.responseText); + + strTrainInfoData.innerHTML = obj.allowedSpeedMps; + + } + } +} \ No newline at end of file diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/js/hud.js b/Source/RunActivity/Viewer3D/WebServices/Web/js/hud.js new file mode 100644 index 0000000000..b13effccb9 --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/js/hud.js @@ -0,0 +1,55 @@ +var PageNo = 0; +var hr = new XMLHttpRequest; + + +function HeadsUp () { + hr.open("POST", "/API/HUD", true); + hr.send("pageno="+PageNo); + hr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var obj = JSON.parse(hr.responseText); + var Rows = obj.commonTable.nRows; + var Cols = obj.commonTable.nCols; + Str = ""; + var next = 0; + for (var row = 0; row < obj.commonTable.nRows; ++row) { + Str += ""; + for (var col=0; col < obj.commonTable.nCols; ++col) { + if (obj.commonTable.values[next] == null) { + Str += ""; + } + else { + Str += ""; + } + ++next; + } + Str += ""; + } + Str += "
" + obj.commonTable.values[next] + "
"; + HUDCommon.innerHTML = Str; + + if (obj.nTables == 2) { + var Rows = obj.extraTable.nRows; + var Cols = obj.extraTable.nCols; + next = 0; + Str = ""; + for (var row = 0; row < obj.extraTable.nRows; ++row) { + Str += ""; + for (var col=0; col < obj.extraTable.nCols; ++col) { + if (obj.extraTable.values[next] == null) { + Str += ""; + } + else { + Str += ""; + } + ++next; + } + Str += ""; + } + Str += "
" + obj.extraTable.values[next] + "
"; + HUDExtra.innerHTML = Str; + } + } + } +} + diff --git a/Source/RunActivity/Viewer3D/WebServices/Web/js/trackmonitor.js b/Source/RunActivity/Viewer3D/WebServices/Web/js/trackmonitor.js new file mode 100644 index 0000000000..337a8aca0c --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/Web/js/trackmonitor.js @@ -0,0 +1,16 @@ +var PageNo = 0; +var hr = new XMLHttpRequest; + + +function TrackMonitor () { + hr.open("POST", "/API/TRACKMONITOR", true); + hr.send("pageno=" + PageNo); + hr.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + var obj = JSON.parse(hr.responseText); + var Str = obj.str; + common.innerHTML = Str; + } + } +} + diff --git a/Source/RunActivity/Viewer3D/WebServices/WebServer.cs b/Source/RunActivity/Viewer3D/WebServices/WebServer.cs new file mode 100644 index 0000000000..664df03f40 --- /dev/null +++ b/Source/RunActivity/Viewer3D/WebServices/WebServer.cs @@ -0,0 +1,648 @@ +// COPYRIGHT 2009, 2010, 2011, 2012, 2013, 2014 by the Open Rails project. +// +// This file is part of Open Rails. +// +// Open Rails is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Open Rails is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Open Rails. If not, see . +// +// =========================================================================================== +// Open Rails Web Server +// The following files have been modified to accomodate the WebServer +// Game.cs +// HUDWindow.cs +// WebServerProcess.cs +// search for "WebServer" to find all occurrences +// +// djr - 20171221 +// =========================================================================================== + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using Newtonsoft.Json; +using Orts.Simulation; +using Orts.Simulation.Physics; + +namespace Orts.Viewer3D.WebServices +{ + // ================================================================= + // State object for reading client data asynchronously + // ================================================================= + public class StateObject + { + public Socket WorkSocket = null; // Client socket. + public const int BufferSize = 1024; // Size of receive buffer. + public byte[] Buffer = new byte[BufferSize]; // Receive buffer. + } + + // ================================================================== + // class for holding HTTP Request data + // ================================================================== + public class HttpRequest + { + public Socket ClientSocket = null; + public string Method = ""; + public string URI = ""; + public string Parameters; + public Dictionary headers = new Dictionary(); + public Dictionary Headers { get => headers; set => headers = value; } + } + + // ================================================================== + // class for holding HTTP Resonse data + // ================================================================== + public class HttpResponse + { + public Socket ClientSocket = null; + public string ResponseCode = ""; + public string ContentType = ""; + public string strContent = ""; + public byte[] byteContent; + } + + // ==================================================================== + // TCP/IP Sockets WebServer + // ==================================================================== + public class WebServer + { + private bool Running = false; + private int timeout = 10; + public Socket ServerSocket = null; + private static Encoding CharEncoder = Encoding.UTF8; + private static string ContentPath = ""; + private IPAddress ipAddress = null; + private int Port = 0; + private int MaxConnections = 0; + + // =========================================================================================== + // Thread signal. + // =========================================================================================== + private static ManualResetEvent allDone = new ManualResetEvent(false); + + // =========================================================================================== + // File exstensions this server will handle - any other extensions are returns as not found + // =========================================================================================== + private static Dictionary extensions = new Dictionary() + { + { "htm", "text/html" }, + { "html", "text/html" }, + { "txt", "text/plain" }, + { "css", "text/css" }, + { "xml", "application/xml" }, + { "js", "application/javascript" }, + { "json", "application/json" }, + { "ico", "image/x-icon" }, + { "png", "image/png" }, + { "gif", "image/gif" }, + { "jpg", "image/jpg" }, + { "jpeg", "image/jpeg" } + }; + + public Dictionary Extensions { get => extensions; set => extensions = value; } + + // =========================================================================================== + // Viewer object from Viewer3D - needed for acces to Heads Up Display Data + // =========================================================================================== + public Viewer viewer; + + // =========================================================================================== + // WebServer constructor + // =========================================================================================== + public WebServer(string ipAddr, int port, int maxConnections, string path) + { + ipAddress = IPAddress.Parse(ipAddr); + Port = port; + ContentPath = path; + MaxConnections = maxConnections; + ApiDict.Add("/API/HUD", ApiHUD); + ApiDict.Add("/API/APISAMPLE", ApiSample); + ApiDict.Add("/API/TRACKMONITOR", ApiTrackMonitor); + ApiDict.Add("/API/TRAININFO", ApiTrainInfo); + return; + } + + // =========================================================================================== + // =========================================================================================== + public void Run() + { + if (Running) + return; + + // Viewer is not yet initialized in the GameState object - wait until it is + while ((viewer = Program.Viewer) == null) + Thread.Sleep(1000); + + try + { + ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + ServerSocket.Bind(new IPEndPoint(IPAddress.Any, Port)); + ServerSocket.Listen(MaxConnections); + ServerSocket.ReceiveTimeout = timeout; + ServerSocket.SendTimeout = timeout; + } + catch (Exception e) + { + Console.WriteLine("Exception Bind Socket: " + e.Message); + return; + } + while (true) + { + Running = true; + // Set the event to nonsignaled state. + allDone.Reset(); + + try + { + // Start an asynchronous socket to listen for connections. + ServerSocket.BeginAccept(new AsyncCallback(acceptCallback), ServerSocket); + } + catch (Exception e) + { + Console.WriteLine("100 Exception calling BeginAccept: " + e.Message); + } + // TODO: + // Break out of any waiting states + // Break out of any async states + // Close down any open sockets !!!! + if (!Running) + { + break; + } + // Wait until a connection is made before continuing. + //Trace.WriteLine("WebServer is waiting for a connection"); + allDone.WaitOne(); + } + } + + // =========================================================================================== + // =========================================================================================== + public void acceptCallback(IAsyncResult ar) + { + // wjc if we stopped the thread just leave + if (Running) + { + // Signal the main thread to continue. + allDone.Set(); + // Get the socket that handles the client request. + Socket listener = (Socket)ar.AsyncState; + Socket handler = listener.EndAccept(ar); + // Create the state object. + StateObject state = new StateObject(); + state.WorkSocket = handler; + handler.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(receiveCallback), + state); + } + } + + // =========================================================================================== + // Main processing loop - read request and call response functions + // =========================================================================================== + public static void receiveCallback(IAsyncResult ar) + { + // Retrieve the state object and the handler socket + // from the asynchronous state object. + StateObject state = (StateObject)ar.AsyncState; + StreamReader streamReader; + HttpRequest request = new HttpRequest(); + HttpResponse response = new HttpResponse(); + request.ClientSocket = state.WorkSocket; + response.ClientSocket = state.WorkSocket; + try + { + int bytesReceived = request.ClientSocket.EndReceive(ar); + streamReader = new StreamReader(new MemoryStream(state.Buffer, 0, bytesReceived)); + } + catch (Exception e) + { + Console.WriteLine("Exception instantiate StreamReader: " + e.Message); + return; + } + while (streamReader.Peek() > -1) + { + string lineRead = streamReader.ReadLine(); + if (lineRead.Length == 0) + { + if (request.Method.Equals("POST")) + { + request.Parameters = streamReader.ReadToEnd(); + ProcessPost(request, response); + } + else if (request.Method.Equals("GET")) + { + ProcessGet(request, response); + } + else + sendNotImplemented(response); + return; + } + else if (request.Method.Equals("")) + { + try + { + request.Method = lineRead.Substring(0, lineRead.IndexOf(" ")); + request.Method.Trim(); + int start = lineRead.IndexOf('/'); + int length = lineRead.LastIndexOf(" ") - start; + request.URI = lineRead.Substring(start, length); + } + catch (Exception e) + { + Console.WriteLine("httpMethod: " + e.Message); + } + + if (!request.Method.Equals("GET") && !request.Method.Equals("POST")) + { + sendNotImplemented(response); + return; + } + } + else + { + try + { + int seperator = lineRead.IndexOf(':'); + string heading = lineRead.Substring(0, seperator); + heading = heading.Trim(); + ++seperator; + string value = lineRead.Substring(seperator); + value = value.Trim(); + request.Headers.Add(heading, value); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + if (streamReader.EndOfStream) + { + break; + } + } + sendServerError(response); + } + + // =========================================================================================== + // =========================================================================================== + public void stop() + { + if (Running) + { + Running = false; + // TODO: + // Will Shutdown and close break out of any async waiting states?? + try + { + // wjc we are just streaming for now, so make sure the socket closes at end of game + // so that the socket is not hung when opening again + //ServerSocket.Shutdown(SocketShutdown.Both); + //tcpListener.Stop(); + ServerSocket.Close(); + + } + catch (Exception e) + { + //Console.WriteLine(e.Message); + Trace.WriteLine("WebServer", e.Message); + } + ServerSocket = null; + } + } + + // =========================================================================================== + // =========================================================================================== + private static void ProcessPost(HttpRequest request, HttpResponse response) + { + request.URI = request.URI.Replace('\\', '/'); + request.URI = request.URI.ToUpper(); + if (!request.URI.StartsWith("/API/")) + { + Console.WriteLine("Post Method - API Not Implemented [{0}]", request.URI); + sendNotImplemented(response); + return; + } + response.strContent = ExecuteApi(request.URI, request.Parameters); + response.ContentType = "application/json"; + sendOkResponse(response); + } + + // =========================================================================================== + // =========================================================================================== + private static void ProcessGetAPI(HttpRequest request, HttpResponse response) + { + sendNotImplemented(response); + } + + // =========================================================================================== + // =========================================================================================== + private static void ProcessGet(HttpRequest request, HttpResponse response) + { + request.URI = request.URI.Replace("/", "\\").Replace("\\..", ""); + if (request.URI.StartsWith("/API/")) + { + ProcessGetAPI(request, response); + return; + } + int length = request.URI.Length; + int start = request.URI.LastIndexOf('.'); + if (start == -1) + { + if (request.URI.Substring(length - 1, 1) != "\\") + request.URI += "\\"; + request.URI += "index.html"; + } + start = request.URI.LastIndexOf('.'); + length = request.URI.Length - start - 1; + string extension = request.URI.Substring(start + 1, length); + if (extensions.ContainsKey(extension)) + { + if (File.Exists(ContentPath + request.URI)) + { + byte[] bytes = File.ReadAllBytes(ContentPath + request.URI); + response.byteContent = new byte[bytes.Length]; + response.byteContent = bytes; + response.ContentType = extensions[extension]; + sendOkResponse(response); + } + else + sendNotFound(response); // We don't support this extension. We are assuming that it doesn't exist. + } + else + sendNotImplemented(response); + return; + } + + // =========================================================================================== + // =========================================================================================== + private static void HTMLContent(HttpResponse response) + { + response.strContent = "" + + "" + + "" + + "" + + "" + + "" + + "

OpenRails WebServer

" + + "
" + response.ResponseCode + "
" + "" + + ""; + + return; + } + + // =========================================================================================== + // =========================================================================================== + private static void sendNotImplemented(HttpResponse response) + { + response.ResponseCode = "501 Not Implemented"; + HTMLContent(response); + SendHttp(response); + } + + // =========================================================================================== + // =========================================================================================== + private static void sendNotFound(HttpResponse response) + { + response.ResponseCode = "404 Not Found"; + HTMLContent(response); + SendHttp(response); + } + + // =========================================================================================== + // =========================================================================================== + private static void sendServerError(HttpResponse response) + { + response.ResponseCode = "500 Internal Server Error"; + HTMLContent(response); + SendHttp(response); + } + + // =========================================================================================== + // =========================================================================================== + private static void sendOkResponse(HttpResponse response) + { + response.ResponseCode = "200 OK"; + SendHttp(response); + } + + // =========================================================================================== + // =========================================================================================== + private static void SendHttp(HttpResponse response) + { + // Convert the string data to byte data using ASCII encoding. + if (response.strContent.Length > 0) + { + response.byteContent = Encoding.ASCII.GetBytes(response.strContent); + } + byte[] byteData = CharEncoder.GetBytes( + "HTTP/1.1 " + response.ResponseCode + "\r\n" + + "Server: OpenRails WebServer\r\n" + + "Content-Length: " + response.byteContent.Length.ToString() + "\r\n" + + "Connection: close\r\n" + + "Content-Type: " + response.ContentType + "\r\n" + + "Cache-Control: no-cache \r\n\r\n" + + System.Text.Encoding.UTF8.GetString(response.byteContent)); + + // Begin sending the data to the remote device. + response.ClientSocket.BeginSend(byteData, 0, + byteData.Length, 0, + new AsyncCallback(SendHttpCallback), + response.ClientSocket); + } + + // =========================================================================================== + // =========================================================================================== + private static void SendHttpCallback(IAsyncResult ar) + { + try + { + // Retrieve the socket from the state object. + Socket clientSocket = (Socket)ar.AsyncState; + + // Complete sending the data to the remote device. + int bytesSent = clientSocket.EndSend(ar); + + clientSocket.Shutdown(SocketShutdown.Both); + clientSocket.Close(); + } + catch (Exception e) + { + Console.WriteLine("Exception Send CallBack: " + e.ToString()); + } + } + + // =========================================================================================== + // API routing classes & functions + // =========================================================================================== + public static Dictionary> ApiDict = new Dictionary>(); + + public static string ExecuteApi(string apiName, string Parameters) + { + Func apiMethod; + if (!ApiDict.TryGetValue(apiName, out apiMethod)) + { + Console.WriteLine("Not Found"); //TODO + } + object result = apiMethod(Parameters); + string json = JsonConvert.SerializeObject(result, Formatting.Indented); + return json; + } + + // ======================================================================================= + // API for Sample Data + // ======================================================================================= + + + public class Embedded + { + public string Str; + public int Numb; + } + public class ApiSampleData + { + public int intData; + public string strData; + public DateTime dateData; + public Embedded embedded; + public string[] strArrayData; + } + + // ------------------------------------------------------------------------------------------- + public object ApiSample(string Parameters) + { + ApiSampleData sampleData = new ApiSampleData(); + + sampleData.intData = 576; + sampleData.strData = "Sample String"; + sampleData.dateData = new DateTime(2018, 1, 1); + + sampleData.embedded = new Embedded(); + sampleData.embedded.Str = "Embeddded String"; + sampleData.embedded.Numb = 123; + + sampleData.strArrayData = new string[5]; + + sampleData.strArrayData[0] = "First member"; + sampleData.strArrayData[1] = "Second member"; + sampleData.strArrayData[2] = "Third Member"; + sampleData.strArrayData[3] = "Forth member"; + sampleData.strArrayData[4] = "Fifth member"; + + return (sampleData); + } + + // ======================================================================================= + // API to display the HUD Windows + // ======================================================================================= + public class HudApiTable + { + public int nRows; + public int nCols; + public string[] values; + } + + // ------------------------------------------------------------------------------------------- + public class HudApiArray + { + public int nTables; + public HudApiTable commonTable; + public HudApiTable extraTable; + } + + + // ------------------------------------------------------------------------------------------- + public object ApiHUD(string Parameters) + { + int index = Parameters.IndexOf('='); + if (index == -1) + return (null); + string strPageno = Parameters.Substring(index + 1, Parameters.Length - index - 1); + strPageno = strPageno.Trim(); + int pageNo = Int32.Parse(strPageno); + + HudApiArray hudApiArray = new HudApiArray(); + hudApiArray.nTables = 1; + + hudApiArray.commonTable = ApiHUD_ProcessTable(0); + if (pageNo > 0) + { + hudApiArray.nTables = 2; + hudApiArray.extraTable = ApiHUD_ProcessTable(pageNo); + } + return hudApiArray; + } + + // ------------------------------------------------------------------------------------------- + public HudApiTable ApiHUD_ProcessTable(int pageNo) + { + int nRows = 0; + int nCols = 0; + int nextCell = 0; + + Viewer3D.Popups.HUDWindow.TableData hudTable = viewer.HUDWindow.PrepareTable(pageNo); + + HudApiTable apiTable = new HudApiTable(); + + apiTable.nRows = hudTable.Cells.GetLength(0); + nRows = apiTable.nRows; + apiTable.nCols = hudTable.Cells.GetLength(1); + nCols = apiTable.nCols; + apiTable.values = new string[nRows * nCols]; + + try + { + for (int i = 0; i < nRows; ++i) + { + for (int j = 0; j < nCols; ++j) + { + apiTable.values[nextCell++] = hudTable.Cells[i, j]; + } + } + } + catch (Exception e) + { + Trace.WriteLine(e.Message); + } + return (apiTable); + } + + // ======================================================================================= + // API for Track Monitor Data + // ======================================================================================= + + // ------------------------------------------------------------------------------------------- + public object ApiTrackMonitor(string Parameters) + { + Train.TrainInfo trainInfo = viewer.PlayerTrain.GetTrainInfo(); + + return (trainInfo); + + } + + // ======================================================================================= + // API for Train Info + // ======================================================================================= + + // ------------------------------------------------------------------------------------------- + public object ApiTrainInfo(string Parameters) + { + Train.TrainInfo trainInfo = viewer.PlayerTrain.GetTrainInfo(); + + return (trainInfo); + } + } +} \ No newline at end of file