Files
maszyna/Driver.cpp
2021-05-03 02:02:53 +02:00

8063 lines
364 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 "Driver.h"
//#include <direct.h>
#include "Globals.h"
#include "translation.h"
#include "Logs.h"
#include "Train.h"
#include "mtable.h"
#include "DynObj.h"
#include "Event.h"
#include "MemCell.h"
#include "simulation.h"
#include "simulationtime.h"
#include "Track.h"
#include "station.h"
#include "keyboardinput.h"
#include "utilities.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 = (pos_event - segment->FastGetPoint(seg_len)).LengthSquared();
do
{
len1 = len2;
seg_len += scan_dir > 0 ? dzielnik : -dzielnik;
len2 = (pos_event - segment->FastGetPoint(seg_len)).LengthSquared();
++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 = Math3D::vector3(0, 0, 0);
trTrack = NULL; // 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;
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
+ " [" + to_string( static_cast<int>( evEvent->input_value( 1 ) ) )
+ ", " + 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 -= (
pVehicle->AxlePositionGet()
- pVehicle->RearPosition()
+ pVehicle->VectorLeft() * pVehicle->MoverParameters->OffsetTrackH )
.Length();
// 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;
// te flagi są ustawiane tutaj, w razie potrzeby
iDrivigFlags &= ~(moveTrackEnd | moveSwitchFound | moveSemaphorFound | /*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 = interpolate( a, AccPreferred, 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 & ( moveSemaphorFound | 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();
iStationStart = 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( locale::string::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;
}
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.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 <=
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( locale::string::driver_hint_waitdeparturetime );
}
} // 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() ) {
case TCommandType::cm_EmergencyBrake: {
pVehicle->RadioStop();
Point.Clear(); // signal received, deactivate
}
default: {
break;
}
}
}
// check signals ahead
if( Point.fDist > 0.0 ) {
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 |= moveSemaphorFound;
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 { clamp( 1.f + iVehicles * 0.5f, 2.f, 4.f ) };
return (
interpolate(
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 (
interpolate(
1.f, 2.f,
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 (
interpolate(
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 )
{
ControllingSet(); // utworzenie połączenia do sterowanego pojazdu
if( pVehicle != nullptr ) {
pVehicles[ 0 ] = pVehicle->GetFirstDynamic( 0 ); // pierwszy w kierunku jazdy (Np. Pc1)
pVehicles[ 1 ] = pVehicle->GetFirstDynamic( 1 ); // ostatni w kierunku jazdy (końcówki)
}
else {
pVehicles[ 0 ] = nullptr;
pVehicles[ 1 ] = nullptr;
}
if( mvOccupied != nullptr ) {
iDirectionOrder = mvOccupied->CabActive; // 1=do przodu (w kierunku sprzęgu 0)
VehicleName = mvOccupied->Name;
if( mvOccupied->CategoryFlag & 2 ) { // 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
_mkdir( "physicslog" );
#elif __linux__
mkdir( "physicslog", 0644 );
#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 locale::strings[ locale::string::driver_scenario_changedirection ];
}
}
switch( static_cast<TOrders>( order & ~Change_direction ) ) {
case Wait_for_orders: { return locale::strings[ locale::string::driver_scenario_waitfororders ]; }
case Prepare_engine: { return locale::strings[ locale::string::driver_scenario_prepareengine ]; }
case Release_engine: { return locale::strings[ locale::string::driver_scenario_releaseengine ]; }
case Change_direction: { return locale::strings[ locale::string::driver_scenario_changedirection ]; }
case Connect: { return locale::strings[ locale::string::driver_scenario_connect ]; }
case Disconnect: {
if( iVehicleCount < 0 ) {
// done with uncoupling, order should update shortly
return locale::strings[ locale::string::driver_scenario_waitfororders ];
}
// try to provide some task details
auto const count { iVehicleCount };
if( iVehicleCount > 1 ) {
std::snprintf(
orderbuffer.data(), orderbuffer.size(),
( iVehicleCount < 5 ?
locale::strings[ locale::string::driver_scenario_fewvehicles ].c_str() : // 2-4
locale::strings[ locale::string::driver_scenario_somevehicles ].c_str() ), // 5+
count );
}
auto const countstring { (
count == 0 ? locale::strings[ locale::string::driver_scenario_allvehicles ] :
count == 1 ? locale::strings[ locale::string::driver_scenario_onevehicle ] :
orderbuffer.data() ) };
std::snprintf(
orderbuffer.data(), orderbuffer.size(),
locale::strings[ locale::string::driver_scenario_disconnect ].c_str(),
countstring.c_str() );
return orderbuffer.data();
}
case Shunt: { return locale::strings[ locale::string::driver_scenario_shunt ]; }
case Loose_shunt: { return locale::strings[ locale::string::driver_scenario_looseshunt ]; }
case Obey_train: { return locale::strings[ locale::string::driver_scenario_obeytrain ]; }
case Bank: { return locale::strings[ locale::string::driver_scenario_bank ]; }
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 )
&& ( ( mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic )
|| ( mvOccupied->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 = 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;
}
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() ) {
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 ] - 8 * fBrake_a1[ BrakeAccTableSize ] );
}
else {
fNominalAccThreshold = std::max( -0.75, -fBrake_a0[ BrakeAccTableSize ] - 8 * 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)
pVehicles[end::front] = p = pVehicle->FirstFind(d); // pojazd na czele składu
// 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 );
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 );
}
}
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->PrevC( coupling::control ) != nullptr )
|| ( pVehicle->NextC( 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 );
}
}
// 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( locale::string::driver_hint_consistdoorlockson );
// potentially enable train heating
{
// HACK: to account for su-45/46 shortcomings diesel-powered engines only activate heating in cold conditions
// TODO: take instead into account presence of converters in attached cars, once said presence is possible to specify
auto const ispassengertrain { ( IsPassengerTrain ) && ( iVehicles - ControlledEnginesCount > 0 ) };
auto const isheatingcouplingactive { pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::heating ) };
auto const isheatingneeded {
(is_emu() || is_dmu() ? true :
// false == isheatingcouplingactive ? false :
(OrderCurrentGet() & (Obey_train | Bank)) == 0 ? false :
ispassengertrain ? (has_diesel_engine() ? (Global.AirTemperature < 10) : true) :
false)};
if( mvControlling->HeatingAllow != isheatingneeded ) {
cue_action(
isheatingneeded ?
locale::string::driver_hint_consistheatingon :
locale::string::driver_hint_consistheatingoff );
}
}
}
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;
}
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->CabActivisation(); // 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 );
}
}
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( locale::string::driver_hint_batteryon );
cue_action( locale::string::driver_hint_radioon );
if( has_diesel_engine() ) {
cue_action( locale::string::driver_hint_oilpumpon );
PrepareHeating();
if( true == IsHeatingTemperatureOK ) {
cue_action( locale::string::driver_hint_fuelpumpon );
}
else {
cue_action( locale::string::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( locale::string::driver_hint_pantographairsourcesetauxiliary );
}
cue_action( locale::string::driver_hint_pantographcompressoron ); // załączenie sprężarki pantografów
if( mvPantographUnit->PantCompFlag ) {
cue_action( locale::string::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( locale::string::driver_hint_pantographcompressoroff ); // sprężarkę pantografów można już wyłączyć
}
}
if( ( fOverhead2 == -1.0 ) && ( iOverheadDown == 0 ) ) {
cue_action( locale::string::driver_hint_pantographsvalveon );
cue_action( locale::string::driver_hint_frontpantographvalveon );
cue_action( locale::string::driver_hint_rearpantographvalveon );
}
}
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( locale::string::driver_hint_compressoroff ); // TODO: discern whether compressor needs converter to operate
cue_action( locale::string::driver_hint_converteroff );
cue_action( locale::string::driver_hint_primaryconverteroverloadreset ); // reset nadmiarowego
}
if( IsAnyLineBreakerOpen ) {
// activate main circuit or engine
cue_action( locale::string::driver_hint_mastercontrollersetzerospeed );
// consist-wide ground relay reset
cue_action( locale::string::driver_hint_maincircuitgroundreset );
cue_action( locale::string::driver_hint_tractionnmotoroverloadreset );
if( mvOccupied->EngineType == TEngineType::DieselEngine ) {
// specjalnie dla SN61 żeby nie zgasł
cue_action( locale::string::driver_hint_mastercontrollersetidle );
}
cue_action( locale::string::driver_hint_linebreakerclose );
}
else {
// main circuit or engine is on, set up vehicle devices and controls
if( false == IsAnyConverterOverloadRelayOpen ) {
cue_action( locale::string::driver_hint_converteron );
// w EN57 sprężarka w ra jest zasilana z silnikowego
// TODO: change condition to presence of required voltage type
if( IsAnyConverterEnabled ) {
cue_action( locale::string::driver_hint_compressoron );
}
if( ( mvControlling->ScndPipePress < 4.5 ) && ( mvControlling->VeselVolume > 0.0 ) ) {
cue_action( locale::string::driver_hint_waitpressuretoolow );
}
// enable motor blowers
if( mvOccupied->MotorBlowers[ end::front ].speed != 0 ) {
cue_action( locale::string::driver_hint_frontmotorblowerson );
}
if( mvOccupied->MotorBlowers[ end::rear ].speed != 0 ) {
cue_action( locale::string::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 ) ) {
cue_action( locale::string::driver_hint_trainbrakerelease );
}
// sync spring brake state across consist
cue_action(
mvOccupied->SpringBrake.Activate ?
locale::string::driver_hint_springbrakeon :
locale::string::driver_hint_springbrakeoff );
}
isready = ( false == IsAnyConverterOverloadRelayOpen )
&& ( mvOccupied->DirActive != 0 )
&& ( false == IsAnyLineBreakerOpen )
&& ( true == IsAnyConverterEnabled )
&& ( true == IsAnyCompressorEnabled )
&& ( ( mvControlling->ScndPipePress > 4.5 ) || ( mvControlling->VeselVolume == 0.0 ) )
&& ( ( mvOccupied->fBrakeCtrlPos == mvOccupied->Handle->GetPos( bh_RP ) || ( 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( locale::string::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( locale::string::driver_hint_trainbrakeapply );
}
cue_action( locale::string::driver_hint_mastercontrollersetreverserunlock );
cue_action( locale::string::driver_hint_directionnone );
// zamykanie drzwi
cue_action( locale::string::driver_hint_doorrightclose );
cue_action( locale::string::driver_hint_doorleftclose );
// heating
cue_action( locale::string::driver_hint_consistheatingoff );
// devices
cue_action( locale::string::driver_hint_compressoroff );
cue_action( locale::string::driver_hint_converteroff );
// line breaker/engine
cue_action( locale::string::driver_hint_linebreakeropen );
// pantographs
cue_action( locale::string::driver_hint_frontpantographvalveoff );
cue_action( locale::string::driver_hint_rearpantographvalveoff );
if( false == mvControlling->Mains ) {
// finish vehicle shutdown
if( has_diesel_engine() ) {
// heating/cooling subsystem
cue_action( locale::string::driver_hint_waterheateroff );
// optionally turn off the water pump as well
if( mvControlling->WaterPump.start_type != start_t::battery ) {
cue_action( locale::string::driver_hint_waterpumpoff );
}
// fuel and oil subsystems
cue_action( locale::string::driver_hint_fuelpumpoff );
cue_action( locale::string::driver_hint_oilpumpoff );
}
// gasimy światła
cue_action( locale::string::driver_hint_lightsoff );
// activate parking brake
// TBD: do it earlier?
cue_action( locale::string::driver_hint_springbrakeon );
if( ( mvOccupied->LocalBrake == TLocalBrake::ManualBrake )
|| ( mvOccupied->MBrake == true ) ) {
cue_action( locale::string::driver_hint_manualbrakon );
}
// switch off remaining power
cue_action( locale::string::driver_hint_radiooff );
cue_action( locale::string::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
bool OK = false;
switch( mvOccupied->BrakeSystem ) {
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: {
// NOTE: can't perform just test whether connected vehicle == nullptr, due to virtual couplers formed with nearby vehicles
bool 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 ) {
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: {
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(
interpolate(
mvOccupied->Handle->GetPos( bh_EPR ),
mvOccupied->Handle->GetPos( bh_EPB ),
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 / mvOccupied->MED_amax - 1.0}; //additional limit when hinted is too low
auto const brakehinted{ -1.0 * mvOccupied->AIHintLocalBrakeAccFactor * AccDesired / mvOccupied->MED_amax }; //preffered by AI
auto const brakeposition{ maxpos * 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 };
switch( mvOccupied->BrakeSystem ) {
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(
interpolate(
mvOccupied->Handle->GetPos( bh_EPR ),
mvOccupied->Handle->GetPos( bh_EPB ),
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 { 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 (mvControlling->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
}
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)) {
// 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 ) > ( 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 useseriesmodevoltage {
interpolate(
mvControlling->EnginePowerSource.CollectorParameters.MinV,
mvControlling->EnginePowerSource.CollectorParameters.MaxV,
( IsHeavyCargoTrain ? 0.35 : 0.40 ) ) };
auto const useseriesmode = (
( mvControlling->Imax > mvControlling->ImaxLo )
|| ( fVoltage < useseriesmodevoltage )
|| ( ( 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 ) ) );
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 - useseriesmodevoltage > ( 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 ?
mvControlling->EnginePowerSource.CollectorParameters.MinV :
useseriesmodevoltage )
> ( 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( locale::string::driver_hint_linebreakerclose );
cue_action( locale::string::driver_hint_converteron );
cue_action( locale::string::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 = 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:
OK = mvControlling->MainCtrlPos > 2;
if (OK)
mvControlling->MainCtrlPos = 2;
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 = 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 (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 (mvControlling->MainCtrlPos ?
mvControlling->RList[mvControlling->MainCtrlPos].Bn > 1 :
false); // 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)
// NOTE: this step is likely to conflict with directive to operate sandbox based on the state of slipping wheels
// TODO: gather all sandbox operating logic in one place
if( fabs( mvControlling->Im ) > 0.75 * mvControlling->ImaxHi ) {
// jeśli prąd jest duży
mvControlling->Sandbox( true ); // piaskujemy tory, coby się nie ślizgać
}
else {
// otherwise we switch the sander off, if it's active
if( mvControlling->SandDose ) {
mvControlling->Sandbox( false );
}
}
if ((fabs(mvControlling->Im) > 0.96 * mvControlling->Imax) ||
mvControlling->SlippingWheels) // 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)
if (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 ) { return; }
//1. Check the type of Main Brake Handle
if (mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic)
{
if (mvOccupied->Handle->Time)
{
if ((BrakeCtrlPosition > 0) && (mvOccupied->Handle->GetCP() - 0.05 > mvOccupied->HighPipePress - BrakeCtrlPosition*0.25*mvOccupied->DeltaPipePress))
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_FB));
else if ((BrakeCtrlPosition > 0) && (mvOccupied->Handle->GetCP() + 0.05 < mvOccupied->HighPipePress - BrakeCtrlPosition*0.25*mvOccupied->DeltaPipePress))
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)
{
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{ 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 ) { return; }
//1. Check the type of Main Brake Handle
if (mvOccupied->BrakeSystem == TBrakeSystem::ElectroPneumatic && mvOccupied->Handle->TimeEP)
{
mvOccupied->BrakeLevelSet(mvOccupied->Handle->GetPos(bh_EPN));
}
if (mvOccupied->BrakeSystem == TBrakeSystem::Pneumatic && 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( locale::string::driver_hint_doorrightpermiton );
}
if( Side & lewe ) {
cue_action( locale::string::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( locale::string::driver_hint_doorrightopen );
}
if( Side & lewe ) {
cue_action( locale::string::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;
return;
}
if( true == doors_open() ) {
if( ( true == mvOccupied->Doors.has_warning )
&& ( false == mvOccupied->DepartureSignal )
&& ( true == TestFlag( iDrivigFlags, moveDoorOpened ) ) ) {
cue_action( locale::string::driver_hint_departuresignalon ); // załącenie bzyczka
fActionTime = -1.5 - 0.1 * Random( 10 ); // 1.5-2.5 second wait
}
}
if( ( false == AIControllFlag ) || ( fActionTime > -0.5 ) ) {
// ai doesn't close the door until it's free to depart, but human driver has free reign to do stupid things
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( locale::string::driver_hint_doorrightclose );
cue_action( locale::string::driver_hint_doorleftclose );
}
}
if( true == doors_permit_active() ) {
cue_action( locale::string::driver_hint_doorrightpermitoff );
cue_action( locale::string::driver_hint_doorleftpermitoff );
}
// if applicable close manually-operated doors in vehicles which may ignore remote signals
{
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 = -2.0 - 0.1 * Random( 15 ); // 2.0-3.5 sec wait, +potentially 0.5 remaining
iDrivigFlags &= ~moveDoorOpened; // zostały zamknięte - nie wykonywać drugi raz
if( ( true == mvOccupied->DepartureSignal )
&& ( Random( 100 ) < Random( 100 ) ) ) {
// potentially turn off the warning before actual departure
// TBD, TODO: dedicated buzzer duration counter?
cue_action( locale::string::driver_hint_departuresignaloff );
}
}
}
}
// 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
iStationStart = 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, szSoundPath + 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, szSoundPath + 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( locale::string::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;
}
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.Status) << " "
<< 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( locale::string::driver_hint_waterpumpbreakeron );
cue_action( locale::string::driver_hint_waterpumpon );
}
if( true == mvControlling->WaterPump.is_active ) {
cue_action( locale::string::driver_hint_waterheaterbreakeron );
cue_action( locale::string::driver_hint_waterheateron );
cue_action( locale::string::driver_hint_watercircuitslinkon );
}
}
else {
// no need to heat anything up, switch the heater off
cue_action( locale::string::driver_hint_watercircuitslinkoff );
cue_action( locale::string::driver_hint_waterheateroff );
cue_action( locale::string::driver_hint_waterheaterbreakeroff );
// optionally turn off the water pump as well
if( mvControlling->WaterPump.start_type != start_t::battery ) {
cue_action( locale::string::driver_hint_waterpumpoff );
cue_action( locale::string::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( locale::string::driver_hint_mastercontrollersetreverserunlock );
cue_action(
iDirection > 0 ?
locale::string::driver_hint_directionforward :
locale::string::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 (t->StationWare.find('@') != std::string::npos)
{ // 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() * 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{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 = sem.Length() - 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;
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
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();
};
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
mvControlling = pVehicle->FindPowered()->MoverParameters; // poszukiwanie członu sterowanego
{
auto *lookup { pVehicle->FindPantographCarrier() };
mvPantographUnit = (
lookup != nullptr ?
lookup->MoverParameters :
mvControlling );
}
};
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ć
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 = 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 ?
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;
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( ( TestFlag( vehicle->Hamulec->GetBrakeStatus(), b_hld ) )
|| ( ( vehicle->Vel < 1.0 ) && ( bp > 0.4 ) ) ) { // ensure the brakes are sufficiently released when starting to move
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 ) {
if( ( BrakeCtrlPosition == gbh_RP ) // jest pozycja jazdy
&& ( false == TestFlag( vehicle->Hamulec->GetBrakeStatus(), b_dmg ) ) // brake isn't broken
&& ( vehicle->PipePress - 5.0 > -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 );
}
}
}
}
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 };
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.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;
// HACK: enable a make-believe compressor in all cars
// TBD, TODO: replace with a more flexible vehicle readiness check in PrepareEngine()
IsAnyCompressorEnabled = is_car();
IsAnyCompressorExplicitlyEnabled = 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 );
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 );
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 )
&& ( true == IsAnyConverterEnabled )
&& ( true == IsAnyCompressorEnabled );
}
void
TController::control_wheelslip() {
// wheel slip
if( mvControlling->SlippingWheels ) {
cue_action( locale::string::driver_hint_sandingon ); // piasku!
cue_action( locale::string::driver_hint_tractiveforcedecrease );
cue_action( locale::string::driver_hint_brakingforcedecrease );
cue_action( locale::string::driver_hint_antislip );
++iDriverFailCount;
}
else {
// deactivate sandbox if we aren't slipping
if( mvControlling->SandDose ) {
cue_action( locale::string::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( locale::string::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->NextC( 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( locale::string::driver_hint_mastercontrollersetzerospeed );
}
if( ( fOverhead2 > 0.0 ) || iOverheadDown ) {
// jazda z opuszczonymi pantografami
if( mvPantographUnit->Pantographs[ end::front ].is_active ) {
cue_action( locale::string::driver_hint_frontpantographvalveoff );
}
if( mvPantographUnit->Pantographs[ end::rear ].is_active ) {
cue_action( locale::string::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( locale::string::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( locale::string::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( locale::string::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( locale::string::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 ) ?
locale::string::driver_hint_frontpantographvalveon :
locale::string::driver_hint_frontpantographvalveoff ) );
cue_action(
( pantographsetup & ( 1 << 1 ) ?
locale::string::driver_hint_rearpantographvalveon :
locale::string::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( locale::string::driver_hint_frontpantographvalveon, 5 ); // discard the hint if speed exceeds 5 km/h
}
}
else {
if( mvPantographUnit->PantRearVolt == 0.0 ) {
cue_action( locale::string::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( locale::string::driver_hint_hornon, 1 ); // low tone horn by default
}
}
else {
if( mvOccupied->WarningSignal != 0 ) {
cue_action( locale::string::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( locale::string::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.Status >= s_aware ) {
// jak zadziałało CA/SHP
if( ( false == is_emu() )
&& ( mvOccupied->DirActive == 0 ) ) {
cue_action( locale::string::driver_hint_directionforward );
}
cue_action( locale::string::driver_hint_securitysystemreset ); // to skasuj
if( ( BrakeCtrlPosition == 0 ) // TODO: verify whether it's 0 in all vehicle types
&& ( AccDesired > 0.0 )
&& ( ( TestFlag( mvOccupied->SecuritySystem.Status, s_SHPebrake ) )
|| ( TestFlag( mvOccupied->SecuritySystem.Status, s_CAebrake ) ) ) ) {
cue_action( locale::string::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( locale::string::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( locale::string::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( locale::string::driver_hint_mastercontrollersetzerospeed );
}
// if the power station is heavily burdened drop down to series mode to reduce the load
auto const useseriesmodevoltage {
interpolate(
mvControlling->EnginePowerSource.CollectorParameters.MinV,
mvControlling->EnginePowerSource.CollectorParameters.MaxV,
( IsHeavyCargoTrain ? 0.35 : 0.40 ) ) };
if( fVoltage <= useseriesmodevoltage ) {
cue_action( locale::string::driver_hint_mastercontrollersetseriesmode );
}
// sanity check
if( ( false == Ready )
&& ( mvControlling->MainCtrlPowerPos() > 1 ) ) {
cue_action( locale::string::driver_hint_mastercontrollersetzerospeed );
}
break;
}
case TEngineType::DieselElectric: {
// styczniki liniowe rozłączone yBARC
if( ( false == mvControlling->StLinFlag )
&& ( false == mvControlling->DelayCtrlFlag )
&& ( mvControlling->MainCtrlPowerPos() > 1 ) ) {
cue_action( locale::string::driver_hint_mastercontrollersetzerospeed );
}
// sanity check
if( ( false == Ready )
&& ( mvControlling->MainCtrlPowerPos() > 1 ) ) {
cue_action( locale::string::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( locale::string::driver_hint_headcodepc1 );
}
else if( m_lighthints[ end::front ] == ( light::redmarker_left | light::headlight_right | light::headlight_upper ) ) {
cue_action( locale::string::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( locale::string::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( locale::string::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 ?
locale::string::driver_hint_consistlightson :
locale::string::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
iStationStart = 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 = clamp( 5 + iVehicles, 10, 15 );
fMaxProximityDist = 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 = 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 = 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( locale::string::driver_hint_doorleftopen );
remove_hint( locale::string::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ć
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( locale::string::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;
}
}
}
}
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( locale::string::driver_hint_mastercontrollersetreverserunlock );
cue_action( locale::string::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( locale::string::driver_hint_independentbrakerelease );
cue_action( locale::string::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( locale::string::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( locale::string::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( mvOccupied->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( locale::string::driver_hint_mastercontrollersetreverserunlock );
cue_action( locale::string::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();
// 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 = 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( locale::string::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 -= 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
interpolate( // ease off as you close to the target velocity
EU07_AI_NOACCELERATION, AccPreferred,
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 * clamp( vel - VelDesired, 0.0, 5.0 );
}
}
// HACK: limit acceleration for cargo trains, to reduce probability of breaking couplers on sudden jolts
// 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 );
}
}
}
}
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( locale::string::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( locale::string::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( locale::string::driver_hint_mastercontrollersetzerospeed );
cue_action( locale::string::driver_hint_tractionnmotoroverloadreset );
++iDriverFailCount;
}
if( IsAnyGroundRelayOpen ) {
cue_action( locale::string::driver_hint_mastercontrollersetzerospeed );
cue_action( locale::string::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( locale::string::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 /* - 0.05 */ )
&& ( 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( locale::string::driver_hint_mastercontrollersetzerospeed );
}
else if( ( velocity > VelDesired + SpeedCtrlMargin)
|| ( fAccGravity < -0.01 ?
AccDesired < 0.0 :
( AbsAccS > AccDesired + 0.05 ) )
|| ( IsAnyCouplerStretched ) ) {
// jak za bardzo przyspiesza albo prędkość przekroczona
// dodany wyjatek na "pelna w przod"
cue_action( locale::string::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( locale::string::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( locale::string::driver_hint_departuresignaloff );
}
cue_action( locale::string::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( locale::string::driver_hint_brakingforcesetzero );
}
}
if( is_emu() ) {
// 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 ) ) };
if( ( AccDesired < accthreshold ) // jeśli hamować - u góry ustawia się hamowanie na fAccThreshold
&& ( ( AbsAccS > AccDesired )
|| ( BrakeCtrlPosition < 0 ) ) ) {
// hamować bardziej, gdy aktualne opóźnienie hamowania mniejsze niż (AccDesired)
cue_action( locale::string::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( locale::string::driver_hint_brakingforcedecrease );
}
}
}
else {
cue_action( locale::string::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(
locale::string::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(
locale::string::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( locale::string::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( locale::string::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( locale::string::driver_hint_independentbrakeapply );
}
else {
cue_action( locale::string::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( mvOccupied->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( locale::string::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( locale::string::driver_hint_mastercontrollersetzerospeed );
if( ( false == mvOccupied->Hamulec->Releaser() )
&& ( isbrakehandleinrightposition ) ) {
cue_action( locale::string::driver_hint_releaseron );
}
}
else {
if( true == mvOccupied->Hamulec->Releaser() ) {
cue_action( locale::string::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( mvOccupied->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 * clamp( iVehicles * 3, 30, 90 );
}
}
}
if( ( mvOccupied->Compressor < 5.0 )
|| ( ( BrakeCtrlPosition < gbh_RP )
&& ( mvOccupied->EqvtPipePress > ( fReady < 0.25 ? 5.1 : 5.2 ) ) ) ) {
cue_action( locale::string::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;
}
}
}