mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 00:49:19 +02:00
Merge branch 'master' into faster-parser
This commit is contained in:
@@ -1755,6 +1755,8 @@ class TMoverParameters
|
||||
bool HasCamshaft{false};
|
||||
int DynamicBrakeType = 0; /*patrz dbrake_**/
|
||||
int DynamicBrakeAmpmeters = 2; /*liczba amperomierzy przy hamowaniu ED*/
|
||||
bool SplitEDPneumaticBrake = false; /*czy hamulec ED i pneumatyczny pomocniczy są rozdzielone na osobne nastawniki*/
|
||||
int DynamicBrakeCtrlPosNo = 10; /*ilosc pozycji nastawnika hamulca ED (DBPN, domyslnie 10)*/
|
||||
double DynamicBrakeRes = 5.8; /*rezystancja oporników przy hamowaniu ED*/
|
||||
double DynamicBrakeRes1 = 5.8; /*rezystancja oporników przy hamowaniu ED - 1 tryb*/
|
||||
double DynamicBrakeRes2 = 5.8; /*rezystancja oporników przy hamowaniu ED - 2 tryb*/
|
||||
@@ -2031,6 +2033,7 @@ class TMoverParameters
|
||||
int ManualBrakePos = 0; /*nastawa hamulca recznego*/
|
||||
double LocalBrakePosA = 0.0; /*nastawa hamulca pomocniczego*/
|
||||
double LocalBrakePosAEIM = 0.0; /*pozycja hamulca pomocniczego ep dla asynchronicznych ezt*/
|
||||
double DynamicBrakeCtrlPos = 0.0; /*nastawa nastawnika hamulca elektrodynamicznego (0..1) - aktywne tylko gdy SplitEDPneumaticBrake*/
|
||||
bool UniversalBrakeButtonActive[3] = {false, false, false}; /* brake button pressed */
|
||||
/*
|
||||
int BrakeStatus = b_off; //0 - odham, 1 - ham., 2 - uszk., 4 - odluzniacz, 8 - antyposlizg, 16 - uzyte EP, 32 - pozycja R, 64 - powrot z R
|
||||
@@ -2381,6 +2384,12 @@ class TMoverParameters
|
||||
/*! ABu 010205: - skrajne polozenia ham. pomocniczego*/
|
||||
bool IncManualBrakeLevel(int CtrlSpeed);
|
||||
bool DecManualBrakeLevel(int CtrlSpeed);
|
||||
/*! oddzielny nastawnik hamulca elektrodynamicznego (gdy SplitEDPneumaticBrake)*/
|
||||
bool IncDynamicBrakeLevel(float const CtrlSpeed);
|
||||
bool DecDynamicBrakeLevel(float const CtrlSpeed);
|
||||
bool DynamicBrakeLevelSet(double Position);
|
||||
double DynamicBrakeRatio(void) const;
|
||||
bool DynamicBrakeAvailable(void) const; /*czy ED dostepny w aktualnym oknie predkosci Vh0..Vh1*/
|
||||
bool DynamicBrakeSwitch(bool Switch);
|
||||
bool RadiostopSwitch(bool Switch);
|
||||
bool AlarmChainSwitch(bool const State);
|
||||
|
||||
@@ -3883,6 +3883,79 @@ bool TMoverParameters::DecLocalBrakeLevel(float const CtrlSpeed)
|
||||
return DBL;
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// zwiększenie nastawy nastawnika hamulca elektrodynamicznego (DynamicBrakeCtrl)
|
||||
// uzywane gdy SplitEDPneumaticBrake = true
|
||||
// *************************************************************************************************
|
||||
bool TMoverParameters::IncDynamicBrakeLevel(float const CtrlSpeed)
|
||||
{
|
||||
if (false == SplitEDPneumaticBrake)
|
||||
return false;
|
||||
if (DynamicBrakeCtrlPosNo < 1)
|
||||
return false;
|
||||
if (DynamicBrakeCtrlPos < 1.0)
|
||||
{
|
||||
DynamicBrakeCtrlPos = std::min(1.0, DynamicBrakeCtrlPos + (double)CtrlSpeed / (double)DynamicBrakeCtrlPosNo);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// zmniejszenie nastawy nastawnika hamulca elektrodynamicznego (DynamicBrakeCtrl)
|
||||
// *************************************************************************************************
|
||||
bool TMoverParameters::DecDynamicBrakeLevel(float const CtrlSpeed)
|
||||
{
|
||||
if (false == SplitEDPneumaticBrake)
|
||||
return false;
|
||||
if (DynamicBrakeCtrlPosNo < 1)
|
||||
return false;
|
||||
if (DynamicBrakeCtrlPos > 0.0)
|
||||
{
|
||||
DynamicBrakeCtrlPos = std::max(0.0, DynamicBrakeCtrlPos - (double)CtrlSpeed / (double)DynamicBrakeCtrlPosNo);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// bezposrednie ustawienie nastawnika hamulca elektrodynamicznego (DynamicBrakeCtrl)
|
||||
// *************************************************************************************************
|
||||
bool TMoverParameters::DynamicBrakeLevelSet(double Position)
|
||||
{
|
||||
if (false == SplitEDPneumaticBrake)
|
||||
return false;
|
||||
DynamicBrakeCtrlPos = clamp(Position, 0.0, 1.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// odczyt nastawy nastawnika hamulca elektrodynamicznego (DynamicBrakeCtrl)
|
||||
// zwraca 0 jezeli SplitEDPneumaticBrake nie jest aktywne (dla zachowania starej logiki)
|
||||
// *************************************************************************************************
|
||||
double TMoverParameters::DynamicBrakeRatio(void) const
|
||||
{
|
||||
if (false == SplitEDPneumaticBrake)
|
||||
return 0.0;
|
||||
return clamp(DynamicBrakeCtrlPos, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// czy hamulec ED jest aktualnie dostepny w oknie predkosci Vh0..Vh1
|
||||
// *************************************************************************************************
|
||||
bool TMoverParameters::DynamicBrakeAvailable(void) const
|
||||
{
|
||||
double const vh0{eimc[eimc_p_Vh0]};
|
||||
double const vh1{eimc[eimc_p_Vh1]};
|
||||
if (vh1 <= 0.001)
|
||||
{
|
||||
// brak zdefiniowanej strefy - ED dziala zawsze
|
||||
return true;
|
||||
}
|
||||
// strefa wylaczenia jest definiowana przez Vh0 (minimum); powyzej Vh0 ED dziala
|
||||
return (Vel >= vh0);
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// Q: 20160711
|
||||
// zwiększenie nastawy hamulca ręcznego
|
||||
@@ -4439,7 +4512,13 @@ void TMoverParameters::UpdatePipePressure(double dt)
|
||||
dpLocalValve = LocHandle->GetPF(std::max(lbpa, LocalBrakePosAEIM), Hamulec->GetBCP(), ScndPipePress, dt, 0);
|
||||
}
|
||||
else
|
||||
dpLocalValve = LocHandle->GetPF(LocalBrakePosAEIM, Hamulec->GetBCP(), ScndPipePress, dt, 0);
|
||||
{
|
||||
// in EIM stock the pneumatic local brake is normally driven only by the MED algorithm
|
||||
// (LocalBrakePosAEIM). When SplitEDPneumaticBrake is active the dedicated LocalBrake
|
||||
// lever should apply pneumatic pressure on the locomotive directly, bypassing MED.
|
||||
double lbpa = SplitEDPneumaticBrake ? LocalBrakePosA : 0.0;
|
||||
dpLocalValve = LocHandle->GetPF(std::max(lbpa, LocalBrakePosAEIM), Hamulec->GetBCP(), ScndPipePress, dt, 0);
|
||||
}
|
||||
|
||||
LockPipe = PipePress < (LockPipe ? LockPipeOff : LockPipeOn);
|
||||
bool lock_new = (LockPipe && !UnlockPipe && (BrakeCtrlPosR > HandleUnlock)) || ((EmergencyCutsOffHandle) && (EmergencyValveFlow > 0)); // new simple codition based on .fiz
|
||||
@@ -6233,14 +6312,19 @@ double TMoverParameters::TractionForce(double dt)
|
||||
}
|
||||
double edBCP = Hamulec->GetEDBCP();
|
||||
auto const localbrakeactive{(CabOccupied != 0) && (LocHandle->GetCP() > 0.25)};
|
||||
// gdy SplitEDPneumaticBrake jest aktywne, hamulec pomocniczy (LocalBrake) NIE wymusza
|
||||
// zalaczenia hamulca elektrodynamicznego - ED jest sterowany wlasnym nastawnikiem
|
||||
auto const dynbrakectrlactive{SplitEDPneumaticBrake && (DynamicBrakeRatio() > 0.01) && DynamicBrakeAvailable()};
|
||||
auto const localbrakeforED{SplitEDPneumaticBrake ? false : localbrakeactive};
|
||||
if ((false == Doors.instances[side::left].is_closed) || (false == Doors.instances[side::right].is_closed) ||
|
||||
(Doors.permit_needed && (Doors.instances[side::left].open_permit || Doors.instances[side::right].open_permit)))
|
||||
{
|
||||
DynamicBrakeFlag = true;
|
||||
}
|
||||
else if (((edBCP < 0.25) && (false == localbrakeactive) && (AnPos < 0.01)) || ((edBCP < 0.25) && (ShuntModeAllow) && (LocalBrakePosA < 0.01)))
|
||||
else if (((edBCP < 0.25) && (false == localbrakeforED) && (false == dynbrakectrlactive) && (AnPos < 0.01)) ||
|
||||
((edBCP < 0.25) && (ShuntModeAllow) && (false == dynbrakectrlactive) && (LocalBrakePosA < 0.01)))
|
||||
DynamicBrakeFlag = false;
|
||||
else if ((((BrakePress > 0.25) && (edBCP > 0.25) || localbrakeactive)) || (AnPos > 0.02))
|
||||
else if ((((BrakePress > 0.25) && (edBCP > 0.25) || localbrakeforED)) || (AnPos > 0.02) || dynbrakectrlactive)
|
||||
DynamicBrakeFlag = true;
|
||||
edBCP = Hamulec->GetEDBCP() * eimc[eimc_p_abed]; // stala napedu
|
||||
if ((DynamicBrakeFlag))
|
||||
@@ -6267,8 +6351,21 @@ double TMoverParameters::TractionForce(double dt)
|
||||
}
|
||||
else
|
||||
{
|
||||
PosRatio = -std::max(std::min(edBCP * 1.0 / MaxBrakePress[0], 1.0), AnPos) * std::max(0.0, std::min(1.0, (Vel - eimc[eimc_p_Vh0]) / (eimc[eimc_p_Vh1] - eimc[eimc_p_Vh0])));
|
||||
eimv[eimv_Fzad] = -std::min(1.0, std::max(LocalBrakeRatio(), edBCP / MaxBrakePress[0]));
|
||||
// strefa wylaczenia ED (Vh0..Vh1) - mnoznik 0..1 zaleznie od predkosci
|
||||
double const vhRamp = std::max(0.0, std::min(1.0, (Vel - eimc[eimc_p_Vh0]) / (eimc[eimc_p_Vh1] - eimc[eimc_p_Vh0])));
|
||||
if (SplitEDPneumaticBrake)
|
||||
{
|
||||
// w trybie split LocalBrake nie zaleca ED; uzywany jest DynamicBrakeCtrl
|
||||
// oraz zwykle zapotrzebowanie z hamulca zespolonego (edBCP)
|
||||
double const edLeverDemand = DynamicBrakeRatio();
|
||||
PosRatio = -std::max(std::max(edLeverDemand, std::min(edBCP / MaxBrakePress[0], 1.0)), AnPos) * vhRamp;
|
||||
eimv[eimv_Fzad] = -std::min(1.0, std::max(edLeverDemand, edBCP / MaxBrakePress[0]));
|
||||
}
|
||||
else
|
||||
{
|
||||
PosRatio = -std::max(std::min(edBCP * 1.0 / MaxBrakePress[0], 1.0), AnPos) * vhRamp;
|
||||
eimv[eimv_Fzad] = -std::min(1.0, std::max(LocalBrakeRatio(), edBCP / MaxBrakePress[0]));
|
||||
}
|
||||
}
|
||||
tmp = 5;
|
||||
}
|
||||
@@ -7231,10 +7328,26 @@ void TMoverParameters::CheckEIMIC(double dt)
|
||||
{
|
||||
double offset = EIMCtrlAdditionalZeros ? 1.0 : 0.0;
|
||||
double multiplier = (EIMCtrlEmergency ? 1.0 : 0.0) + offset;
|
||||
// gdy SplitEDPneumaticBrake jest wlaczone, sterowanie ujemne (hamowanie ED) bierzemy
|
||||
// z osobnego nastawnika DynamicBrakeCtrl, a nie z hamulca pomocniczego (LocalBrake).
|
||||
// Dodatkowo zapotrzebowanie ED jest ograniczone przez strefe Vh0..Vh1: powyzej Vh1 - pelny ED,
|
||||
// pomiedzy Vh1 a Vh0 - liniowe zanikanie, ponizej Vh0 - zerowe (brak hamowania pneumatycznego
|
||||
// jako uzupelnienia, poniewaz LocalBrake jest oddzielny i operuje wylacznie pneumatyka).
|
||||
double brakeDemand = SplitEDPneumaticBrake ? DynamicBrakeRatio() : LocalBrakeRatio();
|
||||
if (SplitEDPneumaticBrake)
|
||||
{
|
||||
double const vh0 = eimc[eimc_p_Vh0];
|
||||
double const vh1 = eimc[eimc_p_Vh1];
|
||||
if (vh1 > vh0 + 0.001)
|
||||
{
|
||||
double const vhRamp = clamp((Vel - vh0) / (vh1 - vh0), 0.0, 1.0);
|
||||
brakeDemand *= vhRamp;
|
||||
}
|
||||
}
|
||||
switch (EIMCtrlType)
|
||||
{
|
||||
case 0:
|
||||
eimic = (LocalBrakeRatio() > 0.01 ? -LocalBrakeRatio() : eimic_analog > 0.01 ? eimic_analog : (double)MainCtrlPos / (double)MainCtrlPosNo);
|
||||
eimic = (brakeDemand > 0.01 ? -brakeDemand : eimic_analog > 0.01 ? eimic_analog : (double)MainCtrlPos / (double)MainCtrlPosNo);
|
||||
if (EIMCtrlAdditionalZeros || EIMCtrlEmergency)
|
||||
{
|
||||
if (eimic > 0.001)
|
||||
@@ -7348,7 +7461,9 @@ void TMoverParameters::CheckEIMIC(double dt)
|
||||
eimic = 0; // shut off retarder
|
||||
if ((UniCtrlIntegratedBrakeCtrl == false) && (UniCtrlIntegratedLocalBrakeCtrl == false))
|
||||
{
|
||||
eimic = (LocalBrakeRatio() > 0.01 ? -LocalBrakeRatio() : eimic);
|
||||
// w trybie SplitEDPneumaticBrake hamowanie ED przychodzi z osobnego nastawnika,
|
||||
// LocalBrake operuje wylacznie pneumatycznym hamulcem lokomotywy
|
||||
eimic = (brakeDemand > 0.01 ? -brakeDemand : eimic);
|
||||
}
|
||||
}
|
||||
if (LocHandleTimeTraxx)
|
||||
@@ -10709,6 +10824,18 @@ void TMoverParameters::LoadFIZ_Cntrl(std::string const &line)
|
||||
extract_value(DynamicBrakeAmpmeters, "DBAM", line, "");
|
||||
}
|
||||
|
||||
// split ED/pneumatic brake control: when true the local brake operates only on the
|
||||
// pneumatic locomotive cylinder while electrodynamic braking is commanded by a
|
||||
// separate "dynamicbrakectrl" lever (or the negative range of jointctrl).
|
||||
extract_value(SplitEDPneumaticBrake, "SplitEDPneumaticBrake", line, "");
|
||||
// number of stepped positions for the dedicated ED brake controller (default 10)
|
||||
DynamicBrakeCtrlPosNo = 10;
|
||||
extract_value(DynamicBrakeCtrlPosNo, "DBPN", line, "");
|
||||
if (DynamicBrakeCtrlPosNo < 1)
|
||||
{
|
||||
DynamicBrakeCtrlPosNo = 10;
|
||||
}
|
||||
|
||||
extract_value(MainCtrlPosNo, "MCPN", line, "");
|
||||
extract_value(ScndCtrlPosNo, "SCPN", line, "");
|
||||
extract_value(ScndInMain, "SCIM", line, "");
|
||||
|
||||
@@ -319,6 +319,12 @@ int eu07_application::init(int Argc, char *Argv[])
|
||||
return result;
|
||||
}
|
||||
|
||||
// configure the OS console according to Globals.ShowSystemConsole.
|
||||
// must run AFTER init_settings (so the ini-loaded value is honoured) and
|
||||
// BEFORE the first WriteLog below (so colored \033[...] sequences emitted
|
||||
// by utilities/Logs.cpp land in a console with VT processing enabled).
|
||||
init_console();
|
||||
|
||||
WriteLog("Starting MaSzyna rail vehicle simulator (release: " + Global.asVersion + ")");
|
||||
WriteLog("For online documentation and additional files refer to: http://eu07.pl");
|
||||
WriteLog("Authors: Marcin_EU, McZapkie, ABu, Winger, Tolaris, nbmx, OLO_EU, Bart, Quark-t, "
|
||||
@@ -978,6 +984,102 @@ void eu07_application::init_debug()
|
||||
#endif
|
||||
}
|
||||
|
||||
// Honours Globals.ShowSystemConsole (default: true).
|
||||
//
|
||||
// Windows: if the toggle is on, ensure a console is attached (allocate one when
|
||||
// the binary was linked as the WINDOWS subsystem and none is inherited), then
|
||||
// reopen the C stdio streams onto it and turn on ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
// so the ANSI colour escapes used by utilities/Logs.cpp render correctly.
|
||||
// If the toggle is off, hide the console window (or free an allocated one) so
|
||||
// the GUI launches without a stray cmd window.
|
||||
//
|
||||
// Other platforms: no-op. stdout is the launching terminal (or already detached);
|
||||
// hiding it on Linux/macOS would mean redirecting to /dev/null and is left to the
|
||||
// user's shell (e.g. "./eu07 >/dev/null 2>&1").
|
||||
void eu07_application::init_console()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
HWND consoleWnd = ::GetConsoleWindow();
|
||||
const bool hadConsole = (consoleWnd != nullptr);
|
||||
|
||||
if (Global.ShowSystemConsole)
|
||||
{
|
||||
if (!hadConsole)
|
||||
{
|
||||
// no console inherited (e.g. WINDOWS subsystem build, or launched
|
||||
// detached) -- create one and wire stdio to it so printf in
|
||||
// utilities/Logs.cpp actually reaches the user
|
||||
if (::AllocConsole())
|
||||
{
|
||||
FILE *fp = nullptr;
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||
freopen_s(&fp, "CONIN$", "r", stdin);
|
||||
// keep C++ streams in sync with the redirected C streams
|
||||
std::ios::sync_with_stdio(true);
|
||||
std::cout.clear();
|
||||
std::cerr.clear();
|
||||
std::clog.clear();
|
||||
std::cin.clear();
|
||||
consoleWnd = ::GetConsoleWindow();
|
||||
}
|
||||
}
|
||||
|
||||
// enable ANSI virtual-terminal processing so the colour escapes in
|
||||
// utilities/Logs.cpp ("\033[1;37;41m...", "\033[32m...") render as
|
||||
// colours instead of literal garbage on the conhost.
|
||||
//
|
||||
// IMPORTANT: do NOT rely on GetStdHandle(STD_OUTPUT_HANDLE). After
|
||||
// AllocConsole + freopen_s the CRT may have rewired things, and on a
|
||||
// console-subsystem build the std handle can also point to a redirected
|
||||
// pipe/file rather than the actual console screen buffer -- in either
|
||||
// case SetConsoleMode silently fails and the escape bytes leak through
|
||||
// as text ("←[32m" etc). Open CONOUT$ directly to get the real screen
|
||||
// buffer handle, then flip VT on it.
|
||||
auto enable_vt = [](const char *devName) {
|
||||
HANDLE h = ::CreateFileA(
|
||||
devName,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
nullptr);
|
||||
if (h == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
DWORD mode = 0;
|
||||
if (::GetConsoleMode(h, &mode))
|
||||
{
|
||||
::SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT);
|
||||
}
|
||||
::CloseHandle(h);
|
||||
};
|
||||
// CONOUT$ is the console's active screen buffer; enabling VT on it
|
||||
// affects everything that ends up being written there, regardless of
|
||||
// which FILE* / std handle the writer used
|
||||
enable_vt("CONOUT$");
|
||||
|
||||
if (consoleWnd)
|
||||
{
|
||||
::ShowWindow(consoleWnd, SW_SHOW);
|
||||
// give the console a recognisable title
|
||||
::SetConsoleTitleA("MaSzyna log");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// user wants no console window -- hide whatever we have
|
||||
if (consoleWnd)
|
||||
{
|
||||
::ShowWindow(consoleWnd, SW_HIDE);
|
||||
}
|
||||
// if we'd allocated one ourselves on a previous run path we'd FreeConsole here,
|
||||
// but on first init the console (if any) was inherited from the launcher and
|
||||
// belongs to it -- just hiding the window is the least-surprising behaviour
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void eu07_application::init_files()
|
||||
{
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ private:
|
||||
// methods
|
||||
bool needs_ogl() const;
|
||||
void init_debug();
|
||||
void init_console();
|
||||
void init_files();
|
||||
int init_settings( int Argc, char *Argv[] );
|
||||
int init_locale();
|
||||
|
||||
@@ -17,11 +17,16 @@ gl::cubemap::~cubemap()
|
||||
|
||||
void gl::cubemap::alloc(GLint format, int width, int height, GLenum components, GLenum type)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, *this);
|
||||
for (GLuint tgt = GL_TEXTURE_CUBE_MAP_POSITIVE_X; tgt <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; tgt++)
|
||||
glTexImage2D(tgt, 0, format, width, height, 0, components, type, nullptr);
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, *this);
|
||||
for (GLuint tgt = GL_TEXTURE_CUBE_MAP_POSITIVE_X; tgt <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; tgt++)
|
||||
glTexImage2D(tgt, 0, format, width, height, 0, components, type, nullptr);
|
||||
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||
// make the cubemap sample-complete with no mipmaps required.
|
||||
// generate_mipmaps() will later override MIN_FILTER once mips actually exist.
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||
}
|
||||
|
||||
void gl::cubemap::bind(int unit)
|
||||
|
||||
@@ -30,6 +30,13 @@ commanddescription_sequence Commands_descriptions = {
|
||||
{"mastercontrollerdecrease", command_target::vehicle, command_mode::oneoff},
|
||||
{"mastercontrollerdecreasefast", command_target::vehicle, command_mode::oneoff},
|
||||
{"mastercontrollerset", command_target::vehicle, command_mode::oneoff},
|
||||
|
||||
{"dynamicbrakecontrollerincrease", command_target::vehicle, command_mode::oneoff},
|
||||
{"dynamicbrakecontrollerdecrease", command_target::vehicle, command_mode::oneoff},
|
||||
{"dynamicbrakecontrollerset", command_target::vehicle, command_mode::oneoff},
|
||||
{"dynamicbrakecontrollerincreasefast", command_target::vehicle, command_mode::oneoff},
|
||||
{"dynamicbrakecontrollerdecreasefast", command_target::vehicle, command_mode::oneoff},
|
||||
|
||||
{"secondcontrollerincrease", command_target::vehicle, command_mode::oneoff},
|
||||
{"secondcontrollerincreasefast", command_target::vehicle, command_mode::oneoff},
|
||||
{"secondcontrollerdecrease", command_target::vehicle, command_mode::oneoff},
|
||||
@@ -410,6 +417,11 @@ std::unordered_map<std::string, user_command> commandMap = {
|
||||
{"mastercontrollerdecrease", user_command::mastercontrollerdecrease},
|
||||
{"mastercontrollerdecreasefast", user_command::mastercontrollerdecreasefast},
|
||||
{"mastercontrollerset", user_command::mastercontrollerset},
|
||||
{"dynamicbrakecontrollerincrease", user_command::dynamicbrakecontrollerincrease},
|
||||
{"dynamicbrakecontrollerdecrease", user_command::dynamicbrakecontrollerdecrease},
|
||||
{"dynamicbrakecontrollerset", user_command::dynamicbrakecontrollerset},
|
||||
{"dynamicbrakecontrollerincreasefast", user_command::dynamicbrakecontrollerincreasefast},
|
||||
{"dynamicbrakecontrollerdecreasefast", user_command::dynamicbrakecontrollerdecreasefast},
|
||||
{"secondcontrollerincrease", user_command::secondcontrollerincrease},
|
||||
{"secondcontrollerincreasefast", user_command::secondcontrollerincreasefast},
|
||||
{"secondcontrollerdecrease", user_command::secondcontrollerdecrease},
|
||||
|
||||
@@ -24,6 +24,11 @@ enum class user_command
|
||||
mastercontrollerdecrease,
|
||||
mastercontrollerdecreasefast,
|
||||
mastercontrollerset,
|
||||
dynamicbrakecontrollerincrease,
|
||||
dynamicbrakecontrollerdecrease,
|
||||
dynamicbrakecontrollerset,
|
||||
dynamicbrakecontrollerincreasefast,
|
||||
dynamicbrakecontrollerdecreasefast,
|
||||
secondcontrollerincrease,
|
||||
secondcontrollerincreasefast,
|
||||
secondcontrollerdecrease,
|
||||
|
||||
@@ -32,6 +32,13 @@ driverkeyboard_input::default_bindings() {
|
||||
{ user_command::mastercontrollerdecrease, {GLFW_KEY_KP_SUBTRACT, ""} },
|
||||
{ user_command::mastercontrollerdecreasefast, {GLFW_KEY_KP_SUBTRACT | keymodifier::shift, ""} },
|
||||
// mastercontrollerset,
|
||||
|
||||
{ user_command::dynamicbrakecontrollerincrease, {GLFW_KEY_KP_3 | keymodifier::control, ""} },
|
||||
{ user_command::dynamicbrakecontrollerdecrease, {GLFW_KEY_KP_9 | keymodifier::control, ""} },
|
||||
// mastercontrollerset,
|
||||
{ user_command::dynamicbrakecontrollerincreasefast, {GLFW_KEY_KP_3 | keymodifier::control | keymodifier::shift, ""} },
|
||||
{ user_command::dynamicbrakecontrollerdecreasefast, {GLFW_KEY_KP_9 | keymodifier::control | keymodifier::shift, ""} },
|
||||
|
||||
{ user_command::secondcontrollerincrease, {GLFW_KEY_KP_DIVIDE, ""} },
|
||||
{ user_command::secondcontrollerincreasefast, {GLFW_KEY_KP_DIVIDE | keymodifier::shift, ""} },
|
||||
{ user_command::secondcontrollerdecrease, {GLFW_KEY_KP_MULTIPLY, ""} },
|
||||
|
||||
@@ -534,6 +534,9 @@ drivermouse_input::default_bindings() {
|
||||
{ "mainctrl:", {
|
||||
user_command::mastercontrollerset,
|
||||
user_command::none } },
|
||||
{ "dynamicbrakectrl:", {
|
||||
user_command::dynamicbrakecontrollerset,
|
||||
user_command::none } },
|
||||
{ "scndctrl:", {
|
||||
user_command::secondcontrollerset,
|
||||
user_command::none } },
|
||||
|
||||
@@ -312,15 +312,37 @@ void python_taskqueue::exit()
|
||||
m_condition.notify_all();
|
||||
// let them free up their shit before we proceed
|
||||
m_workers = {};
|
||||
// get rid of the leftover tasks
|
||||
// with the workers dead we don't have to worry about concurrent access anymore
|
||||
for (auto task : m_tasks.data)
|
||||
// drop any pending render/upload tasks. the workers are joined at this
|
||||
// point so the locks are strictly defensive, but they cost nothing and
|
||||
// document intent. clearing the deque also actually releases the tasks,
|
||||
// which the previous code did not do (cancel() is a no-op stub).
|
||||
{
|
||||
task->cancel();
|
||||
std::lock_guard<std::mutex> lock(m_tasks.mutex);
|
||||
for (auto &task : m_tasks.data)
|
||||
{
|
||||
task->cancel();
|
||||
}
|
||||
m_tasks.data.clear();
|
||||
}
|
||||
// take a bow
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_uploadtasks.mutex);
|
||||
m_uploadtasks.data.clear();
|
||||
}
|
||||
// reclaim cached python objects while the interpreter is still alive,
|
||||
// so no Py_DECREF lands on a finalized interpreter during later teardown
|
||||
acquire_lock();
|
||||
for (auto &entry : m_renderers)
|
||||
{
|
||||
Py_XDECREF(entry.second);
|
||||
}
|
||||
m_renderers.clear();
|
||||
Py_XDECREF(m_stderr);
|
||||
m_stderr = nullptr;
|
||||
Py_XDECREF(m_main);
|
||||
m_main = nullptr;
|
||||
// take a bow
|
||||
Py_Finalize();
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
// adds specified task along with provided collection of data to the work queue. returns true on success
|
||||
@@ -518,8 +540,10 @@ void python_taskqueue::run(GLFWwindow *Context, rendertask_sequence &Tasks, uplo
|
||||
// TBD, TODO: add some idle time between tasks in case we're on a single thread cpu?
|
||||
} while (task != nullptr);
|
||||
// if there's nothing left to do wait until there is
|
||||
// but check every now and then on your own to minimize potential deadlock situations
|
||||
Condition.wait_for(std::chrono::seconds(5));
|
||||
// short timeout: notify_all() in exit() can race with the worker's
|
||||
// transition into the wait, and the run loop also drives prompt
|
||||
// shutdown checks
|
||||
Condition.wait_for(std::chrono::milliseconds(250));
|
||||
}
|
||||
// clean up thread state data
|
||||
PyEval_AcquireLock();
|
||||
@@ -527,6 +551,12 @@ void python_taskqueue::run(GLFWwindow *Context, rendertask_sequence &Tasks, uplo
|
||||
PyThreadState_Clear(threadstate);
|
||||
PyThreadState_Delete(threadstate);
|
||||
PyEval_ReleaseLock();
|
||||
|
||||
// detach the GL context before the worker terminates; some drivers
|
||||
// (NVIDIA on X11, certain Mesa/Wayland configs) hang in process teardown
|
||||
// if a context is still current on a dying thread
|
||||
if (Context)
|
||||
glfwMakeContextCurrent(nullptr);
|
||||
}
|
||||
|
||||
void python_taskqueue::update()
|
||||
|
||||
@@ -8,6 +8,14 @@ vec3 envmap_color( vec3 normal )
|
||||
vec3 refvec = reflect(f_pos.xyz, normal); // view space
|
||||
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space
|
||||
vec3 envcolor = texture(envmap, refvec).rgb;
|
||||
// Sanitize. The env cubemap can briefly contain NaN/Inf after a
|
||||
// reflection-pass regeneration (m_empty_cubemap was bound during the
|
||||
// face render and on NVIDIA can sample as undefined). Without this
|
||||
// guard, a single bad texel propagates through env_reflection in
|
||||
// apply_lights() and produces one-frame black flashes on glossy
|
||||
// (esp. specgloss) surfaces.
|
||||
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
|
||||
envcolor = max(envcolor, vec3(0.0));
|
||||
#else
|
||||
vec3 envcolor = vec3(0.5);
|
||||
#endif
|
||||
@@ -21,8 +29,13 @@ vec3 envmap_color_lod(vec3 fragnormal, float lod)
|
||||
#if ENVMAP_ENABLED
|
||||
vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly
|
||||
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing
|
||||
return textureLod(envmap, refvec, lod).rgb;
|
||||
vec3 envcolor = textureLod(envmap, refvec, lod).rgb;
|
||||
// See envmap_color() above — same NaN/Inf sanitize, also needed here
|
||||
// because mipmap generation propagates a single NaN texel across the
|
||||
// whole mip chain.
|
||||
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
|
||||
return max(envcolor, vec3(0.0));
|
||||
#else
|
||||
return vec3(0.5); // was vec3(0.0), match the non-LOD fallback
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,17 @@ vec4 tonemap(vec4 x)
|
||||
// with pureWhite=1.0 it collapses to identity (L*(1+L)/(1+L) = L) and
|
||||
// just clips HDR>1.0 at the framebuffer -> washed-out / burnt look.
|
||||
// ACES gives a smooth highlight shoulder + slight toe contrast.
|
||||
return FBOUT(vec4(ACESFilm(x.rgb), x.a));
|
||||
|
||||
// Last-line-of-defense sanitize. ACESFilm has the form
|
||||
// (x*(a*x+b)) / (x*(c*x+d)+e)
|
||||
// which maps NaN -> NaN and +Inf -> NaN (Inf/Inf). Either turns the
|
||||
// pixel black after framebuffer clamp. A negative HDR input feeds a
|
||||
// negative numerator/denominator and can produce non-physical output
|
||||
// that also looks like a black flash. Clamp to a sensible range
|
||||
// before the curve so a single bad upstream pixel can't escape.
|
||||
vec3 hdr = x.rgb;
|
||||
hdr = mix(hdr, vec3(0.0), vec3(any(isnan(hdr)) || any(isinf(hdr))));
|
||||
hdr = max(hdr, vec3(0.0));
|
||||
return FBOUT(vec4(ACESFilm(hdr), x.a));
|
||||
//return FBOUT(vec4(reinhard(x.rgb), x.a));
|
||||
}
|
||||
|
||||
@@ -1086,6 +1086,12 @@ bool global_settings::ConfigParseDebug(cParser& Parser, const std::string& token
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "showsystemconsole")
|
||||
{
|
||||
ParseOne(Parser, ShowSystemConsole);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "shakefactor")
|
||||
{
|
||||
ParseOne(Parser, ShakingMultiplierBF);
|
||||
@@ -1516,6 +1522,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "fullphysics", FullPhysics );
|
||||
export_as_text( Output, "debuglog", iWriteLogEnabled );
|
||||
export_as_text( Output, "multiplelogs", MultipleLogs );
|
||||
export_as_text( Output, "showsystemconsole", ShowSystemConsole );
|
||||
export_as_text( Output, "logs.filter", DisabledLogTypes );
|
||||
Output
|
||||
<< "mousescale "
|
||||
|
||||
@@ -86,6 +86,7 @@ struct global_settings {
|
||||
bool priorityLoadText3D{false}; // ladowanie T3D priorytetowo
|
||||
int iWriteLogEnabled{ 3 }; // maska bitowa: 1-zapis do pliku, 2-okienko, 4-nazwy torów
|
||||
bool MultipleLogs{ false };
|
||||
bool ShowSystemConsole{ false }; // toggle visibility of the OS console window with log output
|
||||
unsigned int DisabledLogTypes{ 0 };
|
||||
bool ParserLogIncludes{ true };
|
||||
// simulation
|
||||
|
||||
@@ -190,17 +190,16 @@ bool cParser::getTokens(unsigned int Count, bool ToLower, const char *Break)
|
||||
this->str("");
|
||||
this->clear();
|
||||
*/
|
||||
std::string token;
|
||||
for (unsigned int i = tokens.size(); i < Count; ++i)
|
||||
{
|
||||
readToken(token, ToLower, Break);
|
||||
if (token.empty())
|
||||
std::string token = readToken(ToLower, Break);
|
||||
if (true == token.empty())
|
||||
{
|
||||
// no more tokens
|
||||
break;
|
||||
}
|
||||
tokens.emplace_back(std::move(token));
|
||||
// collect parameters
|
||||
tokens.emplace_back(token);
|
||||
/*
|
||||
if (i == 0)
|
||||
this->str(token);
|
||||
@@ -219,6 +218,18 @@ bool cParser::getTokens(unsigned int Count, bool ToLower, const char *Break)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string cParser::readTokenFromDelegate(bool ToLower, const char *Break)
|
||||
{
|
||||
if (!mIncludeParser)
|
||||
return {};
|
||||
std::string token = mIncludeParser->readToken(ToLower, Break);
|
||||
if (token.empty())
|
||||
{
|
||||
mIncludeParser = nullptr;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
std::string cParser::readTokenFromStream(bool ToLower, const char *Break)
|
||||
{
|
||||
std::string token;
|
||||
@@ -268,7 +279,7 @@ void cParser::stripFirstTokenBOM(std::string& token, bool ToLower, const char* B
|
||||
|
||||
// if first "token" was standalone BOM, read the next real token (avoid recursion)
|
||||
while (token.empty() && mStream->peek() != EOF) {
|
||||
readToken(token, ToLower, Break);
|
||||
token = readToken(ToLower, Break);
|
||||
// readToken will not re-enter BOM stripping because mFirstToken is now false
|
||||
break;
|
||||
}
|
||||
@@ -310,7 +321,7 @@ void cParser::skipIncludeBlock() {
|
||||
// mimic original: while token != "end" readToken(true)
|
||||
std::string t;
|
||||
do {
|
||||
readToken(t, true);
|
||||
t = readToken(true);
|
||||
} while (t != "end" && !t.empty());
|
||||
}
|
||||
|
||||
@@ -355,81 +366,72 @@ void cParser::startIncludeFromParser(cParser& srcParser, bool ToLower, std::stri
|
||||
bool cParser::handleIncludeIfPresent(std::string& token, bool ToLower, const char* Break) {
|
||||
// token-mode include: token == "include"
|
||||
if (expandIncludes && token == "include") {
|
||||
std::string includefile;
|
||||
if (allowRandomIncludes)
|
||||
includefile = deserialize_random_set(*this);
|
||||
else
|
||||
readToken(includefile, ToLower);
|
||||
std::string includefile =
|
||||
allowRandomIncludes ? deserialize_random_set(*this) : readToken(ToLower);
|
||||
|
||||
startIncludeFromParser(*this, ToLower, std::move(includefile));
|
||||
|
||||
// after processing include, return next token from current parser
|
||||
readToken(token, ToLower, Break);
|
||||
token = readToken(ToLower, Break);
|
||||
return true;
|
||||
}
|
||||
|
||||
// line-mode HACK: Break == "\n\r" and line begins with "include"
|
||||
if ((std::strcmp(Break, "\n\r") == 0) && token.compare(0, 7, "include") == 0) {
|
||||
cParser includeparser(token.substr(7));
|
||||
std::string includefile;
|
||||
if (allowRandomIncludes)
|
||||
includefile = deserialize_random_set(includeparser);
|
||||
else
|
||||
includeparser.readToken(includefile, ToLower);
|
||||
std::string includefile =
|
||||
allowRandomIncludes ? deserialize_random_set(includeparser) : includeparser.readToken(ToLower);
|
||||
|
||||
startIncludeFromParser(includeparser, ToLower, std::move(includefile));
|
||||
|
||||
readToken(token, ToLower, Break);
|
||||
token = readToken(ToLower, Break);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void cParser::readToken(std::string &out, bool ToLower, const char *Break)
|
||||
std::string cParser::readToken(bool ToLower, const char *Break)
|
||||
{
|
||||
if (mIncludeParser)
|
||||
std::string token;
|
||||
|
||||
token = readTokenFromDelegate(ToLower, Break);
|
||||
if (token.empty())
|
||||
{
|
||||
mIncludeParser->readToken(out, ToLower, Break);
|
||||
if (out.empty())
|
||||
{
|
||||
mIncludeParser = nullptr;
|
||||
out = readTokenFromStream(ToLower, Break);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
out = readTokenFromStream(ToLower, Break);
|
||||
token = readTokenFromStream(ToLower, Break);
|
||||
}
|
||||
|
||||
stripFirstTokenBOM(out, ToLower, Break);
|
||||
stripFirstTokenBOM(token, ToLower, Break);
|
||||
|
||||
substituteParameters(out, ToLower);
|
||||
substituteParameters(token, ToLower);
|
||||
|
||||
handleIncludeIfPresent(out, ToLower, Break);
|
||||
handleIncludeIfPresent(token, ToLower, Break);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
std::vector<std::string> cParser::readParameters(cParser &Input)
|
||||
{
|
||||
|
||||
std::vector<std::string> includeparameters;
|
||||
std::string parameter;
|
||||
Input.readToken(parameter, false); // w parametrach nie zmniejszamy
|
||||
std::string parameter = Input.readToken(false); // w parametrach nie zmniejszamy
|
||||
while ((parameter.empty() == false) && (parameter != "end"))
|
||||
{
|
||||
includeparameters.emplace_back(parameter);
|
||||
Input.readToken(parameter, false);
|
||||
parameter = Input.readToken(false);
|
||||
}
|
||||
return includeparameters;
|
||||
}
|
||||
|
||||
std::string cParser::readQuotes(char const Quote)
|
||||
{ // read the stream until specified char or stream end
|
||||
std::string token;
|
||||
std::string token = "";
|
||||
char c{0};
|
||||
bool escaped = false;
|
||||
while (mStream->get(c))
|
||||
while (mStream->peek() != EOF)
|
||||
{ // get all chars until the quote mark
|
||||
c = mStream->get();
|
||||
|
||||
if (escaped)
|
||||
{
|
||||
escaped = false;
|
||||
@@ -455,11 +457,13 @@ std::string cParser::readQuotes(char const Quote)
|
||||
|
||||
void cParser::skipComment(std::string const &Endmark)
|
||||
{ // pobieranie znaków aż do znalezienia znacznika końca
|
||||
std::string input;
|
||||
std::string input = "";
|
||||
char c{0};
|
||||
auto const endmarksize = Endmark.size();
|
||||
while (mStream->get(c))
|
||||
while (mStream->peek() != EOF)
|
||||
{
|
||||
// o ile nie koniec pliku
|
||||
c = mStream->get(); // pobranie znaku
|
||||
if (c == '\n')
|
||||
{
|
||||
// update line counter
|
||||
@@ -550,8 +554,8 @@ std::size_t cParser::count()
|
||||
size_t count{0};
|
||||
do
|
||||
{
|
||||
token.clear();
|
||||
readToken(token, false);
|
||||
token = "";
|
||||
token = readToken(false);
|
||||
++count;
|
||||
} while (false == token.empty());
|
||||
|
||||
|
||||
@@ -45,14 +45,11 @@ class cParser //: public std::stringstream
|
||||
inline
|
||||
void
|
||||
ignoreToken() {
|
||||
std::string out;
|
||||
readToken(out); };
|
||||
readToken(); };
|
||||
inline
|
||||
bool
|
||||
expectToken( std::string const &Value ) {
|
||||
std::string out;
|
||||
readToken(out);
|
||||
return out == Value; };
|
||||
return readToken() == Value; };
|
||||
inline
|
||||
bool
|
||||
eof() {
|
||||
@@ -69,6 +66,7 @@ class cParser //: public std::stringstream
|
||||
return m_autoclear; }
|
||||
bool
|
||||
getTokens( unsigned int Count = 1, bool ToLower = true, char const *Break = "\n\r\t ;" );
|
||||
std::string readTokenFromDelegate(bool ToLower, const char *Break);
|
||||
std::string readTokenFromStream(bool ToLower, const char *Break);
|
||||
void stripFirstTokenBOM(std::string &token, bool ToLower, const char *Break);
|
||||
void substituteParameters(std::string &token, bool ToLower);
|
||||
@@ -105,7 +103,7 @@ class cParser //: public std::stringstream
|
||||
void startIncludeFromParser(cParser &srcParser, bool ToLower, std::string includefile);
|
||||
bool handleIncludeIfPresent(std::string &token, bool ToLower, const char *Break);
|
||||
// methods:
|
||||
void readToken(std::string& out, bool ToLower = true, const char *Break = "\n\r\t ;");
|
||||
std::string readToken(bool ToLower = true, const char *Break = "\n\r\t ;");
|
||||
static std::vector<std::string> readParameters( cParser &Input );
|
||||
std::string readQuotes( char const Quote = '\"' );
|
||||
void skipComment( std::string const &Endmark );
|
||||
|
||||
@@ -293,7 +293,7 @@ material_data::assign( std::string const &Replacableskin ) {
|
||||
// check for the pipe method first
|
||||
if( contains( Replacableskin, '|' ) ) {
|
||||
cParser nameparser( Replacableskin );
|
||||
nameparser.getTokens( 4, true, "|" );
|
||||
nameparser.getTokens( 4, false, "|" );
|
||||
int skinindex = 0;
|
||||
std::string texturename; nameparser >> texturename;
|
||||
while( ( texturename != "" ) && ( skinindex < 4 ) ) {
|
||||
|
||||
19425
vehicle/Train.cpp
19425
vehicle/Train.cpp
File diff suppressed because it is too large
Load Diff
@@ -236,11 +236,19 @@ class TTrain {
|
||||
static void OnCommand_aidriverenable( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_aidriverdisable( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_jointcontrollerset( TTrain *Train, command_data const &Command );
|
||||
|
||||
static void OnCommand_mastercontrollerincrease( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_mastercontrollerincreasefast( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_mastercontrollerdecrease( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_mastercontrollerdecreasefast( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_mastercontrollerset( TTrain *Train, command_data const &Command );
|
||||
|
||||
static void OnCommand_DynamicBrakeControllerIncrease( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_DynamicBrakeControllerIncreaseFast( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_DynamicBrakeControllerDecrease( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_DynamicBrakeControllerDecreaseFast( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_DynamicBrakeControllerSet( TTrain *Train, command_data const &Command );
|
||||
|
||||
static void OnCommand_secondcontrollerincrease( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_secondcontrollerincreasefast( TTrain *Train, command_data const &Command );
|
||||
static void OnCommand_secondcontrollerdecrease( TTrain *Train, command_data const &Command );
|
||||
|
||||
Reference in New Issue
Block a user