diff --git a/Driver.cpp b/Driver.cpp index d1747092..39d65109 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -2194,6 +2194,7 @@ bool TController::CheckVehicles(TOrders user) } // with the order established the virtual train manager can do their work p = pVehicles[0]; + ControlledEnginesCount = ( p->MoverParameters->Power > 1.0 ? 1 : 0 ); while (p) { if( p != pVehicle ) { @@ -2207,6 +2208,11 @@ bool TController::CheckVehicles(TOrders user) p->MoverParameters->ConverterSwitch( true, range_t::local ); } } + else { + if( p->MoverParameters->Power > 1.0 ) { + ++ControlledEnginesCount; + } + } } if (p->asDestination == "none") @@ -2537,9 +2543,6 @@ bool TController::PrepareEngine() } if (mvControlling->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) { // jeśli silnikowy jest pantografującym - mvControlling->OperatePantographsValve( operation_t::enable ); - mvControlling->OperatePantographValve( end::front, operation_t::enable ); - mvControlling->OperatePantographValve( end::rear, operation_t::enable ); if (mvControlling->PantPress < 4.2) { // załączenie małej sprężarki if( false == mvControlling->PantAutoValve ) { @@ -2550,11 +2553,15 @@ bool TController::PrepareEngine() } else { // jeżeli jest wystarczające ciśnienie w pantografach - if ((!mvControlling->bPantKurek3) || - (mvControlling->PantPress <= - mvControlling->ScndPipePress)) // kurek przełączony albo główna już pompuje + if ((!mvControlling->bPantKurek3) + || (mvControlling->PantPress <= mvControlling->ScndPipePress)) // kurek przełączony albo główna już pompuje mvControlling->PantCompFlag = false; // sprężarkę pantografów można już wyłączyć } + if( ( fOverhead2 == -1.0 ) && ( iOverheadDown == 0 ) ) { + mvControlling->OperatePantographsValve( operation_t::enable ); + mvControlling->OperatePantographValve( end::front, operation_t::enable ); + mvControlling->OperatePantographValve( end::rear, operation_t::enable ); + } } } @@ -3163,7 +3170,7 @@ bool TController::IncSpeed() 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 ) }; + auto const sufficientpowermargin { fVoltage - useseriesmodevoltage > ( IsHeavyCargoTrain ? 100.0 : 75.0 ) * ControlledEnginesCount }; OK = ( sufficientpowermargin ? @@ -3183,7 +3190,7 @@ bool TController::IncSpeed() mvControlling->RList[ std::min( mvControlling->MainCtrlPos + 1, mvControlling->MainCtrlPosNo ) ].Bn == 1 ? mvControlling->EnginePowerSource.CollectorParameters.MinV : useseriesmodevoltage ) - > ( IsHeavyCargoTrain ? 80.0 : 60.0 ) }; + > ( IsHeavyCargoTrain ? 80.0 : 60.0 ) * ControlledEnginesCount }; OK = ( ( sufficientpowermargin && ( false == mvControlling->DelayCtrlFlag ) ) ? @@ -3994,8 +4001,7 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N if (NewCommand == "Overhead") { // informacja o stanie sieci trakcyjnej - fOverhead1 = - NewValue1; // informacja o napięciu w sieci trakcyjnej (0=brak drutu, zatrzymaj!) + 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 @@ -4700,8 +4706,12 @@ TController::UpdateSituation(double dt) { if( ( fOverhead2 > 0.0 ) || iOverheadDown ) { // jazda z opuszczonymi pantografami - mvControlling->OperatePantographValve( end::front, operation_t::disable ); - mvControlling->OperatePantographValve( end::rear, operation_t::disable ); + if( mvControlling->Pantographs[ end::front ].is_active ) { + mvControlling->OperatePantographValve( end::front, operation_t::disable ); + } + if( mvControlling->Pantographs[ end::rear ].is_active ) { + mvControlling->OperatePantographValve( end::rear, operation_t::disable ); + } } else { // jeśli nie trzeba opuszczać pantografów @@ -4779,15 +4789,17 @@ TController::UpdateSituation(double dt) { // 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( ( iDirection >= 0 ) && ( useregularpantographlayout ) ) { - // jak jedzie w kierunku sprzęgu 0 - if( mvControlling->PantFrontVolt == 0.0 ) { - mvControlling->OperatePantographValve( end::front, operation_t::enable ); + if( mvControlling->EnginePowerSource.CollectorParameters.CollectorsNo > 1 ) { + if( ( iDirection >= 0 ) && ( useregularpantographlayout ) ) { + // jak jedzie w kierunku sprzęgu 0 + if( mvControlling->PantFrontVolt == 0.0 ) { + mvControlling->OperatePantographValve( end::front, operation_t::enable ); + } } - } - else { - if( mvControlling->PantRearVolt == 0.0 ) { - mvControlling->OperatePantographValve( end::rear, operation_t::enable ); + else { + if( mvControlling->PantRearVolt == 0.0 ) { + mvControlling->OperatePantographValve( end::rear, operation_t::enable ); + } } } } diff --git a/Driver.h b/Driver.h index e4e77e51..9ccfab6a 100644 --- a/Driver.h +++ b/Driver.h @@ -459,6 +459,7 @@ private: double fMass = 0.0; // całkowita masa do liczenia stycznej składowej grawitacji double fAccGravity = 0.0; // przyspieszenie składowej stycznej grawitacji int iVehicles = 0; // ilość pojazdów w składzie + int ControlledEnginesCount{ 0 }; // number of powered vehicles under driver's control bool iEngineActive{ false }; // ABu: Czy silnik byl juz zalaczony bool IsCargoTrain{ false }; bool IsHeavyCargoTrain{ false }; diff --git a/DynObj.cpp b/DynObj.cpp index ba47e876..af7cc565 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -3805,24 +3805,28 @@ void TDynamicObject::RenderSounds() { m_startjoltplayed = false; } - double const dt{ Timer::GetDeltaRenderTime() }; - double volume{ 0.0 }; - double frequency{ 1.0 }; - + auto const dt{ Timer::GetDeltaRenderTime() }; m_powertrainsounds.render( *MoverParameters, dt ); + auto volume{ 0.0 }; + auto frequency{ 1.0 }; + // NBMX dzwiek przetwornicy if( MoverParameters->ConverterFlag ) { - frequency = ( - MoverParameters->EngineType != TEngineType::ElectricSeriesMotor ? - 1.0 : - MoverParameters->PantographVoltage > 0.0 ? - MoverParameters->PantographVoltage / ( MoverParameters->NominalVoltage * MoverParameters->RList[ MoverParameters->RlistSize ].Mn ) : - 1.0 ); - frequency = sConverter.m_frequencyoffset + sConverter.m_frequencyfactor * frequency; - sConverter - .pitch( clamp( frequency, 0.75, 1.25 ) ) // arbitrary limits ) - .play( sound_flags::exclusive | sound_flags::looping ); + if( MoverParameters->EngineType == TEngineType::ElectricSeriesMotor ) { + auto const voltage { std::max( MoverParameters->GetAnyTrainsetVoltage(), MoverParameters->PantographVoltage ) }; + if( voltage > 0.0 ) { + // NOTE: we do sound modulation here to avoid sudden jump on voltage loss + frequency = ( voltage / ( MoverParameters->NominalVoltage * MoverParameters->RList[ MoverParameters->RlistSize ].Mn ) ); + frequency *= sConverter.m_frequencyfactor + sConverter.m_frequencyoffset; + sConverter.pitch( clamp( frequency, 0.75, 1.25 ) ); // arbitrary limits ) + } + } + else { + frequency = sConverter.m_frequencyoffset + sConverter.m_frequencyfactor * frequency; + sConverter.pitch( clamp( frequency, 0.75, 1.25 ) ); // arbitrary limits ) + } + sConverter.play( sound_flags::exclusive | sound_flags::looping ); } else { sConverter.stop(); @@ -6522,14 +6526,12 @@ void TDynamicObject::OverheadTrack(float o) } else if (o > 0.0) { // opuszczenie pantografów - ctOwner->iOverheadZero |= - iOverheadMask; // ustawienie bitu - ma jechać bez pobierania prądu + ctOwner->iOverheadZero |= iOverheadMask; // ustawienie bitu - ma jechać bez pobierania prądu ctOwner->iOverheadDown |= iOverheadMask; // ustawienie bitu - ma opuścić pantograf } else { // jazda bezprądowa z podniesionym pantografem - ctOwner->iOverheadZero |= - iOverheadMask; // ustawienie bitu - ma jechać bez pobierania prądu + ctOwner->iOverheadZero |= iOverheadMask; // ustawienie bitu - ma jechać bez pobierania prądu ctOwner->iOverheadDown &= ~iOverheadMask; // zerowanie bitu - może podnieść pantograf } } diff --git a/Globals.h b/Globals.h index 9c79a45b..2c36081d 100644 --- a/Globals.h +++ b/Globals.h @@ -83,7 +83,7 @@ struct global_settings { std::string Weather{ "cloudy:" }; // current weather bool FullPhysics{ true }; // full calculations performed for each simulation step bool bnewAirCouplers{ true }; - double fMoveLight{ -1 }; // numer dnia w roku albo -1 + float fMoveLight{ -1.f }; // numer dnia w roku albo -1 bool FakeLight{ false }; // toggle between fixed and dynamic daylight double fTimeSpeed{ 1.0 }; // przyspieszenie czasu, zmienna do testów double fLatitudeDeg{ 52.0 }; // szerokość geograficzna diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index b9bb4ae1..f86707d4 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -170,13 +170,13 @@ enum class range_t { }; // possible settings of enable/disable input pair; exclusive enum class operation_t { - enable_on = 1, - enable_off = -1, - disable_on = 2, - disable_off = -2, none = 0, enable, disable, + enable_on, + enable_off, + disable_on, + disable_off, }; // start method for devices; exclusive enum class start_t { @@ -1380,6 +1380,7 @@ public: bool Mains = false; /*polozenie glownego wylacznika*/ double MainsInitTime{ 0.0 }; // config, initialization time (in seconds) of the main circuit after it receives power, before it can be closed double MainsInitTimeCountdown{ 0.0 }; // current state of main circuit initialization, remaining time (in seconds) until it's ready + bool LineBreakerClosesAtNoPowerPosOnly{ false }; int MainCtrlPos = 0; /*polozenie glownego nastawnika*/ int ScndCtrlPos = 0; /*polozenie dodatkowego nastawnika*/ int LightsPos = 0; /*polozenie przelacznika wielopozycyjnego swiatel*/ diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 583fe5e9..5d5f655a 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -3193,6 +3193,7 @@ void TMoverParameters::MainSwitch_( bool const State ) { && ( true == NoVoltRelay ) && ( true == OvervoltageRelay ) && ( LastSwitchingTime > CtrlDelay ) + && ( HasCamshaft ? IsMainCtrlActualNoPowerPos() : ( LineBreakerClosesAtNoPowerPosOnly ? IsMainCtrlNoPowerPos() : true ) ) && ( false == TestFlag( DamageFlag, dtrain_out ) ) && ( false == TestFlag( EngDmgFlag, 1 ) ) ) ) { @@ -5086,8 +5087,12 @@ double TMoverParameters::TractionForce( double dt ) { case TEngineType::DieselElectric: { // TODO: move this to the auto relay check when the electric engine code paths are unified - StLinFlag = MotorConnectorsCheck(); - StLinFlag &= IsMainCtrlNoPowerPos(); + StLinFlag &= MotorConnectorsCheck(); + StLinFlag |= ( + ( MainCtrlActualPos == 0 ) + && ( TrainType != dt_EZT ? + MainCtrlPowerPos() == 1 : + MainCtrlPowerPos() > 0 ) ); break; } @@ -6145,12 +6150,14 @@ bool TMoverParameters::AutoRelayCheck(void) bool OK = false; // b:int; bool ARC = false; - auto const motorconnectors { MotorConnectorsCheck() }; + auto const motorconnectorsoff { false == MotorConnectorsCheck() }; // Ra 2014-06: dla SN61 nie działa prawidłowo // yBARC - rozlaczenie stycznikow liniowych - if( ( false == motorconnectors ) - || ( HasCamshaft ? IsMainCtrlActualNoPowerPos() : IsMainCtrlNoPowerPos() ) ) { + if( ( motorconnectorsoff ) + || ( HasCamshaft ? + IsMainCtrlActualNoPowerPos() : + IsMainCtrlNoPowerPos() ) ) { StLinFlag = false; OK = false; if( false == DynamicBrakeFlag ) { @@ -6329,7 +6336,12 @@ bool TMoverParameters::AutoRelayCheck(void) { OK = false; // ybARC - zalaczenie stycznikow liniowych - if( true == motorconnectors ) { + if( ( false == motorconnectorsoff ) + && ( MainCtrlActualPos == 0 ) + && ( ( TrainType == dt_EZT || HasCamshaft ) ? + MainCtrlPowerPos() > 0 : + MainCtrlPowerPos() == 1 ) ) { + DelayCtrlFlag = true; if( LastRelayTime >= InitialCtrlDelay ) { StLinFlag = true; @@ -6339,14 +6351,15 @@ bool TMoverParameters::AutoRelayCheck(void) OK = true; } } - else + else { DelayCtrlFlag = false; + } if( ( false == StLinFlag ) && ( ( MainCtrlActualPos > 0 ) || ( ScndCtrlActualPos > 0 ) ) ) { - if( CoupledCtrl ) { + if( CoupledCtrl || HasCamshaft ) { if( TrainType == dt_EZT ) { // EN57 wal jednokierunkowy calosciowy @@ -6380,7 +6393,8 @@ bool TMoverParameters::AutoRelayCheck(void) else { // wal kulakowy dwukierunkowy if( LastRelayTime > CtrlDownDelay ) { - if( ScndCtrlActualPos > 0 ) { + if( ( CoupledCtrl ) + && ( ScndCtrlActualPos > 0 ) ) { --ScndCtrlActualPos; SetFlag( SoundFlag, sound::shuntfield ); } @@ -6421,16 +6435,7 @@ bool TMoverParameters::MotorConnectorsCheck() { || ( true == StLinSwitchOff ) || ( DirActive == 0 ) }; - if( connectorsoff ) { return false; } - - auto const connectorson { - ( true == StLinFlag ) - || ( ( MainCtrlActualPos == 0 ) - && ( ( TrainType != dt_EZT ? - MainCtrlPowerPos() == 1 : - MainCtrlPowerPos() > 0 ) ) ) }; - - return connectorson; + return ( false == connectorsoff ); } bool TMoverParameters::OperatePantographsValve( operation_t const State, range_t const Notify ) { @@ -6439,8 +6444,15 @@ bool TMoverParameters::OperatePantographsValve( operation_t const State, range_t auto &valve { PantsValve }; - valve.is_enabled = ( State == operation_t::enable ); - valve.is_disabled = ( State == operation_t::disable ); + switch( State ) { + case operation_t::none: { valve.is_enabled = false; valve.is_disabled = false; break; } + case operation_t::enable: { valve.is_enabled = true; valve.is_disabled = false; break; } + case operation_t::disable: { valve.is_enabled = false; valve.is_disabled = true; break; } + case operation_t::enable_on: { valve.is_enabled = true; break; } + case operation_t::enable_off: { valve.is_enabled = false; break; } + case operation_t::disable_on: { valve.is_disabled = true; break; } + case operation_t::disable_off: { valve.is_disabled = false; break; } + } if( Notify != range_t::local ) { SendCtrlToNext( @@ -6458,9 +6470,16 @@ bool TMoverParameters::OperatePantographsValve( operation_t const State, range_t bool TMoverParameters::OperatePantographValve( end const End, operation_t const State, range_t const Notify ) { auto &valve { Pantographs[ End ].valve }; - - valve.is_enabled = ( State == operation_t::enable ); - valve.is_disabled = ( State == operation_t::disable ); + + switch( State ) { + case operation_t::none: { valve.is_enabled = false; valve.is_disabled = false; break; } + case operation_t::enable: { valve.is_enabled = true; valve.is_disabled = false; break; } + case operation_t::disable: { valve.is_enabled = false; valve.is_disabled = true; break; } + case operation_t::enable_on: { valve.is_enabled = true; break; } + case operation_t::enable_off: { valve.is_enabled = false; break; } + case operation_t::disable_on: { valve.is_disabled = true; break; } + case operation_t::disable_off: { valve.is_disabled = false; break; } + } if( Notify != range_t::local ) { SendCtrlToNext( diff --git a/Model3d.cpp b/Model3d.cpp index e75f6f2d..3dc31f8d 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -353,6 +353,9 @@ int TSubModel::Load( cParser &parser, TModel3d *Model, /*int Pos,*/ bool dynamic if( Opacity > 1.f ) { Opacity = std::min( 1.f, Opacity * 0.01f ); } + if( Opacity < -1.f ) { + Opacity = std::max( -1.f, Opacity * 0.01f ); + } if (!parser.expectToken("map:")) Error("Model map parse failure!"); diff --git a/Texture.cpp b/Texture.cpp index 889aeb72..70f3e9da 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -1284,7 +1284,7 @@ texture_manager::info() const { } return - "; textures: " + "textures: " #ifdef EU07_DEFERRED_TEXTURE_UPLOAD + std::to_string( readytexturecount ) + " (" diff --git a/Train.cpp b/Train.cpp index 7dc5ec43..0835c571 100644 --- a/Train.cpp +++ b/Train.cpp @@ -39,11 +39,19 @@ extern user_command command; } */ +void +control_mapper::clear() { + + *this = control_mapper(); +} + void control_mapper::insert( TGauge const &Gauge, std::string const &Label ) { if( Gauge.SubModel != nullptr ) { m_controlnames.emplace( Gauge.SubModel, Label ); } if( Gauge.SubModelOn != nullptr ) { m_controlnames.emplace( Gauge.SubModelOn, Label ); } + + m_names.emplace( Label ); } std::string @@ -58,6 +66,12 @@ control_mapper::find( TSubModel const *Control ) const { } } +bool +control_mapper::contains( std::string const Control ) const { + + return ( m_names.find( Control ) != m_names.end() ); +} + void TCab::Load(cParser &Parser) { // NOTE: clearing control tables here is bit of a crutch, imposed by current scheme of loading compartments anew on each cab change @@ -2075,10 +2089,16 @@ void TTrain::OnCommand_pantographraisefront( TTrain *Train, command_data const & // HACK: presence of pantograph selector prevents manual operation of the individual valves if( Train->ggPantSelectButton.SubModel ) { return; } + // prevent operation without submodel outside of engine compartment + if( ( Train->iCabn != 0 ) + && ( false == Train->m_controlmapper.contains( "pantfront_sw:" ) ) ) { return; } if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down - Train->mvControlled->OperatePantographValve( end::front, operation_t::enable ); + Train->mvControlled->OperatePantographValve( end::front, + Train->mvOccupied->PantSwitchType == "impulse" ? + operation_t::enable_on : + operation_t::enable ); } else if( Command.action == GLFW_RELEASE ) { // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch @@ -2090,11 +2110,17 @@ void TTrain::OnCommand_pantographraiserear( TTrain *Train, command_data const &C // HACK: presence of pantograph selector prevents manual operation of the individual valves if( Train->ggPantSelectButton.SubModel ) { return; } + // prevent operation without submodel outside of engine compartment + if( ( Train->iCabn != 0 ) + && ( false == Train->m_controlmapper.contains( "pantrear_sw:" ) ) ) { return; } if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down - Train->mvControlled->OperatePantographValve( end::rear, operation_t::enable ); - } + Train->mvControlled->OperatePantographValve( end::rear, + Train->mvOccupied->PantSwitchType == "impulse" ? + operation_t::enable_on : + operation_t::enable ); + } else if( Command.action == GLFW_RELEASE ) { // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch OnCommand_pantographtogglerear( Train, Command ); @@ -2105,10 +2131,21 @@ void TTrain::OnCommand_pantographlowerfront( TTrain *Train, command_data const & // HACK: presence of pantograph selector prevents manual operation of the individual valves if( Train->ggPantSelectButton.SubModel ) { return; } + // prevent operation without submodel outside of engine compartment + if( ( Train->iCabn != 0 ) + && ( false == Train->m_controlmapper.contains( + Train->mvOccupied->PantSwitchType == "impulse" ? + "pantfrontoff_sw:" : + "pantfront_sw:" ) ) ) { + return; + } if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down - Train->mvControlled->OperatePantographValve( end::front, operation_t::disable ); + Train->mvControlled->OperatePantographValve( end::front, + Train->mvOccupied->PantSwitchType == "impulse" ? + operation_t::disable_on : + operation_t::disable ); } else if( Command.action == GLFW_RELEASE ) { // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch @@ -2120,14 +2157,24 @@ void TTrain::OnCommand_pantographlowerrear( TTrain *Train, command_data const &C // HACK: presence of pantograph selector prevents manual operation of the individual valves if( Train->ggPantSelectButton.SubModel ) { return; } + if( ( Train->iCabn != 0 ) + && ( false == Train->m_controlmapper.contains( + Train->mvOccupied->PantSwitchType == "impulse" ? + "pantrearoff_sw:" : + "pantrear_sw:" ) ) ) { + return; + } if( Command.action == GLFW_PRESS ) { // only reacting to press, so the switch doesn't flip back and forth if key is held down - Train->mvControlled->OperatePantographValve( end::rear, operation_t::disable ); + Train->mvControlled->OperatePantographValve( end::rear, + Train->mvOccupied->PantSwitchType == "impulse" ? + operation_t::disable_on : + operation_t::disable ); } else if( Command.action == GLFW_RELEASE ) { // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch - OnCommand_pantographtogglefront( Train, Command ); + OnCommand_pantographtogglerear( Train, Command ); } } @@ -6150,9 +6197,9 @@ bool TTrain::Update( double const Deltatime ) || ( mvControlled->MainCtrlActualPos == 0 ) ); // do EU04 btLampkaStyczn.Turn( - mvControlled->StLinFlag ? + ( ( mvOccupied->StLinFlag ) /* || ( mvOccupied->BrakePress > 2.0 ) || ( mvOccupied->PipePress < 3.6 ) */ ) ? false : - mvOccupied->BrakePress < 1.0 ); // mozna prowadzic rozruch + ( mvOccupied->BrakePress < 1.0 ) ); // mozna prowadzic rozruch if( ( ( mvControlled->CabOccupied == 1 ) && ( TestFlag( mvControlled->Couplers[ end::rear ].CouplingFlag, coupling::control ) ) ) || ( ( mvControlled->CabOccupied == -1 ) && ( TestFlag( mvControlled->Couplers[ end::front ].CouplingFlag, coupling::control ) ) ) ) { @@ -6380,7 +6427,7 @@ bool TTrain::Update( double const Deltatime ) if( ( mover->StLinFlag ) || ( mover->BrakePress > 2.0 ) - || ( mover->PipePress < 0.36 ) ) { + || ( mover->PipePress < 3.6 ) ) { btLampkaStycznB.Turn( false ); } else if( mover->BrakePress < 1.0 ) { @@ -6760,57 +6807,6 @@ bool TTrain::Update( double const Deltatime ) mvControlled->AntiSlippingBrake(); } } -/* - // NOTE: crude way to have the pantographs go back up if they're dropped due to insufficient pressure etc - // TODO: rework it into something more elegant, when redoing the whole consist/unit/cab etc arrangement - if( ( DynamicObject->Mechanik == nullptr ) - || ( false == DynamicObject->Mechanik->AIControllFlag ) ) { - // don't mess with the ai driving, at least not while switches don't follow ai-set vehicle state - if( ( mvControlled->Battery ) - || ( mvControlled->ConverterFlag ) ) { - if( ggPantAllDownButton.GetDesiredValue() < 0.05 ) { - // the 'lower all' button overrides state of switches, while active itself - if( ( false == mvControlled->PantFrontUp ) - && ( ggPantFrontButton.GetDesiredValue() >= 0.95 ) ) { - mvControlled->PantFront( true ); - } - if( ( false == mvControlled->PantRearUp ) - && ( ggPantRearButton.GetDesiredValue() >= 0.95 ) ) { - mvControlled->PantRear( true ); - } - } - if( ggPantSelectButton.SubModel ) { - if( ggPantSelectedButton.SubModel ) { - if( ( ggPantSelectedButton.type() == TGaugeType::toggle ) - && ( ggPantSelectedButton.GetDesiredValue() >= 0.95 ) ) { - change_pantograph_selection_state( true ); - } - } - else { - // HACK: force pantographs into currently selected state - change_pantograph_selection( 0, true ); - } - } - } - } -*/ -/* - // check whether we should raise the pantographs, based on volume in pantograph tank - // NOTE: disabled while switch state isn't preserved while moving between compartments - if( mvControlled->PantPress > ( - mvControlled->TrainType == dt_EZT ? - 2.4 : - 3.5 ) ) { - if( ( false == mvControlled->PantFrontUp ) - && ( ggPantFrontButton.GetValue() > 0.95 ) ) { - mvControlled->PantFront( true ); - } - if( ( false == mvControlled->PantRearUp ) - && ( ggPantRearButton.GetValue() > 0.95 ) ) { - mvControlled->PantRear( true ); - } - } -*/ // screens fScreenTimer += Deltatime; if( ( fScreenTimer > Global.PythonScreenUpdateRate * 0.001f ) @@ -7718,6 +7714,7 @@ Math3D::vector3 TTrain::MirrorPosition(bool lewe) 1.5 + Cabine[iCabn].CabPos1.y, Cabine[iCabn].CabPos1.z); case 1: // przednia (1) + [[fallthrough]]; default: return DynamicObject->mMatrix * Math3D::vector3( diff --git a/Train.h b/Train.h index 03ae09e9..0262cb50 100644 --- a/Train.h +++ b/Train.h @@ -59,13 +59,17 @@ private: class control_mapper { typedef std::unordered_map< TSubModel const *, std::string> submodelstring_map; submodelstring_map m_controlnames; + using stringset = std::unordered_set; + stringset m_names; // names of registered controls public: void - clear() { m_controlnames.clear(); } + clear(); void insert( TGauge const &Gauge, std::string const &Label ); std::string find( TSubModel const *Control ) const; + bool + contains( std::string const Control ) const; }; class TTrain { diff --git a/applicationmode.h b/applicationmode.h index 0bc0eaa0..f3f925c3 100644 --- a/applicationmode.h +++ b/applicationmode.h @@ -39,6 +39,11 @@ public: set_progress( float const Progress = 0.f, float const Subtaskprogress = 0.f ) { if( m_userinterface != nullptr ) { m_userinterface->set_progress( Progress, Subtaskprogress ); } } + inline + void + set_tooltip( std::string const &Tooltip ) { + if( m_userinterface != nullptr ) { + m_userinterface->set_tooltip( Tooltip ); } } // maintenance method, called when the mode is activated virtual void diff --git a/drivermode.cpp b/drivermode.cpp index d1b8ba27..3d6fda45 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/. #include "Globals.h" #include "application.h" +#include "translation.h" #include "simulation.h" #include "simulationtime.h" #include "simulationenvironment.h" @@ -80,6 +81,50 @@ driver_mode::drivermode_input::init() { return result; } +std::string +driver_mode::drivermode_input::command_hints( std::pair const &Commands ) const { + + auto const inputhintleft { keyboard.mapping( Commands.first ) }; + auto const inputhintright { keyboard.mapping( Commands.second ) }; + std::string inputhints = + inputhintleft + + ( inputhintright.empty() ? "" : + inputhintleft.empty() ? + inputhintright : + "] [" + inputhintright ); + + return inputhints; +} + +std::unordered_map> commandfallbacks = { + { user_command::mastercontrollerset, { user_command::mastercontrollerincrease, user_command::mastercontrollerdecrease } }, + { user_command::secondcontrollerset, { user_command::secondcontrollerincrease, user_command::secondcontrollerdecrease } }, + { user_command::trainbrakeset, { user_command::trainbrakeincrease, user_command::trainbrakedecrease } }, + { user_command::independentbrakeset, { user_command::independentbrakeincrease, user_command::independentbrakedecrease } }, + { user_command::linebreakeropen, { user_command::linebreakertoggle, user_command::none } }, + { user_command::linebreakerclose, { user_command::linebreakertoggle, user_command::none } }, + { user_command::pantographlowerfront, { user_command::pantographtogglefront, user_command::none } }, + { user_command::pantographlowerrear, { user_command::pantographtogglerear, user_command::none } }, +}; + +std::pair +driver_mode::drivermode_input::command_fallback( user_command const Command ) const { + + if( Command == user_command::none ) { + return { user_command::none, user_command::none }; + } + + auto const lookup { commandfallbacks.find( Command ) }; + + if( lookup == commandfallbacks.end() ) { + return { user_command::none, user_command::none }; + } + + return lookup->second; +} + + + driver_mode::driver_mode() { m_userinterface = std::make_shared(); @@ -217,6 +262,7 @@ driver_mode::update() { // fixed step render time routines fTime50Hz += deltarealtime; // w pauzie też trzeba zliczać czas, bo przy dużym FPS będzie problem z odczytem ramek + bool runonce { false }; while( fTime50Hz >= 1.0 / 50.0 ) { Console::Update(); // to i tak trzeba wywoływać ui::Transcripts.Update(); // obiekt obsługujący stenogramy dźwięków na ekranie @@ -232,6 +278,52 @@ driver_mode::update() { if( std::abs( DebugCamera.Velocity.y ) < 0.01 ) { DebugCamera.Velocity.y = 0.0; } if( std::abs( DebugCamera.Velocity.z ) < 0.01 ) { DebugCamera.Velocity.z = 0.0; } + if( false == runonce ) { + // tooltip update + set_tooltip( "" ); + auto const *train{ simulation::Train }; + if( ( train != nullptr ) && ( false == FreeFlyModeFlag ) ) { + if( false == DebugModeFlag ) { + // in regular mode show control functions, for defined controls + auto const controlname { train->GetLabel( GfxRenderer->Pick_Control() ) }; + if( false == controlname.empty() ) { + auto const bindings { m_input.mouse.bindings( controlname ) }; + auto inputhints { m_input.command_hints( bindings ) }; + // if the commands bound with the control don't have any assigned keys try potential fallbacks + if( inputhints.empty() ) { + inputhints = m_input.command_hints( m_input.command_fallback( bindings.first ) ); + } + if( inputhints.empty() ) { + inputhints = m_input.command_hints( m_input.command_fallback( bindings.second ) ); + } + // ready or not, here we go + if( inputhints.empty() ) { + set_tooltip( locale::label_cab_control( controlname ) ); + } + else { + set_tooltip( + locale::label_cab_control( controlname ) + + " [" + inputhints + "]" ); + } + } + } + else { + // in debug mode show names of submodels, to help with cab setup and/or debugging + auto const cabcontrol = GfxRenderer->Pick_Control(); + set_tooltip( ( cabcontrol ? cabcontrol->pName : "" ) ); + } + } + if( ( true == Global.ControlPicking ) && ( true == FreeFlyModeFlag ) && ( true == DebugModeFlag ) ) { + auto const scenerynode = GfxRenderer->Pick_Node(); + set_tooltip( + ( scenerynode ? + scenerynode->name() : + "" ) ); + } + + runonce = true; + } + fTime50Hz -= 1.0 / 50.0; } diff --git a/drivermode.h b/drivermode.h index 0f4f1a81..61d1606e 100644 --- a/drivermode.h +++ b/drivermode.h @@ -77,6 +77,10 @@ private: bool init(); void poll(); + std::string + command_hints( std::pair const &Commands ) const; + std::pair + command_fallback( user_command const Command ) const; }; // methods diff --git a/drivermouseinput.cpp b/drivermouseinput.cpp index 706d16c3..0565c750 100644 --- a/drivermouseinput.cpp +++ b/drivermouseinput.cpp @@ -372,70 +372,70 @@ drivermouse_input::button( int const Button, int const Action ) { bool pickwaiting = m_pickwaiting; m_pickwaiting = false; - auto const lookup = m_buttonbindings.find( simulation::Train->GetLabel( control ) ); - if( lookup != m_buttonbindings.end() ) { - // if the recognized element under the cursor has a command associated with the pressed button, notify the recipient - mousecommand = ( - Button == GLFW_MOUSE_BUTTON_LEFT ? - lookup->second.left : - lookup->second.right - ); - if( mousecommand == user_command::none ) { return; } - // check manually for commands which have 'fast' variants launched with shift modifier - if( Global.shiftState ) { - switch( mousecommand ) { - case user_command::mastercontrollerincrease: { mousecommand = user_command::mastercontrollerincreasefast; break; } - case user_command::mastercontrollerdecrease: { mousecommand = user_command::mastercontrollerdecreasefast; break; } - case user_command::secondcontrollerincrease: { mousecommand = user_command::secondcontrollerincreasefast; break; } - case user_command::secondcontrollerdecrease: { mousecommand = user_command::secondcontrollerdecreasefast; break; } - case user_command::independentbrakeincrease: { mousecommand = user_command::independentbrakeincreasefast; break; } - case user_command::independentbrakedecrease: { mousecommand = user_command::independentbrakedecreasefast; break; } - default: { break; } - } + auto const controlbindings { bindings( simulation::Train->GetLabel( control ) ) }; + // if the recognized element under the cursor has a command associated with the pressed button, notify the recipient + mousecommand = ( + Button == GLFW_MOUSE_BUTTON_LEFT ? + controlbindings.first : + controlbindings.second + ); + + if( mousecommand == user_command::none ) { + // if we don't have any recognized element under the cursor and the right button was pressed, enter view panning mode + if( Button == GLFW_MOUSE_BUTTON_RIGHT ) { + m_pickmodepanning = true; } + return; + } + // check manually for commands which have 'fast' variants launched with shift modifier + if( Global.shiftState ) { + switch( mousecommand ) { + case user_command::mastercontrollerincrease: { mousecommand = user_command::mastercontrollerincreasefast; break; } + case user_command::mastercontrollerdecrease: { mousecommand = user_command::mastercontrollerdecreasefast; break; } + case user_command::secondcontrollerincrease: { mousecommand = user_command::secondcontrollerincreasefast; break; } + case user_command::secondcontrollerdecrease: { mousecommand = user_command::secondcontrollerdecreasefast; break; } + case user_command::independentbrakeincrease: { mousecommand = user_command::independentbrakeincreasefast; break; } + case user_command::independentbrakedecrease: { mousecommand = user_command::independentbrakedecreasefast; break; } + default: { break; } + } + } - switch( mousecommand ) { - case user_command::mastercontrollerincrease: - case user_command::mastercontrollerdecrease: - case user_command::secondcontrollerincrease: - case user_command::secondcontrollerdecrease: - case user_command::trainbrakeincrease: - case user_command::trainbrakedecrease: - case user_command::independentbrakeincrease: - case user_command::independentbrakedecrease: { - // these commands trigger varying repeat rate mode, - // which scales the rate based on the distance of the cursor from its point when the command was first issued - m_varyingpollrateorigin = m_cursorposition; - m_varyingpollrate = true; - break; - } - case user_command::jointcontrollerset: - case user_command::mastercontrollerset: - case user_command::secondcontrollerset: - case user_command::trainbrakeset: - case user_command::independentbrakeset: { - m_slider.bind( mousecommand ); - mousecommand = user_command::none; - return; - } - default: { - break; - } + switch( mousecommand ) { + case user_command::mastercontrollerincrease: + case user_command::mastercontrollerdecrease: + case user_command::secondcontrollerincrease: + case user_command::secondcontrollerdecrease: + case user_command::trainbrakeincrease: + case user_command::trainbrakedecrease: + case user_command::independentbrakeincrease: + case user_command::independentbrakedecrease: { + // these commands trigger varying repeat rate mode, + // which scales the rate based on the distance of the cursor from its point when the command was first issued + m_varyingpollrateorigin = m_cursorposition; + m_varyingpollrate = true; + break; } - - // NOTE: basic keyboard controls don't have any parameters - // NOTE: as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 - // TODO: pass correct entity id once the missing systems are in place - m_relay.post( mousecommand, 0, 0, Action, 0 ); - if (!pickwaiting) // already depressed - m_relay.post( mousecommand, 0, 0, GLFW_RELEASE, 0 ); - m_updateaccumulator = -0.25; // prevent potential command repeat right after issuing one - } - else { - // if we don't have any recognized element under the cursor and the right button was pressed, enter view panning mode - if( Button == GLFW_MOUSE_BUTTON_RIGHT ) { - m_pickmodepanning = true; - } } } ); + case user_command::jointcontrollerset: + case user_command::mastercontrollerset: + case user_command::secondcontrollerset: + case user_command::trainbrakeset: + case user_command::independentbrakeset: { + m_slider.bind( mousecommand ); + mousecommand = user_command::none; + return; + } + default: { + break; + } + } + // NOTE: basic keyboard controls don't have any parameters + // NOTE: as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0 + // TODO: pass correct entity id once the missing systems are in place + m_relay.post( mousecommand, 0, 0, Action, 0 ); + if (!pickwaiting) // already depressed + m_relay.post( mousecommand, 0, 0, GLFW_RELEASE, 0 ); + m_updateaccumulator = -0.25; // prevent potential command repeat right after issuing one + } ); } } } @@ -495,6 +495,19 @@ drivermouse_input::command() const { m_mousecommandright ); } +// returns pair of bindings associated with specified cab control +std::pair +drivermouse_input::bindings( std::string const &Control ) const { + + auto const lookup{ m_buttonbindings.find( Control ) }; + + if( lookup != m_buttonbindings.end() ) + return { lookup->second.left, lookup->second.right }; + else { + return { user_command::none, user_command::none }; + } +} + void drivermouse_input::default_bindings() { diff --git a/drivermouseinput.h b/drivermouseinput.h index af1928ff..6ff66f02 100644 --- a/drivermouseinput.h +++ b/drivermouseinput.h @@ -66,6 +66,9 @@ public: poll(); user_command command() const; + // returns pair of bindings associated with specified cab control + std::pair + bindings( std::string const &Control ) const; private: // types diff --git a/driveruilayer.cpp b/driveruilayer.cpp index a160aed1..1ac6acbe 100644 --- a/driveruilayer.cpp +++ b/driveruilayer.cpp @@ -164,29 +164,6 @@ driver_ui::update() { } m_paused = ispaused; - set_tooltip( "" ); - - auto const *train { simulation::Train }; - - if( ( train != nullptr ) && ( false == FreeFlyModeFlag ) ) { - if( false == DebugModeFlag ) { - // in regular mode show control functions, for defined controls - set_tooltip( locale::label_cab_control( train->GetLabel( GfxRenderer->Pick_Control() ) ) ); - } - else { - // in debug mode show names of submodels, to help with cab setup and/or debugging - auto const cabcontrol = GfxRenderer->Pick_Control(); - set_tooltip( ( cabcontrol ? cabcontrol->pName : "" ) ); - } - } - if( ( true == Global.ControlPicking ) && ( true == FreeFlyModeFlag ) && ( true == DebugModeFlag ) ) { - auto const scenerynode = GfxRenderer->Pick_Node(); - set_tooltip( - ( scenerynode ? - scenerynode->name() : - "" ) ); - } - ui_layer::update(); } diff --git a/driveruipanels.cpp b/driveruipanels.cpp index b7da9755..4117f136 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -542,7 +542,25 @@ debug_panel::render() { render_section( "Vehicle Engine", m_enginelines ); render_section( "Vehicle AI", m_ailines ); render_section( "Vehicle Scan Table", m_scantablelines ); - render_section( "Scenario", m_scenariolines ); + if( true == render_section( "Scenario", m_scenariolines ) ) { + // cloud cover slider + if( ImGui::SliderFloat( + ( to_string(Global.Overcast, 2 ) + " (" + Global.Weather + ")###overcast" ).c_str(), &Global.Overcast, 0.0f, 2.0f, "Cloud cover" ) ) { + Global.Overcast = clamp( Global.Overcast, 0.0f, 2.0f ); + simulation::Environment.compute_weather(); + } + // day of year slider + if( ImGui::SliderFloat( ( to_string( Global.fMoveLight, 0, 4 ) + " (" + Global.Season + ")###movelight" ).c_str(), &Global.fMoveLight, 0.0f, 355.0f, "Day of year" ) ) { + Global.fMoveLight = clamp( Global.fMoveLight, 0.0f, 355.0f ); + auto const weather { Global.Weather }; + simulation::Environment.compute_season( Global.fMoveLight ); + simulation::Time.init(); + if( weather != Global.Weather ) { + // HACK: force re-calculation of precipitation + Global.Overcast = clamp( Global.Overcast - 0.0001f, 0.0f, 2.0f ); + } + } + } if( true == render_section( "Scenario Event Queue", m_eventqueuelines ) ) { // event queue filter ImGui::Checkbox( "By This Vehicle Only", &m_eventqueueactivevehicleonly ); @@ -597,8 +615,8 @@ debug_panel::update_section_vehicle( std::vector &Output ) { ( mover.Mains ? 'M' : '.' ), ( mover.FuseFlag ? '!' : '.' ), ( mover.PantsValve.is_active ? '+' : '.' ), - ( mover.Pantographs[ end::rear ].valve.is_enabled ? ( mover.Pantographs[ end::rear ].valve.is_active ? 'O' : 'o' ) : '.' ), - ( mover.Pantographs[end::front].valve.is_enabled ? ( mover.Pantographs[ end::front ].valve.is_active ? 'P' : 'p' ) : '.' ), + ( mover.Pantographs[ end::rear ].valve.is_active ? 'O' : ( mover.Pantographs[ end::rear ].valve.is_enabled ? 'o' : '.' ) ), + ( mover.Pantographs[ end::front ].valve.is_active ? 'P' : ( mover.Pantographs[ end::front ].valve.is_enabled ? 'p' : '.' ) ), ( mover.PantPressLockActive ? '!' : ( mover.PantPressSwitchActive ? '*' : '.' ) ), ( mover.WaterPump.is_active ? 'W' : ( false == mover.WaterPump.breaker ? '-' : ( mover.WaterPump.is_enabled ? 'w' : '.' ) ) ), ( true == mover.WaterHeater.is_damaged ? '!' : ( mover.WaterHeater.is_active ? 'H' : ( false == mover.WaterHeater.breaker ? '-' : ( mover.WaterHeater.is_enabled ? 'h' : '.' ) ) ) ), @@ -987,8 +1005,7 @@ debug_panel::update_section_scenario( std::vector &Output ) { Output.emplace_back( textline, Global.UITextColor ); // current luminance level - textline = "Cloud cover: " + to_string( Global.Overcast, 3 ); - textline += "\nLight level: " + to_string( Global.fLuminance, 3 ); + textline = "Light level: " + to_string( Global.fLuminance, 3 ) + ( Global.FakeLight ? "(*)" : "" ); if( Global.FakeLight ) { textline += "(*)"; } textline += "\nWind: azimuth " diff --git a/keyboardinput.cpp b/keyboardinput.cpp index dcb79db2..4f1a61e3 100644 --- a/keyboardinput.cpp +++ b/keyboardinput.cpp @@ -159,8 +159,8 @@ keyboard_input::key( int const Key, int const Action ) { | ( modifier ? 0 : ( input::key_shift ? keymodifier::shift : 0 ) ) | ( modifier ? 0 : ( input::key_ctrl ? keymodifier::control : 0 ) ); - auto const lookup = m_bindings.find( key ); - if( lookup == m_bindings.end() ) { + auto const lookup = m_commands.find( key ); + if( lookup == m_commands.end() ) { // no binding for this key return false; } @@ -187,7 +187,8 @@ keyboard_input::bind() { for( auto const &bindingsetup : m_bindingsetups ) { - m_bindings[ bindingsetup.binding ] = bindingsetup.command; + m_commands[ bindingsetup.binding ] = bindingsetup.command; + m_bindings[ bindingsetup.command ] = bindingsetup.binding; } // cache movement key bindings m_bindingscache.forward = binding( user_command::moveforward ); @@ -201,12 +202,11 @@ keyboard_input::bind() { int keyboard_input::binding( user_command const Command ) const { - for( auto const &binding : m_bindings ) { - if( binding.second == Command ) { - return binding.first; - } - } - return -1; + auto const lookup{ m_bindings.find( Command ) }; + return ( + lookup != m_bindings.end() ? + lookup->second : + -1 ); } bool @@ -274,4 +274,50 @@ keyboard_input::poll() { m_movementvertical = movementvertical; } +std::unordered_map keytonamemap = { + { GLFW_KEY_0, "0" }, { GLFW_KEY_1, "1" }, { GLFW_KEY_2, "2" }, { GLFW_KEY_3, "3" }, { GLFW_KEY_4, "4" }, + { GLFW_KEY_5, "5" }, { GLFW_KEY_6, "6" }, { GLFW_KEY_7, "7" }, { GLFW_KEY_8, "8" }, { GLFW_KEY_9, "9" }, + { GLFW_KEY_MINUS, "-" }, { GLFW_KEY_EQUAL, "=" }, + { GLFW_KEY_A, "A" }, { GLFW_KEY_B, "B" }, { GLFW_KEY_C, "C" }, { GLFW_KEY_D, "D" }, { GLFW_KEY_E, "E" }, + { GLFW_KEY_F, "F" }, { GLFW_KEY_G, "G" }, { GLFW_KEY_H, "H" }, { GLFW_KEY_I, "I" }, { GLFW_KEY_J, "J" }, + { GLFW_KEY_K, "K" }, { GLFW_KEY_L, "L" }, { GLFW_KEY_M, "M" }, { GLFW_KEY_N, "N" }, { GLFW_KEY_O, "O" }, + { GLFW_KEY_P, "P" }, { GLFW_KEY_Q, "Q" }, { GLFW_KEY_R, "R" }, { GLFW_KEY_S, "S" }, { GLFW_KEY_T, "T" }, + { GLFW_KEY_U, "U" }, { GLFW_KEY_V, "V" }, { GLFW_KEY_W, "W" }, { GLFW_KEY_X, "X" }, { GLFW_KEY_Y, "Y" }, { GLFW_KEY_Z, "Z" }, + { GLFW_KEY_BACKSPACE, "BACKSPACE" }, + { GLFW_KEY_LEFT_BRACKET, "[" }, { GLFW_KEY_RIGHT_BRACKET, "]" }, { GLFW_KEY_BACKSLASH, "\\" }, + { GLFW_KEY_SEMICOLON, ";" }, { GLFW_KEY_APOSTROPHE, "'" }, { GLFW_KEY_ENTER, "ENTER" }, + { GLFW_KEY_COMMA, "<" }, { GLFW_KEY_PERIOD, ">" }, { GLFW_KEY_SLASH, "/" }, + { GLFW_KEY_SPACE, "SPACE" }, + { GLFW_KEY_PAUSE, "PAUSE" }, { GLFW_KEY_INSERT, "INSERT" }, { GLFW_KEY_DELETE, "DELETE" }, { GLFW_KEY_HOME, "HOME" }, { GLFW_KEY_END, "END" }, + // numpad block + { GLFW_KEY_KP_DIVIDE, "NUM /" }, { GLFW_KEY_KP_MULTIPLY, "NUM *" }, { GLFW_KEY_KP_SUBTRACT, "NUM -" }, + { GLFW_KEY_KP_7, "NUM 7" }, { GLFW_KEY_KP_8, "NUM 8" }, { GLFW_KEY_KP_9, "NUM 9" }, { GLFW_KEY_KP_ADD, "NUM +" }, + { GLFW_KEY_KP_4, "NUM 4" }, { GLFW_KEY_KP_5, "NUM 5" }, { GLFW_KEY_KP_6, "NUM 6" }, + { GLFW_KEY_KP_1, "NUM 1" }, { GLFW_KEY_KP_2, "NUM 2" }, { GLFW_KEY_KP_3, "NUM 3" }, { GLFW_KEY_KP_ENTER, "NUM ENTER" }, + { GLFW_KEY_KP_0, "NUM 0" }, { GLFW_KEY_KP_DECIMAL, "NUM ." } +}; + +std::string +keyboard_input::mapping( user_command const Command ) const { + + if( Command == user_command::none ) { return ""; } + + auto const binding { this->binding( Command ) }; + if( binding == -1 ) { return ""; } + + auto const lookup { keytonamemap.find( binding & 0xffff ) }; + if( lookup == keytonamemap.end() ) { return ""; } + + std::string mapping; + if( ( binding & keymodifier::shift ) != 0 ) { + mapping += "SHIFT "; + } + if( ( binding & keymodifier::control ) != 0 ) { + mapping += "CTRL "; + } + mapping += lookup->second; + + return mapping; +} + //--------------------------------------------------------------------------- diff --git a/keyboardinput.h b/keyboardinput.h index aa64ed9e..5d01d3ba 100644 --- a/keyboardinput.h +++ b/keyboardinput.h @@ -45,6 +45,8 @@ public: user_command const command() const { return m_command; } + std::string + mapping( user_command const Command ) const; protected: // types @@ -77,6 +79,7 @@ protected: private: // types using usercommand_map = std::unordered_map; + using binding_map = std::unordered_map; struct bindings_cache { @@ -96,7 +99,8 @@ private: // members user_command m_command { user_command::none }; // last, if any, issued command - usercommand_map m_bindings; + usercommand_map m_commands; + binding_map m_bindings; command_relay m_relay; bindings_cache m_bindingscache; glm::vec2 m_movementhorizontal { 0.f }; diff --git a/material.cpp b/material.cpp index ca1ee332..21a47309 100644 --- a/material.cpp +++ b/material.cpp @@ -17,6 +17,8 @@ http://mozilla.org/MPL/2.0/. #include "Globals.h" #include "Logs.h" +opengl_material::path_data opengl_material::paths; + opengl_material::opengl_material() { for (size_t i = 0; i < params.size(); i++) @@ -32,6 +34,21 @@ opengl_material::deserialize( cParser &Input, bool const Loadnow ) { result = true; // once would suffice but, eh } + if( ( path == -1 ) + && ( update_on_weather_change || update_on_season_change ) ) { + // record current texture path in the material, potentially needed when material is reloaded on environment change + // NOTE: we're storing this only for textures that can actually change, to keep the size of path database modest + auto const lookup{ paths.index_map.find( Global.asCurrentTexturePath ) }; + if( lookup != paths.index_map.end() ) { + path = lookup->second; + } + else { + path = paths.data.size(); + paths.data.emplace_back( Global.asCurrentTexturePath ); + paths.index_map.emplace( Global.asCurrentTexturePath, path ); + } + } + return result; } @@ -94,19 +111,20 @@ void opengl_material::finalize(bool Loadnow) if (!shader) { +// TODO: add error severity to logging, re-enable these errors as low severity messages if (textures[0] == null_handle) { - log_error("shader not specified, assuming \"default_0\""); +// log_error("shader not specified, assuming \"default_0\""); shader = GfxRenderer->Fetch_Shader("default_0"); } else if (textures[1] == null_handle) { - log_error("shader not specified, assuming \"default_1\""); +// log_error("shader not specified, assuming \"default_1\""); shader = GfxRenderer->Fetch_Shader("default_1"); } else if (textures[2] == null_handle) { - log_error("shader not specified, assuming \"default_2\""); +// log_error("shader not specified, assuming \"default_2\""); shader = GfxRenderer->Fetch_Shader("default_2"); } } @@ -198,6 +216,55 @@ void opengl_material::finalize(bool Loadnow) } } +bool opengl_material::update() { + + auto const texturepathbackup { Global.asCurrentTexturePath }; + auto const namebackup { name }; + auto const pathbackup { path }; + cParser materialparser( name + ".mat", cParser::buffer_FILE ); // fairly safe to presume .mat is present for branching materials + + // temporarily set texture path to state recorded in the material + Global.asCurrentTexturePath = paths.data[ path ]; + + // clean material slate, restore relevant members + *this = opengl_material(); + name = namebackup; + path = pathbackup; + + auto result { false }; + + if( true == deserialize( materialparser, true ) ) { + try { + finalize( true ); + result = true; + } + catch( gl::shader_exception const &e ) { + ErrorLog( "invalid shader: " + std::string( e.what() ) ); + } + } + // restore texture path + Global.asCurrentTexturePath = texturepathbackup; + + return result; +} + +std::unordered_set seasons = { + "winter:", "spring:", "summer:", "autumn:" +}; + +bool is_season( std::string const &String ) { + + return ( seasons.find( String ) != seasons.end() ); +} + +std::unordered_set weather = { + "clear:", "cloudy:", "rain:", "snow:" }; + +bool is_weather( std::string const &String ) { + + return ( weather.find( String ) != weather.end() ); +} + // imports member data pair from the config file bool opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow ) { @@ -209,6 +276,11 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c if( Priority != -1 ) { // regular attribute processing mode + + // mark potential material change + update_on_weather_change |= is_weather( key ); + update_on_season_change |= is_season( key ); + if( key == Global.Weather ) { // weather textures override generic (pri 0) and seasonal (pri 1) textures // seasonal weather textures (pri 1+2=3) override generic weather (pri 2) textures @@ -433,14 +505,13 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) { if( false == material.name.empty() ) { // if we have material name and shader it means resource was processed succesfully - try { + materialhandle = m_materials.size(); + m_materialmappings.emplace( material.name, materialhandle ); + try { material.finalize(Loadnow); - materialhandle = m_materials.size(); - m_materialmappings.emplace( material.name, materialhandle ); m_materials.emplace_back( std::move(material) ); } catch (gl::shader_exception const &e) { ErrorLog("invalid shader: " + std::string(e.what())); - m_materialmappings.emplace( filename, materialhandle ); } } else { @@ -451,6 +522,22 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) { return materialhandle; }; +void +material_manager::on_weather_change() { + + for( auto &material : m_materials ) { + if( material.update_on_weather_change ) { material.update(); } + } +} + +void +material_manager::on_season_change() { + + for( auto &material : m_materials ) { + if( material.update_on_season_change ) { material.update(); } + } +} + // checks whether specified material is in the material bank. returns handle to the material, or a null handle material_handle material_manager::find_in_databank( std::string const &Materialname ) const { diff --git a/material.h b/material.h index 3b447fdc..f5744e38 100644 --- a/material.h +++ b/material.h @@ -38,10 +38,18 @@ struct opengl_material { bool deserialize( cParser &Input, bool const Loadnow ); void finalize(bool Loadnow); + bool update(); float get_or_guess_opacity() const; bool is_translucent() const; // members + static struct path_data { + std::unordered_map index_map; + std::vector data; + } paths; bool is_good { false }; // indicates material was compiled without failure + int path{ -1 }; // index to material path + bool update_on_weather_change{ false }; + bool update_on_season_change{ false }; private: // methods @@ -49,9 +57,6 @@ private: bool deserialize_mapping( cParser &Input, int const Priority, bool const Loadnow ); void log_error(const std::string &str); - // extracts name of the sound file from provided data stream - std::string - deserialize_filename( cParser &Input ); // members // priorities for textures, shader, opacity @@ -89,6 +94,12 @@ public: material( material_handle const Material ) const { return m_materials[ Material ]; } opengl_material & material( material_handle const Material ) { return m_materials[ Material ]; } + // material updates executed when environment changes + // TODO: registerable callbacks in environment manager + void + on_weather_change(); + void + on_season_change(); private: // types diff --git a/messaging.cpp b/messaging.cpp index 43eb7047..d51f72e4 100644 --- a/messaging.cpp +++ b/messaging.cpp @@ -113,7 +113,7 @@ OnCommandGet(multiplayer::DaneRozkaz *pRozkaz) if (*pRozkaz->iPar == 0) // sprawdzenie czasu if (*pRozkaz->iPar & 1) // ustawienie czasu { - double t = pRozkaz->fPar[1]; + auto t = pRozkaz->fPar[1]; simulation::Time.data().wDay = std::floor(t); // niby nie powinno być dnia, ale... if (Global.fMoveLight >= 0) Global.fMoveLight = t; // trzeba by deklinację Słońca przeliczyć diff --git a/opengl33renderer.cpp b/opengl33renderer.cpp index 24f4edf8..c0b16ea4 100644 --- a/opengl33renderer.cpp +++ b/opengl33renderer.cpp @@ -1597,9 +1597,14 @@ bool opengl33_renderer::Render(world_environment *Environment) if (Environment->m_clouds.mdCloud) { // setup - glm::vec3 color = interpolate(Environment->m_skydome.GetAverageColor(), suncolor, duskfactor * 0.25f) * interpolate(1.f, 0.35f, Global.Overcast / 2.f) // overcast darkens the clouds - * 0.5f; - + glm::vec3 color = + interpolate( + Environment->m_skydome.GetAverageColor(), suncolor, + duskfactor * 0.25f) + * interpolate( + 1.f, 0.35f, + Global.Overcast / 2.f) // overcast darkens the clouds + * 0.5f; // write cloud color into material TSubModel *mdl = Environment->m_clouds.mdCloud->Root; if (mdl->m_material != null_handle) @@ -1751,17 +1756,18 @@ void opengl33_renderer::Bind_Material(material_handle const Material, TSubModel model_ubs.param[entry.location][entry.offset + j] = src[j]; } - if (m_blendingenabled) - { - model_ubs.opacity = -1.0f; - } - else - { - if (!std::isnan(material.opacity)) - model_ubs.opacity = material.opacity; - else - model_ubs.opacity = 0.5f; - } + if( !std::isnan( material.opacity ) ) { + model_ubs.opacity = ( + m_blendingenabled ? + -material.opacity : + material.opacity ); + } + else { + model_ubs.opacity = ( + m_blendingenabled ? + 0.0f : + 0.5f ); + } if (sm) model_ubs.alpha_mult = sm->fVisible; @@ -3926,6 +3932,19 @@ void opengl33_renderer::Update(double const Deltatime) } } + // update resources if there was environmental change + simulation_state simulationstate { + Global.Weather, + Global.Season + }; + std::swap( m_simulationstate, simulationstate ); + if( ( m_simulationstate.season != simulationstate.season ) && ( false == simulationstate.season.empty() ) ) { + m_materials.on_season_change(); + } + if( ( m_simulationstate.weather != simulationstate.weather ) && ( false == simulationstate.weather.empty() ) ) { + m_materials.on_weather_change(); + } + if ((true == Global.ResourceSweep) && (true == simulation::is_ready)) { // garbage collection diff --git a/opengl33renderer.h b/opengl33renderer.h index 755cd8dd..04ef0433 100644 --- a/opengl33renderer.h +++ b/opengl33renderer.h @@ -300,6 +300,10 @@ class opengl33_renderer : public gfx_renderer { std::string m_pickdebuginfo; //debug_stats m_debugstats; std::string m_debugstatstext; + struct simulation_state { + std::string weather; + std::string season; + } m_simulationstate; glm::vec4 m_baseambient{0.0f, 0.0f, 0.0f, 1.0f}; glm::vec4 m_shadowcolor{colors::shadow}; diff --git a/openglrenderer.cpp b/openglrenderer.cpp index 16baee8f..f002f7d0 100644 --- a/openglrenderer.cpp +++ b/openglrenderer.cpp @@ -3601,14 +3601,14 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { #endif // material configuration: // textures... - if( Submodel->m_material < 0 ) { // zmienialne skóry - Bind_Material( Submodel->ReplacableSkinId[ -Submodel->m_material ] ); - } - else { - // również 0 - Bind_Material( Submodel->m_material ); - } - // ...colors... + auto const material { ( + Submodel->m_material < 0 ? + Submodel->ReplacableSkinId[ -Submodel->m_material ] : // zmienialne skóry + Submodel->m_material ) }; // również 0 + // textures... + Bind_Material( material ); + // ...colors and opacity... + auto const opacity { clamp( Material( material ).get_or_guess_opacity(), 0.f, 1.f ) }; if( Submodel->fVisible < 1.f ) { ::glColor4f( Submodel->f4Diffuse.r, @@ -3619,6 +3619,10 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { else { ::glColor3fv( glm::value_ptr( Submodel->f4Diffuse ) ); // McZapkie-240702: zamiast ub } + if( opacity != 0.f ) { + // discard fragments with opacity exceeding the threshold + ::glAlphaFunc( GL_LEQUAL, opacity ); + } if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) { ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( Submodel->f4Specular * m_sunlight.specular.a * m_speculartranslucentscalefactor ) ); } @@ -3639,6 +3643,10 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { m_geometry.draw( Submodel->m_geometry ); // post-draw reset + if( opacity != 0.f ) { + // discard fragments with opacity exceeding the threshold + ::glAlphaFunc( GL_GREATER, 0.0f ); + } if( ( true == m_renderspecular ) && ( m_sunlight.specular.a > 0.01f ) ) { ::glMaterialfv( GL_FRONT, GL_SPECULAR, glm::value_ptr( colors::none ) ); } @@ -4032,6 +4040,19 @@ opengl_renderer::Update( double const Deltatime ) { } } + // update resources if there was environmental change + simulation_state simulationstate { + Global.Weather, + Global.Season + }; + std::swap( m_simulationstate, simulationstate ); + if( ( m_simulationstate.season != simulationstate.season ) && ( false == simulationstate.season.empty() ) ) { + m_materials.on_season_change(); + } + if( ( m_simulationstate.weather != simulationstate.weather ) && ( false == simulationstate.weather.empty() ) ) { + m_materials.on_weather_change(); + } + if( ( true == Global.ResourceSweep ) && ( true == simulation::is_ready ) ) { // garbage collection diff --git a/openglrenderer.h b/openglrenderer.h index ac17a9da..afb375a0 100644 --- a/openglrenderer.h +++ b/openglrenderer.h @@ -320,6 +320,10 @@ private: std::string m_debugtimestext; std::string m_pickdebuginfo; std::string m_debugstatstext; + struct simulation_state { + std::string weather; + std::string season; + } m_simulationstate; glm::vec4 m_baseambient { 0.0f, 0.0f, 0.0f, 1.0f }; glm::vec4 m_shadowcolor { colors::shadow }; diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index 49e824ac..a989567e 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -169,15 +169,16 @@ world_environment::update() { Global.FogColor = m_skydome.GetAverageHorizonColor(); // weather-related simulation factors - // TODO: dynamic change of air temperature and overcast levels + Global.FrictionWeatherFactor = ( + Global.Weather == "rain:" ? 0.85f : + Global.Weather == "snow:" ? 0.75f : + 1.0f ); + if( Global.Weather == "rain:" ) { - // reduce friction in rain - Global.FrictionWeatherFactor = 0.85f; m_precipitationsound.play( sound_flags::exclusive | sound_flags::looping ); } - else if( Global.Weather == "snow:" ) { - // reduce friction due to snow - Global.FrictionWeatherFactor = 0.75f; + else { + m_precipitationsound.stop(); } update_wind(); diff --git a/simulationtime.cpp b/simulationtime.cpp index f1c5531e..81981bfa 100644 --- a/simulationtime.cpp +++ b/simulationtime.cpp @@ -34,7 +34,7 @@ scenario_time::init() { // cache requested elements, if any ::GetLocalTime( &m_time ); - if( Global.fMoveLight > 0.0 ) { + if( Global.fMoveLight > 0.f ) { // day and month of the year can be overriden by scenario setup daymonth( m_time.wDay, m_time.wMonth, m_time.wYear, static_cast( Global.fMoveLight ) ); } diff --git a/sound.cpp b/sound.cpp index fa9c94fc..8a0b722d 100644 --- a/sound.cpp +++ b/sound.cpp @@ -117,20 +117,23 @@ sound_source::deserialize( cParser &Input, sound_type const Legacytype, int cons } if( Legacyparameters & sound_parameters::range ) { - Input.getTokens( 1, false ); - Input >> m_range; + if( Input.getTokens( 1, false ) ) { + Input >> m_range; + } } if( Legacyparameters & sound_parameters::amplitude ) { - Input.getTokens( 2, false ); - Input - >> m_amplitudefactor - >> m_amplitudeoffset; + if( Input.getTokens( 2, false ) ) { + Input + >> m_amplitudefactor + >> m_amplitudeoffset; + } } if( Legacyparameters & sound_parameters::frequency ) { - Input.getTokens( 2, false ); - Input - >> m_frequencyfactor - >> m_frequencyoffset; + if( Input.getTokens( 2, false ) ) { + Input + >> m_frequencyfactor + >> m_frequencyoffset; + } } } // restore parser behaviour diff --git a/translation.cpp b/translation.cpp index 004e20f3..b6f8aef9 100644 --- a/translation.cpp +++ b/translation.cpp @@ -85,6 +85,20 @@ init() { "second controller", "shunt mode power", "tempomat", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (power)", + "tempomat (power)", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (speed)", + "tempomat (speed)", "distance counter", "reverser", "train brake", @@ -114,6 +128,9 @@ init() { "spring brake", "spring brake", "spring brake", + "train brake", + "train brake", + "train brake", "electro-pneumatic brake", "sandbox", "wheelspin brake", @@ -163,6 +180,9 @@ init() { "radiostop test", "radiostop", "radio call 3", + "radio volume", + "radio volume", + "radio volume", "pantograph A", "pantograph B", "pantograph A", @@ -260,6 +280,20 @@ init() { "nastawnik dodatkowy", "sterowanie analogowe", "tempomat", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (moc)", + "tempomat (moc)", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (predkosc)", + "tempomat (predkosc)", "miernik odleglosci", "nastawnik kierunku", "hamulec zespolony", @@ -289,6 +323,9 @@ init() { "hamulec sprezynowy", "hamulec sprezynowy", "hamulec sprezynowy", + "hamulec zespolony", + "hamulec zespolony", + "hamulec zespolony", "hamulec elektropneumatyczny", "piasecznica", "hamulec przeciwposlizgowy", @@ -338,6 +375,9 @@ init() { "test radiostopu", "radiostop", "zew 3", + "glosnosc radia", + "glosnosc radia", + "glosnosc radia", "pantograf A", "pantograf B", "pantograf A", @@ -388,6 +428,20 @@ init() { "scndctrl:", "shuntmodepower:", "tempomat_sw:", + "speedinc_bt:", + "speeddec_bt:", + "speedctrlpowerinc_bt:", + "speedctrlpowerdec_bt:", + "speedbutton0:", + "speedbutton1:", + "speedbutton2:", + "speedbutton3:", + "speedbutton4:", + "speedbutton5:", + "speedbutton6:", + "speedbutton7:", + "speedbutton8:", + "speedbutton9:", "distancecounter_sw:", "dirkey:", "brakectrl:", @@ -417,6 +471,9 @@ init() { "springbraketoggle_bt:", "springbrakeon_bt:", "springbrakeoff_bt:", + "universalbrake1_bt:", + "universalbrake2_bt:", + "universalbrake3_bt:", "epbrake_bt:", "sand_bt:", "antislip_bt:", @@ -466,6 +523,9 @@ init() { "radiotest_sw:", "radiostop_sw:", "radiocall3_sw:", + "radiovolume_sw:", + "radiovolumeprev_sw:", + "radiovolumenext_sw:", "pantfront_sw:", "pantrear_sw:", "pantfrontoff_sw:", diff --git a/translation.h b/translation.h index ec43a056..ed48552b 100644 --- a/translation.h +++ b/translation.h @@ -74,6 +74,20 @@ enum string { cab_scndctrl, cab_shuntmodepower, cab_tempomat, + cab_speedinc, + cab_speeddec, + cab_speedctrlpowerinc, + cab_speedctrlpowerdec, + cab_speedbutton0, + cab_speedbutton1, + cab_speedbutton2, + cab_speedbutton3, + cab_speedbutton4, + cab_speedbutton5, + cab_speedbutton6, + cab_speedbutton7, + cab_speedbutton8, + cab_speedbutton9, cab_distancecounter, cab_dirkey, cab_brakectrl, @@ -103,6 +117,9 @@ enum string { cab_springbrake_bt, cab_springbrakeon_bt, cab_springbrakeoff_bt, + cab_universalbrake1_bt, + cab_universalbrake2_bt, + cab_universalbrake3_bt, cab_epbrake_bt, cab_sand_bt, cab_antislip_bt, @@ -152,6 +169,9 @@ enum string { cab_radiotest_sw, cab_radiostop_sw, cab_radiocall3_sw, + cab_radiovolume_sw, + cab_radiovolumeprev_sw, + cab_radiovolumenext_sw, cab_pantfront_sw, cab_pantrear_sw, cab_pantfrontoff_sw, diff --git a/version.h b/version.h index 510595e8..8b9188cf 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define VERSION_MAJOR 20 -#define VERSION_MINOR 120 +#define VERSION_MINOR 124 #define VERSION_REVISION 0