Skip to content

Commit 9e22e0e

Browse files
authored
Merge pull request #347 from Csantucci/multiple-horn-blows
https://blueprints.launchpad.net/or/+spec/horn-blow-sequence American horn blow sequence for AI trains
2 parents 712907a + 77a3503 commit 9e22e0e

File tree

11 files changed

+313
-114
lines changed

11 files changed

+313
-114
lines changed

Source/Contrib/TrackViewer/Editing/WaitPointDialog.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
<RadioButton GroupName="shuntingOption" Name="selectBlowHorn" VerticalAlignment="Center"
4444
Content="Blow horn for" Width="105" Checked="Option_CheckChanged" />
4545
<TextBox Height="23" Name="blowHornSeconds" Width="35" PreviewTextInput="NumberValidationTextBox" KeyDown="OnKeyDownHandler" TextChanged="TwoDigits_TextChanged"/>
46-
<Label Content="seconds. "/>
46+
<Label Content="seconds. 11 = American horn sequence."/>
4747
</StackPanel>
4848
<Separator/>
4949
<StackPanel Orientation="Horizontal">

Source/Contrib/TrackViewer/Editing/WaitPointDialog.xaml.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public WaitPointDialog(int mouseX, int mouseY, int currentWaitTimeS)
8484
{
8585
aiRequestPassRed.IsChecked = true;
8686
}
87-
else if (currentWaitTimeS >= 60011 && currentWaitTimeS <= 60020)
87+
else if (currentWaitTimeS >= 60011 && currentWaitTimeS <= 60021)
8888
{
8989
int seconds = currentWaitTimeS - 60010;
9090
blowHornSeconds.Text = seconds.ToString(System.Globalization.CultureInfo.CurrentCulture);
@@ -134,18 +134,18 @@ public int GetWaitTime()
134134
}
135135
if (selectBlowHorn.IsChecked == true)
136136
{
137-
// coding is 60011 to 60020
137+
// coding is 60011 to 60021; 60021 used for American Horn Sequence
138138
int seconds = GetIntOrZero(blowHornSeconds.Text);
139139
if (seconds < 0)
140140
{
141141
// we need to allow 0 itself (which we get from an empty string) otherwise it is not even possible to go from '1' to '2' or so.
142142
seconds = 1;
143143
blowHornSeconds.Text = "1";
144144
}
145-
if (seconds > 10)
145+
if (seconds > 11)
146146
{
147-
seconds = 10;
148-
blowHornSeconds.Text = "10";
147+
seconds = 11;
148+
blowHornSeconds.Text = "11";
149149
}
150150
return 60010 + seconds;
151151
}

Source/Documentation/Manual/operation.rst

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,8 +1819,12 @@ AI Train Horn Blow
18191819
------------------
18201820

18211821
Horn blow by AI trains is achieved by inserting into the AI train path a
1822-
waiting point with a waiting time value between 60011 (1 second horn blow) and
1823-
60020 (10 seconds horn blow).
1822+
waiting point.
1823+
If the waiting time value is between 60011 (1 second horn blow) and
1824+
60020 (10 seconds horn blow), a single horn blow is generated.
1825+
If the waiting time value is 60021, a horn blow sequence is generated,
1826+
with the pattern long blow - long blow - short blow - long blow (North
1827+
American horn pattern at level crossings).
18241828

18251829
The AI train will not stop at these waiting points, but will continue at its
18261830
regular speed.
@@ -1857,7 +1861,22 @@ is inserted into the activity file following the line::
18571861

18581862
(note that the number in the brackets may be different), then AI trains will
18591863
blow their horn at level crossings for a random time between 2 and 5
1860-
seconds.The level crossing must be defined as such in the MSTS route editor.
1864+
seconds. The level crossing must be defined as such in the MSTS route editor.
1865+
1866+
If line::
1867+
1868+
ORTSAIHornAtCrossings ( 1 )
1869+
1870+
is followed by line::
1871+
1872+
ORTSAICrossingHornPattern ( US )
1873+
1874+
instead of a single horn blow, a horn blow sequence will be generated
1875+
for all AI trains before crossings,
1876+
with the pattern long blow - long blow - short blow - long blow (North
1877+
American horn pattern at level crossings).
1878+
1879+
18611880
*Simple* road crossings, not defined as level crossings, may also be present in
18621881
the route. The AI train will not blow the horn at these crossings. Examining
18631882
the route with TrackViewer allows identification of the true level crossings.

Source/ORTS.Common/enums.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,20 @@ public static Direction Flip(Direction direction)
4242
return Direction.Forward;
4343
}
4444
}
45+
46+
/// <summary>
47+
/// A type of horn pattern used by AI trains at level crossings.
48+
/// </summary>
49+
public enum LevelCrossingHornPattern
50+
{
51+
/// <summary>
52+
/// A single blast just before the crossing.
53+
/// </summary>
54+
Single,
55+
56+
/// <summary>
57+
/// A long-long-short-long pattern used in the United States and Canada.
58+
/// </summary>
59+
US,
60+
}
4561
}

Source/Orts.Formats.Msts/ActivityFile.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@
273273
using System.Text;
274274
using System.IO;
275275
using Orts.Parsers.Msts; // For class S (seconds)
276+
using ORTS.Common;
276277

277278
namespace Orts.Formats.Msts
278279
{
@@ -492,23 +493,25 @@ public class Tr_Activity_File {
492493
public Traffic_Definition Traffic_Definition;
493494
public PlatformNumPassengersWaiting PlatformNumPassengersWaiting;
494495
public ActivityRestrictedSpeedZones ActivityRestrictedSpeedZones;
495-
public int ORTSAIHornAtCrossings = -1;
496+
public bool AIBlowsHornAtLevelCrossings { get; private set; } = false;
497+
public LevelCrossingHornPattern AILevelCrossingHornPattern { get; private set; } = LevelCrossingHornPattern.Single;
496498

497499

498500
public Tr_Activity_File(STFReader stf) {
499501
stf.MustMatch("(");
500-
stf.ParseBlock(new STFReader.TokenProcessor[] {
502+
var parser = new List<STFReader.TokenProcessor> {
501503
new STFReader.TokenProcessor("player_service_definition",()=>{ Player_Service_Definition = new Player_Service_Definition(stf); }),
502504
new STFReader.TokenProcessor("nextserviceuid",()=>{ NextServiceUID = stf.ReadIntBlock(null); }),
503505
new STFReader.TokenProcessor("nextactivityobjectuid",()=>{ NextActivityObjectUID = stf.ReadIntBlock(null); }),
504-
new STFReader.TokenProcessor("ortsaihornatcrossings", ()=>{ ORTSAIHornAtCrossings = stf.ReadIntBlock(ORTSAIHornAtCrossings); }),
505506
new STFReader.TokenProcessor("events",()=>{ Events = new Events(stf); }),
506507
new STFReader.TokenProcessor("traffic_definition",()=>{ Traffic_Definition = new Traffic_Definition(stf); }),
507508
new STFReader.TokenProcessor("activityobjects",()=>{ ActivityObjects = new ActivityObjects(stf); }),
508509
new STFReader.TokenProcessor("platformnumpassengerswaiting",()=>{ PlatformNumPassengersWaiting = new PlatformNumPassengersWaiting(stf); }), // 35 files. To test, use EUROPE1\ACTIVITIES\aftstorm.act
509510
new STFReader.TokenProcessor("activityfailedsignals",()=>{ ActivityFailedSignals = new ActivityFailedSignals(stf); }),
510511
new STFReader.TokenProcessor("activityrestrictedspeedzones",()=>{ ActivityRestrictedSpeedZones = new ActivityRestrictedSpeedZones(stf); }), // 27 files. To test, use EUROPE1\ACTIVITIES\lclsrvce.act
511-
});
512+
};
513+
parser.AddRange(ORSpecificDataTokenProcessors(stf));
514+
stf.ParseBlock(parser);
512515
}
513516

514517
// Used for explore in activity mode
@@ -525,14 +528,28 @@ public Tr_Activity_File()
525528
public void InsertORSpecificData(STFReader stf)
526529
{
527530
stf.MustMatch("(");
528-
stf.ParseBlock(new STFReader.TokenProcessor[] {
529-
new STFReader.TokenProcessor("ortsaihornatcrossings", ()=>{ ORTSAIHornAtCrossings = stf.ReadIntBlock(ORTSAIHornAtCrossings); }),
531+
var parser = new List<STFReader.TokenProcessor> {
530532
new STFReader.TokenProcessor("events",()=>
531533
{
532534
if ( Events == null) Events = new Events(stf);
533535
else Events.InsertORSpecificData (stf);
534536
}
535537
),
538+
};
539+
parser.AddRange(ORSpecificDataTokenProcessors(stf));
540+
stf.ParseBlock(parser);
541+
}
542+
543+
private IEnumerable<STFReader.TokenProcessor> ORSpecificDataTokenProcessors(STFReader stf)
544+
{
545+
yield return new STFReader.TokenProcessor("ortsaihornatcrossings", () =>
546+
{
547+
AIBlowsHornAtLevelCrossings = stf.ReadIntBlock(Convert.ToInt32(AIBlowsHornAtLevelCrossings)) > 0;
548+
});
549+
yield return new STFReader.TokenProcessor("ortsaicrossinghornpattern", () =>
550+
{
551+
if (Enum.TryParse<LevelCrossingHornPattern>(stf.ReadStringBlock(""), ignoreCase: true, out var value))
552+
AILevelCrossingHornPattern = value;
536553
});
537554
}
538555
}

Source/Orts.Formats.OR/AuxActionRef.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// along with Open Rails. If not, see <http://www.gnu.org/licenses/>.
1717

1818
using Newtonsoft.Json;
19+
using Newtonsoft.Json.Converters;
1920
using ORTS.Common;
2021
using System;
2122
using System.Collections.Generic;
@@ -358,12 +359,16 @@ public class AuxActionHorn : AuxActionRef
358359
public int Delay;
359360
[JsonProperty("RequiredDistance")]
360361
public float RequiredDistance;
362+
[JsonProperty("Pattern")]
363+
[JsonConverter(typeof(StringEnumConverter))]
364+
public LevelCrossingHornPattern Pattern { get; private set; }
361365

362-
public AuxActionHorn(bool isGeneric, int delay = 2, float requiredDistance = 0) : //WorldLocation? location, float requiredSpeedMpS, , int endSignalIndex = -1, AUX_ACTION actionType = AUX_ACTION.SOUND_HORN, , float requiredDistance = 0) :
363-
base(AUX_ACTION.SOUND_HORN ,isGeneric) //location, requiredSpeedMpS, , endSignalIndex, actionType, delay, requiredDistance)
366+
public AuxActionHorn(bool isGeneric, int delay = 2, float requiredDistance = 0, LevelCrossingHornPattern hornPattern = LevelCrossingHornPattern.Single) : //WorldLocation? location, float requiredSpeedMpS, , int endSignalIndex = -1, AUX_ACTION actionType = AUX_ACTION.SOUND_HORN, , float requiredDistance = 0) :
367+
base(AUX_ACTION.SOUND_HORN, isGeneric) //location, requiredSpeedMpS, , endSignalIndex, actionType, delay, requiredDistance)
364368
{
365369
Delay = delay;
366370
RequiredDistance = requiredDistance;
371+
Pattern = hornPattern;
367372
}
368373

369374
public static void Register(string key)
@@ -376,6 +381,7 @@ public void SaveProperties(AuxActionHorn action)
376381
{
377382
Delay = action.Delay;
378383
RequiredDistance = action.RequiredDistance;
384+
Pattern = action.Pattern;
379385
}
380386
}
381387

Source/Orts.Parsers.Msts/STFReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1477,7 +1477,7 @@ public void ParseFile(ParsingBreak breakout, TokenProcessor[] processors)
14771477
/// <summary>Parse an STF file until the end of block ')' marker, using the array of lower case tokens, with a processor delegate/lambda
14781478
/// </summary>
14791479
/// <param name="processors">Array of lower case token, and the delegate/lambda to call when matched.</param>
1480-
public void ParseBlock(TokenProcessor[] processors)
1480+
public void ParseBlock(IEnumerable<TokenProcessor> processors)
14811481
{ // Press F10 'Step Over' to jump to the next token
14821482
#line hidden
14831483
while (!EndOfBlock())

Source/Orts.Simulation/Simulation/AIs/AI.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ public AI(Simulator simulator, CancellationToken cancellation, double activitySt
7979
#endif
8080
if (simulator.Activity != null && simulator.Activity.Tr_Activity.Tr_Activity_File.Traffic_Definition != null)
8181
{
82-
foreach (var sd in simulator.Activity.Tr_Activity.Tr_Activity_File.Traffic_Definition.ServiceDefinitionList)
82+
var activityFile = simulator.Activity.Tr_Activity.Tr_Activity_File;
83+
foreach (var sd in activityFile.Traffic_Definition.ServiceDefinitionList)
8384
{
84-
AITrain train = CreateAITrain(sd,
85-
simulator.Activity.Tr_Activity.Tr_Activity_File.Traffic_Definition.TrafficFile.TrafficDefinition, simulator.TimetableMode);
85+
AITrain train = CreateAITrain(sd, activityFile.Traffic_Definition.TrafficFile.TrafficDefinition, simulator.TimetableMode);
8686
if (cancellation.IsCancellationRequested) // ping loader watchdog
8787
return;
8888
}

0 commit comments

Comments
 (0)