diff --git a/Driver.cpp b/Driver.cpp index 1e32858b..9a78bb68 100644 --- a/Driver.cpp +++ b/Driver.cpp @@ -3080,8 +3080,8 @@ bool TController::DecBrakeEIM() bool TController::IncSpeed() { // zwiększenie prędkości; zwraca false, jeśli dalej się nie da zwiększać - if( true == tsGuardSignal.is_playing() ) { - // jeśli gada, to nie jedziemy + if( fActionTime < 0.0 ) { + // gdy jest nakaz poczekać z jazdą, to nie ruszać return false; } bool OK = true; @@ -3093,6 +3093,7 @@ bool TController::IncSpeed() if (mvOccupied->SpringBrake.IsActive && mvOccupied->SpringBrake.Activate) { mvOccupied->SpringBrakeActivate(false); } + // Doors() call can potentially adjust fActionTime if( fActionTime < 0.0 ) { // gdy jest nakaz poczekać z jazdą, to nie ruszać return false; @@ -5717,6 +5718,8 @@ TController::UpdateSituation(double dt) { // place virtual conductor some distance away tsGuardSignal.offset( { pVehicle->MoverParameters->Dim.W * -0.75f, 1.7f, std::min( -20.0, -0.2 * fLength ) } ); tsGuardSignal.play( sound_flags::exclusive ); + // NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera + fActionTime = -5.0; // niech trochę potrzyma } else { // if (iGuardRadio==iRadioChannel) //zgodność kanału @@ -5727,6 +5730,8 @@ TController::UpdateSituation(double dt) { tsGuardSignal.owner( pVehicle ); tsGuardSignal.offset( { 0.f, 2.f, pVehicle->MoverParameters->Dim.L * 0.4f * ( pVehicle->MoverParameters->CabOccupied < 0 ? -1 : 1 ) } ); tsGuardSignal.play( sound_flags::exclusive ); + // NOTE: we can't rely on is_playing() check as sound playback is based on distance from local camera + fActionTime = -5.0; // niech trochę potrzyma } } } @@ -6984,7 +6989,7 @@ void TController::UpdateDelayFlag() { void TController::TakeControl( bool const Aidriver, bool const Forcevehiclecheck ) { // przejęcie kontroli przez AI albo oddanie - if (AIControllFlag == Aidriver) + if ((AIControllFlag == Aidriver) && (!Forcevehiclecheck)) return; // już jest jak ma być if (Aidriver) //żeby nie wykonywać dwa razy { // teraz AI prowadzi @@ -7145,12 +7150,29 @@ TController::TrackBlock() const { void TController::MoveTo(TDynamicObject *to) { // przesunięcie AI do innego pojazdu (przy zmianie kabiny) - // mvOccupied->CabDeactivisation(); //wyłączenie kabiny w opuszczanym - pVehicle->Mechanik = to->Mechanik; //żeby się zamieniły, jak jest jakieś drugie + if( ( to->Mechanik != nullptr ) + && ( to->Mechanik != this ) ) { + // ai controller thunderdome, there can be only one + if( to->Mechanik->AIControllFlag ) { + if( to->Mechanik->primary() ) { + // take over boss duties + primary( true ); + } + SafeDelete( to->Mechanik ); + } + else { + // can't quite delete a human + if( AIControllFlag ) { + delete this; + } + return; + } + } + pVehicle->Mechanik = nullptr; pVehicle = to; ControllingSet(); // utworzenie połączenia do sterowanego pojazdu pVehicle->Mechanik = this; - // iDirection=0; //kierunek jazdy trzeba dopiero zgadnąć + }; void TController::ControllingSet() diff --git a/DynObj.cpp b/DynObj.cpp index 625d94da..23dda44b 100644 --- a/DynObj.cpp +++ b/DynObj.cpp @@ -2220,17 +2220,6 @@ TDynamicObject::create_controller( std::string const Type, bool const Trainset ) if( Type == "" ) { return; } - if( asName == Global.local_start_vehicle ) { - // jeśli pojazd wybrany do prowadzenia - if( MoverParameters->EngineType != TEngineType::Dumb ) { - // wsadzamy tam sterującego - Controller = Humandriver; - } - else { - // w przeciwnym razie trzeba włączyć pokazywanie kabiny - bDisplayCab = true; - } - } // McZapkie-151102: rozkład jazdy czytany z pliku *.txt z katalogu w którym jest sceneria if( ( Type == "1" ) || ( Type == "2" ) ) { @@ -5539,8 +5528,8 @@ void TDynamicObject::LoadMMediaFile( std::string const &TypeName, std::string co for( auto &bogie : m_bogiesounds ) { bogie.start( ( bogieidx % 2 ? - Random( 0.0, 30.0 ) : - Random( 50.0, 80.0 ) ) + LocalRandom( 0.0, 30.0 ) : + LocalRandom( 50.0, 80.0 ) ) * 0.01 ); ++bogieidx; } diff --git a/Globals.h b/Globals.h index cf04b051..a30a2697 100644 --- a/Globals.h +++ b/Globals.h @@ -33,7 +33,6 @@ struct global_settings { bool ready_to_load { false }; std::time_t starting_timestamp = 0; // starting time, in local timezone uint32_t random_seed = 0; - TDynamicObject *changeDynObj{ nullptr };// info o zmianie pojazdu TCamera pCamera; // parametry kamery TCamera pDebugCamera; std::array FreeCameraInit; // pozycje kamery diff --git a/Names.h b/Names.h index 178d82e4..b3f59303 100644 --- a/Names.h +++ b/Names.h @@ -58,13 +58,6 @@ public: m_itemmap.erase(lookup); } - uint32_t find_id( std::string const &Name) const { - auto lookup = m_itemmap.find( Name ); - return ( - lookup != m_itemmap.end() ? - lookup->second : - -1 ); - } void purge (Type_ *Item) { for (auto it = m_items.begin(); it != m_items.end(); it++) { diff --git a/Train.cpp b/Train.cpp index d828d438..cd95447e 100644 --- a/Train.cpp +++ b/Train.cpp @@ -5827,7 +5827,7 @@ bool TTrain::Update( double const Deltatime ) if (ff != fTachoTimer) // jesli w tej sekundzie nie zmienial { if (fTachoVelocity > 1) // jedzie - fTachoVelocityJump = fTachoVelocity + (2.0 - Random(3) + Random(3)) * 0.5; + fTachoVelocityJump = fTachoVelocity + (2.0 - LocalRandom(3) + LocalRandom(3)) * 0.5; else fTachoVelocityJump = 0; // stoi fTachoTimer = ff; // juz zmienil @@ -6049,7 +6049,8 @@ bool TTrain::Update( double const Deltatime ) if ((mvControlled->CompressorFlag == true) && (mvControlled->CompressorPower == 1) && ((mvControlled->EngineType == TEngineType::ElectricSeriesMotor) || (mvControlled->TrainType == dt_EZT)) && - (DynamicObject->Controller == Humandriver)) // hunter-110212: poprawka dla EZT + (DynamicObject->Controller == Humandriver) // hunter-110212: poprawka dla EZT + && ( false == DynamicObject->Mechanik->AIControllFlag ) ) { // hunter-091012: poprawka (zmiana warunku z CompressorPower /rozne od 0/ na /rowne 1/) if (fConverterTimer < fConverterPrzekaznik) { @@ -7496,6 +7497,9 @@ bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName) } // reset view angles pMechViewAngle = { 0.0, 0.0 }; + + is_cab_initialized = true; // the attempt may fail, but it's the attempt that counts + bool parse = false; int cabindex = 0; DynamicObject->mdKabina = NULL; // likwidacja wskaźnika na dotychczasową kabinę @@ -7871,7 +7875,6 @@ TTrain::MoveToVehicle(TDynamicObject *target) { // > Ra: to nie może być tak robione, to zbytnia proteza jest // indeed, too much hacks... // TODO: cleanup - TTrain *target_train = simulation::Trains.find(target->name()); if (target_train) { // let's try to destroy this TTrain and move to already existing one @@ -7882,18 +7885,22 @@ TTrain::MoveToVehicle(TDynamicObject *target) { Occupied()->CabDeactivisation(); Occupied()->CabOccupied = 0; Occupied()->BrakeLevelSet(Occupied()->Handle->GetPos(bh_NP)); //rozwala sterowanie hamulcem GF 04-2016 - Dynamic()->MechInside = false; + Occupied()->MainCtrlPos = Occupied()->MainCtrlNoPowerPos(); + Occupied()->ScndCtrlPos = 0; + Dynamic()->MechInside = false; Dynamic()->Controller = AIdriver; Dynamic()->bDisplayCab = false; Dynamic()->ABuSetModelShake( {} ); - Dynamic()->Mechanik->MoveTo(target); + if( Dynamic()->Mechanik ) { + Dynamic()->Mechanik->MoveTo( target ); + } target_train->Occupied()->LimPipePress = target_train->Occupied()->PipePress; - target_train->Occupied()->CabActivisation(); // załączenie rozrządu (wirtualne kabiny) + target_train->Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny) target_train->Dynamic()->MechInside = true; - target_train->Dynamic()->Controller = Humandriver; + target_train->Dynamic()->Controller = ( target_train->Dynamic()->Mechanik ? !target_train->Dynamic()->Mechanik->AIControllFlag : Humandriver ); } else { target_train->Dynamic()->bDisplayCab = false; target_train->Dynamic()->ABuSetModelShake( {} ); @@ -7922,20 +7929,24 @@ TTrain::MoveToVehicle(TDynamicObject *target) { Occupied()->CabDeactivisation(); Occupied()->CabOccupied = 0; Occupied()->BrakeLevelSet(Occupied()->Handle->GetPos(bh_NP)); //rozwala sterowanie hamulcem GF 04-2016 - Dynamic()->MechInside = false; + Occupied()->MainCtrlPos = Occupied()->MainCtrlNoPowerPos(); + Occupied()->ScndCtrlPos = 0; + Dynamic()->MechInside = false; Dynamic()->Controller = AIdriver; Dynamic()->bDisplayCab = false; Dynamic()->ABuSetModelShake( {} ); - Dynamic()->Mechanik->MoveTo(target); + if( Dynamic()->Mechanik ) { + Dynamic()->Mechanik->MoveTo( target ); + } DynamicSet(target); Occupied()->LimPipePress = Occupied()->PipePress; - Occupied()->CabActivisation(); // załączenie rozrządu (wirtualne kabiny) + Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny) Dynamic()->MechInside = true; - Dynamic()->Controller = Humandriver; + Dynamic()->Controller = ( Dynamic()->Mechanik ? !Dynamic()->Mechanik->AIControllFlag : Humandriver ); } else { Dynamic()->bDisplayCab = false; Dynamic()->ABuSetModelShake( {} ); @@ -7953,6 +7964,7 @@ TTrain::MoveToVehicle(TDynamicObject *target) { // add it back with updated dynamic name simulation::Trains.insert(this); } + } // checks whether specified point is within boundaries of the active cab @@ -8302,7 +8314,7 @@ void TTrain::set_cab_controls( int const Cab ) { if( true == mvOccupied->Radio ) { ggRadioButton.PutValue( 1.f ); } - ggRadioChannelSelector.PutValue( RadioChannel() - 1 ); + ggRadioChannelSelector.PutValue( ( Dynamic()->Mechanik ? Dynamic()->Mechanik->iRadioChannel : 1 ) - 1 ); // pantographs /* if( mvOccupied->PantSwitchType != "impulse" ) { @@ -9262,3 +9274,19 @@ void train_table::update(double dt) } } } + +TTrain * +train_table::find_id( std::uint16_t const Id ) const { + + if( Id == 0 ) { return nullptr; } + + for( TTrain *train : m_items ) { + if( !train ) { + continue; + } + if( train->id() == Id ) { + return train; + } + } + return nullptr; +} diff --git a/Train.h b/Train.h index 49ccfe84..9edafb78 100644 --- a/Train.h +++ b/Train.h @@ -716,6 +716,7 @@ public: // reszta może by?publiczna // McZapkie: opis kabiny - obszar poruszania sie mechanika oraz zajetosc std::array Cabine; // przedzial maszynowy, kabina 1 (A), kabina 2 (B) int iCabn { 0 }; // 0: mid, 1: front, 2: rear + bool is_cab_initialized { false }; // McZapkie: do poruszania sie po kabinie Math3D::vector3 pMechSittingPosition; // ABu 180404 Math3D::vector3 MirrorPosition( bool lewe ); @@ -773,7 +774,7 @@ private: float fDieselParams[9][10]; // parametry dla silnikow asynchronicznych // plays provided sound from position of the radio void radio_message( sound_source *Message, int const Channel ); - inline auto const &RadioChannel() const { return Dynamic()->Mechanik->iRadioChannel; } + inline auto const RadioChannel() const { return ( Dynamic()->Mechanik ? Dynamic()->Mechanik->iRadioChannel : 1 ); } inline auto &RadioChannel() { return Dynamic()->Mechanik->iRadioChannel; } inline TDynamicObject *Dynamic() { return DynamicObject; }; inline TDynamicObject const *Dynamic() const { return DynamicObject; }; @@ -794,6 +795,7 @@ private: class train_table : public basic_table { public: void update( double dt ); + TTrain *find_id( std::uint16_t const Id ) const; }; //--------------------------------------------------------------------------- diff --git a/application.cpp b/application.cpp index 71b146e1..dd8fee7e 100644 --- a/application.cpp +++ b/application.cpp @@ -167,13 +167,26 @@ void eu07_application::queue_quit() { glfwSetWindowShouldClose(m_windows[0], GLFW_TRUE); } +bool +eu07_application::is_server() const { + + return ( m_network && m_network->servers ); +} + +bool +eu07_application::is_client() const { + + return ( m_network && m_network->client ); +} + int eu07_application::run() { - + auto frame{ 0 }; // main application loop while (!glfwWindowShouldClose( m_windows.front() ) && !m_modestack.empty()) { Timer::subsystem.mainloop_total.start(); + glfwPollEvents(); // ------------------------------------------------------------------- // multiplayer command relaying logic can seem a bit complex @@ -191,6 +204,9 @@ eu07_application::run() { int loop_remaining = MAX_NETWORK_PER_FRAME; while (--loop_remaining > 0) { +#ifdef EU07_DEBUG_NETSYNC + WriteLog( "net: frame " + std::to_string(++frame) + " start", logtype::net ); +#endif command_queue::commands_map commands_to_exec; command_queue::commands_map local_commands = simulation::Commands.pop_intercept_queue(); double slave_sync; @@ -272,7 +288,7 @@ eu07_application::run() { float awaiting = m_network->client->get_awaiting_frames(); // TODO: don't meddle with mode progresbar - m_modes[m_modestack.top()]->set_progress(100.0f, 100.0f * (received - awaiting) / received); + m_modes[m_modestack.top()]->set_progress(100.0f * (received - awaiting) / received); } else { m_modes[m_modestack.top()]->set_progress(0.0f, 0.0f); } @@ -295,8 +311,6 @@ eu07_application::run() { if (!GfxRenderer->Render()) return 0; - glfwPollEvents(); - if (m_modestack.empty()) return 0; diff --git a/application.h b/application.h index f2006c07..8145cf75 100644 --- a/application.h +++ b/application.h @@ -92,6 +92,10 @@ public: generate_sync(); void queue_quit(); + bool + is_server() const; + bool + is_client() const; private: // types diff --git a/command.cpp b/command.cpp index d40a1018..241d05ce 100644 --- a/command.cpp +++ b/command.cpp @@ -386,7 +386,7 @@ command_relay::post(user_command const Command, double const Param1, double cons Position = Global.pCamera.Pos; uint32_t combined_recipient = static_cast( command.target ) | Recipient; - command_data commanddata({Command, Action, Param1, Param2, Timer::GetDeltaTime(), FreeFlyModeFlag, Position }); + command_data commanddata({Command, Action, Param1, Param2, Timer::GetDeltaTime(), Position }); if (Payload) commanddata.payload = *Payload; diff --git a/command.h b/command.h index 215bd3ad..1073cbb2 100644 --- a/command.h +++ b/command.h @@ -324,7 +324,6 @@ struct command_data { double param2; double time_delta; - bool freefly; glm::vec3 location; std::string payload; diff --git a/drivermode.cpp b/drivermode.cpp index 007f5379..437c5e08 100644 --- a/drivermode.cpp +++ b/drivermode.cpp @@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/. #include "simulation.h" #include "simulationtime.h" #include "simulationenvironment.h" +#include "scene.h" #include "lightarray.h" #include "particles.h" #include "Train.h" @@ -146,129 +147,119 @@ driver_mode::update() { double const deltatime = Timer::GetDeltaTime(); // 0.0 gdy pauza - if( Global.iPause == 0 ) { - // jak pauza, to nie ma po co tego przeliczać - simulation::Time.update( deltatime ); - } simulation::State.update_clocks(); simulation::State.update_scripting_interface(); simulation::Environment.update(); + if (deltatime != 0.0) + { + // jak pauza, to nie ma po co tego przeliczać + simulation::Time.update( deltatime ); + // fixed step, simulation time based updates -// m_primaryupdateaccumulator += dt; // unused for the time being - m_secondaryupdateaccumulator += deltatime; -/* - // NOTE: until we have no physics state interpolation during render, we need to rely on the old code, - // as doing fixed step calculations but flexible step render results in ugly mini jitter - // core routines (physics) - int updatecount = 0; - while( ( m_primaryupdateaccumulator >= m_primaryupdaterate ) - &&( updatecount < 20 ) ) { - // no more than 20 updates per single pass, to keep physics from hogging up all run time - Ground.Update( m_primaryupdaterate, 1 ); - ++updatecount; - m_primaryupdateaccumulator -= m_primaryupdaterate; - } -*/ - int updatecount = 1; - if( deltatime > m_primaryupdaterate ) // normalnie 0.01s - { -/* - // NOTE: experimentally disabled physics update cap - auto const iterations = std::ceil(dt / m_primaryupdaterate); - updatecount = std::min( 20, static_cast( iterations ) ); -*/ - updatecount = std::ceil( deltatime / m_primaryupdaterate ); -/* - // NOTE: changing dt wrecks things further down the code. re-acquire proper value later or cleanup here - dt = dt / iterations; // Ra: fizykę lepiej by było przeliczać ze stałym krokiem -*/ - } - auto const stepdeltatime { deltatime / updatecount }; - // NOTE: updates are limited to 20, but dt is distributed over potentially many more iterations - // this means at count > 20 simulation and render are going to desync. is that right? - // NOTE: experimentally changing this to prevent the desync. - // TODO: test what happens if we hit more than 20 * 0.01 sec slices, i.e. less than 5 fps - Timer::subsystem.sim_dynamics.start(); - if( true == Global.FullPhysics ) { - // mixed calculation mode, steps calculated in ~0.05s chunks - while( updatecount >= 5 ) { - simulation::State.update( stepdeltatime, 5 ); - updatecount -= 5; - } - if( updatecount ) { - simulation::State.update( stepdeltatime, updatecount ); - } - } - else { - // simplified calculation mode; faster but can lead to errors - simulation::State.update( stepdeltatime, updatecount ); - } - Timer::subsystem.sim_dynamics.stop(); - - // secondary fixed step simulation time routines - while( m_secondaryupdateaccumulator >= m_secondaryupdaterate ) { - - // awaria PoKeys mogła włączyć pauzę - przekazać informację - if( Global.iMultiplayer ) // dajemy znać do serwera o wykonaniu - if( iPause != Global.iPause ) { // przesłanie informacji o pauzie do programu nadzorującego - multiplayer::WyslijParam( 5, 3 ); // ramka 5 z czasem i stanem zapauzowania - iPause = Global.iPause; - } - - // TODO: generic shake update pass for vehicles within view range - if( Camera.m_owner != nullptr ) { - Camera.m_owner->update_shake( m_secondaryupdaterate ); - } - - m_secondaryupdateaccumulator -= m_secondaryupdaterate; // these should be inexpensive enough we have no cap - } - - // variable step simulation time routines - - if (!change_train.empty()) { - TTrain *train = simulation::Trains.find(change_train); - if (train) { - simulation::Train = train; - InOutKey(); - m_relay.post(user_command::aidriverdisable, 0.0, 0.0, GLFW_PRESS, 0); - change_train.clear(); + // m_primaryupdateaccumulator += dt; // unused for the time being + m_secondaryupdateaccumulator += deltatime; + /* + // NOTE: until we have no physics state interpolation during render, we need to rely on the old code, + // as doing fixed step calculations but flexible step render results in ugly mini jitter + // core routines (physics) + int updatecount = 0; + while( ( m_primaryupdateaccumulator >= m_primaryupdaterate ) + &&( updatecount < 20 ) ) { + // no more than 20 updates per single pass, to keep physics from hogging up all run time + Ground.Update( m_primaryupdaterate, 1 ); + ++updatecount; + m_primaryupdateaccumulator -= m_primaryupdaterate; } + */ + int updatecount = 1; + if( deltatime > m_primaryupdaterate ) // normalnie 0.01s + { + /* + // NOTE: experimentally disabled physics update cap + auto const iterations = std::ceil(dt / m_primaryupdaterate); + updatecount = std::min( 20, static_cast( iterations ) ); + */ + updatecount = std::ceil( deltatime / m_primaryupdaterate ); + /* + // NOTE: changing dt wrecks things further down the code. re-acquire proper value later or cleanup here + dt = dt / iterations; // Ra: fizykę lepiej by było przeliczać ze stałym krokiem + */ + } + auto const stepdeltatime { deltatime / updatecount }; + // NOTE: updates are limited to 20, but dt is distributed over potentially many more iterations + // this means at count > 20 simulation and render are going to desync. is that right? + // NOTE: experimentally changing this to prevent the desync. + // TODO: test what happens if we hit more than 20 * 0.01 sec slices, i.e. less than 5 fps + Timer::subsystem.sim_dynamics.start(); + if( true == Global.FullPhysics ) { + // mixed calculation mode, steps calculated in ~0.05s chunks + while( updatecount >= 5 ) { + simulation::State.update( stepdeltatime, 5 ); + updatecount -= 5; + } + if( updatecount ) { + simulation::State.update( stepdeltatime, updatecount ); + } + } + else { + // simplified calculation mode; faster but can lead to errors + simulation::State.update( stepdeltatime, updatecount ); + } + Timer::subsystem.sim_dynamics.stop(); + + // secondary fixed step simulation time routines + while( m_secondaryupdateaccumulator >= m_secondaryupdaterate ) { + + // awaria PoKeys mogła włączyć pauzę - przekazać informację + if( Global.iMultiplayer ) // dajemy znać do serwera o wykonaniu + if( iPause != Global.iPause ) { // przesłanie informacji o pauzie do programu nadzorującego + multiplayer::WyslijParam( 5, 3 ); // ramka 5 z czasem i stanem zapauzowania + iPause = Global.iPause; + } + + // TODO: generic shake update pass for vehicles within view range + if( Camera.m_owner != nullptr ) { + Camera.m_owner->update_shake( m_secondaryupdaterate ); + } + + m_secondaryupdateaccumulator -= m_secondaryupdaterate; // these should be inexpensive enough we have no cap + } + + // variable step simulation time routines + + if (!change_train.empty()) { + TTrain *train = simulation::Trains.find(change_train); + if (train) { + Global.local_start_vehicle = change_train; + simulation::Train = train; + InOutKey(); + m_relay.post(user_command::aidriverdisable, 0.0, 0.0, GLFW_PRESS, 0); + change_train.clear(); + } + } + + if( ( simulation::Train == nullptr ) && ( false == FreeFlyModeFlag ) ) { + // intercept cases when the driven train got removed after entering portal + InOutKey(); + } + + if (!FreeFlyModeFlag && simulation::Train->Dynamic() != Camera.m_owner) { + // fixup camera after vehicle switch + CabView(); + } + + if( simulation::Train != nullptr ) + TSubModel::iInstance = reinterpret_cast( simulation::Train->Dynamic() ); + else + TSubModel::iInstance = 0; + + simulation::Trains.update(deltatime); + simulation::Events.update(); + simulation::Region->update_events(); + simulation::Lights.update(); } - if( ( simulation::Train == nullptr ) && ( false == FreeFlyModeFlag ) ) { - // intercept cases when the driven train got removed after entering portal - InOutKey(); - } - - if (!FreeFlyModeFlag && simulation::Train->Dynamic() != Camera.m_owner) { - // fixup camera after vehicle switch - CabView(); - } -/* - if( Global.changeDynObj ) { - // ABu zmiana pojazdu - przejście do innego - ChangeDynamic(); - // move inside, but only if the human is in charge (otherwise we'll get pulled in when ai switches cabs) - if( ( simulation::Train != nullptr ) - && ( simulation::Train->Dynamic() != nullptr ) - && ( simulation::Train->Dynamic()->Mechanik != nullptr ) - && ( simulation::Train->Dynamic()->Mechanik->AIControllFlag == false ) - && ( true == FreeFlyModeFlag ) ) { - InOutKey(); - } - } -*/ - if( simulation::Train != nullptr ) - TSubModel::iInstance = reinterpret_cast( simulation::Train->Dynamic() ); - else - TSubModel::iInstance = 0; - - simulation::Trains.update(deltatime); - simulation::Events.update(); - simulation::Region->update_events(); - simulation::Lights.update(); - // render time routines follow: auto const deltarealtime = Timer::GetDeltaRenderTime(); // nie uwzględnia pauzowania ani mnożenia czasu @@ -359,10 +350,10 @@ driver_mode::update() { // NOTE: particle system runs on simulation time, but needs actual camera position to determine how to update each particle source simulation::Particles.update(); - simulation::is_ready = true; - GfxRenderer->Update( deltarealtime ); + simulation::is_ready = simulation::is_ready || ( ( simulation::Train != nullptr ) && ( simulation::Train->is_cab_initialized ) ) || ( Global.local_start_vehicle == "ghostview" ); + return true; } @@ -375,27 +366,24 @@ driver_mode::enter() { simulation::Vehicles.find( Global.local_start_vehicle ) : nullptr ) }; - Camera.Init(Global.FreeCameraInit[0], Global.FreeCameraInitAngle[0], nPlayerTrain ); + Camera.Init(Global.FreeCameraInit[0], Global.FreeCameraInitAngle[0], nullptr ); Global.pCamera = Camera; - Global.pDebugCamera = DebugCamera; + Global.pDebugCamera = DebugCamera; + + FreeFlyModeFlag = true; + DebugCamera = Camera; if (nPlayerTrain) { WriteLog( "Trying to enter player train, \"" + Global.local_start_vehicle + "\"" ); - FreeFlyModeFlag = true; // HACK: entervehicle won't work if the simulation thinks we're outside - m_relay.post(user_command::entervehicle, 0.0, 0.0, GLFW_PRESS, 0, nPlayerTrain->GetPosition()); + m_relay.post(user_command::entervehicle, 0.0, 0.0, GLFW_PRESS, 0, nPlayerTrain->GetPosition(), &Global.local_start_vehicle ); change_train = nPlayerTrain->name(); } - else + else if (Global.local_start_vehicle != "ghostview") { - if (Global.local_start_vehicle != "ghostview") - { - Error("Bad scenario: failed to locate player train, \"" + Global.local_start_vehicle + "\"" ); - } - FreeFlyModeFlag = true; // Ra: automatycznie włączone latanie - Camera.m_owner = nullptr; - DebugCamera = Camera; + Global.local_start_vehicle = "ghostview"; + Error("Bad scenario: failed to locate player train, \"" + Global.local_start_vehicle + "\"" ); } // if (!Global.bMultiplayer) //na razie włączone @@ -430,8 +418,10 @@ driver_mode::on_key( int const Key, int const Scancode, int const Action, int co Global.ctrlState = ( Mods & GLFW_MOD_CONTROL ) ? true : false; Global.altState = ( Mods & GLFW_MOD_ALT ) ? true : false; + bool anyModifier = Mods & (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL | GLFW_MOD_ALT); + // give the ui first shot at the input processing... - if( true == m_userinterface->on_key( Key, Scancode, Action, Mods ) ) { return; } + if( !anyModifier && true == m_userinterface->on_key( Key, Scancode, Action, Mods ) ) { return; } // ...if the input is left untouched, pass it on if( true == m_input.keyboard.key( Key, Action ) ) { return; } @@ -529,9 +519,9 @@ driver_mode::update_camera( double const Deltatime ) { Camera.LookAt = controlled->GetPosition() + 0.4 * controlled->VectorUp() * controlled->MoverParameters->Dim.H; } else { - TDynamicObject *d = std::get( simulation::Region->find_vehicle( Global.pCamera.Pos, 300, false, false ) ); + TDynamicObject *d = std::get( simulation::Region->find_vehicle( Camera.Pos, 300, false, false ) ); if( !d ) - d = std::get( simulation::Region->find_vehicle( Global.pCamera.Pos, 1000, false, false ) ); // dalej szukanie, jesli bliżej nie ma + d = std::get( simulation::Region->find_vehicle( Camera.Pos, 1000, false, false ) ); // dalej szukanie, jesli bliżej nie ma if( d && pDynamicNearest ) { // jeśli jakiś jest znaleziony wcześniej @@ -815,106 +805,32 @@ driver_mode::OnKeyDown(int cKey) { } case GLFW_KEY_F5: { // przesiadka do innego pojazdu - if (!FreeFlyModeFlag) - // only available in free fly mode - break; + if( !FreeFlyModeFlag ) { break; } - TDynamicObject *dynamic = std::get( simulation::Region->find_vehicle( Global.pCamera.Pos, 50, false, false ) ); - if (dynamic) { - m_relay.post(user_command::entervehicle, 0.0, 0.0, GLFW_PRESS, 0); + auto const *targetvehicle { std::get( simulation::Region->find_vehicle( Camera.Pos, 50, false, false ) ) }; - change_train = dynamic->name(); - } - - break; -/* - if( false == FreeFlyModeFlag ) { - // only available in free fly mode + if( targetvehicle == nullptr ) { break; } + if( ( targetvehicle->Mechanik ) && + ( false == targetvehicle->Mechanik->AIControllFlag ) ) { + // for the time being we don't allow more than one person per vehicle break; } - TDynamicObject *targetvehicle = std::get( simulation::Region->find_vehicle( Global.pCamera.Pos, 50, false, false ) ); - - if( targetvehicle != nullptr ) { - - if( ( true == DebugModeFlag ) - || ( targetvehicle->MoverParameters->Vel <= 5.0 ) ) { - // works always in debug mode, or for stopped/slow moving vehicles otherwise - if( simulation::Train == nullptr ) { - simulation::Train = new TTrain(); // jeśli niczym jeszcze nie jeździlismy - } - if( simulation::Train->Dynamic() != nullptr ) { - // jeśli mielismy pojazd - if( simulation::Train->Dynamic()->Mechanik ) { // na skutek jakiegoś błędu może czasem zniknąć - auto const *currentvehicle { simulation::Train->Dynamic() }; - auto const samevehicle { currentvehicle == targetvehicle }; - - if( samevehicle ) { - // we already control desired vehicle so don't overcomplicate things - InOutKey(); // do kabiny - break; - } - - auto const sameconsist { - ( targetvehicle->ctOwner == currentvehicle->Mechanik ) - || ( targetvehicle->ctOwner == currentvehicle->ctOwner ) }; - auto const isincharge { currentvehicle->Mechanik->primary() }; - auto const aidriveractive { currentvehicle->Mechanik->AIControllFlag }; - - if( !sameconsist && isincharge ) { - // oddajemy dotychczasowy AI - simulation::Train->Dynamic()->Mechanik->TakeControl( true ); - } - - if( ( !sameconsist ) // we leave behind an ai driver which should be preserved - || ( aidriveractive ) // we want to preserve existing ai driver - || ( targetvehicle->Mechanik != nullptr ) ) { // .changedynobj swaps drivers but we want a takeover not a swap - - if( sameconsist && !aidriveractive ) { - // we will be taking over controller in the target vehicle, so get rid of the old one - // unless it's an active ai in which case leave it running - SafeDelete( simulation::Train->Dynamic()->Mechanik ); - } - // HACK: by resetting owned vehicle we can reuse dynamic==nullptr code branch below - // TODO: refactor into utility method - simulation::Train->DynamicSet( nullptr ); - } - else { - // we can simply move the 'human' controller to the new vehicle - Global.changeDynObj = targetvehicle; - // TODO: choose active cab based on camera's location relative to vehicle's location -// Global.changeDynObj->MoverParameters->CabOccupied = ( -// Train->DynamicObject->MoverParameters->Neighbours[ exitdirection ].vehicle_end ? -// -1 : -// 1 ); - } - } - } - if( simulation::Train->Dynamic() == nullptr ) { - // jeśli niczym jeszcze nie jeździlismy - if( simulation::Train->Init( targetvehicle ) ) { - // przejmujemy sterowanie - if( true == Global.ctrlState ) { - // make sure we can take over the consist - // TODO: remove ctrl key mode once manual cab (de)activation is in place - simulation::Train->Dynamic()->Mechanik->primary( true ); - } - simulation::Train->Dynamic()->Mechanik->TakeControl( false, true ); - if( true == simulation::Train->Dynamic()->Mechanik->primary() ) { - simulation::Train->Occupied()->CabDeactivisation( true ); // potentially left active -// simulation::Train->Occupied()->CabOccupied = simulation::Train->Occupied()->CabActive; - simulation::Train->Occupied()->CabActivisation(); - } - InOutKey(); // do kabiny - } - else { - SafeDelete( simulation::Train ); // i nie ma czym sterować - } - } - } + if( ( true == DebugModeFlag ) + || ( targetvehicle->MoverParameters->Vel <= 5.0 ) ) { + // works always in debug mode, or for stopped/slow moving vehicles otherwise + m_relay.post( + user_command::entervehicle, + ( Global.ctrlState ? GLFW_MOD_CONTROL : 0 ), // TODO: remove ctrl key mode once manual cab (de)activation is in place, + ( simulation::Train ? simulation::Train->id() : 0 ), + GLFW_PRESS, + 0, + targetvehicle->GetPosition(), + &targetvehicle->name() ); + change_train = targetvehicle->name(); } break; -*/ + } case GLFW_KEY_F6: { // przyspieszenie symulacji do testowania scenerii... uwaga na FPS! @@ -1176,67 +1092,6 @@ driver_mode::CabView() { train->pMechOffset = Camera.m_owneroffset; } -void -driver_mode::ChangeDynamic() { - - auto *train { simulation::Train }; - if( train == nullptr ) { return; } - - auto *vehicle { train->Dynamic() }; - auto *occupied { train->Occupied() }; - auto *driver { vehicle->Mechanik }; - // Ra: to nie może być tak robione, to zbytnia proteza jest - if( driver ) { - // AI może sobie samo pójść - if( false == driver->AIControllFlag ) { - // tylko jeśli ręcznie prowadzony - // jeśli prowadzi AI, to mu nie robimy dywersji! - occupied->CabDeactivisation(); - occupied->CabOccupied = 0; - occupied->BrakeLevelSet( occupied->Handle->GetPos( bh_NP ) ); //rozwala sterowanie hamulcem GF 04-2016 - occupied->MainCtrlPos = occupied->MainCtrlNoPowerPos(); - occupied->ScndCtrlPos = 0; - vehicle->MechInside = false; - vehicle->Controller = AIdriver; - } - } - TDynamicObject *temp = Global.changeDynObj; - vehicle->bDisplayCab = false; - vehicle->ABuSetModelShake( {} ); - - if( driver ) // AI może sobie samo pójść - if( false == driver->AIControllFlag ) { - // tylko jeśli ręcznie prowadzony - // przsunięcie obiektu zarządzającego - driver->MoveTo( temp ); - } - - train->DynamicSet( temp ); - // update helpers - train = simulation::Train; - vehicle = train->Dynamic(); - occupied = train->Occupied(); - driver = vehicle->Mechanik; - Global.local_start_vehicle = vehicle->name(); - if( driver ) // AI może sobie samo pójść - if( false == driver->AIControllFlag ) // tylko jeśli ręcznie prowadzony - { - occupied->LimPipePress = occupied->PipePress; - occupied->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny) - vehicle->MechInside = true; - vehicle->Controller = Humandriver; - } - train->InitializeCab( - occupied->CabActive, - vehicle->asBaseDir + occupied->TypeName + ".mmd" ); - if( false == FreeFlyModeFlag ) { - vehicle->bDisplayCab = true; - vehicle->ABuSetModelShake( {} ); // zerowanie przesunięcia przed powrotem? - CabView(); // na pozycję mecha - } - Global.changeDynObj = nullptr; -} - void driver_mode::InOutKey() { // przełączenie widoku z kabiny na zewnętrzny i odwrotnie diff --git a/drivermode.h b/drivermode.h index eedd307b..3e3618b0 100644 --- a/drivermode.h +++ b/drivermode.h @@ -91,7 +91,6 @@ private: void update_camera( const double Deltatime ); // handles vehicle change flag void OnKeyDown( int cKey ); - void ChangeDynamic(); void InOutKey(); void CabView(); void ExternalView(); diff --git a/driveruilayer.cpp b/driveruilayer.cpp index edf28499..772b54d5 100644 --- a/driveruilayer.cpp +++ b/driveruilayer.cpp @@ -181,8 +181,14 @@ driver_ui::render_() { } if( ImGui::Button( locale::strings[ locale::string::driver_pause_quit ].c_str(), ImVec2( popupwidth, 0 ) ) ) { ImGui::CloseCurrentPopup(); - command_relay commandrelay; - commandrelay.post(user_command::quitsimulation, 0.0, 0.0, GLFW_PRESS, 0); + // NOTE: server shuts down entire network, client or standalone instance only shuts down self + if( Application.is_server() ) { + command_relay commandrelay; + commandrelay.post( user_command::quitsimulation, 0.0, 0.0, GLFW_PRESS, 0 ); + } + else { + Application.queue_quit(); + } } ImGui::EndPopup(); } diff --git a/driveruipanels.cpp b/driveruipanels.cpp index 9730670d..40e9cc5f 100644 --- a/driveruipanels.cpp +++ b/driveruipanels.cpp @@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/. #include "driveruipanels.h" #include "Globals.h" +#include "application.h" #include "translation.h" #include "simulation.h" #include "simulationtime.h" @@ -564,34 +565,45 @@ bool debug_panel::render_section_scenario() { if( false == render_section( "Scenario", m_scenariolines ) ) { return false; } - + if( Application.is_client() ) { + // NOTE: simulation clients can't adjust scenarion state, this is reserved for the server/standalone instance + return true; + } // fog slider { auto fogrange = std::log( Global.fFogEnd ); if( ImGui::SliderFloat( ( to_string( std::exp( fogrange ), 0, 5 ) + " m###fogend" ).c_str(), &fogrange, std::log( 10.0f ), std::log( 25000.0f ), "Fog distance" ) ) { - Global.fFogEnd = clamp( std::exp( fogrange ), 10.0f, 25000.0f ); + command_relay relay; + relay.post( + user_command::setweather, + clamp( std::exp( fogrange ), 10.0f, 25000.0f ), + Global.Overcast, + GLFW_PRESS, 0 ); } } // cloud cover slider { if( ImGui::SliderFloat( ( to_string( Global.Overcast, 2, 5 ) + " (" + 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(); + command_relay relay; + relay.post( + user_command::setweather, + Global.fFogEnd, + clamp( Global.Overcast, 0.0f, 2.0f ), + GLFW_PRESS, 0 ); } } // day of year slider { - if( ImGui::SliderFloat( ( to_string( Global.fMoveLight, 0, 5 ) + " (" + Global.Season + ")###movelight" ).c_str(), &Global.fMoveLight, 0.0f, 364.0f, "Day of year" ) ) { - Global.fMoveLight = clamp( Global.fMoveLight, 0.0f, 365.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( ImGui::SliderFloat( + ( to_string( Global.fMoveLight, 0, 5 ) + " (" + Global.Season + ")###movelight" ).c_str(), &Global.fMoveLight, 0.0f, 364.0f, "Day of year" ) ) { + command_relay relay; + relay.post( + user_command::setdatetime, + clamp( Global.fMoveLight, 0.0f, 365.0f ), + simulation::Time.data().wHour * 60 + simulation::Time.data().wMinute, + GLFW_PRESS, 0 ); } } // time of day slider @@ -605,15 +617,15 @@ debug_panel::render_section_scenario() { + ":" + std::string( to_string( int( 100 + simulation::Time.data().wMinute ) ).substr( 1, 2 ) ) ) }; if( ImGui::SliderInt( ( timestring + " (" + Global.Period + ")###simulationtime" ).c_str(), &time, 0, 1439, "Time of day" ) ) { - time = clamp( time, 0, 1439 ); - auto const hour{ std::floor( time / 60 ) }; - auto const minute{ time % 60 }; - simulation::Time.data().wHour = hour; - simulation::Time.data().wMinute = minute; + command_relay relay; + relay.post( + user_command::setdatetime, + Global.fMoveLight, + clamp( time, 0, 1439 ), + GLFW_PRESS, 0 ); } } - return true; } diff --git a/network/message.cpp b/network/message.cpp index 03de9fae..8de64797 100644 --- a/network/message.cpp +++ b/network/message.cpp @@ -18,12 +18,16 @@ void network::server_hello::serialize(std::ostream &stream) const { sn_utils::ls_uint32(stream, seed); sn_utils::ls_int64(stream, timestamp); + sn_utils::ls_int64(stream, config); + sn_utils::s_str(stream, scenario); } void network::server_hello::deserialize(std::istream &stream) { seed = sn_utils::ld_uint32(stream); timestamp = sn_utils::ld_int64(stream); + config = sn_utils::ld_int64(stream); + scenario = sn_utils::d_str(stream); } void ::network::request_command::serialize(std::ostream &stream) const @@ -41,7 +45,6 @@ void ::network::request_command::serialize(std::ostream &stream) const sn_utils::ls_float64(stream, data.param2); sn_utils::ls_float64(stream, data.time_delta); - sn_utils::s_bool(stream, data.freefly); sn_utils::s_vec3(stream, data.location); sn_utils::s_str(stream, data.payload); @@ -67,7 +70,6 @@ void network::request_command::deserialize(std::istream &stream) data.param2 = sn_utils::ld_float64(stream); data.time_delta = sn_utils::ld_float64(stream); - data.freefly = sn_utils::d_bool(stream); data.location = sn_utils::d_vec3(stream); data.payload = sn_utils::d_str(stream); diff --git a/network/message.h b/network/message.h index aa81a5ed..4ddc0b40 100644 --- a/network/message.h +++ b/network/message.h @@ -40,6 +40,8 @@ struct server_hello : public message uint32_t seed; int64_t timestamp; + int64_t config; + std::string scenario; virtual void serialize(std::ostream &stream) const override; virtual void deserialize(std::istream &stream) override; diff --git a/network/network.cpp b/network/network.cpp index 28f20de1..1f5a1f87 100644 --- a/network/network.cpp +++ b/network/network.cpp @@ -7,6 +7,8 @@ #include "application.h" #include "Globals.h" +std::uint32_t const EU07_NETWORK_VERSION = 2; + namespace network { backend_list_t& backend_list() { @@ -40,7 +42,7 @@ void network::connection::connected() if (is_client) { client_hello msg; - msg.version = 1; + msg.version = EU07_NETWORK_VERSION; msg.start_packet = packet_counter; send_message(msg); } @@ -125,7 +127,7 @@ void network::server::handle_message(std::shared_ptr conn, const mes if (msg.type == message::CLIENT_HELLO) { auto cmd = dynamic_cast(msg); - if (cmd.version != 1 // wrong version + if (cmd.version != EU07_NETWORK_VERSION // wrong version || !Global.ready_to_load) { // not ready yet conn->disconnect(); return; @@ -134,6 +136,8 @@ void network::server::handle_message(std::shared_ptr conn, const mes server_hello reply; reply.seed = Global.random_seed; reply.timestamp = Global.starting_timestamp; + reply.config = 0; // TODO: pass bitfield with state of relevant setting switches + reply.scenario = Global.SceneryFile; conn->state = connection::CATCHING_UP; conn->backbuffer = backbuffer; conn->backbuffer_pos = 0; @@ -244,6 +248,8 @@ void network::client::handle_message(std::shared_ptr conn, const mes Global.random_seed = cmd.seed; Global.random_engine.seed(Global.random_seed); Global.starting_timestamp = cmd.timestamp; + // TODO: configure simulation settings according to received cmd.config + Global.SceneryFile = cmd.scenario; Global.ready_to_load = true; } else if (Global.random_seed != cmd.seed) { ErrorLog("net: seed mismatch", logtype::net); diff --git a/particles.cpp b/particles.cpp index 4c05956e..44df63de 100644 --- a/particles.cpp +++ b/particles.cpp @@ -58,20 +58,20 @@ smoke_source::particle_emitter::deserialize( cParser &Input ) { void smoke_source::particle_emitter::initialize( smoke_particle &Particle ) { - auto const polarangle { glm::radians( Random( inclination[ value_limit::min ], inclination[ value_limit::max ] ) ) }; // theta - auto const azimuthalangle { glm::radians( Random( -180, 180 ) ) }; // phi + auto const polarangle { glm::radians( LocalRandom( inclination[ value_limit::min ], inclination[ value_limit::max ] ) ) }; // theta + auto const azimuthalangle { glm::radians( LocalRandom( -180, 180 ) ) }; // phi // convert spherical coordinates to opengl coordinates auto const launchvector { glm::vec3( std::sin( polarangle ) * std::sin( azimuthalangle ) * -1, std::cos( polarangle ), std::sin( polarangle ) * std::cos( azimuthalangle ) ) }; - auto const launchvelocity { static_cast( Random( velocity[ value_limit::min ], velocity[ value_limit::max ] ) ) }; + auto const launchvelocity { static_cast( LocalRandom( velocity[ value_limit::min ], velocity[ value_limit::max ] ) ) }; Particle.velocity = launchvector * launchvelocity; - Particle.rotation = glm::radians( Random( 0, 360 ) ); - Particle.size = Random( size[ value_limit::min ], size[ value_limit::max ] ); - Particle.opacity = Random( opacity[ value_limit::min ], opacity[ value_limit::max ] ) / Global.SmokeFidelity; + Particle.rotation = glm::radians( LocalRandom( 0, 360 ) ); + Particle.size = LocalRandom( size[ value_limit::min ], size[ value_limit::max ] ); + Particle.opacity = LocalRandom( opacity[ value_limit::min ], opacity[ value_limit::max ] ) / Global.SmokeFidelity; Particle.age = 0; } diff --git a/scenarioloadermode.cpp b/scenarioloadermode.cpp index 95c791b3..8232afda 100644 --- a/scenarioloadermode.cpp +++ b/scenarioloadermode.cpp @@ -36,7 +36,6 @@ scenarioloader_mode::init() { // mode-specific update of simulation data. returns: false on error, true otherwise bool scenarioloader_mode::update() { - if (!Global.ready_to_load) // waiting for network connection return true; @@ -44,7 +43,8 @@ scenarioloader_mode::update() { if (!state) { WriteLog("using simulation seed: " + std::to_string(Global.random_seed), logtype::generic); - WriteLog( "\nLoading scenario \"" + Global.SceneryFile + "\"..." ); + Application.set_title( Global.AppName + " (" + Global.SceneryFile + ")" ); + WriteLog( "\nLoading scenario \"" + Global.SceneryFile + "\"..." ); timestart = std::chrono::system_clock::now(); state = simulation::State.deserialize_begin(Global.SceneryFile); @@ -63,7 +63,7 @@ scenarioloader_mode::update() { // TODO: implement and use next mode cue Application.pop_mode(); - Application.push_mode( eu07_application::mode::driver ); + Application.push_mode( eu07_application::mode::driver ); return true; } @@ -78,7 +78,7 @@ scenarioloader_mode::enter() { simulation::is_ready = false; m_userinterface->set_background( "logo" ); - Application.set_title( Global.AppName + " (" + Global.SceneryFile + ")" ); + Application.set_title( Global.AppName ); m_userinterface->set_progress(); m_userinterface->set_progress( "Loading scenery / Wczytywanie scenerii" ); GfxRenderer->Render(); diff --git a/scene.h b/scene.h index 4ec48c05..c76846a5 100644 --- a/scene.h +++ b/scene.h @@ -59,6 +59,7 @@ struct scratch_data { std::string name; bool initialized { false }; + bool time_initialized { false }; }; // basic element of rudimentary partitioning scheme for the section. fixed size, no further subdivision diff --git a/simulation.cpp b/simulation.cpp index 767ddfc0..8e229506 100644 --- a/simulation.cpp +++ b/simulation.cpp @@ -218,28 +218,58 @@ void state_manager::process_commands() { if (commanddata.command == user_command::entervehicle) { // przesiadka do innego pojazdu - if (!commanddata.freefly) - // only available in free fly mode + // NOTE: because malformed scenario can have vehicle name duplicates we first try to locate vehicle in world, with name search as fallback + TDynamicObject *targetvehicle = std::get( simulation::Region->find_vehicle( commanddata.location, 50, false, false ) ); + if( ( targetvehicle == nullptr ) || ( targetvehicle->name() != commanddata.payload ) ) { + targetvehicle = simulation::Vehicles.find( commanddata.payload ); + } + + if (!targetvehicle) continue; - TDynamicObject *dynamic = std::get( simulation::Region->find_vehicle( commanddata.location, 50, false, false ) ); + auto *senderlocaltrain { simulation::Trains.find_id( static_cast( commanddata.param2 ) ) }; + if( senderlocaltrain ) { + auto *currentvehicle { senderlocaltrain->Dynamic() }; + auto const samevehicle { currentvehicle == targetvehicle }; - if (!dynamic) - continue; + if( samevehicle ) { + // we already control desired vehicle so don't overcomplicate things + continue; + } - TTrain *train = simulation::Trains.find(dynamic->name()); - if (train) - continue; + auto const sameconsist{ + ( targetvehicle->ctOwner == currentvehicle->Mechanik ) + || ( targetvehicle->ctOwner == currentvehicle->ctOwner ) }; + auto const isincharge{ currentvehicle->Mechanik->primary() }; + auto const aidriveractive{ currentvehicle->Mechanik->AIControllFlag }; + // TODO: support for primary mode request passed as commanddata.param1 + if( !sameconsist && isincharge ) { + // oddajemy dotychczasowy AI + currentvehicle->Mechanik->TakeControl( true ); + } + + if( sameconsist && !aidriveractive ) { + // since we're consist owner we can simply move to the destination vehicle + senderlocaltrain->MoveToVehicle( targetvehicle ); + senderlocaltrain->Dynamic()->Mechanik->TakeControl( false, true ); + } + } + + auto *train { simulation::Trains.find( targetvehicle->name() ) }; + + if (train) + continue; train = new TTrain(); - if (train->Init(dynamic)) { + if (train->Init(targetvehicle)) { simulation::Trains.insert(train); } else { delete train; train = nullptr; } - } + + } if (commanddata.command == user_command::queueevent) { std::istringstream ss(commanddata.payload); @@ -268,11 +298,16 @@ void state_manager::process_commands() { if (commanddata.command == user_command::setdatetime) { int yearday = std::round(commanddata.param1); - int minute = std::round(commanddata.param2 * 60.0); + int minute = std::round(commanddata.param2); simulation::Time.set_time(yearday, minute); - simulation::Environment.compute_season(yearday); - } + auto const weather { Global.Weather }; + simulation::Environment.compute_season(yearday); + if( weather != Global.Weather ) { + // HACK: force re-calculation of precipitation + Global.Overcast = clamp( Global.Overcast - 0.0001f, 0.0f, 2.0f ); + } + } if (commanddata.command == user_command::setweather) { Global.fFogEnd = commanddata.param1; @@ -394,6 +429,7 @@ void state_manager::process_commands() { } if (commanddata.command == user_command::quitsimulation) { + // TBD: allow clients to go into offline mode? Application.queue_quit(); } diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index 090a5c70..af6029ad 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -320,6 +320,9 @@ state_serializer::deserialize_firstinit( cParser &Input, scene::scratch_data &Sc simulation::Events.InitLaunchers(); simulation::Memory.InitCells(); + if (!Scratchpad.time_initialized) + init_time(); + Scratchpad.initialized = true; } @@ -628,20 +631,14 @@ state_serializer::deserialize_time( cParser &Input, scene::scratch_data &Scratch >> time.wHour >> time.wMinute; - if( true == Global.ScenarioTimeCurrent ) { - // calculate time shift required to match scenario time with local clock - auto timenow = std::time( 0 ); - auto const *localtime = std::localtime( &timenow ); - Global.ScenarioTimeOffset = ( ( localtime->tm_hour * 60 + localtime->tm_min ) - ( time.wHour * 60 + time.wMinute ) ) / 60.f; - } - else if( false == std::isnan( Global.ScenarioTimeOverride ) ) { - // scenario time override takes precedence over scenario time offset - Global.ScenarioTimeOffset = ( ( Global.ScenarioTimeOverride * 60 ) - ( time.wHour * 60 + time.wMinute ) ) / 60.f; - } - // remaining sunrise and sunset parameters are no longer used, as they're now calculated dynamically // anything else left in the section has no defined meaning skip_until( Input, "endtime" ); + + if (!Scratchpad.time_initialized) + Scratchpad.time_initialized = true; + + init_time(); } void @@ -974,6 +971,19 @@ state_serializer::deserialize_sound( cParser &Input, scene::scratch_data &Scratc return sound; } +void state_serializer::init_time() { + auto &time = simulation::Time.data(); + if( true == Global.ScenarioTimeCurrent ) { + // calculate time shift required to match scenario time with local clock + auto const *localtime = std::gmtime( &Global.starting_timestamp ); + Global.ScenarioTimeOffset = ( ( localtime->tm_hour * 60 + localtime->tm_min ) - ( time.wHour * 60 + time.wMinute ) ) / 60.f; + } + else if( false == std::isnan( Global.ScenarioTimeOverride ) ) { + // scenario time override takes precedence over scenario time offset + Global.ScenarioTimeOffset = ( ( Global.ScenarioTimeOverride * 60 ) - ( time.wHour * 60 + time.wMinute ) ) / 60.f; + } +} + // skips content of stream until specified token void state_serializer::skip_until( cParser &Input, std::string const &Token ) { diff --git a/simulationstateserializer.h b/simulationstateserializer.h index fee8c0f8..2994d7a9 100644 --- a/simulationstateserializer.h +++ b/simulationstateserializer.h @@ -76,6 +76,7 @@ private: TAnimModel * deserialize_model( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); TDynamicObject * deserialize_dynamic( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); sound_source * deserialize_sound( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + void init_time(); // skips content of stream until specified token void skip_until( cParser &Input, std::string const &Token ); // transforms provided location by specifed rotation and offset diff --git a/utilities.cpp b/utilities.cpp index d93cfd7a..3e62257c 100644 --- a/utilities.cpp +++ b/utilities.cpp @@ -27,8 +27,10 @@ Copyright (C) 2007-2014 Maciej Cierniak #include "Globals.h" #include "parser.h" +#include "Logs.h" + bool DebugModeFlag = false; -bool FreeFlyModeFlag = true; +bool FreeFlyModeFlag = false; bool EditorModeFlag = false; bool DebugCameraFlag = false; bool DebugTractionFlag = false; @@ -107,7 +109,12 @@ bool ClearFlag( int &Flag, int const Value ) { double Random(double a, double b) { uint32_t val = Global.random_engine(); - return interpolate(a, b, (double)val / Global.random_engine.max()); +#ifdef EU07_DEBUG_NETSYNC + auto const value = interpolate( a, b, (double)val / Global.random_engine.max() ); + WriteLog( "random: [" + to_string(a,0) + "-" + to_string(b,0) + "] = " + std::to_string( value ) ); + return value; +#endif + return interpolate(a, b, (double)val / Global.random_engine.max()); } double LocalRandom(double a, double b)