17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 13:19:20 +02:00
Files
maszyna/vehicle/Driver.cpp
jakubg1 f8cb5e4431 Fix EP fuse auto-(dis)engaging
When the Autorewident function is enabled, the EP fuse switch will no longer automatically engage or disengage if the player is controlling the train. This would happen for example when approaching a station.
2026-08-24 00:24:15 +02:00

8301 lines
373 KiB
C++

/*
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/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak, Maciej Czapkiewicz and others
*/
#include "stdafx.h"
#include "vehicle/Driver.h"
#include "utilities/Globals.h"
#include "utilities/translation.h"
#include "utilities/Logs.h"
#include "vehicle/Train.h"
#include "world/mtable.h"
#include "vehicle/DynObj.h"
#include "world/Event.h"
#include "world/MemCell.h"
#include "simulation/simulation.h"
#include "simulation/simulationtime.h"
#include "world/Track.h"
#include "world/station.h"
#include "input/keyboardinput.h"
#define LOGVELOCITY 0
#define LOGORDERS 1
#define LOGSTOPS 1
#define LOGBACKSCAN 0
#define LOGPRESS 0
// finds point of specified track nearest to specified event. returns: distance to that point from the specified end of the track
// TODO: move this to file with all generic routines, too easy to forget it's here and it may come useful
double
ProjectEventOnTrack( basic_event const *Event, TTrack const *Track, double const Direction ) {
auto const segment = Track->CurrentSegment();
auto const nearestpoint = segment->find_nearest_point( Event->input_location() );
return Direction > 0 ? nearestpoint * segment->GetLength() : // measure from point1
(1.0 - nearestpoint) * segment->GetLength(); // measure from point2
};
double GetDistanceToEvent(TTrack const *track, basic_event const *event, double scan_dir, double start_dist, int iter = 0, bool back = false)
{
if( track == nullptr ) { return start_dist; }
auto const segment = track->CurrentSegment();
auto const pos_event = event->input_location();
double len1, len2;
double sd = scan_dir;
double seg_len = scan_dir > 0 ? 0.0 : 1.0;
double const dzielnik = 1.0 / segment->GetLength();// rozdzielczosc mniej wiecej 1m
int krok = 0; // krok obliczeniowy do sprawdzania czy odwracamy
len2 = glm::length2(pos_event - segment->FastGetPoint(seg_len));
do
{
len1 = len2;
seg_len += scan_dir > 0 ? dzielnik : -dzielnik;
len2 = glm::length2(pos_event - segment->FastGetPoint(seg_len));
++krok;
} while (len1 > len2 && seg_len >= dzielnik && seg_len <= 1.0 - dzielnik);
//trzeba sprawdzić czy seg_len nie osiągnął skrajnych wartości, bo wtedy
// trzeba sprawdzić tor obok
if (1 == krok)
sd = -sd; // jeśli tylko jeden krok tzn, że event przy poprzednim sprawdzaym torze
if ((1 == krok || seg_len <= dzielnik || seg_len > 1.0 - dzielnik) && iter < 3)
{ // przejście na inny tor
track = track->Connected(int(sd), sd);
start_dist += 1 == krok ? 0 : back ? -segment->GetLength() : segment->GetLength();
if( track != nullptr
&& track->eType == tt_Cross ) {
// NOTE: tracing through crossroads currently poses risk of tracing through wrong segment
// as it's possible to be performerd before setting a route through the crossroads
// as a stop-gap measure we don't trace through crossroads which should be reasonable in most cases
// TODO: establish route before the scan, or a way for the function to know which route to pick
return start_dist;
}
else {
return GetDistanceToEvent( track, event, sd, start_dist, ++iter, 1 == krok ? true : false );
}
}
else
{ // obliczenie mojego toru
seg_len -= scan_dir > 0 ? dzielnik : -dzielnik; //trzeba wrócić do pozycji len1
seg_len = scan_dir < 0 ? 1 - seg_len : seg_len;
seg_len = back ? 1 - seg_len : seg_len; // odwracamy jeśli idzie do tyłu
start_dist -= back ? segment->GetLength() : 0;
return start_dist + segment->GetLength() * seg_len;
}
};
/*
Moduł obsługujący sterowanie pojazdami (składami pociągów, samochodami).
Ma działać zarówno jako AI oraz przy prowadzeniu przez człowieka. W tym
drugim przypadku jedynie informuje za pomocą napisów o tym, co by zrobił
w tym pierwszym. Obejmuje zarówno maszynistę jak i kierownika pociągu
(dawanie sygnału do odjazdu).
Przeniesiona tutaj została zawartość ai_driver.pas przerobiona na C++.
Również niektóre funkcje dotyczące składów z DynObj.cpp.
Teoria jest wtedy kiedy wszystko wiemy, ale nic nie działa.
Praktyka jest wtedy, kiedy wszystko działa, ale nikt nie wie dlaczego.
Tutaj łączymy teorię z praktyką - tu nic nie działa i nikt nie wie dlaczego…
*/
// zrobione:
// 0. pobieranie komend z dwoma parametrami
// 1. przyspieszanie do zadanej predkosci, ew. hamowanie jesli przekroczona
// 2. hamowanie na zadanym odcinku do zadanej predkosci (ze stabilizacja przyspieszenia)
// 3. wychodzenie z sytuacji awaryjnych: bezpiecznik nadmiarowy, poslizg
// 4. przygotowanie pojazdu do drogi, zmiana kierunku ruchu
// 5. dwa sposoby jazdy - manewrowy i pociagowy
// 6. dwa zestawy psychiki: spokojny i agresywny
// 7. przejscie na zestaw spokojny jesli wystepuje duzo poslizgow lub wybic nadmiarowego.
// 8. lagodne ruszanie (przedluzony czas reakcji na 2 pierwszych nastawnikach)
// 9. unikanie jazdy na oporach rozruchowych
// 10. logowanie fizyki //Ra: nie przeniesione do C++
// 11. kasowanie czuwaka/SHP
// 12. procedury wspomagajace "patrzenie" na odlegle semafory
// 13. ulepszone procedury sterowania
// 14. zglaszanie problemow z dlugim staniem na sygnale S1
// 15. sterowanie EN57
// 16. zmiana kierunku //Ra: z przesiadką po ukrotnieniu
// 17. otwieranie/zamykanie drzwi
// 18. Ra: odczepianie z zahamowaniem i podczepianie
// 19. dla Humandriver: tasma szybkosciomierza - zapis do pliku!
// 20. wybór pozycji zaworu maszynisty w oparciu o zadane opoznienie hamowania
// do zrobienia:
// 1. kierownik pociagu
// 2. madrzejsze unikanie grzania oporow rozruchowych i silnika
// 3. unikanie szarpniec, zerwania pociagu itp
// 4. obsluga innych awarii
// 5. raportowanie problemow, usterek nie do rozwiazania
// 7. samouczacy sie algorytm hamowania
// stałe
const double EasyReactionTime = 0.5; //[s] przebłyski świadomości dla zwykłej jazdy
const double HardReactionTime = 0.2;
const double EasyAcceleration = 0.85; //[m/ss]
const double HardAcceleration = 9.81;
const double PassengetTrainAcceleration = 0.40;
const double HeavyPassengetTrainAcceleration = 0.20;
const double CargoTrainAcceleration = 0.25;
const double HeavyCargoTrainAcceleration = 0.10;
const double PrepareTime = 2.0; //[s] przebłyski świadomości przy odpalaniu
bool WriteLogFlag = false;
double const deltalog = 0.05; // przyrost czasu
std::string StopReasonTable[] = {
// przyczyny zatrzymania ruchu AI
"", // stopNone, //nie ma powodu - powinien jechać
"Off", // stopSleep, //nie został odpalony, to nie pojedzie
"Semaphore", // stopSem, //semafor zamknięty
"Time", // stopTime, //czekanie na godzinę odjazdu
"End of track", // stopEnd, //brak dalszej części toru
"Change direction", // stopDir, //trzeba stanąć, by zmienić kierunek jazdy
"Joining", // stopJoin, //zatrzymanie przy (p)odczepianiu
"Block", // stopBlock, //przeszkoda na drodze ruchu
"A command", // stopComm, //otrzymano taką komendę (niewiadomego pochodzenia)
"Out of station", // stopOut, //komenda wyjazdu poza stację (raczej nie powinna zatrzymywać!)
"Radiostop", // stopRadio, //komunikat przekazany radiem (Radiostop)
"External", // stopExt, //przesłany z zewnątrz
"Error", // stopError //z powodu błędu w obliczeniu drogi hamowania
};
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
TSpeedPos::TSpeedPos(TTrack *track, double dist, int flag)
{
Set(track, dist, flag);
};
TSpeedPos::TSpeedPos(basic_event *event, double dist, double length, TOrders order)
{
Set(event, dist, length, order);
};
void TSpeedPos::Clear()
{
iFlags = 0; // brak flag to brak reakcji
fVelNext = -1.0; // prędkość bez ograniczeń
fSectionVelocityDist = 0.0; //brak długości
fDist = 0.0;
vPos = glm::dvec3(0, 0, 0);
trTrack = nullptr; // brak wskaźnika
};
void TSpeedPos::CommandCheck()
{ // sprawdzenie typu komendy w evencie i określenie prędkości
TCommandType command = evEvent->input_command();
double value1 = evEvent->input_value(1);
double value2 = evEvent->input_value(2);
switch (command)
{
case TCommandType::cm_ShuntVelocity:
// prędkość manewrową zapisać, najwyżej AI zignoruje przy analizie tabelki
fVelNext = value1; // powinno być value2, bo druga określa "za"?
iFlags |= spShuntSemaphor;
break;
case TCommandType::cm_SetVelocity:
// w semaforze typu "m" jest ShuntVelocity dla Ms2 i SetVelocity dla S1
// SetVelocity * 0 -> można jechać, ale stanąć przed
// SetVelocity 0 20 -> stanąć przed, potem można jechać 20 (SBL)
// SetVelocity -1 100 -> można jechać, przy następnym ograniczenie (SBL)
// SetVelocity 40 -1 -> PutValues: jechać 40 aż do minięcia (koniec ograniczenia(
fVelNext = value1;
iFlags &= ~(spShuntSemaphor | spPassengerStopPoint | spStopOnSBL);
iFlags |= spSemaphor;// nie manewrowa, nie przystanek, nie zatrzymać na SBL, ale semafor
if (value1 == 0.0) // jeśli pierwsza zerowa
if (value2 != 0.0) // a druga nie
{ // S1 na SBL, można przejechać po zatrzymaniu (tu nie mamy prędkości ani odległości)
fVelNext = value2; // normalnie będzie zezwolenie na jazdę, aby się usunął z tabelki
iFlags |= spStopOnSBL; // flaga, że ma zatrzymać; na pewno nie zezwoli na manewry
}
break;
case TCommandType::cm_SectionVelocity:
// odcinek z ograniczeniem prędkości
fVelNext = value1;
fSectionVelocityDist = value2;
iFlags |= spSectionVel;
break;
case TCommandType::cm_RoadVelocity:
// prędkość drogowa (od tej pory będzie jako domyślna najwyższa)
fVelNext = value1;
iFlags |= spRoadVel;
break;
case TCommandType::cm_PassengerStopPoint:
// nie ma dostępu do rozkładu
// przystanek, najwyżej AI zignoruje przy analizie tabelki
fVelNext = 0.0;
iFlags |= spPassengerStopPoint; // niestety nie da się w tym miejscu współpracować z rozkładem
/*
// NOTE: not used for now as it might be unnecessary
// special case, potentially override any set speed limits if requested
// NOTE: we test it here because for the time being it's only used for passenger stops
if( TestFlag( iFlags, spDontApplySpeedLimit ) ) {
fVelNext = -1;
}
*/
break;
case TCommandType::cm_SetProximityVelocity:
// musi zostać gdyż inaczej nie działają manewry
fVelNext = -1;
iFlags |= spProximityVelocity;
// fSectionVelocityDist = value2;
break;
case TCommandType::cm_OutsideStation:
// w trybie manewrowym: skanować od niej wstecz i stanąć po wyjechaniu za sygnalizator i
// zmienić kierunek
// w trybie pociągowym: można przyspieszyć do wskazanej prędkości (po zjechaniu z rozjazdów)
fVelNext = -1;
iFlags |= spOutsideStation; // W5
break;
case TCommandType::cm_EmergencyBrake:
fVelNext = -1;
break;
case TCommandType::cm_SecuritySystemMagnet:
fVelNext = -1;
break;
default:
// inna komenda w evencie skanowanym powoduje zatrzymanie i wysłanie tej komendy
// nie manewrowa, nie przystanek, nie zatrzymać na SBL
// jak nieznana komenda w komórce sygnałowej, to zatrzymujemy
fVelNext = 0.0;
/*
fVelNext = (
evEvent->is_command() ? 0.0 : // ask for a stop if we have a command for the vehicle
( iFlags & ( spSemaphor | spShuntSemaphor )) != 0 ? fVelNext : // don't change semafor velocity
-1.0 ); // otherwise don't be a bother
*/
// TODO: check whether clearing spShuntSemaphor flag doesn't cause problems
// potentially it can invalidate shunt semaphor used to transmit timetable or similar command
iFlags &= ~(spShuntSemaphor | spPassengerStopPoint | spStopOnSBL);
}
};
bool TSpeedPos::Update()
{
if( fDist < 0.0 ) {
// trzeba zazanaczyć, że minięty
iFlags |= spElapsed;
}
if (iFlags & spTrack) {
// road/track
if (trTrack) {
// może być NULL, jeśli koniec toru (???)
fVelNext = trTrack->VelocityGet(); // aktualizacja prędkości (może być zmieniana eventem)
if( trTrack->iCategoryFlag & 1 ) {
// railways
if( iFlags & spSwitch ) {
// jeśli odcinek zmienny
if( (trTrack->GetSwitchState() & 1) != 0 !=
( ( iFlags & spSwitchStatus ) != 0 ) ) {
// czy stan się zmienił?
// Ra: zakładam, że są tylko 2 możliwe stany
iFlags ^= spSwitchStatus;
if( ( iFlags & spElapsed ) == 0 ) {
// jeszcze trzeba skanowanie wykonać od tego toru
// problem jest chyba, jeśli zwrotnica się przełoży zaraz po zjechaniu z niej
// na Mydelniczce potrafi skanować na wprost mimo pojechania na bok
return true;
}
}
}
}
else {
// roads and others
if( iFlags & 0xF0000000 ) {
// jeśli skrzyżowanie, ograniczyć prędkość przy skręcaniu
if( ( iFlags & 0xF0000000 ) > 0x10000000 ) {
// ±1 to jazda na wprost, ±2 nieby też, ale z przecięciem głównej drogi - chyba że jest równorzędne...
// TODO: uzależnić prędkość od promienia;
// albo niech będzie ograniczona w skrzyżowaniu (velocity z ujemną wartością)
fVelNext = 30.0;
}
}
}
}
}
else if (iFlags & spEvent) {
// jeśli event
if( (iFlags & spElapsed) == 0
|| fVelNext == 0.0 ) {
// ignore already passed signals, but keep an eye on overrun stops
// odczyt komórki pamięci najlepiej by było zrobić jako notyfikację,
// czyli zmiana komórki wywoła jakąś podaną funkcję
CommandCheck(); // sprawdzenie typu komendy w evencie i określenie prędkości
}
}
return false;
};
std::string TSpeedPos::GetName() const
{
if( iFlags & spTrack ) // jeśli tor
return trTrack->name();
else if( iFlags & spEvent ) // jeśli event
return
evEvent->m_name
+ " [" + std::to_string( static_cast<int>( evEvent->input_value( 1 ) ) )
+ ", " + std::to_string( static_cast<int>( evEvent->input_value( 2 ) ) )
+ "]";
else
return "";
}
std::string TSpeedPos::TableText() const
{ // pozycja tabelki pr?dko?ci
if (iFlags & spEnabled)
{ // o ile pozycja istotna
return to_hex_str(iFlags, 8) + " " + to_string(fDist, 1, 6) +
" " + (fVelNext == -1.0 ? " -" : to_string(static_cast<int>(fVelNext), 0, 3)) + " " + GetName();
}
return "Empty";
}
bool TSpeedPos::IsProperSemaphor(TOrders order)
{ // sprawdzenie czy semafor jest zgodny z trybem jazdy
if (order < Obey_train) // Wait_for_orders, Prepare_engine, Change_direction, Connect, Disconnect, Shunt
{
if (iFlags & (spSemaphor | spShuntSemaphor))
return true;
else if (iFlags & spOutsideStation)
return true;
}
else if (order & Obey_train)
{
if (iFlags & spSemaphor)
return true;
}
return false; // true gdy zatrzymanie, wtedy nie ma po co skanować dalej
}
bool TSpeedPos::Set(basic_event *event, double dist, double length, TOrders order)
{ // zapamiętanie zdarzenia
fDist = dist;
iFlags = spEvent;
evEvent = event;
vPos = event->input_location(); // współrzędne eventu albo komórki pamięci (zrzutować na tor?)
// ignore events located behind the consist, but with exception of stop points which may be needed to update freshly received timetable
if( dist + length < 0
&& event->input_command() != TCommandType::cm_PassengerStopPoint ) {
return false;
}
iFlags |= spEnabled;
CommandCheck(); // sprawdzenie typu komendy w evencie i określenie prędkości
// zależnie od trybu sprawdzenie czy jest tutaj gdzieś semafor lub tarcza manewrowa
// jeśli wskazuje stop wtedy wystawiamy true jako koniec sprawdzania
// WriteLog("EventSet: Vel=" + AnsiString(fVelNext) + " iFlags=" + AnsiString(iFlags) + " order="+AnsiString(order));
if (order < Obey_train) // Wait_for_orders, Prepare_engine, Change_direction, Connect, Disconnect, Shunt
{
if (iFlags & (spSemaphor | spShuntSemaphor) && fVelNext == 0.0)
return true;
else if (iFlags & spOutsideStation)
return true;
}
else if (order & Obey_train)
{
if (iFlags & spSemaphor && fVelNext == 0.0)
return true;
}
return false; // true gdy zatrzymanie, wtedy nie ma po co skanować dalej
};
void TSpeedPos::Set(TTrack *track, double dist, int flag)
{ // zapamiętanie zmiany prędkości w torze
fDist = dist; // odległość do początku toru
trTrack = track; // TODO: (t) może być NULL i nie odczytamy końca poprzedniego :/
if (trTrack)
{
iFlags = flag | (trTrack->eType == tt_Normal ? spTrack : spTrack | spSwitch ); // zapamiętanie kierunku wraz z typem
if (iFlags & spSwitch)
if (trTrack->GetSwitchState() & 1)
iFlags |= spSwitchStatus;
fVelNext = trTrack->VelocityGet();
if (trTrack->iDamageFlag & 128)
fVelNext = 0.0; // jeśli uszkodzony, to też stój
if (iFlags & spEnd)
fVelNext = trTrack->iCategoryFlag & 1 ?
0.0 :
20.0; // jeśli koniec, to pociąg stój, a samochód zwolnij
vPos =
iFlags & spReverse ?
trTrack->CurrentSegment()->FastGetPoint_1() :
trTrack->CurrentSegment()->FastGetPoint_0();
}
};
//---------------------------------------------------------------------------
void TController::TableClear()
{ // wyczyszczenie tablicy
sSpeedTable.clear();
iLast = -1;
iTableDirection = 0; // nieznany
tLast = nullptr;
fLastVel = -1.0;
SemNextIndex = -1;
SemNextStopIndex = -1;
eSignSkip = nullptr; // nic nie pomijamy
};
std::vector<basic_event *> TController::CheckTrackEvent( TTrack *Track, double const fDirection ) const
{ // sprawdzanie eventów na podanym torze do podstawowego skanowania
std::vector<basic_event *> events;
auto const &eventsequence { ( fDirection > 0 ? Track->m_events2 : Track->m_events1 ) };
for( auto const &event : eventsequence ) {
if( event.second != nullptr
&& event.second->m_passive ) {
events.emplace_back( event.second );
}
}
return events;
}
bool TController::TableAddNew()
{ // zwiększenie użytej tabelki o jeden rekord
sSpeedTable.emplace_back(); // add a new slot
iLast = sSpeedTable.size() - 1;
return true; // false gdy się nałoży
};
bool TController::TableNotFound(basic_event const *Event, double const Distance ) const
{ // sprawdzenie, czy nie został już dodany do tabelki (np. podwójne W4 robi problemy)
auto lookup =
std::find_if(
sSpeedTable.begin(),
sSpeedTable.end(),
[Event]( TSpeedPos const &speedpoint ){
return true == TestFlag(speedpoint.iFlags, spEnabled | spEvent) && speedpoint.evEvent == Event; } );
if (Global.iWriteLogEnabled & 8
&& lookup != sSpeedTable.end() ) {
WriteLog( "Speed table for " + OwnerName() + " already contains event " + lookup->evEvent->m_name );
}
// ignore duplicates which seem to be reasonably apart from each other, on account of looping tracks
// NOTE: since supplied distance is only rough approximation of distance to the event, we're using large safety margin
return lookup == sSpeedTable.end() || Distance - lookup->fDist > 100.0;
};
void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle)
{ // skanowanie trajektorii na odległość (fDistance) od (pVehicle) w kierunku przodu składu i
// uzupełnianie tabelki
// WriteLog("Starting TableTraceRoute");
TTrack *pTrack{ nullptr }; // zaczynamy od ostatniego analizowanego toru
double fTrackLength{ 0.0 }; // długość aktualnego toru (krótsza dla pierwszego)
double fCurrentDistance{ 0.0 }; // aktualna przeskanowana długość
double fLastDir{ 0.0 };
if (iTableDirection != iDirection ) {
// jeśli zmiana kierunku, zaczynamy od toru ze wskazanym pojazdem
TableClear();
/*
// aktualna prędkość // changed to -1 to recognize speed limit, if any
fLastVel = -1.0;
sSpeedTable.clear();
iLast = -1;
tLast = nullptr; //żaden nie sprawdzony
SemNextIndex = -1;
SemNextStopIndex = -1;
*/
if( VelSignalLast == 0.0 ) {
// don't allow potential red light overrun keep us from reversing
VelSignalLast = -1.0;
}
iTableDirection = iDirection; // ustalenie w jakim kierunku jest wypełniana tabelka względem pojazdu
pTrack = pVehicle->GetTrack(); // odcinek, na którym stoi
fTrackLength = pVehicle->RaTranslationGet(); // pozycja na tym torze (odległość od Point1)
fLastDir = pVehicle->DirectionGet() * pVehicle->RaDirectionGet(); // ustalenie kierunku skanowania na torze
if( fLastDir < 0.0 ) {
// jeśli w kierunku Point2 toru
fTrackLength = pTrack->Length() - fTrackLength; // przeskanowana zostanie odległość do Point2
}
// account for the fact tracing begins from active axle, not the actual front of the vehicle
// NOTE: position of the couplers is modified by track offset, but the axles ain't, so we need to account for this as well
fTrackLength -= glm::length(
pVehicle->AxlePositionGet()
- pVehicle->RearPosition()
+ pVehicle->VectorLeft() * pVehicle->MoverParameters->OffsetTrackH );
// aktualna odległość ma być ujemna gdyż jesteśmy na końcu składu
fCurrentDistance = -fLength - fTrackLength;
fTrackLength = pTrack->Length(); //skasowanie zmian w zmiennej żeby poprawnie liczyło w dalszych krokach
MoveDistanceReset(); // AI startuje 1s po zaczęciu jazdy i mógł już coś przejechać
}
else {
if( iTableDirection == 0 ) { return; }
// NOTE: provisory fix for BUG: sempahor indices no longer matching table size
// TODO: find and really fix the reason it happens
if( SemNextIndex != -1
&& SemNextIndex >= sSpeedTable.size() ) {
SemNextIndex = -1;
}
if( SemNextStopIndex != -1
&& SemNextStopIndex >= sSpeedTable.size() ) {
SemNextStopIndex = -1;
}
// kontynuacja skanowania od ostatnio sprawdzonego toru (w ostatniej pozycji zawsze jest tor)
if( SemNextStopIndex != -1
&& sSpeedTable[SemNextStopIndex].fVelNext == 0.0 ) {
// znaleziono semafor lub tarczę lub tor z prędkością zero, trzeba sprawdzić czy to nadał semafor
// jeśli jest następny semafor to sprawdzamy czy to on nadał zero
if( OrderCurrentGet() & Obey_train
&& sSpeedTable[SemNextStopIndex].iFlags & spSemaphor ) {
return;
}
else {
if( OrderCurrentGet() < Obey_train
&& sSpeedTable[SemNextStopIndex].iFlags & (spSemaphor | spShuntSemaphor | spOutsideStation) ) {
return;
}
}
}
auto const &lastspeedpoint = sSpeedTable[ iLast ];
pTrack = lastspeedpoint.trTrack;
assert( pTrack != nullptr );
// flaga ustawiona, gdy Point2 toru jest blizej
fLastDir = (lastspeedpoint.iFlags & spReverse) != 0 ? -1.0 : 1.0;
fCurrentDistance = lastspeedpoint.fDist; // aktualna odleglosc do jego Point1
fTrackLength = pTrack->Length();
}
if( iTableDirection == 0 ) {
// don't bother
return;
}
while (fCurrentDistance < fDistance)
{
if (pTrack != tLast) // ostatni zapisany w tabelce nie był jeszcze sprawdzony
{ // jeśli tor nie był jeszcze sprawdzany
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table for " + OwnerName() + " tracing through track " + pTrack->name() );
}
auto const events { CheckTrackEvent( pTrack, fLastDir ) };
for( auto *pEvent : events ) {
if( pEvent != nullptr ) // jeśli jest semafor na tym torze
{ // trzeba sprawdzić tabelkę, bo dodawanie drugi raz tego samego przystanku nie jest korzystne
if (TableNotFound(pEvent, fCurrentDistance)) // jeśli nie ma
{
TableAddNew(); // zawsze jest true
if (Global.iWriteLogEnabled & 8) {
WriteLog("Speed table for " + OwnerName() + " found new event, " + pEvent->m_name);
}
auto &newspeedpoint = sSpeedTable[iLast];
/*
if( newspeedpoint.Set(
pEvent,
fCurrentDistance + ProjectEventOnTrack( pEvent, pTrack, fLastDir ),
OrderCurrentGet() ) ) {
*/
if( newspeedpoint.Set(
pEvent,
GetDistanceToEvent( pTrack, pEvent, fLastDir, fCurrentDistance ),
fLength,
OrderCurrentGet() ) ) {
fDistance = newspeedpoint.fDist; // jeśli sygnał stop, to nie ma potrzeby dalej skanować
SemNextStopIndex = iLast;
if (SemNextIndex == -1) {
SemNextIndex = iLast;
}
if (Global.iWriteLogEnabled & 8) {
WriteLog("(stop signal from "
+ (SemNextStopIndex != -1 ? sSpeedTable[SemNextStopIndex].GetName() : "unknown semaphor")
+ ")");
}
}
else {
if( true == newspeedpoint.IsProperSemaphor(OrderCurrentGet())
&& SemNextIndex == -1 ) {
SemNextIndex = iLast; // sprawdzamy czy pierwszy na drodze
}
if (Global.iWriteLogEnabled & 8) {
WriteLog("(forward signal for "
+ (SemNextIndex != -1 ? sSpeedTable[SemNextIndex].GetName() : "unknown semaphor")
+ ")");
}
}
}
}
} // event dodajemy najpierw, żeby móc sprawdzić, czy tor został dodany po odczytaniu prędkości następnego
if( pTrack->VelocityGet() == 0.0 // zatrzymanie
|| pTrack->iAction // jeśli tor ma własności istotne dla skanowania
|| pTrack->VelocityGet() != fLastVel ) // następuje zmiana prędkości
{ // odcinek dodajemy do tabelki, gdy jest istotny dla ruchu
TableAddNew();
sSpeedTable[ iLast ].Set(
pTrack, fCurrentDistance,
fLastDir < 0 ? spEnabled | spReverse : spEnabled ); // dodanie odcinka do tabelki z flagą kierunku wejścia
if (pTrack->eType == tt_Cross) {
// na skrzyżowaniach trzeba wybrać segment, po którym pojedzie pojazd
// dopiero tutaj jest ustalany kierunek segmentu na skrzyżowaniu
/*
// NOTE: manual selection disabled due to multiplayer
int routewanted;
if( false == AIControllFlag ) {
routewanted = (
input::keys[ GLFW_KEY_LEFT ] != GLFW_RELEASE ? 1 :
input::keys[ GLFW_KEY_RIGHT ] != GLFW_RELEASE ? 2 :
3 );
}
else {
routewanted = 1 + std::floor( Random( static_cast<double>( pTrack->RouteCount() ) - 0.001 ) );
}
*/
auto const routewanted { 1 + std::floor( Random( static_cast<double>( pTrack->RouteCount() ) - 0.001 ) ) };
sSpeedTable[iLast].iFlags |=
(pTrack->CrossSegment(fLastDir < 0 ? tLast->iPrevDirection : tLast->iNextDirection,
/*
iRouteWanted )
*/
routewanted) &
0xf)
<< 28; // ostatnie 4 bity pola flag
sSpeedTable[iLast].iFlags &= ~spReverse; // usunięcie flagi kierunku, bo może być błędna
if (sSpeedTable[iLast].iFlags < 0) {
sSpeedTable[iLast].iFlags |= spReverse; // ustawienie flagi kierunku na podstawie wybranego segmentu
}
if (int(fLastDir) * sSpeedTable[iLast].iFlags < 0) {
fLastDir = -fLastDir;
}
/*
if (AIControllFlag) {
// dla AI na razie losujemy kierunek na kolejnym skrzyżowaniu
iRouteWanted = 1 + Random(3);
}
*/
}
}
else if ( pTrack->fRadius != 0.0 // odległość na łuku lepiej aproksymować cięciwami
|| ( tLast != nullptr
&& tLast->fRadius != 0.0 )) // koniec łuku też jest istotny
{ // albo dla liczenia odległości przy pomocy cięciw - te usuwać po przejechaniu
if (TableAddNew()) {
// dodanie odcinka do tabelki
sSpeedTable[iLast].Set(
pTrack, fCurrentDistance,
fLastDir < 0 ? spEnabled | spCurve | spReverse : spEnabled | spCurve );
}
}
}
fCurrentDistance += fTrackLength; // doliczenie kolejnego odcinka do przeskanowanej długości
tLast = pTrack; // odhaczenie, że sprawdzony
fLastVel = pTrack->VelocityGet(); // prędkość na poprzednio sprawdzonym odcinku
pTrack = pTrack->Connected(
pTrack->eType == tt_Cross ? sSpeedTable[iLast].iFlags >> 28 : static_cast<int>(fLastDir),
fLastDir); // może być NULL
if (pTrack != nullptr )
{ // jeśli kolejny istnieje
if( tLast != nullptr ) {
if( tLast->VelocityGet() > 0
&& ( tLast->VelocityGet() < pTrack->VelocityGet()
|| pTrack->VelocityGet() < 0 ) ) {
if( iLast != -1
&& sSpeedTable[iLast].trTrack == tLast ) {
// if the track is already in the table we only need to mark it as relevant
sSpeedTable[ iLast ].iFlags |= spEnabled;
}
else {
// otherwise add it
TableAddNew();
sSpeedTable[ iLast ].Set(
tLast,
fCurrentDistance - fTrackLength, // by now the current distance points to beginning of next track
( fLastDir > 0 ?
pTrack->iPrevDirection :
pTrack->iNextDirection ) ?
spEnabled :
spEnabled | spReverse );
}
}
}
// zwiększenie skanowanej odległości tylko jeśli istnieje dalszy tor
fTrackLength = pTrack->Length();
}
else
{ // definitywny koniec skanowania, chyba że dalej puszczamy samochód po gruncie...
if( iLast == -1
|| ( false == TestFlag(sSpeedTable[iLast].iFlags, spEnabled | spEnd)
&& sSpeedTable[iLast].trTrack != tLast ) ) {
// only if we haven't already marked end of the track and if the new track doesn't duplicate last one
if( TableAddNew() ) {
// zapisanie ostatniego sprawdzonego toru
sSpeedTable[iLast].Set(
tLast, fCurrentDistance - fTrackLength, // by now the current distance points to beginning of next track,
fLastDir < 0 ? spEnabled | spEnd | spReverse : spEnabled | spEnd);
}
}
else if( sSpeedTable[ iLast ].trTrack == tLast ) {
// otherwise just mark the last added track as the final one
// TODO: investigate exactly how we can wind up not marking the last existing track as actual end
if( false == TestFlag( sSpeedTable[ iLast ].trTrack->iCategoryFlag, 0x100 ) ) {
// don't mark portals, as these aren't exactly track ends, but teleport devices
sSpeedTable[ iLast ].iFlags |= spEnabled | spEnd;
}
}
// to ostatnia pozycja, bo NULL nic nie da, a może się podpiąć obrotnica, czy jakieś transportery
return;
}
}
if( TableAddNew() ) {
// zapisanie ostatniego sprawdzonego toru
sSpeedTable[ iLast ].Set(
pTrack, fCurrentDistance,
fLastDir < 0 ? spNone | spReverse : spNone );
}
};
void TController::TableCheck(double fDistance)
{ // przeliczenie odległości w tabelce, ewentualnie doskanowanie (bez analizy prędkości itp.)
if( iTableDirection != iDirection ) {
// jak zmiana kierunku, to skanujemy od końca składu
TableTraceRoute( fDistance, pVehicles[ end::rear ] );
TableSort();
}
else if (iTableDirection)
{ // trzeba sprawdzić, czy coś się zmieniło
auto const distance = MoveDistanceGet();
for (auto &sp : sSpeedTable) {
// aktualizacja odległości dla wszystkich pozycji tabeli
sp.UpdateDistance(distance);
}
MoveDistanceReset(); // kasowanie odległości po aktualizacji tabelki
for( int i = 0; i < iLast; ++i )
{ // aktualizacja rekordów z wyjątkiem ostatniego
if (sSpeedTable[i].iFlags & spEnabled) // jeśli pozycja istotna
{
if (sSpeedTable[i].Update())
{
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table for " + OwnerName() + " detected switch change at " + sSpeedTable[ i ].trTrack->name() + " (generating fresh trace)" );
}
// usuwamy wszystko za tym torem
while (sSpeedTable.back().trTrack != sSpeedTable[i].trTrack)
{ // usuwamy wszystko dopóki nie trafimy na tą zwrotnicę
sSpeedTable.pop_back();
--iLast;
}
tLast = sSpeedTable[ i ].trTrack;
TableTraceRoute( fDistance, pVehicles[ end::rear ] );
TableSort();
// nie kontynuujemy pętli, trzeba doskanować ciąg dalszy
break;
}
if (sSpeedTable[i].iFlags & spTrack) // jeśli odcinek
{
if( sSpeedTable[ i ].fDist + sSpeedTable[ i ].trTrack->Length() < -fLength ) // a skład wyjechał całą długością poza
{ // degradacja pozycji
sSpeedTable[i].iFlags &= ~spEnabled; // nie liczy się
}
else if( ( sSpeedTable[ i ].iFlags & 0xF0000028 ) == spElapsed ) {
// jest z tyłu (najechany) i nie jest zwrotnicą ani skrzyżowaniem
if( sSpeedTable[ i ].fVelNext < 0 ) // a nie ma ograniczenia prędkości
{
sSpeedTable[ i ].iFlags = 0; // to nie ma go po co trzymać (odtykacz usunie ze środka)
}
}
}
else if (sSpeedTable[i].iFlags & spEvent) // jeśli event
{
if (sSpeedTable[i].fDist < (
typeid( *sSpeedTable[i].evEvent ) == typeid( putvalues_event ) ?
-fLength :
0)) // jeśli jest z tyłu
if (mvOccupied->CategoryFlag == 2 && sSpeedTable[i].fDist < -0.75)
{ // pociąg staje zawsze, a samochód tylko jeśli nie przejedzie całą długością (może być zaskoczony zmianą)
// WriteLog("TableCheck: Event is behind. Delete from table: " + sSpeedTable[i].evEvent->asName);
sSpeedTable[i].iFlags &= ~spEnabled; // degradacja pozycji dla samochodu;
// semafory usuwane tylko przy sprawdzaniu, bo wysyłają komendy
}
}
}
}
sSpeedTable[iLast].Update(); // aktualizacja ostatniego
// WriteLog("TableCheck: Upate last track. Dist=" + AnsiString(sSpeedTable[iLast].fDist));
if( sSpeedTable[ iLast ].fDist < fDistance ) {
TableTraceRoute( fDistance, pVehicles[ end::rear ] ); // doskanowanie dalszego odcinka
TableSort();
}
// garbage collection
TablePurger();
}
};
auto const passengerstopmaxdistance { 400.0 }; // maximum allowed distance between passenger stop point and consist head
TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fNext, double &fAcc)
{ // ustalenie parametrów, zwraca typ komendy, jeśli sygnał podaje prędkość do jazdy
// fVelDes - prędkość zadana
// fDist - dystans w jakim należy rozważyć ruch
// fNext - prędkość na końcu tego dystansu
// fAcc - zalecane przyspieszenie w chwili obecnej - kryterium wyboru dystansu
auto a { 0.0 }; // przyspieszenie
auto v { 0.0 }; // prędkość
auto d { 0.0 }; // droga
auto d_to_next_sem { 10000.0 }; //ustaiwamy na pewno dalej niż widzi AI
auto go { TCommandType::cm_Unknown };
auto speedlimitiscontinuous { true }; // stays true if potential active speed limit is unbroken to the last (relevant) point in scan table
eSignNext = nullptr;
IsAtPassengerStop = false;
IsScheduledPassengerStopVisible = false;
mvOccupied->SecuritySystem.set_cabsignal_lock(false);
// te flagi są ustawiane tutaj, w razie potrzeby
iDrivigFlags &= ~(moveTrackEnd | moveSwitchFound | moveSignalFound | /*moveSpeedLimitFound*/ moveStopPointFound );
for( std::size_t idx = 0; idx < sSpeedTable.size(); ++idx ) {
// o ile dana pozycja tabelki jest istotna
if( ( sSpeedTable[ idx ].iFlags & spEnabled ) == 0 ) { continue; }
auto &point { sSpeedTable[ idx ] };
if( TestFlag( point.iFlags, spPassengerStopPoint ) ) {
if( TableUpdateStopPoint( go, point, d_to_next_sem ) ) { continue; };
}
v = point.fVelNext; // odczyt prędkości do zmiennej pomocniczej
if( TestFlag( point.iFlags, spSwitch ) ) {
// zwrotnice są usuwane z tabelki dopiero po zjechaniu z nich
iDrivigFlags |= moveSwitchFound; // rozjazd z przodu/pod ogranicza np. sens skanowania wstecz
SwitchClearDist = point.fDist + point.trTrack->Length() + fLength;
}
else if ( TestFlag( point.iFlags, spEvent ) ) // W4 może się deaktywować
{ // jeżeli event, może być potrzeba wysłania komendy, aby ruszył
if( TableUpdateEvent( v, go, point, d_to_next_sem, idx ) ) { continue; }
}
auto const railwaytrackend { true == TestFlag(point.iFlags, spEnd) && is_train() };
if( v >= 0.0 // pozycje z prędkością -1 można spokojnie pomijać
|| railwaytrackend ) {
d = point.fDist;
if( v >= 0.0 ) {
// points located in front of us can potentially affect our acceleration and target speed
if( d > 0.0
&& false == TestFlag(point.iFlags, spElapsed) ) {
// sygnał lub ograniczenie z przodu (+32=przejechane)
// 2014-02: jeśli stoi, a ma do przejechania kawałek, to niech jedzie
if( mvOccupied->Vel < 0.01
&& true == TestFlag(point.iFlags, spEnabled | spEvent | spPassengerStopPoint)
&& false == IsAtPassengerStop ) {
// ma podjechać bliżej - czy na pewno w tym miejscu taki warunek?
a = d > passengerstopmaxdistance || (iDrivigFlags & moveStopCloser) != 0 ? fAcc : 0.0;
}
else {
// przyspieszenie: ujemne, gdy trzeba hamować
if( v >= 0.0 ) {
a = ( v * v - mvOccupied->Vel * mvOccupied->Vel ) / ( 25.92 * d );
if( mvOccupied->Vel < v
|| v == 0.0 ) {
// if we're going slower than the target velocity and there's enough room for safe stop, speed up
auto const brakingdistance { 1.2 * fBrakeDist * braking_distance_multiplier( v ) };
if( brakingdistance > 0.0 ) {
// maintain desired acc while we have enough room to brake safely, when close enough start paying attention
// try to make a smooth transition instead of sharp change
a = std::lerp( a, AccPreferred, std::clamp( ( d - brakingdistance ) / brakingdistance, 0.0, 1.0 ) );
}
}
if( d < fMinProximityDist
&& v < fVelDes ) {
// jak jest już blisko, ograniczenie aktualnej prędkości
fVelDes = v;
}
}
}
}
// points located behind can affect our current speed, but little else
else if ( point.iFlags & spTrack) // jeśli tor
{ // tor ogranicza prędkość, dopóki cały skład nie przejedzie,
if( v >= 1.0 ) { // EU06 się zawieszało po dojechaniu na koniec toru postojowego
if( d + point.trTrack->Length() < -fLength ) {
if( false == railwaytrackend ) {
// zapętlenie, jeśli już wyjechał za ten odcinek
continue;
}
}
}
if( v < fVelDes ) {
// ograniczenie aktualnej prędkości aż do wyjechania za ograniczenie
fVelDes = v;
}
if( v < VelLimitLastDist.first ) {
VelLimitLastDist.second = d + point.trTrack->Length() + fLength;
}
else if( VelLimitLastDist.second > 0 ) { // the speed limit can potentially start afterwards, so don't mark it as broken too soon
if( ( point.iFlags & ( spSwitch | spPassengerStopPoint ) ) == 0 ) {
speedlimitiscontinuous = false;
}
}
if( false == railwaytrackend ) {
continue; // i tyle wystarczy
}
}
else {
// event trzyma tylko jeśli VelNext=0, nawet po przejechaniu (nie powinno dotyczyć samochodów?)
a = v > 0.0 ?
fAcc :
mvOccupied->Vel < 0.01 ?
0.0 : // already standing still so no need to bother with brakes
-2.0; // ruszanie albo hamowanie
}
}
// track can potentially end, which creates another virtual point of interest with speed limit of 0 at the end of it
// TBD, TODO: when tracing the route create a dedicated table entry for it, to simplify the code?
if( railwaytrackend ) {
// if the railway track ends here set the velnext accordingly as well
// TODO: test this with turntables and such
auto const stopatendacceleration = -1.0 * mvOccupied->Vel * mvOccupied->Vel / ( 25.92 * ( d + point.trTrack->Length() ) );
if( stopatendacceleration < a ) {
a = stopatendacceleration;
v = 0.0;
d += point.trTrack->Length();
if( d < fMinProximityDist ) {
// jak jest już blisko, ograniczenie aktualnej prędkości
fVelDes = v;
}
}
}
if( a <= fAcc
&& ( v < fNext || fNext < 0 ) ) { // filter out consecutive, farther out blocks with the same speed limit; they'd make us accelerate slower due to their lower a value
// mniejsze przyspieszenie to mniejsza możliwość rozpędzenia się albo konieczność hamowania
// jeśli droga wolna, to może być a>1.0 i się tu nie załapuje
fAcc = a; // zalecane przyspieszenie (nie musi być uwzględniane przez AI)
fNext = v; // istotna jest prędkość na końcu tego odcinka
fDist = d; // dlugość odcinka
}
/*
else if ((fAcc > 0) && (v >= 0) && (v > fNext)) {
// jeśli nie ma wskazań do hamowania, można podać drogę i prędkość na jej końcu
fNext = v; // istotna jest prędkość na końcu tego odcinka
fDist = d; // dlugość odcinka (kolejne pozycje mogą wydłużać drogę, jeśli prędkość jest stała)
}
*/
// we'll pick first scanned target speed as our goal
// farther scan table points can override it through previous clause, if they require lower acceleration or speed reduction
else if( a > 0 && a <= fAcc && v >= 0 && fNext < 0 ) {
fAcc = a;
fNext = v;
fDist = d;
}
// potentially update our current speed limit
if( v < VelLimitLastDist.first
&& ( d < 0 // the point counts as part of last speed limit either if it's behind us
|| VelLimitLastDist.second > 0 ) ) { // or if we already have the last limit ongoing
VelLimitLastDist.second = d + fLength;
if( ( point.iFlags & spTrack ) != 0 ) {
VelLimitLastDist.second += point.trTrack->Length();
}
}
else {
// if we found a point which isn't part of the current speed limit mark the limit as broken
// NOTE: we exclude switches from this rule, as speed limits generally extend through these
if( ( point.iFlags & ( spSwitch | spPassengerStopPoint ) ) == 0 ) {
speedlimitiscontinuous = false;
}
}
} // if (v>=0.0)
// finding a point which doesn't impose speed limit means any potential active speed limit can't extend through entire scan table
// NOTE: we test actual speed value for the given point, as events may receive (v = -1) as a way to disable them in irrelevant modes
if( point.fVelNext < 0 ) {
// NOTE: we exclude switches from this rule, as speed limits generally extend through these
if( ( point.iFlags & ( spSwitch | spPassengerStopPoint ) ) == 0 ) {
speedlimitiscontinuous = false;
}
}
if (fNext >= 0.0)
{ // jeśli ograniczenie
if( ( point.iFlags & ( spEnabled | spEvent ) ) == ( spEnabled | spEvent ) ) { // tylko sygnał przypisujemy
if( eSignNext == nullptr ) { // jeśli jeszcze nic nie zapisane tam
eSignNext = point.evEvent; // dla informacji
}
}
if( fNext == 0.0 ) {
break; // nie ma sensu analizować tabelki dalej
}
}
}
// jeśli mieliśmy ograniczenie z semafora i nie ma przed nami
if( VelSignalLast >= 0.0
&& (iDrivigFlags & (moveSignalFound | moveSwitchFound | moveStopPointFound)) == 0
&& true == TestFlag(OrderCurrentGet(), Obey_train) ) {
VelSignalLast = -1.0;
}
// if there's unbroken speed limit through our scan table take a note of it
if( VelLimitLastDist.second > 0
&& speedlimitiscontinuous ) {
VelLimitLastDist.second = EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE;
}
// take into account the effect switches have on duration of signal-imposed speed limit, in calculation of speed limit end point
if( VelSignalLast >= 0.0 && SwitchClearDist >= 0.0 ) {
VelLimitLastDist.second = std::max( VelLimitLastDist.second, SwitchClearDist );
}
//analiza spisanych z tabelki ograniczeń i nadpisanie aktualnego
// if stopped at a valid passenger stop, hold there
if( true == IsAtPassengerStop && mvOccupied->Vel < 0.01 ) {
fVelDes = 0.0;
}
else {
fVelDes = min_speed( fVelDes, VelSignalLast );
fVelDes = min_speed( fVelDes, VelLimitLast );
fVelDes = min_speed( fVelDes, VelRoad );
fVelDes = min_speed( fVelDes, VelRestricted );
}
// nastepnego semafora albo zwrotnicy to uznajemy, że mijamy W5
FirstSemaphorDist = d_to_next_sem; // przepisanie znalezionej wartosci do zmiennej
return go;
};
bool
TController::TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, double const Signaldistance ) {
// stop points are irrelevant when not in one of the basic modes
if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { return true; }
// jeśli przystanek, trzeba obsłużyć wg rozkładu
iDrivigFlags |= moveStopPointFound;
// first 19 chars of the command is expected to be "PassengerStopPoint:" so we skip them
if( ToLower( Point.evEvent->input_text() ).compare( 19, sizeof( asNextStop ), ToLower( asNextStop ) ) != 0 )
{ // jeśli nazwa nie jest zgodna
if( false == IsScheduledPassengerStopVisible // check if our next scheduled stop didn't show up earlier in the scan
&& Point.fDist < 1.15 * fBrakeDist + 300
&& Point.fDist > 0 ) // tylko jeśli W4 jest blisko, przy dwóch może zaczać szaleć
{
// porównuje do następnej stacji, więc trzeba przewinąć do poprzedniej
// nastepnie ustawić następną na aktualną tak żeby prawidłowo ją obsłużył w następnym kroku
if( true == TrainParams.RewindTimeTable( Point.evEvent->input_text() ) ) {
asNextStop = TrainParams.NextStop();
TrainParams.StationStart = TrainParams.StationIndex;
}
}
else if( Point.fDist < -fLength ) {
// jeśli został przejechany
Point.iFlags = 0; // to można usunąć (nie mogą być usuwane w skanowaniu)
}
return true; // ignorowanie jakby nie było tej pozycji
}
else if (iDrivigFlags & moveStopPoint) // jeśli pomijanie W4, to nie sprawdza czasu odjazdu
{ // tylko gdy nazwa zatrzymania się zgadza
if( ( OrderCurrentGet() & ( Obey_train | Bank ) ) != 0 ) {
// check whether the station specifies radio channel change
// NOTE: we don't do it in shunt mode, as shunting operations tend to use dedicated radio channel
// NOTE: there's a risk radio channel change was specified by a station which we skipped during timetable rewind
// we ignore this for the time being as it's not a high priority error
auto const radiochannel { TrainParams.radio_channel() };
if( radiochannel > 0 ) {
if( iGuardRadio != 0 ) {
iGuardRadio = radiochannel;
}
if( iRadioChannel != radiochannel ) {
cue_action( driver_hint::radiochannel, radiochannel );
}
}
}
IsScheduledPassengerStopVisible = true; // block potential timetable rewind if the next stop shows up later in the scan
if (false == TrainParams.IsStop())
{ // jeśli nie ma tu postoju
Point.fVelNext = -1; // maksymalna prędkość w tym miejscu
// przy 160km/h jedzie 44m/s, to da dokładność rzędu 5 sekund
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
#endif
// przy jakim dystansie (stanie licznika) ma przesunąć na następny postój
fLastStopExpDist = mvOccupied->DistCounter + 0.250 + 0.001 * fLength;
TrainParams.UpdateMTable( simulation::Time, asNextStop );
UpdateDelayFlag();
TrainParams.StationIndexInc(); // przejście do następnej
asNextStop = TrainParams.NextStop(); // pobranie kolejnego miejsca zatrzymania
Point.iFlags = 0; // nie liczy się już
return true; // table entry recognized and handled
}
} // koniec obsługi przelotu na W4
else {
// zatrzymanie na W4
if ( false == Point.bMoved ) {
// potentially shift the stop point in accordance with its defined parameters
/*
// https://rainsted.com/pl/Wersja/18.2.133#Okr.C4.99gi_dla_W4_i_W32
Pierwszy parametr ujemny - preferowane zatrzymanie czoła składu (np. przed przejściem).
Pierwszy parametr dodatni - preferowane zatrzymanie środka składu (np. przy wiacie, przejściu podziemnym).
Drugi parametr ujemny - wskazanie zatrzymania dla krótszych składów (W32).
Drugi paramer dodatni - długość peronu (W4).
*/
auto L = 0.0;
auto Par1 = Point.evEvent->input_value(1);
auto Par2 = Point.evEvent->input_value(2);
if (Par2 >= 0 || fLength < -Par2) { //użyj tego W4
if (Par1 < 0) {
L = -Par1;
}
else {
//środek
L = Par1 - fMinProximityDist - fLength * 0.5;
}
L = std::max(0.0, std::min(L, std::abs(Par2) - fMinProximityDist - fLength));
Point.UpdateDistance(L);
Point.bMoved = true;
Point.fMoved = L;
}
else {
Point.iFlags = 0;
}
}
// for human-driven vehicles discard the stop point if they leave it far enough behind
if( false == AIControllFlag
&& Point.fDist < -1 * std::max(fLength + 100, 250.0) - Point.fMoved ) {
Point.iFlags = 0; // nie liczy się już zupełnie (nie wyśle SetVelocity)
Point.fVelNext = -1; // można jechać za W4
if( Point.fDist <= 0.0 && eSignNext == Point.evEvent ) {
// sanity check, if we're held by this stop point, let us go
VelSignalLast = -1;
}
return true;
}
IsAtPassengerStop = Point.fDist <= passengerstopmaxdistance
// Ra 2F1I: odległość plus długość pociągu musi być mniejsza od długości
// peronu, chyba że pociąg jest dłuższy, to wtedy minimalna.
// jeśli długość peronu ((sSpeedTable[i].evEvent->ValueGet(2)) nie podana,
// przyjąć odległość fMinProximityDist
&& ((iDrivigFlags & moveStopCloser) != 0 ?
Point.fDist + fLength + (Point.fMoved - fMinProximityDist * 0.5f) <= std::max(std::abs(Point.evEvent->input_value(2)),
2.0 * fMaxProximityDist + fLength) : // fmaxproximitydist typically equals ~50 m
Point.fDist < Signaldistance);
if( !eSignNext ) {
//jeśli nie widzi następnego sygnału ustawia dotychczasową
eSignNext = Point.evEvent;
}
if( mvOccupied->Vel > EU07_AI_MOVEMENT ) {
// jeśli jedzie (nie trzeba czekać, aż się drgania wytłumią - drzwi zamykane od 1.0) to będzie zatrzymanie
Point.fVelNext = 0;
// potentially announce pending stop
if( m_lastannouncement != announcement_t::approaching
&& Point.fDist < 750
&& Point.fDist > 250 ) {
announce( announcement_t::approaching );
}
} else if( true == IsAtPassengerStop ) {
// jeśli się zatrzymał przy W4, albo stał w momencie zobaczenia W4
if( !AIControllFlag ) {
// w razie przełączenia na AI ma nie podciągać do W4, gdy użytkownik zatrzymał za daleko
iDrivigFlags &= ~moveStopCloser;
}
if (TrainParams.UpdateMTable( simulation::Time, asNextStop) ) {
// to się wykona tylko raz po zatrzymaniu na W4
if( TrainParams.StationIndex < TrainParams.StationCount ) {
// jeśli są dalsze stacje, bez trąbienia przed odjazdem
// also ignore any horn cue that may be potentially set below 1 km/h and before the actual full stop
iDrivigFlags &= ~( moveStartHorn | moveStartHornNow );
}
UpdateDelayFlag();
// perform loading/unloading
// HACK: manual check if we didn't already do load exchange at this stop
// TODO: remove the check once the station system is in place
if( m_lastexchangestop != asNextStop ) {
m_lastexchangestop = asNextStop;
m_lastexchangedirection = pVehicle->DirectionGet();
m_lastexchangeplatforms = static_cast<int>( std::floor( std::abs( Point.evEvent->input_value( 2 ) ) ) ) % 10;
auto const exchangetime { simulation::Station.update_load( pVehicles[ end::front ], TrainParams, m_lastexchangeplatforms ) };
WaitingSet( exchangetime );
// announce the stop name while at it
announce( announcement_t::current );
m_makenextstopannouncement = true;
}
if (TrainParams.DirectionChange()) {
// jeśli "@" w rozkładzie, to wykonanie dalszych komend
// wykonanie kolejnej komendy, nie dotyczy ostatniej stacji
if (iDrivigFlags & movePushPull) {
// SN61 ma się też nie ruszać, chyba że ma wagony
iDrivigFlags |= moveStopHere; // EZT ma stać przy peronie
if (OrderNextGet() != Change_direction) {
OrderPush(Change_direction); // zmiana kierunku
OrderPush(
TrainParams.StationIndex < TrainParams.StationCount ?
Obey_train :
Shunt); // to dalej wg rozkładu
}
}
else {
// a dla lokomotyw...
// pozwolenie na przejechanie za W4 przed czasem i nie ma stać
iDrivigFlags &= ~( moveStopPoint | moveStopHere );
}
// przejście do kolejnego rozkazu (zmiana kierunku, odczepianie)
JumpToNextOrder();
// ma nie podjeżdżać pod W4 po przeciwnej stronie
iDrivigFlags &= ~moveStopCloser;
// ten W4 nie liczy się już zupełnie (nie wyśle SetVelocity)
Point.iFlags = 0;
// jechać
Point.fVelNext = -1;
// nie analizować prędkości
return true;
}
}
if (OrderCurrentGet() & ( Shunt | Loose_shunt )) {
OrderNext(Obey_train); // uruchomić jazdę pociągową
CheckVehicles(); // zmienić światła
}
if (TrainParams.StationIndex < TrainParams.StationCount) {
// jeśli są dalsze stacje, czekamy do godziny odjazdu
if ( true == IsCargoTrain
|| true == TrainParams.IsMaintenance()
|| TrainParams.IsTimeToGo(simulation::Time.data().wHour, simulation::Time.data().wMinute + simulation::Time.data().wSecond * 0.0167) ) {
// z dalszą akcją czekamy do godziny odjazdu
// cargo trains and passenger trains at maintenance stop don't need to wait
IsAtPassengerStop = false;
// przy jakim dystansie (stanie licznika) ma przesunąć na następny postój
fLastStopExpDist = mvOccupied->DistCounter + 0.050 + 0.001 * fLength;
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
#endif
// update consist weight, brake settings and ai braking tables
// NOTE: this calculation is expected to run after completing loading/unloading
CheckVehicles(); // nastawianie hamulca do jazdy pociągowej
if( static_cast<int>( std::floor( std::abs( Point.evEvent->input_value( 1 ) ) ) ) % 2 ) {
// nie podjeżdżać do semafora, jeśli droga nie jest wolna
iDrivigFlags |= moveStopHere;
}
else {
//po czasie jedź dalej
iDrivigFlags &= ~moveStopHere;
}
iDrivigFlags |= moveStopCloser; // do następnego W4 podjechać blisko (z dociąganiem)
Point.iFlags = 0; // nie liczy się już zupełnie (nie wyśle SetVelocity)
Point.fVelNext = -1; // można jechać za W4
if( Point.fDist <= 0.0 && eSignNext == Point.evEvent ) {
// sanity check, if we're held by this stop point, let us go
VelSignalLast = -1;
}
if( Command == TCommandType::cm_Unknown ) { // jeśli nie było komendy wcześniej
Command = TCommandType::cm_Ready; // gotów do odjazdu z W4 (semafor może zatrzymać)
}
if( false == tsGuardSignal.empty() ) {
// jeśli mamy głos kierownika, to odegrać
iDrivigFlags |= moveGuardSignal;
}
return true; // nie analizować prędkości
} // koniec startu z zatrzymania
else {
cue_action(driver_hint::waitdeparturetime);
if (TrainParams.IsTimeToGo(simulation::Time.data().wHour, simulation::Time.data().wMinute + simulation::Time.data().wSecond*0.0167 + 0.1)) { //if is less than 6 sec to departure
iDrivigFlags |= moveGuardOpenDoor;
GuardOpenDoor();
}
}
} // koniec obsługi początkowych stacji
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
#endif
asNextStop = TrainParams.NextStop(); // informacja o końcu trasy
TrainParams.NewName("none"); // czyszczenie nieaktualnego rozkładu
// ma nie podjeżdżać pod W4 i ma je pomijać
iDrivigFlags &= ~moveStopCloser;
if( false == TestFlag( iDrivigFlags, movePushPull ) ) {
// if the consist can change direction through a simple cab change it doesn't need fiddling with recognition of passenger stops
iDrivigFlags &= ~moveStopPoint;
}
fLastStopExpDist = -1.0f; // nie ma rozkładu, nie ma usuwania stacji
Point.iFlags = 0; // W4 nie liczy się już (nie wyśle SetVelocity)
Point.fVelNext = -1; // można jechać za W4
if( Point.fDist <= 0.0 && eSignNext == Point.evEvent ) {
// sanity check, if we're held by this stop point, let us go
VelSignalLast = -1;
}
// wykonanie kolejnego rozkazu (Change_direction albo Shunt)
JumpToNextOrder();
// ma się nie ruszać aż do momentu podania sygnału
iDrivigFlags |= moveStopHere | moveStartHorn;
return true; // nie analizować prędkości
} // koniec obsługi ostatniej stacji
} // vel 0, at passenger stop
else {
// HACK: momentarily deactivate W4 to trick the controller into moving closer
Point.fVelNext = -1;
} // vel 0, outside of passenger stop
} // koniec obsługi zatrzymania na W4
} // koniec warunku pomijania W4 podczas zmiany czoła
else
{ // skoro pomijanie, to jechać i ignorować W4
Point.iFlags = 0; // W4 nie liczy się już (nie zatrzymuje jazdy)
Point.fVelNext = -1;
return true; // nie analizować prędkości
}
return false; //
}
bool
TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPos &Point, double &Signaldistance, int const Pointindex ) {
// sprawdzanie eventów pasywnych miniętych
if( Point.fDist < 0.0 ) {
if( SemNextIndex == Pointindex ) {
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextIndex ].GetName() );
}
SemNextIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód
}
if( SemNextStopIndex == Pointindex ) {
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table update for " + OwnerName() + ", passed semaphor " + sSpeedTable[ SemNextStopIndex ].GetName() );
}
SemNextStopIndex = -1; // jeśli minęliśmy semafor od ograniczenia to go kasujemy ze zmiennej sprawdzającej dla skanowania w przód
}
switch( Point.evEvent->input_command() ) {
// TBD, TODO: expand emergency_brake handling to a more generic security system signal
case TCommandType::cm_EmergencyBrake: {
pVehicle->RadioStop();
Point.Clear(); // signal received, deactivate
return true;
}
case TCommandType::cm_SecuritySystemMagnet: {
// NOTE: magnet induction calculation presumes the driver is located in the front vehicle
// TBD, TODO: take into account actual position of controlled/occupied vehicle in the consist, whichever comes first
auto const magnetlocation { pVehicles[ end::front ]->MoverParameters->SecuritySystem.MagnetLocation };
auto const magnetrange { 1.0 };
auto const ismagnetpassed { Point.fDist < -( magnetlocation + magnetrange ) };
if( Point.fDist < -magnetlocation ) {
// NOTE: normally we'd activate the magnet once the leading vehicle passes it
// but on a fresh scan after direction change it would be detected as long as it's under consist, and meet the (simple) activation condition
// thus we're doing a more precise check in such situation (we presume direction change takes place only if the vehicle is standing still)
if( mvOccupied->Vel > EU07_AI_NOMOVEMENT
|| false == ismagnetpassed ) {
mvOccupied->SecuritySystem.set_cabsignal_lock(!AIControllFlag); // don't make life difficult for the ai, but a human driver is a fair game
PutCommand(
Point.evEvent->input_text(),
Point.evEvent->input_value( 1 ),
Point.evEvent->input_value( 2 ),
nullptr );
}
}
if( ismagnetpassed ) {
Point.Clear();
mvOccupied->SecuritySystem.set_cabsignal_lock(false);
}
return true;
}
default: {
break;
}
}
}
// check signals ahead
if( Point.fDist > 0.0 ) {
// bail out early for signals which activate when passed
// TBD: make it an event method?
switch( Point.evEvent->input_command() ) {
case TCommandType::cm_EmergencyBrake: {
return true;
}
case TCommandType::cm_SecuritySystemMagnet: {
return true;
}
default: {
break;
}
}
if( Point.IsProperSemaphor( OrderCurrentGet() ) ) {
// special rule for cars: ignore stop signals at distance too short to come to a stop
// as trying to stop in such situation is likely to place the car on train tracks
if( is_car()
&& Point.fVelNext != -1.0
&& Point.fVelNext < 1.0
&& Point.fDist < -0.5 + std::min(fBrakeDist * 0.2, mvOccupied->Vel * 0.2) ) {
Point.Clear();
return true;
}
// jeśli jest mienięty poprzedni semafor a wcześniej
// byl nowy to go dorzucamy do zmiennej, żeby cały czas widział najbliższy
if( SemNextIndex == -1 ) {
SemNextIndex = Pointindex;
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table update for " + OwnerName() + ", next semaphor is " + sSpeedTable[ SemNextIndex ].GetName() );
}
}
if( SemNextStopIndex == -1
|| ( sSpeedTable[SemNextStopIndex].fVelNext != 0
&& Point.fVelNext == 0 ) ) {
SemNextStopIndex = Pointindex;
}
}
}
if (Point.iFlags & spOutsideStation)
{ // jeśli W5, to reakcja zależna od trybu jazdy
if (OrderCurrentGet() & Obey_train)
{ // w trybie pociągowym: można przyspieszyć do wskazanej prędkości (po zjechaniu z rozjazdów)
Velocity = -1.0; // ignorować?
if ( Point.fDist < 0.0) { // jeśli wskaźnik został minięty
VelSignalLast = Velocity; //ustawienie prędkości na -1
}
else if( ( iDrivigFlags & moveSwitchFound ) == 0 ) { // jeśli rozjazdy już minięte
VelSignalLast = Velocity; //!!! to też koniec ograniczenia
}
}
else
{ // w trybie manewrowym: skanować od niego wstecz, stanąć po wyjechaniu za sygnalizator i zmienić kierunek
Velocity = 0.0; // zmiana kierunku może być podanym sygnałem, ale wypadało by zmienić światło wcześniej
if( ( iDrivigFlags & moveSwitchFound ) == 0 ) { // jeśli nie ma rozjazdu
// check for presence of a signal facing the opposite direction
// if there's one, we'll want to pass it before changing direction
basic_event *foundevent = nullptr;
if( Point.fDist - fMaxProximityDist > 0 ) {
auto scandistance{ Point.fDist + fLength - fMaxProximityDist };
auto *scanvehicle{ pVehicles[ end::rear ] };
auto scandirection{ scanvehicle->DirectionGet() * scanvehicle->RaDirectionGet() };
auto *foundtrack = BackwardTraceRoute( scandistance, scandirection, scanvehicle, foundevent, -1, end::front, false );
}
if( foundevent == nullptr ) {
iDrivigFlags |= moveTrackEnd; // to dalsza jazda trwale ograniczona (W5, koniec toru)
}
}
}
}
else if ( Point.iFlags & spStopOnSBL) {
// jeśli S1 na SBL
if( mvOccupied->Vel < 2.0 ) {
// stanąć nie musi, ale zwolnić przynajmniej
// jest w maksymalnym zasięgu to można go pominąć (wziąć drugą prędkosć)
// as long as there isn't any obstacle in arbitrary view range
if( Point.fDist < fMaxProximityDist
&& Obstacle.distance > 1000 ) {
eSignSkip = Point.evEvent;
// jazda na widoczność - skanować możliwość kolizji i nie podjeżdżać zbyt blisko
// usunąć flagę po podjechaniu blisko semafora zezwalającego na jazdę
// ostrożnie interpretować sygnały - semafor może zezwalać na jazdę pociągu z przodu!
iDrivigFlags |= moveVisibility;
// store the ordered restricted speed and don't exceed it until the flag is cleared
VelRestricted = Point.evEvent->input_value( 2 );
}
}
if( eSignSkip != Point.evEvent ) {
// jeśli ten SBL nie jest do pominięcia to ma 0 odczytywać
Velocity = Point.evEvent->input_value( 1 );
// TODO sprawdzić do której zmiennej jest przypisywane v i zmienić to tutaj
}
}
else if ( Point.IsProperSemaphor(OrderCurrentGet()))
{ // to semaphor
if( Point.fDist < 0 ) {
// for human-driven vehicles ignore the signal if it was passed by sufficient distance
if( false == AIControllFlag
&& Point.fDist < -1 * std::max(fLength + 100, 250.0) ) {
VelSignal = -1.0;
Point.Clear();
return true;
}
// for ai-driven vehicles always play by the rules
else {
VelSignalLast = Point.fVelNext; //minięty daje prędkość obowiązującą
}
}
else {
//jeśli z przodu to dajemy flagę, że jest
iDrivigFlags |= moveSignalFound;
Signaldistance = std::min( Point.fDist, Signaldistance );
// if there's another vehicle closer to the signal, then it's likely its intended recipient
// HACK: if so, make it a stop point, to prevent non-signals farther down affect us
auto const isforsomeoneelse { is_train() && Obstacle.distance < Point.fDist };
if( Point.fDist <= Signaldistance ) {
if( isforsomeoneelse ) {
VelSignalNext = 0.0;
Velocity = 0.0;
}
else {
VelSignalNext = Point.fVelNext;
if( Velocity < 0 ) {
Velocity = fVelMax;
VelSignal = fVelMax;
}
}
}
}
}
else if ( Point.iFlags & spRoadVel)
{ // to W6
if ( Point.fDist < 0)
VelRoad = Point.fVelNext;
}
else if ( Point.iFlags & spSectionVel)
{ // to W27
if ( Point.fDist < 0) // teraz trzeba sprawdzić inne warunki
{
if ( Point.fSectionVelocityDist == 0.0) {
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "TableUpdate: Event is behind. SVD = 0: " + Point.evEvent->m_name );
}
Point.Clear(); // jeśli punktowy to kasujemy i nie dajemy ograniczenia na stałe
}
else if ( Point.fSectionVelocityDist < 0.0) {
// ograniczenie obowiązujące do następnego
if ( Point.fVelNext == min_speed(Point.fVelNext, VelLimitLast)
&& Point.fVelNext != VelLimitLast) {
// jeśli ograniczenie jest mniejsze niż obecne to obowiązuje od zaraz
VelLimitLast = Point.fVelNext;
}
else if ( Point.fDist < -fLength) {
// jeśli większe to musi wyjechać za poprzednie
VelLimitLast = Point.fVelNext;
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "TableUpdate: Event is behind. SVD < 0: " + Point.evEvent->m_name );
}
Point.Clear(); // wyjechaliśmy poza poprzednie, można skasować
}
}
else
{ // jeśli większe to ograniczenie ma swoją długość
if ( Point.fVelNext == min_speed(Point.fVelNext, VelLimitLast)
&& Point.fVelNext != VelLimitLast) {
// jeśli ograniczenie jest mniejsze niż obecne to obowiązuje od zaraz
VelLimitLast = Point.fVelNext;
}
else if ( Point.fDist < -fLength
&& Point.fVelNext != VelLimitLast) {
// jeśli większe to musi wyjechać za poprzednie
VelLimitLast = Point.fVelNext;
}
else if (Point.fDist < -fLength - Point.fSectionVelocityDist) {
VelLimitLast = -1.0;
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "TableUpdate: Event is behind. SVD > 0: " + Point.evEvent->m_name );
}
Point.Clear(); // wyjechaliśmy poza poprzednie, można skasować
}
}
}
}
//sprawdzenie eventów pasywnych przed nami
{ // zawalidrogi nie ma (albo pojazd jest samochodem), sprawdzić sygnał
if ( Point.iFlags & spShuntSemaphor) // jeśli Tm - w zasadzie to sprawdzić komendę!
{ // jeśli podana prędkość manewrowa
if( Velocity == 0.0
&& true == TestFlag(OrderCurrentGet(), Obey_train) ) {
// jeśli tryb pociągowy a tarcze ma ShuntVelocity 0 0
Velocity = -1; // ignorować, chyba że prędkość stanie się niezerowa
if( true == TestFlag( Point.iFlags, spElapsed ) ) {
// a jak przejechana to można usunąć, bo podstawowy automat usuwa tylko niezerowe
Point.Clear();
}
}
else if( Command == TCommandType::cm_Unknown ) {
// jeśli jeszcze nie ma komendy
// komenda jest tylko gdy ma jechać, bo stoi na podstawie tabelki
if( Velocity != 0.0 ) {
// jeśli nie było komendy wcześniej - pierwsza się liczy - ustawianie VelSignal
Command = TCommandType::cm_ShuntVelocity; // w trybie pociągowym tylko jeśli włącza tryb manewrowy (v!=0.0)
// Ra 2014-06: (VelSignal) nie może być tu ustawiane, bo Tm może być daleko
if( VelSignal == 0.0 ) {
// aby stojący ruszył
VelSignal = Velocity;
}
if( Point.fDist < 0.0 ) {
// jeśli przejechany
//!!! ustawienie, gdy przejechany jest lepsze niż wcale, ale to jeszcze nie to
VelSignal = Velocity;
// to można usunąć (nie mogą być usuwane w skanowaniu)
Point.Clear();
}
}
}
}
else if( ( Point.iFlags & spSectionVel ) == 0 ) {
//jeśli jakiś event pasywny ale nie ograniczenie
if( Command == TCommandType::cm_Unknown ) {
// jeśli nie było komendy wcześniej - pierwsza się liczy - ustawianie VelSignal
if( Velocity < 0.0
|| Velocity >= 1.0 ) {
// bo wartość 0.1 służy do hamowania tylko
Command = TCommandType::cm_SetVelocity; // może odjechać
// Ra 2014-06: (VelSignal) nie może być tu ustawiane, bo semafor może być daleko
// VelSignal=v; //nie do końca tak, to jest druga prędkość; -1 nie wpisywać...
if( VelSignal == 0.0 ) {
// aby stojący ruszył
VelSignal = -1.0;
}
if( Point.fDist < 0.0 ) {
// jeśli przejechany
VelSignal = Velocity == 0.0 ? 0.0 : -1.0;
// ustawienie, gdy przejechany jest lepsze niż wcale, ale to jeszcze nie to
if( Point.iFlags & spEvent ) {
// jeśli event
if( Point.evEvent != eSignSkip
|| Point.fVelNext != VelRestricted ) {
// ale inny niż ten, na którym minięto S1, chyba że się już zmieniło
// sygnał zezwalający na jazdę wyłącza jazdę na widoczność (po S1 na SBL)
iDrivigFlags &= ~moveVisibility;
// remove restricted speed
VelRestricted = -1.0;
}
}
// jeśli nie jest ograniczeniem prędkości to można usunąć
// (nie mogą być usuwane w skanowaniu)
Point.Clear();
}
}
else if( Point.evEvent->is_command() ) {
// jeśli prędkość jest zerowa, a komórka zawiera komendę
eSignNext = Point.evEvent; // dla informacji
// make sure the command isn't for someone else
// TBD, TODO: revise this check for bank/loose_shunt; we might want to ignore obstacles with no owner in these modes
if( Point.fDist < Obstacle.distance ) {
if( true == TestFlag( iDrivigFlags, moveStopHere ) ) {
// jeśli ma stać, dostaje komendę od razu
Command = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu
}
// NOTE: human drivers are likely to stop wherever and expect things to still work, so we give them more leeway
else if( Point.fDist <= ( AIControllFlag ? fMaxProximityDist : std::max( 250.0, fMaxProximityDist ) ) ) {
// jeśli ma dociągnąć, to niech dociąga
// (moveStopCloser dotyczy dociągania do W4, nie semafora)
Command = TCommandType::cm_Command; // komenda z komórki, do wykonania po zatrzymaniu
}
}
}
}
}
} // jeśli nie ma zawalidrogi
return false;
}
// modifies brake distance for low target speeds, to ease braking rate in such situations
float
TController::braking_distance_multiplier( float const Targetvelocity ) const {
auto const frictionmultiplier { 1.f / Global.FrictionWeatherFactor };
if( Targetvelocity > 65.f ) { return 1.f * frictionmultiplier; }
if( Targetvelocity < 5.f ) {
// HACK: engaged automatic transmission means extra/earlier braking effort is needed for the last leg before full stop
if( is_dmu()
&& mvOccupied->Vel < 40.0
&& Targetvelocity == 0.f ) {
auto const multiplier { std::clamp( 1.f + iVehicles * 0.5f, 2.f, 4.f ) };
return std::lerp(multiplier, 1.f, static_cast<float>(mvOccupied->Vel / 40.0)) * frictionmultiplier;
}
// HACK: cargo trains or trains going downhill with high braking threshold need more distance to come to a full stop
if( fBrake_a0[1] > 0.2
&& ( true == IsCargoTrain
|| fAccGravity > 0.025 ) ) {
return std::lerp(1.f, 2.f, std::clamp((fBrake_a0[1] - 0.2) / 0.2, 0.0, 1.0)) * frictionmultiplier;
}
return 1.f * frictionmultiplier;
}
// stretch the braking distance up to 3 times; the lower the speed, the greater the stretch
return std::lerp(3.f, 1.f, (Targetvelocity - 5.f) / 60.f) * frictionmultiplier;
}
void TController::TablePurger()
{ // odtykacz: usuwa mniej istotne pozycje ze środka tabelki, aby uniknąć zatkania
//(np. brak ograniczenia pomiędzy zwrotnicami, usunięte sygnały, minięte odcinki łuku)
if( sSpeedTable.size() < 2 ) {
return;
}
// simplest approach should be good enough for start -- just copy whatever is still relevant, then swap
// do a trial run first, to see if we need to bother at all
std::size_t trimcount{ 0 };
for( std::size_t idx = 0; idx < sSpeedTable.size() - 1; ++idx ) {
auto const &speedpoint = sSpeedTable[ idx ];
if( 0 == (speedpoint.iFlags & spEnabled)
|| ( (speedpoint.iFlags & (spElapsed | spTrack | spCurve | spSwitch)) == (spElapsed | spTrack | spCurve)
&& speedpoint.fVelNext < 0.0 ) ) {
// NOTE: we could break out early here, but running through entire thing gives us exact size needed for new table
++trimcount;
}
}
if( trimcount == 0 ) {
// there'd be no gain, may as well bail
return;
}
std::vector<TSpeedPos> trimmedtable; trimmedtable.reserve( sSpeedTable.size() - trimcount );
// we can only update pointers safely after new table is finalized, so record their indices until then
for( std::size_t idx = 0; idx < sSpeedTable.size() - 1; ++idx ) {
// cache placement of semaphors in the new table, if we encounter them
if( idx == SemNextIndex ) {
SemNextIndex = trimmedtable.size();
}
if( idx == SemNextStopIndex ) {
SemNextStopIndex = trimmedtable.size();
}
auto const &speedpoint = sSpeedTable[ idx ];
if( 0 == (speedpoint.iFlags & spEnabled)
|| ( (speedpoint.iFlags & (spElapsed | spTrack | spCurve | spSwitch)) == (spElapsed | spTrack | spCurve)
&& speedpoint.fVelNext < 0.0 ) ) {
// if the trimmed point happens to be currently active semaphor we need to invalidate their placements
if( idx == SemNextIndex ) {
SemNextIndex = -1;
}
if( idx == SemNextStopIndex ) {
SemNextStopIndex = -1;
}
continue;
}
// we're left with useful speed point record we should copy
trimmedtable.emplace_back( speedpoint );
}
// always copy the last entry
trimmedtable.emplace_back( sSpeedTable.back() );
if( Global.iWriteLogEnabled & 8 ) {
WriteLog( "Speed table garbage collection for " + OwnerName() + " cut away " + std::to_string( trimcount ) + ( trimcount == 1 ? " record" : " records" ) );
}
// update the data
std::swap( sSpeedTable, trimmedtable );
iLast = sSpeedTable.size() - 1;
};
void TController::TableSort() {
if( sSpeedTable.size() < 3 ) {
// we skip last slot and no point in checking if there's only one other entry
return;
}
TSpeedPos sp_temp = TSpeedPos(); // uzywany do przenoszenia
for( int i = 0; i < iLast - 1; ++i ) {
// pętla tylko do dwóch pozycji od końca bo ostatniej nie modyfikujemy
if (sSpeedTable[i].fDist > sSpeedTable[i + 1].fDist)
{ // jesli pozycja wcześniejsza jest dalej to źle
sp_temp = sSpeedTable[i + 1];
sSpeedTable[i + 1] = sSpeedTable[i]; // zamiana
sSpeedTable[i] = sp_temp;
// jeszcze sprawdzenie czy pozycja nie była indeksowana dla eventów
if (SemNextIndex == i)
++SemNextIndex;
else if (SemNextIndex == i + 1)
--SemNextIndex;
if (SemNextStopIndex == i)
++SemNextStopIndex;
else if (SemNextStopIndex == i + 1)
--SemNextStopIndex;
}
}
}
//---------------------------------------------------------------------------
TController::TController(bool AI, TDynamicObject *NewControll, bool InitPsyche, bool Primary) :// czy ma aktywnie prowadzić?
AIControllFlag( AI ), pVehicle( NewControll )
{
if( pVehicle != nullptr ) {
pVehicles[ end::front ] = pVehicle->GetFirstDynamic( end::front ); // pierwszy w kierunku jazdy (Np. Pc1)
pVehicles[ end::rear ] = pVehicle->GetFirstDynamic( end::rear ); // ostatni w kierunku jazdy (końcówki)
}
else {
pVehicles[ end::front ] = nullptr;
pVehicles[ end::rear ] = nullptr;
}
ControllingSet(); // utworzenie połączenia do sterowanego pojazdu
if( mvOccupied != nullptr ) {
iDirectionOrder = mvOccupied->CabActive; // 1=do przodu (w kierunku sprzęgu 0)
VehicleName = mvOccupied->Name;
if( is_car() ) { // samochody: na podst. http://www.prawko-kwartnik.info/hamowanie.html
// fDriverBraking=0.0065; //mnożone przez (v^2+40*v) [km/h] daje prawie drogę hamowania [m]
fDriverBraking = 0.03; // coś nie hamują te samochody zbyt dobrze
fDriverDist = 5.0; // 5m - zachowywany odstęp przed kolizją
fVelPlus = 10.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelMinus = 2.0; // margines prędkości powodujący załączenie napędu
}
else { // pociągi i statki
fDriverBraking = 0.06; // mnożone przez (v^2+40*v) [km/h] daje prawie drogę hamowania [m]
fDriverDist = 50.0; // 50m - zachowywany odstęp przed kolizją
fVelPlus = 5.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelMinus = 5.0; // margines prędkości powodujący załączenie napędu
}
// fAccThreshold może podlegać uczeniu się - hamowanie powinno być rejestrowane, a potem analizowane
// próg opóźnienia dla zadziałania hamulca
fAccThreshold = is_emu() ? -0.55 : is_dmu() ? -0.45 : -0.2;
/*
// HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors
if( ( mvOccupied->TrainType == dt_EZT )
&& ( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) ) {
fAccThreshold += 0.10;
}
*/
}
// TrainParams=NewTrainParams;
// if (TrainParams)
// asNextStop=TrainParams.NextStop();
// else
TrainParams = TTrainParameters("none"); // rozkład jazdy
// OrderCommand="";
// OrderValue=0;
OrdersClear();
if( true == Primary ) {
iDrivigFlags |= movePrimary; // aktywnie prowadzące pojazd
}
SetDriverPsyche(); // na końcu, bo wymaga ustawienia zmiennych
TableClear();
if( WriteLogFlag ) {
#ifdef _WIN32
CreateDirectory( "physicslog", nullptr);
#elif __unix__
mkdir( "physicslog", 0744 );
#endif
LogFile.open( std::string( "physicslog/" + VehicleName + ".dat" ),
std::ios::in | std::ios::out | std::ios::trunc );
#if LOGPRESS == 0
LogFile
<< "Time[s] Velocity[m/s] Acceleration[m/ss] "
<< "Coupler.Dist[m] Coupler.Force[N] TractionForce[kN] FrictionForce[kN] BrakeForce[kN] "
<< "BrakePress[MPa] PipePress[MPa] MotorCurrent[A] "
<< "MCP SCP BCP LBP Direction Command CVal1 CVal2 "
<< "Security Wheelslip "
<< "EngineTemp[Deg] OilTemp[Deg] WaterTemp[Deg] WaterAuxTemp[Deg]"
<< "\r\n";
LogFile << std::fixed << std::setprecision( 4 );
#endif
#if LOGPRESS == 1
LogFile << string( "t\tVel\tAcc\tPP\tVVP\tBP\tBVP\tCVP" ).c_str() << "\n";
#endif
LogFile.flush();
}
// HACK: give the simulation a small window to potentially replace the AI with a human driver
fActionTime = -5.0;
};
void TController::CloseLog()
{
if (WriteLogFlag)
{
LogFile.close();
}
};
TController::~TController()
{ // wykopanie mechanika z roboty
CloseLog();
};
// zamiana kodu rozkazu na opis
std::string TController::Order2Str(TOrders Order) const {
if( (Order & Change_direction) != 0
&& Order != Change_direction ) {
// może być nałożona na inną i wtedy ma priorytet
return
Order2Str( Change_direction )
+ " + "
+ Order2Str( static_cast<TOrders>( Order & ~Change_direction ) );
}
switch( Order ) {
case Wait_for_orders: return "Wait_for_orders";
case Prepare_engine: return "Prepare_engine";
case Release_engine: return "Release_engine";
case Change_direction: return "Change_direction";
case Connect: return "Connect";
case Disconnect: return "Disconnect";
case Shunt: return "Shunt";
case Loose_shunt: return "Loose_shunt";
case Obey_train: return "Obey_train";
case Bank: return "Bank";
case Jump_to_first_order: return "Jump_to_first_order";
default: return "Undefined";
}
}
std::array<char, 64> orderbuffer;
std::string TController::OrderCurrent() const
{ // pobranie aktualnego rozkazu celem wyświetlenia
auto const order { OrderCurrentGet() };
// generally prioritize direction change...
if( order & Change_direction ) {
// ...but not in the middle of coupling operation
if( false == TestFlag( iDrivigFlags, moveConnect ) ) {
return STR("Change direction");
}
}
switch( static_cast<TOrders>( order & ~Change_direction ) ) {
case Wait_for_orders: { return STR("Wait for orders"); }
case Prepare_engine: { return STR("Start the engine"); }
case Release_engine: { return STR("Shut down the engine"); }
case Change_direction: { return STR("Change direction"); }
case Connect: { return STR("Couple to consist ahead"); }
case Disconnect: {
if( iVehicleCount < 0 ) {
// done with uncoupling, order should update shortly
return STR("Wait for orders");
}
// try to provide some task details
auto const count { iVehicleCount };
if( iVehicleCount > 1 ) {
std::snprintf(
orderbuffer.data(), orderbuffer.size(),
STR_C("the engine plus %d next vehicles"), // TODO: implement plural forms
count );
}
auto const countstring { (
count == 0 ? STR("the engine") :
count == 1 ? STR("the engine plus the next vehicle") :
orderbuffer.data() ) };
std::snprintf(
orderbuffer.data(), orderbuffer.size(),
STR_C("Uncouple %s"),
countstring.c_str() );
return orderbuffer.data();
}
case Shunt: { return STR("Shunt according to signals"); }
case Loose_shunt: { return STR("Loose shunt according to signals"); }
case Obey_train: { return STR("Drive according to signals and timetable"); }
case Bank: { return STR("Bank consist ahead"); }
default: { return{}; }
}
};
void TController::OrdersClear()
{ // czyszczenie tabeli rozkazów na starcie albo po dojściu do końca
OrderPos = 0;
OrderTop = 1; // szczyt stosu rozkazów
for (int b = 0; b < maxorders; b++)
OrderList[b] = Wait_for_orders;
#if LOGORDERS
OrdersDump("OrdersClear", false);
#endif
};
void TController::Activation()
{ // umieszczenie obsady w odpowiednim członie, wykonywane wyłącznie gdy steruje AI
iDirection = iDirectionOrder; // kierunek (względem sprzęgów pojazdu z AI) właśnie został ustalony (zmieniony)
if (iDirection)
{ // jeśli jest ustalony kierunek
auto *initialvehicle { pVehicle };
/*
auto const initiallocalbrakelevel { mvOccupied->LocalBrakePosA };
*/
auto const initialspringbrakestate { mvOccupied->SpringBrake.Activate };
// switch off tempomat
SpeedCntrl( 0.0 );
mvControlling->DecScndCtrl( 2 );
// reset controls
ZeroSpeed();
ZeroDirection();
mvOccupied->CabOccupied = mvOccupied->CabActive; // użytkownik moze zmienić CabOccupied wychodząc
mvOccupied->CabDeactivisation(); // tak jest w Train.cpp
if (TestFlag(pVehicle->MoverParameters->Couplers[iDirectionOrder < 0 ? end::rear : end::front].CouplingFlag, coupling::control)) {
ZeroLocalBrake();
if( initialspringbrakestate ) {
mvOccupied->SpringBrakeActivate( false );
}
// odcięcie na zaworze maszynisty, FVel6 po drugiej stronie nie luzuje
mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos(bh_NP) ); // odcięcie na zaworze maszynisty
BrakeLevelSet( mvOccupied->BrakeCtrlPos ); //ustawienie zmiennej GBH
}
// przejście AI na drugą stronę EN57, ET41 itp.
// TODO: clean this up, there's lot of redundancy with TMoverParameters::ChangeCab() and TTrain::MoveToVehicle()
{
int movedirection { ( iDirection < 0 ? end::rear : end::front ) };
auto *targetvehicle { pVehicle->FirstFind( movedirection, coupling::control ) };
if( pVehicle != targetvehicle ) {
auto *targetvehicledriver { targetvehicle->Mechanik }; // zapamiętanie tego, co ewentualnie tam siedzi, żeby w razie dwóch zamienić miejscami
// move to the new vehicle
targetvehicle->Mechanik = this; // na razie bilokacja
targetvehicle->MoverParameters->SetInternalCommand("", 0, 0); // usunięcie ewentualnie zalegającej komendy (Change_direction?)
if( targetvehicle->DirectionGet() != pVehicle->DirectionGet() ) {
// jeśli są przeciwne do siebie to będziemy jechać w drugą stronę względem zasiedzianego pojazdu
iDirection = -iDirection;
}
// move the other driver to our old vehicle
pVehicle->Mechanik = targetvehicledriver; // wsadzamy tego, co ewentualnie był (podwójna trakcja)
// NOTE: having caboccupied != 0 (by swapping in the driver from the target vehicle) may lead to dysfunctional brake
// TODO: investigate and resolve if necessary
pVehicle->MoverParameters->CabOccupied = targetvehicle->MoverParameters->CabOccupied;
if( pVehicle->Mechanik ) { // not guaranteed, as target vehicle can be empty
pVehicle->Mechanik->BrakeLevelSet( pVehicle->MoverParameters->BrakeCtrlPos );
}
// finish driver swap
pVehicle = targetvehicle;
}
}
if (pVehicle != initialvehicle)
{ // jeśli zmieniony został pojazd prowadzony
ControllingSet(); // utworzenie połączenia do sterowanego pojazdu (może się zmienić) - silnikowy dla EZT
if( mvOccupied->BrakeCtrlPosNo > 0
&& ( BrakeSystem == TBrakeSystem::Pneumatic
|| BrakeSystem == TBrakeSystem::ElectroPneumatic ) ) {
mvOccupied->LimPipePress = mvOccupied->PipePress;
mvOccupied->ActFlowSpeed = 0;
}
BrakeLevelSet( mvOccupied->BrakeCtrlPos );
}
if( mvControlling->EngineType == TEngineType::DieselEngine ) {
// dla 2Ls150 - przed ustawieniem kierunku - można zmienić tryb pracy
if( mvControlling->ShuntModeAllow ) {
mvControlling->CurrentSwitch(
(OrderCurrentGet() & (Shunt | Loose_shunt)) != 0
|| fMass > 224000.0 ); // do tego na wzniesieniu może nie dać rady na liniowym
}
}
// Ra: to przełączanie poniżej jest tu bez sensu
mvOccupied->CabOccupied = iDirection; // aktywacja kabiny w prowadzonym pojeżdzie (silnikowy może być odwrotnie?)
mvOccupied->CabActivisation(); // uruchomienie kabin w członach
DirectionForward(true); // nawrotnik do przodu
mvOccupied->SpringBrakeActivate( initialspringbrakestate );
/*
// NOTE: this won't restore local brake if the vehicle has integrated local brake control
// TBD, TODO: fix or let the ai activate the brake again as part of its standard logic?
if (initiallocalbrakelevel > 0.0) // hamowanie tylko jeśli był wcześniej zahamowany (bo możliwe, że jedzie!)
mvOccupied->LocalBrakePosA = initiallocalbrakelevel; // zahamuj jak wcześniej
*/
if( pVehicle != initialvehicle ) {
auto *train { simulation::Trains.find( initialvehicle->name() ) };
if( train ) {
train->MoveToVehicle( pVehicle );
}
}
CheckVehicles(); // sprawdzenie składu, AI zapali światła
TableClear(); // resetowanie tabelki skanowania torów
}
};
void TController::AutoRewident()
{ // autorewident: nastawianie hamulców w składzie
int r = 0, g = 0, p = 0; // ilości wagonów poszczególnych typów
TDynamicObject *d = pVehicles[0]; // pojazd na czele składu
// 1. Zebranie informacji o składzie pociągu — przejście wzdłuż składu i odczyt parametrów:
// · ilość wagonów -> są zliczane, wszystkich pojazdów jest (iVehicles)
// · długość (jako suma) -> jest w (fLength)
// · masa (jako suma) -> jest w (fMass)
while (d)
{ // klasyfikacja pojazdów wg BrakeDelays i mocy (licznik)
if (d->MoverParameters->Power < 1) // - lokomotywa - Power>1 - ale może być nieczynna na końcu...
if (TestFlag(d->MoverParameters->BrakeDelays, bdelay_R))
++r; // - wagon pospieszny - jest R
else if (TestFlag(d->MoverParameters->BrakeDelays, bdelay_G))
++g; // - wagon towarowy - jest G (nie ma R)
else
++p; // - wagon osobowy - reszta (bez G i bez R)
d = d->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła)
}
// 2. Określenie typu pociągu i nastawy:
int ustaw; //+16 dla pasażerskiego
if (r + g + p == 0)
ustaw = 16 + bdelay_R; // lokomotywa luzem (może być wieloczłonowa)
else
{ // jeśli są wagony
ustaw = g < std::min(4, r + p) ? 16 : 0;
if (ustaw) // jeśli towarowe < Min(4, pospieszne+osobowe)
{ // to skład pasażerski - nastawianie pasażerskiego
ustaw += g && r < g + p ? bdelay_P : bdelay_R;
// jeżeli towarowe>0 oraz pospiesze<=towarowe+osobowe to P (0)
// inaczej R (2)
}
else
{ // inaczej towarowy - nastawianie towarowego
if (fLength < 300.0 && fMass < 600000.0) //[kg]
ustaw |= bdelay_P; // jeżeli długość<300 oraz masa<600 to P (0)
else if (fLength < 500.0 && fMass < 1300000.0)
ustaw |= bdelay_R; // jeżeli długość<500 oraz masa<1300 to GP (2)
else
ustaw |= bdelay_G; // inaczej G (1)
}
// zasadniczo na sieci PKP kilka lat temu na P/GP jeździły tylko kontenerowce o
// rozkładowej 90 km/h. Pozostałe jeździły 70 km/h i były nastawione na G.
}
d = pVehicles[0]; // pojazd na czele składu
p = 0; // będziemy tu liczyć wagony od lokomotywy dla nastawy GP
while (d)
{ // 3. Nastawianie
if( true == AIControllFlag
|| d != pVehicle ) {
// don't touch human-controlled vehicle, but others are free game
switch( ustaw ) {
case bdelay_P: {
// towarowy P - lokomotywa na G, reszta na P.
d->MoverParameters->BrakeDelaySwitch(
d->MoverParameters->Power > 1 ?
bdelay_G :
bdelay_P );
break;
}
case bdelay_G: {
// towarowy G - wszystko na G, jeśli nie ma to P (powinno się wyłączyć hamulec)
d->MoverParameters->BrakeDelaySwitch(
TestFlag( d->MoverParameters->BrakeDelays, bdelay_G ) ?
bdelay_G :
bdelay_P );
break;
}
case bdelay_R: {
// towarowy GP - lokomotywa oraz 5 pierwszych pojazdów przy niej na G, reszta na P
if( d->MoverParameters->Power > 1 ) {
d->MoverParameters->BrakeDelaySwitch( bdelay_G );
p = 0; // a jak będzie druga w środku?
}
else {
d->MoverParameters->BrakeDelaySwitch(
++p <= 5 ?
bdelay_G :
bdelay_P );
}
break;
}
case 16 + bdelay_R: {
// pasażerski R - na R, jeśli nie ma to P
d->MoverParameters->BrakeDelaySwitch(
TestFlag( d->MoverParameters->BrakeDelays, bdelay_R ) ?
bdelay_R :
bdelay_P );
break;
}
case 16 + bdelay_P: {
// pasażerski P - wszystko na P
d->MoverParameters->BrakeDelaySwitch( bdelay_P );
break;
}
}
// potentially release manual brake
d->MoverParameters->DecManualBrakeLevel( ManualBrakePosNo );
d->MoverParameters->SpringBrake.Activate = false;
}
d = d->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła)
}
//ustawianie trybu pracy zadajnika hamulca, wystarczy raz po inicjalizacji AI
if( true == AIControllFlag ) {
// if a human is in charge leave the brake mode up to them, otherwise do as you like
for( int i = 1; i <= 8; i *= 2 ) {
if( ( mvOccupied->BrakeOpModes & i ) > 0 ) {
mvOccupied->BrakeOpModeFlag = i;
}
}
}
// teraz zerujemy tabelkę opóźnienia hamowania
for (int i = 0; i < BrakeAccTableSize; ++i)
{
fBrake_a0[i+1] = 0;
fBrake_a1[i+1] = 0;
}
if( OrderCurrentGet() & ( Shunt | Loose_shunt ) ) {
// for uniform behaviour and compatibility with older scenarios set default acceleration table values for shunting
fAccThreshold = is_emu() ? -0.55 : is_dmu() ? -0.45 : -0.2;
// HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors
if( is_emu()
&& mvControlling->EngineType == TEngineType::ElectricInductionMotor ) {
fAccThreshold += 0.10;
}
fNominalAccThreshold = fAccThreshold;
}
BrakeSystem = consist_brake_system();
// Change the EP fuse state only when AI is in charge, don't fiddle with controls when the user is controlling the vehicle
if (AIControllFlag) {
mvOccupied->EpFuseSwitch(BrakeSystem == TBrakeSystem::ElectroPneumatic);
}
if( OrderCurrentGet() & ( Obey_train | Bank ) ) {
// 4. Przeliczanie siły hamowania
double const velstep = mvOccupied->Vmax * 0.5 / BrakeAccTableSize;
d = pVehicles[0]; // pojazd na czele składu
while (d) {
for( int i = 0; i < BrakeAccTableSize; ++i ) {
fBrake_a0[ i + 1 ] += d->MoverParameters->BrakeForceR( 0.25, velstep*( 1 + 2 * i ) );
fBrake_a1[ i + 1 ] += d->MoverParameters->BrakeForceR( 1.00, velstep*( 1 + 2 * i ) );
}
d = d->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła)
}
for (int i = 0; i < BrakeAccTableSize; ++i)
{
fBrake_a1[i+1] -= fBrake_a0[i+1];
fBrake_a0[i+1] /= fMass;
fBrake_a0[i + 1] += 0.001*velstep*(1 + 2 * i);
fBrake_a1[i+1] /= 12 * fMass;
}
IsPassengerTrain = is_train() && false == is_emu() && false == is_dmu() && (mvOccupied->BrakeDelayFlag & bdelay_G) == 0;
IsCargoTrain = is_train() && (mvOccupied->BrakeDelayFlag & bdelay_G) != 0;
IsHeavyCargoTrain = true == IsCargoTrain && fBrake_a0[1] > 0.4 && iVehicles - ControlledEnginesCount > 0 && fMass / iVehicles > 50000;
BrakingInitialLevel = IsHeavyCargoTrain ? 1.25 : IsCargoTrain ? 1.25 : 1.00;
BrakingLevelIncrease = IsHeavyCargoTrain ? 0.25 : IsCargoTrain ? 0.25 : 0.25;
if( is_emu() ) {
auto ep_factor { ( BrakeSystem == TBrakeSystem::ElectroPneumatic ? 8 : 4 ) };
if( mvControlling->EngineType == TEngineType::ElectricInductionMotor ) {
// HACK: emu with induction motors need to start their braking a bit sooner than the ones with series motors
fNominalAccThreshold = std::max( -0.60, -fBrake_a0[ BrakeAccTableSize ] - ep_factor * fBrake_a1[ BrakeAccTableSize ] );
}
else {
fNominalAccThreshold = std::max( -0.75, -fBrake_a0[ BrakeAccTableSize ] - ep_factor * fBrake_a1[ BrakeAccTableSize ] );
}
fBrakeReaction = 0.25;
}
else if( is_dmu() ) {
fNominalAccThreshold = std::max( -0.75, -fBrake_a0[ BrakeAccTableSize ] - 8 * fBrake_a1[ BrakeAccTableSize ] );
fBrakeReaction = 0.25;
}
else if (ustaw > 16) {
fNominalAccThreshold = -fBrake_a0[ BrakeAccTableSize ] - 4 * fBrake_a1[ BrakeAccTableSize ];
fBrakeReaction = 1.00 + fLength*0.004;
}
else {
fNominalAccThreshold = -fBrake_a0[ BrakeAccTableSize ] - 1 * fBrake_a1[ BrakeAccTableSize ];
fBrakeReaction = 1.00 + fLength*0.005;
}
fAccThreshold = fNominalAccThreshold;
}
}
double TController::ESMVelocity(bool Main)
{
double fCurrentCoeff = 0.9;
double fFrictionCoeff = 0.85;
double ESMVel = 9999;
int MCPN = mvControlling->MainCtrlActualPos;
int SCPN = mvControlling->ScndCtrlActualPos;
if (Main)
MCPN += 1;
else
SCPN += 1;
if (mvControlling->RList[MCPN].ScndAct < 255 && mvControlling->ScndCtrlActualPos == 0)
SCPN = mvControlling->RList[MCPN].ScndAct;
double FrictionMax = mvControlling->Mass*9.81*mvControlling->Adhesive(mvControlling->RunningTrack.friction)*fFrictionCoeff;
double IF = mvControlling->Imax;
double MS = 0;
double Fmax = 0;
for (int i = 0; i < 5; i++)
{
MS = mvControlling->MomentumF(IF, IF, SCPN);
Fmax = MS * mvControlling->RList[MCPN].Bn * mvControlling->RList[MCPN].Mn * 2 / mvControlling->WheelDiameter * mvControlling->Transmision.Ratio;
if( Fmax != 0.0 ) {
IF = 0.5 * IF * ( 1 + FrictionMax / Fmax );
}
else {
// NOTE: gets trimmed to actual highest acceptable value after the loop
IF = std::numeric_limits<double>::max();
break;
}
}
IF = std::min(IF, mvControlling->Imax*fCurrentCoeff);
double R = mvControlling->RList[MCPN].R + mvControlling->CircuitRes + mvControlling->RList[MCPN].Mn*mvControlling->WindingRes;
double pole = mvControlling->MotorParam[SCPN].fi *
std::max(abs(IF) / (abs(IF) + mvControlling->MotorParam[SCPN].Isat) - mvControlling->MotorParam[SCPN].fi0, 0.0);
double Us = abs(mvControlling->EngineVoltage) - IF*R;
double ns = std::max(0.0, Us / (pole*mvControlling->RList[MCPN].Mn));
ESMVel = ns * mvControlling->WheelDiameter*M_PI*3.6/mvControlling->Transmision.Ratio;
return ESMVel;
}
int TController::CheckDirection() {
int d = mvOccupied->DirAbsolute; // który sprzęg jest z przodu
if( !d ) {
// jeśli nie ma ustalonego kierunku to jedziemy wg aktualnej kabiny
d = mvOccupied->CabActive;
}
return d;
}
bool TController::CheckVehicles(TOrders user)
{ // sprawdzenie stanu posiadanych pojazdów w składzie i zapalenie świateł
TDynamicObject *p; // roboczy wskaźnik na pojazd
iVehicles = 0; // ilość pojazdów w składzie
auto d = CheckDirection();
d = d >= 0 ? 0 : 1; // kierunek szukania czoła (numer sprzęgu)
p = pVehicle->FirstFind(d); // pojazd na czele składu
if (!p)
p = pVehicle;
pVehicles[end::front] = p;
// liczenie pojazdów w składzie i ustalenie parametrów
auto dir = d = 1 - d; // a dalej będziemy zliczać od czoła do tyłu
fLength = 0.0; // długość składu do badania wyjechania za ograniczenie
fMass = 0.0; // całkowita masa do liczenia stycznej składowej grawitacji
fVelMax = -1; // ustalenie prędkości dla składu
bool main = true; // czy jest głównym sterującym
iDrivigFlags |= moveOerlikons; // zakładamy, że są same Oerlikony
// Ra 2014-09: ustawić moveMultiControl, jeśli wszystkie są w ukrotnieniu (i skrajne mają kabinę?)
while (p)
{ // sprawdzanie, czy jest głównym sterującym, żeby nie było konfliktu
if( p->Mechanik // jeśli ma obsadę
&& p->Mechanik != this ) { // ale chodzi o inny pojazd, niż aktualnie sprawdzający
if( p->Mechanik->iDrivigFlags & movePrimary ) {
// a tamten ma priorytet
// TODO: take into account drivers' operating modes, one or more of them might be on banking duty
if( iDrivigFlags & movePrimary
&& mvOccupied->DirAbsolute
&& mvOccupied->BrakeCtrlPos >= -1 ) {
// jeśli rządzi i ma kierunek
p->Mechanik->primary( false ); // dezaktywuje tamtego
p->Mechanik->ZeroLocalBrake();
p->MoverParameters->BrakeLevelSet( p->MoverParameters->Handle->GetPos( bh_NP ) ); // odcięcie na zaworze maszynisty
p->Mechanik->BrakeLevelSet( p->MoverParameters->BrakeCtrlPos ); //ustawienie zmiennej GBH
}
else {
main = false; // nici z rządzenia
}
}
}
++iVehicles; // jest jeden pojazd więcej
pVehicles[end::rear] = p; // zapamiętanie ostatniego
fLength += p->MoverParameters->Dim.L; // dodanie długości pojazdu
fMass += p->MoverParameters->TotalMass; // dodanie masy łącznie z ładunkiem
fVelMax = min_speed( fVelMax, p->MoverParameters->Vmax ); // ustalenie maksymalnej prędkości dla składu
// reset oerlikon brakes consist flag as different type was detected
if( p->MoverParameters->BrakeSubsystem != TBrakeSubSystem::ss_ESt
&& p->MoverParameters->BrakeSubsystem != TBrakeSubSystem::ss_LSt ) {
iDrivigFlags &= ~moveOerlikons;
}
p = p->Neighbour(dir); // pojazd podłączony od wskazanej strony
}
if( main ) {
iDrivigFlags |= movePrimary; // nie znaleziono innego, można się porządzić
}
ControllingSet(); // ustalenie członu do sterowania (może być inny niż zasiedziany)
if (iDrivigFlags & movePrimary)
{ // jeśli jest aktywnie prowadzącym pojazd, może zrobić własny porządek
auto pantmask = 1;
p = pVehicles[end::front];
// establish ownership and vehicle order
while (p)
{
if (p->MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector)
{ // jeśli pojazd posiada pantograf, to przydzielamy mu maskę, którą będzie informował o jeździe bezprądowej
p->iOverheadMask = pantmask;
pantmask = pantmask << 1; // przesunięcie bitów, max. 32 pojazdy z pantografami w składzie
}
d = p->DirectionSet(d ? 1 : -1); // zwraca położenie następnego (1=zgodny,0=odwrócony - względem czoła składu)
p->ctOwner = this; // dominator oznacza swoje terytorium
p = p->Next(); // pojazd podłączony od tyłu (licząc od czoła)
}
// with the order established the virtual train manager can do their work
p = pVehicles[ end::front ];
ControlledEnginesCount = p->MoverParameters->Power > 1.0 ? 1 : 0;
auto hasheaters { false };
while (p)
{
if( p != pVehicle ) {
if( false == p->is_connected( pVehicle, coupling::control ) ) {
// NOTE: don't set battery in controllable vehicles, let the user/ai do it explicitly
// HACK: wagony muszą mieć baterię załączoną do otwarcia drzwi...
p->MoverParameters->BatterySwitch( true );
// enable heating and converter in carriages with can be heated
if( p->MoverParameters->HeatingPower > 0 ) {
p->MoverParameters->HeatingAllow = true;
p->MoverParameters->ConverterSwitch( true, range_t::local );
hasheaters = true;
}
}
else {
if( p->MoverParameters->Power > 1.0 ) {
++ControlledEnginesCount;
}
}
}
if( p->asDestination == "none" ) {
p->DestinationSet( TrainParams.Relation2, TrainParams.TrainName ); // relacja docelowa, jeśli nie było
}
if( AIControllFlag ) { // jeśli prowadzi komputer
p->RaLightsSet( 0, 0 ); // gasimy światła
}
p = p->Next(); // pojazd podłączony od tyłu (licząc od czoła)
}
if( user == Connect && true == main ) {
// HACK: with additional vehicles in the consist ensure all linked vehicles are set to move in the same direction
if( pVehicle->Prev(coupling::control) != nullptr
|| pVehicle->Next(coupling::control) != nullptr ) {
sync_consist_reversers();
}
// potentially sync compartment lighting state for the newly connected vehicles
if( mvOccupied->CompartmentLights.start_type == start_t::manual ) {
mvOccupied->CompartmentLightsSwitchOff( mvOccupied->CompartmentLights.is_disabled );
mvOccupied->CompartmentLightsSwitch( mvOccupied->CompartmentLights.is_enabled );
}
}
// kasowanie pamieci hamowania kontrolnego
if (OrderCurrentGet() & (Shunt | Loose_shunt | Disconnect | Connect | Change_direction)) {
DynamicBrakeTest = 0;
DBT_VelocityBrake
= DBT_VelocityRelease
= DBT_VelocityFinish
= DBT_BrakingTime
= DBT_ReleasingTime
= DBT_MidPointAcc
= 0;
}
if( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) {
// nastawianie hamulca do jazdy pociągowej
AutoRewident();
/*
if( ( true == TestFlag( iDrivigFlags, moveConnect ) )
&& ( true == TestFlag( OrderCurrentGet(), Connect ) ) ) {
iCoupler = 0; // dalsza jazda manewrowa już bez łączenia
iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania
SetVelocity( 0, 0, stopJoin ); // wyłączyć przyspieszanie
JumpToNextOrder(); // wykonanie następnej komendy
}
*/
}
// detect push-pull train configurations and mark them accordingly
if( pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::control ) ) {
// zmiana czoła przez zmianę kabiny
iDrivigFlags |= movePushPull;
}
else {
// zmiana czoła przez manewry
iDrivigFlags &= ~movePushPull;
}
// HACK: ensure vehicle lights are active from the beginning, if it had pre-activated battery
if( mvOccupied->LightsPosNo > 0 ) {
pVehicle->SetLights();
}
if( iEngineActive ) {
// potentially adjust light state
control_lights();
// custom ai action for disconnect mode: switch off lights on disconnected vehicle(s)
if( is_train() ) {
if( true == TestFlag( OrderCurrentGet(), Disconnect ) ) {
if( AIControllFlag ) {
// światła manewrowe (Tb1) tylko z przodu, aby nie pozostawić odczepionego ze światłem
if( mvOccupied->DirActive >= 0 ) { // jak ma kierunek do przodu
pVehicles[ end::rear ]->RaLightsSet( -1, 0 );
}
else { // jak dociska
pVehicles[ end::front ]->RaLightsSet( 0, -1 );
}
}
}
}
// enable door locks
cue_action( driver_hint::consistdoorlockson );
// potentially enable train heating
{
auto const ispassengertrain { IsPassengerTrain && iVehicles - ControlledEnginesCount > 0 };
// TODO: replace connection test with connection check between last engine and first car, specifically
auto const isheatingcouplingactive { (
ControlledEnginesCount == 1 ?
pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::heating ) :
true ) };
auto const isheatingneeded {
(is_emu() || is_dmu() ? true :
(OrderCurrentGet() & (Obey_train | Bank)) == 0 ? false :
false == isheatingcouplingactive ? false :
ispassengertrain ? hasheaters :
false) };
if( mvControlling->HeatingAllow != isheatingneeded ) {
cue_action(
isheatingneeded ?
driver_hint::consistheatingon :
driver_hint::consistheatingoff );
}
}
}
if( user == Connect
|| user == Disconnect ) {
// HACK: force route table update on consist change, new consist length means distances to points of interest are now wrong
iTableDirection = 0;
}
} // blok wykonywany, gdy aktywnie prowadzi
return true;
}
void TController::Lights(int head, int rear)
{ // zapalenie świateł w skłądzie
pVehicles[ end::front ]->RaLightsSet(head, -1); // zapalenie przednich w pierwszym
pVehicles[ end::rear ]->RaLightsSet(-1, rear); // zapalenie końcówek w ostatnim
}
void TController::DirectionInitial()
{ // ustawienie kierunku po wczytaniu trainset (może jechać na wstecznym
mvOccupied->CabActivisationAuto(); // załączenie rozrządu (wirtualne kabiny)
if (mvOccupied->Vel > EU07_AI_NOMOVEMENT)
{ // jeśli na starcie jedzie
iDirection = iDirectionOrder =
mvOccupied->V > 0 ? 1 : -1; // początkowa prędkość wymusza kierunek jazdy
DirectionForward(mvOccupied->V * mvOccupied->CabActive >= 0.0); // a dalej ustawienie nawrotnika
}
CheckVehicles(); // sprawdzenie świateł oraz skrajnych pojazdów do skanowania
};
void TController::DirectionChange() {
auto const initialstate { iDirection };
iDirection = CheckDirection();
if( iDirection != initialstate ) {
CheckVehicles( Change_direction );
}
}
TBrakeSystem TController::consist_brake_system() const {
if( mvOccupied->BrakeSystem != TBrakeSystem::ElectroPneumatic ) { return mvOccupied->BrakeSystem; }
auto isepcapable = true;
if( pVehicles[ end::front ] != pVehicles[ end::rear ] ) {
// more detailed version, will use manual braking also for coupled sets of controlled vehicles
auto *vehicle = pVehicles[ end::front ]; // start from first
while( true == isepcapable
&& vehicle != nullptr ) {
// NOTE: we could simplify this by doing only check of the rear coupler, but this can be quite tricky in itself
// TODO: add easier ways to access front/rear coupler taking into account vehicle's direction
isepcapable = (vehicle->MoverParameters->Couplers[end::front].Connected == nullptr ||
(vehicle->MoverParameters->Couplers[end::front].CouplingFlag & coupling::control && vehicle->MoverParameters->Couplers[end::front].Connected->Power > -1)) &&
(vehicle->MoverParameters->Couplers[end::rear].Connected == nullptr ||
(vehicle->MoverParameters->Couplers[end::rear].CouplingFlag & coupling::control && vehicle->MoverParameters->Couplers[end::rear].Connected->Power > -1));
vehicle = vehicle->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła)
}
}
return isepcapable ? TBrakeSystem::ElectroPneumatic : TBrakeSystem::Pneumatic;
}
int TController::OrderDirectionChange(int newdir, TMoverParameters *Vehicle)
{ // zmiana kierunku jazdy, niezależnie od kabiny
int testd = newdir;
if (Vehicle->Vel < 0.5)
{ // jeśli prawie stoi, można zmienić kierunek, musi być wykonane dwukrotnie, bo za pierwszym razem daje na zero
switch (newdir * Vehicle->CabActive)
{ // DirectionBackward() i DirectionForward() to zmiany względem kabiny
case -1: // if (!Vehicle->DirectionBackward()) testd=0; break;
DirectionForward(false);
break;
case 1: // if (!Vehicle->DirectionForward()) testd=0; break;
DirectionForward(true);
break;
}
if (testd == 0)
VelforDriver = -1; // kierunek został zmieniony na żądany, można jechać
}
else // jeśli jedzie
VelforDriver = 0; // ma się zatrzymać w celu zmiany kierunku
if (Vehicle->DirActive == 0 && VelforDriver < Vehicle->Vel) // Ra: to jest chyba bez sensu
IncBrake(); // niech hamuje
if (Vehicle->DirActive == testd * Vehicle->CabActive)
VelforDriver = -1; // można jechać, bo kierunek jest zgodny z żądanym
if (is_emu())
if (Vehicle->DirActive > 0)
// if () //tylko jeśli jazda pociągowa (tego nie wiemy w momencie odpalania silnika)
Vehicle->DirectionForward(); // Ra: z przekazaniem do silnikowego
return (int)VelforDriver; // zwraca prędkość mechanika
}
void TController::WaitingSet(double Seconds)
{ // ustawienie odczekania po zatrzymaniu (ustawienie w trakcie jazdy zatrzyma)
fStopTime = -Seconds; // ujemna wartość oznacza oczekiwanie (potem >=0.0)
}
void TController::SetVelocity(double NewVel, double NewVelNext, TStopReason r)
{ // ustawienie nowej prędkości
WaitingTime = -WaitingExpireTime; // przypisujemy -WaitingExpireTime, a potem porównujemy z zerem
if (NewVel == 0.0) { // jeśli ma stanąć
if (r != stopNone) // a jest powód podany
eStopReason = r; // to zapamiętać nowy powód
}
else {
eStopReason = stopNone; // podana prędkość, to nie ma powodów do stania
// to całe poniżej to warunki zatrąbienia przed ruszeniem
if( is_train() // tylko pociągi trąbią (unimogi tylko na torach, więc trzeba raczej sprawdzać tor)
&& (OrderCurrentGet() & (Shunt | Loose_shunt | Obey_train | Bank | Connect | Prepare_engine)) != 0 // jeśli jedzie w dowolnym trybie
&& (iDrivigFlags & moveStartHorn) != 0 // jezeli trąbienie włączone
&& (iDrivigFlags & (moveStartHornDone | moveConnect)) == 0 // jeśli nie zatrąbione i nie jest to moment podłączania składu
&& mvOccupied->Vel < 1.0 // jesli stoi (na razie, bo chyba powinien też, gdy hamuje przed semaforem)
&& ( NewVel >= 1.0 || NewVel < 0.0 ) ) { // o ile prędkość jest znacząca zatrąb po odhamowaniu
iDrivigFlags |= moveStartHornNow;
}
}
VelSignal = NewVel; // prędkość zezwolona na aktualnym odcinku
VelNext = NewVelNext; // prędkość przy następnym obiekcie
}
double TController::BrakeAccFactor() const
{
double Factor = 1.0;
if( fAccThreshold != 0.0
&& AccDesired < 0.0
&& ( ActualProximityDist > fMinProximityDist
|| mvOccupied->Vel > VelDesired + fVelPlus ) ) {
Factor += fBrakeReaction * (/*mvOccupied->BrakeCtrlPosR*/ BrakeCtrlPosition < 0.5 ? 1.5 : 1) * mvOccupied->Vel / ( std::max( 0.0, ActualProximityDist ) + 1 ) * ( ( AccDesired - AbsAccS ) / fAccThreshold );
}
/*
if (mvOccupied->TrainType == dt_DMU && mvOccupied->Vel > 40 && VelNext<40)
Factor *= 1 + 0.25 * ( (1600 - VelNext * VelNext) / (mvOccupied->Vel * mvOccupied->Vel) );
*/
return Factor;
}
void TController::SetDriverPsyche() {
if (Psyche == Aggressive && OrderCurrentGet() == Obey_train) {
ReactionTime = HardReactionTime; // w zaleznosci od charakteru maszynisty
if (is_car()) {
WaitingExpireTime = 1; // tyle ma czekać samochód, zanim się ruszy
AccPreferred = 3.0; //[m/ss] agresywny
}
else {
WaitingExpireTime = 61; // tyle ma czekać, zanim się ruszy
AccPreferred = HardAcceleration; // agresywny
}
}
else {
ReactionTime = EasyReactionTime; // spokojny
if (is_car()) {
WaitingExpireTime = 3; // tyle ma czekać samochód, zanim się ruszy
AccPreferred = 2.0; //[m/ss]
}
else {
WaitingExpireTime = 65; // tyle ma czekać, zanim się ruszy
AccPreferred = EasyAcceleration;
}
}
}
bool TController::PrepareEngine()
{ // odpalanie silnika
// HACK: don't immediately activate inert vehicle in case the simulation is about to replace us with human driver
if( mvOccupied->Vel < 1.0 && fActionTime < 0.0 ) { return false; }
LastReactionTime = 0.0;
ReactionTime = mvOccupied->Vel < 5 ? PrepareTime : EasyReactionTime; // react faster with rolling start
cue_action( driver_hint::batteryon );
cue_action( driver_hint::cabactivation);
cue_action( driver_hint::radioon );
if( has_diesel_engine() ) {
cue_action( driver_hint::oilpumpon );
PrepareHeating();
if( true == IsHeatingTemperatureOK ) {
cue_action( driver_hint::fuelpumpon );
}
else {
cue_action( driver_hint::waittemperaturetoolow );
}
}
if( mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector
&& mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 0 ) {
// if our pantograph unit isn't a pantograph-devoid fallback
auto const sufficientpantographpressure { (
is_emu() ?
2.5 : // Ra 2013-12: Niebugocław mówi, że w EZT podnoszą się przy 2.5
3.5 )
+ 0.1 }; // leeway margin
if (mvPantographUnit->PantPress < sufficientpantographpressure) {
// załączenie małej sprężarki
if( false == mvPantographUnit->PantAutoValve ) {
// odłączenie zbiornika głównego, bo z nim nie da rady napompować
cue_action( driver_hint::pantographairsourcesetauxiliary );
}
cue_action( driver_hint::pantographcompressoron ); // załączenie sprężarki pantografów
if( mvPantographUnit->PantCompFlag ) {
cue_action( driver_hint::waitpantographpressuretoolow );
}
}
else {
// jeżeli jest wystarczające ciśnienie w pantografach
if( false == mvPantographUnit->bPantKurek3
|| mvPantographUnit->PantPress <= mvPantographUnit->ScndPipePress ) { // kurek przełączony albo główna już pompuje
cue_action( driver_hint::pantographcompressoroff ); // sprężarkę pantografów można już wyłączyć
}
}
// TODO: make pantograph setup a part of control_pantographs() and call it here instead
if( fOverhead2 == -1.0 && iOverheadDown == 0 ) {
cue_action( driver_hint::pantographsvalveon );
cue_action( driver_hint::frontpantographvalveon, iDirection >= 0 ? 5 : 0 );
cue_action( driver_hint::rearpantographvalveon, iDirection >= 0 ? 0 : 5 );
}
}
auto const ispoweravailable =
mvControlling->EnginePowerSource.SourceType != TPowerSource::CurrentCollector
|| std::max(mvControlling->GetTrainsetHighVoltage(), mvControlling->PantographVoltage) > mvControlling->EnginePowerSource.CollectorParameters.MinV;
bool isready = false;
if( IsHeatingTemperatureOK
&& ispoweravailable ) {
// najpierw ustalamy kierunek, jeśli nie został ustalony
PrepareDirection();
if( IsAnyConverterOverloadRelayOpen ) {
// wywalił bezpiecznik nadmiarowy przetwornicy
cue_action( driver_hint::compressoroff ); // TODO: discern whether compressor needs converter to operate
cue_action( driver_hint::converteroff );
cue_action( driver_hint::primaryconverteroverloadreset ); // reset nadmiarowego
}
if( IsAnyLineBreakerOpen ) {
// activate main circuit or engine
cue_action( driver_hint::mastercontrollersetzerospeed );
// consist-wide ground relay reset
cue_action( driver_hint::maincircuitgroundreset );
cue_action( driver_hint::tractionnmotoroverloadreset );
if( mvOccupied->EngineType == TEngineType::DieselEngine ) {
// specjalnie dla SN61 żeby nie zgasł
cue_action( driver_hint::mastercontrollersetidle );
}
cue_action( driver_hint::linebreakerclose );
}
else {
// main circuit or engine is on, set up vehicle devices and controls
if( false == IsAnyConverterOverloadRelayOpen ) {
if( IsAnyConverterPresent ) {
cue_action( driver_hint::converteron );
}
// w EN57 sprężarka w ra jest zasilana z silnikowego
// TODO: change condition to presence of required voltage type
if( IsAnyCompressorPresent && IsAnyConverterEnabled ) {
cue_action( driver_hint::compressoron );
}
if( mvControlling->ScndPipePress < 4.5 && mvControlling->VeselVolume > 0.0 ) {
cue_action( driver_hint::waitpressuretoolow );
}
// enable motor blowers
if( mvOccupied->MotorBlowers[ end::front ].speed != 0 ) {
cue_action( driver_hint::frontmotorblowerson );
}
if( mvOccupied->MotorBlowers[ end::rear ].speed != 0 ) {
cue_action( driver_hint::rearmotorblowerson );
}
}
// sync virtual brake state with the 'real' one
if( mvOccupied->BrakeHandle != TBrakeHandle::NoHandle ) {
std::unordered_map<int, int> const brakepositions{
{ static_cast<int>( mvOccupied->Handle->GetPos( bh_RP ) ), gbh_RP },
{ static_cast<int>( mvOccupied->Handle->GetPos( bh_NP ) ), gbh_NP },
{ static_cast<int>( mvOccupied->Handle->GetPos( bh_FS ) ), gbh_FS } };
auto const lookup{ brakepositions.find( static_cast<int>( mvOccupied->fBrakeCtrlPos ) ) };
if( lookup != brakepositions.end() ) {
BrakeLevelSet( lookup->second ); // GBH
}
}
// set up train brake
if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_RP ) ) {
if ( !mvOccupied->Handle->Time
|| mvOccupied->Handle->GetCP() < mvOccupied->HighPipePress - 0.05 );
cue_action( driver_hint::trainbrakerelease );
}
// sync spring brake state across consist
cue_action(
mvOccupied->SpringBrake.Activate ?
driver_hint::springbrakeon :
driver_hint::springbrakeoff );
}
isready = false == IsAnyConverterOverloadRelayOpen
&& mvOccupied->DirActive != 0
&& false == IsAnyLineBreakerOpen
&& ( false == IsAnyConverterPresent || true == IsAnyConverterEnabled )
&& ( false == IsAnyCompressorPresent || true == IsAnyCompressorEnabled )
&& ( mvControlling->ScndPipePress > 4.5 || mvControlling->VeselVolume == 0.0 )
&& ( static_cast<int>(mvOccupied->fBrakeCtrlPos) == static_cast<int>(mvOccupied->Handle->GetPos(bh_RP))
|| static_cast<int>(mvOccupied->fBrakeCtrlPos) != static_cast<int>(mvOccupied->Handle->GetPos(bh_NP))
|| mvOccupied->BrakeHandle == TBrakeHandle::NoHandle );
}
if( true == isready ) {
// jeśli dotychczas spał teraz nie ma powodu do stania
eAction = TAction::actUnknown;
if( eStopReason == stopSleep ) {
eStopReason = stopNone;
}
// może skanować sygnały i reagować na komendy
iDrivigFlags |= moveActive;
}
iEngineActive = isready;
return iEngineActive;
}
// wyłączanie silnika (test wyłączenia, a część wykonawcza tylko jeśli steruje komputer)
bool TController::ReleaseEngine() {
/*
if( mvOccupied->Vel > 0.01 ) {
VelDesired = 0.0;
AccDesired = std::min( AccDesired, -1.25 ); // hamuj solidnie
if( true == AIControllFlag ) {
// TBD, TODO: make a dedicated braking procedure out of it for potential reuse
ZeroSpeed();
IncBrake();
ReactionTime = 0.1;
}
// don't bother with the rest until we're standing still
return false;
}
*/
// don't bother with the rest until we're standing still
if( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) { return false; }
LastReactionTime = 0.0;
ReactionTime = PrepareTime;
cue_action( driver_hint::releaseroff );
// release train brake if on flats...
if( std::abs( fAccGravity ) < 0.01 ) {
apply_independent_brake_only();
}
// ... but on slopes engage train brake
else {
// AccDesired = std::min( AccDesired, -0.9 );
cue_action( driver_hint::trainbrakeapply );
}
cue_action( driver_hint::mastercontrollersetreverserunlock );
cue_action( driver_hint::directionnone );
// zamykanie drzwi
cue_action( driver_hint::doorrightclose );
cue_action( driver_hint::doorleftclose );
// heating
cue_action( driver_hint::consistheatingoff );
// devices
cue_action( driver_hint::compressoroff );
cue_action( driver_hint::converteroff );
// line breaker/engine
cue_action( driver_hint::linebreakeropen );
// pantographs
cue_action( driver_hint::frontpantographvalveoff );
cue_action( driver_hint::rearpantographvalveoff );
if( false == mvControlling->Mains ) {
// finish vehicle shutdown
if( has_diesel_engine() ) {
// heating/cooling subsystem
cue_action( driver_hint::waterheateroff );
// optionally turn off the water pump as well
if( mvControlling->WaterPump.start_type != start_t::battery ) {
cue_action( driver_hint::waterpumpoff );
}
// fuel and oil subsystems
cue_action( driver_hint::fuelpumpoff );
cue_action( driver_hint::oilpumpoff );
}
// gasimy światła
cue_action( driver_hint::lightsoff );
// activate parking brake
// TBD: do it earlier?
cue_action( driver_hint::springbrakeon );
if( mvOccupied->LocalBrake == TLocalBrake::ManualBrake
|| mvOccupied->MBrake == true ) {
cue_action( driver_hint::manualbrakon );
}
// switch off remaining power
cue_action( driver_hint::radiooff );
cue_action( driver_hint::batteryoff );
}
auto const OK {
mvOccupied->DirActive == 0
// && ( false == IsAnyCompressorEnabled )
// && ( false == IsAnyConverterEnabled )
&& false == mvControlling->Mains
&& false == mvOccupied->Power24vIsAvailable };
if (OK) {
// jeśli się zatrzymał
iEngineActive = false;
eStopReason = stopSleep; // stoimy z powodu wyłączenia
eAction = TAction::actSleep; //śpi (wygaszony)
OrderNext(Wait_for_orders); //żeby nie próbował coś robić dalej
iDrivigFlags &= ~moveActive; // ma nie skanować sygnałów i nie reagować na komendy
TableClear(); // zapominamy ograniczenia
VelSignalLast = -1.0;
}
return OK;
}
bool TController::IncBrake()
{ // zwiększenie hamowania
auto OK { false };
auto const bs {
BrakeSystem == TBrakeSystem::ElectroPneumatic && ForcePNBrake ?
TBrakeSystem::Pneumatic :
BrakeSystem };
switch( bs ) {
case TBrakeSystem::Individual: {
if( mvOccupied->LocalBrake == TLocalBrake::ManualBrake ) {
OK = mvOccupied->IncManualBrakeLevel( 1 + static_cast<int>( std::floor( 0.5 + std::fabs( AccDesired ) ) ) );
}
else {
OK = mvOccupied->IncLocalBrakeLevel( std::floor( 1.5 + std::abs( AccDesired ) ) );
}
break;
}
case TBrakeSystem::Pneumatic: {
if (bs != mvOccupied->BrakeSystem)
{
while (mvOccupied->BrakeOpModeFlag > bom_PN)
{
mvOccupied->BrakeOpModeFlag >>= 1;
}
}
// NOTE: can't perform just test whether connected vehicle == nullptr, due to virtual couplers formed with nearby vehicles
auto standalone { true };
if( mvOccupied->TrainType == dt_ET41
|| mvOccupied->TrainType == dt_ET42 ) {
// NOTE: we're doing simplified checks full of presuptions here.
// they'll break if someone does strange thing like turning around the second unit
if( mvOccupied->Couplers[end::rear].CouplingFlag & coupling::permanent
&& mvOccupied->Couplers[end::rear].Connected->Couplers[end::rear].Connected != nullptr ) {
standalone = false;
}
if( mvOccupied->Couplers[end::front].CouplingFlag & coupling::permanent
&& mvOccupied->Couplers[end::front].Connected->Couplers[end::front].Connected != nullptr ) {
standalone = false;
}
}
else if( is_dmu() ) {
// enforce use of train brake for DMUs
standalone = false;
}
else if( (OrderCurrentGet() & Loose_shunt) != 0
&& mvOccupied->Vel - VelDesired < fVelPlus + fVelMinus ) {
// try to reduce train brake use in loose shunting mode
standalone = true;
}
else {
/*
standalone =
( ( mvOccupied->Couplers[ 0 ].CouplingFlag == 0 )
&& ( mvOccupied->Couplers[ 1 ].CouplingFlag == 0 ) );
*/
if( pVehicles[ end::front ] != pVehicles[ end::rear ] ) {
// more detailed version, will use manual braking also for coupled sets of controlled vehicles
auto *vehicle = pVehicles[ end::front ]; // start from first
while( true == standalone
&& vehicle != nullptr ) {
// NOTE: we could simplify this by doing only check of the rear coupler, but this can be quite tricky in itself
// TODO: add easier ways to access front/rear coupler taking into account vehicle's direction
standalone =
(vehicle->MoverParameters->Couplers[end::front].Connected == nullptr ||
(vehicle->MoverParameters->Couplers[end::front].CouplingFlag & coupling::control && vehicle->MoverParameters->Couplers[end::front].Connected->Power > 1)) &&
(vehicle->MoverParameters->Couplers[end::rear].Connected == nullptr ||
(vehicle->MoverParameters->Couplers[end::rear].CouplingFlag & coupling::control && vehicle->MoverParameters->Couplers[end::rear].Connected->Power > 1));
vehicle = vehicle->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła)
}
}
}
//standalone = standalone && ( mvControlling->EIMCtrlType == 0 );
if( true == standalone && false == ForcePNBrake ) {
if( mvControlling->EIMCtrlType > 0 ) {
OK = IncBrakeEIM();
}
else {
OK = mvOccupied->IncLocalBrakeLevel(
1 + static_cast<int>( std::floor( 0.5 + std::fabs( AccDesired ) ) ) ); // hamowanie lokalnym bo luzem jedzie
}
}
else {
if( /*GBH mvOccupied->BrakeCtrlPos*/ BrakeCtrlPosition + 1 == gbh_MAX ) {
if (AccDesired < -1.5) // hamowanie nagle
OK = /*mvOccupied->*/BrakeLevelAdd(1.0); //GBH
else
OK = false;
}
else {
// dodane dla towarowego
float pos_corr = 0;
TDynamicObject *d;
d = pVehicles[0]; // pojazd na czele składu
while (d)
{ // przeliczanie dodatkowego potrzebnego spadku ciśnienia
if( ( d->MoverParameters->Hamulec->GetBrakeStatus() & b_dmg ) == 0 ) {
pos_corr -= ( std::min( 5.0, d->MoverParameters->Hamulec->GetCRP() ) - 5.0 ) * d->MoverParameters->TotalMass;
}
d = d->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła)
}
pos_corr = pos_corr / fMass * 2.5;
if (mvOccupied->BrakeHandle == TBrakeHandle::FV4a)
{
pos_corr += mvOccupied->Handle->GetCP()*0.2;
}
double deltaAcc = -AccDesired*BrakeAccFactor() - (fBrake_a0[0] + 4.0 * (/*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition - 1 - pos_corr)*fBrake_a1[0]);
if( deltaAcc > fBrake_a1[0])
{
if( /*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition < 0.1 ) {
OK = /*mvOccupied->*/BrakeLevelAdd( BrakingInitialLevel ); //GBH
// HACK: stronger braking to overcome SA134 engine behaviour
if( is_dmu()
&& VelNext == 0.0
&& fBrakeDist < 200.0 ) {
BrakeLevelAdd(
fBrakeDist / ActualProximityDist < 0.8 ?
0.5 :
1.0 );
}
}
else {
OK = /*mvOccupied->*/BrakeLevelAdd( BrakingLevelIncrease ); //GBH
// brake harder if the acceleration is much higher than desired
/*if( ( deltaAcc > 2 * fBrake_a1[ 0 ] )
&& ( mvOccupied->BrakeCtrlPosR + BrakingLevelIncrease <= 5.0 ) ) {
mvOccupied->BrakeLevelAdd( BrakingLevelIncrease );
} GBH */
if( deltaAcc > 2 * fBrake_a1[0]
&& BrakeCtrlPosition + BrakingLevelIncrease <= 5.0 ) {
/*mvOccupied->*/BrakeLevelAdd( BrakingLevelIncrease );
}
}
}
else
OK = false;
}
}
if( /*GBH mvOccupied->BrakeCtrlPos*/BrakeCtrlPosition > 0 ) {
if( mvOccupied->Hamulec->Releaser() ) {
mvOccupied->BrakeReleaser( 0 );
}
}
break;
}
case TBrakeSystem::ElectroPneumatic: {
while( mvOccupied->BrakeOpModeFlag << 1 <= mvOccupied->BrakeOpModes ) {
mvOccupied->BrakeOpModeFlag <<= 1;
}
if( mvOccupied->EngineType == TEngineType::ElectricInductionMotor ) {
if (mvOccupied->BrakeHandle == TBrakeHandle::MHZ_EN57) {
if (mvOccupied->BrakeCtrlPos < mvOccupied->Handle->GetPos(bh_FB))
OK = mvOccupied->BrakeLevelAdd(1.0);
}
else {
OK = IncBrakeEIM();
}
}
else if( mvOccupied->Handle->TimeEP == false ) {
auto const initialbrakeposition { mvOccupied->fBrakeCtrlPos };
auto const AccMax { std::min( fBrake_a0[ 0 ] + 12 * fBrake_a1[ 0 ], mvOccupied->MED_amax ) };
mvOccupied->BrakeLevelSet(
std::lerp(
mvOccupied->Handle->GetPos( bh_EPR ),
mvOccupied->Handle->GetPos( bh_EPB ),
std::clamp( -AccDesired / AccMax * mvOccupied->AIHintLocalBrakeAccFactor, 0.0, 1.0 ) ) );
OK = mvOccupied->fBrakeCtrlPos != initialbrakeposition;
}
else if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_EPB ) ) {
mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_EPB ) );
if( mvOccupied->Handle->GetPos( bh_EPR ) - mvOccupied->Handle->GetPos( bh_EPN ) < 0.1 )
mvOccupied->SwitchEPBrake( 1 ); // to nie chce działać
OK = true;
}
else
OK = false;
break;
}
default: { break; }
}
return OK;
}
bool TController::IncBrakeEIM()
{ // zwiększenie hamowania
bool OK = false;
switch (mvOccupied->EIMCtrlType)
{
case 0: {
if( mvOccupied->MED_amax != 9.81 ) {
auto const maxpos{mvOccupied->EIMCtrlEmergency ? 0.9 : 1.0 };
auto const brakelimit{ -2.2 * AccDesired / fMedAmax - 1.0}; //additional limit when hinted is too low
auto const brakehinted{ -1.0 * mvOccupied->AIHintLocalBrakeAccFactor * AccDesired / fMedAmax }; //preffered by AI
auto const brakeposition{ maxpos * std::clamp(std::max(brakelimit, brakehinted), 0.0, 1.0)};
OK = brakeposition != mvOccupied->LocalBrakePosA;
mvOccupied->LocalBrakePosA = brakeposition;
}
else {
OK = mvOccupied->IncLocalBrakeLevel( 1 );
}
break;
}
case 1: {
OK = mvOccupied->MainCtrlPos > 0;
if( OK )
mvOccupied->MainCtrlPos = 0;
break;
}
case 2: {
OK = mvOccupied->MainCtrlPos > 1;
if( OK )
mvOccupied->MainCtrlPos = 1;
break;
}
case 3: {
if( mvOccupied->UniCtrlIntegratedLocalBrakeCtrl ) {
OK = mvOccupied->MainCtrlPos > 0;
if( OK )
mvOccupied->MainCtrlPos = 0;
}
else {
OK = mvOccupied->IncLocalBrakeLevel( 1 );
}
break;
}
default: {
break;
}
}
return OK;
}
// zmniejszenie siły hamowania
bool TController::DecBrake() {
auto OK { false };
auto const bs {
BrakeSystem == TBrakeSystem::ElectroPneumatic && ForcePNBrake ?
TBrakeSystem::Pneumatic :
BrakeSystem };
switch( bs ) {
case TBrakeSystem::Individual: {
auto const positionchange { 1 + std::floor( 0.5 + std::abs( AccDesired ) ) };
OK = mvOccupied->LocalBrake == TLocalBrake::ManualBrake ? mvOccupied->DecManualBrakeLevel(positionchange) : mvOccupied->DecLocalBrakeLevel(positionchange);
break;
}
case TBrakeSystem::Pneumatic: {
auto deltaAcc { -1.0 };
if( fBrake_a0[0] != 0.0
|| fBrake_a1[0] != 0.0 ) {
auto const pos_diff { ( is_dmu() ? 0.25 : 1.0 ) };
deltaAcc = -AccDesired * BrakeAccFactor() - ( fBrake_a0[ 0 ] + 4 * (/*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition - pos_diff )*fBrake_a1[ 0 ] );
}
if (deltaAcc < 0) {
if(/*GBH mvOccupied->BrakeCtrlPosR*/BrakeCtrlPosition > 0 ) {
OK = /*mvOccupied->*/BrakeLevelAdd( -0.25 );
//if ((deltaAcc < 5 * fBrake_a1[0]) && (mvOccupied->BrakeCtrlPosR >= 1.2))
// mvOccupied->BrakeLevelAdd(-1.0);
/* if (mvOccupied->BrakeCtrlPosR < 0.74) GBH */
if( BrakeCtrlPosition < 0.74 )
/*mvOccupied->*/BrakeLevelSet( gbh_RP );
}
}
if( !OK ) {
OK = mvOccupied->DecLocalBrakeLevel(2);
}
if( !OK ) {
OK = DecBrakeEIM();
}
/*
// NOTE: disabled, duplicate of AI's behaviour in UpdateSituation()
if (mvOccupied->PipePress < 3.0)
Need_BrakeRelease = true;
*/
break;
}
case TBrakeSystem::ElectroPneumatic: {
if( mvOccupied->EngineType == TEngineType::ElectricInductionMotor ) {
if( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_EN57 ) {
if( mvOccupied->BrakeCtrlPos > mvOccupied->Handle->GetPos( bh_RP ) ) {
OK = mvOccupied->BrakeLevelAdd( -1.0 );
}
}
else {
OK = DecBrakeEIM();
}
}
else if( mvOccupied->Handle->TimeEP == false ) {
auto const initialbrakeposition { mvOccupied->fBrakeCtrlPos };
auto const AccMax { std::min( fBrake_a0[ 0 ] + 12 * fBrake_a1[ 0 ], mvOccupied->MED_amax ) };
mvOccupied->BrakeLevelSet(
std::lerp(
mvOccupied->Handle->GetPos( bh_EPR ),
mvOccupied->Handle->GetPos( bh_EPB ),
std::clamp( -AccDesired / AccMax * mvOccupied->AIHintLocalBrakeAccFactor, 0.0, 1.0 ) ) );
OK = mvOccupied->fBrakeCtrlPos != initialbrakeposition;
}
else {
OK = false;
if (mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos(bh_EPR)) {
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPR));
OK = true;
}
if (mvOccupied->Handle->GetPos(bh_EPR) - mvOccupied->Handle->GetPos(bh_EPN) < 0.1) {
OK = OK || mvOccupied->SwitchEPBrake(1);
}
}
if( !OK ) {
OK = mvOccupied->DecLocalBrakeLevel( 2 );
}
break;
}
default: {
break;
}
}
return OK;
}
void TController::LapBrake() {
if( mvOccupied->Handle->TimeEP ) {
if( mvOccupied->Handle->GetPos( bh_EPR ) - mvOccupied->Handle->GetPos( bh_EPN ) < 0.1 ) {
mvOccupied->SwitchEPBrake( 0 );
}
else {
mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_EPN ) );
}
}
}
void TController::ZeroLocalBrake() {
if( mvOccupied->UniCtrlIntegratedLocalBrakeCtrl ) {
while( DecBrakeEIM() ) {
;
}
}
else {
mvOccupied->DecLocalBrakeLevel( LocalBrakePosNo );
}
}
bool TController::DecBrakeEIM()
{ // zmniejszenie siły hamowania
bool OK = false;
switch (mvOccupied->EIMCtrlType)
{
case 0: {
if( mvOccupied->MED_amax != 9.81 ) {
auto const desiredacceleration { ( mvOccupied->Vel > EU07_AI_NOMOVEMENT ? AccDesired : std::max( 0.0, AccDesired ) ) };
auto const brakeposition { std::clamp( -1.0 * mvOccupied->AIHintLocalBrakeAccFactor * desiredacceleration / mvOccupied->MED_amax, 0.0, 1.0 ) };
OK = brakeposition != mvOccupied->LocalBrakePosA;
mvOccupied->LocalBrakePosA = brakeposition;
}
else {
OK = mvOccupied->DecLocalBrakeLevel( 1 );
}
break;
}
case 1: {
OK = mvOccupied->MainCtrlPos < 2;
if( OK )
mvOccupied->MainCtrlPos = 2;
break;
}
case 2: {
OK = mvOccupied->MainCtrlPos < 3;
if( OK )
mvOccupied->MainCtrlPos = 3;
break;
}
case 3: {
OK = mvOccupied->MainCtrlPos < mvOccupied->MainCtrlMaxDirChangePos;
if( OK )
mvOccupied->MainCtrlPos = mvOccupied->MainCtrlMaxDirChangePos;
break;
}
}
return OK;
}
bool TController::IncSpeed()
{ // zwiększenie prędkości; zwraca false, jeśli dalej się nie da zwiększać
auto OK { false };
switch (mvOccupied->EngineType)
{
case TEngineType::None: // McZapkie-041003: wagon sterowniczy
if (mvControlling->MainCtrlPosNo > 0) // jeśli ma czym kręcić
iDrivigFlags |= moveIncSpeed; // ustawienie flagi jazdy
return false;
case TEngineType::ElectricSeriesMotor:
if (mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) // jeśli pantografujący
{
if (fOverhead2 >= 0.0) // a jazda bezprądowa ustawiana eventami (albo opuszczenie)
return false; // to nici z ruszania
if (iOverheadZero) // jazda bezprądowa z poziomu toru ustawia bity
return false; // to nici z ruszania
}
// TODO: move all fVoltage assignments to a single, more generic place
if( mvControlling->EnginePowerSource.SourceType == TPowerSource::Accumulator ) {
fVoltage = mvControlling->BatteryVoltage;
}
if (!IsAnyMotorOverloadRelayOpen
&& !mvControlling->ControlPressureSwitch) {
if (mvControlling->IsMainCtrlNoPowerPos()
|| mvControlling->StLinFlag) { // youBy polecił dodać 2012-09-08 v367
// na pozycji 0 przejdzie, a na pozostałych będzie czekać, aż się załączą liniowe (zgaśnie DelayCtrlFlag)
if (Ready || iDrivigFlags & movePress) {
auto const usehighoverloadrelaythreshold {
mvControlling->TrainType != dt_ET42 // ET42 uses these variables for different purpose. TODO: fix this
&& mvControlling->ImaxHi > mvControlling->ImaxLo
&& mvOccupied->Vel < (mvControlling->IsMotorOverloadRelayHighThresholdOn() ? 30.0 : 20.0)
&& iVehicles - ControlledEnginesCount > 0
// && ( std::fabs( mvControlling->Im ) > 0.85 * mvControlling->Imax )
&& mvControlling->Imax * mvControlling->EngineVoltage * ControlledEnginesCount /
(fMass * (fAccGravity == 0.025 ? -0.01 : // prevent div/0
fAccGravity - 0.025)) <
-2.8 };
// use series mode:
// if high threshold is set for motor overload relay,
// if the power station is heavily burdened,
// if it generates enough traction force
// to build up speed to 30/40 km/h for passenger/cargo train (10 km/h less if going uphill)
auto const sufficienttractionforce { std::abs( mvControlling->Ft ) * ControlledEnginesCount > ( IsHeavyCargoTrain ? 75 : 50 ) * 1000.0 };
auto const sufficientacceleration { AbsAccS >= ( IsHeavyCargoTrain ? 0.03 : IsCargoTrain ? 0.06 : 0.09 ) };
auto const seriesmodefieldshunting { mvControlling->ScndCtrlPos > 0 && mvControlling->RList[mvControlling->MainCtrlPos].Bn == 1 };
auto const parallelmodefieldshunting { mvControlling->ScndCtrlPos > 0 && mvControlling->RList[mvControlling->MainCtrlPos].Bn > 1 };
auto const minvoltage { ( mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ? mvPantographUnit->EnginePowerSource.CollectorParameters.MinV : 0.0 ) };
auto const maxvoltage { ( mvPantographUnit->EnginePowerSource.SourceType == TPowerSource::CurrentCollector ? mvPantographUnit->EnginePowerSource.CollectorParameters.MaxV : 0.0 ) };
auto const seriesmodevoltage {
std::lerp(
minvoltage,
maxvoltage,
IsHeavyCargoTrain ? 0.35 : 0.40 ) };
auto const useseriesmode = mvControlling->Imax > mvControlling->ImaxLo || true == usehighoverloadrelaythreshold || fVoltage < seriesmodevoltage ||
(true == sufficientacceleration && true == sufficienttractionforce &&
mvOccupied->Vel <= (IsCargoTrain ? 40 : 30) + (seriesmodefieldshunting ? 5 : 0) - (fAccGravity < -0.025 ? 10 : 0));
// when not in series mode use the first available parallel mode configuration until 50/60 km/h for passenger/cargo train
// (if there's only one parallel mode configuration it'll be used regardless of current speed)
auto const usefieldshunting = mvControlling->StLinFlag && mvControlling->RList[mvControlling->MainCtrlPos].R < 0.01 &&
(useseriesmode ? mvControlling->RList[mvControlling->MainCtrlPos].Bn == 1 :
true == sufficientacceleration && true == sufficienttractionforce && mvOccupied->Vel <= (IsCargoTrain ? 60 : 50) + (parallelmodefieldshunting ? 5 : 0) ?
mvControlling->RList[mvControlling->MainCtrlPos].Bn > 1 :
mvControlling->MainCtrlPos == mvControlling->MainCtrlPosNo);
// if needed enable high threshold for overload relay...
if( mvControlling->TrainType != dt_ET42 ) { // ET42 uses these variables for different purpose. TODO: fix this
if( usehighoverloadrelaythreshold ) {
if( mvControlling->Imax < mvControlling->ImaxHi ) {
// to enable this setting we'll typically need the main controller in series mode (which is not guaranteed)
if( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) {
if( false == mvControlling->IsScndCtrlNoPowerPos() ) {
mvControlling->DecScndCtrl( 2 );
}
while( false == mvControlling->IsMainCtrlNoPowerPos()
&& mvControlling->RList[mvControlling->MainCtrlPos].Bn > 1 ) {
mvControlling->DecMainCtrl( 1 ); // kręcimy nastawnik jazdy o 1 wstecz
}
}
mvControlling->CurrentSwitch( true ); // rozruch wysoki (za to może się ślizgać)
}
}
// ...or disable high threshold for overload relay if no longer needed
else {
if( mvControlling->IsMotorOverloadRelayHighThresholdOn()
&& std::fabs(mvControlling->Im) < mvControlling->ImaxLo ) {
mvControlling->CurrentSwitch( false ); // rozruch wysoki wyłącz
}
}
}
double Vs = 99999;
if( usefieldshunting ?
mvControlling->ScndCtrlPos < mvControlling->ScndCtrlPosNo :
mvControlling->MainCtrlPos < mvControlling->MainCtrlPosNo ) {
Vs = ESMVelocity( !usefieldshunting );
}
if( std::abs(mvControlling->Im) < (fReady < 0.4 ? mvControlling->Imin : mvControlling->IminLo)
|| mvControlling->Vel > Vs ) {
// Ra: wywalał nadmiarowy, bo Im może być ujemne; jak nie odhamowany, to nie przesadzać z prądem
if( usefieldshunting ) {
// to dać bocznik
// engage the shuntfield only if there's sufficient power margin to draw from
auto const sufficientpowermargin { fVoltage - seriesmodevoltage > ( IsHeavyCargoTrain ? 100.0 : 75.0 ) * ControlledEnginesCount };
OK = sufficientpowermargin ? mvControlling->IncScndCtrl(1) : true;
}
else {
// jeśli ustawiony bocznik to bocznik na zero po chamsku
if( mvControlling->ScndCtrlPos ) {
mvControlling->DecScndCtrl( 2 );
}
// kręcimy nastawnik jazdy
// don't draw too much power;
// keep from dropping into series mode when entering/using parallel mode, and from shutting down in the series mode
auto const sufficientpowermargin {
fVoltage - (
mvControlling->RList[ std::min( mvControlling->MainCtrlPos + 1, mvControlling->MainCtrlPosNo ) ].Bn == 1 ?
minvoltage :
seriesmodevoltage )
> ( IsHeavyCargoTrain ? 100.0 : 75.0 ) * ControlledEnginesCount };
OK = sufficientpowermargin && false == mvControlling->DelayCtrlFlag ? mvControlling->IncMainCtrl(1) : true;
// czekaj na 1 pozycji, zanim się nie włączą liniowe
if( true == mvControlling->StLinFlag ) {
iDrivigFlags |= moveIncSpeed;
}
else {
iDrivigFlags &= ~moveIncSpeed;
}
if( mvControlling->Im == 0
&& mvControlling->MainCtrlPowerPos() > 1 ) {
// brak prądu na dalszych pozycjach
// nie załączona lokomotywa albo wywalił nadmiarowy
Need_TryAgain = true;
}
}
}
else {
OK = false;
}
}
}
}
// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania
break;
case TEngineType::Dumb:
if (!IsAnyMotorOverloadRelayOpen)
if (Ready || iDrivigFlags & movePress) //{(BrakePress<=0.01*MaxBrakePress)}
{
OK = IncSpeedEIM();
if( !OK )
OK = mvControlling->IncMainCtrl(1);
if (!OK)
OK = mvControlling->IncScndCtrl(1);
}
break;
case TEngineType::DieselElectric:
if (!IsAnyMotorOverloadRelayOpen
&& !mvControlling->ControlPressureSwitch) {
if (mvControlling->IsMainCtrlNoPowerPos() ||
mvControlling->StLinFlag) { // youBy polecił dodać 2012-09-08 v367
// na pozycji 0 przejdzie, a na pozostałych będzie czekać, aż się załączą liniowe (zgaśnie DelayCtrlFlag)
if (Ready || iDrivigFlags & movePress) //{(BrakePress<=0.01*MaxBrakePress)}
{
OK = IncSpeedEIM();
if( !OK )
OK = mvControlling->IncMainCtrl(1);
if (!OK)
OK = mvControlling->IncScndCtrl(1);
}
}
}
break;
case TEngineType::ElectricInductionMotor:
if( !IsAnyMotorOverloadRelayOpen ) {
if( Ready || iDrivigFlags & movePress || mvOccupied->ShuntMode ) //{(BrakePress<=0.01*MaxBrakePress)}
{
OK = IncSpeedEIM();
if( mvControlling->SpeedCtrl && mvControlling->Mains ) {
// cruise control
auto const couplinginprogress{ true == TestFlag(iDrivigFlags, moveConnect) && true == TestFlag(OrderCurrentGet(), Connect) };
auto const SpeedCntrlVel{ (
ActualProximityDist > std::max(50.0, fMaxProximityDist) || couplinginprogress ?
VelDesired :
min_speed( VelDesired, VelNext ) ) };
if( SpeedCntrlVel >= mvControlling->SpeedCtrlUnit.MinVelocity
&& SpeedCntrlVel - mvControlling->SpeedCtrlUnit.MinVelocity == quantize(SpeedCntrlVel - mvControlling->SpeedCtrlUnit.MinVelocity, mvControlling->SpeedCtrlUnit.VelocityStep) ) {
SpeedCntrl( SpeedCntrlVel );
}
else if( SpeedCntrlVel > 0.1 ) {
SpeedCntrl( 0.0 );
mvControlling->DecScndCtrl( 2 );
}
}
}
}
break;
case TEngineType::WheelsDriven:
if (!mvControlling->CabActive)
mvControlling->CabActivisation();
if (sin(mvControlling->eAngle) > 0)
mvControlling->IncMainCtrl(3 + 3 * floor(0.5 + fabs(AccDesired)));
else
mvControlling->DecMainCtrl(3 + 3 * floor(0.5 + fabs(AccDesired)));
break;
case TEngineType::DieselEngine:
if (mvControlling->ShuntModeAllow)
{ // dla 2Ls150 można zmienić tryb pracy, jeśli jest w liniowym i nie daje rady (wymaga zerowania kierunku)
// mvControlling->ShuntMode=(OrderList[OrderPos]&Shunt)||(fMass>224000.0);
}
if (mvControlling->SpeedCtrl && mvControlling->Mains) {// cruise control
auto const couplinginprogress { true == TestFlag(iDrivigFlags, moveConnect) && true == TestFlag(OrderCurrentGet(), Connect) };
auto const SpeedCntrlVel { (
ActualProximityDist > std::max(50.0, fMaxProximityDist) || couplinginprogress ?
VelDesired :
min_speed( VelDesired, VelNext ) ) };
if( SpeedCntrlVel >= mvControlling->SpeedCtrlUnit.MinVelocity
&& SpeedCntrlVel - mvControlling->SpeedCtrlUnit.MinVelocity == quantize(SpeedCntrlVel - mvControlling->SpeedCtrlUnit.MinVelocity, mvControlling->SpeedCtrlUnit.VelocityStep) ) {
SpeedCntrl(SpeedCntrlVel);
}
else if (SpeedCntrlVel > 0.1) {
SpeedCntrl(0.0);
mvControlling->DecScndCtrl( 2 );
}
}
if (mvControlling->EIMCtrlType > 0) {
if (true == Ready)
{
bool max = mvControlling->Vel > mvControlling->dizel_minVelfullengage
|| (mvControlling->SpeedCtrl && mvControlling->ScndCtrlPos > 0);
DizelPercentage = max ? 100 : 1;
}
break;
}
else {
if( true == Ready ) {
if( mvControlling->Vel > mvControlling->dizel_minVelfullengage
&& mvControlling->RList[mvControlling->MainCtrlPos].Mn > 0 ) {
OK = mvControlling->IncMainCtrl( 1 );
}
if( mvControlling->RList[ mvControlling->MainCtrlPos ].Mn == 0 ) {
OK = mvControlling->IncMainCtrl( 1 );
}
}
}
// TODO: move this check to a more suitable place, or scrap it
if( false == mvControlling->Mains ) {
SpeedCntrl(0.0);
mvControlling->DecScndCtrl( 2 );
cue_action( driver_hint::linebreakerclose );
cue_action( driver_hint::converteron );
cue_action( driver_hint::compressoron );
}
break;
}
return OK;
}
void TController::ZeroSpeed( bool const Enforce ) {
while( DecSpeed( Enforce ) ) {
;
}
}
bool TController::DecSpeed(bool force)
{ // zmniejszenie prędkości (ale nie hamowanie)
bool OK = false; // domyślnie false, aby wyszło z pętli while
switch (mvOccupied->EngineType)
{
case TEngineType::None: // McZapkie-041003: wagon sterowniczy
iDrivigFlags &= ~moveIncSpeed; // usunięcie flagi jazdy
if (force) // przy aktywacji kabiny jest potrzeba natychmiastowego wyzerowania
if (mvControlling->MainCtrlPosNo > 0) // McZapkie-041003: wagon sterowniczy, np. EZT
mvControlling->DecMainCtrl( std::min( mvControlling->MainCtrlPowerPos(), 2 ) );
// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania
return false;
case TEngineType::ElectricSeriesMotor:
OK = mvControlling->DecScndCtrl(2); // najpierw bocznik na zero
if (!OK)
OK = mvControlling->DecMainCtrl( std::min( mvControlling->MainCtrlPowerPos(), 2 ) );
// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania
break;
case TEngineType::Dumb:
case TEngineType::DieselElectric:
OK = mvControlling->DecScndCtrl(2);
if (!OK)
OK = mvControlling->DecMainCtrl( std::min( mvControlling->MainCtrlPowerPos(), 2 + mvControlling->MainCtrlPowerPos() / 2 ) );
break;
case TEngineType::ElectricInductionMotor:
OK = DecSpeedEIM();
break;
case TEngineType::WheelsDriven:
if (!mvControlling->CabActive)
mvControlling->CabActivisation();
if (sin(mvControlling->eAngle) < 0)
mvControlling->IncMainCtrl(3 + 3 * floor(0.5 + fabs(AccDesired)));
else
mvControlling->DecMainCtrl(3 + 3 * floor(0.5 + fabs(AccDesired)));
break;
case TEngineType::DieselEngine:
if (mvControlling->EIMCtrlType > 0)
{
DizelPercentage = 0;
if (force) {
SpeedCntrl(0.0); //wylacz od razu tempomat
mvControlling->DecScndCtrl(2);
}
if (VelDesired > 0.1 && mvControlling->SpeedCtrlUnit.MinVelocity > VelDesired) {
SpeedCntrl(0.0); //wylacz od razu tempomat
mvControlling->DecScndCtrl(2);
}
break;
}
if (mvControlling->Vel > mvControlling->dizel_minVelfullengage)
{
if (mvControlling->RList[mvControlling->MainCtrlPos].Mn > 0)
OK = mvControlling->DecMainCtrl(1);
}
else {
while( mvControlling->RList[mvControlling->MainCtrlPos].Mn > 0
&& mvControlling->MainCtrlPowerPos() > 1 ) {
OK = mvControlling->DecMainCtrl( 1 );
}
}
if (force) { // przy aktywacji kabiny jest potrzeba natychmiastowego wyzerowania
OK = mvControlling->DecMainCtrl(mvControlling->MainCtrlPowerPos() > 2 ? 2 : 1);
SpeedCntrl(0.0); //wylacz od razu tempomat
mvControlling->DecScndCtrl( 2 );
}
break;
}
return OK;
};
bool TController::IncSpeedEIM() {
bool OK = false; // domyślnie false, aby wyszło z pętli while
switch( mvControlling->EIMCtrlType ) {
case 0:
OK = mvControlling->IncMainCtrl( 1 );
break;
case 1:
OK = mvControlling->MainCtrlPos < 6;
if( OK ) {
mvControlling->MainCtrlPos = 6;
}
/*
// TBD, TODO: set position based on desired acceleration?
OK = mvControlling->MainCtrlPos < mvControlling->MainCtrlPosNo;
if( OK ) {
mvControlling->MainCtrlPos = safe_clamp( mvControlling->MainCtrlPos + 1, 6, mvControlling->MainCtrlPosNo );
}
*/
break;
case 2:
OK = mvControlling->MainCtrlPos < 4;
if( OK ) {
mvControlling->MainCtrlPos = 4;
}
break;
}
return OK;
}
bool TController::DecSpeedEIM( int const Amount )
{ // zmniejszenie prędkości (ale nie hamowanie)
bool OK = false; // domyślnie false, aby wyszło z pętli while
switch( mvControlling->EIMCtrlType ) {
case 0: {
OK = mvControlling->DecMainCtrl( Amount );
break;
}
case 1: {
OK = mvControlling->MainCtrlPos > 4;
if( OK ) {
mvControlling->MainCtrlPos = 4;
}
break;
}
case 2: {
if( AccDesired > 0
&& mvControlling->SpeedCtrlUnit.IsActive
&& mvControlling->SpeedCtrlUnit.PowerStep > 0
&& mvControlling->SpeedCtrlUnit.DesiredPower > mvControlling->SpeedCtrlUnit.MinPower ) {
mvControlling->SpeedCtrlPowerDec();
}
else {
OK = mvControlling->MainCtrlPos > 2;
if( OK ) {
mvControlling->MainCtrlPos = 2;
}
}
break;
}
default: {
break;
}
}
return OK;
}
void TController::BrakeLevelSet(double b)
{ // ustawienie pozycji hamulca na wartość (b) w zakresie od -2 do BrakeCtrlPosNo
// jedyny dopuszczalny sposób przestawienia hamulca zasadniczego
if (BrakeCtrlPosition == b)
return; // nie przeliczać, jak nie ma zmiany
BrakeCtrlPosition = std::clamp(b, (double)gbh_MIN, (double)gbh_MAX);
}
bool TController::BrakeLevelAdd(double b)
{ // dodanie wartości (b) do pozycji hamulca (w tym ujemnej)
// zwraca false, gdy po dodaniu było by poza zakresem
BrakeLevelSet(BrakeCtrlPosition + b);
return b > 0.0 ? BrakeCtrlPosition < gbh_MAX :
BrakeCtrlPosition > -1.0; // true, jeśli można kontynuować
}
// Ra: regulacja prędkości, wykonywana w każdym przebłysku świadomości AI
// ma dokręcać do bezoporowych i zdejmować pozycje w przypadku przekroczenia prądu
void TController::SpeedSet() {
if( false == AIControllFlag ) { return; }
switch (mvOccupied->EngineType)
{
case TEngineType::None: { // McZapkie-041003: wagon sterowniczy
if (mvControlling->MainCtrlPosNo > 0)
{ // jeśli ma czym kręcić
// TODO: sprawdzanie innego czlonu //if (!FuseFlagCheck())
if( ( iDrivigFlags & moveIncSpeed ) == 0 ) {
// przetok rozłączać od razu (no dependency on fActionTime)
while( mvControlling->MainCtrlPos
&& mvControlling->DecMainCtrl(1) ) {
; // na zero
}
if( fActionTime >= 0.0 ) {
fActionTime = -5.0; // niech trochę potrzyma
}
// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania
}
else {
// jak ma jechać
if( fActionTime < 0.0 ) { break; }
if( false == Ready ) { break; }
if( mvOccupied->DirActive > 0 ) {
mvOccupied->DirectionForward(); //żeby EN57 jechały na drugiej nastawie
}
if( mvControlling->MainCtrlPos > 0
&& false == mvControlling->StLinFlag ) {
// jak niby jedzie, ale ma rozłączone liniowe to na zero i czekać na przewalenie kułakowego
mvControlling->DecMainCtrl( 2 );
}
else {
// ruch nastawnika uzależniony jest od aktualnie ustawionej pozycji
switch( mvControlling->MainCtrlPos ) {
case 0:
if( mvControlling->MainCtrlActualPos ) {
// jeśli kułakowy nie jest wyzerowany to czekać na wyzerowanie
break;
}
mvControlling->IncMainCtrl( 1 ); // przetok; bez "break", bo nie ma czekania na 1. pozycji
case 1:
if( VelDesired >= 20 )
mvControlling->IncMainCtrl( 1 ); // szeregowa
case 2:
if( VelDesired >= 50 )
mvControlling->IncMainCtrl( 1 ); // równoległa
case 3:
if( VelDesired >= 80 )
mvControlling->IncMainCtrl( 1 ); // bocznik 1
case 4:
if( VelDesired >= 90 )
mvControlling->IncMainCtrl( 1 ); // bocznik 2
case 5:
if( VelDesired >= 100 )
mvControlling->IncMainCtrl( 1 ); // bocznik 3
}
}
if( mvControlling->MainCtrlPos ) // jak załączył pozycję
{
fActionTime = -5.0; // niech trochę potrzyma
// mvControlling->AutoRelayCheck(); // sprawdzenie logiki sterowania
}
}
}
break;
}
case TEngineType::ElectricSeriesMotor: {
/*
if (Ready || (iDrivigFlags & movePress)) { // o ile może jechać
// if (fAccGravity < -0.10) // i jedzie pod górę większą niż 10 promil
{ // procedura wjeżdżania na ekstremalne wzniesienia
if( std::fabs( mvControlling->Im ) > 0.85 * mvControlling->Imax ) { // a prąd jest większy niż 85% nadmiarowego
if( mvControlling->Imax * mvControlling->EngineVoltage / ( fMass * fAccGravity ) < -2.8 ) // a na niskim się za szybko nie pojedzie
{ // włączenie wysokiego rozruchu;
// (I*U)[A*V=W=kg*m*m/sss]/(m[kg]*a[m/ss])=v[m/s]; 2.8m/ss=10km/h
if( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) { // jeśli jedzie na równoległym, to zbijamy do szeregowego, aby włączyć wysoki rozruch
if( mvControlling->ScndCtrlPos > 0 ) { // jeżeli jest bocznik
mvControlling->DecScndCtrl( 2 ); // wyłączyć bocznik, bo może blokować skręcenie NJ
}
do { // skręcanie do bezoporowej na szeregowym
mvControlling->DecMainCtrl( 1 ); // kręcimy nastawnik jazdy o 1 wstecz
}
while( ( false == mvControlling->IsMainCtrlNoPowerPos() )
&& ( mvControlling->RList[ mvControlling->MainCtrlPos ].Bn > 1 ) ); // oporowa zapętla
}
if( mvControlling->Imax < mvControlling->ImaxHi ) { // jeśli da się na wysokim
mvControlling->CurrentSwitch( true ); // rozruch wysoki (za to może się ślizgać)
}
if( ReactionTime > 0.1 ) {
ReactionTime = 0.1; // orientuj się szybciej
}
} // if (Im>Imin)
}
if( std::fabs( mvControlling->Im ) > 0.96 * mvControlling->Imax ) {// jeśli prąd jest duży (można 690 na 750)
if( mvControlling->ScndCtrlPos > 0 ) { // jeżeli jest bocznik
mvControlling->DecScndCtrl( 2 ); // zmniejszyć bocznik
}
else {
mvControlling->DecMainCtrl( 1 ); // kręcimy nastawnik jazdy o 1 wstecz
}
}
}
// else // gdy nie jedzie ambitnie pod górę
{ // sprawdzenie, czy rozruch wysoki jest potrzebny
if( ( mvControlling->Imax > mvControlling->ImaxLo )
&& ( mvOccupied->Vel >= 30.0 ) ) { // jak się rozpędził
// if (fAccGravity > -0.02) // a i pochylenie mnijsze niż 2‰
mvControlling->CurrentSwitch( false ); // rozruch wysoki wyłącz
}
}
}
*/
break;
}
case TEngineType::Dumb: {
break;
}
case TEngineType::DieselElectric: {
break;
}
case TEngineType::ElectricInductionMotor: {
break;
}
case TEngineType::DieselEngine: {
// Ra 2014-06: "automatyczna" skrzynia biegów...
auto const &motorparams { mvControlling->MotorParam[ mvControlling->ScndCtrlPos ] };
auto const velocity { ( mvControlling->ShuntMode ? mvControlling->AnPos : 1.0 ) * mvControlling->Vel };
if( false == motorparams.AutoSwitch ) {
// gdy biegi ręczne
// jak prędkość większa niż procent maksymalnej na danym biegu, wrzucić wyższy
if( velocity > 0.75 * motorparams.mfi ) {
// ...presuming there is a higher gear
if( mvControlling->ScndCtrlPos < mvControlling->ScndCtrlPosNo ) {
mvControlling->DecMainCtrl( 2 );
while( mvControlling->IncScndCtrl(1)
&& mvControlling->MotorParam[mvControlling->ScndCtrlPos].mIsat == 0.0 ) { // jeśli bieg jałowy to kolejny
;
}
}
}
// jak prędkość mniejsza niż minimalna na danym biegu, wrzucić niższy
else if( velocity < motorparams.fi ) {
// ... but ensure we don't switch all way down to 0
if( mvControlling->ScndCtrlPos > 1 ) {
mvControlling->DecMainCtrl( 2 );
while( mvControlling->ScndCtrlPos > 1 // repeat the entry check as we're working in a loop
&& mvControlling->DecScndCtrl(1)
&& mvControlling->MotorParam[mvControlling->ScndCtrlPos].mIsat == 0.0 ) { // jeśli bieg jałowy to kolejny
;
}
}
}
}
break;
}
default: {
break;
}
} // enginetype
};
void TController::SpeedCntrl(double DesiredSpeed)
{
if( false == mvControlling->SpeedCtrl ) { return; }
if (mvControlling->EngineType == TEngineType::DieselEngine)
{
if (DesiredSpeed < 0.1) {
mvControlling->DecScndCtrl(2);
DesiredSpeed = 0;
}
else if (mvControlling->ScndCtrlPos < 1) {
mvControlling->IncScndCtrl(1);
}
mvControlling->RunCommand("SpeedCntrl", DesiredSpeed, mvControlling->CabActive);
}
else
if (mvControlling->ScndCtrlPosNo == 1)
{
mvControlling->IncScndCtrl(1);
mvControlling->RunCommand("SpeedCntrl", DesiredSpeed, mvControlling->CabActive);
}
else if (mvControlling->ScndCtrlPosNo > 1 && !mvOccupied->SpeedCtrlTypeTime)
{
int DesiredPos = 1 + mvControlling->ScndCtrlPosNo * ((DesiredSpeed - 1.0) / mvControlling->Vmax);
while( mvControlling->ScndCtrlPos > DesiredPos && true == mvControlling->DecScndCtrl(1) ) { ; } // all work is done in the condition loop
while( mvControlling->ScndCtrlPos < DesiredPos && true == mvControlling->IncScndCtrl(1) ) { ; } // all work is done in the condition loop
}
if( mvControlling->SpeedCtrlUnit.PowerStep > 0 && mvControlling->ScndCtrlPos > 0 ) {
while (mvControlling->SpeedCtrlUnit.DesiredPower < mvControlling->SpeedCtrlUnit.MaxPower)
{
mvControlling->SpeedCtrlPowerInc();
}
}
};
void TController::SetTimeControllers()
{
// TBD, TODO: rework this method to use hint system and regardless of driver type
if( false == AIControllFlag || 0 == mvOccupied->CabActive ) { return; }
//1. Check the type of Main Brake Handle
if( BrakeSystem == TBrakeSystem::Pneumatic || ForcePNBrake )
{
if (mvOccupied->Handle->Time)
{
auto const pressuredifference { mvOccupied->Handle->GetCP() - ( mvOccupied->HighPipePress - BrakeCtrlPosition * 0.25*mvOccupied->DeltaPipePress ) };
if (BrakeCtrlPosition > 0 && pressuredifference > 0.05)
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_FB));
else if (BrakeCtrlPosition > 0 && pressuredifference < -0.05)
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_RP));
else if (BrakeCtrlPosition == 0)
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_RP));
else if (BrakeCtrlPosition == -1)
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_FS));
else if (BrakeCtrlPosition == -2)
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_NP));
}
if( mvOccupied->BrakeHandle == TBrakeHandle::FV4a ) {
mvOccupied->BrakeLevelSet( BrakeCtrlPosition );
}
if( mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P
|| mvOccupied->BrakeHandle == TBrakeHandle::MHZ_EN57 ) {
if( BrakeCtrlPosition == 0 ) {
mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_RP ) );
}
else if( BrakeCtrlPosition == -1 ) {
mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_FS ) );
}
else if( BrakeCtrlPosition == -2 ) {
mvOccupied->BrakeLevelSet( mvOccupied->Handle->GetPos( bh_NP ) );
}
else if( BrakeCtrlPosition > 4.5 ) {
mvOccupied->BrakeLevelSet( 10 );
}
else if( BrakeCtrlPosition > 3.70 ) {
mvOccupied->BrakeLevelSet( 9 );
}
else {
mvOccupied->BrakeLevelSet( round( ( BrakeCtrlPosition * 0.4 - 0.1 ) / 0.15 ) );
}
}
}
//2. Check the type of Secondary Brake Handle
//3. Check the type od EIMCtrlType
/*
if (mvOccupied->EIMCtrlType > 0)
{
if (mvOccupied->EIMCtrlType == 1) //traxx
{
if (mvOccupied->LocalBrakePosA > 0.95) mvOccupied->MainCtrlPos = 0;
}
else if (mvOccupied->EIMCtrlType == 2) //elf
{
if (mvOccupied->LocalBrakePosA > 0.95) mvOccupied->MainCtrlPos = 1;
}
}
*/
if( mvOccupied->UniCtrlIntegratedLocalBrakeCtrl
&& mvOccupied->LocalBrakePosA > 0.95 ) {
while( IncBrakeEIM() ) { ; }
}
//4. Check Speed Control System
if (mvOccupied->EngineType == TEngineType::ElectricInductionMotor && mvOccupied->ScndCtrlPosNo > 1 && mvOccupied->SpeedCtrlTypeTime)
{
double SpeedCntrlVel =
ActualProximityDist > std::max(50.0, fMaxProximityDist) ?
VelDesired :
min_speed(VelDesired, VelNext);
SpeedCntrlVel = 10 * std::floor(SpeedCntrlVel*0.1);
if (mvOccupied->ScndCtrlPosNo == 4)
{
if (mvOccupied->NewSpeed + 0.1 < SpeedCntrlVel)
mvOccupied->ScndCtrlPos = 3;
if (mvOccupied->NewSpeed - 0.1 > SpeedCntrlVel)
mvOccupied->ScndCtrlPos = 1;
}
}
//5. Check Main Controller in Dizels
//5.1. Digital controller in DMUs with hydro
if (mvControlling->EngineType == TEngineType::DieselEngine && mvControlling->EIMCtrlType == 3)
{
DizelPercentage_Speed = DizelPercentage; //wstepnie procenty
auto MinVel{ std::min(mvControlling->hydro_TC_LockupSpeed, mvControlling->Vmax / 6) }; //minimal velocity
//when speed controll unit is active - start with the procedure
if (mvControlling->SpeedCtrl && mvControlling->ScndCtrlPos > 0) {
if (mvControlling->ScndCtrlPos > 0 && mvControlling->Vel < 1 + mvControlling->SpeedCtrlUnit.StartVelocity && DizelPercentage > 0)
DizelPercentage_Speed = 101; //keep last position to start
}
else if (VelDesired > MinVel) //more power for faster ride
{
auto const Factor{ 10 * mvControlling->Vmax / (mvControlling->Vmax + 3 * mvControlling->Vel) };
auto DesiredPercentage{ std::clamp(
VelDesired > mvControlling->Vel ? (VelDesired - mvControlling->Vel) / Factor : 0,
0.0, 1.0) }; //correction for reaching desired velocity
if (VelDesired < 0.5 * mvControlling->Vmax //low velocity and reaching desired
&& VelDesired - mvControlling->Vel < 10) {
DesiredPercentage = std::min(DesiredPercentage, 0.75);
}
DizelPercentage_Speed = std::round(DesiredPercentage * DizelPercentage);
}
else //slow acceleration during shunting wth minimal velocity
{
DizelPercentage_Speed = std::min(DizelPercentage, mvControlling->Vel < 0.99 * VelDesired ? 1 : 0);
}
auto const DizelActualPercentage { int(100.4 * mvControlling->eimic_real) };
auto const PosInc { mvControlling->MainCtrlPosNo };
auto PosDec { 0 };
for( int i = PosInc; i >= 0; --i ) {
if( mvControlling->UniCtrlList[i].SetCtrlVal <= 0
&& mvControlling->UniCtrlList[i].SpeedDown > 0.01 ) {
PosDec = i;
break;
}
}
if( std::abs( DizelPercentage_Speed - DizelActualPercentage ) > ( DizelPercentage > 1 ? 0 : 0 ) ) {
if( PosDec > 0
&& ( DizelActualPercentage - DizelPercentage_Speed > 50
|| ( DizelPercentage_Speed == 0
&& DizelActualPercentage > 10 ) ) ) {
//one position earlier should be fast decreasing
PosDec -= 1;
}
auto const DesiredPos { (
DizelPercentage_Speed > DizelActualPercentage ?
PosInc :
PosDec ) };
while( mvControlling->MainCtrlPos > DesiredPos ) { mvControlling->DecMainCtrl( 1 ); }
while( mvControlling->MainCtrlPos < DesiredPos ) { mvControlling->IncMainCtrl( 1 ); }
}
if (BrakeCtrlPosition < 0.1) //jesli nie hamuje
mvOccupied->BrakeLevelSet(mvControlling->UniCtrlList[mvControlling->MainCtrlPos].mode); //zeby nie bruzdzilo machanie zespolonym
}
else
//5.2. Analog direct controller
if (mvControlling->EngineType == TEngineType::DieselEngine && mvControlling->Vmax > 30)
{
int MaxPos = mvControlling->MainCtrlPosNo;
int MinPos = MaxPos;
for (int i = MaxPos; i > 1 && mvControlling->RList[i].Mn > 0; i--) MinPos = i;
if (MaxPos > MinPos && mvControlling->MainCtrlPos > 0 && AccDesired > 0)
{
double Factor = 5 * mvControlling->Vmax / (mvControlling->Vmax + mvControlling->Vel);
int DesiredPos = MinPos + (MaxPos - MinPos)*(VelDesired > mvControlling->Vel ? (VelDesired - mvControlling->Vel) / Factor : 0);
if (DesiredPos > MaxPos) DesiredPos = MaxPos;
if (DesiredPos < MinPos) DesiredPos = MinPos;
if (!mvControlling->SlippingWheels)
{
while (mvControlling->MainCtrlPos > DesiredPos) mvControlling->DecMainCtrl(1);
if (mvControlling->Vel>mvControlling->dizel_minVelfullengage)
while (mvControlling->MainCtrlPos < DesiredPos) mvControlling->IncMainCtrl(1);
}
}
}
// 5.5 universal control for diesel electric vehicles
if (mvControlling->EngineType == TEngineType::DieselElectric && mvControlling->EIMCtrlType == 3)
{
// NOTE: partial implementation for speedinc/dec
// TBD, TODO: implement fully?
if( AccDesired >= 0.0
&& mvControlling->StLinFlag ) {
auto const PosInc { mvControlling->MainCtrlPosNo };
auto PosKeep { 0 };
auto PosDec{ 0 };
for( int i = PosInc; i >= 0; --i ) {
if( mvControlling->UniCtrlList[ i ].SetCtrlVal <= 0 ) {
if( mvControlling->UniCtrlList[ i ].SpeedDown == 0.0 ) {
PosKeep = i;
}
if( mvControlling->UniCtrlList[ i ].SpeedDown > 0.01 ) {
PosDec = i;
break;
}
}
}
auto const DesiredPos { (
AccDesired > AbsAccS + 0.05 ? PosInc :
AccDesired < AbsAccS - 0.05 ? PosDec :
PosKeep ) };
while( mvControlling->MainCtrlPos > DesiredPos && mvControlling->DecMainCtrl( 1 ) ) { ; }
while( mvControlling->MainCtrlPos < DesiredPos && mvControlling->IncMainCtrl( 1 ) ) { ; }
}
}
//6. UniversalBrakeButtons
//6.1. Checking flags for Over pressure
if (std::abs(BrakeCtrlPosition - gbh_FS)<0.5) {
UniversalBrakeButtons |= TUniversalBrake::ub_HighPressure | TUniversalBrake::ub_Overload;
}
else {
UniversalBrakeButtons &= ~(TUniversalBrake::ub_HighPressure | TUniversalBrake::ub_Overload);
}
//6.2. Setting buttons
for (int i = 0; i < 3; i++) {
mvOccupied->UniversalBrakeButton(i, UniversalBrakeButtons & mvOccupied->UniversalBrakeButtonFlag[i]);
}
};
void TController::CheckTimeControllers()
{
// TODO: rework this method to use hint system and regardless of driver type
if( false == AIControllFlag || 0 == mvControlling->CabActive ) { return; }
//1. Check the type of Main Brake Handle
if( BrakeSystem == TBrakeSystem::ElectroPneumatic && mvOccupied->Handle->TimeEP && !ForcePNBrake )
{
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPN));
}
if( ( BrakeSystem == TBrakeSystem::Pneumatic || ForcePNBrake ) && mvOccupied->Handle->Time )
{
if (BrakeCtrlPosition > 0)
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_MB));
else
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_RP));
}
//2. Check the type of Secondary Brake Handle
//3. Check the type od EIMCtrlType
if (mvOccupied->EIMCtrlType > 0)
{
if (mvOccupied->EIMCtrlType == 1) //traxx
{
if (mvOccupied->MainCtrlPos > 3) mvOccupied->MainCtrlPos = 5;
if (mvOccupied->MainCtrlPos < 3) mvOccupied->MainCtrlPos = 1;
}
else if (mvOccupied->EIMCtrlType == 2) //elf
{
if (mvOccupied->eimic > 0) mvOccupied->MainCtrlPos = 3;
if (mvOccupied->eimic < 0) mvOccupied->MainCtrlPos = 2;
}
}
//4. Check Speed Control System
if (mvOccupied->EngineType == TEngineType::ElectricInductionMotor && mvOccupied->ScndCtrlPosNo>1 && mvOccupied->SpeedCtrlTypeTime)
{
if (mvOccupied->ScndCtrlPosNo == 4)
{
mvOccupied->ScndCtrlPos = 2;
}
}
//5. Check Main Controller in Dizels
//5.1. Digital controller in DMUs with hydro
if (mvControlling->EngineType == TEngineType::DieselEngine && mvControlling->EIMCtrlType == 3)
{
int DizelActualPercentage = 100.4 * mvControlling->eimic_real;
int NeutralPos = mvControlling->MainCtrlPosNo - 1; //przedostatnia powinna wstrzymywać - hipoteza robocza
for (int i = mvControlling->MainCtrlPosNo; i >= 0; i--)
if (mvControlling->UniCtrlList[i].SetCtrlVal <= 0 && mvControlling->UniCtrlList[i].SpeedDown < 0.01) //niby zero, ale nie zmniejsza procentów
{
NeutralPos = i;
break;
}
if (BrakeCtrlPosition < 0.1) //jesli nie hamuje
{
if (DizelActualPercentage >= DizelPercentage_Speed && mvControlling->MainCtrlPos > NeutralPos)
while (mvControlling->MainCtrlPos > NeutralPos) mvControlling->DecMainCtrl(1);
if (DizelActualPercentage <= DizelPercentage_Speed && mvControlling->MainCtrlPos < NeutralPos)
while (mvControlling->MainCtrlPos < NeutralPos) mvControlling->IncMainCtrl(1);
mvOccupied->BrakeLevelSet(mvControlling->UniCtrlList[mvControlling->MainCtrlPos].mode); //zeby nie bruzdzilo machanie zespolonym
}
}
};
// otwieranie/zamykanie drzwi w składzie albo (tylko AI) EZT
void TController::Doors( bool const Open, int const Side ) {
// otwieranie drzwi
if( true == Open ) {
auto const lewe = pVehicle->DirectionGet() > 0 ? 1 : 2;
auto const prawe = 3 - lewe;
// grant door control permission if it's not automatic
// TBD: stricter requirements?
if( true == pVehicle->MoverParameters->Doors.permit_needed ) {
if( Side & prawe ) {
cue_action( driver_hint::doorrightpermiton );
}
if( Side & lewe ) {
cue_action( driver_hint::doorleftpermiton );
}
}
// consist-wide remote signals to open doors doors
if( pVehicle->MoverParameters->Doors.open_control == control_t::conductor
|| pVehicle->MoverParameters->Doors.open_control == control_t::driver
// NOTE: disabled for mixed controls, leave it up to passengers to open doors by themselves
/* || ( pVehicle->MoverParameters->Doors.open_control == control_t::mixed ) */ ) {
if( Side & prawe ) {
cue_action( driver_hint::doorrightopen );
}
if( Side & lewe ) {
cue_action( driver_hint::doorleftopen );
}
}
}
// zamykanie drzwi
else {
// if the doors are already closed and locked then there's nothing to do
if( false == doors_permit_active()
&& false == doors_open() ) {
iDrivigFlags &= ~moveDoorOpened;
iDrivigFlags &= ~moveDepartureWarned;
return;
}
if (mvOccupied->Doors.has_warning && !TestFlag(iDrivigFlags, moveDepartureWarned))
{
if (!mvOccupied->DepartureSignal)
{
// Not warned yet, not warning yet - start the warning signal.
cue_action( driver_hint::departuresignalon ); // załącenie bzyczka
fActionTime = Random( -0.8, -0.3 ); // 0.3-0.8 second buzzer
return;
}
// Not warned yet, warning now - stop the warning signal and mark it as done.
cue_action( driver_hint::departuresignaloff );
fActionTime = Random( -0.2, 0.0 ); // Wait just a bit more before departing
iDrivigFlags |= moveDepartureWarned;
return;
}
if( true == doors_open() ) {
// consist-wide remote signals to close doors
if( pVehicle->MoverParameters->Doors.close_control == control_t::conductor
|| pVehicle->MoverParameters->Doors.close_control == control_t::driver
|| pVehicle->MoverParameters->Doors.close_control == control_t::mixed ) {
cue_action( driver_hint::doorrightclose );
cue_action( driver_hint::doorleftclose );
}
}
if( true == doors_permit_active() ) {
cue_action( driver_hint::doorrightpermitoff );
cue_action( driver_hint::doorleftpermitoff );
}
// if applicable close manually-operated doors in vehicles which may ignore remote signals
{
const auto *vehicle = pVehicles[ end::front ]; // pojazd na czole składu
while( vehicle != nullptr ) {
// zamykanie drzwi w pojazdach - flaga zezwolenia była by lepsza
auto const ismanualdoor {
vehicle->MoverParameters->Doors.auto_velocity == -1.f
&& ( vehicle->MoverParameters->Doors.close_control == control_t::passenger
|| vehicle->MoverParameters->Doors.close_control == control_t::mixed ) };
if( true == ismanualdoor
&& ( vehicle->LoadExchangeTime() == 0.f
|| vehicle->MoverParameters->Vel > EU07_AI_MOVEMENT ) ) {
vehicle->MoverParameters->OperateDoors( side::right, false, range_t::local );
vehicle->MoverParameters->OperateDoors( side::left, false, range_t::local );
}
vehicle = vehicle->Next(); // pojazd podłączony z tyłu (patrząc od czoła)
}
}
fActionTime = Random(-3.5, -1.0); // 1.0-3.5 sec wait
iDrivigFlags &= ~moveDoorOpened; // zostały zamknięte - nie wykonywać drugi raz
iDrivigFlags &= ~moveDepartureWarned;
}
}
// returns true if any vehicle in the consist has open doors
bool
TController::doors_open() const {
return IsAnyDoorOpen[side::right] || IsAnyDoorOpen[side::left];
}
bool
TController::doors_permit_active() const {
return IsAnyDoorPermitActive[side::right] || IsAnyDoorPermitActive[side::left];
}
void
TController::announce( announcement_t const Announcement ) {
m_lastannouncement = Announcement;
if( IsCargoTrain ) {
// cargo trains aren't likely to have announcement systems, so we can skip the procedure altogether
return;
}
auto *vehicle { pVehicles[ end::front ] };
while( vehicle ) {
vehicle->announce( Announcement );
vehicle = vehicle->Next();
}
}
void TController::RecognizeCommand()
{ // odczytuje i wykonuje komendę przekazaną lokomotywie
TCommand *c = &mvOccupied->CommandIn;
PutCommand(c->Command, c->Value1, c->Value2, c->Location, stopComm);
c->Command = ""; // usunięcie obsłużonej komendy
}
void TController::PutCommand(std::string NewCommand, double NewValue1, double NewValue2, const TLocation &NewLocation, TStopReason reason)
{ // wysłanie komendy przez event PutValues, jak pojazd ma obsadę, to wysyła tutaj, a nie do pojazdu bezpośrednio
// zamiana na współrzędne scenerii
glm::dvec3 sl { -NewLocation.X, NewLocation.Z, NewLocation.Y };
if (!PutCommand(NewCommand, NewValue1, NewValue2, &sl, reason))
mvOccupied->PutCommand(NewCommand, NewValue1, NewValue2, NewLocation);
}
bool TController::PutCommand( std::string NewCommand, double NewValue1, double NewValue2, glm::dvec3 const *NewLocation, TStopReason reason )
{ // analiza komendy
if (NewCommand == "CabSignal")
{ // SHP wyzwalane jest przez człon z obsadą, ale obsługiwane przez silnikowy
// nie jest to najlepiej zrobione, ale bez symulacji obwodów lepiej nie będzie
// Ra 2014-04: jednak przeniosłem do rozrządczego
mvOccupied->PutCommand(NewCommand, NewValue1, NewValue2, mvOccupied->Loc);
mvOccupied->RunInternalCommand(); // rozpoznaj komende bo lokomotywa jej nie rozpoznaje
return true; // załatwione
}
if (NewCommand == "Overhead")
{ // informacja o stanie sieci trakcyjnej
fOverhead1 = NewValue1; // informacja o napięciu w sieci trakcyjnej (0=brak drutu, zatrzymaj!)
fOverhead2 = NewValue2; // informacja o sposobie jazdy (-1=normalnie, 0=bez prądu, >0=z
// opuszczonym i ograniczeniem prędkości)
return true; // załatwione
}
if (NewCommand == "Emergency_brake") // wymuszenie zatrzymania, niezależnie kto prowadzi
{ // Ra: no nadal nie jest zbyt pięknie
// SetVelocity(0, 0, reason);
mvOccupied->PutCommand("Emergency_brake", 1.0, 1.0, mvOccupied->Loc);
return true; // załatwione
}
if (NewCommand.compare(0, 10, "Timetable:") == 0)
{ // przypisanie nowego rozkładu jazdy, również prowadzonemu przez użytkownika
NewCommand.erase(0, 10); // zostanie nazwa pliku z rozkładem
#if LOGSTOPS
WriteLog("New timetable for " + pVehicle->asName + ": " + NewCommand); // informacja
#endif
TrainParams = TTrainParameters(NewCommand); // rozkład jazdy
tsGuardSignal = sound_source { sound_placement::internal, 2 * EU07_SOUND_CABCONTROLSCUTOFFRANGE }; // wywalenie kierownika
m_lastexchangestop.clear();
if (NewCommand != "none")
{
if (false == TrainParams.LoadTTfile(
std::string(Global.asCurrentSceneryPath.c_str()), floor(NewValue2 + 0.5),
NewValue1)) // pierwszy parametr to przesunięcie rozkładu w czasie
{
if (ConversionError == -8)
ErrorLog("Missed timetable: " + NewCommand);
WriteLog("Cannot load timetable file " + NewCommand + "\r\nError " +
std::to_string(ConversionError) + " in position " + std::to_string(TrainParams.StationCount));
NewCommand = ""; // puste, dla wymiennej tekstury
}
else
{ // inicjacja pierwszego przystanku i pobranie jego nazwy
// HACK: clear the potentially set door state flag to ensure door get opened if applicable
if( true == AIControllFlag ) {
// simplified door closing procedure, to sync actual door state with the door state flag
// NOTE: this may result in visually ugly quick switch between closing and opening the doors, but eh
if( pVehicle->MoverParameters->Doors.close_control == control_t::driver
|| pVehicle->MoverParameters->Doors.close_control == control_t::mixed ) {
pVehicle->MoverParameters->OperateDoors( side::right, false );
pVehicle->MoverParameters->OperateDoors( side::left, false );
}
if( pVehicle->MoverParameters->Doors.permit_needed ) {
pVehicle->MoverParameters->PermitDoors( side::right, false );
pVehicle->MoverParameters->PermitDoors( side::left, false );
}
}
iDrivigFlags &= ~moveDoorOpened;
TrainParams.UpdateMTable( simulation::Time, TrainParams.NextStationName );
TrainParams.StationIndexInc(); // przejście do następnej
TrainParams.StationStart = TrainParams.StationIndex;
asNextStop = TrainParams.NextStop();
m_lastannouncement = announcement_t::idle;
iDrivigFlags |= movePrimary; // skoro dostał rozkład, to jest teraz głównym
// NewCommand = Global.asCurrentSceneryPath + NewCommand;
auto lookup =
FileExists(
{ Global.asCurrentSceneryPath + NewCommand, paths::sounds + NewCommand },
{ ".ogg", ".flac", ".wav" } );
if( false == lookup.first.empty() ) {
// wczytanie dźwięku odjazdu podawanego bezpośrenido
tsGuardSignal = sound_source( sound_placement::external, 75.f ).deserialize( lookup.first + lookup.second, sound_type::single );
iGuardRadio = 0; // nie przez radio
}
else {
NewCommand += "radio";
lookup =
FileExists(
{ Global.asCurrentSceneryPath + NewCommand, paths::sounds + NewCommand },
{ ".ogg", ".flac", ".wav" } );
if( false == lookup.first.empty() ) {
// wczytanie dźwięku odjazdu w wersji radiowej (słychać tylko w kabinie)
tsGuardSignal = sound_source( sound_placement::internal, EU07_SOUND_HANDHELDRADIORANGE ).deserialize( lookup.first + lookup.second, sound_type::single );
iGuardRadio = iRadioChannel;
}
}
NewCommand = TrainParams.Relation2; // relacja docelowa z rozkładu
}
// jeszcze poustawiać tekstury na wyświetlaczach
TDynamicObject *p = pVehicles[end::front];
while (p)
{
p->DestinationSet(NewCommand, TrainParams.TrainName); // relacja docelowa
p = p->Next(); // pojazd podłączony od tyłu (licząc od czoła)
}
}
if (NewLocation) // jeśli podane współrzędne eventu/komórki ustawiającej rozkład (trainset
// nie podaje)
{
auto v = *NewLocation - pVehicle->GetPosition(); // wektor do punktu sterującego
auto d = pVehicle->VectorFront(); // wektor wskazujący przód
iDirectionOrder = (v.x * d.x + v.z * d.z) * NewValue1 > 0 ?
1 :
-1; // do przodu, gdy iloczyn skalarny i prędkość dodatnie
/*
if (NewValue1!=0.0) //jeśli ma jechać
if (iDirectionOrder==0) //a kierunek nie był określony (normalnie określany przez
reardriver/headdriver)
iDirectionOrder=NewValue1>0?1:-1; //ustalenie kierunku jazdy względem sprzęgów
else
if (NewValue1<0) //dla ujemnej prędkości
iDirectionOrder=-iDirectionOrder; //ma jechać w drugą stronę
*/
if (AIControllFlag) // jeśli prowadzi komputer
Activation(); // umieszczenie obsługi we właściwym członie, ustawienie nawrotnika w
// przód
}
/*
else
if (!iDirectionOrder)
{//jeśli nie ma kierunku, trzeba ustalić
if (mvOccupied->V!=0.0)
iDirectionOrder=mvOccupied->V>0?1:-1;
else
iDirectionOrder=mvOccupied->CabOccupied;
if (!iDirectionOrder) iDirectionOrder=1;
}
*/
// jeśli wysyłane z Trainset, to wszystko jest już odpowiednio ustawione
// if (!NewLocation) //jeśli wysyłane z Trainset
// if (mvOccupied->CabActive*mvOccupied->V*NewValue1<0) //jeśli zadana prędkość niezgodna z
// aktualnym kierunkiem jazdy
// DirectionForward(false); //jedziemy do tyłu (nawrotnik do tyłu)
// CheckVehicles(); //sprawdzenie składu, AI zapali światła
TableClear(); // wyczyszczenie tabelki prędkości, bo na nowo trzeba określić kierunek i
// sprawdzić przystanki
OrdersInit(fabs(NewValue1)); // ustalenie tabelki komend wg rozkładu oraz prędkości początkowej
// if (NewValue1!=0.0) if (!AIControllFlag) DirectionForward(NewValue1>0.0); //ustawienie
// nawrotnika użytkownikowi (propaguje się do członów)
// if (NewValue1>0)
// TrainNumber=floor(NewValue1); //i co potem ???
return true; // załatwione
}
if (NewCommand == "SetVelocity")
{
if (NewLocation)
vCommandLocation = *NewLocation;
if (NewValue1 != 0.0 && OrderCurrentGet() != Obey_train)
{ // o ile jazda
if( false == iEngineActive ) {
// trzeba odpalić silnik najpierw, światła ustawi
OrderNext( Prepare_engine );
}
OrderNext(Obey_train); // to uruchomić jazdę pociągową (od razu albo po odpaleniu silnika
OrderCheck(); // jeśli jazda pociągowa teraz, to wykonać niezbędne operacje
}
if (NewValue1 != 0.0) // jeśli jechać
iDrivigFlags &= ~moveStopHere; // podjeżanie do semaforów zezwolone
else
iDrivigFlags |= moveStopHere; // stać do momentu podania komendy jazdy
SetVelocity(NewValue1, NewValue2, reason); // bylo: nic nie rob bo SetVelocity zewnetrznie
// jest wywolywane przez dynobj.cpp
return true;
}
if (NewCommand == "SetProximityVelocity")
{
/*
if (SetProximityVelocity(NewValue1,NewValue2))
if (NewLocation)
vCommandLocation=*NewLocation;
*/
return true;
}
if (NewCommand == "ShuntVelocity")
{ // uruchomienie jazdy manewrowej bądź zmiana prędkości
if (NewLocation)
vCommandLocation = *NewLocation;
// if (OrderList[OrderPos]=Obey_train) and (NewValue1<>0))
if (false == iEngineActive)
OrderNext(Prepare_engine); // trzeba odpalić silnik najpierw
OrderNext(Shunt); // zamieniamy w aktualnej pozycji, albo dodajey za odpaleniem silnika
if (NewValue1 != 0.0)
{
// if (iVehicleCount>=0) WriteLog("Skasowano ilosć wagonów w ShuntVelocity!");
iVehicleCount = -2; // wartość neutralna
CheckVehicles(); // zabrać to do OrderCheck()
}
// dla prędkości ujemnej przestawić nawrotnik do tyłu? ale -1=brak ograniczenia !!!!
iDrivigFlags &= ~movePress; // bez dociskania
SetVelocity(NewValue1, NewValue2, reason);
if (NewValue1 != 0.0)
iDrivigFlags &= ~moveStopHere; // podjeżanie do semaforów zezwolone
else
iDrivigFlags |= moveStopHere; // ma stać w miejscu
if (fabs(NewValue1) > 2.0) // o ile wartość jest sensowna (-1 nie jest konkretną wartością)
fShuntVelocity = fabs(NewValue1); // zapamiętanie obowiązującej prędkości dla manewrów
return true;
}
if (NewCommand == "Wait_for_orders")
{ // oczekiwanie; NewValue1 - czas oczekiwania, -1 = na inną komendę
if (NewValue1 > 0.0 ? NewValue1 > fStopTime : false)
fStopTime = NewValue1; // Ra: włączenie czekania bez zmiany komendy
else
OrderList[OrderPos] = Wait_for_orders; // czekanie na komendę (albo dać OrderPos=0)
return true;
}
if (NewCommand == "Prepare_engine")
{ // włączenie albo wyłączenie silnika (w szerokim sensie)
OrdersClear(); // czyszczenie tabelki rozkazów, aby nic dalej nie robił
if (NewValue1 == 0.0)
OrderNext(Release_engine); // wyłączyć silnik (przygotować pojazd do jazdy)
else if (NewValue1 > 0.0)
OrderNext(Prepare_engine); // odpalić silnik (wyłączyć wszystko, co się da)
// po załączeniu przejdzie do kolejnej komendy, po wyłączeniu na Wait_for_orders
return true;
}
if (NewCommand == "Change_direction")
{
TOrders o = OrderCurrentGet(); // co robił przed zmianą kierunku
if (false == iEngineActive)
OrderNext(Prepare_engine); // trzeba odpalić silnik najpierw
if (NewValue1 == 0.0)
iDirectionOrder = -iDirection; // zmiana na przeciwny niż obecny
else if (NewLocation) // jeśli podane współrzędne eventu/komórki ustawiającej rozkład
// (trainset nie podaje)
{
auto v = *NewLocation - pVehicle->GetPosition(); // wektor do punktu sterującego
auto d = pVehicle->VectorFront(); // wektor wskazujący przód
iDirectionOrder = (v.x * d.x + v.z * d.z) * NewValue1 > 0 ?
1 :
-1; // do przodu, gdy iloczyn skalarny i prędkość dodatnie
// iDirectionOrder=1; else if (NewValue1<0.0) iDirectionOrder=-1;
}
if (
iDirectionOrder != iDirection)
OrderNext(Change_direction); // zadanie komendy do wykonania
if (o >= Shunt) // jeśli jazda manewrowa albo pociągowa
OrderNext(o); // to samo robić po zmianie
else if (!o) // jeśli wcześniej było czekanie
OrderNext(Shunt); // to dalej jazda manewrowa
if (mvOccupied->Vel >= EU07_AI_MOVEMENT) // jeśli jedzie
iDrivigFlags &= ~moveStartHorn; // to bez trąbienia po ruszeniu z zatrzymania
// Change_direction wykona się samo i następnie przejdzie do kolejnej komendy
return true;
}
if (NewCommand == "Obey_train") {
if( false == iEngineActive ) {
OrderNext( Prepare_engine ); // trzeba odpalić silnik najpierw
}
OrderNext( Obey_train );
// if (NewValue1>0) TrainNumber=floor(NewValue1); //i co potem ???
OrderCheck(); // jeśli jazda pociągowa teraz, to wykonać niezbędne operacje
return true;
}
if (NewCommand == "Bank") {
if( false == iEngineActive ) {
OrderNext( Prepare_engine ); // trzeba odpalić silnik najpierw
}
OrderNext( Bank );
// if (NewValue1>0) TrainNumber=floor(NewValue1); //i co potem ???
OrderCheck(); // jeśli jazda pociągowa teraz, to wykonać niezbędne operacje
return true;
}
if( NewCommand == "Shunt" || NewCommand == "Loose_shunt" )
{ // NewValue1 - ilość wagonów (-1=wszystkie); NewValue2: 0=odczep, 1..63=dołącz, -1=bez zmian
//-3,-y - podłączyć do całego stojącego składu (sprzęgiem y>=1), zmienić kierunek i czekać w trybie pociągowym
//-2,-y - podłączyć do całego stojącego składu (sprzęgiem y>=1), zmienić kierunek i czekać
//-2, y - podłączyć do całego stojącego składu (sprzęgiem y>=1) i czekać
//-1,-y - podłączyć do całego stojącego składu (sprzęgiem y>=1) i jechać w powrotną stronę
//-1, y - podłączyć do całego stojącego składu (sprzęgiem y>=1) i jechać dalej
//-1, 0 - tryb manewrowy bez zmian (odczepianie z pozostawieniem wagonów nie ma sensu)
// 0, 0 - odczepienie lokomotywy
// x,-y - podłączyć się do składu (sprzęgiem y>=1), a następnie odczepić i zabrać (x) wagonów
// 1, 0 - odczepienie lokomotywy z jednym wagonem
iDrivigFlags &= ~moveStopHere; // podjeżanie do semaforów zezwolone
if( false == iEngineActive ) {
OrderNext( Prepare_engine ); // trzeba odpalić silnik najpierw
}
if (NewValue2 != 0) // jeśli podany jest sprzęg
{
iCoupler = std::floor( std::abs( NewValue2 ) ); // jakim sprzęgiem
OrderNext(Connect); // najpierw połącz pojazdy
if (NewValue1 >= 0.0) // jeśli ilość wagonów inna niż wszystkie
{ // to po podpięciu należy się odczepić
iDirectionOrder = -iDirection; // zmiana na ciągnięcie
OrderPush(Change_direction); // najpierw zmień kierunek, bo odczepiamy z tyłu
OrderPush(Disconnect); // a odczep już po zmianie kierunku
if( NewValue2 > 0.0
&& NewCommand == "Loose_shunt" ) {
// after decoupling continue pushing in the original direction
// NOTE: for backward compatibility this option isn't supported for basic shunting mode
iDirectionOrder = iDirection; // back to pushing
OrderPush( Change_direction );
}
}
else if (NewValue2 < 0.0) // jeśli wszystkie, a sprzęg ujemny, to tylko zmiana kierunku po podczepieniu
{ // np. Shunt -1 -3
iDirectionOrder = -iDirection; // jak się podczepi, to jazda w przeciwną stronę
OrderNext(Change_direction);
}
WaitingTime = 0.0; // nie ma co dalej czekać, można zatrąbić i jechać, chyba że już jedzie
}
else { // if (NewValue2==0.0) //zerowy sprzęg
if( NewValue1 >= 0.0 ) {
// jeśli ilość wagonów inna niż wszystkie będzie odczepianie,
// ale jeśli wagony są z przodu, to trzeba najpierw zmienić kierunek
if( mvOccupied->Couplers[mvOccupied->DirAbsolute > 0 ? end::rear : end::front].Connected == nullptr // z tyłu nic
&& mvOccupied->Couplers[mvOccupied->DirAbsolute > 0 ? end::front : end::rear].Connected != nullptr ) { // a z przodu skład
iDirectionOrder = -iDirection; // zmiana na ciągnięcie
OrderNext( Change_direction ); // najpierw zmień kierunek (zastąpi Disconnect)
OrderPush( Disconnect ); // a odczep już po zmianie kierunku
}
else if( mvOccupied->Couplers[ mvOccupied->DirAbsolute > 0 ? end::rear : end::front ].Connected != nullptr ) { // z tyłu coś
OrderNext( Disconnect ); // jak ciągnie, to tylko odczep (NewValue1) wagonów
}
else {
// both couplers empty
// NOTE: this condition implements legacy behavior (mis)used by some scenarios to keep vehicle idling without moving
SetVelocity( 0, 0, stopJoin );
iDrivigFlags |= moveStopHere; // nie podjeżdżać do semafora, jeśli droga nie jest wolna
}
WaitingTime = 0.0; // nie ma co dalej czekać, można zatrąbić i jechać, chyba że już jedzie
}
}
if (NewValue1 == -1.0) {
iDrivigFlags &= ~moveStopHere; // ma jechać
WaitingTime = 0.0; // nie ma co dalej czekać, można zatrąbić i jechać
}
if( NewValue1 < -1.5 ) { // jeśli -2/-3, czyli czekanie z ruszeniem na sygnał
iDrivigFlags |= moveStopHere; // nie podjeżdżać do semafora, jeśli droga nie jest wolna
}
// (nie dotyczy Connect)
if( NewValue1 < -2.5 ) { // jeśli -3 to potem jazda pociągowa
OrderNext( NewCommand == "Shunt" ? Obey_train : Bank );
}
else { // otherwise continue shunting
OrderNext( NewCommand == "Shunt" ? Shunt : Loose_shunt );
}
CheckVehicles(); // sprawdzić światła
iVehicleCount = std::floor( NewValue1 );
return true;
}
if( NewCommand == "Jump_to_first_order" ) {
JumpToFirstOrder();
return true;
}
if (NewCommand == "Jump_to_order")
{
if( NewValue1 == -1.0 ) {
JumpToNextOrder();
}
else if (NewValue1 >= 0 && NewValue1 < maxorders)
{
OrderPos = floor(NewValue1);
if( !OrderPos ) {
// zgodność wstecz: dopiero pierwsza uruchamia
OrderPos = 1;
}
#if LOGORDERS
OrdersDump("Jump_to_order");
#endif
}
return true;
}
if (NewCommand == "Warning_signal")
{
if( NewValue1 > 0 && NewValue2 > 0 ) {
fWarningDuration = NewValue1; // czas trąbienia
cue_action( driver_hint::hornon, NewValue2 ); // horn combination flag
}
return true;
}
if (NewCommand == "Radio_channel") {
// wybór kanału radiowego (którego powinien używać AI, ręczny maszynista musi go ustawić sam)
if (NewValue1 >= 0) {
// wartości ujemne są zarezerwowane, -1 = nie zmieniać kanału
if( AIControllFlag ) {
iRadioChannel = NewValue1;
}
if( iGuardRadio ) {
// kierownikowi też zmienić
iGuardRadio = NewValue1;
}
}
// NewValue2 może zawierać dodatkowo oczekiwany kod odpowiedzi, np. dla W29 "nawiązać
// łączność radiową z dyżurnym ruchu odcinkowym"
return true;
}
if( NewCommand == "SetLights" ) {
// set consist lights pattern hints
m_lighthints[ end::front ] = static_cast<int>( NewValue1 );
m_lighthints[ end::rear ] = static_cast<int>( NewValue2 );
if( true == TestFlag( OrderCurrentGet(), Obey_train ) ) {
// light hints only apply in the obey_train mode
CheckVehicles();
}
return true;
}
if (NewCommand == "SetSignal") {
TSignals signal = (TSignals)std::lrint(NewValue1);
for (int i = Signal_START; i <= Signal_MAX; i++)
iDrivigFlags &= ~(1 << i);
if (NewValue2 > 0.5)
iDrivigFlags |= 1 << (uint32_t)signal;
else
CheckVehicles(); // restore light state
}
return false; // nierozpoznana - wysłać bezpośrednio do pojazdu
};
void TController::PhysicsLog()
{ // zapis logu - na razie tylko wypisanie parametrów
if (LogFile.is_open())
{
#if LOGPRESS == 0
/*
<< "Time[s] Velocity[m/s] Acceleration[m/ss] "
<< "Coupler.Dist[m] Coupler.Force[N] TractionForce[kN] FrictionForce[kN] BrakeForce[kN] "
<< "BrakePress[MPa] PipePress[MPa] MotorCurrent[A] "
<< "MCP SCP BCP LBP DmgFlag Command CVal1 CVal2 "
<< "EngineTemp[Deg] OilTemp[Deg] WaterTemp[Deg] WaterAuxTemp[Deg]"
*/
LogFile
<< ElapsedTime << " "
<< fabs(11.31 * mvOccupied->WheelDiameter * mvOccupied->nrot) << " "
<< mvControlling->AccS << " "
<< mvOccupied->Couplers[1].Dist << " "
<< mvOccupied->Couplers[1].CForce << " "
<< mvOccupied->Ft << " "
<< mvOccupied->Ff << " "
<< mvOccupied->Fb << " "
<< mvOccupied->BrakePress << " "
<< mvOccupied->PipePress << " "
<< mvControlling->Im << " "
<< int(mvControlling->MainCtrlPos) << " "
<< int(mvControlling->ScndCtrlPos) << " "
<< mvOccupied->fBrakeCtrlPos << " "
<< mvOccupied->LocalBrakePosA << " "
<< int(mvControlling->DirActive) << " "
<< ( mvOccupied->CommandIn.Command.empty() ? "none" : mvOccupied->CommandIn.Command.c_str() ) << " "
<< mvOccupied->CommandIn.Value1 << " "
<< mvOccupied->CommandIn.Value2 << " "
<< int(mvControlling->SecuritySystem.is_blinking()) << " "
<< int(mvControlling->SlippingWheels) << " "
<< mvControlling->dizel_heat.Ts << " "
<< mvControlling->dizel_heat.To << " "
<< mvControlling->dizel_heat.temperatura1 << " "
<< mvControlling->dizel_heat.temperatura2
<< "\r\n";
#endif
#if LOGPRESS == 1
LogFile << ElapsedTime << "\t" << fabs(11.31 * mvOccupied->WheelDiameter * mvOccupied->nrot)
<< "\t";
LogFile << Controlling->AccS << "\t";
LogFile << mvOccupied->PipePress << "\t" << mvOccupied->CntrlPipePress << "\t"
<< mvOccupied->BrakePress << "\t";
LogFile << mvOccupied->Volume << "\t" << mvOccupied->Hamulec->GetCRP() << "\n";
#endif
LogFile.flush();
}
};
void
TController::Update( double const Timedelta ) {
// uruchamiać przynajmniej raz na sekundę
if( ( iDrivigFlags & movePrimary ) == 0 ) { return; } // pasywny nic nie robi
if( false == simulation::is_ready ) { return; }
update_timers( Timedelta );
update_logs( Timedelta );
auto const reactiontime { std::min( ReactionTime, 2.0 ) };
if( LastReactionTime < reactiontime ) { return; }
LastReactionTime -= reactiontime;
/*
// TBD, TODO: put this in an appropriate place, or get rid of it
// NOTE: this section moved all cars to the edge of their respective roads
// it may have some use eventually for collision avoidance,
// but when enabled all the time it produces silly effect
if( mvOccupied->Vel > 1.0 ) {
// przy prowadzeniu samochodu trzeba każdą oś odsuwać oddzielnie, inaczej kicha wychodzi
if (mvOccupied->CategoryFlag & 2) // jeśli samochód
// if (fabs(mvOccupied->OffsetTrackH)<mvOccupied->Dim.W) //Ra: szerokość drogi tu powinna być?
if (!mvOccupied->ChangeOffsetH(-0.01 * mvOccupied->Vel * dt)) // ruch w poprzek drogi
mvOccupied->ChangeOffsetH(0.01 * mvOccupied->Vel * dt); // Ra: co to miało być, to nie wiem
}
*/
update_hints();
// main ai update routine
determine_consist_state();
determine_braking_distance();
determine_proximity_ranges();
// vicinity check
auto const awarenessrange {
std::max(
750.0,
mvOccupied->Vel > EU07_AI_MOVEMENT ?
400 + fBrakeDist :
30.0 * fDriverDist ) }; // 1500m dla stojących pociągów;
if( is_active() ) {
scan_route( awarenessrange );
}
scan_obstacles( awarenessrange );
// generic actions
control_security_system( reactiontime );
if( iEngineActive ) {
control_wheelslip();
control_horns( reactiontime );
control_pantographs();
if( fActionTime > 0.0 ) {
// potentially delayed and/or low priority actions
control_lights();
control_doors();
control_compartment_lights();
}
}
CheckTimeControllers();
// external command actions
UpdateCommand();
// mode specific actions
handle_engine();
handle_orders();
// situational velocity and acceleration adjustments
pick_optimal_speed( awarenessrange );
if( iEngineActive ) {
control_tractive_and_braking_force();
}
SetTimeControllers();
}
// configures vehicle heating given current situation; returns: true if vehicle can be operated normally, false otherwise
bool
TController::PrepareHeating() {
switch( mvControlling->EngineType ) {
case TEngineType::DieselElectric:
case TEngineType::DieselEngine: {
auto const &heat { mvControlling->dizel_heat };
// determine whether there's need to enable the water heater
// if the heater has configured maximum temperature, it'll disable itself automatically, so we can leave it always running
// otherwise enable the heater only to maintain minimum required temperature
auto const lowtemperature { (
( heat.water.config.temp_min > 0 && heat.temperatura1 < heat.water.config.temp_min + (mvControlling->WaterHeater.is_active ? 5 : 0) )
|| ( heat.water_aux.config.temp_min > 0 && heat.temperatura2 < heat.water_aux.config.temp_min + (mvControlling->WaterHeater.is_active ? 5 : 0) )
|| ( heat.oil.config.temp_min > 0 && heat.To < heat.oil.config.temp_min + (mvControlling->WaterHeater.is_active ? 5 : 0) ) ) };
auto const heateron { (
mvControlling->WaterHeater.config.temp_max > 0
|| true == lowtemperature ) };
if( true == heateron ) {
// make sure the water pump is running before enabling the heater
if( false == mvControlling->WaterPump.is_active ) {
cue_action( driver_hint::waterpumpbreakeron );
cue_action( driver_hint::waterpumpon );
}
if( true == mvControlling->WaterPump.is_active ) {
cue_action( driver_hint::waterheaterbreakeron );
cue_action( driver_hint::waterheateron );
cue_action( driver_hint::watercircuitslinkon );
}
}
else {
// no need to heat anything up, switch the heater off
cue_action( driver_hint::watercircuitslinkoff );
cue_action( driver_hint::waterheateroff );
cue_action( driver_hint::waterheaterbreakeroff );
// optionally turn off the water pump as well
if( mvControlling->WaterPump.start_type != start_t::battery ) {
cue_action( driver_hint::waterpumpoff );
cue_action( driver_hint::waterpumpbreakeroff );
}
}
IsHeatingTemperatureTooLow = lowtemperature;
break;
}
default: {
IsHeatingTemperatureTooLow = false;
break;
}
}
// TBD, TODO: potentially pay attention to too high temperature as well?
IsHeatingTemperatureOK = false == IsHeatingTemperatureTooLow;
return IsHeatingTemperatureOK;
}
void
TController::PrepareDirection() {
if( iDirection == 0 ) {
// jeśli nie ma ustalonego kierunku
if( mvOccupied->Vel < EU07_AI_NOMOVEMENT ) { // ustalenie kierunku, gdy stoi
iDirection = mvOccupied->CabActive; // wg wybranej kabiny
/*
if( iDirection == 0 ) {
// jeśli nie ma ustalonego kierunku
if( mvOccupied->Couplers[ end::rear ].Connected == nullptr ) {
// jeśli z tyłu nie ma nic
iDirection = -1; // jazda w kierunku sprzęgu 1
}
if( mvOccupied->Couplers[ end::front ].Connected == nullptr ) {
// jeśli z przodu nie ma nic
iDirection = 1; // jazda w kierunku sprzęgu 0
}
}
*/
}
else {
// ustalenie kierunku, gdy jedzie
iDirection = mvOccupied->V >= 0 ? 1 : // jazda w kierunku sprzęgu 0
-1; // jazda w kierunku sprzęgu 1
}
}
cue_action( driver_hint::mastercontrollersetreverserunlock );
cue_action(
iDirection > 0 ?
driver_hint::directionforward :
driver_hint::directionbackward );
}
void TController::JumpToNextOrder( bool const Ignoremergedchangedirection )
{ // wykonanie kolejnej komendy z tablicy rozkazów
if (OrderList[OrderPos] != Wait_for_orders)
{
if( (OrderList[OrderPos] & Change_direction) != 0 // jeśli zmiana kierunku
&& OrderList[OrderPos] != Change_direction ) { // ale nałożona na coś
if( false == Ignoremergedchangedirection ) {
// usunięcie zmiany kierunku z innej komendy
OrderList[ OrderPos ] = TOrders( OrderList[ OrderPos ] & ~Change_direction );
OrderCheck();
return;
}
}
if (OrderPos < maxorders - 1)
++OrderPos;
else
OrderPos = 0;
}
OrderCheck();
#if LOGORDERS
OrdersDump("JumpToNextOrder"); // normalnie nie ma po co tego wypisywać
#endif
};
void TController::JumpToFirstOrder()
{ // taki relikt
OrderPos = 1;
if (OrderTop == 0)
OrderTop = 1;
OrderCheck();
#if LOGORDERS
OrdersDump("JumpToFirstOrder"); // normalnie nie ma po co tego wypisywać
#endif
};
void TController::OrderCheck()
{ // reakcja na zmianę rozkazu
if( OrderCurrentGet() != Obey_train ) {
// reset light hints
m_lighthints[ end::front ] = m_lighthints[ end::rear ] = -1;
}
if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect | Obey_train | Bank ) ) {
CheckVehicles(); // sprawdzić światła
}
if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) {
// HACK: ensure consist doors will be closed on departure
iDrivigFlags |= moveDoorOpened;
}
if (OrderList[OrderPos] & Change_direction) // może być nałożona na inną i wtedy ma priorytet
iDirectionOrder = -iDirection; // trzeba zmienić jawnie, bo się nie domyśli
else if (OrderList[OrderPos] == Obey_train)
iDrivigFlags |= moveStopPoint; // W4 są widziane
else if (OrderList[OrderPos] == Disconnect)
iVehicleCount = iVehicleCount < 0 ? 0 : iVehicleCount; // odczepianie lokomotywy
else if (OrderList[OrderPos] == Connect)
iDrivigFlags &= ~moveStopPoint; // podczas jazdy na połączenie nie zwracać uwagi na W4
else if (OrderList[OrderPos] == Wait_for_orders)
OrdersClear(); // czyszczenie rozkazów i przeskok do zerowej pozycji
}
void TController::OrderNext(TOrders NewOrder)
{ // ustawienie rozkazu do wykonania jako następny
if (OrderList[OrderPos] == NewOrder)
return; // jeśli robi to, co trzeba, to koniec
if (!OrderPos)
OrderPos = 1; // na pozycji zerowej pozostaje czekanie
OrderTop = OrderPos; // ale może jest czymś zajęty na razie
if (NewOrder >= Shunt) // jeśli ma jechać
{ // ale może być zajęty chwilowymi operacjami
while (OrderList[OrderTop] != Wait_for_orders && OrderList[OrderTop] < Shunt) // jeśli coś robi
++OrderTop; // pomijamy wszystkie tymczasowe prace
}
else
{ // jeśli ma ustawioną jazdę, to wyłączamy na rzecz operacji
while (OrderList[OrderTop] ?
OrderList[OrderTop] < Shunt && OrderList[OrderTop] != NewOrder :
false) // jeśli coś robi
++OrderTop; // pomijamy wszystkie tymczasowe prace
}
OrderList[OrderTop++] = NewOrder; // dodanie rozkazu jako następnego
#if LOGORDERS
OrdersDump( "OrderNext" ); // normalnie nie ma po co tego wypisywać
#endif
}
void TController::OrderPush(TOrders NewOrder)
{ // zapisanie na stosie kolejnego rozkazu do wykonania
if (OrderPos == OrderTop) // jeśli miałby być zapis na aktalnej pozycji
if (OrderList[OrderPos] < Shunt) // ale nie jedzie
++OrderTop; // niektóre operacje muszą zostać najpierw dokończone => zapis na kolejnej
if (OrderList[OrderTop] != NewOrder) // jeśli jest to samo, to nie dodajemy
OrderList[OrderTop++] = NewOrder; // dodanie rozkazu na stos
// if (OrderTop<OrderPos) OrderTop=OrderPos;
if (OrderTop >= maxorders)
ErrorLog("Commands overflow: The program will now crash");
#if LOGORDERS
OrdersDump( "OrderPush: [" + Order2Str( NewOrder ) + "]" ); // normalnie nie ma po co tego wypisywać
#endif
}
void TController::OrdersDump( std::string const Neworder, bool const Verbose )
{ // wypisanie kolejnych rozkazów do logu
WriteLog( "New order for [" + pVehicle->asName + "]: " + Neworder );
if( false == Verbose ) { return; }
for (int b = 0; b < maxorders; ++b)
{
WriteLog( ( OrderPos == b ? ">" : " " ) + (std::to_string(b) + ": " + Order2Str(OrderList[b])));
if (b) // z wyjątkiem pierwszej pozycji
if (OrderList[b] == Wait_for_orders) // jeśli końcowa komenda
break; // dalej nie trzeba
}
};
void TController::OrdersInit(double fVel)
{ // wypełnianie tabelki rozkazów na podstawie rozkładu
// ustawienie kolejności komend, niezależnie kto prowadzi
OrdersClear(); // usunięcie poprzedniej tabeli
OrderPush(Prepare_engine); // najpierw odpalenie silnika
if (TrainParams.StationCount == 0)
{ // brak rozkładu to jazda manewrowa
// if (fVel > 0.05) // typowo 0.1 oznacza gotowość do jazdy, 0.01 tylko załączenie silnika
OrderPush(Shunt); // jeśli nie ma rozkładu, to manewruje
}
else
{ // jeśli z rozkładem, to jedzie na szlak
if (fVel > 0.0 && fVel < 0.02)
OrderPush(Shunt); // dla prędkości 0.01 włączamy jazdę manewrową
else if (TrainParams.DirectionChange() ? // jeśli obrót na pierwszym przystanku
(iDrivigFlags & movePushPull ? // SZT również! SN61 zależnie od wagonów...
TrainParams.TimeTable[1].StationName == TrainParams.Relation1 :
false) :
false)
OrderPush(Shunt); // a teraz start będzie w manewrowym, a tryb pociągowy włączy W4
else
// jeśli start z pierwszej stacji i jednocześnie jest na niej zmiana kierunku, to EZT ma mieć Shunt
OrderPush(Obey_train); // dla starych scenerii start w trybie pociagowym
if (DebugModeFlag) // normalnie nie ma po co tego wypisywać
WriteLog("/* Timetable: " + TrainParams.ShowRelation());
TMTableLine *t;
for (int i = 0; i <= TrainParams.StationCount; ++i)
{
t = TrainParams.TimeTable + i;
if (DebugModeFlag) {
// normalnie nie ma po co tego wypisywa?
WriteLog(
t->StationName
+ " " + std::to_string(t->Ah) + ":" + std::to_string(t->Am)
+ ", " + std::to_string(t->Dh) + ":" + std::to_string(t->Dm)
+ " " + t->StationWare);
}
if ( contains( t->StationWare, '@' ) )
{ // zmiana kierunku i dalsza jazda wg rozk?adu
if (iDrivigFlags & movePushPull) // SZT również! SN61 zależnie od wagonów...
{ // jeśli skład zespolony, wystarczy zmienić kierunek jazdy
OrderPush(Change_direction); // zmiana kierunku
}
else
{ // dla zwykłego składu wagonowego odczepiamy lokomotywę
OrderPush(Disconnect); // odczepienie lokomotywy
OrderPush(Shunt); // a dalej manewry
}
if (i < TrainParams.StationCount) // jak nie ostatnia stacja
OrderPush(Obey_train); // to dalej wg rozkładu
}
}
if( DebugModeFlag ) {
// normalnie nie ma po co tego wypisywać
WriteLog( "*/" );
}
OrderPush(Shunt); // po wykonaniu rozkładu przełączy się na manewry
}
// McZapkie-100302 - to ma byc wyzwalane ze scenerii
if( fVel == 0.0 ) {
// jeśli nie ma prędkości początkowej, to śpi
SetVelocity( 0, 0, stopSleep );
}
else {
// jeśli podana niezerowa prędkość
if( fVel >= 1.0
|| fVel < 0.02 ) {
// jeśli ma jechać - dla 0.01 ma podjechać manewrowo po podaniu sygnału
// to do następnego W4 ma podjechać blisko
iDrivigFlags = iDrivigFlags & ~moveStopHere | moveStopCloser;
}
else {
// czekać na sygnał
iDrivigFlags |= moveStopHere;
}
JumpToFirstOrder();
if (fVel >= 1.0) // jeśli ma jechać
SetVelocity(fVel, -1); // ma ustawić żądaną prędkość
else
SetVelocity(0, 0, stopSleep); // prędkość w przedziale (0;1) oznacza, że ma stać
}
#if LOGORDERS
OrdersDump( "OrdersInit", DebugModeFlag ); // wypisanie kontrolne tabelki rozkazów
#endif
// McZapkie! - zeby w ogole AI ruszyl to musi wykonac powyzsze rozkazy
// Ale mozna by je zapodac ze scenerii
};
std::string TController::StopReasonText() const
{ // informacja tekstowa o przyczynie zatrzymania
if (eStopReason != 7) // zawalidroga będzie inaczej
return StopReasonTable[eStopReason];
else
return "Blocked by " + pVehicles[0]->PrevAny()->name();
};
//----------------------------------------------------------------------------------------------------------------------
// McZapkie: skanowanie semaforów
// Ra: stare funkcje skanujące, używane podczas manewrów do szukania sygnalizatora z tyłu
//- nie reagują na PutValues, bo nie ma takiej potrzeby
//- rozpoznają tylko zerową prędkość (jako koniec toru i brak podstaw do dalszego skanowania)
//----------------------------------------------------------------------------------------------------------------------
bool TController::IsOccupiedByAnotherConsist( TTrack *Track, double const Distance = 0 )
{ // najpierw sprawdzamy, czy na danym torze są pojazdy z innego składu
if( false == Track->Dynamics.empty() ) {
for( auto dynamic : Track->Dynamics ) {
if( dynamic->ctOwner != this ) {
// jeśli jest jakiś cudzy to tor jest zajęty i skanowanie nie obowiązuje
if( Distance == 0 ) {
return true;
}
else {
// based on provided position of scanning vehicle and scan direction, filter out vehicles on irrelevant end
auto const scandirection { Distance > 0 ? 1 : -1 }; // positive value means scan towards point2 end of the track
auto const obstaclelocation { scandirection > 0 ? Track->Length() - dynamic->RaTranslationGet() : dynamic->RaTranslationGet() };
// if detected vehicle is closer to the end of the track in scanned direction than we are, it means it's blocking our way
if( obstaclelocation < std::abs( Distance ) ) {
return true;
}
}
}
}
}
return false; // wolny
};
basic_event * TController::CheckTrackEventBackward(double fDirection, TTrack *Track, TDynamicObject *Vehicle, int const Eventdirection, end const End)
{ // sprawdzanie eventu w torze, czy jest sygnałowym - skanowanie do tyłu
// NOTE: this method returns only one event which meets the conditions, due to limitations in the caller
// TBD, TODO: clean up the caller and return all suitable events, as in theory things will go awry if the track has more than one signal
auto const dir{ Vehicle->VectorFront() * (double)Vehicle->DirectionGet() };
auto const pos{ End == end::front ? Vehicle->RearPosition() : Vehicle->HeadPosition() };
auto const &eventsequence { ( fDirection * Eventdirection > 0 ? Track->m_events2 : Track->m_events1 ) };
for( auto const &event : eventsequence ) {
if( event.second != nullptr
&& event.second->m_passive
&& typeid(*event.second) == typeid(getvalues_event) ) {
// since we're checking for events behind us discard the sources in front of the scanning vehicle
auto const sl{ event.second->input_location() }; // położenie komórki pamięci
auto const sem{ sl - pos }; // wektor do komórki pamięci od końca składu
auto const isahead { dir.x * sem.x + dir.z * sem.z > 0 };
if( End == end::front ? isahead : !isahead ) {
// iloczyn skalarny jest ujemny, gdy sygnał stoi z tyłu
return event.second;
}
}
}
return nullptr;
};
TTrack * TController::BackwardTraceRoute(double &fDistance, double &fDirection, TDynamicObject *Vehicle, basic_event *&Event, int const Eventdirection, end const End, bool const Untiloccupied )
{ // szukanie sygnalizatora w kierunku przeciwnym jazdy (eventu odczytu komórki pamięci)
auto *Track = Vehicle->GetTrack();
double fCurrentDistance = Vehicle->RaTranslationGet(); // aktualna pozycja na torze
if (fDirection > 0) // jeśli w kierunku Point2 toru
fCurrentDistance = Track->Length() - fCurrentDistance;
double s = 0;
if( Track->VelocityGet() == 0.0 ) { // jak prędkosć 0 albo uszkadza, to nie ma po co skanować
fDistance = s; // to na tym torze stoimy
return nullptr; // stop, skanowanie nie dało sensownych rezultatów
}
// jak tor zajęty innym składem, to nie ma po co skanować
if( Untiloccupied && IsOccupiedByAnotherConsist( Track, fCurrentDistance * fDirection ) ) {
fDistance = s; // to na tym torze stoimy
return nullptr; // stop, skanowanie nie dało sensownych rezultatów
}
if ((Event = CheckTrackEventBackward(fDirection, Track, Vehicle, Eventdirection, End)) != nullptr)
{ // jeśli jest semafor na tym torze
fDistance = s; // to na tym torze stoimy
return Track;
}
TTrack *pTrackFrom; // odcinek poprzedni, do znajdywania końca dróg
while (s < fDistance)
{
// Track->ScannedFlag=true; //do pokazywania przeskanowanych torów
pTrackFrom = Track; // zapamiętanie aktualnego odcinka
s += fCurrentDistance; // doliczenie kolejnego odcinka do przeskanowanej długości
if (fDirection > 0)
{ // jeśli szukanie od Point1 w kierunku Point2
if (Track->iNextDirection)
fDirection = -fDirection;
Track = Track->CurrentNext(); // może być NULL
}
else // if (fDirection<0)
{ // jeśli szukanie od Point2 w kierunku Point1
if (!Track->iPrevDirection)
fDirection = -fDirection;
Track = Track->CurrentPrev(); // może być NULL
}
if (Track == pTrackFrom)
Track = nullptr; // koniec, tak jak dla torów
// gdy dalej toru nie ma albo zerowa prędkość, albo uszkadza pojazd
// zwraca NULL, że skanowanie nie dało sensownych rezultatów
if( Track == nullptr ) {
fDistance = s;
return nullptr;
}
if( Track->VelocityGet() == 0.0 )
{
fDistance = s;
return nullptr;
}
if( Untiloccupied && IsOccupiedByAnotherConsist( Track ) ) {
fDistance = s;
return nullptr;
}
fCurrentDistance = Track->Length();
if ((Event = CheckTrackEventBackward(fDirection, Track, Vehicle, Eventdirection, End)) != nullptr)
{ // znaleziony tor z eventem
fDistance = s;
return Track;
}
}
Event = nullptr; // jak dojdzie tu, to nie ma semafora
// zwraca ostatni sprawdzony tor
fDistance = s;
return Track;
}
// sprawdzanie zdarzeń semaforów i ograniczeń szlakowych
void TController::SetProximityVelocity( double dist, double vel, glm::dvec3 const *pos )
{ // Ra:przeslanie do AI prędkości
/*
//!!!! zastąpić prawidłową reakcją AI na SetProximityVelocity !!!!
if (vel==0)
{//jeśli zatrzymanie, to zmniejszamy dystans o 10m
dist-=10.0;
};
if (dist<0.0) dist=0.0;
if ((vel<0)?true:dist>0.1*(MoverParameters->Vel*MoverParameters->Vel-vel*vel)+50)
{//jeśli jest dalej od umownej drogi hamowania
*/
PutCommand( "SetProximityVelocity", dist, vel, pos );
/*
}
else
{//jeśli jest zagrożenie, że przekroczy
Mechanik->SetVelocity(floor(0.2*sqrt(dist)+vel),vel,stopError);
}
*/
}
TCommandType TController::BackwardScan( double const Range )
{ // sprawdzanie zdarzeń semaforów z tyłu pojazdu, zwraca komendę
// dzięki temu będzie można stawać za wskazanym sygnalizatorem, a zwłaszcza jeśli będzie jazda na kozioł
// ograniczenia prędkości nie są wtedy istotne, również koniec toru jest do niczego nie przydatny
// zwraca true, jeśli należy odwrócić kierunek jazdy pojazdu
if( OrderCurrentGet() & ~(Shunt | Loose_shunt | Connect) ) {
// skanowanie sygnałów tylko gdy jedzie w trybie manewrowym albo czeka na rozkazy
return TCommandType::cm_Unknown;
}
// kierunek jazdy względem sprzęgów pojazdu na czele
int const startdir = -pVehicles[0]->DirectionGet();
if( startdir == 0 ) {
// jeśli kabina i kierunek nie jest określony nie robimy nic
return TCommandType::cm_Unknown;
}
// szukamy od pierwszej osi w wybranym kierunku
double scandir = startdir * pVehicles[0]->RaDirectionGet();
if( scandir == 0.0 ) { return TCommandType::cm_Unknown; }
// skanowanie toru w poszukiwaniu eventów GetValues (PutValues nie są przydatne)
// Ra: przy wstecznym skanowaniu prędkość nie ma znaczenia
double scandist = Range; // zmodyfikuje na rzeczywiście przeskanowane
basic_event *e = nullptr; // event potencjalnie od semafora
// opcjonalnie może być skanowanie od "wskaźnika" z przodu, np. W5, Tm=Ms1, koniec toru wg
// drugiej osi w kierunku ruchu
auto const *scantrack{BackwardTraceRoute(scandist, scandir, pVehicles[end::front], e)};
auto const dir{(double)startdir *
pVehicles[end::front]->VectorFront()}; // wektor w kierunku jazdy/szukania
// jeśli wstecz wykryto koniec toru to raczej nic się nie da w takiej sytuacji zrobić
if (e == nullptr)
{
return TCommandType::cm_Unknown;
}
if (typeid(*e) != typeid(getvalues_event))
{
return TCommandType::cm_Unknown;
}
// jeśli jest jakiś sygnał na widoku
double scanvel; // prędkość ustawiona semaforem
#if LOGBACKSCAN
std::string edir{"Backward scan by " + pVehicle->asName + " - " +
((scandir > 0) ? "Event2 " : "Event1 ")};
edir += "(" + (e->name()) + ")";
#endif
{
// najpierw sprawdzamy, czy semafor czy inny znak został przejechany
auto const sl{e->input_location()}; // położenie komórki pamięci
auto const pos{pVehicles[end::rear]->RearPosition()}; // pozycja tyłu
auto const sem{sl - pos}; // wektor do komórki pamięci od końca składu
if (dir.x * sem.x + dir.z * sem.z < 0)
{
// jeśli został minięty
// iloczyn skalarny jest ujemny, gdy sygnał stoi z tyłu
#if LOGBACKSCAN
WriteLog(edir + " - ignored as not passed yet");
#endif
return TCommandType::cm_Unknown; // nic
}
scanvel = e->input_value(1); // prędkość przy tym semaforze
// przeliczamy odległość od semafora - potrzebne by były współrzędne początku składu
scandist = glm::length(sem) - 2; // 2m luzu przy manewrach wystarczy
if (scandist < 0)
{
// ujemnych nie ma po co wysyłać
scandist = 0;
}
}
auto move{false}; // czy AI w trybie manewerowym ma dociągnąć pod S1
if (e->input_command() == TCommandType::cm_SetVelocity)
{
if (scanvel == 0.0 ? OrderCurrentGet() & (Shunt | Loose_shunt | Connect) :
OrderCurrentGet() & Connect)
{ // przy podczepianiu ignorować wyjazd?
move = true; // AI w trybie manewerowym ma dociągnąć pod S1
}
else
{
if (scandist > fMinProximityDist &&
mvOccupied->Vel > EU07_AI_NOMOVEMENT && (OrderCurrentGet() & (Shunt | Loose_shunt)) == 0)
{
// jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości
// jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył
#if LOGBACKSCAN
// WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist) +
// AnsiString(scanvel));
WriteLog(edir);
#endif
// SetProximityVelocity(scandist,scanvel,&sl);
return scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown;
}
else
{
// ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma stać
// if ((MoverParameters->Vel==0.0)||(scanvel==0.0)) //jeśli stoi lub ma stanąć/stać
// semafor na tym torze albo lokomtywa stoi, a ma ruszyć, albo ma stanąć, albo nie
// ruszać stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam
// PutCommand("SetVelocity",scanvel,e->Params[9].asMemCell->Value2(),&sl,stopSem);
#if LOGBACKSCAN
WriteLog(edir + " - [SetVelocity] [" + to_string(scanvel, 2) + "] [" +
to_string(e->input_value(2), 2) + "]");
#endif
return scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown;
}
}
}
// reakcja AI w trybie manewrowym dodatkowo na sygnały manewrowe
if (OrderCurrentGet() ? OrderCurrentGet() & (Shunt | Loose_shunt | Connect) :
true) // w Wait_for_orders też widzi tarcze
{
if (move || e->input_command() == TCommandType::cm_ShuntVelocity)
{ // jeśli powyżej było SetVelocity 0 0, to dociągamy pod S1
/*
if ((scandist > fMinProximityDist) &&
((mvOccupied->Vel > EU07_AI_NOMOVEMENT) || (scanvel == 0.0)))
{
// jeśli tarcza jest daleko, to:
//- jesli pojazd jedzie, to informujemy o zmianie prędkości
//- jeśli stoi, to z własnej inicjatywy może podjechać pod zamkniętą tarczę
if (mvOccupied->Vel > EU07_AI_NOMOVEMENT) // tylko jeśli jedzie
{ // Mechanik->PutCommand("SetProximityVelocity",scandist,scanvel,sl);
#if LOGBACKSCAN
// WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist)+"
// "+AnsiString(scanvel));
WriteLog(edir);
#endif
// SetProximityVelocity(scandist,scanvel,&sl);
// jeśli jedzie na W5 albo koniec toru, to można zmienić kierunek
return (iDrivigFlags & moveTrackEnd) ? TCommandType::cm_ChangeDirection :
TCommandType::cm_Unknown;
// TBD: request direction change also if VelNext in current direction is 0,
// while signal behind requests movement?
}
}
else
{
// ustawiamy prędkość tylko wtedy, gdy ma ruszyć, albo stanąć albo ma stać pod
// tarczą stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam if
// ((MoverParameters->Vel==0.0)||(scanvel==0.0)) //jeśli jedzie lub ma stanąć/stać
{ // nie dostanie komendy jeśli jedzie i ma jechać
// PutCommand("ShuntVelocity",scanvel,e->Params[9].asMemCell->Value2(),&sl,stopSem);
#if LOGBACKSCAN
WriteLog(edir + " - [ShuntVelocity] [" + to_string(scanvel, 2) + "] [" +
to_string(e->input_value(2), 2) + "]");
#endif
return (scanvel > 0 ? TCommandType::cm_ShuntVelocity :
TCommandType::cm_Unknown);
}
}
// NOTE: dead code due to returns in branching above
if ((scanvel != 0.0) && (scandist < 100.0))
{
// jeśli Tm w odległości do 100m podaje zezwolenie na jazdę, to od razu ją
// ignorujemy, aby móc szukać kolejnej eSignSkip=e; //wtedy uznajemy ignorowaną przy
// poszukiwaniu nowej
#if LOGBACKSCAN
WriteLog(edir + " - will be ignored due to Ms2");
#endif
return (scanvel > 0 ? TCommandType::cm_ShuntVelocity : TCommandType::cm_Unknown);
}
*/
return VelNext > 0 ?
TCommandType::cm_Unknown : // no need to bother if we can continue in current direction
scanvel == 0 ?
TCommandType::cm_Unknown : // no need to bother with a stop signal
TCommandType::cm_ShuntVelocity; // otherwise report a relevant shunt signal behind
} // if (move?...
} // if (OrderCurrentGet()==Shunt)
return e->m_passive && e->is_command() ? TCommandType::cm_Command : TCommandType::cm_Unknown;
};
std::string TController::NextStop() const
{ // informacja o następnym zatrzymaniu, wyświetlane pod [F1]
if (asNextStop == "[End of route]")
return ""; // nie zawiera nazwy stacji, gdy dojechał do końca
// dodać godzinę odjazdu
auto nextstop{ Bezogonkow( asNextStop, true ) };
auto const *t{ TrainParams.TimeTable + TrainParams.StationIndex };
if( t->Ah >= 0 ) {
// przyjazd
nextstop += " przyj." + std::to_string( t->Ah ) + ":"
+ to_minutes_str( t->Am, true, 3 );
}
if( t->Dh >= 0 ) {
// jeśli jest godzina odjazdu
nextstop += " odj." + std::to_string( t->Dh ) + ":"
+ to_minutes_str( t->Dm, true, 3 );
}
return nextstop;
};
void TController::UpdateDelayFlag() {
if( TrainParams.CheckTrainLatency() < 0.0 ) {
// odnotowano spóźnienie
iDrivigFlags |= moveLate;
}
else {
// przyjazd o czasie
iDrivigFlags &= ~moveLate;
}
}
//-----------koniec skanowania semaforow
void TController::TakeControl( bool const Aidriver, bool const Forcevehiclecheck )
{ // przejęcie kontroli przez AI albo oddanie
if (AIControllFlag == Aidriver && !Forcevehiclecheck)
return; // już jest jak ma być
if (Aidriver) //żeby nie wykonywać dwa razy
{ // teraz AI prowadzi
AIControllFlag = AIdriver;
pVehicle->Controller = AIdriver;
control_lights(); // reinicjalizacja swiatel
mvOccupied->CabActivisation(true);
iDirection = 0; // kierunek jazdy trzeba dopiero zgadnąć
TableClear(); // ponowne utworzenie tabelki, bo człowiek mógł pojechać niezgodnie z sygnałami
if( action() != TAction::actSleep ) {
// gdy zgaszone światła, flaga podjeżdżania pod semafory pozostaje bez zmiany
// conditional below disabled to get around the situation where the AI train does nothing ever
// because it is waiting for orders which don't come until the engine is engaged, i.e. effectively never
if( OrderCurrentGet() ) {
// jeśli coś robi
PrepareEngine(); // niech sprawdzi stan silnika
}
else {
// jeśli nic nie robi
OrderNext( Prepare_engine );
if( pVehicle->MoverParameters->iLights[ ( mvOccupied->CabActive < 0 ?
end::rear :
end::front ) ]
& ( light::headlight_left | light::headlight_right | light::headlight_upper ) ) // któreś ze świateł zapalone?
{ // od wersji 357 oczekujemy podania komend dla AI przez scenerię
/*
if( pVehicle->MoverParameters->iLights[ mvOccupied->CabActive < 0 ? end::rear : end::front ] & light::headlight_upper ) // górne światło zapalone
OrderNext( Obey_train ); // jazda pociągowa
else
OrderNext( Shunt ); // jazda manewrowa
*/
if( mvOccupied->Vel >= 1.0 ) // jeśli jedzie (dla 0.1 ma stać)
iDrivigFlags &= ~moveStopHere; // to ma nie czekać na sygnał, tylko jechać
else
iDrivigFlags |= moveStopHere; // a jak stoi, to niech czeka
}
}
CheckVehicles(); // ustawienie świateł
}
}
else
{ // a teraz użytkownik
if (!is_train() || !is_car()) // gasimy swiatla jesli przejmujemy odstawione
pVehicle->RaLightsSet(0, 0);
AIControllFlag = Humandriver;
pVehicle->Controller = Humandriver;
if( eAction == TAction::actSleep ) {
eAction = TAction::actUnknown;
}
if( Forcevehiclecheck ) {
// update consist ownership and other consist data
CheckVehicles();
}
}
};
void TController::DirectionForward(bool forward)
{ // ustawienie jazdy do przodu dla true i do tyłu dla false (zależy od kabiny)
ZeroSpeed( true ); // TODO: check if force switch is needed anymore here
// HACK: make sure the master controller isn't set in position which prevents direction change
mvControlling->MainCtrlPos = std::min( mvControlling->MainCtrlPos, mvControlling->MainCtrlMaxDirChangePos );
if( forward ) {
// do przodu w obecnej kabinie
while( mvOccupied->DirActive <= 0
&& mvOccupied->DirectionForward() ) {
// force scan table update
iTableDirection = 0;
}
}
else {
// do tyłu w obecnej kabinie
while( mvOccupied->DirActive >= 0
&& mvOccupied->DirectionBackward() ) {
// force scan table update
iTableDirection = 0;
}
}
if( mvOccupied->TrainType == dt_SN61 ) {
// specjalnie dla SN61 żeby nie zgasł
while( mvControlling->RList[mvControlling->MainCtrlPos].Mn == 0
&& mvControlling->IncMainCtrl(1) ) {
; // all work is done in the header
}
}
}
void TController::ZeroDirection() {
while( mvOccupied->DirActive > 0 && mvOccupied->DirectionBackward() ) { ; }
while( mvOccupied->DirActive < 0 && mvOccupied->DirectionForward() ) { ; }
}
void TController::sync_consist_reversers() {
auto const currentdirection { mvOccupied->DirActive };
auto const fastforward { is_emu() && mvOccupied->EngineType != TEngineType::ElectricInductionMotor
&& mvOccupied->Imin == mvOccupied->IminHi };
// move reverser all way in the opposite direction...
for( auto idx = 0; idx < 3; ++idx ) {
if( currentdirection >= 0 ) {
mvOccupied->DirectionBackward();
}
else {
mvOccupied->DirectionForward();
}
}
// ...then restore original setting
while( mvOccupied->DirActive != currentdirection ) {
if( false == (
currentdirection >= 0 ?
mvOccupied->DirectionForward() :
mvOccupied->DirectionBackward() ) ) {
// sanity check for potential endless loop
break;
}
}
// potentially restore 'fast forward' setting
if( currentdirection > 0 && true == fastforward ) {
mvOccupied->DirectionForward();
}
}
Mtable::TTrainParameters const &
TController::TrainTimetable() const {
return TrainParams;
}
std::string TController::Relation() const
{ // zwraca relację pociągu
return TrainParams.ShowRelation();
};
const std::string& TController::TrainName() const
{ // zwraca numer pociągu
return TrainParams.TrainName;
};
int TController::StationCount() const
{ // zwraca ilość stacji (miejsc zatrzymania)
return TrainParams.StationCount;
};
int TController::StationIndex() const
{ // zwraca indeks aktualnej stacji (miejsca zatrzymania)
return TrainParams.StationIndex;
};
bool TController::IsStop() const
{ // informuje, czy jest zatrzymanie na najbliższej stacji
return TrainParams.IsStop();
};
// returns most recently calculated distance to potential obstacle ahead
double
TController::TrackObstacle() const {
return Obstacle.distance;
}
void TController::MoveTo(TDynamicObject *to)
{ // przesunięcie AI do innego pojazdu (przy zmianie kabiny)
if( to->Mechanik != nullptr
&& to->Mechanik != this ) {
// ai controller thunderdome, there can be only one
if( to->Mechanik->AIControllFlag ) {
if( to->Mechanik->primary() ) {
// take over boss duties
primary( true );
}
SafeDelete( to->Mechanik );
}
else {
// can't quite delete a human
if( AIControllFlag ) {
delete this;
}
return;
}
}
pVehicle->Mechanik = nullptr;
pVehicle = to;
ControllingSet(); // utworzenie połączenia do sterowanego pojazdu
pVehicle->Mechanik = this;
};
void TController::ControllingSet()
{ // znajduje człon do sterowania w EZT będzie to silnikowy
// problematyczne jest sterowanie z członu biernego, dlatego damy AI silnikowy
// dzięki temu będzie wirtualna kabina w silnikowym, działająca w rozrządczym
// w plikach FIZ zostały zgubione ujemne maski sprzęgów, stąd problemy z EZT
mvOccupied = pVehicle->MoverParameters; // domyślny skrót do obiektu parametrów
BrakeSystem = mvOccupied->BrakeSystem; // domyślny sposób hamowania
mvControlling = pVehicle->FindPowered()->MoverParameters; // poszukiwanie członu sterowanego
{
auto *lookup { pVehicle->FindPantographCarrier() };
mvPantographUnit = lookup != nullptr ? lookup->MoverParameters : mvControlling;
}
BrakeSystem = consist_brake_system();
};
std::string TController::TableText( std::size_t const Index ) const
{ // pozycja tabelki prędkości
if( Index < sSpeedTable.size() ) {
return sSpeedTable[ Index ].TableText();
}
else {
return "";
}
};
int TController::CrossRoute(TTrack *tr)
{ // zwraca numer segmentu dla skrzyżowania (tr)
// pożądany numer segmentu jest określany podczas skanowania drogi
// droga powinna być określona sposobem przejazdu przez skrzyżowania albo współrzędnymi miejsca
// docelowego
for( std::size_t i = 0; i < sSpeedTable.size(); ++i )
{ // trzeba przejrzeć tabelę skanowania w poszukiwaniu (tr)
// i jak się znajdzie, to zwrócić zapamiętany numer segmentu i kierunek przejazdu
// (-6..-1,1..6)
if( true == TestFlag(sSpeedTable[i].iFlags, spEnabled | spTrack)
&& sSpeedTable[i].trTrack == tr ) {
// jeśli pozycja odpowiadająca skrzyżowaniu (tr)
return sSpeedTable[i].iFlags >> 28; // najstarsze 4 bity jako liczba -8..7
}
}
return 0; // nic nie znaleziono?
};
/*
void TController::RouteSwitch(int d)
{ // ustawienie kierunku jazdy z kabiny
d &= 3;
if( ( d != 0 )
&& ( iRouteWanted != d ) ) { // nowy kierunek
iRouteWanted = d; // zapamiętanie
if( mvOccupied->CategoryFlag & 2 ) {
// jeśli samochód
for( std::size_t i = 0; i < sSpeedTable.size(); ++i ) {
// szukanie pierwszego skrzyżowania i resetowanie kierunku na nim
if( true == TestFlag( sSpeedTable[ i ].iFlags, spEnabled | spTrack ) ) {
// jeśli pozycja istotna (1) oraz odcinek (2)
if( false == TestFlag( sSpeedTable[ i ].iFlags, spElapsed ) ) {
// odcinek nie może być miniętym
if( sSpeedTable[ i ].trTrack->eType == tt_Cross ) // jeśli skrzyżowanie
{
while( sSpeedTable.size() >= i ) {
// NOTE: we're ignoring semaphor flags and not resetting them like we do for train route trimming
// but what if there's street lights?
// TODO: investigate
sSpeedTable.pop_back();
}
iLast = sSpeedTable.size();
}
}
}
}
}
}
};
*/
std::string TController::OwnerName() const
{
return pVehicle ? pVehicle->MoverParameters->Name : "none";
};
void
TController::update_timers( double dt ) {
ElapsedTime += dt;
WaitingTime += dt;
fBrakeTime -= dt; // wpisana wartość jest zmniejszana do 0, gdy ujemna należy zmienić nastawę hamulca
if( mvOccupied->fBrakeCtrlPos != mvOccupied->Handle->GetPos( bh_FS ) ) {
// brake charging timeout starts after charging ends
BrakeChargingCooldown += dt;
}
fStopTime += dt; // zliczanie czasu postoju, nie ruszy dopóki ujemne
fActionTime += dt; // czas używany przy regulacji prędkości i zamykaniu drzwi
LastReactionTime += dt;
if( mvOccupied->Vel < 0.05
&& (OrderCurrentGet() & (Shunt | Loose_shunt | Obey_train | Bank)) != 0 ) {
IdleTime += dt;
}
else {
IdleTime = 0.0;
}
fWarningDuration -= dt; // horn activation while the value is positive
}
void
TController::update_logs( double const dt ) {
// log vehicle data
LastUpdatedTime += dt;
if( WriteLogFlag
&& LastUpdatedTime > deltalog ) {
// zapis do pliku DAT
PhysicsLog();
LastUpdatedTime -= deltalog;
}
}
void
TController::determine_consist_state() {
// ABu-160305 testowanie gotowości do jazdy
// Ra: przeniesione z DynObj, skład użytkownika też jest testowany, żeby mu przekazać, że ma odhamować
if (mvOccupied->CabActive == 0 && mvOccupied->Power24vIsAvailable)
{
cue_action( driver_hint::cabactivation );
}
else if (!mvOccupied->AutomaticCabActivation
&& ( mvOccupied->CabActive == -mvOccupied->CabOccupied || !mvOccupied->CabMaster || !mvOccupied->Power24vIsAvailable ) )
{
cue_action( driver_hint::cabdeactivation );
}
int index = double(BrakeAccTableSize) * (mvOccupied->Vel / mvOccupied->Vmax);
index = std::min(BrakeAccTableSize, std::max(1, index));
fBrake_a0[0] = fBrake_a0[index];
fBrake_a1[0] = fBrake_a1[index];
if (is_emu() || is_dmu()) {
auto Coeff = std::clamp( mvOccupied->Vel*0.015 , 0.5 , 1.0);
fAccThreshold = fNominalAccThreshold * Coeff - fBrake_a0[BrakeAccTableSize] * (1.0 - Coeff);
}
Ready = true; // wstępnie gotowy
fReady = 0.0; // założenie, że odhamowany
IsConsistBraked = mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic && false == ForcePNBrake ? mvOccupied->BrakePress > 2.0 :
mvOccupied->PipePress < std::max(3.9, mvOccupied->BrakePressureActual.PipePressureVal) + 0.1;
fAccGravity = 0.0; // przyspieszenie wynikające z pochylenia
IsAnyCouplerStretched = false;
IsAnyDoorOnlyOpen[ side::right ] = IsAnyDoorOnlyOpen[ side::left ] = false;
IsAnyDoorOpen[ side::right ] = IsAnyDoorOpen[ side::left ] = false;
IsAnyDoorPermitActive[ side::right ] = IsAnyDoorPermitActive[ side::left ] = false;
ConsistShade = 0.0;
auto *p { pVehicles[ end::front ] }; // pojazd na czole składu
double dy; // składowa styczna grawitacji, w przedziale <0,1>
while (p)
{ // sprawdzenie odhamowania wszystkich połączonych pojazdów
auto *vehicle { p->MoverParameters };
auto const bp { std::max( 0.0, vehicle->BrakePress - ( vehicle->SpeedCtrlUnit.Parking ? vehicle->MaxBrakePress[ 0 ] * vehicle->StopBrakeDecc : 0.0 ) ) };
if (Ready) {
// bo jak coś nie odhamowane, to dalej nie ma co sprawdzać
if( TestFlagAny(vehicle->Hamulec->GetBrakeStatus(), b_hld | b_on)
|| ( vehicle->Vel < 1.0 ? bp > 0.4 : // ensure the brakes are sufficiently released when starting to move
vehicle->Fb * 0.001 > 10.0 ) ) { // once in motion we can make a more lenient check
Ready = false;
}
// Ra: odluźnianie przeładowanych lokomotyw, ciągniętych na zimno - prowizorka...
if( bp >= 0.4 ) { // wg UIC określone sztywno na 0.04
if( AIControllFlag || (Global.AITrainman && mvOccupied->Vel < EU07_AI_NOMOVEMENT && !is_emu() && !is_dmu())) {
if( BrakeCtrlPosition == gbh_RP // jest pozycja jazdy
&& false == TestFlag(vehicle->Hamulec->GetBrakeStatus(), b_dmg) // brake isn't broken
&& vehicle->PipePress - mvOccupied->Handle->GetRP() > -0.1 // jeśli ciśnienie jak dla jazdy
&& vehicle->Hamulec->GetCRP() > vehicle->PipePress + 0.12 ) { // za dużo w zbiorniku
// indywidualne luzowanko
vehicle->BrakeReleaser( 1 );
}
}
}
if (bp < 0.1) {
if ( AIControllFlag || Global.AITrainman ) {
if (false == TestFlag(vehicle->Hamulec->GetBrakeStatus(), b_dmg) // brake isn't broken
&& vehicle->Hamulec->GetCRP() < vehicle->PipePress - 0.1 ) { // już nie jest za dużo w zbiorniku
// koniec indywidualnego luzowanka
vehicle->BrakeReleaser( 0 );
}
}
}
}
fReady = std::max( bp, fReady ); // szukanie najbardziej zahamowanego
if( ( dy = p->VectorFront().y ) != 0.0 ) {
// istotne tylko dla pojazdów na pochyleniu
// ciężar razy składowa styczna grawitacji
fAccGravity -= vehicle->TotalMassxg * dy * ( p->DirectionGet() == iDirection ? 1 : -1 );
}
// check coupler state
IsAnyCouplerStretched =
IsAnyCouplerStretched
|| vehicle->Couplers[end::front].stretch_duration > 0.0
|| vehicle->Couplers[end::rear].stretch_duration > 0.0;
// check door state
{
auto const switchsides{ p->DirectionGet() != (iDirection == 0 ? mvOccupied->CabOccupied : iDirection) };
auto const &rightdoor { vehicle->Doors.instances[ ( switchsides ? side::left : side::right ) ] };
auto const &leftdoor { vehicle->Doors.instances[ ( switchsides ? side::right : side::left ) ] };
if( vehicle->Doors.close_control != control_t::autonomous ) {
IsAnyDoorOpen[ side::right ] |= false == rightdoor.is_closed;
IsAnyDoorOpen[ side::left ] |= false == leftdoor.is_closed;
}
if (vehicle->Doors.close_control != control_t::autonomous) {
IsAnyDoorOnlyOpen[ side::right ] |= false == rightdoor.is_door_closed;
IsAnyDoorOnlyOpen[ side::left ] |= false == leftdoor.is_door_closed;
}
if( vehicle->Doors.permit_needed ) {
IsAnyDoorPermitActive[ side::right ] |= rightdoor.open_permit;
IsAnyDoorPermitActive[ side::left ] |= leftdoor.open_permit;
}
}
// measure lighting level
// TBD: apply weight (multiplier) to partially lit vehicles?
ConsistShade += p->fShade > 0.0 ? p->fShade : 1.0;
p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła)
}
// calculate average amount of received sunlight
ConsistShade /= iVehicles;
// test state of main devices in all vehicles under control
IsAnyConverterOverloadRelayOpen = false;
IsAnyMotorOverloadRelayOpen = false;
IsAnyGroundRelayOpen = false;
IsAnyLineBreakerOpen = false;
IsAnyCompressorPresent = false;
IsAnyCompressorEnabled = false;
IsAnyCompressorExplicitlyEnabled = false;
IsAnyConverterPresent = false;
IsAnyConverterEnabled = false;
IsAnyConverterExplicitlyEnabled = false;
pVehicle->for_each(
coupling::control,
[this]( TDynamicObject * Vehicle ) {
auto const *vehicle { Vehicle->MoverParameters };
IsAnyConverterOverloadRelayOpen |= vehicle->ConvOvldFlag;
IsAnyMotorOverloadRelayOpen |= vehicle->FuseFlag;
IsAnyGroundRelayOpen |= !vehicle->GroundRelay;
IsAnyCompressorPresent |= vehicle->CompressorSpeed > 0.0;
IsAnyCompressorEnabled |= vehicle->CompressorSpeed > 0.0 ? (vehicle->CompressorAllow || vehicle->CompressorStart == start_t::automatic) && vehicle->CompressorAllowLocal : false;
IsAnyCompressorExplicitlyEnabled |= vehicle->CompressorSpeed > 0.0 ? vehicle->CompressorAllow && vehicle->CompressorAllowLocal : false;
IsAnyConverterPresent |= vehicle->ConverterStart != start_t::disabled;
IsAnyConverterEnabled |= ( vehicle->ConverterAllow || vehicle->ConverterStart == start_t::automatic ) && vehicle->ConverterAllowLocal;
IsAnyConverterExplicitlyEnabled |= vehicle->ConverterAllow && vehicle->ConverterAllowLocal;
if( vehicle->Power > 0.01 ) {
IsAnyLineBreakerOpen |= !vehicle->Mains; } } );
// siłę generują pojazdy na pochyleniu ale działa ona całość składu, więc a=F/m
fAccGravity *= iDirection >= 0 ? 1 : -1;
fAccGravity /= fMass;
{
auto absaccs { fAccGravity }; // Ra 2014-03: jesli skład stoi, to działa na niego składowa styczna grawitacji
if( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) {
absaccs = 0;
auto *d = pVehicles[ end::front ]; // pojazd na czele składu
while( d ) {
absaccs += d->MoverParameters->TotalMass * d->MoverParameters->AccS * ( d->DirectionGet() == iDirection ? 1 : -1 );
d = d->Next(); // kolejny pojazd, podłączony od tyłu (licząc od czoła)
}
absaccs *= iDirection >= 0 ? 1 : -1;
absaccs /= fMass;
}
AbsAccS = absaccs;
}
#if LOGVELOCITY
WriteLog( "Vel=" + to_string( DirectionalVel() ) + ", AbsAccS=" + to_string( AbsAccS ) + ", AccGrav=" + to_string( fAccGravity ) );
#endif
if( !Ready // v367: jeśli wg powyższych warunków skład nie jest odhamowany
&& fAccGravity < -0.05 // jeśli ma pod górę na tyle, by się stoczyć
&& fReady < 0.8 ) { // delikatniejszy warunek, obejmuje wszystkie wagony
Ready = true; //żeby uznać za odhamowany
}
// second pass, for diesel engines verify the (live) engines are fully started
// TODO: cache presence of diesel engines in the consist, to skip this test if there isn't any
p = pVehicles[ end::front ]; // pojazd na czole składu
while( true == Ready
&& p != nullptr ) {
auto const *vehicle { p->MoverParameters };
if( has_diesel_engine() ) {
Ready = vehicle->Vel > EU07_AI_MOVEMENT // already moving
|| false == vehicle->Mains // deadweight vehicle
|| vehicle->enrot > 0.8 * (vehicle->EngineType == TEngineType::DieselEngine ? vehicle->dizel_nmin : vehicle->DElist[0].RPM / 60.0);
}
p = p->Next(); // pojazd podłączony z tyłu (patrząc od czoła)
}
// test consist state for external events and/or weird things human user could've done
iEngineActive &=
false == IsAnyConverterOverloadRelayOpen
&& false == IsAnyLineBreakerOpen
&& ( false == IsAnyConverterPresent || true == IsAnyConverterEnabled )
&& ( false == IsAnyCompressorPresent || true == IsAnyCompressorEnabled );
}
void
TController::control_wheelslip() {
// wheel slip
if( mvControlling->SlippingWheels ) {
cue_action( driver_hint::sandingon ); // piasku!
cue_action( driver_hint::tractiveforcedecrease );
cue_action( driver_hint::brakingforcedecrease );
cue_action( driver_hint::antislip );
++iDriverFailCount;
}
else if( std::fabs( mvControlling->Im ) > 0.75 * mvControlling->ImaxHi ) {
cue_action( driver_hint::sandingon ); // piaskujemy tory, coby się nie ślizgać
}
else {
// deactivate sandbox if we aren't slipping
if( mvControlling->SandDose ) {
cue_action( driver_hint::sandingoff );
}
}
}
void
TController::control_pantographs() {
if( mvPantographUnit->EnginePowerSource.SourceType != TPowerSource::CurrentCollector ) { return; }
if( false == mvPantographUnit->PantAutoValve
&& mvOccupied->ScndPipePress > 4.3 ) {
// gdy główna sprężarka bezpiecznie nabije ciśnienie to można przestawić kurek na zasilanie pantografów z głównej pneumatyki
cue_action( driver_hint::pantographairsourcesetmain );
}
// uśrednione napięcie sieci: przy spadku poniżej wartości minimalnej opóźnić rozruch o losowy czas
fVoltage = 0.5 * (fVoltage + std::max( mvControlling->GetTrainsetHighVoltage(), mvControlling->PantographVoltage ) );
if( fVoltage < mvControlling->EnginePowerSource.CollectorParameters.MinV ) {
// gdy rozłączenie WS z powodu niskiego napięcia
if( fActionTime >= PrepareTime ) {
// jeśli czas oczekiwania nie został ustawiony, losowy czas oczekiwania przed ponownym załączeniem jazdy
fActionTime = -2.0 - Random( 10 );
}
}
// raise/lower pantographs as needed
auto const useregularpantographlayout {
pVehicle->Next(coupling::control) == nullptr // standalone
|| is_emu() // special case
|| mvControlling->TrainType == dt_ET41 }; // special case
if( mvOccupied->Vel > EU07_AI_NOMOVEMENT ) {
if( fOverhead2 >= 0.0 || iOverheadZero ) {
// jeśli jazda bezprądowa albo z opuszczonym pantografem
cue_action( driver_hint::mastercontrollersetzerospeed );
}
if( fOverhead2 > 0.0 || iOverheadDown ) {
// jazda z opuszczonymi pantografami
if( mvPantographUnit->Pantographs[ end::front ].is_active ) {
cue_action( driver_hint::frontpantographvalveoff );
}
if( mvPantographUnit->Pantographs[ end::rear ].is_active ) {
cue_action( driver_hint::rearpantographvalveoff );
}
}
else {
// jeśli nie trzeba opuszczać pantografów
// TODO: check if faction alone reduces frequency enough
if( fActionTime > 0.0 ) {
if( mvOccupied->AIHintPantstate == 0 ) {
// jazda na tylnym
if( iDirection >= 0 && useregularpantographlayout ) {
// jak jedzie w kierunku sprzęgu 0
if( mvPantographUnit->PantRearVolt == 0.0
// filter out cases with single _other_ working pantograph so we don't try to raise something we can't
&& ( mvPantographUnit->PantographVoltage == 0.0
|| mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) ) {
cue_action( driver_hint::rearpantographvalveon );
}
}
else {
// jak jedzie w kierunku sprzęgu 0
if( mvPantographUnit->PantFrontVolt == 0.0
// filter out cases with single _other_ working pantograph so we don't try to raise something we can't
&& ( mvPantographUnit->PantographVoltage == 0.0
|| mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) ) {
cue_action( driver_hint::frontpantographvalveon );
}
}
if( mvOccupied->Vel > 5 ) {
// opuszczenie przedniego po rozpędzeniu się o ile jest więcej niż jeden
if( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) {
if( iDirection >= 0 && useregularpantographlayout ) // jak jedzie w kierunku sprzęgu 0
{ // poczekać na podniesienie tylnego
if( mvPantographUnit->PantFrontVolt != 0.0
&& mvPantographUnit->PantRearVolt != 0.0 ) { // czy jest napięcie zasilające na tylnym?
cue_action( driver_hint::frontpantographvalveoff ); // opuszcza od sprzęgu 0
}
}
else { // poczekać na podniesienie przedniego
if( mvPantographUnit->PantRearVolt != 0.0
&& mvPantographUnit->PantFrontVolt != 0.0 ) { // czy jest napięcie zasilające na przednim?
cue_action( driver_hint::rearpantographvalveoff ); // opuszcza od sprzęgu 1
}
}
}
}
}
else {
// use suggested pantograph setup
if( mvOccupied->Vel > 5 ) {
auto const pantographsetup{ mvOccupied->AIHintPantstate };
cue_action(
pantographsetup & 1 << 0 ? driver_hint::frontpantographvalveon : driver_hint::frontpantographvalveoff );
cue_action(
pantographsetup & 1 << 1 ? driver_hint::rearpantographvalveon : driver_hint::rearpantographvalveoff );
}
}
}
}
}
else {
if( mvOccupied->AIHintPantUpIfIdle
&& IdleTime > 45.0
// NOTE: abs(stoptime) covers either at least 15 sec remaining for a scheduled stop, or 15+ secs spent at a basic stop
&& std::abs(fStopTime) > 15.0 ) {
// spending a longer at a stop, raise also front pantograph
if( mvPantographUnit->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) {
if( iDirection >= 0 && useregularpantographlayout ) {
// jak jedzie w kierunku sprzęgu 0
if( mvPantographUnit->PantFrontVolt == 0.0 ) {
cue_action( driver_hint::frontpantographvalveon, 5 ); // discard the hint if speed exceeds 5 km/h
}
}
else {
if( mvPantographUnit->PantRearVolt == 0.0 ) {
cue_action( driver_hint::rearpantographvalveon, 5 ); // discard the hint if speed exceeds 5 km/h
}
}
}
}
}
}
void
TController::control_horns( double const Timedelta ) {
// horn control
if( fWarningDuration > 0.0 ) {
// jeśli pozostało coś do wytrąbienia trąbienie trwa nadal
if( mvOccupied->WarningSignal == 0 ) {
cue_action( driver_hint::hornon, 1 ); // low tone horn by default
}
}
else {
if( mvOccupied->WarningSignal != 0 ) {
cue_action( driver_hint::hornoff ); // a tu się kończy
}
}
if( mvOccupied->Vel > EU07_AI_MOVEMENT ) {
// jesli jedzie, można odblokować trąbienie, bo się wtedy nie włączy
iDrivigFlags &= ~moveStartHornDone; // zatrąbi dopiero jak następnym razem stanie
iDrivigFlags |= moveStartHorn; // i trąbić przed następnym ruszeniem
}
if( true == TestFlag(iDrivigFlags, moveStartHornNow)
&& true == Ready
&& true == iEngineActive
&& mvControlling->MainCtrlPowerPos() > 0 ) {
// uruchomienie trąbienia przed ruszeniem
fWarningDuration = 0.3; // czas trąbienia
cue_action( driver_hint::hornon, pVehicle->iHornWarning ); // wysokość tonu (2=wysoki)
iDrivigFlags |= moveStartHornDone; // nie trąbić aż do ruszenia
iDrivigFlags &= ~moveStartHornNow; // trąbienie zostało zorganizowane
}
}
void
TController::control_security_system( double const Timedelta ) {
if( mvOccupied->SecuritySystem.is_cabsignal_blinking() && mvOccupied->SecuritySystem.has_separate_acknowledge()) {
// jak zadziałało SHP
if( false == is_emu()
&& mvOccupied->DirActive == 0 ) {
cue_action( driver_hint::directionforward );
}
cue_action( driver_hint::shpsystemreset ); // to skasuj
if( BrakeCtrlPosition == 0 // TODO: verify whether it's 0 in all vehicle types
&& AccDesired > 0.0
&& mvOccupied->SecuritySystem.is_braking() ) {
cue_action( driver_hint::trainbrakerelease );
}
}
if( mvOccupied->SecuritySystem.is_blinking() ) {
// jak zadziałało CA/SHP
if( false == is_emu()
&& mvOccupied->DirActive == 0 ) {
cue_action( driver_hint::directionforward );
}
cue_action( driver_hint::securitysystemreset ); // to skasuj
if( BrakeCtrlPosition == 0 // TODO: verify whether it's 0 in all vehicle types
&& AccDesired > 0.0
&& mvOccupied->SecuritySystem.is_braking() ) {
cue_action( driver_hint::trainbrakerelease );
}
}
// basic emergency stop handling, while at it
if( true == mvOccupied->RadioStopFlag // radio-stop
&& mvOccupied->Vel < 0.01 // and actual stop
&& true == mvOccupied->Radio ) { // and we didn't touch the radio yet
// turning off the radio should reset the flag, during security system check
if( m_radiocontroltime > 5.0 ) { // arbitrary delay between stop and disabling the radio
cue_action( driver_hint::radiooff );
}
else {
m_radiocontroltime += Timedelta;
}
}
if( iEngineActive
&& false == mvOccupied->Radio
&& false == mvOccupied->RadioStopFlag ) {
// otherwise if it's safe to do so, turn the radio back on
// arbitrary 5 sec delay before switching radio back on
m_radiocontroltime = std::min( m_radiocontroltime, 5.0 );
if( m_radiocontroltime < 0.0 ) {
cue_action( driver_hint::radioon );
}
else {
m_radiocontroltime -= Timedelta;
}
}
}
void
TController::control_handles() {
switch( mvControlling->EngineType ) {
case TEngineType::ElectricSeriesMotor: {
// styczniki liniowe rozłączone yBARC
if( false == mvControlling->StLinFlag
&& false == mvControlling->DelayCtrlFlag
&& mvControlling->MainCtrlPowerPos() > 1 ) {
cue_action( driver_hint::mastercontrollersetzerospeed );
}
// if the power station is heavily burdened drop down to series mode to reduce the load
auto const useseriesmodevoltage {
std::lerp(
mvControlling->EnginePowerSource.CollectorParameters.MinV,
mvControlling->EnginePowerSource.CollectorParameters.MaxV,
IsHeavyCargoTrain ? 0.35 : 0.40 ) };
if( fVoltage <= useseriesmodevoltage ) {
cue_action( driver_hint::mastercontrollersetseriesmode );
}
// sanity check
if( false == Ready
&& mvControlling->MainCtrlPowerPos() > 1 ) {
cue_action( driver_hint::mastercontrollersetzerospeed );
}
break;
}
case TEngineType::DieselElectric: {
// styczniki liniowe rozłączone yBARC
if( false == mvControlling->StLinFlag
&& false == mvControlling->DelayCtrlFlag
&& mvControlling->MainCtrlPowerPos() > 1 ) {
cue_action( driver_hint::mastercontrollersetzerospeed );
}
// sanity check
if( false == Ready
&& mvControlling->MainCtrlPowerPos() > 1 ) {
cue_action( driver_hint::mastercontrollersetzerospeed );
}
break;
}
default: {
break;
} } // enginetype
}
// activate consist lamp codes
void
TController::control_lights() {
if( is_train() ) {
// jeśli jazda pociągowa
if( true == TestFlag( OrderCurrentGet(), Obey_train ) ) {
// head lights
if( m_lighthints[ end::front ] == -1 ) {
cue_action( driver_hint::headcodepc1 );
}
else if( m_lighthints[ end::front ] == ( light::redmarker_left | light::headlight_right | light::headlight_upper ) ) {
cue_action( driver_hint::headcodepc2 );
}
else {
// custom light pattern
if( AIControllFlag ) {
pVehicles[ end::front ]->RaLightsSet( m_lighthints[ end::front ], -1 );
}
}
// tail lights
if( m_lighthints[ end::rear ] == -1 ) {
cue_action( driver_hint::headcodepc5 );
}
else {
if( AIControllFlag ) {
pVehicles[ end::rear ]->RaLightsSet( -1, m_lighthints[ end::rear ] );
}
}
}
// for shunting mode
else if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) {
cue_action( driver_hint::headcodetb1 );
}
}
else if( is_car() ) {
Lights(
light::headlight_left | light::headlight_right,
light::redmarker_left | light::redmarker_right );
}
}
void
TController::control_compartment_lights() {
// compartment lights
if( mvOccupied->CompartmentLights.start_type != start_t::manual ) { return; }
auto const currentlightstate { mvOccupied->CompartmentLights.is_enabled };
auto const lightlevel { Global.fLuminance * ConsistShade };
auto const desiredlightstate{ (
currentlightstate ?
lightlevel < 0.40 : // turn off if lighting level goes above 0.4
lightlevel < 0.35 ) }; // turn on if lighting level goes below 0.35
if( desiredlightstate != currentlightstate ) {
cue_action( desiredlightstate ? driver_hint::consistlightson : driver_hint::consistlightsoff );
}
}
void
TController::control_doors() {
if( false == doors_open() ) { return; }
// jeżeli jedzie
// nie zamykać drzwi przy drganiach, bo zatrzymanie na W4 akceptuje niewielkie prędkości
if( mvOccupied->Vel > EU07_AI_MOVEMENT ) {
Doors( false );
return;
}
// HACK: crude way to deal with automatic door opening on W4 preventing further ride
// for human-controlled vehicles with no door control and dynamic brake auto-activating with door open
// TODO: check if this situation still happens and the hack is still needed
if( false == AIControllFlag ) {
// for diesel engines react when engine is put past idle revolutions
// for others straightforward master controller check
if( mvControlling->EngineType == TEngineType::DieselEngine ? mvControlling->RList[mvControlling->MainCtrlPos].Mn > 0 : mvControlling->MainCtrlPowerPos() > 0 ) {
Doors( false );
return;
}
}
}
void
TController::UpdateCommand() {
if( false == mvOccupied->CommandIn.Command.empty() ) {
if( false == mvOccupied->RunInternalCommand() ) {
// rozpoznaj komende bo lokomotywa jej nie rozpoznaje
RecognizeCommand(); // samo czyta komendę wstawioną do pojazdu?
}
}
}
void
TController::UpdateNextStop() {
if( fLastStopExpDist > 0.0
&& mvOccupied->DistCounter > fLastStopExpDist ) {
// zaktualizować wyświetlanie rozkładu
TrainParams.StationStart = TrainParams.StationIndex;
fLastStopExpDist = -1.0; // usunąć licznik
if( true == m_makenextstopannouncement ) {
announce( announcement_t::next );
m_makenextstopannouncement = false; // keep next stop announcements suppressed until another scheduled stop
}
}
}
void
TController::determine_braking_distance() {
// przybliżona droga hamowania
// NOTE: we're ensuring some minimal braking distance to reduce ai flipping states between starting and braking
auto const velceil { std::max( 2.0, std::ceil( mvOccupied->Vel ) ) };
fBrakeDist = fDriverBraking * velceil * ( 40.0 + velceil );
if( fMass > 1000000.0 ) {
// korekta dla ciężkich, bo przeżynają - da to coś?
fBrakeDist *= 2.0;
}
if( -fAccThreshold > 0.05
&& mvOccupied->CategoryFlag == 1 ) {
fBrakeDist = velceil * velceil / 25.92 / -fAccThreshold;
}
if( mvOccupied->BrakeDelayFlag == bdelay_G ) {
// dla nastawienia G koniecznie należy wydłużyć drogę na czas reakcji
fBrakeDist += 2 * velceil;
}
if( mvOccupied->Vel > 15.0
&& mvControlling->EngineType == TEngineType::ElectricInductionMotor
&& is_emu() ) {
// HACK: make the induction motor powered EMUs start braking slightly earlier
fBrakeDist += 10.0;
}
/*
// take into account effect of gravity (but to stay on safe side of calculations, only downhill)
if( fAccGravity > 0.025 ) {
fBrakeDist *= ( 1.0 + fAccGravity );
// TBD: use version which shortens route going uphill, too
//fBrakeDist = std::max( fBrakeDist, fBrakeDist * ( 1.0 + fAccGravity ) );
}
*/
}
void
TController::scan_route( double const Range ) {
TableCheck( Range );
}
// check for potential collisions
void
TController::scan_obstacles( double const Range ) {
// HACK: vehicle order in the consist is based on intended travel direction
// if our actual travel direction doesn't match that, we should be scanning from the other end of the consist
// we cast to int to avoid getting confused by microstutters
auto *frontvehicle { pVehicles[ ( static_cast<int>( mvOccupied->V ) * iDirection >= 0 ? end::front : end::rear ) ] };
int routescandirection;
// for moving vehicle determine heading from velocity; for standing fall back on the set direction
if( std::abs(frontvehicle->MoverParameters->V) > 0.5 ? // ignore potential micro-stutters in oposite direction during "almost stop"
frontvehicle->MoverParameters->V > 0.0 :
pVehicle->DirectionGet() == frontvehicle->DirectionGet() ? iDirection >= 0 : iDirection <= 0 ) {
// towards coupler 0
routescandirection = end::front;
}
else {
// towards coupler 1
routescandirection = end::rear;
}
/*
if( pVehicle->MoverParameters->CabOccupied < 0 ) {
// flip the scan direction in the rear cab
routescandirection ^= routescandirection;
}
*/
Obstacle = neighbour_data();
auto const obstaclescanrange { std::max( is_car() ? 250.0 : 1000.0, Range ) };
auto const lookup { frontvehicle->find_vehicle( routescandirection, obstaclescanrange ) };
if( std::get<bool>( lookup ) == true ) {
Obstacle.vehicle = std::get<TDynamicObject *>( lookup );
Obstacle.vehicle_end = std::get<int>( lookup );
Obstacle.distance = std::get<double>( lookup );
if( Obstacle.distance < ( is_car() ? 50 : 100 ) ) {
// at short distances (re)calculate range between couplers directly
Obstacle.distance = TMoverParameters::CouplerDist( frontvehicle->MoverParameters, Obstacle.vehicle->MoverParameters );
}
}
}
void
TController::determine_proximity_ranges() {
// ustalenie dystansów w pozostałych przypadkach
switch (OrderCurrentGet())
{
case Connect: {
// podłączanie do składu
if (iDrivigFlags & moveConnect) {
// jeśli stanął już blisko, unikając zderzenia i można próbować podłączyć
fMinProximityDist = -1.0;
fMaxProximityDist = 0.0; //[m] dojechać maksymalnie
fVelPlus = 1.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu
}
else {
// jak daleko, to jazda jak dla Shunt na kolizję
fMinProximityDist = 2.0;
fMaxProximityDist = 5.0; //[m] w takim przedziale odległości powinien stanąć
fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelMinus = 1.0; // margines prędkości powodujący załączenie napędu
}
break;
}
case Disconnect: {
// 20.07.03 - manewrowanie wagonami
fMinProximityDist = 1.0;
fMaxProximityDist = 10.0; //[m]
fVelPlus = 1.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu
break;
}
case Shunt: {
// na jaką odleglość i z jaką predkością ma podjechać
// TODO: test if we can use the distances calculation from obey_train
fMinProximityDist = std::min( 5 + iVehicles, 25 );
fMaxProximityDist = std::min( 10 + iVehicles, 50 );
// HACK: modern vehicles might brake slower at low speeds, increase safety margin as crude counter
if( mvControlling->EIMCtrlType > 0 ) {
fMinProximityDist += 5.0;
fMaxProximityDist += 5.0;
}
// take into account weather conditions
if (Global.FrictionWeatherFactor < 1.f
&& iVehicles > 1 ) {
fMinProximityDist += 5.0;
fMaxProximityDist += 5.0;
}
fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
// margines prędkości powodujący załączenie napędu
// były problemy z jazdą np. 3km/h podczas ładowania wagonów
fVelMinus = std::min( 0.1 * fShuntVelocity, 3.0 );
break;
}
case Loose_shunt: {
fMinProximityDist = -1.0;
fMaxProximityDist = 0.0; //[m] dojechać maksymalnie
fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelMinus = 0.5; // margines prędkości powodujący załączenie napędu
break;
}
case Obey_train: {
// na jaka odleglosc i z jaka predkoscia ma podjechac do przeszkody
// jeśli pociąg
if( is_train() ) {
fMinProximityDist = std::clamp( 5 + iVehicles, 10, 15 );
fMaxProximityDist = std::clamp( 10 + iVehicles, 15, 40 );
if( IsCargoTrain ) {
// increase distances for cargo trains to take into account slower reaction to brakes
fMinProximityDist += 10.0;
fMaxProximityDist += 10.0;
/*
if( IsHeavyCargoTrain ) {
// cargo trains with high braking threshold may require even larger safety margin
fMaxProximityDist += 20.0;
}
*/
}
if (Global.FrictionWeatherFactor < 1.f
&& iVehicles > 1 ) {
// take into account weather conditions
fMinProximityDist += 5.0;
fMaxProximityDist += 5.0;
}
if( mvOccupied->Vel < 0.1 ) {
// jak stanie za daleko, to niech nie dociąga paru metrów
fMaxProximityDist = 50.0;
}
if( iDrivigFlags & moveLate ) {
// jeśli spóźniony, to gna
fVelMinus = 1.0;
// dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelPlus = 5.0;
}
else {
// gdy nie musi się sprężać
// margines prędkości powodujący załączenie napędu; min 1.0 żeby nie ruszał przy 0.1
fVelMinus = std::clamp( std::round( 0.05 * VelDesired ), 1.0, 5.0 );
// normalnie dopuszczalne przekroczenie to 5% prędkości ale nie więcej niż 5km/h
// bottom margin raised to 2 km/h to give the AI more leeway at low speed limits
fVelPlus = std::clamp( std::ceil( 0.05 * VelDesired ), 2.0, 5.0 );
}
}
// samochod (sokista też)
else {
fMinProximityDist = std::max( 3.5, mvOccupied->Vel * 0.2 );
fMaxProximityDist = std::max( 9.5, mvOccupied->Vel * 0.375 ); //[m]
if( Global.FrictionWeatherFactor < 1.f ) {
// take into account weather conditions
fMinProximityDist += 0.75;
fMaxProximityDist += 0.75;
}
// margines prędkości powodujący załączenie napędu
fVelMinus = 2.0;
// dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelPlus = std::min( 10.0, mvOccupied->Vel * 0.1 );
}
break;
}
case Bank: {
// TODO: implement
break;
}
default: {
fMinProximityDist = 5.0;
fMaxProximityDist = 10.0; //[m]
fVelPlus = 2.0; // dopuszczalne przekroczenie prędkości na ograniczeniu bez hamowania
fVelMinus = 5.0; // margines prędkości powodujący załączenie napędu
}
} // switch OrderList[OrderPos]
}
void
TController::check_departure() {
if(( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank | Connect ) ) == 0 ) { return; }
// odjechać sam może tylko jeśli jest w trybie jazdy
// automatyczne ruszanie po odstaniu albo spod SBL
if( VelSignal == 0.0
&& WaitingTime > 0.0
&& mvOccupied->RunningTrack.Velmax != 0.0 ) {
// jeśli stoi, a upłynął czas oczekiwania i tor ma niezerową prędkość
if( OrderCurrentGet() & (Shunt | Loose_shunt | Obey_train | Bank)
&& iDrivigFlags & moveStopHere ) {
// zakaz ruszania z miejsca bez otrzymania wolnej drogi
WaitingTime = -WaitingExpireTime;
}
else {
if( mvOccupied->CategoryFlag & 1 ) { // jeśli pociąg
PrepareDirection(); // zmieni ustawiony kierunek
SetVelocity( 20, 20 ); // jak się nastał, to niech jedzie 20km/h
WaitingTime = 0.0;
fWarningDuration = 1.5; // a zatrąbić trochę
}
else { // samochód ma stać, aż dostanie odjazd, chyba że stoi przez kolizję
if( eStopReason == stopBlock
&& Obstacle.distance > fDriverDist ) {
PrepareDirection(); // zmieni ustawiony kierunek
SetVelocity( -1, -1 ); // jak się nastał, to niech jedzie
WaitingTime = 0.0;
}
}
}
}
else if( VelSignal == 0.0 && VelNext > 0.0 && mvOccupied->Vel < 1.0 ) {
if( iCoupler > 0 || (iDrivigFlags & moveStopHere) == 0 ) {
// Ra: tu jest coś nie tak, bo bez tego warunku ruszało w manewrowym !!!!
SetVelocity( VelNext, VelNext, stopSem ); // omijanie SBL
}
}
}
// verify progress of load exchange
void
TController::check_load_exchange() {
ExchangeTime = 0.f;
DoesAnyDoorNeedOpening = false;
if( fStopTime > 0 ) { return; }
// czas postoju przed dalszą jazdą (np. na przystanku)
auto *vehicle { pVehicles[ end::front ] };
while( vehicle != nullptr ) {
auto const vehicleexchangetime { vehicle->LoadExchangeTime() };
DoesAnyDoorNeedOpening |= vehicleexchangetime > 0 && vehicle->LoadExchangeSpeed() == 0;
ExchangeTime = std::max( ExchangeTime, vehicleexchangetime );
vehicle = vehicle->Next();
}
if( pVehicle->DirectionGet() != m_lastexchangedirection ) {
// generally means the ai driver moved to the opposite end of the consist
// TODO: investigate whether user playing with the reverser can mess this up
auto const left { m_lastexchangedirection > 0 ? 1 : 2 };
auto const right { 3 - left };
m_lastexchangeplatforms =
( ( m_lastexchangeplatforms & left ) != 0 ? right : 0 )
+ ( ( m_lastexchangeplatforms & right ) != 0 ? left : 0 );
m_lastexchangedirection = pVehicle->DirectionGet();
}
if( false == TrainParams.IsMaintenance()
&& ( false == TestFlag(iDrivigFlags, moveDoorOpened)
|| true == DoesAnyDoorNeedOpening ) ) {
iDrivigFlags |= moveDoorOpened; // nie wykonywać drugi raz
remove_hint( driver_hint::doorleftopen );
remove_hint( driver_hint::doorrightopen );
Doors( true, m_lastexchangeplatforms );
}
}
void
TController::UpdateChangeDirection() {
// TODO: rework into driver mode independent routine
if( false == TestFlag( OrderCurrentGet(), Change_direction ) ) { return; }
if( true == AIControllFlag ) {
// sprobuj zmienic kierunek (może być zmieszane z jeszcze jakąś komendą)
if( mvOccupied->Vel < EU07_AI_NOMOVEMENT ) {
// jeśli się zatrzymał, to zmieniamy kierunek jazdy, a nawet kabinę/człon
Activation(); // ustawienie zadanego wcześniej kierunku i ewentualne przemieszczenie AI
} // Change_direction (tylko dla AI)
}
// shared part of the routine, implement if direction matches what was requested
if( mvOccupied->Vel < EU07_AI_NOMOVEMENT
&& iDirection == iDirectionOrder ) {
// NOTE: we can't be sure there's a visible signal within scan range after direction change
// which would normally overwrite the old limit, so we reset signal value manually here
VelSignal = -1;
VelSignalNext = -1;
PrepareEngine();
JumpToNextOrder(); // następnie robimy, co jest do zrobienia (Shunt albo Obey_train)
if( OrderCurrentGet() & ( Shunt | Loose_shunt | Connect ) ) {
// jeśli dalej mamy manewry
if( false == TestFlag( iDrivigFlags, moveStopHere ) ) {
// o ile nie ma stać w miejscu,
// jechać od razu w przeciwną stronę i nie trąbić z tego tytułu:
iDrivigFlags &= ~moveStartHorn;
SetVelocity( fShuntVelocity, fShuntVelocity );
}
}
}
}
void
TController::UpdateLooseShunt() {
// when loose shunting try to detect situations where engaged brakes in a consist to be pushed prevent movement
// TODO: run also for potential settings-based virtual assistant
auto const autobrakerelease { ( true == AIControllFlag ) };
if( autobrakerelease
&& Obstacle.distance < 1.0
&& AccDesired > 0.1
&& mvOccupied->Vel < 1.0 ) {
auto *vehicle { Obstacle.vehicle };
auto const direction { ( vehicle->Prev() != nullptr ? end::front : end::rear ) };
while( vehicle != nullptr ) {
if( vehicle->MoverParameters->BrakePress > 0.2 ) {
vehicle->MoverParameters->BrakeLevelSet( 0 ); // hamulec na zero, aby nie hamował
vehicle->MoverParameters->BrakeReleaser( 1 ); // wyluzuj pojazd, aby dało się dopychać
}
// NOTE: we trust the consist to be arranged in a valid chain
// TBD, TODO: traversal direction validation?
vehicle = direction == end::front ? vehicle->Prev() : vehicle->Next();
}
}
}
void
TController::UpdateObeyTrain() {
UpdateNextStop();
if( (iDrivigFlags & moveGuardSignal) != 0
&& VelDesired > 0.0 ) {
// komunikat od kierownika tu, bo musi być wolna droga i odczekany czas stania
iDrivigFlags &= ~moveGuardSignal; // tylko raz nadać
iDrivigFlags &= ~moveGuardOpenDoor; // nie trzeba już otwierać drzwi
if( false == tsGuardSignal.empty() ) {
tsGuardSignal.stop();
// w zasadzie to powinien mieć flagę, czy jest dźwiękiem radiowym, czy bezpośrednim
// albo trzeba zrobić dwa dźwięki, jeden bezpośredni, słyszalny w
// pobliżu, a drugi radiowy, słyszalny w innych lokomotywach
// na razie zakładam, że to nie jest dźwięk radiowy, bo trzeba by zrobić
// obsługę kanałów radiowych itd.
if( iGuardRadio == 0 ) {
// jeśli nie przez radio
tsGuardSignal.owner( pVehicle );
// place virtual conductor some distance away
tsGuardSignal.offset( { pVehicle->MoverParameters->Dim.W * -0.75f, 1.7f, std::min( -20.0, -0.2 * fLength ) } );
tsGuardSignal.play( sound_flags::exclusive );
// NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera
fActionTime = -5.0; // niech trochę potrzyma
}
else {
// radio message
tsGuardSignal.owner( pVehicle );
/*
tsGuardSignal.offset( { 0.f, 2.f, pVehicle->MoverParameters->Dim.L * 0.4f * ( pVehicle->MoverParameters->CabOccupied < 0 ? -1 : 1 ) } );
tsGuardSignal.play( sound_flags::exclusive );
*/
simulation::radio_message( &tsGuardSignal, iGuardRadio );
// NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera
fActionTime = -5.0; // niech trochę potrzyma
}
}
}
}
void
TController::UpdateConnect() {
// podłączanie do składu
if (iDrivigFlags & moveConnect) {
// sprzęgi sprawdzamy w pierwszej kolejności, bo jak połączony, to koniec
auto *vehicle { iCouplingVehicle.value().first };
auto const couplingend { iCouplingVehicle.value().second };
auto *vehicleparameters { vehicle->MoverParameters };
if( vehicleparameters->Couplers[ couplingend ].CouplingFlag != iCoupler ) {
auto const &neighbour { vehicleparameters->Neighbours[ couplingend ] };
if( neighbour.vehicle != nullptr ) {
if( neighbour.distance < 10 ) {
// check whether we need to attach coupler adapter
auto coupleriscompatible { true };
auto const &coupler { vehicleparameters->Couplers[ couplingend ] };
if( coupler.type() != TCouplerType::Automatic ) {
auto const &othercoupler = neighbour.vehicle->MoverParameters->Couplers[ ( neighbour.vehicle_end != 2 ? neighbour.vehicle_end : coupler.ConnectedNr ) ];
if( othercoupler.type() == TCouplerType::Automatic ) {
coupleriscompatible = false;
cue_action( driver_hint::couplingadapterattach, couplingend );
}
}
// próba podczepienia
if( AIControllFlag || Global.AITrainman ) {
if( coupleriscompatible && neighbour.distance < 2 ) {
vehicleparameters->Attach(
couplingend, neighbour.vehicle_end,
neighbour.vehicle->MoverParameters,
iCoupler );
}
}
}
}
}
// NOTE: no else as the preceeding block can potentially establish expected connection
if( vehicleparameters->Couplers[ couplingend ].CouplingFlag == iCoupler ) {
// jeżeli został podłączony
CheckVehicles( Connect ); // sprawdzić światła nowego składu
iCoupler = 0; // dalsza jazda manewrowa już bez łączenia
iCouplingVehicle.reset();
iDrivigFlags &= ~moveConnect; // zdjęcie flagi doczepiania
JumpToNextOrder( true ); // wykonanie następnej komendy
}
} // moveConnect
else {
// początek podczepiania, z wyłączeniem sprawdzania fTrackBlock
if( Obstacle.distance <= 20.0 ) {
if( !iCouplingVehicle ) {
// write down which vehicle should be coupled with the target consist,
// so we don't lose track of it if the user does something unexpected
iCouplingVehicle = {
pVehicles[ end::front ],
( pVehicles[ end::front ]->DirectionGet() > 0 ?
end::front :
end::rear ) };
iDrivigFlags |= moveConnect;
}
}
}
}
void
TController::GuardOpenDoor() {
if ((iDrivigFlags & moveGuardOpenDoor) != 0) {
auto *vehicle{ pVehicles[end::front] };
while (vehicle != nullptr && vehicle->MoverParameters->Doors.range == 0) {
vehicle = vehicle->Next();
}
if (vehicle != nullptr && vehicle->MoverParameters->Doors.range > 0) {
auto const lewe = vehicle->DirectionGet() > 0 ? 1 : 2;
auto const prawe = 3 - lewe;
if (m_lastexchangeplatforms & lewe) {
vehicle->MoverParameters->OperateDoors(side::left, true, range_t::local);
}
if (m_lastexchangeplatforms & prawe) {
vehicle->MoverParameters->OperateDoors(side::right, true, range_t::local);
}
}
else {
iDrivigFlags &= ~moveGuardOpenDoor;
}
}
}
int
TController::unit_count( int const Threshold ) const {
auto *vehicle { pVehicle };
auto unitcount { 1 };
do {
auto const decoupledend{ ( vehicle->DirectionGet() > 0 ? // numer sprzęgu od strony czoła składu
end::rear :
end::front ) };
auto const coupling { vehicle->MoverParameters->Couplers[ decoupledend ].CouplingFlag };
if( coupling == coupling::faux ) {
break;
}
// jeżeli sprzęg zablokowany to liczymy człony jako jeden
if( ( coupling & coupling::permanent ) == 0 ) {
++unitcount;
}
vehicle = vehicle->Next();
} while( unitcount < Threshold );
return unitcount;
}
void
TController::UpdateDisconnect() {
if( iVehicleCount >= 0 ) {
// early test for human drivers, who might disregard the proper procedure, or perform it before they receive the order to
if( false == AIControllFlag ) {
// iVehicleCount = 0 means the consist should be reduced to (1) leading unit, thus we increase our test values by 1
if( unit_count( iVehicleCount + 2 ) <= iVehicleCount + 1 ) {
iVehicleCount = -2; // odczepiono, co było do odczepienia
return; // we'll wrap up the procedure on the next update beat
}
}
// regular uncoupling procedure, performed by ai and naively expected from the human drivers
// 3rd stage: change direction, compress buffers and uncouple
if( iDirection != iDirectionOrder ) {
cue_action( driver_hint::mastercontrollersetreverserunlock );
cue_action( driver_hint::directionother );
}
if( (iDrivigFlags & movePress) != 0 && iDirection == iDirectionOrder ) {
if( iVehicleCount >= 0 ) {
// zmieni się po odczepieniu
WriteLog( "Uncoupling [" + mvOccupied->Name + "]: actuating and compressing buffers..." );
cue_action( driver_hint::independentbrakerelease );
cue_action( driver_hint::bufferscompress );
}
WriteLog( "Uncoupling [" + mvOccupied->Name + "]: from " + ( mvOccupied->DirAbsolute > 0 ? "front" : "rear" ) );
auto *decoupledvehicle { pVehicle }; // pojazd do odczepienia, w (pVehicle) siedzi AI
int decoupledend; // numer sprzęgu, który sprawdzamy albo odczepiamy
auto vehiclecount { iVehicleCount }; // ile wagonów ma zostać
// szukanie pojazdu do odczepienia
do {
decoupledend = decoupledvehicle->DirectionGet() > 0 ? // numer sprzęgu od strony czoła składu
end::front :
end::rear;
// jeżeli sprzęg zablokowany to liczymy człony jako jeden
if( decoupledvehicle->MoverParameters->Couplers[ decoupledend ].CouplingFlag & coupling::permanent ) {
++vehiclecount;
}
if( decoupledvehicle != pVehicle ) {
decoupledvehicle->MoverParameters->BrakeReleaser( 1 ); // wyluzuj pojazd, aby dało się dopychać
}
if( vehiclecount ) { // jeśli jeszcze nie koniec
decoupledvehicle = decoupledvehicle->Prev(); // kolejny w stronę czoła składu (licząc od tyłu), bo dociskamy
if( decoupledvehicle == nullptr ) {
vehiclecount = 0; // nie ma co dalej sprawdzać, odczepianie zakończone
}
}
} while( vehiclecount-- );
if( decoupledvehicle == nullptr ) {
// no target, or already just virtual coupling
WriteLog( "Uncoupling [" + mvOccupied->Name + "]: didn't find anything to disconnect" );
iVehicleCount = -2; // odczepiono, co było do odczepienia
}
else {
if( AIControllFlag || Global.AITrainman ) {
decoupledvehicle->Dettach( decoupledend );
}
// tylko jeśli odepnie
if( decoupledvehicle->MoverParameters->Couplers[ decoupledend ].CouplingFlag == coupling::faux ) {
WriteLog( "Uncoupling [" + mvOccupied->Name + "]: uncoupled" );
iVehicleCount = -2;
// update trainset state
CheckVehicles( Disconnect );
// potentially remove coupler adapter
/*
if( decoupledvehicle->MoverParameters->Couplers[ decoupledend ].has_adapter() ) {
decoupledvehicle->remove_coupler_adapter( decoupledend );
}
*/
if( pVehicles[ end::front ]->MoverParameters->Couplers[ decoupledend ].has_adapter() ) {
cue_action( driver_hint::couplingadapterremove, decoupledend );
}
}
} // a jak nie, to dociskać dalej
}
// 2nd stage: apply consist brakes and change direction
if( ( iDrivigFlags & movePress ) == 0 ) {
// store initial consist direction
if( !iDirectionBackup ) {
iDirectionBackup = iDirection;
}
if( false == IsConsistBraked ) {
WriteLog( "Uncoupling [" + mvOccupied->Name + "]: applying consist brakes..." );
cue_action( driver_hint::trainbrakeapply );
}
// jeśli w miarę został zahamowany (ciśnienie mniejsze niż podane na pozycji 3, zwyle 0.37)
else {
WriteLog( "Uncoupling [" + mvOccupied->Name + "]: direction change" );
/* // TODO: test if this block is needed
if( BrakeSystem == TBrakeSystem::ElectroPneumatic ) {
// wyłączenie EP, gdy wystarczy (może nie być potrzebne, bo na początku jest)
mvOccupied->BrakeLevelSet( 0 );
}
*/
iDirectionOrder = -iDirection; // zmiana kierunku jazdy na przeciwny (dociskanie)
iDrivigFlags |= movePress; // następnie będzie dociskanie
}
}
} // odczepianie
if( iVehicleCount < 0 ) {
// 4th stage: restore initial direction
if( iDirectionBackup ) {
iDirectionOrder = iDirectionBackup.value();
iDirectionBackup.reset();
}
if( iDirection != iDirectionOrder ) {
WriteLog( "Uncoupling [" + mvOccupied->Name + "]: second direction change" );
cue_action( driver_hint::mastercontrollersetreverserunlock );
cue_action( driver_hint::directionother ); // zmiana kierunku jazdy na właściwy
}
// 5th stage: clean up and move on to next order
if( iDirection == iDirectionOrder ) {
iDrivigFlags &= ~movePress; // koniec dociskania
JumpToNextOrder( true ); // zmieni światła
TableClear(); // skanowanie od nowa
iDrivigFlags &= ~moveStartHorn; // bez trąbienia przed ruszeniem
}
}
}
void
TController::handle_engine() {
// HACK: activate route scanning if an idling vehicle is activated by a human user
if( OrderCurrentGet() == Wait_for_orders
&& false == iEngineActive
// && ( false == AIControllFlag )
&& true == mvOccupied->Power24vIsAvailable ) {
OrderNext( Prepare_engine );
}
// basic engine preparation
if( OrderCurrentGet() == Prepare_engine ) {
if( PrepareEngine() ) { // gotowy do drogi?
JumpToNextOrder();
}
}
// engine state can potentially deteriorate in one of usual driving modes
if( OrderCurrentGet() & (Change_direction | Connect | Disconnect | Shunt | Loose_shunt | Obey_train | Bank)
&& false == iEngineActive ) {
// jeśli coś ma robić to niech odpala do skutku
PrepareEngine();
}
}
void
TController::handle_orders() {
switch (OrderCurrentGet())
{
case Release_engine: {
if( ReleaseEngine() ) { // zdana maszyna?
JumpToNextOrder();
}
break;
}
case Obey_train: {
UpdateObeyTrain();
break;
}
case Loose_shunt: {
UpdateLooseShunt();
break;
}
case Connect: {
UpdateConnect();
break;
}
case Disconnect: {
UpdateDisconnect();
break;
}
case Jump_to_first_order: {
if( OrderPos > 1 ) {
OrderPos = 1; // w zerowym zawsze jest czekanie
}
else {
++OrderPos;
}
#if LOGORDERS
OrdersDump("Jump_to_first_order");
#endif
break;
}
default: {
// special case, change_direction potentially received during partially completed coupling operation
// this is unlikely to happen for the AI, but can happen for a (slower) human user
if( TestFlag( iDrivigFlags, moveConnect ) ) {
UpdateConnect();
}
break;
}
} // switch OrderList[OrderPos]
UpdateChangeDirection();
}
void
TController::pick_optimal_speed( double const Range ) {
// if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank | Connect | Disconnect | Change_direction ) ) == 0 ) { return; }
// set initial velocity and acceleration values
SetDriverPsyche(); // ustawia AccPreferred (potrzebne tu?)
VelDesired = fVelMax; // wstępnie prędkość maksymalna dla pojazdu(-ów), będzie następnie ograniczana
AccDesired = AccPreferred; // AccPreferred wynika z osobowości mechanika
// nie przekraczać rozkladowej
if( (OrderCurrentGet() & Obey_train) != 0
&& TrainParams.TTVmax > 0.0 ) {
VelDesired =
min_speed(
VelDesired,
TrainParams.TTVmax );
}
// VelNext = VelDesired; // maksymalna prędkość wynikająca z innych czynników niż trajektoria ruchu
// HACK: -1 means we can pick up 0 speed limits in ( point.velnext > velnext ) tests aimed to find highest next speed,
// but also doubles as 'max speed' in min_speed tests, so it shouldn't interfere if we don't find any speed limits
VelNext = -1.0;
// basic velocity and acceleration adjustments
// jeśli manewry, to ograniczamy prędkość
if( ( OrderCurrentGet() & ( Obey_train | Bank ) ) == 0 ) { // spokojne manewry
SetVelocity( fShuntVelocity, fShuntVelocity );
VelDesired =
min_speed(
VelDesired,
fShuntVelocity );
}
// uncoupling mode changes velocity/acceleration between stages
if( ( OrderCurrentGet() & Disconnect ) != 0 ) {
if( iVehicleCount >= 0 ) {
// 3rd stage: compress buffers and uncouple
if( (iDrivigFlags & movePress) != 0 && iDirection == iDirectionOrder ) {
SetVelocity( 2, 0 ); // jazda w ustawionym kierunku z prędkością 2
}
// 1st stage: bring it to stop
// 2nd stage: apply consist brakes and change direction
else {
SetVelocity( 0, 0, stopJoin );
}
}
else {
// 4th stage: restore initial direction
if( ( iDrivigFlags & movePress ) != 0 ) {
SetVelocity( 0, 0, stopJoin );
}
}
}
VelLimitLastDist = { VelDesired, -1 };
SwitchClearDist = -1;
ActualProximityDist = Range; // funkcja Update() może pozostawić wartości bez zmian
// if we're idling bail out early
if( false == is_active() ) {
VelDesired = 0.0;
VelNext = 0.0;
AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION );
return;
}
// Ra: odczyt (ActualProximityDist), (VelNext) i (AccPreferred) z tabelki prędkosci
check_route_ahead( Range );
check_departure();
// if ordered to turn off the vehicle, try to stop
if( true == TestFlag( OrderCurrentGet(), Release_engine ) ) {
SetVelocity( 0, 0, stopSleep );
}
// if ordered to change direction, try to stop
if( true == TestFlag( OrderCurrentGet(), Change_direction ) ) {
SetVelocity( 0, 0, stopDir );
}
adjust_desired_speed_for_obstacles();
adjust_desired_speed_for_limits();
adjust_desired_speed_for_target_speed( Range );
adjust_desired_speed_for_current_speed();
adjust_desired_speed_for_braking_test();
// Ra 2F1I: wyłączyć kiedyś to uśrednianie i przeanalizować skanowanie, czemu migocze
if( AccDesired > EU07_AI_NOACCELERATION ) { // hamowania lepeiej nie uśredniać
AccDesired = fAccDesiredAv =
0.2 * AccDesired +
0.8 * fAccDesiredAv; // uśrednione, żeby ograniczyć migotanie
}
if( VelDesired == 0.0 ) {
// Ra 2F1J: jeszcze jedna prowizoryczna łatka
AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION );
}
// last step sanity check, until the whole calculation is straightened out
AccDesired = std::min( AccDesired, AccPreferred );
AccDesired = std::clamp(
AccDesired,
is_car() ? -2.0 : -0.9,
is_car() ? 2.0 : 0.9 );
// if the route ahead is blocked we might need to head the other way
check_route_behind( 1000 ); // NOTE: legacy scan range value
}
void
TController::adjust_desired_speed_for_obstacles() {
// prędkość w kierunku jazdy, ujemna gdy jedzie w przeciwną stronę niż powinien
auto const vel { DirectionalVel() };
if (VelDesired < 0.0)
VelDesired = fVelMax; // bo VelDesired<0 oznacza prędkość maksymalną
// Ra: jazda na widoczność
if( Obstacle.distance < 5000 ) {
// mamy coś z przodu
// prędkość pojazdu z przodu (zakładając, że jedzie w tę samą stronę!!!)
auto const k { Obstacle.vehicle->MoverParameters->Vel };
if( k - vel < 5 ) {
// porównanie modułów prędkości [km/h]
// zatroszczyć się trzeba, jeśli tamten nie jedzie znacząco szybciej
ActualProximityDist = std::min<double>(
ActualProximityDist,
Obstacle.distance );
if( Obstacle.distance <= (
mvOccupied->CategoryFlag & 2 ?
100.0 : // cars
250.0 ) ) { // others
// regardless of driving mode at close distance take precaution measures:
// match the other vehicle's speed or slow down if the other vehicle is stopped
VelDesired =
min_speed(
VelDesired,
std::max(
k,
mvOccupied->CategoryFlag & 2 ?
40.0 : // cars
20.0 ) ); // others
if( vel > VelDesired + fVelPlus ) {
// if going too fast force some prompt braking
AccPreferred = std::min(
mvOccupied->CategoryFlag & 2 ? -0.65 : // cars
-0.30, // others
AccPreferred );
}
}
double const distance = Obstacle.distance - fMaxProximityDist - fBrakeDist * 1.15; // odległość bezpieczna zależy od prędkości
if( distance < 0.0 ) {
// jeśli odległość jest zbyt mała
if( k < 10.0 ) // k - prędkość tego z przodu
{ // jeśli tamten porusza się z niewielką prędkością albo stoi
// keep speed difference within a safer margin
VelDesired = std::floor(
min_speed(
VelDesired,
Obstacle.distance > 100 ? k + 20.0 : std::min(8.0, k + 4.0) ) );
if( ( OrderCurrentGet() & ( Connect | Loose_shunt ) ) != 0 ) {
// jeśli spinanie, to jechać dalej
AccPreferred = std::min( 0.35, AccPreferred ); // nie hamuj
VelNext = std::floor( std::min( 8.0, k + 2.0 ) ); // i pakuj się na tamtego
}
else {
// a normalnie to hamować
VelNext = 0.0;
if( Obstacle.distance <= fMinProximityDist ) {
VelDesired = 0.0;
}
if( mvOccupied->CategoryFlag & 1
&& OrderCurrentGet() & Obey_train ) {
// trains which move normally should try to stop at safe margin
ActualProximityDist -= fDriverDist;
}
}
}
else {
// jeśli oba jadą, to przyhamuj lekko i ogranicz prędkość
if( Obstacle.distance < (
mvOccupied->CategoryFlag & 2 ?
fMaxProximityDist + 0.5 * vel : // cars
2.0 * fMaxProximityDist + 2.0 * vel ) ) { //others
// jak tamten jedzie wolniej a jest w drodze hamowania
AccPreferred = std::min( -0.9, AccPreferred );
VelNext = min_speed( std::round( k ) - 5.0, VelDesired );
if( Obstacle.distance <= (
mvOccupied->CategoryFlag & 2 ?
fMaxProximityDist : // cars
2.0 * fMaxProximityDist ) ) { //others
// try to force speed change if obstacle is really close
VelDesired = VelNext;
}
}
}
ReactionTime = mvOccupied->Vel > EU07_AI_NOMOVEMENT ? 0.1 : // orientuj się, bo jest goraco
2.0; // we're already standing still, so take it easy
}
else {
if( OrderCurrentGet() & Connect ) {
// if there's something nearby in the connect mode don't speed up too much
VelDesired =
min_speed(
VelDesired,
Obstacle.distance > 100 ? 20.0 : 4.0 );
}
}
}
}
}
void
TController::adjust_desired_speed_for_limits() {
// speed caps checks
// sprawdzamy możliwe ograniczenia prędkości
if( VelSignal >= 0 ) {
// jeśli skład był zatrzymany na początku i teraz już może jechać
VelDesired =
min_speed(
VelDesired,
VelSignal );
}
if( mvOccupied->RunningTrack.Velmax >= 0 ) {
// ograniczenie prędkości z trajektorii ruchu
VelDesired =
min_speed(
VelDesired,
mvOccupied->RunningTrack.Velmax ); // uwaga na ograniczenia szlakowej!
}
if( VelforDriver >= 0 ) {
// tu jest zero przy zmianie kierunku jazdy
// Ra: tu może być 40, jeśli mechanik nie ma znajomości szlaaku, albo kierowca jeździ 70
VelDesired =
min_speed(
VelDesired,
VelforDriver );
}
// recalculate potential load exchange duration
check_load_exchange();
if( ExchangeTime > 0
|| mvOccupied->Vel > 2.0 ) { // HACK: force timer reset if the load exchange is cancelled due to departure
WaitingSet( ExchangeTime );
}
if( fStopTime < 0 ) {
VelDesired = 0.0; // jak ma czekać, to nie ma jazdy
cue_action( driver_hint::waitloadexchange );
return; // speed limit can't get any lower
}
if( ( OrderCurrentGet() & ( Shunt | Loose_shunt | Obey_train | Bank ) ) != 0 ) {
// w Connect nie, bo moveStopHere odnosi się do stanu po połączeniu
if( (iDrivigFlags & moveStopHere) != 0
&& mvOccupied->Vel < 0.01
&& VelSignalNext == 0.0 ) {
// jeśli ma czekać na wolną drogę, stoi a wyjazdu nie ma, to ma stać
VelDesired = 0.0;
return; // speed limit can't get any lower
}
}
if( OrderCurrentGet() == Wait_for_orders ) {
// wait means sit and wait
VelDesired = 0.0;
return; // speed limit can't get any lower
}
}
void
TController::adjust_desired_speed_for_target_speed( double const Range ) {
// ustalanie zadanego przyspieszenia
//(ActualProximityDist) - odległość do miejsca zmniejszenia prędkości
//(AccPreferred) - wynika z psychyki oraz uwzglęnia już ewentualne zderzenie z pojazdem z przodu, ujemne gdy należy hamować
//(AccDesired) - uwzględnia sygnały na drodze ruchu, ujemne gdy należy hamować
//(fAccGravity) - chwilowe przspieszenie grawitacyjne, ujemne działa przeciwnie do zadanego kierunku jazdy
//(AbsAccS) - chwilowe przyspieszenie pojazu (uwzględnia grawitację), ujemne działa przeciwnie do zadanego kierunku jazdy
//(AccDesired) porównujemy z (fAccGravity) albo (AbsAccS)
// gdy jedzie wolniej niż potrzeba, albo nie ma przeszkód na drodze
// normalna jazda
AccDesired = VelDesired != 0.0 ? AccPreferred : -0.01;
auto const vel { DirectionalVel() };
if( VelNext >= 0.0
&& ActualProximityDist <= Range
&& vel >= VelNext ) {
// gdy zbliża się i jest za szybki do nowej prędkości, albo stoi na zatrzymaniu
if (vel > EU07_AI_NOMOVEMENT) {
// jeśli jedzie
if( vel < VelNext
&& ActualProximityDist > fMaxProximityDist * (1.0 + 0.1 * vel) ) {
// jeśli jedzie wolniej niż można i jest wystarczająco daleko, to można przyspieszyć
if( AccPreferred > 0.0 ) {
// jeśli nie ma zawalidrogi dojedz do semafora/przeszkody
AccDesired = AccPreferred;
}
}
else if (ActualProximityDist > fMinProximityDist) {
// jedzie szybciej, niż trzeba na końcu ActualProximityDist, ale jeszcze jest daleko
if (ActualProximityDist < fMaxProximityDist) {
// jak minął już maksymalny dystans po prostu hamuj (niski stopień)
// ma stanąć, a jest w drodze hamowania albo ma jechać
/*
VelDesired = min_speed( VelDesired, VelNext );
*/
if( VelNext == 0.0 ) {
if( mvOccupied->CategoryFlag & 1 ) {
// trains
if( OrderCurrentGet() & (Shunt | Connect)
&& Obstacle.distance < 50 ) {
// crude detection of edge case, if approaching another vehicle coast slowly until min distance
// this should allow to bunch up trainsets more on sidings
VelDesired = min_speed( 5.0, VelDesired );
}
else {
// hamowanie tak, aby stanąć
VelDesired = VelNext;
AccDesired = ( VelNext * VelNext - vel * vel ) / ( 25.92 * ( ActualProximityDist + 0.1 - 0.5*fMinProximityDist ) );
AccDesired = std::min( AccDesired, fAccThreshold );
}
}
else {
// for cars (and others) coast at low speed until we hit min proximity range
VelDesired = min_speed( VelDesired, 5.0 );
}
}
}
else {
// outside of max safe range
AccDesired = AccPreferred;
if( vel > min_speed( ActualProximityDist > 10.0 ? 10.0 : 5.0, VelDesired ) ) {
// allow to coast at reasonably low speed
auto const brakingdistance { fBrakeDist * braking_distance_multiplier( VelNext ) };
auto const slowdowndistance { (
mvOccupied->CategoryFlag == 2 ? // cars can stop on a dime, for bigger vehicles we enforce some minimal braking distance
brakingdistance :
std::max(
(OrderCurrentGet() & Connect) == 0 ? 100.0 : 25.0,
brakingdistance ) ) };
if( brakingdistance + std::max(slowdowndistance, fMaxProximityDist) >= ActualProximityDist - fMaxProximityDist ) {
// don't slow down prematurely; as long as we have room to come to a full stop at a safe distance, we're good
// ensure some minimal coasting speed, otherwise a vehicle entering this zone at very low speed will be crawling forever
auto const brakingpointoffset = VelNext * braking_distance_multiplier( VelNext );
AccDesired = std::min(
AccDesired,
( VelNext * VelNext - vel * vel )
/ ( 25.92
* std::max(
ActualProximityDist - brakingpointoffset,
std::min(
ActualProximityDist,
brakingpointoffset ) )
+ 0.1 ) ); // najpierw hamuje mocniej, potem zluzuje
}
}
}
AccDesired = std::min( AccDesired, AccPreferred );
}
else {
// jest bliżej niż fMinProximityDist
// utrzymuj predkosc bo juz blisko
/*
VelDesired = min_speed( VelDesired, VelNext );
*/
if( VelNext == 0.0 ) {
VelDesired = VelNext;
}
else {
if( vel <= VelNext + fVelPlus ) {
// jeśli niewielkie przekroczenie, ale ma jechać
AccDesired = std::max( 0.0, AccPreferred ); // to olej (zacznij luzować)
}
}
ReactionTime = 0.1; // i orientuj się szybciej
}
}
else {
// zatrzymany (vel==0.0)
if( VelNext > 0.0 ) {
// można jechać
AccDesired = AccPreferred;
}
else {
// jeśli daleko jechać nie można
if( ActualProximityDist > (
mvOccupied->CategoryFlag & 2 ?
fMinProximityDist : // cars
fMaxProximityDist ) ) { // trains and others
// ale ma kawałek do sygnalizatora
if( AccPreferred > 0.0 ) {
// dociagnij do semafora;
AccDesired = AccPreferred;
}
else {
//stoj
VelDesired = 0.0;
}
}
else {
// VelNext=0 i stoi bliżej niż fMaxProximityDist
VelDesired = 0.0;
}
}
}
}
}
void
TController::adjust_desired_speed_for_current_speed() {
// decisions based on current speed
auto const vel { DirectionalVel() };
if( is_train() ) {
// on flats or uphill we can be less careful
if( vel > VelDesired ) {
// jesli jedzie za szybko do AKTUALNEGO
if( VelDesired == 0.0 ) {
// jesli stoj, to hamuj, ale i tak juz za pozno :)
AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie
}
else {
// slow down, not full stop
if( vel > VelDesired + fVelPlus ) {
// hamuj tak średnio
AccDesired = std::min( AccDesired, -0.05 + fAccThreshold );
}
else {
// o 5 km/h to olej (zacznij luzować)
AccDesired = std::min(
AccDesired, // but don't override decceleration for VelNext
std::max( 0.0, AccPreferred ) );
}
}
}
if( fAccGravity > 0.025 ) {
// going sharply downhill we may need to start braking sooner than usual
// try to estimate increase of current velocity before engaged brakes start working
auto const speedestimate = vel + ( 1.0 - fBrake_a0[ 0 ] ) * 30.0 * AbsAccS;
if( speedestimate > VelDesired ) {
// jesli jedzie za szybko do AKTUALNEGO
if( VelDesired == 0.0 ) {
// jesli stoj, to hamuj, ale i tak juz za pozno :)
AccDesired = std::min( AccDesired, -0.85 ); // hamuj solidnie
}
else {
// if it looks like we'll exceed maximum speed start thinking about slight slowing down
AccDesired = std::min( AccDesired, -0.05 + fAccThreshold );
// HACK: for cargo trains with high braking threshold ensure we cross that threshold
if( true == IsCargoTrain
&& fBrake_a0[0] > 0.2 ) {
AccDesired -= std::clamp( fBrake_a0[ 0 ] - 0.2, 0.0, 0.15 );
}
}
}
else {
// stop accelerating when close enough to target speed
AccDesired = std::min(
AccDesired, // but don't override decceleration for VelNext
std::lerp( // ease off as you close to the target velocity
EU07_AI_NOACCELERATION, AccPreferred,
std::clamp( VelDesired - speedestimate, 0.0, fVelMinus ) / fVelMinus ) );
}
// final tweaks
if( vel > EU07_AI_NOMOVEMENT ) {
// going downhill also take into account impact of gravity
AccDesired -= fAccGravity;
// HACK: if the max allowed speed was exceeded something went wrong; brake harder
AccDesired -= 0.15 * std::clamp( vel - VelDesired, 0.0, 5.0 );
}
}
// HACK: limit acceleration for cargo trains, to reduce probability of breaking couplers on sudden jolts
auto MaxAcc{ 0.5 * mvOccupied->Couplers[(mvOccupied->DirAbsolute >= 0 ? end::rear : end::front)].FmaxC / fMass };
if( iVehicles - ControlledEnginesCount > 0 ) {
MaxAcc *= std::clamp( vel * 0.025, 0.2, 1.0 );
}
AccDesired = std::min(AccDesired, safe_clamp(MaxAcc, HeavyCargoTrainAcceleration, AccPreferred));
// TBD: expand this behaviour to all trains with car(s) exceeding certain weight?
/*
if( ( IsPassengerTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) {
AccDesired = std::min( AccDesired, ( iVehicles - ControlledEnginesCount > 8 ? HeavyPassengetTrainAcceleration : PassengetTrainAcceleration ) );
}
if( ( IsCargoTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) {
AccDesired = std::min( AccDesired, CargoTrainAcceleration );
}
if( ( IsHeavyCargoTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) ) {
AccDesired = std::min( AccDesired, HeavyCargoTrainAcceleration );
} */
}
else {
// for cars the older version works better
if( vel > VelDesired ) {
// jesli jedzie za szybko do AKTUALNEGO
if( VelDesired == 0.0 ) {
// jesli stoj, to hamuj, ale i tak juz za pozno :)
AccDesired = std::min( AccDesired, -0.9 ); // hamuj solidnie
}
else {
// slow down, not full stop
if( vel > VelDesired + fVelPlus ) {
// hamuj tak średnio
AccDesired = std::min( AccDesired, -fBrake_a0[ 0 ] * 0.5 );
}
else {
// o 5 km/h to olej (zacznij luzować)
AccDesired = std::min(
AccDesired, // but don't override decceleration for VelNext
std::max( 0.0, AccPreferred ) );
}
}
}
}
// NOTE: as a stop-gap measure the routine is limited to trains only while car calculations seem off
if( is_train() ) {
if( vel < VelDesired ) {
// don't adjust acceleration when going above current target speed
if( -AccDesired * BrakeAccFactor() < (
false == Ready || VelNext > vel - 40.0 ?
fBrake_a0[ 0 ] * 0.8 :
-fAccThreshold )
/ ( 1.2 * braking_distance_multiplier( VelNext ) ) ) {
AccDesired = std::max( EU07_AI_NOACCELERATION, AccDesired );
}
}
}
}
// hamowanie kontrolne
void
TController::adjust_desired_speed_for_braking_test() {
// if( false == Global.DynamicBrakeTest ) { return; }
if( DynamicBrakeTest >= 5 ) { return; } // all done
if( false == is_train() ) { return; } // not applicable
if( mvOccupied->BrakeHandle == TBrakeHandle::FVel6 ) { return; } // leads to bugs in DecBrake, disable for now
if( false == TestFlag( OrderCurrentGet(), Obey_train ) ) { return; } // not applicable
if( false == AIControllFlag ) { return; } // TBD: add notification about braking test and enable it for human driver as well?
auto const vel { DirectionalVel() };
switch( DynamicBrakeTest ) {
case 0: {
if( primary()
&& vel < VelDesired
&& fAccGravity >= 0.0 // not if going uphill
&& AccDesired >= EU07_AI_ACCELERATION
&& TrainParams.TTVmax >= 10.0
&& vel > std::min(TrainParams.TTVmax, 60.0) - 2.0 ) {
DynamicBrakeTest = 1;
DBT_BrakingTime = ElapsedTime;
DBT_VelocityBrake = vel;
}
break;
}
case 1: {
AccDesired = EU07_AI_BRAKINGTESTACCELERATION;
VelDesired = DBT_VelocityBrake;
if( ElapsedTime - DBT_BrakingTime > 1 ) {
ForcePNBrake = true;
mvOccupied->EpFuseSwitch( false );
DynamicBrakeTest = 2;
}
break;
}
case 2: {
AccDesired = EU07_AI_BRAKINGTESTACCELERATION;
VelDesired = DBT_VelocityBrake;
if( ElapsedTime - DBT_BrakingTime > 2 ) {
DBT_BrakingTime = ElapsedTime;
DBT_VelocityBrake = vel;
DBT_VelocityRelease = vel - 8.0;
DynamicBrakeTest = 3;
}
break;
}
case 3: {
AccDesired = safe_clamp( -AbsAccS, fAccThreshold * 1.01, fAccThreshold * 1.21 );
VelDesired = DBT_VelocityBrake;
if( vel <= DBT_VelocityRelease ) {
DynamicBrakeTest = 4;
DBT_BrakingTime = ElapsedTime - DBT_BrakingTime;
DBT_MidPointAcc = AbsAccS;
DBT_ReleasingTime = ElapsedTime;
}
break;
}
case 4: {
if( Ready ) {
if( BrakeSystem == TBrakeSystem::ElectroPneumatic ) {
mvOccupied->EpFuseSwitch( true );
}
ForcePNBrake = false;
DynamicBrakeTest = 5;
DBT_ReleasingTime = ElapsedTime - DBT_ReleasingTime;
DBT_VelocityFinish = vel;
}
break;
}
default: {
break;
}
}
}
void
TController::control_tractive_and_braking_force() {
#if LOGVELOCITY
WriteLog("Dist=" + FloatToStrF(ActualProximityDist, ffFixed, 7, 1) +
", VelDesired=" + FloatToStrF(VelDesired, ffFixed, 7, 1) +
", AccDesired=" + FloatToStrF(AccDesired, ffFixed, 7, 3) +
", VelSignal=" + AnsiString(VelSignal) + ", VelNext=" +
AnsiString(VelNext));
#endif
if (iDriverFailCount > maxdriverfails) {
Psyche = Easyman;
if (iDriverFailCount > maxdriverfails * 2)
SetDriverPsyche();
}
control_relays();
control_motor_connectors();
// if the radio-stop was issued don't waste effort trying to fight it
if( true == mvOccupied->RadioStopFlag // radio-stop
&& mvOccupied->Vel > EU07_AI_NOMOVEMENT ) { // and still moving
cue_action( driver_hint::mastercontrollersetzerospeed ); // just throttle down...
return; // ...and don't touch any other controls
}
// if we're slipping leave the controls alone, there's a separate logic section trying to deal with it
if( true == mvControlling->SlippingWheels ) {
return;
}
// ensure train brake isn't locked
if( BrakeCtrlPosition == gbh_NP ) {
cue_action( driver_hint::trainbrakerelease );
}
control_releaser();
control_main_pipe();
control_tractive_force();
control_braking_force();
#if LOGVELOCITY
WriteLog("BrakePos=" + AnsiString(mvOccupied->BrakeCtrlPos) + ", MainCtrl=" +
AnsiString(mvControlling->MainCtrlPos));
#endif
}
// ensure relays are active
void TController::control_relays() {
if( fActionTime < 0.0 ) { return; }
if( IsAnyMotorOverloadRelayOpen ) {
cue_action( driver_hint::mastercontrollersetzerospeed );
cue_action( driver_hint::tractionnmotoroverloadreset );
++iDriverFailCount;
}
if( IsAnyGroundRelayOpen ) {
cue_action( driver_hint::mastercontrollersetzerospeed );
cue_action( driver_hint::maincircuitgroundreset );
++iDriverFailCount;
}
}
void TController::control_motor_connectors() {
// ensure motor connectors are active
if( mvControlling->EngineType == TEngineType::ElectricSeriesMotor
|| mvControlling->EngineType == TEngineType::DieselElectric
|| is_emu() ) {
if( Need_TryAgain ) {
// true, jeśli druga pozycja w elektryku nie załapała
cue_action( driver_hint::mastercontrollersetzerospeed );
Need_TryAgain = false;
}
}
}
void TController::control_tractive_force() {
auto const velocity { DirectionalVel() };
// jeśli przyspieszenie pojazdu jest mniejsze niż żądane oraz...
if( AccDesired > EU07_AI_NOACCELERATION // don't add power if not asked for actual speed-up
&& (AbsAccS < AccDesired /*- std::min( 0.05, 0.01 * iDriverFailCount )*/ || (mvOccupied->SpeedCtrlUnit.IsActive && velocity < mvOccupied->SpeedCtrlUnit.FullPowerVelocity))
&& false == TestFlag(iDrivigFlags, movePress) ) {
// ...jeśli prędkość w kierunku czoła jest mniejsza od dozwolonej o margines...
if( velocity < (
VelDesired == 1.0 ? // work around for trains getting stuck on tracks with speed limit = 1
VelDesired :
VelDesired - fVelMinus ) ) {
// within proximity margin don't exceed next scheduled speed, outside of the margin accelerate as you like
if( ActualProximityDist > (is_car() ? fMinProximityDist : // cars are allowed to move within min proximity distance
fMaxProximityDist) ? // other vehicle types keep wider margin
true :
velocity + 1.0 < VelNext ) {
// ...to można przyspieszyć
increase_tractive_force();
}
}
}
// zmniejszanie predkosci
// margines dla prędkości jest doliczany tylko jeśli oczekiwana prędkość jest większa od 5km/h
if( false == TestFlag( iDrivigFlags, movePress ) ) {
double SpeedCtrlMargin = mvControlling->SpeedCtrlUnit.IsActive && VelDesired > 5 ? 3 : 0;
// jeśli nie dociskanie
if( AccDesired <= EU07_AI_NOACCELERATION ) {
cue_action( driver_hint::mastercontrollersetzerospeed );
}
else if( velocity > VelDesired + SpeedCtrlMargin
|| ( fAccGravity < -0.01 ?
AccDesired < 0.0 :
AbsAccS > AccDesired + 10.05 )
|| IsAnyCouplerStretched ) {
// jak za bardzo przyspiesza albo prędkość przekroczona
// dodany wyjatek na "pelna w przod"
cue_action( driver_hint::tractiveforcedecrease );
}
}
control_handles();
SpeedSet(); // ciągla regulacja prędkości
}
void TController::increase_tractive_force() {
if( fActionTime < 0.0 ) { return; } // gdy jest nakaz poczekać z jazdą, to nie ruszać
if( IsAnyCouplerStretched ) { return; } // train is already stretched past its limits, don't pull even harder
if( mvOccupied->SpringBrake.Activate ) {
cue_action( driver_hint::springbrakeoff );
}
if( VelDesired > 0.0 // to prevent door shuffle on stop
&& ( doors_open() || doors_permit_active() ) ) {
// zamykanie drzwi - tutaj wykonuje tylko AI (zmienia fActionTime)
Doors( false );
// Doors() call can potentially adjust fActionTime
if( fActionTime < 0.0 ) { return; }
}
if( true == mvOccupied->DepartureSignal ) {
cue_action( driver_hint::departuresignaloff );
}
cue_action( driver_hint::tractiveforceincrease );
}
void TController::control_braking_force() {
// don't touch the brakes when compressing buffers during coupling/uncoupling
if( TestFlag( iDrivigFlags, movePress ) ) { return; }
auto const velocity { DirectionalVel() };
// jeśli przyspieszamy, to nie hamujemy
if( AccDesired > 0.0 ) {
if( OrderCurrentGet() != Disconnect // przy odłączaniu nie zwalniamy tu hamulca
&& false == TestFlag(iDrivigFlags, movePress) ) {
cue_action( driver_hint::brakingforcesetzero );
}
}
if( is_emu() && !ForcePNBrake ) {
// właściwie, to warunek powinien być na działający EP
// Ra: to dobrze hamuje EP w EZT
auto const accthreshold { (
fAccGravity < 0.025 ? // HACK: when going downhill be more responsive to desired deceleration
fAccThreshold :
std::max( -0.2, fAccThreshold ) ) };
auto const AccMax{ std::min(fBrake_a0[0] + 12 * fBrake_a1[0], mvOccupied->MED_amax) };
auto const accmargin = AccMax > 1.1 * AccDesired && fAccGravity < 0.025 ?
0.05 : 0.0;
if( AccDesired < accthreshold // jeśli hamować - u góry ustawia się hamowanie na fAccThreshold
&& ( AbsAccS > AccDesired + accmargin
|| BrakeCtrlPosition < 0 ) ) {
// hamować bardziej, gdy aktualne opóźnienie hamowania mniejsze niż (AccDesired)
cue_action( driver_hint::brakingforceincrease );
}
else if( OrderCurrentGet() != Disconnect ) { // przy odłączaniu nie zwalniamy tu hamulca
if( AbsAccS < AccDesired - 0.05 ) {
// jeśli opóźnienie większe od wymaganego (z histerezą) luzowanie, gdy za dużo
// TBD: check if the condition isn't redundant with the DecBrake() code
if( BrakeCtrlPosition >= 0 ) {
if( VelDesired > 0.0 ) { // sanity check to prevent unintended brake release on sharp slopes
cue_action( driver_hint::brakingforcedecrease );
}
}
}
else {
cue_action( driver_hint::brakingforcelap );
}
}
} // type & dt_ezt
else {
// a stara wersja w miarę dobrze działa na składy wagonowe
if( ( AccDesired < fAccGravity - 0.1 && AbsAccS > AccDesired + fBrake_a1[0] ) // regular braking
|| ( fAccGravity < -0.05 && velocity < -0.1 ) ) { // also brake if uphill and slipping back
// u góry ustawia się hamowanie na fAccThreshold
if( fBrakeTime < 0.0
|| AccDesired < fAccGravity - 0.5
|| BrakeCtrlPosition <= 0 ) {
// jeśli upłynął czas reakcji hamulca, chyba że nagłe albo luzował
// TODO: check whether brake delay variable still has any purpose
cue_action(
driver_hint::brakingforceincrease,
// Ra: ten czas należy zmniejszyć, jeśli czas dojazdu do zatrzymania jest mniejszy
( 3.0
+ 0.5 * ( (
mvOccupied->BrakeDelayFlag > bdelay_G ?
mvOccupied->BrakeDelay[ 1 ] :
mvOccupied->BrakeDelay[ 3 ] )
- 3.0 ) )
* 0.5 ); // Ra: tymczasowo, bo przeżyna S1
}
}
if ( AccDesired < fAccGravity - 0.05
&& AccDesired - fBrake_a1[0] * 0.51 - AbsAccS > 0.05 ) {
// jak hamuje, to nie tykaj kranu za często
// yB: luzuje hamulec dopiero przy różnicy opóźnień rzędu 0.2
if( OrderCurrentGet() != Disconnect ) { // przy odłączaniu nie zwalniamy tu hamulca
if( VelDesired > 0.0 ) { // sanity check to prevent unintended brake release on sharp slopes
// TODO: check whether brake delay variable still has any purpose
cue_action(
driver_hint::brakingforcedecrease,
(mvOccupied->BrakeDelayFlag > bdelay_G ? mvOccupied->BrakeDelay[0] : mvOccupied->BrakeDelay[2]) / 3.0
* 0.5 ); // Ra: tymczasowo, bo przeżyna S1
}
}
}
// stop-gap measure to ensure cars actually brake to stop even when above calculactions go awry
// instead of releasing the brakes and creeping into obstacle at 1-2 km/h
if( is_car() ) {
if( VelDesired == 0.0
&& velocity > VelDesired
&& ActualProximityDist <= fMinProximityDist
&& mvOccupied->LocalBrakePosA < 0.01 ) {
cue_action( driver_hint::brakingforceincrease );
}
}
}
// odhamowywanie składu po zatrzymaniu i zabezpieczanie lokomotywy
if( mvOccupied->Vel < EU07_AI_NOMOVEMENT
&& ( VelDesired == 0.0
|| AccDesired <= EU07_AI_NOACCELERATION ) ) {
if( (OrderCurrentGet() & (Disconnect | Connect | Change_direction)) == 0 // przy (p)odłączaniu nie zwalniamy tu hamulca
&& std::abs(fAccGravity) < 0.01 ) { // only do this on flats, on slopes keep applied the train brake
apply_independent_brake_only();
}
// if told to change direction don't confuse human driver with request to leave applied brake in the cab they're about to leave
if( ( OrderCurrentGet() & Change_direction ) != 0 ) {
cue_action( driver_hint::independentbrakerelease );
}
}
}
void TController::apply_independent_brake_only() {
if( mvOccupied->LocalBrake != TLocalBrake::ManualBrake ) {
if( false == mvOccupied->ShuntMode // no need for independent brake if autobrake is active
&& is_equal(mvOccupied->fBrakeCtrlPos, mvOccupied->Handle->GetPos(bh_RP), 0.2) ) {
cue_action( driver_hint::independentbrakeapply );
}
else {
cue_action( driver_hint::trainbrakerelease );
}
}
}
void TController::control_releaser() {
// HACK: don't immediately touch releaser in inert vehicle, in case the simulation is about to replace us with human driver
if( mvOccupied->Vel < 1.0 && fActionTime < 0.0 ) { return; }
// TODO: combine all releaser handling in single decision tree instead of having bits all over the place
// TODO: replace handle and system conditions with flag indicating releaser presence
if( BrakeSystem != TBrakeSystem::Pneumatic ) { return; }
auto const hasreleaser {
false == (is_dmu() || is_emu()) // TODO: a more refined test than rejecting these types wholesale
&& ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a
|| mvOccupied->BrakeHandle == TBrakeHandle::MHZ_6P
|| mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K5P
|| mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P
|| mvOccupied->BrakeHandle == TBrakeHandle::M394 ) };
if( false == hasreleaser ) { return; }
auto actuate { false };
auto isbrakehandleinrightposition { true };
if( AccDesired > EU07_AI_NOACCELERATION ) {
// fill up brake pipe if needed
if( mvOccupied->PipePress < 3.0 ) {
actuate = true;
// some vehicles require brake handle to be moved to specific position
if( mvOccupied->HandleUnlock != -3 ) {
cue_action( driver_hint::trainbrakesetpipeunlock );
isbrakehandleinrightposition &= BrakeCtrlPosition == mvOccupied->HandleUnlock;
}
}
// wyluzuj lokomotywę, to szybciej ruszymy
if( mvOccupied->BrakePress > 0.4
&& mvOccupied->Hamulec->GetCRP() > 4.9 ) {
actuate = true;
}
// keep engine brakes released during coupling/uncoupling
if( true == TestFlag(iDrivigFlags, movePress)
&& mvOccupied->BrakePress > 0.1 ) {
actuate = true;
}
}
// don't overcharge train brake pipe
if( mvOccupied->PipePress > 5.2 ) {
actuate = false;
}
if( actuate ) {
// some vehicles may require master controller in neutral position
cue_action( driver_hint::mastercontrollersetzerospeed );
if( false == mvOccupied->Hamulec->Releaser()
&& isbrakehandleinrightposition ) {
cue_action( driver_hint::releaseron );
}
}
else {
if( true == mvOccupied->Hamulec->Releaser() ) {
cue_action( driver_hint::releaseroff );
}
}
}
void TController::control_main_pipe() {
// unlocking main pipe
if( AccDesired > -0.03
&& true == mvOccupied->LockPipe ) {
UniversalBrakeButtons |= TUniversalBrake::ub_UnlockPipe;
}
else if (false == mvOccupied->LockPipe ) {
UniversalBrakeButtons &= ~TUniversalBrake::ub_UnlockPipe;
}
// napełnianie uderzeniowe
if( BrakeSystem != TBrakeSystem::Pneumatic ) { return; }
if( mvOccupied->BrakeHandle == TBrakeHandle::FV4a
|| mvOccupied->BrakeHandle == TBrakeHandle::MHZ_6P
|| mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K5P
|| mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P
|| mvOccupied->BrakeHandle == TBrakeHandle::M394 ) {
if( iDrivigFlags & moveOerlikons
|| true == IsCargoTrain ) {
if( BrakeCtrlPosition == gbh_RP
&& AbsAccS < 0.03
&& AccDesired > -0.03
&& VelDesired - mvOccupied->Vel > 2.0 ) {
if( fReady > 0.35
&& mvOccupied->EqvtPipePress < 4.5 // don't charge with a risk of overcharging the main pipe
&& mvOccupied->Compressor >= 7.5 // don't charge without sufficient pressure in the tank
&& BrakeChargingCooldown >= 0.0 // don't charge while cooldown is active
&& ( ActualProximityDist > 100.0 // don't charge if we're about to be braking soon
|| min_speed(mvOccupied->Vel, VelNext) >= mvOccupied->Vel ) ) {
BrakeLevelSet( gbh_FS );
// don't charge the brakes too often, or we risk overcharging
BrakeChargingCooldown = -1 * std::clamp( iVehicles * 3, 30, 90 );
}
}
}
if( mvOccupied->Compressor < 5.0
|| ( BrakeCtrlPosition < gbh_RP
&& mvOccupied->EqvtPipePress > (fReady < 0.25 ? 5.1 : 5.2) ) ) {
cue_action( driver_hint::trainbrakerelease );
}
}
}
void
TController::check_route_ahead( double const Range ) {
auto const comm { TableUpdate( VelDesired, ActualProximityDist, VelNext, AccDesired ) };
switch (comm) {
// ustawienie VelSignal - trochę proteza = do przemyślenia
case TCommandType::cm_Ready: { // W4 zezwolił na jazdę
// ewentualne doskanowanie trasy za W4, który zezwolił na jazdę
TableCheck( Range );
TableUpdate( VelDesired, ActualProximityDist, VelNext, AccDesired ); // aktualizacja po skanowaniu
if( VelNext == 0.0 ) {
break; // ale jak coś z przodu zamyka, to ma stać
}
if( iDrivigFlags & moveStopCloser ) {
VelSignal = -1.0; // ma czekać na sygnał z sygnalizatora!
}
break;
}
case TCommandType::cm_SetVelocity: { // od wersji 357 semafor nie budzi wyłączonej lokomotywy
if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { // jedzie w dowolnym trybie albo Wait_for_orders
if( std::fabs( VelSignal ) >= 1.0 ) { // 0.1 nie wysyła się do samochodow, bo potem nie ruszą
PutCommand( "SetVelocity", VelSignal, VelNext, nullptr ); // komenda robi dodatkowe operacje
}
}
break;
}
case TCommandType::cm_ShuntVelocity: { // od wersji 357 Tm nie budzi wyłączonej lokomotywy
if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) { // jedzie w dowolnym trybie albo Wait_for_orders
PutCommand( "ShuntVelocity", VelSignal, VelNext, nullptr );
}
else if( iCoupler ) { // jeśli jedzie w celu połączenia
SetVelocity( VelSignal, VelNext );
}
break;
}
case TCommandType::cm_Command: { // komenda z komórki
if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank ) ) == 0 ) {
// jedzie w dowolnym trybie albo Wait_for_orders
if( mvOccupied->Vel < 0.1 ) {
// dopiero jak stanie
/*
PutCommand( eSignNext->text(), eSignNext->value( 1 ), eSignNext->value( 2 ), nullptr );
eSignNext->StopCommandSent(); // się wykonało już
*/ // replacement of the above
eSignNext->send_command( *this );
}
}
break;
}
default: {
break;
}
}
}
void
TController::check_route_behind( double const Range ) {
if( VelNext != 0.0 ) { return; } // not if we can move ahead
if( TestFlag( iDrivigFlags, moveConnect ) ) { return; } // not if we're in middle of coupling operation
if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Connect ) ) == 0 ) {
// jedzie w Shunt albo Connect, albo Wait_for_orders
// w trybie Connect skanować do tyłu tylko jeśli przed kolejnym sygnałem nie ma taboru do podłączenia
auto const couplinginprogress {
TestFlag(OrderCurrentGet(), Connect)
&& ( Obstacle.distance < std::min(2000.0, FirstSemaphorDist)
|| TestFlag(iDrivigFlags, moveConnect) ) };
if( couplinginprogress ) { return; }
auto const commandbehind { BackwardScan( Range ) };
if( commandbehind != TCommandType::cm_Unknown ) {
// jeśli w drugą można jechać
// należy sprawdzać odległość od znalezionego sygnalizatora,
// aby w przypadku prędkości 0.1 wyciągnąć najpierw skład za sygnalizator
// i dopiero wtedy zmienić kierunek jazdy, oczekując podania prędkości >0.5
if( commandbehind == TCommandType::cm_Command ) {
// jeśli komenda Shunt to ją odbierz bez przemieszczania się (np. odczep wagony po dopchnięciu do końca toru)
iDrivigFlags |= moveStopHere;
}
iDirectionOrder = -iDirection; // zmiana kierunku jazdy
// zmiana kierunku bez psucia kolejnych komend
OrderList[ OrderPos ] = TOrders( OrderCurrentGet() | Change_direction );
}
}
}
void
TController::UpdateBrakingHelper() {
if (HelperState > 0 && -AccDesired < fBrake_a0[0] + 2 * fBrake_a1[0] && mvOccupied->Vel > EU07_AI_NOMOVEMENT) {
HelperState = 0;
}
switch( HelperState ) {
case 0: {
if( -AccDesired > fBrake_a0[0] + 8 * fBrake_a1[0] ) {
HelperState = 1;
}
break;
}
case 1: {
if( -AccDesired > fBrake_a0[0] + 12 * fBrake_a1[0] ) {
HelperState = 2;
}
break;
}
case 2: {
if( -AccDesired > 0 && -ActualProximityDist > 5 ) {
HelperState = 3;
}
break;
}
default: {
break;
}
}
}