mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-08 05:10:41 +01:00
fix: single horn lever tones, skid of a stopped vehicle, destination signs after the timetable
- a cab with only the combined horn lever (horn_bt:) and no separate tone buttons could sound the low and high tones at once, which the lever can't do; the tones are now exclusive there, and releasing the other tone's key keeps the lever in place (eu07/maszyna#392), - a skid ends when the wheel speed crosses the vehicle speed, which never happens with wheels locked by the brakes once the vehicle has stopped; it stayed flagged as slipping, its braking force capped by the sliding adhesion, so a few such wagons could push a braked consist around (eu07/maszyna#479). Like the Vel > 0.1 trap of the skid start, a (nearly) stopped vehicle with wheels turning at its speed is no longer slipping, - lit electronic destination signs kept showing the relation after the timetable had ended, until the battery was switched off (eu07/maszyna#446); they are blanked when the timetable runs out, physical signs stay up until changed. What the horn keys do to WarningSignal moves out of the TTrain command handlers into vehicle/horn, which the unit tests call; the handlers keep the gauges. Tests: tests/unit/test_horn.cpp, both kinds of cabs, and tests/unit/test_wheelslip.cpp, a wagon which stopped in a skid on slippery rail holds on a slope like one which stopped on dry rail (without the fix it runs away, ~70 km/h after 30 s).
This commit is contained in:
@@ -462,6 +462,7 @@ set(CORE_SOURCES
|
||||
"utilities/utilities.cpp"
|
||||
"scripting/ladderlogic.cpp"
|
||||
"vehicle/handover.cpp"
|
||||
"vehicle/horn.cpp"
|
||||
)
|
||||
add_library(eu07_core STATIC ${CORE_SOURCES})
|
||||
target_link_libraries(eu07_core PUBLIC eu07_settings)
|
||||
|
||||
@@ -5032,6 +5032,14 @@ void TMoverParameters::ComputeTotalForce(double dt)
|
||||
{
|
||||
SlippingWheels = true;
|
||||
}
|
||||
if (true == SlippingWheels && Vel < 0.1 // the same crude trap: (nearly) stopped vehicle...
|
||||
&& std::abs(nrot * M_PI * WheelDiameter - V) < 0.1 / 3.6) // ...with wheels turning at its speed, i.e. locked wheels at the end of a skid
|
||||
{
|
||||
// locked wheels never pass the rotation speed test below once the vehicle stops, and a skidding vehicle
|
||||
// keeps its braking force capped by the sliding adhesion, so the rest of the consist could push it around
|
||||
SlippingWheels = false;
|
||||
nrot = V / M_PI / WheelDiameter;
|
||||
}
|
||||
double temp_nrot = nrot;
|
||||
if (true == SlippingWheels)
|
||||
{
|
||||
|
||||
39
tests/unit/test_horn.cpp
Normal file
39
tests/unit/test_horn.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
|
||||
#include "vehicle/horn.h"
|
||||
|
||||
// the horn keys of the cab: low tone (A) and high tone (shift+A), held to sound
|
||||
|
||||
TEST_CASE("horn: a single lever for both tones sounds one tone at a time (eu07/maszyna#392)") {
|
||||
auto signal{0};
|
||||
signal = horn::pressed(signal, horn::low, true);
|
||||
REQUIRE(signal == horn::low);
|
||||
|
||||
// the lever moved over to the high tone while the low tone key is still held
|
||||
signal = horn::pressed(signal, horn::high, true);
|
||||
CHECK(signal == horn::high);
|
||||
|
||||
// letting go of the low tone key leaves the high tone sounding
|
||||
signal = horn::released(signal, horn::low);
|
||||
CHECK(signal == horn::high);
|
||||
|
||||
signal = horn::released(signal, horn::high);
|
||||
CHECK(signal == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: separate buttons for the tones can sound both at once") {
|
||||
auto signal{0};
|
||||
signal = horn::pressed(signal, horn::low, false);
|
||||
signal = horn::pressed(signal, horn::high, false);
|
||||
CHECK(signal == (horn::low | horn::high));
|
||||
|
||||
signal = horn::released(signal, horn::low);
|
||||
CHECK(signal == horn::high);
|
||||
}
|
||||
|
||||
TEST_CASE("horn: releasing a tone leaves the other bits of the signal alone") {
|
||||
auto const third{4}; // the third horn, operated separately
|
||||
CHECK(horn::released(horn::low | third, horn::low) == third);
|
||||
CHECK(horn::pressed(third, horn::high, true) == (horn::high | third));
|
||||
}
|
||||
62
tests/unit/test_wheelslip.cpp
Normal file
62
tests/unit/test_wheelslip.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
using test::definition;
|
||||
using test::track;
|
||||
using test::vehicle;
|
||||
|
||||
namespace {
|
||||
|
||||
struct braking_result {
|
||||
bool skidded{false}; // the wheels locked while braking
|
||||
bool stopped{false}; // came (almost) to a stop on the braking rail
|
||||
};
|
||||
|
||||
// brakes the wagon from 40 km/h on the Braking rail until it's (nearly) stopped, then has it stand on the
|
||||
// Standing one for 30 seconds
|
||||
braking_result brake_to_stop_then_stand(vehicle &Coach, track const &Braking, track const &Standing) {
|
||||
braking_result result;
|
||||
Coach.hold_brake_pipe(5.0);
|
||||
Coach.run(5.0, Braking);
|
||||
Coach.hold_brake_pipe(3.5); // service braking
|
||||
// a skidding wagon brakes on the sliding adhesion only, give it time
|
||||
for (auto step = 0; step < 60000; ++step) { // 600 s in 0.01 s steps
|
||||
result.skidded |= Coach->SlippingWheels;
|
||||
if (auto const speed{std::abs(Coach.speed())}; speed > 0.0 && speed < 0.1) {
|
||||
// almost stopped; a skidding wagon is still skidding at this point
|
||||
result.stopped = true;
|
||||
break;
|
||||
}
|
||||
Coach.step(Braking);
|
||||
}
|
||||
Coach.run(30.0, Standing);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// the engine doesn't let a stopped, braked vehicle slip (the Vel > 0.1 trap in TMoverParameters::ComputeTotalForce),
|
||||
// so a wagon which stopped in a skid should be no different from one which stopped without it. it used to stay
|
||||
// skidding instead, with its brakes capped by the sliding adhesion, and slid or crept away (eu07/maszyna#479)
|
||||
TEST_CASE("wheelslip: a wagon which stopped in a skid holds like one which stopped without it") {
|
||||
track const dry{0.15, 0.0};
|
||||
track const slippery{0.02, 0.0};
|
||||
// held only by the brakes: the sliding adhesion on this rail is too low for the slope
|
||||
track const slipperyslope{0.02, 0.08};
|
||||
|
||||
vehicle normal{definition::load("wagon"), 40.0};
|
||||
auto const normalbraking{brake_to_stop_then_stand(normal, dry, slipperyslope)};
|
||||
vehicle skidding{definition::load("wagon"), 40.0};
|
||||
auto const skiddingbraking{brake_to_stop_then_stand(skidding, slippery, slipperyslope)};
|
||||
|
||||
// the test means something only when the braking went as planned
|
||||
REQUIRE(normalbraking.stopped);
|
||||
REQUIRE_FALSE(normalbraking.skidded);
|
||||
REQUIRE(skiddingbraking.stopped);
|
||||
REQUIRE(skiddingbraking.skidded);
|
||||
|
||||
CHECK(std::abs(normal.speed()) < 0.1);
|
||||
CHECK(std::abs(skidding.speed()) < 0.1);
|
||||
CHECK_FALSE(skidding->SlippingWheels);
|
||||
}
|
||||
@@ -1304,6 +1304,10 @@ TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, doub
|
||||
#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 ) ) {
|
||||
|
||||
@@ -7794,6 +7794,13 @@ int TDynamicObject::RouteWish(TTrack *tr)
|
||||
return Mechanik ? Mechanik->CrossRoute(tr) : 0; // wg AI albo prosto
|
||||
};
|
||||
|
||||
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 to, std::string 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ą
|
||||
|
||||
@@ -816,6 +816,8 @@ private:
|
||||
// zapytanie do AI, po którym segmencie skrzyżowania jechać
|
||||
int RouteWish(TTrack *tr);
|
||||
void DestinationSet(std::string to, std::string 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;
|
||||
|
||||
@@ -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);
|
||||
@@ -7863,9 +7858,13 @@ void TTrain::OnCommand_hornlowactivate(TTrain *Train, command_data const &Comman
|
||||
// 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 +7884,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);
|
||||
@@ -7907,9 +7900,13 @@ void TTrain::OnCommand_hornhighactivate(TTrain *Train, command_data const &Comma
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
24
vehicle/horn.cpp
Normal file
24
vehicle/horn.cpp
Normal 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
24
vehicle/horn.h
Normal 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
|
||||
Reference in New Issue
Block a user