17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 01:07:28 +01:00

Keep one primary per consist and let a secondary AI claim it.

A driver without movePrimary no longer just idles. UpdateSecondary() cuts
BrakeValveActive so a secondary AI stops fighting the primary over the brake
pipe, and - as long as no driver in the consist has iEngineActive - keeps
scanning ahead of CabOccupied, so it can take movePrimary and start the engine
once a signal permits.

scan_route() orients by CabOccupied when DirActive is 0, and
ClaimConsistPrimary() clears movePrimary on every other driver in the consist,
then calls TableClear() and CheckVehicles(), so sSpeedTable is rebuilt for the
new front and ctOwner follows it.

- walk the consist with FirstFind()/Neighbour() instead of Next()/Prev(), which
  stopped at the starting vehicle and mishandled reversed members;
- ReassignConsistPrimary() picks a single driver after any coupling change,
  both from TDynamicObject::couple() and from the AI path;
- IsOccupiedByAnotherConsist() decides membership with BelongsToConsist() and
  takes a signed distance, so a consist left behind no longer cuts the scan
  short. DistanceToAnotherConsist() hands back the gap it already computes, so
  OtherConsistAtSignal() counts a foreign vehicle only while it stands closer
  than the signal itself, instead of comparing track starts;
- TSpeedPos::Set() marks an entry with spNotOurShuntSignal when a
  spShuntSemaphor was already permitting as the entry was added. A shunting
  signal does not address its permission to a particular consist, so a dormant
  driver does not wake up on a permission it did not see light up; the flag
  drops once the signal shows stop, so its own turn is never blocked. spSemaphor
  is excluded: a semaphore sets a route for us and permits us even when found
  already lit. The check reads SemPermitIndex - the entry that produced the
  permitting command - because SemNextIndex can point at an earlier signal.
  The flag is also stamped whenever the permission turns out to belong to a
  consist standing in front of us - both where TableUpdateEvent() already knows
  the signal is for the vehicle ahead, and in check_route_ahead() - so the
  addressee pulling away does not hand the still-lit signal over to whoever
  waited behind it;
- the scantable debug panel lists the flags by name (spNotOurShuntSignal shows
  as not-ours) and folds the nearest vehicle ahead into the table by its own
  distance.
This commit is contained in:
maj00r
2026-09-06 10:16:24 +02:00
parent 7a469190ca
commit aaccd7b9b7
9 changed files with 759 additions and 175 deletions

View File

@@ -339,6 +339,9 @@ bool TSpeedPos::Update()
// 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
if( fVelNext == 0.0 ) {
iFlags &= ~spNotOurShuntSignal; // saw "stop" - the next permission will light up with us here
}
}
}
return false;
@@ -359,13 +362,24 @@ std::string TSpeedPos::GetName() const
}
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();
{ // one speed table entry - distance/velocity/name plus readable flag tags instead of hex
if( ( iFlags & spEnabled ) == 0 ) {
return ""; // disabled - empty string, the F2 panel skips such an entry
}
return "Empty";
std::string tags;
for( auto const &description : SpeedPosFlagDescriptions ) {
if( iFlags & description.flag ) {
if( false == tags.empty() ) { tags += " "; }
tags += description.name;
}
}
return
to_string( fDist, 1, 8 )
+ " " + ( fVelNext == -1.0 ? " -" : to_string( static_cast<int>( fVelNext ), 0, 3 ) )
+ " " + GetName()
+ ( tags.empty() ? "" : " [" + tags + "]" );
}
bool TSpeedPos::IsProperSemaphor(TOrders order)
@@ -385,6 +399,14 @@ bool TSpeedPos::IsProperSemaphor(TOrders order)
return false; // true gdy zatrzymanie, wtedy nie ma po co skanować dalej
}
bool TSpeedPos::AllowsDeparture() const
{ // signal ahead permits - a dormant AI may start without waiting for a change
if( fDist <= 0.0 ) { return false; }
if( ( iFlags & spEnabled ) == 0 ) { return false; }
if( ( iFlags & ( spSemaphor | spShuntSemaphor ) ) == 0 ) { return false; }
return fVelNext < 0.0 || fVelNext >= 1.0;
}
bool TSpeedPos::Set(basic_event *event, double dist, double length, TOrders order)
{ // zapamiętanie zdarzenia
fDist = dist;
@@ -398,6 +420,11 @@ bool TSpeedPos::Set(basic_event *event, double dist, double length, TOrders orde
}
iFlags |= spEnabled;
CommandCheck(); // sprawdzenie typu komendy w evencie i określenie prędkości
if( ( iFlags & spShuntSemaphor ) != 0
&& fVelNext != 0.0 ) {
// a shunting signal addresses nobody in particular; a semaphore sets a route for us
iFlags |= spNotOurShuntSignal; // permission found already lit - we did not see who it was given to
}
// 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));
@@ -451,6 +478,7 @@ void TController::TableClear()
fLastVel = -1.0;
SemNextIndex = -1;
SemNextStopIndex = -1;
SemPermitIndex = std::size_t( -1 );
eSignSkip = nullptr; // nic nie pomijamy
};
@@ -503,7 +531,10 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle)
double fCurrentDistance{ 0.0 }; // aktualna przeskanowana długość
double fLastDir{ 0.0 };
if (iTableDirection != iDirection ) {
// with no direction set (dormant AI) we scan by the occupied cab
int const scandirection{ iDirection != 0 ? iDirection : mvOccupied->CabOccupied };
if (iTableDirection != scandirection ) {
// jeśli zmiana kierunku, zaczynamy od toru ze wskazanym pojazdem
TableClear();
/*
@@ -519,10 +550,19 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle)
// 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
iTableDirection = scandirection;
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( pVehicles[ end::front ] != nullptr
&& pVehicles[ end::front ] != pVehicle ) {
// driver not in the front vehicle: DirectionGet() may point backwards relative to the consist
auto const towardfront{ pVehicles[ end::front ]->GetPosition() - pVehicle->GetPosition() };
auto const nose{ pVehicle->VectorFront() * static_cast<double>( pVehicle->DirectionGet() ) };
if( towardfront.x * nose.x + towardfront.z * nose.z < 0.0 ) {
fLastDir = -fLastDir;
}
}
if( fLastDir < 0.0 ) {
// jeśli w kierunku Point2 toru
fTrackLength = pTrack->Length() - fTrackLength; // przeskanowana zostanie odległość do Point2
@@ -635,6 +675,10 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle)
+ ")");
}
}
if( iEngineActive ) {
// only a dormant driver acts on it, so do not tag it for anybody else
newspeedpoint.iFlags &= ~spNotOurShuntSignal;
}
}
}
} // event dodajemy najpierw, żeby móc sprawdzić, czy tor został dodany po odczytaniu prędkości następnego
@@ -700,6 +744,16 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle)
}
}
auto const distancetotrackend{ fCurrentDistance + fTrackLength };
// sign of the distance encodes the scanning direction (fLastDir < 0 = towards Point2)
if( distancetotrackend > 0.0 // the track still reaches ahead of us
&& IsOccupiedByAnotherConsist( pTrack, distancetotrackend * ( fLastDir < 0.0 ? 1.0 : -1.0 ) ) ) {
// another consist stands here: it owns the signals beyond it, so we scan no further. The track
// stays in the table, marked checked - scanning resumes from the last entry, no dup control
tLast = pTrack;
break;
}
fCurrentDistance += fTrackLength; // doliczenie kolejnego odcinka do przeskanowanej długości
tLast = pTrack; // odhaczenie, że sprawdzony
fLastVel = pTrack->VelocityGet(); // prędkość na poprzednio sprawdzonym odcinku
@@ -772,7 +826,9 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle)
void TController::TableCheck(double fDistance)
{ // przeliczenie odległości w tabelce, ewentualnie doskanowanie (bez analizy prędkości itp.)
if( iTableDirection != iDirection ) {
// as in TableTraceRoute: a dormant AI has no iDirection, so we orient by the cab
int const scandirection{ iDirection != 0 ? iDirection : mvOccupied->CabOccupied };
if( iTableDirection != scandirection ) {
// jak zmiana kierunku, to skanujemy od końca składu
TableTraceRoute( fDistance, pVehicles[ end::rear ] );
TableSort();
@@ -859,6 +915,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN
auto d { 0.0 }; // droga
auto d_to_next_sem { 10000.0 }; //ustaiwamy na pewno dalej niż widzi AI
auto go { TCommandType::cm_Unknown };
SemPermitIndex = std::size_t( -1 ); // filled in by whichever point yields a permitting command
auto speedlimitiscontinuous { true }; // stays true if potential active speed limit is unbroken to the last (relevant) point in scan table
eSignNext = nullptr;
IsAtPassengerStop = false;
@@ -1546,6 +1603,13 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo
if( isforsomeoneelse ) {
VelSignalNext = 0.0;
Velocity = 0.0;
// it permits, but for the vehicle standing in front of us. Record that now -
// once it pulls away the signal alone no longer says whose permission it was
if( false == iEngineActive
&& ( Point.iFlags & spShuntSemaphor )
&& Point.fVelNext != 0.0 ) {
Point.iFlags |= spNotOurShuntSignal;
}
}
else {
VelSignalNext = Point.fVelNext;
@@ -1553,6 +1617,16 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo
Velocity = fVelMax;
VelSignal = fVelMax;
}
if( Command == TCommandType::cm_Unknown && Point.AllowsDeparture() ) {
// already permitting as it entered the FOV - wake up without waiting for a change
Command = ( Point.iFlags & spShuntSemaphor ) != 0 ?
TCommandType::cm_ShuntVelocity :
TCommandType::cm_SetVelocity;
SemPermitIndex = Pointindex;
if( std::fabs( VelSignal ) < 1.0 ) {
VelSignal = Point.fVelNext < 0.0 ? -1.0 : Point.fVelNext;
}
}
}
}
}
@@ -1630,6 +1704,7 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo
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)
SemPermitIndex = Pointindex;
// Ra 2014-06: (VelSignal) nie może być tu ustawiane, bo Tm może być daleko
if( VelSignal == 0.0 ) {
// aby stojący ruszył
@@ -1653,6 +1728,7 @@ TController::TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPo
|| Velocity >= 1.0 ) {
// bo wartość 0.1 służy do hamowania tylko
Command = TCommandType::cm_SetVelocity; // może odjechać
SemPermitIndex = Pointindex;
// 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 ) {
@@ -1820,6 +1896,15 @@ void TController::TableSort() {
//---------------------------------------------------------------------------
bool TController::primary( bool const Primary )
{
SetFlag( iDrivigFlags, Primary ? movePrimary : -movePrimary );
if( mvOccupied != nullptr ) {
mvOccupied->BrakeValveActive = Primary;
}
return primary();
}
TController::TController(bool AI, TDynamicObject *NewControll, bool InitPsyche, bool Primary) :// czy ma aktywnie prowadzić?
AIControllFlag( AI ), pVehicle( NewControll )
{
@@ -1870,9 +1955,7 @@ TController::TController(bool AI, TDynamicObject *NewControll, bool InitPsyche,
// OrderValue=0;
OrdersClear();
if( true == Primary ) {
iDrivigFlags |= movePrimary; // aktywnie prowadzące pojazd
}
primary( Primary );
SetDriverPsyche(); // na końcu, bo wymaga ustawienia zmiennych
TableClear();
@@ -2035,6 +2118,7 @@ void TController::Activation()
// 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
mvOccupied->BrakeValveActive = false;
}
// przejście AI na drugą stronę EN57, ET41 itp.
// TODO: clean this up, there's lot of redundancy with TMoverParameters::ChangeCab() and TTrain::MoveToVehicle()
@@ -2049,6 +2133,7 @@ void TController::Activation()
if( targetvehicle->DirectionGet() != pVehicle->DirectionGet() ) {
// jeśli są przeciwne do siebie to będziemy jechać w drugą stronę względem zasiedzianego pojazdu
iDirection = -iDirection;
iDirectionOrder = iDirection; // so that DirectionChange does not flip it back
}
// move the other driver to our old vehicle
pVehicle->Mechanik = targetvehicledriver; // wsadzamy tego, co ewentualnie był (podwójna trakcja)
@@ -2100,6 +2185,7 @@ void TController::Activation()
}
CheckVehicles(); // sprawdzenie składu, AI zapali światła
sync_consist_reversers(); // after a cab change the members' reversers have to agree
TableClear(); // resetowanie tabelki skanowania torów
}
};
@@ -2343,6 +2429,10 @@ int TController::CheckDirection() {
// jeśli nie ma ustalonego kierunku to jedziemy wg aktualnej kabiny
d = mvOccupied->CabActive;
}
if( !d ) {
// dormant: reverser/cab inactive - orient by the occupied cab
d = mvOccupied->CabOccupied;
}
return d;
}
@@ -2368,14 +2458,14 @@ bool TController::CheckVehicles(TOrders user)
{ // 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 ) {
if( p->Mechanik->primary() ) {
// a tamten ma priorytet
// TODO: take into account drivers' operating modes, one or more of them might be on banking duty
if( iDrivigFlags & movePrimary
if( primary()
&& mvOccupied->DirAbsolute
&& mvOccupied->BrakeCtrlPos >= -1 ) {
// jeśli rządzi i ma kierunek
p->Mechanik->primary( false ); // dezaktywuje tamtego
p->Mechanik->primary( false );
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
@@ -2398,12 +2488,12 @@ bool TController::CheckVehicles(TOrders user)
p = p->Neighbour(dir); // pojazd podłączony od wskazanej strony
}
if( main ) {
iDrivigFlags |= movePrimary; // nie znaleziono innego, można się porządzić
primary( true );
}
ControllingSet(); // ustalenie członu do sterowania (może być inny niż zasiedziany)
if (iDrivigFlags & movePrimary)
if( primary() )
{ // jeśli jest aktywnie prowadzącym pojazd, może zrobić własny porządek
auto pantmask = 1;
p = pVehicles[end::front];
@@ -2565,15 +2655,19 @@ void TController::Lights(int head, int rear)
}
void TController::DirectionInitial()
{ // ustawienie kierunku po wczytaniu trainset (może jechać na wstecznym
mvOccupied->CabActivisationAuto(); // załączenie rozrządu (wirtualne kabiny)
if (mvOccupied->Vel > EU07_AI_NOMOVEMENT)
{ // jeśli na starcie jedzie
iDirection = iDirectionOrder =
mvOccupied->V > 0 ? 1 : -1; // początkowa prędkość wymusza kierunek jazdy
DirectionForward(mvOccupied->V * mvOccupied->CabActive >= 0.0); // a dalej ustawienie nawrotnika
{ // sets the direction after a trainset is loaded
mvOccupied->CabActivisationAuto();
if( mvOccupied->Vel >= EU07_AI_MOVEMENT ) {
// actually moving - direction from the sign of V
iDirection = iDirectionOrder = mvOccupied->V > 0 ? 1 : -1;
DirectionForward( mvOccupied->V * mvOccupied->CabActive >= 0.0 );
}
CheckVehicles(); // sprawdzenie świateł oraz skrajnych pojazdów do skanowania
else {
// standing: reverser forward relative to the cab, not from the sign of a micro V
DirectionForward( true );
iDirection = iDirectionOrder = CheckDirection();
}
CheckVehicles();
};
void TController::DirectionChange() {
@@ -3977,7 +4071,7 @@ void TController::SpeedCntrl(double DesiredSpeed)
void TController::SetTimeControllers()
{
// TBD, TODO: rework this method to use hint system and regardless of driver type
if( false == AIControllFlag || 0 == mvOccupied->CabActive ) { return; }
if( !primary() || !AIControllFlag || mvOccupied->CabActive == 0 ) { return; }
//1. Check the type of Main Brake Handle
if( BrakeSystem == TBrakeSystem::Pneumatic || ForcePNBrake )
@@ -4188,7 +4282,7 @@ void TController::SetTimeControllers()
void TController::CheckTimeControllers()
{
// TODO: rework this method to use hint system and regardless of driver type
if( false == AIControllFlag || 0 == mvControlling->CabActive ) { return; }
if( !primary() || !AIControllFlag || mvControlling->CabActive == 0 ) { return; } // a secondary crew does not touch the controllers
//1. Check the type of Main Brake Handle
if( BrakeSystem == TBrakeSystem::ElectroPneumatic && mvOccupied->Handle->TimeEP && !ForcePNBrake )
@@ -4389,8 +4483,99 @@ void TController::PutCommand(std::string NewCommand, double NewValue1, double Ne
mvOccupied->PutCommand(NewCommand, NewValue1, NewValue2, NewLocation);
}
bool TController::CommandClaimsPrimary( std::string const &Command, double const Value )
{ // a driving/startup order wakes a secondary crew up to lead, otherwise nobody moves the consist
auto const isdrivingorder = [&Command]( std::initializer_list<char const *> const names ) {
for( auto const *name : names ) {
if( Command == name ) { return true; }
}
return false;
};
return
Command.compare( 0, 10, "Timetable:" ) == 0
|| ( Command == "SetVelocity" && Value != 0.0 )
|| isdrivingorder( { "Prepare_engine", "ShuntVelocity", "Obey_train", "Bank", "Shunt", "Loose_shunt" } );
}
namespace {
// front-to-back walk like CheckVehicles(); Neighbour() flips the coupler side on reversed members
template <typename Callable>
void ForEachInConsist( TDynamicObject *Vehicle, Callable Function ) {
if( Vehicle == nullptr ) { return; }
int side { end::front };
auto *vehicle { Vehicle->FirstFind( side ) };
if( vehicle == nullptr ) { vehicle = Vehicle; }
auto dir { 1 - side };
while( vehicle != nullptr ) {
if( false == Function( vehicle ) ) { return; } // false stops the walk
vehicle = vehicle->Neighbour( dir );
}
}
}
void TController::ClaimConsistPrimary()
{ // this driver is to be the only primary left in the consist
ForEachInConsist(
pVehicle,
[this]( TDynamicObject *Vehicle ) {
auto *other { Vehicle->Mechanik };
if( other == nullptr
|| other == this ) { return true; }
if( other->primary() ) {
other->ZeroLocalBrake();
if( other->mvOccupied->BrakeCtrlPosNo > 0 ) { // only vehicles with a driver's brake valve (otherwise Handle == null)
other->mvOccupied->BrakeLevelSet( other->mvOccupied->Handle->GetPos( bh_NP ) );
other->BrakeLevelSet( other->mvOccupied->BrakeCtrlPos );
}
}
// always - sync the valve on the already secondary ones too
other->primary( false );
return true;
} );
primary( true );
TableClear(); // after a change of primary the table has to be built from scratch
CheckVehicles(); // otherwise the members' ctOwner stays with the previous primary
}
TController *
TController::ActiveEnginePrimary() const {
// a primary with the engine already running - then we do not steal primary
TController *active { nullptr };
ForEachInConsist(
pVehicle,
[&active]( TDynamicObject *Vehicle ) {
auto *driver { Vehicle->Mechanik };
if( driver != nullptr
&& driver->primary()
&& driver->iEngineActive ) {
active = driver;
return false;
}
return true;
} );
return active;
}
bool TController::PutCommand( std::string NewCommand, double NewValue1, double NewValue2, glm::dvec3 const *NewLocation, TStopReason reason )
{ // analiza komendy
auto const shouldtakeprimary{
AIControllFlag
&& !primary()
&& CommandClaimsPrimary( NewCommand, NewValue1 )
&& ActiveEnginePrimary() == nullptr }; // steal only while the engines are down
if( shouldtakeprimary ) {
ClaimConsistPrimary();
if( mvOccupied->CabActive == 0 ) {
mvOccupied->CabActivisationAuto();
}
if( mvOccupied->DirActive == 0 ) {
DirectionForward( true );
}
DirectionChange();
}
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
@@ -4459,7 +4644,7 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N
TrainParams.StationStart = TrainParams.StationIndex;
asNextStop = TrainParams.NextStop();
m_lastannouncement = announcement_t::idle;
iDrivigFlags |= movePrimary; // skoro dostał rozkład, to jest teraz głównym
primary( true );
// NewCommand = Global.asCurrentSceneryPath + NewCommand;
auto lookup =
FileExists(
@@ -4882,13 +5067,78 @@ void TController::PhysicsLog()
}
};
void
TController::SettleSecondaryAI() {
// no force, no brake, vigilance kept quiet; runs every tick so nothing is remembered
mvOccupied->BrakeValveActive = false; // the primary drives the brake pipe
// without a NN acknowledging alone is not enough (see TSecuritySystem::is_braking)
mvOccupied->SecuritySystem.acknowledge_press();
mvOccupied->SecuritySystem.acknowledge_release();
if( false == ( mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable ) ) {
mvOccupied->SecuritySystem.set_enabled( false );
}
ZeroSpeed( true );
ZeroLocalBrake();
if( mvOccupied->SpringBrake.Activate ) {
mvOccupied->SpringBrakeActivate( false );
}
}
void
TController::UpdateSecondary( double const Awarenessrange ) {
// does not drive; scans while the consist is shut down (wake-up / taking over on a signal)
if( AIControllFlag ) {
SettleSecondaryAI();
}
else {
// a human sits in the cab - they handle it themselves
mvOccupied->BrakeValveActive = true;
}
if( pVehicle->ctOwner != nullptr
&& pVehicle->ctOwner != this
&& pVehicle->ctOwner->iEngineActive ) {
return; // the consist runs under an active primary - do not duplicate the scan
}
// a secondary needs no responsiveness - scans rarely, cheap with many shut down consists
auto const scaninterval{ std::max( ReactionTime, EU07_AI_SECONDARYSCANINTERVAL ) };
if( LastReactionTime < scaninterval ) { return; }
LastReactionTime -= scaninterval;
OrientScanToCab();
scan_route( Awarenessrange );
// no obstacles - a dormant driver only waits for a signal; the primary computes them later
ScanForWakeup( Awarenessrange );
}
void
TController::ScanForWakeup( double const Range ) {
// a permission may wake a dormant AI up (Prepare_engine in the mask)
if( AIControllFlag
&& ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank | Prepare_engine ) ) == 0 ) {
check_route_ahead( Range );
}
}
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; }
if( !simulation::is_ready ) { return; }
update_timers( Timedelta );
auto const awarenessrange {
std::max(
750.0,
mvOccupied->Vel > EU07_AI_MOVEMENT ?
400 + fBrakeDist :
30.0 * fDriverDist ) };
if( !primary() ) {
UpdateSecondary( awarenessrange );
return;
}
mvOccupied->BrakeValveActive = true; // opens the valve, may have been cut off while secondary
update_logs( Timedelta );
auto const reactiontime { std::min( ReactionTime, 2.0 ) };
@@ -4913,16 +5163,9 @@ TController::Update( double const Timedelta ) {
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 );
}
// we scan while dormant too, to see a permission and wake up
scan_route( awarenessrange );
scan_obstacles( awarenessrange );
// generic actions
control_security_system( reactiontime );
@@ -5015,8 +5258,12 @@ TController::PrepareDirection() {
if( iDirection == 0 ) {
// jeśli nie ma ustalonego kierunku
if( mvOccupied->Vel < EU07_AI_NOMOVEMENT ) { // ustalenie kierunku, gdy stoi
// as in DirectionInitial / TakeControl: below 1 km/h counts as standing
if( mvOccupied->Vel < EU07_AI_MOVEMENT ) { // determine the direction while standing
iDirection = mvOccupied->CabActive; // wg wybranej kabiny
if( iDirection == 0 ) {
iDirection = mvOccupied->CabOccupied;
}
/*
if( iDirection == 0 ) {
// jeśli nie ma ustalonego kierunku
@@ -5262,28 +5509,46 @@ std::string TController::StopReasonText() const
//- 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;
}
}
}
bool TController::BelongsToConsist( TDynamicObject const *Vehicle ) const
{ // ctOwner alone is not enough: cleared after uncoupling until CheckVehicles
if( Vehicle == nullptr ) { return false; }
if( Vehicle->ctOwner == this ) { return true; }
if( Vehicle->Mechanik == this ) { return true; }
if( Vehicle->ctOwner != nullptr
&& Vehicle->ctOwner == pVehicle->ctOwner ) { return true; }
return pVehicle->is_connected( Vehicle ); // with no owner settled, the coupling is all that is left
}
double TController::DistanceToAnotherConsist( TTrack *Track, double const Distance ) const
{ // Distance is signed by the scan direction and reaches the far end of the track
auto nearest { -1.0 };
auto const scandirection { Distance > 0 ? 1 : -1 }; // positive value means scan towards point2 end of the track
for( auto *dynamic : Track->Dynamics ) {
if( BelongsToConsist( dynamic ) ) { continue; }
// how far the foreign vehicle sits from that same far end
auto const obstaclelocation { scandirection > 0 ? Track->Length() - dynamic->RaTranslationGet() : dynamic->RaTranslationGet() };
// what is left is the gap between us and it; a negative one means it stands behind us
auto const distance { std::abs( Distance ) - obstaclelocation };
if( distance <= 0.0 ) { continue; }
if( nearest < 0.0
|| distance < nearest ) {
nearest = distance;
}
}
return false; // wolny
return nearest;
}
bool TController::IsOccupiedByAnotherConsist( TTrack *Track, double const Distance ) const
{ // whether the track carries vehicles of another consist
if( Track->Dynamics.empty() ) { return false; }
if( Distance == 0.0 ) {
// no scan reference given - any foreign vehicle on the track counts
for( auto *dynamic : Track->Dynamics ) {
if( false == BelongsToConsist( dynamic ) ) { return true; }
}
return false;
}
return DistanceToAnotherConsist( Track, Distance ) >= 0.0;
};
basic_event * TController::CheckTrackEventBackward(double fDirection, TTrack *Track, TDynamicObject *Vehicle, int const Eventdirection, end const End)
@@ -5614,16 +5879,26 @@ void TController::UpdateDelayFlag() {
//-----------koniec skanowania semaforow
void TController::ReleaseTransientControls()
{
mvOccupied->WarningSignal = 0;
mvControlling->SandboxManual( false );
fWarningDuration = 0.0;
iDrivigFlags &= ~( moveStartHornNow | moveStartHornDone );
}
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ć
ReleaseTransientControls();
if (Aidriver) //żeby nie wykonywać dwa razy
{ // teraz AI prowadzi
AIControllFlag = AIdriver;
pVehicle->Controller = AIdriver;
control_lights(); // reinicjalizacja swiatel
mvOccupied->CabActivisation(true);
sync_consist_reversers(); // after taking over from a human the reversers may have drifted apart
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 ) {
@@ -5781,13 +6056,14 @@ TController::TrackObstacle() const {
void TController::MoveTo(TDynamicObject *to)
{ // przesunięcie AI do innego pojazdu (przy zmianie kabiny)
auto takeprimary{false}; // we take the lead only once the predecessor is gone
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 );
takeprimary = to->Mechanik->primary();
if( takeprimary ) {
to->Mechanik->primary( false ); // cut the valve before the delete, so it is not left open
}
SafeDelete( to->Mechanik );
}
@@ -5803,7 +6079,9 @@ void TController::MoveTo(TDynamicObject *to)
pVehicle = to;
ControllingSet(); // utworzenie połączenia do sterowanego pojazdu
pVehicle->Mechanik = this;
if( takeprimary ) {
primary( true );
}
};
void TController::ControllingSet()
@@ -5823,14 +6101,32 @@ void TController::ControllingSet()
std::string TController::TableText( std::size_t const Index ) const
{ // pozycja tabelki prędkości
if( Index < sSpeedTable.size() ) {
return sSpeedTable[ Index ].TableText();
}
else {
if( Index >= sSpeedTable.size() ) {
return "";
}
return sSpeedTable[ Index ].TableText();
};
double TController::TableDistance( std::size_t const Index ) const
{ // for slotting the obstacle into the table by distance
return
Index < sSpeedTable.size() ?
sSpeedTable[ Index ].fDist :
std::numeric_limits<double>::max();
}
std::string TController::ObstacleText() const
{ // a row shaped like TableText, so the panel shows the vehicle at the right distance
if( Obstacle.vehicle == nullptr ) {
return "";
}
return
to_string( Obstacle.distance, 1, 8 )
+ " - " + Obstacle.vehicle->asName
+ " [vehicle" + ( Obstacle.vehicle->ctOwner == this ? " own-consist]" : "]" );
}
int TController::CrossRoute(TTrack *tr)
{ // zwraca numer segmentu dla skrzyżowania (tr)
// pożądany numer segmentu jest określany podczas skanowania drogi
@@ -6532,6 +6828,27 @@ TController::scan_route( double const Range ) {
TableCheck( Range );
}
void
TController::OrientScanToCab() {
// like the start of CheckVehicles, without primary / lights / ControllingSet
iDirection = CheckDirection();
if( iDirection == 0 ) { return; }
auto d{ iDirection >= 0 ? 0 : 1 };
auto *p{ pVehicle->FirstFind( d ) };
if( p == nullptr ) {
p = pVehicle;
}
pVehicles[ end::front ] = p;
auto dir{ 1 - d }; // Neighbour() advances this index to the next member, so it cannot be const
fLength = 0.0;
while( p != nullptr ) {
pVehicles[ end::rear ] = p;
fLength += p->MoverParameters->Dim.L;
p = p->Neighbour( dir );
}
}
// check for potential collisions
void
TController::scan_obstacles( double const Range ) {
@@ -6540,11 +6857,16 @@ TController::scan_obstacles( double const Range ) {
// we cast to int to avoid getting confused by microstutters
auto *frontvehicle { pVehicles[ ( static_cast<int>( mvOccupied->V ) * iDirection >= 0 ? end::front : end::rear ) ] };
// no direction set (dormant AI): orient physically by the occupied cab, else both tests below
// hold and the scan always runs towards coupler 0 - half the time behind us, taking our own
// members for an obstacle and missing the consist standing ahead
auto const scandirection { iDirection != 0 ? iDirection : mvOccupied->CabOccupied };
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 ) {
pVehicle->DirectionGet() == frontvehicle->DirectionGet() ? scandirection >= 0 : scandirection <= 0 ) {
// towards coupler 0
routescandirection = end::front;
}
@@ -7118,12 +7440,21 @@ TController::UpdateDisconnect() {
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 );
&& false == iEngineActive ) {
// scanning is geometric: the driver sees a signal also with the battery off
iDrivigFlags |= moveActive;
if( AIControllFlag
&& primary()
&& mvOccupied->DirActive == 0
&& SemNextIndex != -1 ) {
// there is a signal ahead and no direction set yet - set it, so the scan runs forwards
if( mvOccupied->CabActive == 0 ) {
mvOccupied->CabActivisationAuto();
}
DirectionForward( true );
DirectionChange();
}
}
// basic engine preparation
if( OrderCurrentGet() == Prepare_engine ) {
@@ -7248,8 +7579,8 @@ TController::pick_optimal_speed( double const Range ) {
SwitchClearDist = -1;
ActualProximityDist = Range; // funkcja Update() może pozostawić wartości bez zmian
// if we're idling bail out early
if( false == is_active() ) {
ScanForWakeup( Range );
VelDesired = 0.0;
VelNext = 0.0;
AccDesired = std::min( AccDesired, EU07_AI_NOACCELERATION );
@@ -8179,11 +8510,98 @@ void TController::control_main_pipe() {
}
}
bool
TController::OtherConsistAtSignal() const {
// permission goes to the consist in front of the signal, so a foreign vehicle counts only
// while it stands closer to us than the signal itself
if( SemPermitIndex >= sSpeedTable.size() ) { return false; }
auto const signaldistance { sSpeedTable[ SemPermitIndex ].fDist };
for( auto const &point : sSpeedTable ) {
// a track starting past the signal cannot hold anything standing in front of it
if( point.fDist > signaldistance ) { break; }
if( ( point.iFlags & spTrack ) == 0 ) { continue; }
if( point.trTrack == nullptr ) { continue; }
// only ahead of our front; a consist behind us would make every permission someone else's
auto const distancetotrackend { point.fDist + point.trTrack->Length() };
if( distancetotrackend <= 0.0 ) { continue; }
auto const signeddistance {
distancetotrackend * ( ( point.iFlags & spReverse ) != 0 ? 1.0 : -1.0 ) };
auto const obstacledistance { DistanceToAnotherConsist( point.trTrack, signeddistance ) };
if( obstacledistance < 0.0 ) { continue; } // nothing foreign ahead here
if( obstacledistance <= signaldistance ) { return true; }
}
return false;
}
bool
TController::SignalPermissionIsOurs( bool const Signalgivesgo ) const {
// the permission belongs to the consist standing closest to the signal
if( false == Signalgivesgo ) { return true; }
// a shunting permission found already lit was handed to whoever stood here before us; it only
// keeps a dormant driver from waking up, and drops as soon as the signal shows stop
if( false == iEngineActive
&& SemPermitIndex < sSpeedTable.size() // std::size_t( -1 ) never is
&& ( sSpeedTable[ SemPermitIndex ].iFlags & spNotOurShuntSignal ) ) {
return false;
}
// the addressee usually cuts the scan short and the signal never makes it into the table;
// this closes the case where it did, with the addressee still standing in front of it
return false == OtherConsistAtSignal();
}
TController *
TController::DepartureDriver() const {
// after OrientScanToCab / CheckVehicles the front of the consist is pVehicles[front]
if( pVehicles[ end::front ] != nullptr
&& pVehicles[ end::front ]->Mechanik != nullptr ) {
return pVehicles[ end::front ]->Mechanik;
}
return const_cast<TController *>( this );
}
void
TController::WakeForDeparture( std::string const &Command, double const Value1, double const Value2 ) {
// the primary already has the engine running - we do not steal, we just pass the order on
if( auto *active { ActiveEnginePrimary() } ) {
active->PutCommand( Command, Value1, Value2, nullptr );
return;
}
// engines down - the lead goes to the driver at the front (scanning direction)
auto *target { primary() ? this : DepartureDriver() };
if( target == nullptr ) {
target = this;
}
if( target != this ) {
if( false == target->primary() ) {
target->ClaimConsistPrimary();
}
target->PutCommand( Command, Value1, Value2, nullptr );
return;
}
PutCommand( Command, Value1, Value2, nullptr );
}
void
TController::check_route_ahead( double const Range ) {
auto const comm { TableUpdate( VelDesired, ActualProximityDist, VelNext, AccDesired ) };
auto const signalpermits { comm == TCommandType::cm_SetVelocity || comm == TCommandType::cm_ShuntVelocity };
if( false == SignalPermissionIsOurs( signalpermits ) ) {
// the permission was given to another consist, we wait for our own. Remember it on the
// entry, so the addressee pulling away does not hand the signal over to us; the flag is
// dropped when it shows stop, and only a dormant driver ever carries one
if( false == iEngineActive
&& SemPermitIndex < sSpeedTable.size()
&& ( sSpeedTable[ SemPermitIndex ].iFlags & spShuntSemaphor ) ) {
sSpeedTable[ SemPermitIndex ].iFlags |= spNotOurShuntSignal;
}
return;
}
switch (comm) {
// ustawienie VelSignal - trochę proteza = do przemyślenia
case TCommandType::cm_Ready: { // W4 zezwolił na jazdę
@@ -8198,17 +8616,17 @@ TController::check_route_ahead( double const Range ) {
}
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
case TCommandType::cm_SetVelocity: {
if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank | Prepare_engine ) ) == 0 ) {
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
WakeForDeparture( "SetVelocity", VelSignal, VelNext );
}
}
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 );
case TCommandType::cm_ShuntVelocity: {
if( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank | Prepare_engine ) ) == 0 ) {
WakeForDeparture( "ShuntVelocity", VelSignal, VelNext );
}
else if( iCoupler ) { // jeśli jedzie w celu połączenia
SetVelocity( VelSignal, VelNext );
@@ -8216,8 +8634,7 @@ TController::check_route_ahead( double const Range ) {
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( ( OrderCurrentGet() & ~( Shunt | Loose_shunt | Obey_train | Bank | Prepare_engine ) ) == 0 ) {
if( mvOccupied->Vel < 0.1 ) {
// dopiero jak stanie
/*