Skip to content

Commit 4dced6b

Browse files
authored
Merge pull request #532 from Csantucci/distributed-power-official
Distributed power american style. Blueprint https://blueprints.launchpad.net/or/+spec/distributed-power
2 parents f93a4dc + 758e2f8 commit 4dced6b

File tree

28 files changed

+1805
-64
lines changed

28 files changed

+1805
-64
lines changed
Loading

Source/Documentation/Manual/physics.rst

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,18 +2031,106 @@ Some of the cab monitoring gauges provide visibility of what is happening on the
20312031
the "closest" powered car near the Control car and uses its information.
20322032

20332033

2034-
Engines -- Multiple Units in Same Consist or AI Engines
2035-
=======================================================
2036-
2037-
In an OR player train one locomotive is controlled by the player, while
2038-
the other units are controlled by default by the train's MU (multiple
2039-
unit) signals for braking and throttle position, etc. The
2040-
player-controlled locomotive generates the MU signals which are passed
2041-
along to every unit in the train. For AI trains, the AI software directly
2042-
generates the MU signals, i.e. there is no player-controlled locomotive.
2034+
Multiple Units of Locomotives in Same Consist
2035+
=============================================
2036+
2037+
In an OR player train one locomotive is controlled by the player, while
2038+
the other units are controlled by default by the train's MU (multiple
2039+
unit) signals for braking and throttle position, etc. The
2040+
player-controlled locomotive generates the MU signals which are passed
2041+
along to every unit in the train.
2042+
2043+
Distributed Power
2044+
-----------------
2045+
2046+
This is applicable only to trains hauled by diesel locomotives equipped with
2047+
dynamic brakes.
2048+
2049+
More locomotive groups may be present in American long freight trains; a
2050+
locomotive group is defined as a set of locomotives that have no wagons in
2051+
between.
2052+
Groups different from the group including the lead locomotive are called
2053+
remote groups.
2054+
2055+
Remote groups can be controlled in two modes: *synchronous* or
2056+
*asynchronous*. Locomotives in the player train can be arranged to be part of
2057+
one of the two above control groups. So each locomotive group (except the lead one,
2058+
which always belongs to the *sync* control group)
2059+
can be either part the *sync* or of the *async* control group. However, if a
2060+
locomotive group is part of the *async* control group, all locomotive groups behind
2061+
it must also be part of the *async* control group.
2062+
2063+
The arrangement can be changed during run,
2064+
which in real life is performed by using the locomotive's onboard computer
2065+
(e.g. Locotrol).
2066+
2067+
This functionality is necessary in American long-train freight
2068+
operations. An example use case is when a train finished climbing a
2069+
mountain pass. At this point the lead locomotives have to start dynamic
2070+
braking driving downhill, while the trailing units still need to keep pushing
2071+
the train's end uphill. The locomotive driver has the possibility to build
2072+
the *fence*, i.e. move the last locomotive group to the *async* control group.
2073+
2074+
While locomotives in *sync* control group always copy the traction and dynamic
2075+
brake settings of the lead (man-controlled) locomotive, for locomotives in
2076+
*async* control group these can be adjusted independently. Other controls, like
2077+
reverser and air brake are always synchronized throughout the whole train,
2078+
and changing reverser setting or applying air brakes will force *async*
2079+
controlled locomotives to fall back to idle.
2080+
2081+
The *fence* between *sync* and *async* control groups can be moved back and forth
2082+
along the train. This is useful when there are one or more middle-train
2083+
locomotive groups in the consist. However it is not possible to move the
2084+
fence to separate two directly interconnected locomotives: such locomotives
2085+
can be rearranged only together.
2086+
2087+
If the driver dismantles the *fence*, by moving it after the last locomotive
2088+
group, all locomotive groups are moved back to *sync* control group, and
2089+
therefore all locomotives will work in sync with the leading one.
2090+
All-sync operation is also the default, when the game starts.
2091+
2092+
Traction and dynamic brake settings for *sync* group can be controlled by the
2093+
usual keys: ``<A>`` and ``<D>``. The following additional controls are
2094+
available for controlling the *async* group settings:
2095+
2096+
- Move To Back -- ``<Ctrl+Shift+O>``: Move one locomotive group to back (*async*)
2097+
control group (*fence* is moved towards the front of the train).
2098+
- Move To Front -- ``<Ctrl+O>``: Move one locomotive group to front (*sync*)
2099+
control group (*fence* is moved towards the back of the train).
2100+
- Traction -- ``<Ctrl+L>``: Switch *async* group to traction mode.
2101+
- Idle -- ``<Ctrl+Shift+L>``: Switch *async* group to idle state.
2102+
- Brake -- ``<Ctrl+'> (key two positions at the right of L)``: Switch *async*
2103+
group to dynamic braking mode.
2104+
- More -- ``<Ctrl+U>``: Increase *async* group traction or dynamic brake by a notch,
2105+
depending on its mode setting.
2106+
- Less -- ``<Ctrl+Shift+U>``: Decrease *async* group traction or dynamic brake by a notch,
2107+
depending on its mode setting.
2108+
2109+
HUD shows the sync--async configuration in line *Multiple Units* on main page.
2110+
When it reads e.g. "2--2 | 1", then it means that front and middle-train
2111+
double-unit locomotives are controlled in *sync* with the leading unit,
2112+
while the trailing pushing unit is controlled *async* independently.
2113+
The actual set value of traction or dynamic brake of *async* group is shown in
2114+
lines *Throttle* and *Dynamic Brake*, respectively, in brackets, e.g.:
2115+
Throttle: 0% (50%).
2116+
2117+
The complete distributed power configuration is displayed in the
2118+
Distributed Power Info extended HUD page, where the state of all locomotives
2119+
in the train are shown, as well as in the *Train DPU Info* window, which is
2120+
displayed after pressing ``<Shift+F9>``, and which shows only the state of the first
2121+
locomotive of each locomotive group, as occurs also in displays of real
2122+
locomotives.
2123+
2124+
.. image:: images/physics-dpu-window.png
2125+
2126+
Engines of AI Trains
2127+
--------------------
2128+
2129+
For AI trains, the AI software directly generates the remote control
2130+
signals, i.e. there is no player-controlled locomotive.
20432131
In this way, all engines use the same physics code for power and friction.
20442132

2045-
This software model will ensure that non-player controlled engines will
2133+
This software model will ensure that non-player controlled engines will
20462134
behave exactly the same way as player controlled ones.
20472135

20482136
.. _physics-braking:

Source/ORTS.Common/Input/UserCommand.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public enum UserCommand
4343
[GetString("Display Station Labels")] DisplayStationLabels,
4444
[GetString("Display Switch Window")] DisplaySwitchWindow,
4545
[GetString("Display Train Operations Window")] DisplayTrainOperationsWindow,
46+
[GetString("Display Train Dpu Window")] DisplayTrainDpuWindow,
4647
[GetString("Display Next Station Window")] DisplayNextStationWindow,
4748
[GetString("Display Compass Window")] DisplayCompassWindow,
4849
[GetString("Display Train List Window")] DisplayTrainListWindow,
@@ -210,5 +211,14 @@ public enum UserCommand
210211
[GetString("Control AI Fire On")] ControlAIFireOn,
211212
[GetString("Control AI Fire Off")] ControlAIFireOff,
212213
[GetString("Control AI Fire Reset")] ControlAIFireReset,
214+
215+
//Distributed power
216+
[GetString("Control DP Move To Front")] ControlDPMoveToFront,
217+
[GetString("Control DP Move To Back")] ControlDPMoveToBack,
218+
[GetString("Control DP Traction")] ControlDPTraction,
219+
[GetString("Control DP Idle")] ControlDPIdle,
220+
[GetString("Control DP Brake")] ControlDPBrake,
221+
[GetString("Control DP More")] ControlDPMore,
222+
[GetString("Control DP Less")] ControlDPLess,
213223
}
214224
}

Source/ORTS.Settings/InputSettings.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,14 @@ static void InitializeCommands(UserCommandInput[] Commands)
436436

437437
Commands[(int)UserCommand.ControlWaterScoop] = new UserCommandKeyInput(0x15);
438438
Commands[(int)UserCommand.ControlWiper] = new UserCommandKeyInput(0x2F);
439+
// Distributed power
440+
Commands[(int)UserCommand.ControlDPMoveToFront] = new UserCommandKeyInput(0x18, KeyModifiers.Control); //O
441+
Commands[(int)UserCommand.ControlDPMoveToBack] = new UserCommandKeyInput(0x18, KeyModifiers.Control | KeyModifiers.Shift); //O
442+
Commands[(int)UserCommand.ControlDPTraction] = new UserCommandKeyInput(0x26, KeyModifiers.Control); //L
443+
Commands[(int)UserCommand.ControlDPIdle] = new UserCommandKeyInput(0x26, KeyModifiers.Control | KeyModifiers.Shift); //L
444+
Commands[(int)UserCommand.ControlDPBrake] = new UserCommandKeyInput(0x28, KeyModifiers.Control); //
445+
Commands[(int)UserCommand.ControlDPMore] = new UserCommandKeyInput(0x16, KeyModifiers.Control); //U
446+
Commands[(int)UserCommand.ControlDPLess] = new UserCommandKeyInput(0x16, KeyModifiers.Control | KeyModifiers.Shift); //U
439447

440448
Commands[(int)UserCommand.DebugClockBackwards] = new UserCommandKeyInput(0x0C);
441449
Commands[(int)UserCommand.DebugClockForwards] = new UserCommandKeyInput(0x0D);
@@ -475,6 +483,7 @@ static void InitializeCommands(UserCommandInput[] Commands)
475483
Commands[(int)UserCommand.DisplaySwitchWindow] = new UserCommandKeyInput(0x42);
476484
Commands[(int)UserCommand.DisplayTrackMonitorWindow] = new UserCommandModifiableKeyInput(0x3E, Commands[(int)UserCommand.DisplayNextWindowTab]);
477485
Commands[(int)UserCommand.DisplayTrainOperationsWindow] = new UserCommandKeyInput(0x43);
486+
Commands[(int)UserCommand.DisplayTrainDpuWindow] = new UserCommandKeyInput(0x43, KeyModifiers.Shift);
478487

479488
Commands[(int)UserCommand.GameAutopilotMode] = new UserCommandKeyInput(0x1E, KeyModifiers.Alt);
480489
Commands[(int)UserCommand.GameChangeCab] = new UserCommandKeyInput(0x12, KeyModifiers.Control);

Source/ORTS.Settings/UserSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ public enum DirectXFeature
425425
[Default(new[] { 50, 50 })]
426426
public int[] WindowPosition_TrainOperations { get; set; }
427427
[Default(new[] { 50, 50 })]
428+
public int[] WindowPosition_TrainDpu { get; set; }
429+
[Default(new[] { 50, 50 })]
428430
public int[] WindowPosition_CarOperations { get; set; }
429431
[Default(new[] { 50, 50 })]
430432
public int[] WindowPosition_ComposeMessage { get; set; }

Source/Orts.Simulation/Common/Commands.cs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,134 @@ public override string ToString()
13111311
}
13121312
}
13131313

1314+
// Distributed Power controls
1315+
[Serializable()]
1316+
public sealed class DPMoveToFrontCommand : Command
1317+
{
1318+
public static MSTSWagon Receiver { get; set; }
1319+
1320+
public DPMoveToFrontCommand(CommandLog log)
1321+
: base(log)
1322+
{
1323+
Redo();
1324+
}
1325+
1326+
public override void Redo()
1327+
{
1328+
Receiver.Train.DPMoveToFront();
1329+
// Report();
1330+
}
1331+
}
1332+
1333+
[Serializable()]
1334+
public sealed class DPMoveToBackCommand : Command
1335+
{
1336+
public static MSTSWagon Receiver { get; set; }
1337+
1338+
public DPMoveToBackCommand(CommandLog log)
1339+
: base(log)
1340+
{
1341+
Redo();
1342+
}
1343+
1344+
public override void Redo()
1345+
{
1346+
Receiver.Train.DPMoveToBack();
1347+
// Report();
1348+
}
1349+
}
1350+
1351+
[Serializable()]
1352+
public sealed class DPIdleCommand : Command
1353+
{
1354+
public static MSTSWagon Receiver { get; set; }
1355+
1356+
public DPIdleCommand(CommandLog log)
1357+
: base(log)
1358+
{
1359+
Redo();
1360+
}
1361+
1362+
public override void Redo()
1363+
{
1364+
Receiver.Train.DPIdle();
1365+
// Report();
1366+
}
1367+
}
1368+
1369+
[Serializable()]
1370+
public sealed class DPTractionCommand : Command
1371+
{
1372+
public static MSTSWagon Receiver { get; set; }
1373+
1374+
public DPTractionCommand(CommandLog log)
1375+
: base(log)
1376+
{
1377+
Redo();
1378+
}
1379+
1380+
public override void Redo()
1381+
{
1382+
Receiver.Train.DPTraction();
1383+
// Report();
1384+
}
1385+
}
1386+
1387+
[Serializable()]
1388+
public sealed class DPDynamicBrakeCommand : Command
1389+
{
1390+
public static MSTSWagon Receiver { get; set; }
1391+
1392+
public DPDynamicBrakeCommand(CommandLog log)
1393+
: base(log)
1394+
{
1395+
Redo();
1396+
}
1397+
1398+
public override void Redo()
1399+
{
1400+
Receiver.Train.DPDynamicBrake();
1401+
// Report();
1402+
}
1403+
}
1404+
1405+
[Serializable()]
1406+
public sealed class DPMoreCommand : Command
1407+
{
1408+
public static MSTSWagon Receiver { get; set; }
1409+
1410+
public DPMoreCommand(CommandLog log)
1411+
: base(log)
1412+
{
1413+
Redo();
1414+
}
1415+
1416+
public override void Redo()
1417+
{
1418+
Receiver.Train.DPMore();
1419+
// Report();
1420+
}
1421+
}
1422+
1423+
[Serializable()]
1424+
public sealed class DPLessCommand : Command
1425+
{
1426+
public static MSTSWagon Receiver { get; set; }
1427+
1428+
public DPLessCommand(CommandLog log)
1429+
: base(log)
1430+
{
1431+
Redo();
1432+
}
1433+
1434+
public override void Redo()
1435+
{
1436+
Receiver.Train.DPLess();
1437+
// Report();
1438+
}
1439+
}
1440+
1441+
13141442
// Steam controls
13151443
[Serializable()]
13161444
public sealed class ContinuousSteamHeatCommand : ContinuousCommand

Source/Orts.Simulation/MultiPlayer/Message.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,6 +1357,7 @@ public override void HandleMsg() //only client will get message, thus will set s
13571357
train.InitializeBrakes();
13581358
//train.InitializeSignals(false);//client do it won't have impact
13591359
train.CheckFreight();
1360+
train.SetDPUnitIDs();
13601361
bool canPlace = true;
13611362
Train.TCSubpathRoute tempRoute = train.CalculateInitialTrainPosition(ref canPlace);
13621363

@@ -1567,6 +1568,7 @@ public override void HandleMsg() //only client will get message, thus will set s
15671568
train.CalculatePositionOfCars();
15681569
train.travelled = Travelled;
15691570
train.CheckFreight();
1571+
train.SetDPUnitIDs();
15701572
return;
15711573
}
15721574
train1.TrainType = Train.TRAINTYPE.REMOTE;
@@ -1601,6 +1603,7 @@ public override void HandleMsg() //only client will get message, thus will set s
16011603
train1.InitializeBrakes();
16021604
//train1.InitializeSignals(false);
16031605
train1.CheckFreight();
1606+
train1.SetDPUnitIDs();
16041607
bool canPlace = true;
16051608
Train.TCSubpathRoute tempRoute = train1.CalculateInitialTrainPosition(ref canPlace);
16061609

@@ -2621,6 +2624,7 @@ public override void HandleMsg()
26212624
t.MUDirection = (Direction)mDirection1;
26222625
train.ControlMode = Train.TRAIN_CONTROL.EXPLORER;
26232626
train.CheckFreight();
2627+
train.SetDPUnitIDs();
26242628
train.InitializeBrakes();
26252629
canPlace = true;
26262630
tempRoute = train.CalculateInitialTrainPosition(ref canPlace);
@@ -2671,6 +2675,7 @@ public override void HandleMsg()
26712675
train2.LeadLocomotive = null;
26722676
train2.LeadNextLocomotive();
26732677
train2.CheckFreight();
2678+
train2.SetDPUnitIDs();
26742679

26752680
//train2 may contain myself, and no other players, thus will make myself controlling this train
26762681
/*if (train2.Cars.Contains(MPManager.Simulator.PlayerLocomotive))
@@ -2693,6 +2698,7 @@ public override void HandleMsg()
26932698
train2.MUDirection = (Direction)mDirection2;
26942699
train2.ControlMode = Train.TRAIN_CONTROL.EXPLORER;
26952700
train2.CheckFreight();
2701+
train2.SetDPUnitIDs();
26962702
train2.InitializeBrakes();
26972703
canPlace = true;
26982704
tempRoute = train2.CalculateInitialTrainPosition(ref canPlace);
@@ -2967,6 +2973,7 @@ public override void HandleMsg()
29672973
train.MUDirection = (Direction)mDirection;
29682974
train.RearTDBTraveller = new Traveller(MPManager.Simulator.TSectionDat, MPManager.Simulator.TDB.TrackDB.TrackNodes, TileX, TileZ, X, Z, direction == 0 ? Traveller.TravellerDirection.Forward : Traveller.TravellerDirection.Backward);
29692975
train.CheckFreight();
2976+
train.SetDPUnitIDs();
29702977
train.CalculatePositionOfCars();
29712978
train.LeadLocomotive = null; train2.LeadLocomotive = null;
29722979
if (Lead != -1 && Lead < train.Cars.Count) train.LeadLocomotive = train.Cars[Lead];

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,7 @@ public AITrain CreateAITrainDetail(Service_Definition sd, Traffic_Service_Defini
961961
#endif
962962
train.CreateRoute(false); // create route without use of FrontTDBtraveller
963963
train.CheckFreight(); // check if train is freight or passenger
964+
train.SetDPUnitIDs(); // distributed power
964965
if (!isInitialPlayerTrain || train.InitialSpeed != 0) train.AITrainDirectionForward = true;
965966
train.BrakeLine3PressurePSI = 0;
966967

Source/Orts.Simulation/Simulation/AIs/AITrain.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4434,6 +4434,7 @@ public void CoupleAI(Train attachTrain, bool thisTrainFront, bool attachTrainFro
44344434
attachTrain.PresentPosition[1].SetTCPosition(tn.TCCrossReference, offset, direction);
44354435
// set various items
44364436
attachTrain.CheckFreight();
4437+
attachTrain.SetDPUnitIDs();
44374438
attachTrain.activityClearingDistanceM = attachTrain.Cars.Count < standardTrainMinCarNo ? shortClearingDistanceM : standardClearingDistanceM;
44384439
attachCar.SignalEvent(Event.Couple);
44394440

@@ -4549,6 +4550,7 @@ public void CoupleAIToStatic(Train attachTrain, bool thisTrainFront, bool attach
45494550
PresentPosition[1].SetTCPosition(tn.TCCrossReference, offset, direction);
45504551
// set various items
45514552
CheckFreight();
4553+
SetDPUnitIDs();
45524554
activityClearingDistanceM = Cars.Count < standardTrainMinCarNo ? shortClearingDistanceM : standardClearingDistanceM;
45534555
attachCar.SignalEvent(Event.Couple);
45544556

@@ -4789,8 +4791,10 @@ public void TerminateCoupling(Train attachTrain, bool thisTrainFront, bool attac
47894791
attachTrain.PresentPosition[1].SetTCPosition(tn.TCCrossReference, offset, direction);
47904792
// set various items
47914793
CheckFreight();
4794+
SetDPUnitIDs();
47924795
activityClearingDistanceM = Cars.Count < standardTrainMinCarNo ? shortClearingDistanceM : standardClearingDistanceM;
47934796
attachTrain.CheckFreight();
4797+
attachTrain.SetDPUnitIDs();
47944798
attachTrain.activityClearingDistanceM = attachTrain.Cars.Count < standardTrainMinCarNo ? shortClearingDistanceM : standardClearingDistanceM;
47954799
// anticipate reversal point and remove active action
47964800
TCRoute.ReversalInfo[TCRoute.activeSubpath].ReverseReversalOffset = Math.Max(PresentPosition[0].TCOffset - 10f, 0.3f);

0 commit comments

Comments
 (0)