17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 18:09:04 +01:00

fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds

- automatic couplers don't catch above the safe collision speed (#86): with crash damage on they couple at a
  closing speed of 15 km/h at most, a faster hit locks the coupling until the vehicles part and the collision
  runs its course. On the way: CollisionVelocity() read a head-on 10 + 10 km/h as 14.1 km/h (quaternions turn
  by half the angle), it's now taken from the touching ends; CollisionDetect() took the other vehicle's speed
  the wrong way round for vehicles facing each other. Head-on hits now derail sooner, at the intended limit,
- a single horn lever can't sound both tones at once (eu07/maszyna#392),
- a skid ends once the vehicle stops on locked wheels (eu07/maszyna#479),
- electronic destination signs are blanked at the end of the timetable (eu07/maszyna#446),
- horns need the air or the voltage to sound (eu07/maszyna#290, #76): HornType=[Pneumatic]|Electric and
  Horn3Type in Cntrl.; a pneumatic horn needs 0.2 MPa in the main reservoir (or the main pipe without one),
  an electric one the low voltage; the lever stays where it was put,
- compressor start delay (#67): CompressorStartDelay [s] in Brake., counted from the compressor getting its
  power, after the engine starts say; none by default,
- the channel and volume buttons of a radio without power do nothing (eu07/maszyna#429); the power check of
  the radio is TMoverParameters::RadioPowered(),
- a lever moved smoothly sounds the positions it passes (#83).

Sonar findings in the functions changed: nesting, nested conditionals, commented-out code, std::format,
references to const. Unit tests: test::pair for two vehicles running into each other, test_couplers,
test_horn, test_wheelslip, test_compressor, test_radio, test_gauge_positions.
This commit is contained in:
jerrrrycho
2026-10-17 14:41:59 +02:00
parent c7f9796387
commit ec345f94cb
21 changed files with 1235 additions and 171 deletions

View File

@@ -30,6 +30,8 @@ http://mozilla.org/MPL/2.0/.
#include "world/station.h"
#include "input/keyboardinput.h"
#include <format>
#define LOGVELOCITY 0
#define LOGORDERS 1
#define LOGSTOPS 1
@@ -1093,10 +1095,9 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub
if (Point.fDist < passengerstopmaxdistance * 0.5 ) {
// zaliczamy posterunek w pewnej odległości przed (choć W4 nie zasłania już semafora)
#if LOGSTOPS
WriteLog(
pVehicle->asName + " as " + TrainParams.TrainName
+ ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute)
+ " passed " + asNextStop); // informacja
WriteLog(std::format(
"{} as {}: at {}:{} passed {}",
pVehicle->asName, TrainParams.TrainName, simulation::Time.data().wHour, simulation::Time.data().wMinute, asNextStop)); // informacja
#endif
// przy jakim dystansie (stanie licznika) ma przesunąć na następny postój
fLastStopExpDist = mvOccupied->DistCounter + 0.250 + 0.001 * fLength;
@@ -1253,10 +1254,9 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub
TrainParams.StationIndexInc(); // przejście do następnej
asNextStop = TrainParams.NextStop(); // pobranie kolejnego miejsca zatrzymania
#if LOGSTOPS
WriteLog(
pVehicle->asName + " as " + TrainParams.TrainName
+ ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute)
+ " next " + asNextStop); // informacja
WriteLog(std::format(
"{} as {}: at {}:{} next {}",
pVehicle->asName, TrainParams.TrainName, simulation::Time.data().wHour, simulation::Time.data().wMinute, asNextStop)); // informacja
#endif
// update consist weight, brake settings and ai braking tables
// NOTE: this calculation is expected to run after completing loading/unloading
@@ -1297,13 +1297,16 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub
else {
// jeśli dojechaliśmy do końca rozkładu
#if LOGSTOPS
WriteLog(
pVehicle->asName + " as " + TrainParams.TrainName
+ ": at " + std::to_string(simulation::Time.data().wHour) + ":" + std::to_string(simulation::Time.data().wMinute)
+ " end of route."); // informacja
WriteLog(std::format(
"{} as {}: at {}:{} end of route.",
pVehicle->asName, TrainParams.TrainName, simulation::Time.data().wHour, simulation::Time.data().wMinute)); // informacja
#endif
asNextStop = TrainParams.NextStop(); // informacja o końcu trasy
TrainParams.NewName("none"); // czyszczenie nieaktualnego rozkładu
// koniec kursu, gasimy wyświetlacze relacji (tablice blaszane zostają)
for( auto *vehicle { pVehicles[ end::front ] }; vehicle != nullptr; vehicle = vehicle->Next() ) {
vehicle->DestinationClear();
}
// ma nie podjeżdżać pod W4 i ma je pomijać
iDrivigFlags &= ~moveStopCloser;
if( false == TestFlag( iDrivigFlags, movePushPull ) ) {

View File

@@ -4825,12 +4825,13 @@ void TDynamicObject::RenderSounds() {
// horns
{
// for moving vehicle combine regular horn activation flag with emergency brake horn activation flag, if the brake is active
auto const warningsignal { (
MoverParameters->Vel > 0.5 && MoverParameters->AlarmChainFlag ?
// for moving vehicle combine regular horn activation flag with emergency brake horn activation flag, if the brake is active;
// the horns without the air or the voltage they need stay silent
auto const warningsignal { MoverParameters->SoundingHorns(
( MoverParameters->Vel > 0.5 && MoverParameters->AlarmChainFlag ?
MoverParameters->EmergencyBrakeWarningSignal :
0 )
| MoverParameters->WarningSignal };
| MoverParameters->WarningSignal ) };
if( TestFlag( warningsignal, 1 ) ) {
sHorn1.play( sound_flags::exclusive | sound_flags::looping );
@@ -7794,7 +7795,14 @@ int TDynamicObject::RouteWish(TTrack *tr)
return Mechanik ? Mechanik->CrossRoute(tr) : 0; // wg AI albo prosto
};
void TDynamicObject::DestinationSet(std::string to, std::string numer) {
void TDynamicObject::DestinationClear() {
if( false == DestinationSign.has_light ) { return; } // physical destination signs remain up until manually changed
// update_destinations() shows the blank sign in place of the missing destination
DestinationSign.destination = null_handle;
}
void TDynamicObject::DestinationSet(std::string const &to, std::string const &numer) {
// ustawienie stacji docelowej oraz wymiennej tekstury 4, jeśli istnieje plik
// w zasadzie, to każdy wagon mógłby mieć inną stację docelową
// zwłaszcza w towarowych, pod kątem zautomatyzowania maewrów albo pracy górki
@@ -7823,6 +7831,15 @@ void TDynamicObject::DestinationSet(std::string to, std::string numer) {
if( DestinationSign.script.empty() ) { return; } // no script so no way to make the texture
if( numer == "none" ) { return; } // blank or incomplete/malformed timetable, don't bother
auto const trainname { [this]() -> std::string {
if( ctOwner != nullptr ) { return ctOwner->TrainName(); }
if( Mechanik != nullptr ) { return Mechanik->TrainName(); }
return "none"; } }; // shouldn't get here but, eh
auto const instance { [this]() -> std::string {
if( DestinationSign.instancing == "name" ) { return MoverParameters->Name; }
if( DestinationSign.instancing == "type" ) { return MoverParameters->TypeName; }
return "none"; } };
std::string signrequest {
"make:"
+ DestinationSign.script
@@ -7834,17 +7851,10 @@ void TDynamicObject::DestinationSet(std::string to, std::string numer) {
ctOwner->Vehicle()->MoverParameters->Name ) // owned vehicle, safer to point to owner as carriages can have identical names
// basic instancing string
// NOTE: underscore doesn't have any magic meaning for the time being, it's just less likely to conflict with regular dictionary keys
+ "&_id1=" + (
ctOwner != nullptr ? ctOwner->TrainName() :
Mechanik != nullptr ? Mechanik->TrainName() :
"none" ) }; // shouldn't get here but, eh
+ "&_id1=" + trainname() };
// TBD, TODO: replace instancing with support for variables in extra parameters string?
if( false == DestinationSign.instancing.empty() ) {
signrequest +=
"&_id2=" + (
DestinationSign.instancing == "name" ? MoverParameters->Name :
DestinationSign.instancing == "type" ? MoverParameters->TypeName :
"none" );
signrequest += "&_id2=" + instance();
}
// optionl extra parameters
if( false == DestinationSign.parameters.empty() ) {

View File

@@ -815,7 +815,9 @@ private:
void ParamSet(int what, int into);
// zapytanie do AI, po którym segmencie skrzyżowania jechać
int RouteWish(TTrack *tr);
void DestinationSet(std::string to, std::string numer);
void DestinationSet(std::string const &to, std::string const &numer);
// blanks electronic destination sign at the end of the service; physical signs stay until changed
void DestinationClear();
material_handle DestinationFind( std::string Destination );
void OverheadTrack(float o);
glm::dvec3 get_future_movement() const;

View File

@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "vehicle/Gauge.h"
#include "vehicle/gaugepositions.h"
#include "utilities/parser.h"
#include "model/Model3d.h"
#include "vehicle/DynObj.h"
@@ -302,42 +303,37 @@ bool
TGauge::UpdateValue( float fNewDesired ) {
auto const desiredtimes100 = static_cast<int>( std::round( 100.0 * fNewDesired ) );
if( desiredtimes100 == static_cast<int>( std::round( 100.0 * m_targetvalue ) ) ) {
auto const previoustimes100 = static_cast<int>( std::round( 100.0 * m_targetvalue ) );
if( desiredtimes100 == previoustimes100 ) {
return true;
}
m_targetvalue = fNewDesired;
// if there's any sound associated with new requested value, play it
// check value-specific table first...
if( auto const fullinteger { desiredtimes100 % 100 == 0 }; fullinteger ) {
// filter out values other than full integers
auto const lookup = m_soundfxvalues.find( desiredtimes100 / 100 );
if( lookup != m_soundfxvalues.end() ) {
lookup->second.play();
return true;
}
}
else {
if( desiredtimes100 % 100 != 0 ) {
// toggle the control to continous range/exclusive sound mode from now on
m_soundtype = sound_flags::exclusive;
}
// if there's any sound associated with a position reached on the way to the new requested value, play it
// check value-specific table first...
if( auto const reached = gauge_positions::last_reached( m_soundfxvalues, previoustimes100, desiredtimes100 );
reached != m_soundfxvalues.end() ) {
reached->second.play();
return true;
}
// ...and if there isn't any, fall back on the basic set...
// HACK: crude way to discern controls with continuous and quantized value range
if( auto const currentvalue = GetValue(); currentvalue < fNewDesired ) {
auto const currentvalue = GetValue();
if( currentvalue < fNewDesired && false == m_soundfxincrease.empty() ) {
// shift up
if( false == m_soundfxincrease.empty() ) {
m_soundfxincrease.play( m_soundtype );
return true;
}
m_soundfxincrease.play( m_soundtype );
return true;
}
else if( currentvalue > fNewDesired ) {
if( currentvalue > fNewDesired && false == m_soundfxdecrease.empty() ) {
// shift down
if( false == m_soundfxdecrease.empty() ) {
m_soundfxdecrease.play( m_soundtype );
return true;
}
m_soundfxdecrease.play( m_soundtype );
return true;
}
return false; // no suitable sound was found
};
}
void TGauge::PutValue(float fNewDesired)
{ // McZapkie-281102: natychmiastowe wpisanie wartosci

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "vehicle/Train.h"
#include "vehicle/horn.h"
#include "utilities/Globals.h"
#include "simulation/simulation.h"
@@ -7841,16 +7842,10 @@ void TTrain::OnCommand_hornlowactivate(TTrain *Train, command_data const &Comman
if (Command.action == GLFW_PRESS)
{
// only need to react to press, sound will continue until stopped
if (false == TestFlag(Train->mvOccupied->WarningSignal, 1))
if (false == TestFlag(Train->mvOccupied->WarningSignal, horn::low))
{
// turn on
Train->mvOccupied->WarningSignal |= 1;
/*
if( true == TestFlag( Train->mvOccupied->WarningSignal, 2 ) ) {
// low and high horn are treated as mutually exclusive
Train->mvControlled->WarningSignal &= ~2;
}
*/
Train->mvOccupied->WarningSignal = horn::pressed(Train->mvOccupied->WarningSignal, horn::low, Train->has_single_horn_lever());
// visual feedback
Train->ggHornButton.UpdateValue(-1.0);
Train->ggHornLowButton.UpdateValue(1.0);
@@ -7859,13 +7854,13 @@ void TTrain::OnCommand_hornlowactivate(TTrain *Train, command_data const &Comman
else if (Command.action == GLFW_RELEASE)
{
// turn off
/*
// NOTE: we turn off both low and high horn, due to unreliability of release event when shift key is involved
Train->mvOccupied->WarningSignal &= ~( 1 | 2 );
*/
Train->mvOccupied->WarningSignal &= ~1;
Train->mvOccupied->WarningSignal = horn::released(Train->mvOccupied->WarningSignal, horn::low);
// visual feedback
Train->ggHornButton.UpdateValue(0.0);
if (false == (Train->has_single_horn_lever() && TestFlag(Train->mvOccupied->WarningSignal, horn::high)))
{
// leave the single lever alone if it was moved to the other tone in the meantime
Train->ggHornButton.UpdateValue(0.0);
}
Train->ggHornLowButton.UpdateValue(0.0);
}
}
@@ -7885,16 +7880,10 @@ void TTrain::OnCommand_hornhighactivate(TTrain *Train, command_data const &Comma
if (Command.action == GLFW_PRESS)
{
// only need to react to press, sound will continue until stopped
if (false == TestFlag(Train->mvOccupied->WarningSignal, 2))
if (false == TestFlag(Train->mvOccupied->WarningSignal, horn::high))
{
// turn on
Train->mvOccupied->WarningSignal |= 2;
/*
if( true == TestFlag( Train->mvOccupied->WarningSignal, 1 ) ) {
// low and high horn are treated as mutually exclusive
Train->mvControlled->WarningSignal &= ~1;
}
*/
Train->mvOccupied->WarningSignal = horn::pressed(Train->mvOccupied->WarningSignal, horn::high, Train->has_single_horn_lever());
// visual feedback
Train->ggHornButton.UpdateValue(1.0);
Train->ggHornHighButton.UpdateValue(1.0);
@@ -7903,13 +7892,13 @@ void TTrain::OnCommand_hornhighactivate(TTrain *Train, command_data const &Comma
else if (Command.action == GLFW_RELEASE)
{
// turn off
/*
// NOTE: we turn off both low and high horn, due to unreliability of release event when shift key is involved
Train->mvOccupied->WarningSignal &= ~( 1 | 2 );
*/
Train->mvOccupied->WarningSignal &= ~2;
Train->mvOccupied->WarningSignal = horn::released(Train->mvOccupied->WarningSignal, horn::high);
// visual feedback
Train->ggHornButton.UpdateValue(0.0);
if (false == (Train->has_single_horn_lever() && TestFlag(Train->mvOccupied->WarningSignal, horn::low)))
{
// leave the single lever alone if it was moved to the other tone in the meantime
Train->ggHornButton.UpdateValue(0.0);
}
Train->ggHornHighButton.UpdateValue(0.0);
}
}
@@ -7953,12 +7942,7 @@ void TTrain::OnCommand_radiotoggle(TTrain *Train, command_data const &Command)
return;
}
// NOTE: we ignore the lack of 3d model to allow system reset after receiving radio-stop signal
/*
if( false == Train->m_controlmapper.contains( "radio_sw:" ) ) {
return;
}
*/
// NOTE: works without the switch in the 3d model too, to allow system reset after receiving radio-stop signal
// only reacting to press, so the sound can loop uninterrupted
if (false == Train->mvOccupied->Radio)
{
@@ -8003,9 +7987,13 @@ void TTrain::OnCommand_radiochannelincrease(TTrain *Train, command_data const &C
if (Command.action == GLFW_PRESS)
{
command_data newCommand = Command;
newCommand.param1 = Train->RadioChannel() + 1;
OnCommand_radiochannelset(Train, newCommand);
// the channel buttons of a radio switched off or without power do nothing
if (Train->mvOccupied->RadioPowered())
{
command_data newCommand = Command;
newCommand.param1 = Train->RadioChannel() + 1;
OnCommand_radiochannelset(Train, newCommand);
}
Train->ggRadioChannelNext.UpdateValue(1.0);
}
@@ -8021,9 +8009,12 @@ void TTrain::OnCommand_radiochanneldecrease(TTrain *Train, command_data const &C
if (Command.action == GLFW_PRESS)
{
command_data newCommand = Command;
newCommand.param1 = Train->RadioChannel() - 1;
OnCommand_radiochannelset(Train, newCommand);
if (Train->mvOccupied->RadioPowered())
{
command_data newCommand = Command;
newCommand.param1 = Train->RadioChannel() - 1;
OnCommand_radiochannelset(Train, newCommand);
}
Train->ggRadioChannelPrevious.UpdateValue(1.0);
}
@@ -8049,7 +8040,7 @@ void TTrain::OnCommand_radiostopsend(TTrain *Train, command_data const &Command)
if (Command.action == GLFW_PRESS)
{
if (true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->mvOccupied->RadioPowered())
{
simulation::Region->RadioStop(Train->Dynamic()->GetPosition());
}
@@ -8067,7 +8058,7 @@ void TTrain::OnCommand_radiostopenable(TTrain *Train, command_data const &Comman
{
if (Command.action == GLFW_PRESS && Train->ggRadioStop.GetValue() == 0)
{
if (true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->mvOccupied->RadioPowered())
{
simulation::Region->RadioStop(Train->Dynamic()->GetPosition());
}
@@ -8090,7 +8081,7 @@ void TTrain::OnCommand_radiostoptest(TTrain *Train, command_data const &Command)
if (Command.action == GLFW_PRESS)
{
if (Train->RadioChannel() == 10 && true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->RadioChannel() == 10 && Train->mvOccupied->RadioPowered())
{
Train->Dynamic()->RadioStop();
}
@@ -8108,7 +8099,7 @@ void TTrain::OnCommand_radiocall1send(TTrain *Train, command_data const &Command
{
if (Command.action == GLFW_PRESS)
{
if (Train->RadioChannel() != 10 && true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->RadioChannel() != 10 && Train->mvOccupied->RadioPowered())
{
simulation::Events.queue_receivers(radio_message::call1, Train->Dynamic()->GetPosition());
}
@@ -8127,7 +8118,7 @@ void TTrain::OnCommand_radiocall3send(TTrain *Train, command_data const &Command
if (Command.action == GLFW_PRESS)
{
if (Train->RadioChannel() != 10 && true == Train->mvOccupied->Radio && (Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable))
if (Train->RadioChannel() != 10 && Train->mvOccupied->RadioPowered())
{
simulation::Events.queue_receivers(radio_message::call3, Train->Dynamic()->GetPosition());
}
@@ -8146,9 +8137,13 @@ void TTrain::OnCommand_radiovolumeincrease(TTrain *Train, command_data const &Co
if (Command.action == GLFW_PRESS)
{
command_data newCommand = Command;
newCommand.param1 = Train->m_radiovolume + 0.125;
OnCommand_radiovolumeset(Train, newCommand);
// so do the volume buttons
if (Train->mvOccupied->RadioPowered())
{
command_data newCommand = Command;
newCommand.param1 = Train->m_radiovolume + 0.125;
OnCommand_radiovolumeset(Train, newCommand);
}
Train->ggRadioVolumeNext.UpdateValue(1.0);
}
else if (Command.action == GLFW_RELEASE)
@@ -8163,9 +8158,12 @@ void TTrain::OnCommand_radiovolumedecrease(TTrain *Train, command_data const &Co
if (Command.action == GLFW_PRESS)
{
command_data newCommand = Command;
newCommand.param1 = Train->m_radiovolume - 0.125;
OnCommand_radiovolumeset(Train, newCommand);
if (Train->mvOccupied->RadioPowered())
{
command_data newCommand = Command;
newCommand.param1 = Train->m_radiovolume - 0.125;
OnCommand_radiovolumeset(Train, newCommand);
}
Train->ggRadioVolumePrevious.UpdateValue(1.0);
}
else if (Command.action == GLFW_RELEASE)
@@ -10106,7 +10104,7 @@ void TTrain::update_sounds_radio()
std::erase_if(m_radiomessages, [](auto const &source) { return false == source.second->is_playing(); });
}
// adjust audibility of remaining messages based on current radio conditions
auto const radioenabled{true == mvOccupied->Radio && (mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable)};
auto const radioenabled{mvOccupied->RadioPowered()};
for (auto const &[messagechannel, messagesound] : m_radiomessages)
{
auto const volume{true == radioenabled && Dynamic()->Mechanik != nullptr && messagechannel == RadioChannel() ? m_radiovolume : 0.0};
@@ -10893,7 +10891,7 @@ void TTrain::radio_message(sound_source *Message, int const Channel)
m_radiomessages.emplace_back(Channel, std::make_shared<sound_source>(m_radiosound));
// assign sound to the template and play it
auto &message = *m_radiomessages.back().second.get();
auto const radioenabled{true == mvOccupied->Radio && (mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable)};
auto const radioenabled{mvOccupied->RadioPowered()};
auto const volume{true == radioenabled && Dynamic()->Mechanik != nullptr && Channel == RadioChannel() ? 1.0 : 0.0};
message.copy_sounds(*Message).gain(static_cast<float>(volume)).play();
}

View File

@@ -195,6 +195,9 @@ class TTrain {
bool initialize_button(cParser &Parser, std::string const &Label, int const Cabindex);
// helper, returns true for EMU with oerlikon brake
bool is_eztoer() const;
// helper, returns true if both horn tones are operated with the same lever, without separate low and high tone buttons
bool has_single_horn_lever() const {
return ggHornButton.SubModel != nullptr && ggHornLowButton.SubModel == nullptr && ggHornHighButton.SubModel == nullptr; }
// locates nearest vehicle belonging to the consist
TDynamicObject *find_nearest_consist_vehicle(bool freefly, glm::vec3 pos) const;
// mover master controller to specified position

35
vehicle/gaugepositions.h Normal file
View File

@@ -0,0 +1,35 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <map>
// positions of a cab control which have sounds of their own
namespace gauge_positions {
// the entry of the last position of Positions the control reached on its way from the Previous to the Desired value,
// both in hundredths of a position, or Positions.end() if it reached none: a lever moved smoothly passes the
// positions rather than stops at them, and should sound each one it gets to
template <typename Value_>
typename std::map<int, Value_>::iterator last_reached(std::map<int, Value_> &Positions, int const Previous, int const Desired) {
auto reached{Positions.end()};
for (auto position{Positions.begin()}; position != Positions.end(); ++position) {
auto const at{position->first * 100};
if (Desired > Previous && at > Previous && at <= Desired) {
reached = position; // moving up, the last one in the ascending order is the one reached last
}
else if (Desired < Previous && at < Previous && at >= Desired) {
return position; // moving down, the first one is
}
}
return reached;
}
} // namespace gauge_positions

24
vehicle/horn.cpp Normal file
View File

@@ -0,0 +1,24 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "vehicle/horn.h"
namespace horn {
int pressed(int const Signal, int const Tone, bool const Singlelever) {
auto const other{Tone == low ? high : low};
return (Signal | Tone) & (Singlelever ? ~other : ~0);
}
int released(int const Signal, int const Tone) {
return Signal & ~Tone;
}
} // namespace horn

24
vehicle/horn.h Normal file
View File

@@ -0,0 +1,24 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
// the horn controls of the cab, on the WarningSignal bits of the vehicle (1: low tone, 2: high tone)
namespace horn {
int const low{1};
int const high{2};
// the signal after the key or button of the Tone is pressed. a single lever for both tones, without
// separate buttons for them, can't sound both tones at once
int pressed(int Signal, int Tone, bool Singlelever);
// the signal after the key or button of the Tone is released
int released(int Signal, int Tone);
} // namespace horn