/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* MaSzyna EU07 locomotive simulator Copyright (C) 2001-2004 Marcin Wozniak, Maciej Czapkiewicz and others */ #include "stdafx.h" #include "vehicle/train/Train.h" #include "utilities/Globals.h" #include "simulation/simulation.h" #include "world/Event.h" #include "simulation/simulationtime.h" #include "utilities/Logs.h" #include "model/Model3d.h" #include "vehicle/Driver.h" #include "vehicle/DynObj.h" #include "Console.h" #include "application/application.h" #include "rendering/renderer.h" #include #include #include /* namespace input { extern user_command command; } */ auto const EU07_CONTROLLER_BASERETURNDELAY{0.5f}; auto const EU07_CONTROLLER_KEYBOARDETURNDELAY{1.5f}; 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 control_mapper::find(TSubModel const *Control) const { auto const lookup = m_controlnames.find(Control); if (lookup != m_controlnames.end()) { return lookup->second; } else { return ""; } } bool control_mapper::contains(std::string const Control) const { return m_names.find(Control) != m_names.end(); } bool TTrain::is_eztoer() const { return mvControlled->TrainType == dt_EZT && mvOccupied->BrakeSubsystem == TBrakeSubSystem::ss_ESt && mvControlled->Power24vIsAvailable == true && mvControlled->EpFuse == true && mvControlled->DirActive != 0; // od yB } // mover master controller to specified position void TTrain::set_master_controller(double const Position) { auto positionchange{std::min(Position, mvControlled->CoupledCtrl ? mvControlled->MainCtrlPosNo + mvControlled->ScndCtrlPosNo : mvControlled->MainCtrlPosNo) - (mvControlled->CoupledCtrl ? mvControlled->MainCtrlPos + mvControlled->ScndCtrlPos : mvControlled->MainCtrlPos)}; while (positionchange < 0 && true == mvControlled->DecMainCtrl(1)) { ++positionchange; } while (positionchange > 0 && true == mvControlled->IncMainCtrl(1)) { --positionchange; } } // moves train brake lever to specified position, potentially emits switch sound if conditions are met void TTrain::set_train_brake(double const Position) { auto const originalbrakeposition{static_cast(100.0 * mvOccupied->fBrakeCtrlPos)}; mvOccupied->BrakeLevelSet(Position); if (static_cast(100.0 * mvOccupied->fBrakeCtrlPos) == originalbrakeposition) { return; } if (true == is_eztoer() && false == ((originalbrakeposition / 100 == 0 || originalbrakeposition / 100 >= 5) && (mvOccupied->BrakeCtrlPos == 0 || mvOccupied->BrakeCtrlPos >= 5))) { // sound feedback if the lever movement activates one of the switches if (dsbPneumaticSwitch) { dsbPneumaticSwitch->play(); } } } void TTrain::zero_charging_train_brake() { if (mvOccupied->BrakeCtrlPos == -1 && DynamicObject->Controller != AIdriver && Global.iFeedbackMode < 3 && (mvOccupied->BrakeHandle == TBrakeHandle::FVel6 || mvOccupied->BrakeHandle == TBrakeHandle::MHZ_EN57 || mvOccupied->BrakeHandle == TBrakeHandle::MHZ_K8P)) { // Odskakiwanie hamulce EP set_train_brake(0); } } void TTrain::set_train_brake_speed(TDynamicObject *Vehicle, int const Speed) { if (true == Vehicle->MoverParameters->BrakeDelaySwitch(Speed)) { // visual feedback // TODO: add setting indicator to vehicle class, for external lever/indicator if (Vehicle == DynamicObject) { if (ggBrakeProfileCtrl.SubModel != nullptr) { ggBrakeProfileCtrl.UpdateValue((mvOccupied->BrakeDelayFlag & bdelay_R) != 0 ? 2.0 : mvOccupied->BrakeDelayFlag - 1, dsbSwitch); } if (ggBrakeProfileG.SubModel != nullptr) { ggBrakeProfileG.UpdateValue(mvOccupied->BrakeDelayFlag == bdelay_G ? 1.0 : 0.0, dsbSwitch); } if (ggBrakeProfileR.SubModel != nullptr) { ggBrakeProfileR.UpdateValue((mvOccupied->BrakeDelayFlag & bdelay_R) != 0 ? 1.0 : 0.0, dsbSwitch); } } } } void TTrain::set_paired_open_motor_connectors_button(bool const State) { if (mvControlled->TrainType == dt_ET41 || mvControlled->TrainType == dt_ET42) { // crude implementation of the button affecting entire unit for multi-unit engines // TODO: rework it into part of standard command propagation system if (mvControlled->Couplers[end::front].Connected != nullptr && true == TestFlag(mvControlled->Couplers[end::front].CouplingFlag, coupling::permanent)) { mvControlled->Couplers[end::front].Connected->StLinSwitchOff = State; } if (mvControlled->Couplers[end::rear].Connected != nullptr && true == TestFlag(mvControlled->Couplers[end::rear].CouplingFlag, coupling::permanent)) { mvControlled->Couplers[end::rear].Connected->StLinSwitchOff = State; } } } // locates nearest vehicle belonging to the consist TDynamicObject *TTrain::find_nearest_consist_vehicle(bool freefly, glm::vec3 pos) const { if (!freefly) return DynamicObject; auto coupler{-2}; // scan for vehicle, not any specific coupler auto *vehicle{DynamicObject->ABuScanNearestObject(pos, DynamicObject->GetTrack(), 1, 1500, coupler)}; if (vehicle == nullptr) vehicle = DynamicObject->ABuScanNearestObject(pos, DynamicObject->GetTrack(), -1, 1500, coupler); // TBD, TODO: perform owner test for the located vehicle return vehicle; } bool TTrain::Update(double const Deltatime) { // train state update // line breaker: if (m_linebreakerstate == 0) { if (true == mvControlled->Mains) { // crude way to sync state of the linebreaker with ai-issued commands m_linebreakerstate = 1; } } if (m_linebreakerstate == 1) { if (false == (mvControlled->Mains || mvControlled->dizel_startup)) { // crude way to catch cases where the main was knocked out // because the state of the line breaker isn't changed to match, we need to do it here manually m_linebreakerstate = 0; } } if ((ggMainButton.SubModel != nullptr && ggMainButton.GetDesiredValue() > 0.95) || ggMainOnButton.SubModel != nullptr && ggMainOnButton.GetDesiredValue() > 0.95 || ggIgnitionKey.GetDesiredValue() > 0.95) { // HACK: fallback // keep track of period the line breaker button is held down, to determine when/if circuit closes if (mvControlled->MainSwitchCheck()) { fMainRelayTimer += Deltatime; } } else { // button isn't down, reset the timer fMainRelayTimer = 0.0f; } if (ggMainOffButton.GetDesiredValue() > 0.95) { // if the button disconnecting the line breaker is down prevent the timer from accumulating fMainRelayTimer = 0.0f; } if (m_linebreakerstate == 0) { if (fMainRelayTimer > mvControlled->InitialCtrlDelay) { // wlaczanie WSa z opoznieniem // mark the line breaker as ready to close; for electric series vehicles with impulse switch the setup is completed on button release m_linebreakerstate = 2; } } if (m_linebreakerstate == 2) { // for diesels and/or vehicles with toggle switch setup we complete the engine start here // TODO: make it a test for main_on_bt of type push_delayed instead if (ggMainOnButton.SubModel == nullptr || mvControlled->EngineType != TEngineType::ElectricSeriesMotor) { // try to finalize state change of the line breaker, set the state based on the outcome m_linebreakerstate = mvControlled->MainSwitch(true) ? 1 : 0; } } // door permits for (auto idx = 0; idx < 2; ++idx) { auto &doorpermittimer{m_doorpermittimers[idx]}; if (doorpermittimer < 0.f) { continue; } doorpermittimer -= Deltatime; if (doorpermittimer < 0.f) { mvOccupied->OperateDoors(static_cast(idx), true); } } // train measurement timer if (trainLenghtMeasureTimer >= 0.f) { trainLenghtMeasureTimer -= Deltatime; if (trainLenghtMeasureTimer < 0.f) trainLenghtMeasureTimer = -1.f; } // battery timer if (fBatteryTimer >= 0.f) { fBatteryTimer -= Deltatime; if (fBatteryTimer < 0.f) fBatteryTimer = -1.f; } // helper variables if (DynamicObject->Mechanik != nullptr) { m_doors = DynamicObject->Mechanik->IsAnyDoorOpen[side::right] || DynamicObject->Mechanik->IsAnyDoorOpen[side::left]; m_doorpermits = DynamicObject->Mechanik->IsAnyDoorPermitActive[side::right] || DynamicObject->Mechanik->IsAnyDoorPermitActive[side::left]; m_doorspermitleft = mvOccupied->Doors.instances[(cab_to_end() == end::front ? side::left : side::right)].open_permit && (simulation::Time.data().wSecond % 2 < 1 || mvOccupied->DoorsPermitLightBlinking < 1 || mvOccupied->DoorsPermitLightBlinking < 2 && DynamicObject->Mechanik->IsAnyDoorOpen[(cab_to_end() == end::front ? side::left : side::right)] || (mvOccupied->DoorsPermitLightBlinking < 3 && DynamicObject->Mechanik->IsAnyDoorOnlyOpen[(cab_to_end() == end::front ? side::left : side::right)])); m_doorspermitright = mvOccupied->Doors.instances[(cab_to_end() == end::front ? side::right : side::left)].open_permit && (simulation::Time.data().wSecond % 2 < 1 || mvOccupied->DoorsPermitLightBlinking < 1 || mvOccupied->DoorsPermitLightBlinking < 2 && DynamicObject->Mechanik->IsAnyDoorOpen[(cab_to_end() == end::front ? side::right : side::left)] || (mvOccupied->DoorsPermitLightBlinking < 3 && DynamicObject->Mechanik->IsAnyDoorOnlyOpen[(cab_to_end() == end::front ? side::right : side::left)])); } m_dirforward = mvControlled->DirActive > 0; m_dirneutral = mvControlled->DirActive == 0; m_dirbackward = mvControlled->DirActive < 0; // check for received user commands // NOTE: this is a temporary arrangement, for the transition period from old command setup to the new one // eventually commands are going to be retrieved directly by the vehicle, filtered through active control stand // and ultimately executed, provided the stand allows it. command_data commanddata; while (simulation::Commands.pop(commanddata, static_cast(command_target::vehicle) | id())) { auto lookup = m_commandhandlers.find(commanddata.command); if (lookup != m_commandhandlers.end()) { // debug data if (commanddata.action == GLFW_PRESS) { WriteLog(mvOccupied->Name + " received command: [" + simulation::Commands_descriptions[static_cast(commanddata.command)].name + "]"); } // pass the command to the assigned handler lookup->second(this, commanddata); } } UpdateCab(); if (DynamicObject->Mechanik != nullptr && false == DynamicObject->Mechanik->AIControllFlag) { // nie blokujemy AI if (mvOccupied->TrainType == dt_ET40 || mvOccupied->TrainType == dt_EP05 || mvOccupied->HasCamshaft) { // dla ET40 i EU05 automatyczne cofanie nastawnika - i tak nie będzie to działać dobrze... // TODO: use deltatime to stabilize speed /* if( false == ( ( input::command == user_command::mastercontrollerset ) || ( input::command == user_command::mastercontrollerincrease ) || ( input::command == user_command::mastercontrollerdecrease ) ) ) { */ if (false == (m_mastercontrollerinuse || Global.ctrlState)) { m_mastercontrollerreturndelay -= Deltatime; if (m_mastercontrollerreturndelay < 0.f) { m_mastercontrollerreturndelay = EU07_CONTROLLER_BASERETURNDELAY; if (mvOccupied->MainCtrlPos > mvOccupied->MainCtrlActualPos) { mvOccupied->DecMainCtrl(1); } else if (mvOccupied->MainCtrlPos < mvOccupied->MainCtrlActualPos) { // Ra 15-01: a to nie miało być tylko cofanie? mvOccupied->IncMainCtrl(1); } } } } } // McZapkie: predkosc wyswietlana na tachometrze brana jest z obrotow kol auto const maxtacho{3.0}; double maxSpeed = mvControlled->Vmax * 1.05; // zachowanie starej logiki jak nie ma definicji max tarczki if (mvOccupied->maxTachoSpeed != 0) { maxSpeed = mvOccupied->maxTachoSpeed; } fTachoVelocity = static_cast(std::min(std::abs(11.31 * mvControlled->WheelDiameter * mvControlled->nrot), maxSpeed)); { // skacze osobna zmienna float ff = simulation::Time.data().wSecond; // skacze co sekunde - pol sekundy // pomiar, pol sekundy ustawienie if (ff != fTachoTimer) // jesli w tej sekundzie nie zmienial { if (fTachoVelocity >= 5) // jedzie fTachoVelocityJump = fTachoVelocity + (2.0 - LocalRandom(3) + LocalRandom(3)) * 0.5; else if (fTachoVelocity < 5 && fTachoVelocity > 1) fTachoVelocityJump = Random(0, 4); // tu ma sie bujac jak wariat i zatrzymac na jakiejs predkosci // fTachoVelocityJump = 0; // stoi fTachoTimer = ff; // juz zmienil } } if (fTachoVelocity > 1) // McZapkie-270503: podkrecanie tachometru { // szybciej zacznij stukac fTachoCount = std::min(maxtacho, fTachoCount + Deltatime * 3); } else if (fTachoCount > 0) { // schodz powoli - niektore haslery to ze 4 sekundy potrafia stukac fTachoCount = std::max(0.0, fTachoCount - Deltatime * 0.66); } // Ra 2014-09: napięcia i prądy muszą być ustalone najpierw, bo wysyłane są ewentualnie na PoKeys if (mvControlled->EngineType != TEngineType::DieselElectric && mvControlled->EngineType != TEngineType::ElectricInductionMotor) { // Ra 2014-09: czy taki rozdzia? ma sens? fHVoltage = std::max(mvControlled->PantographVoltage, mvControlled->GetTrainsetHighVoltage()); // Winger czy to nie jest zle? } // *mvControlled->Mains); else { fHVoltage = mvControlled->EngineVoltage; } if (ShowNextCurrent) { // jeśli pokazywać drugi człon if (mvSecond) { // o ile jest ten drugi fHCurrent[0] = mvSecond->ShowCurrent(0) * 1.05; fHCurrent[1] = mvSecond->ShowCurrent(1) * 1.05; fHCurrent[2] = mvSecond->ShowCurrent(2) * 1.05; fHCurrent[3] = mvSecond->ShowCurrent(3) * 1.05; } else fHCurrent[0] = fHCurrent[1] = fHCurrent[2] = fHCurrent[3] = 0.0; // gdy nie ma człona } else { // normalne pokazywanie fHCurrent[0] = mvControlled->ShowCurrent(0); fHCurrent[1] = mvControlled->ShowCurrent(1); fHCurrent[2] = mvControlled->ShowCurrent(2); fHCurrent[3] = mvControlled->ShowCurrent(3); } bool kier = DynamicObject->DirectionGet() * mvOccupied->CabOccupied > 0; TDynamicObject *p = DynamicObject->GetFirstDynamic(mvOccupied->CabOccupied < 0 ? end::rear : end::front, 4); int in = 0; fEIMParams[0][6] = 0; iCarNo = 0; iPowerNo = 0; iUnitNo = 1; for (int i = 0; i < 8; i++) { bMains[i] = false; fCntVol[i] = 0.0f; bPants[i][0] = false; bPants[i][1] = false; bFuse[i] = false; bBatt[i] = false; bConv[i] = false; bComp[i][0] = false; bComp[i][1] = false; // bComp[i][2] = false; // bComp[i][3] = false; bHeat[i] = false; } bCompressors.clear(); for (int i = 0; i < 20; i++) { if (p) { fPress[i][0] = p->MoverParameters->BrakePress; fPress[i][1] = p->MoverParameters->PipePress; fPress[i][2] = p->MoverParameters->ScndPipePress; fPress[i][3] = p->MoverParameters->CntrlPipePress; fPress[i][4] = p->MoverParameters->Hamulec->GetBRP(); fPress[i][5] = (p->MoverParameters->TotalMass - p->MoverParameters->Mred) * 0.001; fPress[i][6] = p->MoverParameters->SpringBrake.SBP; bBrakes[i][0] = p->MoverParameters->SpringBrake.IsActive; bBrakes[i][1] = p->MoverParameters->SpringBrake.ShuttOff; bDoors[i][1] = p->MoverParameters->Doors.instances[side::left].position > 0.f; bDoors[i][2] = p->MoverParameters->Doors.instances[side::right].position > 0.f; bDoors[i][3] = p->MoverParameters->Doors.instances[side::left].step_position > 0.f; bDoors[i][4] = p->MoverParameters->Doors.instances[side::right].step_position > 0.f; bDoors[i][0] = bDoors[i][1] || bDoors[i][2]; iDoorNo[i] = p->iAnimType[ANIM_DOORS]; iUnits[i] = iUnitNo; cCode[i] = p->MoverParameters->TypeName[p->MoverParameters->TypeName.length() - 1]; asCarName[i] = p->name(); if (p->MoverParameters->EnginePowerSource.SourceType == TPowerSource::CurrentCollector) { bPants[iUnitNo - 1][end::front] = bPants[iUnitNo - 1][end::front] || p->MoverParameters->Pantographs[end::front].is_active; bPants[iUnitNo - 1][end::rear] = bPants[iUnitNo - 1][end::rear] || p->MoverParameters->Pantographs[end::rear].is_active; } // TBD, TODO: clean up compressor data arrangement? if (iUnitNo <= 8) { bComp[iUnitNo - 1][0] = bComp[iUnitNo - 1][0] || p->MoverParameters->CompressorAllow || p->MoverParameters->CompressorStart == start_t::automatic; } if (p->MoverParameters->CompressorSpeed > 0.00001) { if (iUnitNo <= 8) { bComp[iUnitNo - 1][1] = bComp[iUnitNo - 1][1] || p->MoverParameters->CompressorFlag; } bCompressors.emplace_back(p->MoverParameters->CompressorAllow || p->MoverParameters->CompressorStart == start_t::automatic, p->MoverParameters->CompressorFlag, i); } bSlip[i] = p->MoverParameters->SlippingWheels; if (in < 8 && p->MoverParameters->eimc[eimc_p_Pmax] > 1) { fEIMParams[1 + in][0] = p->MoverParameters->eimv[eimv_Fmax]; fEIMParams[1 + in][1] = std::max(fEIMParams[1 + in][0], 0.f); fEIMParams[1 + in][2] = -std::min(fEIMParams[1 + in][0], 0.f); fEIMParams[1 + in][3] = p->MoverParameters->eimv[eimv_Fmax] / std::max(p->MoverParameters->eimv[eimv_Fful], 1.); fEIMParams[1 + in][4] = std::max(fEIMParams[1 + in][3], 0.f); fEIMParams[1 + in][5] = -std::min(fEIMParams[1 + in][3], 0.f); fEIMParams[1 + in][6] = p->MoverParameters->eimv[eimv_If]; fEIMParams[1 + in][7] = p->MoverParameters->eimv[eimv_U]; fEIMParams[1 + in][8] = p->MoverParameters->Itot; // p->MoverParameters->eimv[eimv_Ipoj]; fEIMParams[1 + in][9] = p->MoverParameters->EngineVoltage; fEIMParams[0][6] += fEIMParams[1 + in][8]; bMains[in] = p->MoverParameters->Mains; fCntVol[in] = p->MoverParameters->BatteryVoltage; bFuse[in] = p->MoverParameters->FuseFlag; bBatt[in] = p->MoverParameters->Battery; bConv[in] = p->MoverParameters->ConverterFlag; bHeat[in] = p->MoverParameters->Heating; // bComp[in][2] = (p->MoverParameters->CompressorAllow || (p->MoverParameters->CompressorStart == start_t::automatic)); // bComp[in][3] = (p->MoverParameters->CompressorFlag); in++; iPowerNo = in; } if (in < 8 && (p->MoverParameters->EngineType == TEngineType::DieselEngine || p->MoverParameters->EngineType == TEngineType::DieselElectric)) { fDieselParams[1 + in][0] = p->MoverParameters->enrot * 60; fDieselParams[1 + in][1] = p->MoverParameters->nrot; fDieselParams[1 + in][2] = p->MoverParameters->RList[p->MoverParameters->MainCtrlPos].R; fDieselParams[1 + in][3] = p->MoverParameters->dizel_fill; fDieselParams[1 + in][4] = p->MoverParameters->RList[p->MoverParameters->MainCtrlPos].Mn; fDieselParams[1 + in][5] = p->MoverParameters->dizel_engage; fDieselParams[1 + in][6] = p->MoverParameters->dizel_heat.Twy; fDieselParams[1 + in][7] = p->MoverParameters->OilPump.pressure; fDieselParams[1 + in][8] = p->MoverParameters->dizel_heat.Ts; fDieselParams[1 + in][9] = p->MoverParameters->hydro_R_Fill; bMains[in] = p->MoverParameters->Mains; fCntVol[in] = p->MoverParameters->BatteryVoltage; bFuse[in] = p->MoverParameters->FuseFlag; bBatt[in] = p->MoverParameters->Battery; bConv[in] = p->MoverParameters->ConverterFlag; bHeat[in] = p->MoverParameters->Heating; in++; iPowerNo = in; } if ((kier ? p->Next(coupling::permanent) : p->Prev(coupling::permanent)) != (kier ? p->Next(coupling::control) : p->Prev(coupling::control))) iUnitNo++; p = kier ? p->Next(coupling::control) : p->Prev(coupling::control); iCarNo = i + 1; } else { fPress[i][0] = fPress[i][1] = fPress[i][2] = fPress[i][3] = fPress[i][4] = fPress[i][5] = 0; bDoors[i][0] = bDoors[i][1] = bDoors[i][2] = bDoors[i][3] = bDoors[i][4] = false; bBrakes[i][0] = bBrakes[i][1] = false; bSlip[i] = false; iUnits[i] = 0; cCode[i] = 0; //'0'; asCarName[i] = ""; } } // if (mvControlled == mvOccupied) // fEIMParams[0][3] = mvControlled->eimv[eimv_Fzad]; // procent zadany // else // fEIMParams[0][3] = // mvControlled->eimv[eimv_Fzad] - mvOccupied->LocalBrakeRatio(); // procent zadany fEIMParams[0][3] = mvOccupied->eimic_real; fEIMParams[0][4] = std::max(fEIMParams[0][3], 0.f); fEIMParams[0][5] = -std::min(fEIMParams[0][3], 0.f); fEIMParams[0][1] = fEIMParams[0][4] * mvControlled->eimv[eimv_Fful]; fEIMParams[0][2] = fEIMParams[0][5] * mvControlled->eimv[eimv_Fful]; fEIMParams[0][0] = fEIMParams[0][1] - fEIMParams[0][2]; fEIMParams[0][7] = 0; fEIMParams[0][8] = 0; fEIMParams[0][9] = 0; for (int i = in; i < 8; i++) { for (int j = 0; j <= 9; j++) { fEIMParams[1 + i][j] = 0; fDieselParams[1 + i][j] = 0; } } #ifdef _WIN32 if (Global.iFeedbackMode == 4) { // wykonywać tylko gdy wyprowadzone na pulpit // Ra: sterowanie miernikiem: zbiornik główny Console::ValueSet(0, mvOccupied->Compressor); // Ra: sterowanie miernikiem: przewód główny Console::ValueSet(1, mvOccupied->PipePress); // Ra: sterowanie miernikiem: cylinder hamulcowy Console::ValueSet(2, mvOccupied->BrakePress); // woltomierz wysokiego napięcia Console::ValueSet(3, fHVoltage); // Ra: sterowanie miernikiem: drugi amperomierz Console::ValueSet(4, fHCurrent[2]); // pierwszy amperomierz; dla EZT prąd całkowity Console::ValueSet(5, fHCurrent[mvControlled->TrainType & dt_EZT ? 0 : 1]); // Ra: prędkość na pin 43 - wyjście analogowe (to nie jest PWM); skakanie zapewnia mechanika napędu Console::ValueSet(6, fTachoVelocity); } #endif //------------------ // hunter-261211: nadmiarowy przetwornicy i ogrzewania // Ra 15-01: to musi stąd wylecieć - zależności nie mogą być w kabinie if (mvControlled->ConverterFlag == true) { fConverterTimer += Deltatime; if (mvControlled->CompressorFlag == true && mvControlled->CompressorPower == 1 && (mvControlled->EngineType == TEngineType::ElectricSeriesMotor || mvControlled->TrainType == dt_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) { mvControlled->ConvOvldFlag = true; if (mvControlled->TrainType != dt_EZT) mvControlled->MainSwitch(false, mvControlled->TrainType == dt_EZT ? range_t::unit : range_t::local); } else if (fConverterTimer >= fConverterPrzekaznik) { // changed switch from always true to take into account state of the compressor switch mvControlled->CompressorSwitch(mvControlled->CompressorAllow); } } } else fConverterTimer = 0; //------------------ auto const lowvoltagepower{mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable}; // youBy - prad w drugim czlonie: galaz lub calosc { TDynamicObject *tmp{nullptr}; if (DynamicObject->NextConnected()) if (TestFlag(mvControlled->Couplers[end::rear].CouplingFlag, coupling::control) && mvOccupied->CabOccupied == 1) tmp = DynamicObject->NextConnected(); if (DynamicObject->PrevConnected()) if (TestFlag(mvControlled->Couplers[end::front].CouplingFlag, coupling::control) && mvOccupied->CabOccupied == -1) tmp = DynamicObject->PrevConnected(); if (tmp) { if (tmp->MoverParameters->Power > 0) { if (ggI1B.SubModel) { ggI1B.UpdateValue(tmp->MoverParameters->ShowCurrent(1)); ggI1B.Update(); } if (ggI2B.SubModel) { ggI2B.UpdateValue(tmp->MoverParameters->ShowCurrent(2)); ggI2B.Update(); } if (ggI3B.SubModel) { ggI3B.UpdateValue(tmp->MoverParameters->ShowCurrent(3)); ggI3B.Update(); } if (ggItotalB.SubModel) { ggItotalB.UpdateValue(tmp->MoverParameters->ShowCurrent(0)); ggItotalB.Update(); } if (ggWater1TempB.SubModel) { ggWater1TempB.UpdateValue(tmp->MoverParameters->dizel_heat.temperatura1); ggWater1TempB.Update(); } if (ggOilPressB.SubModel) { ggOilPressB.UpdateValue(tmp->MoverParameters->OilPump.pressure); ggOilPressB.Update(); } } } } // McZapkie-300302: zegarek if (ggClockMInd.SubModel) { ggClockSInd.UpdateValue(simulation::Time.data().wSecond); ggClockSInd.Update(); ggClockMInd.UpdateValue(simulation::Time.data().wMinute); ggClockMInd.Update(); ggClockHInd.UpdateValue(simulation::Time.data().wHour + simulation::Time.data().wMinute / 60.0); ggClockHInd.Update(); } Cabine[iCabn].Update(lowvoltagepower); // nowy sposób ustawienia animacji /* if (ggZbS.SubModel) { ggZbS.UpdateValue(mvOccupied->Handle->GetCP()); ggZbS.Update(); } */ // replacement for the above. TODO: move it to a more suitable place m_brakehandlecp = mvOccupied->Handle->GetCP(); // youBy - napiecie na silnikach if (ggEngineVoltage.SubModel) { if (mvControlled->DynamicBrakeFlag) { ggEngineVoltage.UpdateValue(std::abs(mvControlled->Im * 5)); } else { int x; if (mvControlled->TrainType == dt_ET42 && mvControlled->Imax == mvControlled->ImaxHi) x = 1; else x = 2; if (mvControlled->RList[mvControlled->MainCtrlActualPos].Mn > 0 && std::abs(mvControlled->Im) > 0) { ggEngineVoltage.UpdateValue(x * (std::abs(mvControlled->EngineVoltage) - mvControlled->RList[mvControlled->MainCtrlActualPos].R * std::abs(mvControlled->Im)) / mvControlled->RList[mvControlled->MainCtrlActualPos].Mn); } else { ggEngineVoltage.UpdateValue(0); } } ggEngineVoltage.Update(); } // Winger 140404 - woltomierz NN if (ggLVoltage.SubModel) { // NOTE: since we don't have functional converter object, we're faking it here by simple check whether converter is on // TODO: implement object-based circuits and power systems model so we can have this working more properly ggLVoltage.UpdateValue(std::max(mvOccupied->Power110vIsAvailable ? mvOccupied->NominalBatteryVoltage : 0.0, mvOccupied->Power24vIsAvailable ? mvOccupied->BatteryVoltage : 0.0)); ggLVoltage.Update(); } if (mvControlled->EngineType == TEngineType::DieselElectric) { // ustawienie zmiennych dla silnika spalinowego fEngine[1] = mvControlled->ShowEngineRotation(1); fEngine[2] = mvControlled->ShowEngineRotation(2); } else if (mvControlled->EngineType == TEngineType::DieselEngine) { // albo dla innego spalinowego fEngine[1] = mvControlled->ShowEngineRotation(1); fEngine[2] = mvControlled->ShowEngineRotation(2); fEngine[3] = mvControlled->ShowEngineRotation(3); if (ggMainGearStatus.SubModel) { if (mvControlled->Mains) ggMainGearStatus.UpdateValue(1.1 - std::abs(mvControlled->dizel_automaticgearstatus)); else ggMainGearStatus.UpdateValue(0.0); ggMainGearStatus.Update(); } if (ggIgnitionKey.SubModel && ggIgnitionKey.GetDesiredValue() == 0.0) { ggIgnitionKey.UpdateValue(mvControlled->Mains || mvControlled->dizel_startup || fMainRelayTimer > 0.f || (ggMainButton.SubModel != nullptr && ggMainButton.GetDesiredValue() > 0.95) || (ggMainOnButton.SubModel != nullptr && ggMainOnButton.GetDesiredValue() > 0.95)); } ggIgnitionKey.Update(); } if (mvControlled->SlippingWheels) { // Ra 2014-12: lokomotywy 181/182 dostają SlippingWheels po zahamowaniu powyżej 2.85 bara i buczały double veldiff = (DynamicObject->GetVelocity() - fTachoVelocity) / mvControlled->Vmax; if (veldiff < -0.01) { // 1% Vmax rezerwy, żeby 181/182 nie buczały po zahamowaniu, ale to proteza if (std::abs(mvControlled->Im) > 10.0) { btLampkaPoslizg.Turn(true); } } } else { btLampkaPoslizg.Turn(false); } // Lampka pracujacej sprezacki if (mvControlled->CompressorFlag || mvOccupied->CompressorFlag) btCompressors.Turn(true); else btCompressors.Turn(false); // Lampka zezwolenia na hamowanie ED if (mvControlled->EpFuse) btEDenabled.Turn(true); else btEDenabled.Turn(false); // Lampka aktywowanej kabiny if (mvControlled->CabActive != 0) { btCabActived.Turn(true); } else { btCabActived.Turn(false); } if (mvControlled->Battery && mvControlled->CabActive != 0) btAKLVents.Turn(true); else btAKLVents.Turn(false); if (true == lowvoltagepower) { // McZapkie-141102: SHP i czuwak, TODO: sygnalizacja kabinowa if (mvOccupied->SecuritySystem.is_vigilance_blinking()) { if (fBlinkTimer > fCzuwakBlink) fBlinkTimer = -fCzuwakBlink; else fBlinkTimer += Deltatime; btLampkaCzuwaka.Turn(fBlinkTimer > 0); } else { fBlinkTimer = 0.0; btLampkaCzuwaka.Turn(false); } btLampkaSHP.Turn(mvOccupied->SecuritySystem.is_cabsignal_blinking()); btLampkaCzuwakaSHP.Turn(btLampkaSHP.GetValue() || btLampkaCzuwaka.GetValue()); btLampkaWylSzybki.Turn(m_linebreakerstate == 2 || mvControlled->Mains); btLampkaWylSzybkiOff.Turn(m_linebreakerstate != 2 && !mvControlled->Mains); btLampkaMainBreakerReady.Turn(mvControlled->MainsInitTimeCountdown <= 0.0 && m_linebreakerstate != 2 && !mvControlled->Mains); btLampkaMainBreakerBlinkingIfReady.Turn(m_linebreakerstate == 2 || mvControlled->Mains || mvControlled->MainsInitTimeCountdown < 0.0 && simulation::Time.data().wMilliseconds > 500); btLampkaPrzetw.Turn(mvOccupied->Power110vIsAvailable); btLampkaPrzetwOff.Turn(!mvOccupied->Power110vIsAvailable); btLampkaNadmPrzetw.Turn(Dynamic()->Mechanik ? Dynamic()->Mechanik->IsAnyConverterOverloadRelayOpen : mvControlled->ConvOvldFlag); btLampkaOpory.Turn(mvControlled->StLinFlag && mvControlled->ResistorsFlagCheck()); btLampkaBezoporowa.Turn(mvControlled->ResistorsFlagCheck() || mvControlled->MainCtrlActualPos == 0); // do EU04 btLampkaStyczn.Turn(!mvControlled->StLinFlag && !mvControlled->ControlPressureSwitch && mvControlled->BrakePress < 1.0); // mozna prowadzic rozruch btLampkaPrzekRozn.Turn(!mvControlled->GroundRelay && !mvControlled->ControlPressureSwitch && mvControlled->BrakePress < 1.0); // relay is off and needs a reset btLampkaNadmSil.Turn(mvControlled->FuseFlagCheck() && !mvControlled->ControlPressureSwitch && mvControlled->BrakePress < 1.0); // relay is off and needs a reset btLampkaUkrotnienie.Turn(TestFlag(mvControlled->Couplers[mvControlled->CabOccupied == 1 ? rear : front].CouplingFlag, control)); btLampkaHamPosp.Turn(TestFlag(mvOccupied->Hamulec->GetBrakeStatus(), 1)); // lampka drugiego stopnia hamowania // TODO: youBy wyciągnąć flagę wysokiego stopnia btLampkaNadmWent.Turn(mvControlled->RventRot < 5.0 && mvControlled->ResistorsFlagCheck()); // hunter-121211: lampka zanikowo-pradowego wentylatorow btLampkaWysRozr.Turn(mvControlled->Imax >= mvControlled->ImaxHi); btLampkaNapNastHam.Turn(mvControlled->DirActive != 0); // napiecie na nastawniku hamulcowym btLampkaSprezarka.Turn(mvControlled->CompressorFlag); // mutopsitka dziala btLampkaSprezarkaOff.Turn(!mvControlled->CompressorFlag); btLampkaFuelPumpOff.Turn(!mvControlled->FuelPump.is_active); // boczniki // Ra: w SU45 boczniki wchodzą na ScndCtrlPos, a nie na ScndCtrlActualPos // - pokićkał ktoś? int scp = mvControlled->RList[mvControlled->MainCtrlPos].ScndAct; scp = scp == 255 ? 0 : scp; // Ra: whatta hella is this? btLampkaBoczniki.Turn(!mvControlled->DelayCtrlFlag && mvControlled->ScndCtrlActualPos > 0 || scp > 0); btLampkaBocznik1.Turn(!mvControlled->DelayCtrlFlag && mvControlled->ScndCtrlPos > 0 || mvControlled->ScndCtrlActualPos > 0 || scp > 0); btLampkaBocznik2.Turn(!mvControlled->DelayCtrlFlag && mvControlled->ScndCtrlPos > 1 || mvControlled->ScndCtrlActualPos > 1 || scp > 1); btLampkaBocznik3.Turn(!mvControlled->DelayCtrlFlag && mvControlled->ScndCtrlPos > 2 || mvControlled->ScndCtrlActualPos > 2 || scp > 2); btLampkaBocznik4.Turn(!mvControlled->DelayCtrlFlag && mvControlled->ScndCtrlPos > 3 || mvControlled->ScndCtrlActualPos > 3 || scp > 3); // biegi dla motoraka btLampkaGear1.Turn(mvControlled->ScndCtrlActualPos == 1 && mvControlled->dizel_engagestate > 0); // bieg 1 btLampkaGear2.Turn(mvControlled->ScndCtrlActualPos == 2 && mvControlled->dizel_engagestate > 0); // bieg 2 btLampkaHydroLockup.Turn(mvControlled->hydro_TC_Lockup); // zblokowanie skrzyni z silnikiem if (mvControlled->Signalling == true) { if (mvOccupied->BrakePress >= 1.45f) { btLampkaHamowanie1zes.Turn(true); } if (mvControlled->BrakePress < 0.75f) { btLampkaHamowanie1zes.Turn(false); } } else { btLampkaHamowanie1zes.Turn(false); } switch (mvControlled->TrainType) { // zależnie od typu lokomotywy case dt_EZT: { btLampkaHamienie.Turn(mvControlled->BrakePress >= 0.2 && mvControlled->Signalling); break; } case dt_ET41: { // odhamowanie drugiego członu if (mvSecond) { // bo może komuś przyjść do głowy jeżdżenie jednym członem btLampkaHamienie.Turn(mvSecond->BrakePress < 0.4); } break; } default: { btLampkaHamienie.Turn(mvOccupied->BrakePress >= 0.1 || mvControlled->DynamicBrakeFlag); btLampkaBrakingOff.Turn(mvOccupied->BrakePress < 0.1 && !mvControlled->DynamicBrakeFlag); break; } } // KURS90 btLampkaMaxSila.Turn(abs(mvControlled->Im) >= 350); btLampkaPrzekrMaxSila.Turn(abs(mvControlled->Im) >= 450); btLampkaRadio.Turn(mvOccupied->Radio); btLampkaRadioMessage.Turn(radio_message_played); btLampkaRadioStop.Turn(mvOccupied->Radio && mvOccupied->RadioStopFlag); btLampkaHamulecReczny.Turn(mvOccupied->ManualBrakePos > 0); // NBMX wrzesien 2003 - drzwi oraz sygnał odjazdu if (DynamicObject->Mechanik != nullptr) { btLampkaDoorLeft.Turn(DynamicObject->Mechanik->IsAnyDoorOpen[(cab_to_end() == end::front ? side::left : side::right)]); btLampkaDoorRight.Turn(DynamicObject->Mechanik->IsAnyDoorOpen[(cab_to_end() == end::front ? side::right : side::left)]); } btLampkaBlokadaDrzwi.Turn(mvOccupied->Doors.is_locked); btLampkaDoorLockOff.Turn(false == mvOccupied->Doors.lock_enabled); btLampkaDepartureSignal.Turn(mvControlled->DepartureSignal); btLampkaNapNastHam.Turn(mvControlled->DirActive != 0 && mvOccupied->EpFuse); // napiecie na nastawniku hamulcowym // Wylaczanie lampek kierunku gdy jedziemy // Feature uruchamiany z fiz z sekcji Ctrl. wpisem HideDirStatusWhenMoving=Yes (domyslnie No) if (mvOccupied->HideDirStatusWhenMoving && // Czy ta funkcja jest w ogole wlaczona mvOccupied->Vel > mvOccupied->HideDirStatusSpeed) // Uzaleznienie od predkosci { btLampkaForward.Turn(false); btLampkaBackward.Turn(false); btLampkaNeutral.Turn(false); } else { btLampkaForward.Turn(mvControlled->DirActive > 0); // jazda do przodu btLampkaBackward.Turn(mvControlled->DirActive < 0); // jazda do tyłu btLampkaNeutral.Turn(mvControlled->DirActive == 0); // kierunek neutral } btLampkaED.Turn(mvControlled->DynamicBrakeFlag); // hamulec ED btLampkaBrakeProfileG.Turn(TestFlag(mvOccupied->BrakeDelayFlag, bdelay_G)); btLampkaBrakeProfileP.Turn(TestFlag(mvOccupied->BrakeDelayFlag, bdelay_P)); btLampkaBrakeProfileR.Turn(TestFlag(mvOccupied->BrakeDelayFlag, bdelay_R)); btLampkaSpringBrakeActive.Turn(mvOccupied->SpringBrake.IsActive); btLampkaSpringBrakeInactive.Turn(!mvOccupied->SpringBrake.IsActive); // light indicators // NOTE: sides are hardcoded to deal with setups where single cab is equipped with all indicators btLampkaUpperLight.Turn((mvOccupied->iLights[end::front] & light::headlight_upper) != 0); btLampkaLeftLight.Turn((mvOccupied->iLights[end::front] & light::headlight_left) != 0); btLampkaRightLight.Turn((mvOccupied->iLights[end::front] & light::headlight_right) != 0); btLampkaLeftEndLight.Turn((mvOccupied->iLights[end::front] & light::redmarker_left) != 0); btLampkaRightEndLight.Turn((mvOccupied->iLights[end::front] & light::redmarker_right) != 0); btLampkaRearUpperLight.Turn((mvOccupied->iLights[end::rear] & light::headlight_upper) != 0); btLampkaRearLeftLight.Turn((mvOccupied->iLights[end::rear] & light::headlight_left) != 0); btLampkaRearRightLight.Turn((mvOccupied->iLights[end::rear] & light::headlight_right) != 0); btLampkaRearLeftEndLight.Turn((mvOccupied->iLights[end::rear] & light::redmarker_left) != 0); btLampkaRearRightEndLight.Turn((mvOccupied->iLights[end::rear] & light::redmarker_right) != 0); // others btLampkaMalfunction.Turn(mvControlled->dizel_heat.PA); // overheat indicator lamps btLampkaOilOverheat.Turn(mvControlled->dizel_heat.oil.is_hot); btLampkaWaterOverheat.Turn(mvControlled->dizel_heat.water.is_hot); btLampkaWaterAuxOverheat.Turn(mvControlled->dizel_heat.water_aux.is_hot); btLampkaEngineOverheat.Turn(mvControlled->dizel_heat.engine_is_hot); btLampkaMotorBlowers.Turn(mvControlled->MotorBlowers[end::front].is_active && mvControlled->MotorBlowers[end::rear].is_active); btLampkaCoolingFans.Turn(mvControlled->RventRot > 1.0); btLampkaTempomat.Turn(mvOccupied->SpeedCtrlUnit.IsActive); btLampkaDistanceCounter.Turn(m_distancecounter >= 0.f); // universal devices state indicators for (auto idx = 0; idx < btUniversals.size(); ++idx) { btUniversals[idx].Turn(ggUniversals[idx].GetValue() > 0.5); } } else { // wylaczone btLampkaCzuwaka.Turn(false); btLampkaSHP.Turn(false); btLampkaCzuwakaSHP.Turn(false); btLampkaWylSzybki.Turn(false); btLampkaWylSzybkiOff.Turn(false); btLampkaMainBreakerReady.Turn(false); btLampkaMainBreakerBlinkingIfReady.Turn(false); btLampkaWysRozr.Turn(false); btLampkaOpory.Turn(false); btLampkaStyczn.Turn(false); btLampkaPrzekRozn.Turn(false); btLampkaNadmSil.Turn(false); btLampkaUkrotnienie.Turn(false); btLampkaHamPosp.Turn(false); btLampkaBoczniki.Turn(false); btLampkaBocznik1.Turn(false); btLampkaBocznik2.Turn(false); btLampkaBocznik3.Turn(false); btLampkaBocznik4.Turn(false); btLampkaGear1.Turn(false); btLampkaGear2.Turn(false); btLampkaHydroLockup.Turn(false); btLampkaNapNastHam.Turn(false); btLampkaPrzetw.Turn(false); btLampkaPrzetwOff.Turn(false); btLampkaNadmPrzetw.Turn(false); btLampkaSprezarka.Turn(false); btLampkaSprezarkaOff.Turn(false); btLampkaFuelPumpOff.Turn(false); btLampkaBezoporowa.Turn(false); btLampkaHamowanie1zes.Turn(false); btLampkaHamienie.Turn(false); btLampkaBrakingOff.Turn(false); btLampkaBrakeProfileG.Turn(false); btLampkaBrakeProfileP.Turn(false); btLampkaBrakeProfileR.Turn(false); btLampkaSpringBrakeActive.Turn(false); btLampkaSpringBrakeInactive.Turn(false); // overheat indicator lamps off btLampkaOilOverheat.Turn(false); btLampkaWaterOverheat.Turn(false); btLampkaWaterAuxOverheat.Turn(false); btLampkaEngineOverheat.Turn(false); btLampkaMaxSila.Turn(false); btLampkaPrzekrMaxSila.Turn(false); btLampkaRadio.Turn(false); btLampkaRadioMessage.Turn(false); btLampkaRadioStop.Turn(false); btLampkaHamulecReczny.Turn(false); btLampkaDoorLeft.Turn(false); btLampkaDoorRight.Turn(false); btLampkaBlokadaDrzwi.Turn(false); btLampkaDoorLockOff.Turn(false); btLampkaDepartureSignal.Turn(false); btLampkaNapNastHam.Turn(false); btLampkaForward.Turn(false); btLampkaBackward.Turn(false); btLampkaNeutral.Turn(false); btLampkaED.Turn(false); // light indicators btLampkaUpperLight.Turn(false); btLampkaLeftLight.Turn(false); btLampkaRightLight.Turn(false); btLampkaLeftEndLight.Turn(false); btLampkaRightEndLight.Turn(false); btLampkaRearUpperLight.Turn(false); btLampkaRearLeftLight.Turn(false); btLampkaRearRightLight.Turn(false); btLampkaRearLeftEndLight.Turn(false); btLampkaRearRightEndLight.Turn(false); // others btLampkaMalfunction.Turn(false); btLampkaMotorBlowers.Turn(false); btLampkaCoolingFans.Turn(false); btLampkaTempomat.Turn(false); btLampkaDistanceCounter.Turn(false); // universal devices state indicators for (auto &universal : btUniversals) { universal.Turn(false); } } { // yB - wskazniki drugiego czlonu TDynamicObject *tmp{nullptr}; //=mvControlled->mvSecond; //Ra 2014-07: trzeba to jeszcze wyjąć z kabiny... // Ra 2014-07: no nie ma potrzeby szukać tego w każdej klatce if (TestFlag(mvControlled->Couplers[1].CouplingFlag, coupling::control) && mvOccupied->CabOccupied > 0) tmp = DynamicObject->NextConnected(); if (TestFlag(mvControlled->Couplers[0].CouplingFlag, coupling::control) && mvOccupied->CabOccupied < 0) tmp = DynamicObject->PrevConnected(); if (tmp) { if (lowvoltagepower) { auto const *mover{tmp->MoverParameters}; btLampkaWylSzybkiB.Turn(mover->Mains); btLampkaWylSzybkiBOff.Turn(false == mover->Mains /*&& ( mover->MainsInitTimeCountdown <= 0.0 )*/ /*&& ( fHVoltage != 0.0 )*/); btLampkaOporyB.Turn(mover->ResistorsFlagCheck()); btLampkaBezoporowaB.Turn(true == mover->ResistorsFlagCheck() || mover->MainCtrlActualPos == 0); // do EU04 if (mover->StLinFlag || mover->ControlPressureSwitch) { btLampkaStycznB.Turn(false); } else if (mover->BrakePress < 1.0) { btLampkaStycznB.Turn(true); // mozna prowadzic rozruch } // hunter-271211: sygnalizacja poslizgu w pierwszym pojezdzie, gdy wystapi w drugim if (mover->SlippingWheels) { // Ra 2014-12: lokomotywy 181/182 dostają SlippingWheels po zahamowaniu powyżej 2.85 bara i buczały auto const veldiff{(DynamicObject->GetVelocity() - fTachoVelocity) / mvControlled->Vmax}; if (veldiff < -0.01) { // 1% Vmax rezerwy, żeby 181/182 nie buczały po zahamowaniu, ale to proteza auto const lightstate{std::abs(mover->Im) > 10.0}; btLampkaPoslizg.Turn(btLampkaPoslizg.GetValue() || lightstate); } } btLampkaSprezarkaB.Turn(mover->CompressorFlag); // mutopsitka dziala btLampkaSprezarkaBOff.Turn(false == mover->CompressorFlag); if (mvControlled->Signalling == true) { if (mover->BrakePress >= 1.45f) { btLampkaHamowanie2zes.Turn(true); } if (mover->BrakePress < 0.75f) { btLampkaHamowanie2zes.Turn(false); } } else { btLampkaHamowanie2zes.Turn(false); } btLampkaNadmPrzetwB.Turn(mover->ConvOvldFlag); // nadmiarowy przetwornicy? btLampkaPrzetwB.Turn(mover->ConverterFlag); // zalaczenie przetwornicy btLampkaPrzetwBOff.Turn(false == mover->ConverterFlag); btLampkaHVoltageB.Turn(mover->NoVoltRelay && mover->OvervoltageRelay); btLampkaMalfunctionB.Turn(mover->dizel_heat.PA); // motor fuse indicator turns on if the fuse was blown in any unit under control if (mover->Mains) { btLampkaNadmSil.Turn(btLampkaNadmSil.GetValue() || mover->FuseFlagCheck()); } } else // wylaczone { btLampkaWylSzybkiB.Turn(false); btLampkaWylSzybkiBOff.Turn(false); btLampkaOporyB.Turn(false); btLampkaStycznB.Turn(false); btLampkaSprezarkaB.Turn(false); btLampkaSprezarkaBOff.Turn(false); btLampkaBezoporowaB.Turn(false); btLampkaHamowanie2zes.Turn(false); btLampkaNadmPrzetwB.Turn(false); btLampkaPrzetwB.Turn(false); btLampkaPrzetwBOff.Turn(false); btLampkaHVoltageB.Turn(false); btLampkaMalfunctionB.Turn(false); } } } // McZapkie-080602: obroty (albo translacje) regulatorow if (ggJointCtrl.SubModel != nullptr) { // joint master controller moves forward to adjust power and backward to adjust brakes auto const brakerangemultiplier{/* NOTE: scaling disabled as it was conflicting with associating sounds with control positions ( mvControlled->CoupledCtrl ? mvControlled->MainCtrlPosNo + mvControlled->ScndCtrlPosNo : mvControlled->MainCtrlPosNo ) / static_cast(LocalBrakePosNo) */ 1}; // when SplitEDPneumaticBrake is active the negative range of the joint controller // represents the dedicated dynamic-brake lever (DBPN steps), not the pneumatic local brake auto const negativePart{mvControlled->SplitEDPneumaticBrake ? (mvControlled->DynamicBrakeCtrlPos > 0.0 ? mvControlled->DynamicBrakeCtrlPos * mvControlled->DynamicBrakeCtrlPosNo * -1 * brakerangemultiplier : 0.0) : mvOccupied->LocalBrakePosA > 0.0 ? mvOccupied->LocalBrakePosA * LocalBrakePosNo * -1 * brakerangemultiplier : 0.0}; ggJointCtrl.UpdateValue(negativePart < 0.0 ? negativePart : mvControlled->CoupledCtrl ? double(mvControlled->MainCtrlPos + mvControlled->ScndCtrlPos) : double(mvControlled->MainCtrlPos), dsbNastawnikJazdy); ggJointCtrl.Update(); } if (ggMainCtrl.SubModel != nullptr) { #ifdef _WIN32 if (DynamicObject->Mechanik != nullptr && false == DynamicObject->Mechanik->AIControllFlag // nie blokujemy AI && Global.iFeedbackMode == 4 && Global.fCalibrateIn[2][1] != 0.0) { set_master_controller(Console::AnalogCalibrateGet(2) * mvOccupied->MainCtrlPosNo); mvOccupied->eimic_analog = Console::AnalogCalibrateGet(2); } #endif if (mvControlled->CoupledCtrl) { ggMainCtrl.UpdateValue(double(mvControlled->MainCtrlPos + mvControlled->ScndCtrlPos), dsbNastawnikJazdy); } else { ggMainCtrl.UpdateValue(double(mvControlled->MainCtrlPos), dsbNastawnikJazdy); } ggMainCtrl.Update(); } if (ggMainCtrlAct.SubModel != nullptr) { if (mvControlled->CoupledCtrl) ggMainCtrlAct.UpdateValue(double(mvControlled->MainCtrlActualPos + mvControlled->ScndCtrlActualPos)); else ggMainCtrlAct.UpdateValue(double(mvControlled->MainCtrlActualPos)); ggMainCtrlAct.Update(); } if (ggScndCtrl.SubModel != nullptr) { // Ra: od byte odejmowane boolean i konwertowane potem na double? if (false == ggScndCtrl.is_push()) { ggScndCtrl.UpdateValue(double(mvControlled->ScndCtrlPos - (mvControlled->TrainType == dt_ET42 && mvControlled->DynamicBrakeFlag)), dsbNastawnikBocz); } ggScndCtrl.Update(); } if (ggScndCtrlButton.SubModel != nullptr) { if (ggScndCtrlButton.is_toggle()) { ggScndCtrlButton.UpdateValue(mvControlled->ScndCtrlPos > 0 ? 1.f : 0.f, dsbSwitch); } ggScndCtrlButton.Update(lowvoltagepower); } if (ggScndCtrlOffButton.SubModel != nullptr) { ggScndCtrlOffButton.Update(lowvoltagepower); } if (ggDistanceCounterButton.SubModel != nullptr) { ggDistanceCounterButton.Update(); } if (ggDirKey.SubModel != nullptr) { if (mvControlled->TrainType != dt_EZT) { ggDirKey.UpdateValue(double(mvControlled->DirActive), dsbReverserKey); } else { ggDirKey.UpdateValue(double(mvControlled->DirActive) + double(mvControlled->Imin == mvControlled->IminHi), dsbReverserKey); } ggDirKey.Update(); } if (ggBrakeCtrl.SubModel != nullptr) { #ifdef _WIN32 if (DynamicObject->Mechanik ? (DynamicObject->Mechanik->AIControllFlag ? false : Global.iFeedbackMode == 4 /*|| (Global.bMWDmasterEnable && Global.bMWDBreakEnable)*/) : false && Global.fCalibrateIn[0][1] != 0.0) // nie blokujemy AI { // Ra: nie najlepsze miejsce, ale na początek gdzieś to dać trzeba // Firleju: dlatego kasujemy i zastepujemy funkcją w Console if (mvOccupied->BrakeHandle == TBrakeHandle::FV4a) { double b = Console::AnalogCalibrateGet(0); b = b * 8.0 - 2.0; b = std::clamp(b, -2.0, (double)mvOccupied->BrakeCtrlPosNo); // przycięcie zmiennej do granic ggBrakeCtrl.UpdateValue(b); // przesów bez zaokrąglenia mvOccupied->BrakeLevelSet(b); } else if (mvOccupied->BrakeHandle == TBrakeHandle::FVel6) // może można usunąć ograniczenie do FV4a i FVel6? { double b = Console::AnalogCalibrateGet(0); b = b * 7.0 - 1.0; b = std::clamp(b, -1.0, (double)mvOccupied->BrakeCtrlPosNo); // przycięcie zmiennej do granic ggBrakeCtrl.UpdateValue(b); // przesów bez zaokrąglenia mvOccupied->BrakeLevelSet(b); } else { double b = Console::AnalogCalibrateGet(0); b = b * (mvOccupied->Handle->GetPos(bh_MAX) - mvOccupied->Handle->GetPos(bh_MIN)) + mvOccupied->Handle->GetPos(bh_MIN); b = std::clamp(b, mvOccupied->Handle->GetPos(bh_MIN), mvOccupied->Handle->GetPos(bh_MAX)); // przycięcie zmiennej do granic ggBrakeCtrl.UpdateValue(b); // przesów bez zaokrąglenia mvOccupied->BrakeLevelSet(b); } } else #endif { // else //standardowa prodedura z kranem powiązanym z klawiaturą // ggBrakeCtrl.UpdateValue(double(mvOccupied->BrakeCtrlPos)); ggBrakeCtrl.UpdateValue(mvOccupied->fBrakeCtrlPos); ggBrakeCtrl.Update(); } } if (ggLocalBrake.SubModel != nullptr) { #ifdef _WIN32 if (DynamicObject->Mechanik != nullptr && false == DynamicObject->Mechanik->AIControllFlag // nie blokujemy AI && mvOccupied->BrakeLocHandle == TBrakeHandle::FD1 && Global.iFeedbackMode == 4 && Global.fCalibrateIn[0][1] != 0.0) { // Ra: nie najlepsze miejsce, ale na początek gdzieś to dać trzeba // Firleju: dlatego kasujemy i zastepujemy funkcją w Console auto const b = std::clamp(Console::AnalogCalibrateGet(1), 0.f, 1.f); mvOccupied->LocalBrakePosA = b; ggLocalBrake.UpdateValue(b * LocalBrakePosNo); } else #endif { // standardowa prodedura z kranem powiązanym z klawiaturą ggLocalBrake.UpdateValue(mvOccupied->LocalBrakePosA * LocalBrakePosNo); } ggLocalBrake.Update(); } ggDirForwardButton.Update(lowvoltagepower); ggDirNeutralButton.Update(lowvoltagepower); ggDirBackwardButton.Update(lowvoltagepower); ggAlarmChain.Update(); ggBrakeProfileCtrl.Update(); ggBrakeProfileG.Update(); ggBrakeProfileR.Update(); ggBrakeOperationModeCtrl.Update(); ggWiperSw.Update(); ggMaxCurrentCtrl.UpdateValue(true == mvControlled->ShuntModeAllow ? (true == mvControlled->ShuntMode ? 1.f : 0.f) : mvControlled->MotorOverloadRelayHighThreshold ? 1.f : 0.f); ggMaxCurrentCtrl.Update(); // NBMX wrzesien 2003 - drzwi ggDoorLeftPermitButton.Update(lowvoltagepower); ggDoorRightPermitButton.Update(lowvoltagepower); ggDoorPermitPresetButton.Update(lowvoltagepower); ggDoorLeftButton.Update(lowvoltagepower); ggDoorRightButton.Update(lowvoltagepower); ggDoorLeftOnButton.Update(lowvoltagepower); ggDoorRightOnButton.Update(lowvoltagepower); ggDoorLeftOffButton.Update(lowvoltagepower); ggDoorRightOffButton.Update(lowvoltagepower); ggDoorAllOnButton.Update(lowvoltagepower); ggDoorAllOffButton.Update(lowvoltagepower); ggDoorSignallingButton.Update(lowvoltagepower); ggDoorStepButton.Update(lowvoltagepower); // NBMX dzwignia sprezarki ggCompressorButton.Update(); ggCompressorLocalButton.Update(); ggCompressorListButton.Update(); //--------- // hunter-080812: poprawka na ogrzewanie w elektrykach - usuniete uzaleznienie od przetwornicy if (mvControlled->Heating == true && mvControlled->ConvOvldFlag == false) btLampkaOgrzewanieSkladu.Turn(true); else btLampkaOgrzewanieSkladu.Turn(false); //---------- // lights auto const lightpower{(InstrumentLightType == 0 ? mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable : InstrumentLightType == 1 ? mvControlled->Mains : InstrumentLightType == 2 ? mvOccupied->Power110vIsAvailable : InstrumentLightType == 3 ? mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable : InstrumentLightType == 4 ? mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable : false)}; InstrumentLightActive = InstrumentLightType == 3 ? true : // TODO: link the light state with the state of the master key InstrumentLightType == 4 ? mvOccupied->iLights[end::front] != 0 || mvOccupied->iLights[end::rear] != 0 : InstrumentLightActive; btInstrumentLight.Turn(InstrumentLightActive && lightpower); btDashboardLight.Turn(DashboardLightActive && lightpower); btTimetableLight.Turn(TimetableLightActive && lightpower); // guziki: ggMainOffButton.Update(); ggMainOnButton.Update(); ggMainButton.Update(); ggSecurityResetButton.Update(); ggSHPResetButton.Update(); ggReleaserButton.Update(); ggSpringBrakeOnButton.Update(); ggSpringBrakeOffButton.Update(); ggUniveralBrakeButton1.Update(); ggUniveralBrakeButton2.Update(); ggUniveralBrakeButton3.Update(); ggEPFuseButton.Update(); ggAntiSlipButton.Update(); ggSandButton.Update(); ggAutoSandButton.Update(); ggFuseButton.Update(); ggConverterFuseButton.Update(); ggStLinOffButton.Update(); ggRadioChannelSelector.Update(); ggRadioChannelPrevious.Update(); ggRadioChannelNext.Update(); ggRadioStop.Update(); ggRadioTest.Update(); ggRadioCall1.Update(); ggRadioCall3.Update(); ggRadioVolumeSelector.Update(); ggRadioVolumePrevious.Update(); ggRadioVolumeNext.Update(); ggDepartureSignalButton.Update(); /* ggPantFrontButton.Update(); ggPantRearButton.Update(); ggPantFrontButtonOff.Update(); ggPantRearButtonOff.Update(); */ ggPantAllDownButton.Update(); ggPantSelectedDownButton.Update(); ggPantSelectedButton.Update(); ggPantValvesButton.Update(); ggPantCompressorButton.Update(); ggPantCompressorValve.Update(); ggPantValvesOff.Update(); ggPantValvesUpdate.Update(); ggLightsButton.Update(); ggUpperLightButton.Update(); ggLeftLightButton.Update(); ggRightLightButton.Update(); ggLeftEndLightButton.Update(); ggRightEndLightButton.Update(); ggModernLightDimSw.Update(); // hunter-230112 ggRearUpperLightButton.Update(); ggRearLeftLightButton.Update(); ggRearRightLightButton.Update(); ggRearLeftEndLightButton.Update(); ggRearRightEndLightButton.Update(); ggDimHeadlightsButton.Update(); ggDimHeadlightsButton.Update(); //------------ ggConverterButton.Update(); ggConverterLocalButton.Update(); ggConverterOffButton.Update(); ggTrainHeatingButton.Update(); ggSignallingButton.Update(); ggNextCurrentButton.Update(); ggHornButton.Update(); ggHornLowButton.Update(); ggHornHighButton.Update(); ggWhistleButton.Update(); if (DynamicObject->Mechanik != nullptr) { ggHelperButton.UpdateValue(DynamicObject->Mechanik->HelperState); } ggHelperButton.Update(); ggSpeedControlIncreaseButton.Update(lowvoltagepower); ggSpeedControlDecreaseButton.Update(lowvoltagepower); ggSpeedControlPowerIncreaseButton.Update(lowvoltagepower); ggSpeedControlPowerDecreaseButton.Update(lowvoltagepower); for (auto &speedctrlbutton : ggSpeedCtrlButtons) { speedctrlbutton.Update(lowvoltagepower); } for (auto &universal : ggUniversals) { universal.Update(); } for (auto &item : ggInverterEnableButtons) { item.Update(); } for (auto &item : ggInverterDisableButtons) { item.Update(); } for (auto &item : ggInverterToggleButtons) { item.Update(); } for (auto &relayresetbutton : ggRelayResetButtons) { relayresetbutton.Update(); } // hunter-091012 ggInstrumentLightButton.Update(); ggDashboardLightButton.Update(); ggTimetableLightButton.Update(); ggCabLightDimButton.Update(); ggCompartmentLightsButton.Update(); ggCompartmentLightsOnButton.Update(); ggCompartmentLightsOffButton.Update(); ggBatteryButton.Update(); ggBatteryOnButton.Update(); ggBatteryOffButton.Update(); if (ggCabActivationButton.SubModel != nullptr && ggCabActivationButton.type() != TGaugeType::push) { ggCabActivationButton.UpdateValue(mvOccupied->IsCabMaster() ? 1.0 : 0.0); } ggCabActivationButton.Update(); ggWaterPumpBreakerButton.Update(); ggWaterPumpButton.Update(); ggWaterHeaterBreakerButton.Update(); ggWaterHeaterButton.Update(); ggWaterCircuitsLinkButton.Update(); ggFuelPumpButton.Update(); ggOilPumpButton.Update(); ggMotorBlowersFrontButton.Update(); ggMotorBlowersRearButton.Update(); ggMotorBlowersAllOffButton.Update(); // wyprowadzenie sygnałów dla haslera na PoKeys (zaznaczanie na taśmie) btHaslerBrakes.Turn(mvOccupied->BrakePress > 0.4); // ciśnienie w cylindrach btHaslerCurrent.Turn(mvOccupied->Im != 0.0); // prąd na silnikach // calculate current level of interior illumination { // TODO: organize it along with rest of train update in a more sensible arrangement // Ra: uzeleżnic od napięcia w obwodzie sterowania // hunter-091012: uzaleznienie jasnosci od przetwornicy int cabidx{0}; for (auto &cab : Cabine) { auto const cablightlevel = (cab.bLight == false ? 0.f : cab.bLightDim == true ? 0.4f : 1.f) * (mvOccupied->Power110vIsAvailable ? 1.f : 0.5f); if (cab.LightLevel != cablightlevel) { cab.LightLevel = cablightlevel; DynamicObject->set_cab_lights(cabidx, cab.LightLevel); } if (cabidx == iCabn) { DynamicObject->InteriorLightLevel = cablightlevel; } ++cabidx; } } // anti slip system activation, maintained while the control button is down if (mvOccupied->BrakeSystem != TBrakeSystem::ElectroPneumatic) { if (ggAntiSlipButton.GetDesiredValue() > 0.95) { mvControlled->AntiSlippingBrake(); } } // screens if (!FreeFlyModeFlag && simulation::Train == this) // don't bother if we're outside update_screens(Deltatime); // update direction relay if (prevBatState != mvOccupied->Power24vIsAvailable) SetupDirectionRelays(); if (prevDirection != mvOccupied->DirActive) UpdateDirectionRelays(); prevBatState = mvOccupied->Power24vIsAvailable; prevDirection = mvOccupied->DirActive; // sounds update_sounds(Deltatime); return true; //(DynamicObject->Update(dt)); } // koniec update void TTrain::UpdateDirectionRelays() { if (mvOccupied->DirActive < 0 && mvOccupied->Power24vIsAvailable) // wstecz Dynamic()->sDirectionRelayR.play(); if (mvOccupied->DirActive == 0 && mvOccupied->Power24vIsAvailable) // neutral Dynamic()->sDirectionRelayN.play(); if (mvOccupied->DirActive > 0 && mvOccupied->Power24vIsAvailable) // przod Dynamic()->sDirectionRelayD.play(); } void TTrain::SetupDirectionRelays() { if (mvOccupied->Power24vIsAvailable) { if (mvOccupied->DirActive < 0 && mvOccupied->Power24vIsAvailable) // wstecz Dynamic()->sDirectionRelayR.play(); if (mvOccupied->DirActive > 0 && mvOccupied->Power24vIsAvailable) // przod Dynamic()->sDirectionRelayD.play(); } else if (mvOccupied->DirActive != 0) // neutral Dynamic()->sDirectionRelayN.play(); } void TTrain::add_distance(double const Distance) { auto const meterenabled{m_distancecounter >= 0 && (mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable)}; if (true == meterenabled) { m_distancecounter += Distance * Occupied()->CabOccupied; } else { m_distancecounter = -1.f; } } uint16_t TTrain::id() { if (vid == 0) { vid = ++simulation::prev_train_id; WriteLog("net: assigning id " + std::to_string(vid) + " to vehicle " + Dynamic()->name(), logtype::net); } return vid; } void train_table::updateAsync(double dt) { const int threads = std::max(1, Global.trainThreads); const size_t total = m_items.size(); const size_t chunkSize = (total + threads - 1) / threads; std::vector> futures; futures.reserve(threads); for (int i = 0; i < threads; ++i) { const size_t start = i * chunkSize; const size_t end = std::min(start + chunkSize, total); if (start >= end) break; // brak więcej danych futures.emplace_back(std::async(std::launch::async, [this, start, end, dt]() { for (size_t j = start; j < end; ++j) { TTrain *train = m_items[j]; if (train) train->Update(dt); } })); } // Poczekaj aż wszystkie wątki skończą for (auto &f : futures) f.get(); // Teraz kasowanie (tylko w głównym wątku) for (TTrain *train : m_items) { if (!train) continue; if (train->pending_delete) { purge(train->Dynamic()->name()); if (simulation::Train == train) simulation::Train = nullptr; } else if (simulation::Train != train && Global.network_servers.empty() && !Global.network_client) { purge(train->Dynamic()->name()); } } } void train_table::update(double dt) { for (TTrain *train : m_items) { if (!train) continue; train->Update(dt); if (train->pending_delete) { purge(train->Dynamic()->name()); if (simulation::Train == train) simulation::Train = nullptr; } // for single-player destroy non-player trains else if (simulation::Train != train && Global.network_servers.empty() && !Global.network_client) { purge(train->Dynamic()->name()); } } } 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; }