diff --git a/Driver.cpp b/Driver.cpp index b6f4a838..0d3ad5eb 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -1784,8 +1784,7 @@ void TController::OrdersClear() 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) + iDirection = iDirectionOrder; // kierunek (względem sprzęgów pojazdu z AI) właśnie został ustalony (zmieniony) if (iDirection) { // jeśli jest ustalony kierunek TDynamicObject *old = pVehicle, *d = pVehicle; // w tym siedzi AI @@ -1794,7 +1793,7 @@ void TController::Activation() ZeroSpeed(); ZeroDirection(); mvOccupied->SpringBrakeActivate(true); - if (TestFlag(d->MoverParameters->Couplers[iDirectionOrder < 0 ? 1 : 0].CouplingFlag, ctrain_controll)) { + if (TestFlag(d->MoverParameters->Couplers[iDirectionOrder < 0 ? end::rear : end::front].CouplingFlag, ctrain_controll)) { mvControlling->MainSwitch( false); // dezaktywacja czuwaka, jeśli przejście do innego członu mvOccupied->DecLocalBrakeLevel(LocalBrakePosNo); // zwolnienie hamulca w opuszczanym pojeździe // mvOccupied->BrakeLevelSet((mvOccupied->BrakeHandle==FVel6)?4:-2); //odcięcie na @@ -1806,20 +1805,17 @@ void TController::Activation() mvOccupied->ActiveCab = mvOccupied->CabNo; // użytkownik moze zmienić ActiveCab wychodząc mvOccupied->CabDeactivisation(); // tak jest w Train.cpp // przejście AI na drugą stronę EN57, ET41 itp. - while (TestFlag(d->MoverParameters->Couplers[iDirection < 0 ? 1 : 0].CouplingFlag, ctrain_controll)) + while (TestFlag(d->MoverParameters->Couplers[iDirection < 0 ? end::rear : end::front].CouplingFlag, ctrain_controll)) { // jeśli pojazd z przodu jest ukrotniony, to przechodzimy do niego d = iDirection * d->DirectionGet() < 0 ? d->Next() : d->Prev(); // przechodzimy do następnego członu if (d) { - drugi = d->Mechanik; // zapamiętanie tego, co ewentualnie tam siedzi, żeby w razie - // dwóch zamienić miejscami + drugi = d->Mechanik; // zapamiętanie tego, co ewentualnie tam siedzi, żeby w razie dwóch zamienić miejscami d->Mechanik = this; // na razie bilokacja - d->MoverParameters->SetInternalCommand( - "", 0, 0); // usunięcie ewentualnie zalegającej komendy (Change_direction?) + d->MoverParameters->SetInternalCommand("", 0, 0); // usunięcie ewentualnie zalegającej komendy (Change_direction?) if (d->DirectionGet() != pVehicle->DirectionGet()) // jeśli są przeciwne do siebie - iDirection = -iDirection; // to będziemy jechać w drugą stronę względem - // zasiedzianego pojazdu + iDirection = -iDirection; // to będziemy jechać w drugą stronę względem zasiedzianego pojazdu pVehicle->Mechanik = drugi; // wsadzamy tego, co ewentualnie był (podwójna trakcja) pVehicle->MoverParameters->CabNo = 0; // wyłączanie kabin po drodze pVehicle->MoverParameters->ActiveCab = 0; // i zaznaczenie, że nie ma tam nikogo diff --git a/DynObj.cpp b/DynObj.cpp index 8663c2e7..4cde4156 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -2009,12 +2009,18 @@ TDynamicObject::Init(std::string Name, // nazwa pojazdu, np. "EU07-424" smBuforPrawy[ i ]->WillBeAnimated(); } } - for( auto &axle : m_axlesounds ) { - // wyszukiwanie osi (0 jest na końcu, dlatego dodajemy długość?) - axle.distance = ( - Reversed ? - -axle.offset : - ( axle.offset + MoverParameters->Dim.L ) ) + fDist; + if( Track->fSoundDistance > 0.f ) { + for( auto &axle : m_axlesounds ) { + // wyszukiwanie osi (0 jest na końcu, dlatego dodajemy długość?) + axle.distance = + clamp_circular( + ( Reversed ? + -axle.offset : + axle.offset ) + - 0.5 * MoverParameters->Dim.L + + fDist, + Track->fSoundDistance ); + } } // McZapkie-250202 end. Track->AddDynamicObject(this); // wstawiamy do toru na pozycję 0, a potem przesuniemy @@ -3155,12 +3161,19 @@ bool TDynamicObject::Update(double dt, double dt1) if( MyTrack->fSoundDistance != -1 ) { if( MyTrack->fSoundDistance != dRailLength ) { - dRailLength = MyTrack->fSoundDistance; - for( auto &axle : m_axlesounds ) { - axle.distance = axle.offset + MoverParameters->Dim.L; + if( dRailLength > 0.0 ) { + for( auto &axle : m_axlesounds ) { + axle.distance = + clamp_circular( + axle.distance - dRailLength + + axle.offset + /* - 0.5 * MoverParameters->Dim.L */, + MyTrack->fSoundDistance ); + } } + dRailLength = MyTrack->fSoundDistance; } - if( dRailLength != -1 ) { + if( dRailLength > 0.0 ) { if( MoverParameters->Vel > 0 ) { // TODO: track quality and/or environment factors as separate subroutine auto volume = @@ -3182,35 +3195,37 @@ bool TDynamicObject::Update(double dt, double dt1) break; } } - - auto axleindex { 0 }; - for( auto &axle : m_axlesounds ) { - axle.distance -= dDOMoveLen * Sign( dDOMoveLen ); - if( axle.distance < 0 ) { - axle.distance += dRailLength; - if( MoverParameters->Vel > 2.5 ) { - // NOTE: for combined clatter sound we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range - auto const frequency = ( - true == axle.clatter.is_combined() ? - MoverParameters->Vel * 0.01 : - 1.0 ); - axle.clatter - .pitch( frequency ) - .gain( volume ) - .play(); - // crude bump simulation, drop down on even axles, move back up on the odd ones - MoverParameters->AccVert += - interpolate( - 0.01, 0.05, - clamp( - GetVelocity() / ( 1 + MoverParameters->Vmax ), - 0.0, 1.0 ) ) - * ( ( axleindex % 2 ) != 0 ? - 1 : - -1 ); + if( dRailLength > 0.0 ) { + auto axleindex { 0 }; + for( auto &axle : m_axlesounds ) { + axle.distance += dDOMoveLen * DirectionGet(); + if( ( axle.distance < 0 ) + || ( axle.distance > dRailLength ) ) { + axle.distance = clamp_circular( axle.distance, dRailLength ); + if( MoverParameters->Vel > 0.1 ) { + // NOTE: for combined clatter sound we supply 1/100th of actual value, as the sound module converts does the opposite, converting received (typically) 0-1 values to 0-100 range + auto const frequency = ( + true == axle.clatter.is_combined() ? + MoverParameters->Vel * 0.01 : + 1.0 ); + axle.clatter + .pitch( frequency ) + .gain( volume ) + .play(); + // crude bump simulation, drop down on even axles, move back up on the odd ones + MoverParameters->AccVert += + interpolate( + 0.01, 0.05, + clamp( + GetVelocity() / ( 1 + MoverParameters->Vmax ), + 0.0, 1.0 ) ) + * ( ( axleindex % 2 ) != 0 ? + 1 : + -1 ); + } } + ++axleindex; } - ++axleindex; } } } @@ -4268,11 +4283,24 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co std::string asAnimName; bool Stop_InternalData = false; pants = NULL; // wskaźnik pierwszego obiektu animującego dla pantografów - cParser parser( TypeName + ".mmd", cParser::buffer_FILE, asBaseDir ); - if( false == parser.ok() ) { - ErrorLog( "Failed to load appearance data for vehicle " + MoverParameters->Name ); - return; + { + // preliminary check whether the file exists + cParser parser( TypeName + ".mmd", cParser::buffer_FILE, asBaseDir ); + if( false == parser.ok() ) { + ErrorLog( "Failed to load appearance data for vehicle " + MoverParameters->Name ); + return; + } } + // use #include wrapper to access the appearance data file + // this allows us to provide the file content with user-defined parameters + cParser parser( + "include " + TypeName + ".mmd" + + " " + asName // (p1) + + " " + TypeName // (p2) + + " " + ReplacableSkin // (p3) + + " end", + cParser::buffer_TEXT, + asBaseDir ); std::string token; do { token = ""; @@ -4430,15 +4458,30 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co parser.getTokens(); parser >> asModel; replace_slashes( asModel ); - if( asModel[ 0 ] == '/' ) { - // filename can potentially begin with a slash, and we don't need it - asModel.erase( 0, 1 ); - } + erase_leading_slashes( asModel ); asModel = asBaseDir + asModel; // McZapkie-200702 - dynamics maja swoje modele w dynamic/basedir Global.asCurrentTexturePath = asBaseDir; // biezaca sciezka do tekstur to dynamic/... mdLowPolyInt = TModelsManager::GetModel(asModel, true); } + else if(token == "attachments:") { + // additional 3d models attached to main body + // content provided as a series of values together enclosed in "{}" + // each value is a name of additional 3d model + // value can be optionally set of values enclosed in "[]" in which case one value will be picked randomly + // TBD: reconsider something more yaml-compliant and/or ability to define offset and rotation + while( ( ( token = parser.getToken() ) != "" ) + && ( token != "}" ) ) { + auto attachmentmodelname { deserialize_random_set( parser ) }; + replace_slashes( attachmentmodelname ); + Global.asCurrentTexturePath = asBaseDir; // biezaca sciezka do tekstur to dynamic/... + auto *attachmentmodel { TModelsManager::GetModel( asBaseDir + attachmentmodelname, true ) }; + if( attachmentmodel != nullptr ) { + mdAttachments.emplace_back( attachmentmodel ); + } + } + } + else if(token == "loads:") { // default load visualization models overrides // content provided as "key: value" pairs together enclosed in "{}" @@ -4982,18 +5025,18 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co // add another axle entry to the list axle_sounds axle { 0, - std::atof( token.c_str() ), + std::atof( token.c_str() ) * -1.0, // for axle locations negative value means ahead of centre but vehicle faces +Z in 'its' space { sound_placement::external, static_cast( dSDist ) } }; axle.clatter.deserialize( parser, sound_type::single ); axle.clatter.owner( this ); - axle.clatter.offset( { 0, 0, -axle.offset } ); // vehicle faces +Z in 'its' space, for axle locations negative value means ahead of centre + axle.clatter.offset( { 0, 0, axle.offset } ); m_axlesounds.emplace_back( axle ); } // arrange the axles in case they're listed out of order std::sort( std::begin( m_axlesounds ), std::end( m_axlesounds ), []( axle_sounds const &Left, axle_sounds const &Right ) { - return ( Left.offset < Right.offset ); } ); + return ( Left.offset > Right.offset ); } ); } else if( ( token == "engine:" ) diff --git a/DynObj.h b/DynObj.h index bae28d60..b6c72a0d 100644 --- a/DynObj.h +++ b/DynObj.h @@ -200,6 +200,7 @@ public: TModel3d *mdLowPolyInt; // ABu 010305: wnetrze lowpoly std::array LowPolyIntCabs {}; // pointers to low fidelity version of individual driver cabs bool JointCabs{ false }; // flag for vehicles with multiple virtual 'cabs' sharing location and 3d model(s) + std::vector mdAttachments; // additional models attached to main body struct destination_data { TSubModel *sign { nullptr }; // submodel mapped with replacable texture -4 bool has_light { false }; // the submodel was originally configured with self-illumination attribute diff --git a/EvLaunch.cpp b/EvLaunch.cpp index 39011426..275be42d 100644 --- a/EvLaunch.cpp +++ b/EvLaunch.cpp @@ -78,25 +78,6 @@ bool TEventLauncher::Load(cParser *parser) } parser->getTokens(); *parser >> DeltaTime; - if (DeltaTime < 0) - DeltaTime = -DeltaTime; // dla ujemnego zmieniamy na dodatni - else if (DeltaTime > 0) - { // wartość dodatnia oznacza wyzwalanie o określonej godzinie - iMinute = int(DeltaTime) % 100; // minuty są najmłodszymi cyframi dziesietnymi - iHour = int(DeltaTime - iMinute) / 100; // godzina to setki - DeltaTime = 0; // bez powtórzeń - // potentially shift the provided time by requested offset - auto const timeoffset { static_cast( Global.ScenarioTimeOffset * 60 ) }; - if( timeoffset != 0 ) { - auto const adjustedtime { clamp_circular( iHour * 60 + iMinute + timeoffset, 24 * 60 ) }; - iHour = ( adjustedtime / 60 ) % 24; - iMinute = adjustedtime % 60; - } - WriteLog( - "EventLauncher at " - + std::to_string( iHour ) + ":" - + ( iMinute < 10 ? "0" : "" ) + to_string( iMinute ) ); // wyświetlenie czasu - } parser->getTokens(); *parser >> token; asEvent1Name = token; // pierwszy event @@ -145,6 +126,29 @@ bool TEventLauncher::Load(cParser *parser) parser->getTokens(); // słowo zamykające *parser >> token; } + + if( DeltaTime < 0 ) + DeltaTime = -DeltaTime; // dla ujemnego zmieniamy na dodatni + else if( DeltaTime > 0 ) { // wartość dodatnia oznacza wyzwalanie o określonej godzinie + iMinute = int( DeltaTime ) % 100; // minuty są najmłodszymi cyframi dziesietnymi + iHour = int( DeltaTime - iMinute ) / 100; // godzina to setki + DeltaTime = 0; // bez powtórzeń + // potentially shift the provided time by requested offset + auto const timeoffset{ static_cast( Global.ScenarioTimeOffset * 60 ) }; + if( timeoffset != 0 ) { + auto const adjustedtime{ clamp_circular( iHour * 60 + iMinute + timeoffset, 24 * 60 ) }; + iHour = ( adjustedtime / 60 ) % 24; + iMinute = adjustedtime % 60; + } + + WriteLog( + "EventLauncher at " + + std::to_string( iHour ) + ":" + + ( iMinute < 10 ? "0" : "" ) + to_string( iMinute ) + + " (" + asEvent1Name + + ( asEvent2Name != "none" ? " / " + asEvent2Name : "" ) + + ")" ); // wyświetlenie czasu + } return true; } diff --git a/TrkFoll.cpp b/TrkFoll.cpp index d1dba363..d7f2aebe 100644 --- a/TrkFoll.cpp +++ b/TrkFoll.cpp @@ -244,7 +244,7 @@ bool TTrackFollower::Move(double fDistance, bool bPrimary) { // gdy zostaje na tym samym torze (przesuwanie już nie zmienia toru) if (bPrimary) { // tylko gdy początkowe ustawienie, dodajemy eventy stania do kolejki - if (Owner->MoverParameters->CabNo != 0) { + if (Owner->MoverParameters->ActiveCab != 0) { pCurrentTrack->QueueEvents( pCurrentTrack->m_events1, Owner, -1.0 ); pCurrentTrack->QueueEvents( pCurrentTrack->m_events2, Owner, -1.0 ); diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 34267430..cb4be4be 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -44,6 +44,7 @@ drivingaid_panel::update() { auto const *mover = controlled->MoverParameters; auto const *driver = controlled->Mechanik; + auto const *owner = ( controlled->ctOwner != nullptr ? controlled->ctOwner : controlled->Mechanik ); { // throttle, velocity, speed limits and grade std::string expandedtext; @@ -60,8 +61,8 @@ drivingaid_panel::update() { gradetext = m_buffer.data(); } // next speed limit - auto const speedlimit { static_cast( std::floor( driver->VelDesired ) ) }; - auto const nextspeedlimit { static_cast( std::floor( driver->VelNext ) ) }; + auto const speedlimit { static_cast( std::floor( owner->VelDesired ) ) }; + auto const nextspeedlimit { static_cast( std::floor( owner->VelNext ) ) }; std::string nextspeedlimittext; if( nextspeedlimit != speedlimit ) { std::snprintf( @@ -117,7 +118,7 @@ drivingaid_panel::update() { { // alerter, hints std::string expandedtext; if( is_expanded ) { - auto const stoptime { static_cast( std::ceil( -1.0 * controlled->Mechanik->fStopTime ) ) }; + auto const stoptime { static_cast( std::ceil( -1.0 * owner->fStopTime ) ) }; if( stoptime > 0 ) { std::snprintf( m_buffer.data(), m_buffer.size(), @@ -126,7 +127,7 @@ drivingaid_panel::update() { expandedtext = m_buffer.data(); } else { - auto const trackblockdistance{ std::abs( controlled->Mechanik->TrackBlock() ) }; + auto const trackblockdistance{ std::abs( owner->TrackBlock() ) }; if( trackblockdistance <= 75.0 ) { std::snprintf( m_buffer.data(), m_buffer.size(), diff --git a/parser.h b/parser.h index f47f349b..fb44f55d 100644 --- a/parser.h +++ b/parser.h @@ -50,20 +50,24 @@ class cParser //: public std::stringstream bool expectToken( std::string const &Value ) { return readToken() == Value; }; + inline bool eof() { return mStream->eof(); }; + inline bool ok() { - return !mStream->fail(); }; + return ( !mStream->fail() ); }; cParser & autoclear( bool const Autoclear ); + inline bool autoclear() const { return m_autoclear; } bool getTokens( unsigned int Count = 1, bool ToLower = true, char const *Break = "\n\r\t ;" ); // returns next incoming token, if any, without removing it from the set + inline std::string peek() const { return ( diff --git a/particles.cpp b/particles.cpp index 23ab6b5c..0889fe8a 100644 --- a/particles.cpp +++ b/particles.cpp @@ -167,10 +167,16 @@ smoke_source::update( double const Timedelta, bool const Onlydespawn ) { std::min( m_spawncount + ( m_spawnrate * Timedelta * Global.SmokeFidelity ), m_max_particles ) ); - // HACK: don't spawn particles in tunnels, to prevent smoke clipping through 'terrain' outside - if( ( m_ownertype == owner_type::vehicle ) - && ( m_owner.vehicle->RaTrackGet()->eEnvironment == e_tunnel ) ) { + // consider special spawn rate cases + if( m_ownertype == owner_type::vehicle ) { + // HACK: don't spawn particles in tunnels, to prevent smoke clipping through 'terrain' outside + if( m_owner.vehicle->RaTrackGet()->eEnvironment == e_tunnel ) { m_spawncount = 0.f; + } + if( false == m_owner.vehicle->bEnabled ) { + // don't spawn particles for vehicles which left the scenario + m_spawncount = 0.f; + } } // update spawned particles for( auto particleiterator { std::begin( m_particles ) }; particleiterator != std::end( m_particles ); ++particleiterator ) { diff --git a/renderer.cpp b/renderer.cpp index d94d2984..4ce99e6c 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -2393,8 +2393,13 @@ opengl_renderer::Render( TDynamicObject *Dynamic ) { } } if( Dynamic->mdModel ) { + // main model Render( Dynamic->mdModel, Dynamic->Material(), squaredistance ); } + // optional attached models + for( auto *attachment : Dynamic->mdAttachments ) { + Render( attachment, Dynamic->Material(), squaredistance ); + } // post-render cleanup m_renderspecular = false; if( Dynamic->fShade > 0.0f ) { @@ -2411,10 +2416,18 @@ opengl_renderer::Render( TDynamicObject *Dynamic ) { Render( Dynamic->mdLowPolyInt, Dynamic->Material(), squaredistance ); // } } - if( Dynamic->mdModel ) + if( Dynamic->mdModel ) { + // main model Render( Dynamic->mdModel, Dynamic->Material(), squaredistance ); - if( Dynamic->mdLoad ) // renderowanie nieprzezroczystego ładunku + } + // optional attached models + for( auto *attachment : Dynamic->mdAttachments ) { + Render( attachment, Dynamic->Material(), squaredistance ); + } + if( Dynamic->mdLoad ) { + // renderowanie nieprzezroczystego ładunku Render( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} ); + } // post-render cleanup break; } @@ -3523,11 +3536,18 @@ opengl_renderer::Render_Alpha( TDynamicObject *Dynamic ) { // } } - if( Dynamic->mdModel ) + if( Dynamic->mdModel ) { + // main model Render_Alpha( Dynamic->mdModel, Dynamic->Material(), squaredistance ); - - if( Dynamic->mdLoad ) // renderowanie nieprzezroczystego ładunku + } + // optional attached models + for( auto *attachment : Dynamic->mdAttachments ) { + Render_Alpha( attachment, Dynamic->Material(), squaredistance ); + } + if( Dynamic->mdLoad ) { + // renderowanie nieprzezroczystego ładunku Render_Alpha( Dynamic->mdLoad, Dynamic->Material(), squaredistance, { 0.f, Dynamic->LoadOffset, 0.f }, {} ); + } // post-render cleanup m_renderspecular = false; diff --git a/version.h b/version.h index 0a6ea607..05bc72ea 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 19 -#define VERSION_MINOR 916 +#define VERSION_MINOR 925 #define VERSION_REVISION 0