/*
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 "Train.h"

#include "Globals.h"
#include "simulation.h"
#include "Event.h"
#include "simulationtime.h"
#include "Camera.h"
#include "Logs.h"
#include "MdlMngr.h"
#include "Model3d.h"
#include "dumb3d.h"
#include "Timer.h"
#include "Driver.h"
#include "DynObj.h"
#include "mtable.h"
#include "Console.h"
#include "application.h"
#include "renderer.h"
/*
namespace input {

extern user_command command;

}
*/
void
control_mapper::clear() {

    *this = control_mapper();
}

void
control_mapper::insert( TGauge const &Gauge, std::string const &Label ) {

    if( Gauge.SubModel   != nullptr ) { m_controlnames.emplace( Gauge.SubModel, Label ); }
    if( Gauge.SubModelOn != nullptr ) { m_controlnames.emplace( Gauge.SubModelOn, Label ); }

    m_names.emplace( Label );
}

std::string
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() );
}

void TTrain::screen_entry::deserialize( cParser &Input ) {

    while( true == deserialize_mapping( Input ) ) {
        ; // all work done by while()
    }
}

bool TTrain::screen_entry::deserialize_mapping( cParser &Input ) {
    // token can be a key or block end
    auto const key { Input.getToken<std::string>( true, "\n\r\t  ,;[]" ) };

    if( ( true == key.empty() ) || ( key == "}" ) ) { return false; }

    if( key == "{" ) {
        script = Input.getToken<std::string>();
    }
    else if( key == "target:" ) {
        target = Input.getToken<std::string>();
    }
	else if (key == "updatetime:") {
		updatetime = Input.getToken<int>();
	}
    else if( key == "parameters:" ) {
        parameters = dictionary_source( Input.getToken<std::string>() );
    }
    else {
        // HACK: we expect this to be true only if the screen entry doesn't start with a { which means legacy configuration format
        target = key;
        script = Input.getToken<std::string>();
        return false;
    }

    return true;
}

void TCab::Load(cParser &Parser)
{
    // NOTE: clearing control tables here is bit of a crutch, imposed by current scheme of loading compartments anew on each cab change
    ggList.clear();
    btList.clear();

    std::string token;
    Parser.getTokens();
    Parser >> token;
    if (token == "cablight:")
    {
		Parser.getTokens( 9, false );
/*
		Parser
			>> dimm.r
			>> dimm.g
			>> dimm.b
			>> intlit.r
			>> intlit.g
			>> intlit.b
			>> intlitlow.r
			>> intlitlow.g
			>> intlitlow.b;
*/
		Parser.getTokens(); Parser >> token;
    }
	CabPos1.x = std::stod( token );
	Parser.getTokens( 5, false );
	Parser
		>> CabPos1.y
		>> CabPos1.z
		>> CabPos2.x
		>> CabPos2.y
		>> CabPos2.z;

    bEnabled = true;
    bOccupied = true;
}

TGauge &TCab::Gauge(int n)
{ // pobranie adresu obiektu aniomowanego ruchem
/*
    if (n < 0)
    { // rezerwacja wolnego
        ggList[iGauges].Clear();
        return ggList + iGauges++;
    }
    else if (n < iGauges)
        return ggList + n;
    return NULL;
*/
    if( n < 0 ) {
        ggList.emplace_back();
        return ggList.back();
    }
    else {
        return ggList[ n ];
    }
};

TButton &TCab::Button(int n)
{ // pobranie adresu obiektu animowanego wyborem 1 z 2
/*
    if (n < 0)
    { // rezerwacja wolnego
        return btList + iButtons++;
    }
    else if (n < iButtons)
        return btList + n;
    return NULL;
*/
    if( n < 0 ) {
        btList.emplace_back();
        return btList.back();
    }
    else {
        return btList[ n ];
    }
};

void TCab::Update( bool const Power )
{ // odczyt parametrów i ustawienie animacji submodelom
    for( auto &gauge : ggList ) {
        // animacje izometryczne
        gauge.UpdateValue(); // odczyt parametru i przeliczenie na kąt
        gauge.Update(); // ustawienie animacji
    }

    for( auto &button : btList ) {
        // animacje dwustanowe
        button.Update( Power ); // odczyt parametru i wybór submodelu
    }
};

// NOTE: we're currently using universal handlers and static handler map but it may be beneficial to have these implemented on individual class instance basis
// TBD, TODO: consider this approach if we ever want to have customized consist behaviour to received commands, based on the consist/vehicle type or whatever
TTrain::commandhandler_map const TTrain::m_commandhandlers = {

    { user_command::aidriverenable, &TTrain::OnCommand_aidriverenable },
    { user_command::aidriverdisable, &TTrain::OnCommand_aidriverdisable },
    { user_command::jointcontrollerset, &TTrain::OnCommand_jointcontrollerset },
    { user_command::mastercontrollerincrease, &TTrain::OnCommand_mastercontrollerincrease },
    { user_command::mastercontrollerincreasefast, &TTrain::OnCommand_mastercontrollerincreasefast },
    { user_command::mastercontrollerdecrease, &TTrain::OnCommand_mastercontrollerdecrease },
    { user_command::mastercontrollerdecreasefast, &TTrain::OnCommand_mastercontrollerdecreasefast },
    { user_command::mastercontrollerset, &TTrain::OnCommand_mastercontrollerset },
    { user_command::secondcontrollerincrease, &TTrain::OnCommand_secondcontrollerincrease },
    { user_command::secondcontrollerincreasefast, &TTrain::OnCommand_secondcontrollerincreasefast },
    { user_command::secondcontrollerdecrease, &TTrain::OnCommand_secondcontrollerdecrease },
    { user_command::secondcontrollerdecreasefast, &TTrain::OnCommand_secondcontrollerdecreasefast },
    { user_command::secondcontrollerset, &TTrain::OnCommand_secondcontrollerset },
    { user_command::notchingrelaytoggle, &TTrain::OnCommand_notchingrelaytoggle },
    { user_command::tempomattoggle, &TTrain::OnCommand_tempomattoggle },
    { user_command::mucurrentindicatorothersourceactivate, &TTrain::OnCommand_mucurrentindicatorothersourceactivate },
    { user_command::independentbrakeincrease, &TTrain::OnCommand_independentbrakeincrease },
    { user_command::independentbrakeincreasefast, &TTrain::OnCommand_independentbrakeincreasefast },
    { user_command::independentbrakedecrease, &TTrain::OnCommand_independentbrakedecrease },
    { user_command::independentbrakedecreasefast, &TTrain::OnCommand_independentbrakedecreasefast },
    { user_command::independentbrakeset, &TTrain::OnCommand_independentbrakeset },
    { user_command::independentbrakebailoff, &TTrain::OnCommand_independentbrakebailoff },
	{ user_command::universalbrakebutton1, &TTrain::OnCommand_universalbrakebutton1 },
	{ user_command::universalbrakebutton2, &TTrain::OnCommand_universalbrakebutton2 },
	{ user_command::universalbrakebutton3, &TTrain::OnCommand_universalbrakebutton3 },
    { user_command::trainbrakeincrease, &TTrain::OnCommand_trainbrakeincrease },
    { user_command::trainbrakedecrease, &TTrain::OnCommand_trainbrakedecrease },
    { user_command::trainbrakeset, &TTrain::OnCommand_trainbrakeset },
    { user_command::trainbrakecharging, &TTrain::OnCommand_trainbrakecharging },
    { user_command::trainbrakerelease, &TTrain::OnCommand_trainbrakerelease },
    { user_command::trainbrakefirstservice, &TTrain::OnCommand_trainbrakefirstservice },
    { user_command::trainbrakeservice, &TTrain::OnCommand_trainbrakeservice },
    { user_command::trainbrakefullservice, &TTrain::OnCommand_trainbrakefullservice },
    { user_command::trainbrakehandleoff, &TTrain::OnCommand_trainbrakehandleoff },
    { user_command::trainbrakeemergency, &TTrain::OnCommand_trainbrakeemergency },
    { user_command::trainbrakebasepressureincrease, &TTrain::OnCommand_trainbrakebasepressureincrease },
    { user_command::trainbrakebasepressuredecrease, &TTrain::OnCommand_trainbrakebasepressuredecrease },
    { user_command::trainbrakebasepressurereset, &TTrain::OnCommand_trainbrakebasepressurereset },
    { user_command::trainbrakeoperationtoggle, &TTrain::OnCommand_trainbrakeoperationtoggle },
    { user_command::manualbrakeincrease, &TTrain::OnCommand_manualbrakeincrease },
    { user_command::manualbrakedecrease, &TTrain::OnCommand_manualbrakedecrease },
    { user_command::alarmchaintoggle, &TTrain::OnCommand_alarmchaintoggle },
    { user_command::wheelspinbrakeactivate, &TTrain::OnCommand_wheelspinbrakeactivate },
    { user_command::sandboxactivate, &TTrain::OnCommand_sandboxactivate },
    { user_command::autosandboxtoggle, &TTrain::OnCommand_autosandboxtoggle },
    { user_command::autosandboxactivate, &TTrain::OnCommand_autosandboxactivate },
    { user_command::autosandboxdeactivate, &TTrain::OnCommand_autosandboxdeactivate },
    { user_command::epbrakecontroltoggle, &TTrain::OnCommand_epbrakecontroltoggle },
	{ user_command::trainbrakeoperationmodeincrease, &TTrain::OnCommand_trainbrakeoperationmodeincrease },
	{ user_command::trainbrakeoperationmodedecrease, &TTrain::OnCommand_trainbrakeoperationmodedecrease },
    { user_command::brakeactingspeedincrease, &TTrain::OnCommand_brakeactingspeedincrease },
    { user_command::brakeactingspeeddecrease, &TTrain::OnCommand_brakeactingspeeddecrease },
    { user_command::brakeactingspeedsetcargo, &TTrain::OnCommand_brakeactingspeedsetcargo },
    { user_command::brakeactingspeedsetpassenger, &TTrain::OnCommand_brakeactingspeedsetpassenger },
    { user_command::brakeactingspeedsetrapid, &TTrain::OnCommand_brakeactingspeedsetrapid },
    { user_command::brakeloadcompensationincrease, &TTrain::OnCommand_brakeloadcompensationincrease },
    { user_command::brakeloadcompensationdecrease, &TTrain::OnCommand_brakeloadcompensationdecrease },
    { user_command::mubrakingindicatortoggle, &TTrain::OnCommand_mubrakingindicatortoggle },
    { user_command::reverserincrease, &TTrain::OnCommand_reverserincrease },
    { user_command::reverserdecrease, &TTrain::OnCommand_reverserdecrease },
    { user_command::reverserforwardhigh, &TTrain::OnCommand_reverserforwardhigh },
    { user_command::reverserforward, &TTrain::OnCommand_reverserforward },
    { user_command::reverserneutral, &TTrain::OnCommand_reverserneutral },
    { user_command::reverserbackward, &TTrain::OnCommand_reverserbackward },
    { user_command::alerteracknowledge, &TTrain::OnCommand_alerteracknowledge },
    { user_command::cabsignalacknowledge, &TTrain::OnCommand_cabsignalacknowledge },
    { user_command::batterytoggle, &TTrain::OnCommand_batterytoggle },
    { user_command::batteryenable, &TTrain::OnCommand_batteryenable },
    { user_command::batterydisable, &TTrain::OnCommand_batterydisable },
    { user_command::pantographcompressorvalvetoggle, &TTrain::OnCommand_pantographcompressorvalvetoggle },
    { user_command::pantographcompressorvalveenable, &TTrain::OnCommand_pantographcompressorvalveenable },
    { user_command::pantographcompressorvalvedisable, &TTrain::OnCommand_pantographcompressorvalvedisable },
    { user_command::pantographcompressoractivate, &TTrain::OnCommand_pantographcompressoractivate },
    { user_command::pantographtogglefront, &TTrain::OnCommand_pantographtogglefront },
    { user_command::pantographtogglerear, &TTrain::OnCommand_pantographtogglerear },
    { user_command::pantographraisefront, &TTrain::OnCommand_pantographraisefront },
    { user_command::pantographraiserear, &TTrain::OnCommand_pantographraiserear },
    { user_command::pantographlowerfront, &TTrain::OnCommand_pantographlowerfront },
    { user_command::pantographlowerrear, &TTrain::OnCommand_pantographlowerrear },
    { user_command::pantographlowerall, &TTrain::OnCommand_pantographlowerall },
    { user_command::pantographselectnext, &TTrain::OnCommand_pantographselectnext },
    { user_command::pantographselectprevious, &TTrain::OnCommand_pantographselectprevious },
    { user_command::pantographtoggleselected, &TTrain::OnCommand_pantographtoggleselected },
    { user_command::pantographraiseselected, &TTrain::OnCommand_pantographraiseselected },
    { user_command::pantographlowerselected, &TTrain::OnCommand_pantographlowerselected },
    { user_command::pantographvalvesupdate, &TTrain::OnCommand_pantographvalvesupdate },
    { user_command::pantographvalvesoff, &TTrain::OnCommand_pantographvalvesoff },
    { user_command::linebreakertoggle, &TTrain::OnCommand_linebreakertoggle },
    { user_command::linebreakeropen, &TTrain::OnCommand_linebreakeropen },
    { user_command::linebreakerclose, &TTrain::OnCommand_linebreakerclose },
    { user_command::fuelpumptoggle, &TTrain::OnCommand_fuelpumptoggle },
    { user_command::fuelpumpenable, &TTrain::OnCommand_fuelpumpenable },
    { user_command::fuelpumpdisable, &TTrain::OnCommand_fuelpumpdisable },
    { user_command::oilpumptoggle, &TTrain::OnCommand_oilpumptoggle },
    { user_command::oilpumpenable, &TTrain::OnCommand_oilpumpenable },
    { user_command::oilpumpdisable, &TTrain::OnCommand_oilpumpdisable },
    { user_command::waterheaterbreakertoggle, &TTrain::OnCommand_waterheaterbreakertoggle },
    { user_command::waterheaterbreakerclose, &TTrain::OnCommand_waterheaterbreakerclose },
    { user_command::waterheaterbreakeropen, &TTrain::OnCommand_waterheaterbreakeropen },
    { user_command::waterheatertoggle, &TTrain::OnCommand_waterheatertoggle },
    { user_command::waterheaterenable, &TTrain::OnCommand_waterheaterenable },
    { user_command::waterheaterdisable, &TTrain::OnCommand_waterheaterdisable },
    { user_command::waterpumpbreakertoggle, &TTrain::OnCommand_waterpumpbreakertoggle },
    { user_command::waterpumpbreakerclose, &TTrain::OnCommand_waterpumpbreakerclose },
    { user_command::waterpumpbreakeropen, &TTrain::OnCommand_waterpumpbreakeropen },
    { user_command::waterpumptoggle, &TTrain::OnCommand_waterpumptoggle },
    { user_command::waterpumpenable, &TTrain::OnCommand_waterpumpenable },
    { user_command::waterpumpdisable, &TTrain::OnCommand_waterpumpdisable },
    { user_command::watercircuitslinktoggle, &TTrain::OnCommand_watercircuitslinktoggle },
    { user_command::watercircuitslinkenable, &TTrain::OnCommand_watercircuitslinkenable },
    { user_command::watercircuitslinkdisable, &TTrain::OnCommand_watercircuitslinkdisable },
    { user_command::convertertoggle, &TTrain::OnCommand_convertertoggle },
    { user_command::converterenable, &TTrain::OnCommand_converterenable },
    { user_command::converterdisable, &TTrain::OnCommand_converterdisable },
    { user_command::convertertogglelocal, &TTrain::OnCommand_convertertogglelocal },
    { user_command::converteroverloadrelayreset, &TTrain::OnCommand_converteroverloadrelayreset },
    { user_command::compressortoggle, &TTrain::OnCommand_compressortoggle },
    { user_command::compressorenable, &TTrain::OnCommand_compressorenable },
    { user_command::compressordisable, &TTrain::OnCommand_compressordisable },
    { user_command::compressortogglelocal, &TTrain::OnCommand_compressortogglelocal },
	{ user_command::compressorpresetactivatenext, &TTrain::OnCommand_compressorpresetactivatenext },
	{ user_command::compressorpresetactivateprevious, &TTrain::OnCommand_compressorpresetactivateprevious },
	{ user_command::compressorpresetactivatedefault, &TTrain::OnCommand_compressorpresetactivatedefault },
    { user_command::motorblowerstogglefront, &TTrain::OnCommand_motorblowerstogglefront },
    { user_command::motorblowerstogglerear, &TTrain::OnCommand_motorblowerstogglerear },
    { user_command::motorblowersdisableall, &TTrain::OnCommand_motorblowersdisableall },
    { user_command::coolingfanstoggle, &TTrain::OnCommand_coolingfanstoggle },
    { user_command::motorconnectorsopen, &TTrain::OnCommand_motorconnectorsopen },
    { user_command::motorconnectorsclose, &TTrain::OnCommand_motorconnectorsclose },
    { user_command::motordisconnect, &TTrain::OnCommand_motordisconnect },
    { user_command::motoroverloadrelaythresholdtoggle, &TTrain::OnCommand_motoroverloadrelaythresholdtoggle },
    { user_command::motoroverloadrelaythresholdsetlow, &TTrain::OnCommand_motoroverloadrelaythresholdsetlow },
    { user_command::motoroverloadrelaythresholdsethigh, &TTrain::OnCommand_motoroverloadrelaythresholdsethigh },
    { user_command::motoroverloadrelayreset, &TTrain::OnCommand_motoroverloadrelayreset },
    { user_command::universalrelayreset1, &TTrain::OnCommand_universalrelayreset },
    { user_command::universalrelayreset2, &TTrain::OnCommand_universalrelayreset },
    { user_command::universalrelayreset3, &TTrain::OnCommand_universalrelayreset },
    { user_command::heatingtoggle, &TTrain::OnCommand_heatingtoggle },
    { user_command::heatingenable, &TTrain::OnCommand_heatingenable },
    { user_command::heatingdisable, &TTrain::OnCommand_heatingdisable },
    { user_command::lightspresetactivatenext, &TTrain::OnCommand_lightspresetactivatenext },
    { user_command::lightspresetactivateprevious, &TTrain::OnCommand_lightspresetactivateprevious },
    { user_command::headlighttoggleleft, &TTrain::OnCommand_headlighttoggleleft },
    { user_command::headlightenableleft, &TTrain::OnCommand_headlightenableleft },
    { user_command::headlightdisableleft, &TTrain::OnCommand_headlightdisableleft },
    { user_command::headlighttoggleright, &TTrain::OnCommand_headlighttoggleright },
    { user_command::headlightenableright, &TTrain::OnCommand_headlightenableright },
    { user_command::headlightdisableright, &TTrain::OnCommand_headlightdisableright },
    { user_command::headlighttoggleupper, &TTrain::OnCommand_headlighttoggleupper },
    { user_command::headlightenableupper, &TTrain::OnCommand_headlightenableupper },
    { user_command::headlightdisableupper, &TTrain::OnCommand_headlightdisableupper },
    { user_command::redmarkertoggleleft, &TTrain::OnCommand_redmarkertoggleleft },
    { user_command::redmarkerenableleft, &TTrain::OnCommand_redmarkerenableleft },
    { user_command::redmarkerdisableleft, &TTrain::OnCommand_redmarkerdisableleft },
    { user_command::redmarkertoggleright, &TTrain::OnCommand_redmarkertoggleright },
    { user_command::redmarkerenableright, &TTrain::OnCommand_redmarkerenableright },
    { user_command::redmarkerdisableright, &TTrain::OnCommand_redmarkerdisableright },
    { user_command::headlighttogglerearleft, &TTrain::OnCommand_headlighttogglerearleft },
    { user_command::headlighttogglerearright, &TTrain::OnCommand_headlighttogglerearright },
    { user_command::headlighttogglerearupper, &TTrain::OnCommand_headlighttogglerearupper },
    { user_command::redmarkertogglerearleft, &TTrain::OnCommand_redmarkertogglerearleft },
    { user_command::redmarkertogglerearright, &TTrain::OnCommand_redmarkertogglerearright },
    { user_command::redmarkerstoggle, &TTrain::OnCommand_redmarkerstoggle },
    { user_command::endsignalstoggle, &TTrain::OnCommand_endsignalstoggle },
    { user_command::headlightsdimtoggle, &TTrain::OnCommand_headlightsdimtoggle },
    { user_command::headlightsdimenable, &TTrain::OnCommand_headlightsdimenable },
    { user_command::headlightsdimdisable, &TTrain::OnCommand_headlightsdimdisable },
    { user_command::interiorlighttoggle, &TTrain::OnCommand_interiorlighttoggle },
    { user_command::interiorlightenable, &TTrain::OnCommand_interiorlightenable },
    { user_command::interiorlightdisable, &TTrain::OnCommand_interiorlightdisable },
    { user_command::interiorlightdimtoggle, &TTrain::OnCommand_interiorlightdimtoggle },
    { user_command::interiorlightdimenable, &TTrain::OnCommand_interiorlightdimenable },
    { user_command::interiorlightdimdisable, &TTrain::OnCommand_interiorlightdimdisable },
    { user_command::compartmentlightstoggle, &TTrain::OnCommand_compartmentlightstoggle },
    { user_command::compartmentlightsenable, &TTrain::OnCommand_compartmentlightsenable },
    { user_command::compartmentlightsdisable, &TTrain::OnCommand_compartmentlightsdisable },
    { user_command::instrumentlighttoggle, &TTrain::OnCommand_instrumentlighttoggle },
    { user_command::instrumentlightenable, &TTrain::OnCommand_instrumentlightenable },
    { user_command::instrumentlightdisable, &TTrain::OnCommand_instrumentlightdisable },
    { user_command::dashboardlighttoggle, &TTrain::OnCommand_dashboardlighttoggle },
    { user_command::timetablelighttoggle, &TTrain::OnCommand_timetablelighttoggle },
    { user_command::doorlocktoggle, &TTrain::OnCommand_doorlocktoggle },
    { user_command::doortoggleleft, &TTrain::OnCommand_doortoggleleft },
    { user_command::doortoggleright, &TTrain::OnCommand_doortoggleright },
    { user_command::doorpermitleft, &TTrain::OnCommand_doorpermitleft },
    { user_command::doorpermitright, &TTrain::OnCommand_doorpermitright },
    { user_command::doorpermitpresetactivatenext, &TTrain::OnCommand_doorpermitpresetactivatenext },
    { user_command::doorpermitpresetactivateprevious, &TTrain::OnCommand_doorpermitpresetactivateprevious },
    { user_command::dooropenleft, &TTrain::OnCommand_dooropenleft },
    { user_command::dooropenright, &TTrain::OnCommand_dooropenright },
    { user_command::doorcloseleft, &TTrain::OnCommand_doorcloseleft },
    { user_command::doorcloseright, &TTrain::OnCommand_doorcloseright },
    { user_command::dooropenall, &TTrain::OnCommand_dooropenall },
    { user_command::doorcloseall, &TTrain::OnCommand_doorcloseall },
    { user_command::doorsteptoggle, &TTrain::OnCommand_doorsteptoggle },
    { user_command::doormodetoggle, &TTrain::OnCommand_doormodetoggle },
    { user_command::nearestcarcouplingincrease, &TTrain::OnCommand_nearestcarcouplingincrease },
    { user_command::nearestcarcouplingdisconnect, &TTrain::OnCommand_nearestcarcouplingdisconnect },
    { user_command::nearestcarcoupleradapterattach, &TTrain::OnCommand_nearestcarcoupleradapterattach },
    { user_command::nearestcarcoupleradapterremove, &TTrain::OnCommand_nearestcarcoupleradapterremove },
    { user_command::occupiedcarcouplingdisconnect, &TTrain::OnCommand_occupiedcarcouplingdisconnect },
    { user_command::departureannounce, &TTrain::OnCommand_departureannounce },
    { user_command::hornlowactivate, &TTrain::OnCommand_hornlowactivate },
    { user_command::hornhighactivate, &TTrain::OnCommand_hornhighactivate },
    { user_command::whistleactivate, &TTrain::OnCommand_whistleactivate },
    { user_command::radiotoggle, &TTrain::OnCommand_radiotoggle },
    { user_command::radiochannelincrease, &TTrain::OnCommand_radiochannelincrease },
    { user_command::radiochanneldecrease, &TTrain::OnCommand_radiochanneldecrease },
    { user_command::radiostopsend, &TTrain::OnCommand_radiostopsend },
    { user_command::radiostoptest, &TTrain::OnCommand_radiostoptest },
    { user_command::radiocall3send, &TTrain::OnCommand_radiocall3send },
	{ user_command::radiovolumeincrease, &TTrain::OnCommand_radiovolumeincrease },
	{ user_command::radiovolumedecrease, &TTrain::OnCommand_radiovolumedecrease },
    { user_command::cabchangeforward, &TTrain::OnCommand_cabchangeforward },
    { user_command::cabchangebackward, &TTrain::OnCommand_cabchangebackward },
    { user_command::generictoggle0, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle1, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle2, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle3, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle4, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle5, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle6, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle7, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle8, &TTrain::OnCommand_generictoggle },
    { user_command::generictoggle9, &TTrain::OnCommand_generictoggle },
    { user_command::vehiclemoveforwards, &TTrain::OnCommand_vehiclemoveforwards },
    { user_command::vehiclemovebackwards, &TTrain::OnCommand_vehiclemovebackwards },
    { user_command::vehicleboost, &TTrain::OnCommand_vehicleboost },
	{ user_command::springbraketoggle, &TTrain::OnCommand_springbraketoggle },
	{ user_command::springbrakeenable, &TTrain::OnCommand_springbrakeenable },
	{ user_command::springbrakedisable, &TTrain::OnCommand_springbrakedisable },
	{ user_command::springbrakeshutofftoggle, &TTrain::OnCommand_springbrakeshutofftoggle },
	{ user_command::springbrakeshutoffenable, &TTrain::OnCommand_springbrakeshutoffenable },
	{ user_command::springbrakeshutoffdisable, &TTrain::OnCommand_springbrakeshutoffdisable },
	{ user_command::springbrakerelease, &TTrain::OnCommand_springbrakerelease },
    { user_command::distancecounteractivate, &TTrain::OnCommand_distancecounteractivate },
	{ user_command::speedcontrolincrease, &TTrain::OnCommand_speedcontrolincrease },
	{ user_command::speedcontroldecrease, &TTrain::OnCommand_speedcontroldecrease },
	{ user_command::speedcontrolpowerincrease, &TTrain::OnCommand_speedcontrolpowerincrease },
	{ user_command::speedcontrolpowerdecrease, &TTrain::OnCommand_speedcontrolpowerdecrease },
	{ user_command::speedcontrolbutton0, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton1, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton2, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton3, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton4, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton5, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton6, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton7, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton8, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::speedcontrolbutton9, &TTrain::OnCommand_speedcontrolbutton },
	{ user_command::inverterenable1, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable2, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable3, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable4, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable5, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable6, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable7, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable8, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable9, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable10, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable11, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterenable12, &TTrain::OnCommand_inverterenable },
	{ user_command::inverterdisable1, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable2, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable3, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable4, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable5, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable6, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable7, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable8, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable9, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable10, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable11, &TTrain::OnCommand_inverterdisable },
	{ user_command::inverterdisable12, &TTrain::OnCommand_inverterdisable },
	{ user_command::invertertoggle1, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle2, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle3, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle4, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle5, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle6, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle7, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle8, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle9, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle10, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle11, &TTrain::OnCommand_invertertoggle },
	{ user_command::invertertoggle12, &TTrain::OnCommand_invertertoggle },
};

std::vector<std::string> const TTrain::fPress_labels = {

    "ch1:  ", "ch2:  ", "ch3:  ", "ch4:  ", "ch5:  ", "ch6:  ", "ch7:  ", "ch8:  ", "ch9:  ", "ch0:  "
};

TTrain::TTrain() {

    ShowNextCurrent = false;
    // McZapkie-240302 - przyda sie do tachometru
    fTachoVelocity = 0;
    fTachoCount = 0;
    fPPress = fNPress = 0;

    // asMessage="";
    pMechOffset = Math3D::vector3(0, 0, 0);
    fBlinkTimer = 0;
    fHaslerTimer = 0;
    DynamicSet(NULL); // ustawia wszystkie mv*
    //-----
    pMechSittingPosition = Math3D::vector3(0, 0, 0); // ABu: 180404
    fTachoTimer = 0.0; // włączenie skoków wskazań prędkościomierza

    //
    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 < 9; ++i )
		for (int j = 0; j < 10; ++j)
		{
			fEIMParams[i][j] = 0.0;
			fDieselParams[i][j] = 0.0;
		}

	for ( int i = 0; i < 20; ++i )
	{
		for ( int j = 0; j < 6; ++j )
			fPress[i][j] = 0.0;
		bBrakes[i][0] = bBrakes[i][1] = false;
	}
}

TTrain::~TTrain()
{
}

bool TTrain::Init(TDynamicObject *NewDynamicObject, bool e3d)
{ // powiązanie ręcznego sterowania kabiną z pojazdem
    if( NewDynamicObject->Mechanik == nullptr ) {
        /*
        ErrorLog( "Bad config: can't take control of inactive vehicle \"" + NewDynamicObject->asName + "\"" );
        return false;
        */
        auto const activecab { (
            NewDynamicObject->MoverParameters->CabOccupied > 0 ? "1" :
            NewDynamicObject->MoverParameters->CabOccupied < 0 ? "2" :
            "p" ) };
        NewDynamicObject->create_controller( activecab, NewDynamicObject->ctOwner != nullptr );
    }

    DynamicSet(NewDynamicObject);
    if (!e3d)
        if (DynamicObject->Mechanik == NULL)
            return false;

    DynamicObject->MechInside = true;

    fMainRelayTimer = 0; // Hunter, do k...y nędzy, ustawiaj wartości początkowe zmiennych!

    iCabn = (
        mvOccupied->CabOccupied > 0 ? 1 :
        mvOccupied->CabOccupied < 0 ? 2 :
        0 );

    {
        Global.CurrentMaxTextureSize = Global.iMaxCabTextureSize;

        auto const filename { mvOccupied->TypeName + ".mmd" };
        LoadMMediaFile( filename );
        InitializeCab(
            mvOccupied->CabOccupied,
            filename );

        Global.CurrentMaxTextureSize = Global.iMaxTextureSize;

        if( DynamicObject->Controller == Humandriver ) {
            // McZapkie-030303: mozliwosc wyswietlania kabiny, w przyszlosci dac opcje w mmd
            DynamicObject->bDisplayCab = true;
        }
    }

    // Ra: taka proteza - przesłanie kierunku do członów connected
    /*
    if (mvControlled->DirActive > 0)
    { // było do przodu
        mvControlled->DirectionBackward();
        mvControlled->DirectionForward();
    }
    else if (mvControlled->DirActive < 0)
    {
        mvControlled->DirectionForward();
        mvControlled->DirectionBackward();
    }
    */
    if( false == DynamicObject->Mechanik->AIControllFlag ) {
        DynamicObject->Mechanik->sync_consist_reversers();
    }

    return true;
}

dictionary_source *TTrain::GetTrainState( dictionary_source const &Extraparameters ) {

    if( ( mvOccupied   == nullptr )
     || ( mvControlled == nullptr ) ) { return nullptr; }

    auto *dict { new dictionary_source( Extraparameters ) };
    if( dict == nullptr ) { return nullptr; }

    dict->insert( "name", DynamicObject->asName );
    dict->insert( "cab", mvOccupied->CabOccupied );
    // basic systems state data
    dict->insert( "battery", mvOccupied->Power24vIsAvailable );
    dict->insert( "linebreaker", mvControlled->Mains );
    dict->insert( "main_init", ( mvControlled->MainsInitTimeCountdown < mvControlled->MainsInitTime ) && ( mvControlled->MainsInitTimeCountdown > 0.0 ) );
    dict->insert( "main_ready", ( false == mvControlled->Mains ) && ( fHVoltage > 0.0 ) && ( mvControlled->MainsInitTimeCountdown <= 0.0 ) );
    dict->insert( "converter", mvOccupied->Power110vIsAvailable );
    dict->insert( "converter_overload", mvControlled->ConvOvldFlag );
    dict->insert( "compress", mvControlled->CompressorFlag );
    dict->insert( "pant_compressor", mvPantographUnit->PantCompFlag );
    dict->insert( "lights_front", mvOccupied->iLights[ end::front ] );
    dict->insert( "lights_rear", mvOccupied->iLights[ end::rear ] );
    dict->insert( "lights_compartments", mvOccupied->CompartmentLights.is_active || mvOccupied->CompartmentLights.is_disabled );
    if( Dynamic()->Mechanik ) {
        auto const *controller { Dynamic()->Mechanik };
        auto const cabmodifier { cab_to_end() == end::front ? 1 : -1 };
        auto const traindirection { controller->Direction() * cabmodifier };
        auto const *frontvehicle { controller->Vehicle( traindirection >= 0 ? end::front : end::rear ) };
        auto const *rearvehicle { controller->Vehicle( traindirection >= 0 ? end::rear : end::front ) };
        auto const frontvehicledirection { ( frontvehicle->DirectionGet() == controller->Vehicle()->DirectionGet() ? 1 : -1 ) };
        auto const rearvehicledirection { ( rearvehicle->DirectionGet() == controller->Vehicle()->DirectionGet() ? 1 : -1 ) };
        auto const fronttrainlights { frontvehicle->MoverParameters->iLights[ frontvehicledirection * cabmodifier >= 0 ? end::front : end::rear ] };
        auto const reartrainlights{ rearvehicle->MoverParameters->iLights[ rearvehicledirection * cabmodifier >= 0 ? end::rear : end::front ] };
        dict->insert( "lights_train_front", fronttrainlights );
        dict->insert( "lights_train_rear", reartrainlights );
    }
    else {
        // fallback, in the unlikely case we lose the controller
        dict->insert( "lights_train_front", mvOccupied->iLights[ end::front ] );
        dict->insert( "lights_train_rear", mvOccupied->iLights[ end::rear ] );
    }
    // reverser
    dict->insert( "direction", mvOccupied->DirActive );
    // throttle
    dict->insert( "mainctrl_pos", mvControlled->MainCtrlPos );
    dict->insert( "main_ctrl_actual_pos", mvControlled->MainCtrlActualPos );
    dict->insert( "scndctrl_pos", mvControlled->ScndCtrlPos );
    dict->insert( "scnd_ctrl_actual_pos", mvControlled->ScndCtrlActualPos );
	dict->insert( "brakectrl_pos", mvControlled->fBrakeCtrlPos );
	dict->insert( "localbrake_pos", mvControlled->LocalBrakePosA );
	dict->insert( "new_speed", mvOccupied->NewSpeed);
 	dict->insert( "speedctrl", mvOccupied->SpeedCtrlValue);
	dict->insert( "speedctrlpower", mvOccupied->SpeedCtrlUnit.DesiredPower );
	dict->insert( "speedctrlactive", mvOccupied->SpeedCtrlUnit.IsActive );
	dict->insert( "speedctrlstandby", mvOccupied->SpeedCtrlUnit.Standby );
   // brakes
    dict->insert( "manual_brake", ( mvOccupied->ManualBrakePos > 0 ) );
    bool const bEP = ( mvControlled->LocHandle->GetCP() > 0.2 ) || ( fEIMParams[ 0 ][ 5 ] > 0.01 );
    dict->insert( "dir_brake", bEP );
    bool bPN { false };
    if( ( typeid( *mvOccupied->Hamulec ) == typeid( TLSt ) )
     || ( typeid( *mvOccupied->Hamulec ) == typeid( TEStED ) ) ) {

        TBrake* temp_ham = mvOccupied->Hamulec.get();
        bPN = ( static_cast<TLSt*>( temp_ham )->GetEDBCP() > 0.2 );
    }
    dict->insert( "indir_brake", bPN );
	dict->insert( "emergency_brake", mvOccupied->AlarmChainFlag );
	dict->insert( "brake_delay_flag", mvOccupied->BrakeDelayFlag );
	dict->insert( "brake_op_mode_flag", mvOccupied->BrakeOpModeFlag );
    // other controls
    dict->insert( "ca", mvOccupied->SecuritySystem.is_blinking());
    dict->insert( "shp", mvOccupied->SecuritySystem.is_cabsignal_blinking());
    dict->insert( "distance_counter", m_distancecounter );
    dict->insert( "pantpress", std::abs( mvPantographUnit->PantPress ) );
    dict->insert( "universal3", InstrumentLightActive );
	for (auto idx = 0; idx < ggUniversals.size(); idx++) {
		if (idx != 3) {
			dict->insert("universal" + std::to_string(idx), (ggUniversals[idx].GetValue() > 0.5));
		}
	}
    dict->insert( "radio", mvOccupied->Radio );
    dict->insert( "radio_channel", RadioChannel() );
	dict->insert( "radio_volume", Global.RadioVolume );
    dict->insert( "door_lock", mvOccupied->Doors.lock_enabled );
	dict->insert( "door_step", mvOccupied->Doors.step_enabled );
    // movement data
    dict->insert( "velocity", std::abs( mvOccupied->Vel ) );
    dict->insert( "tractionforce", std::abs( mvOccupied->Ft ) );
    dict->insert( "slipping_wheels", mvOccupied->SlippingWheels );
    dict->insert( "sanding", mvOccupied->SandDose );
    dict->insert( "odometer", mvOccupied->DistCounter );
    // electric current data
    dict->insert( "traction_voltage", std::abs( mvPantographUnit->PantographVoltage ) );
    dict->insert( "voltage", std::abs( mvControlled->EngineVoltage ) );
    dict->insert( "im", std::abs(  mvControlled->Im ) );
    dict->insert( "fuse", mvControlled->FuseFlag );
    dict->insert( "epfuse", mvOccupied->EpFuse );
    // induction motor state data
    char const *TXTT[ 10 ] = { "fd", "fdt", "fdb", "pd", "pdt", "pdb", "itothv", "1", "2", "3" };
    char const *TXTC[ 10 ] = { "fr", "frt", "frb", "pr", "prt", "prb", "im", "vm", "ihv", "uhv" };
	char const *TXTD[ 10 ] = { "enrot", "nrot", "fill_des", "fill_real", "clutch_des", "clutch_real", "water_temp", "oil_press", "engine_temp", "retarder_fill" };
    char const *TXTP[ 6 ] = { "bc", "bp", "sp", "cp", "rp", "mass" };
	char const *TXTB[ 2 ] = { "spring_active", "spring_shutoff" };
    for( int j = 0; j < 10; ++j )
        dict->insert( ( "eimp_t_" + std::string( TXTT[ j ] ) ), fEIMParams[ 0 ][ j ] );
    for( int i = 0; i < 8; ++i ) {
        auto const idx { std::to_string( i + 1 ) };
        for( int j = 0; j < 10; ++j )
            dict->insert( ( "eimp_c" + idx + "_" + std::string( TXTC[ j ] ) ), fEIMParams[ i + 1 ][ j ] );

		for (int j = 0; j < 10; ++j)
			dict->insert(("diesel_param_" + idx + "_" + std::string(TXTD[j])), fDieselParams[i + 1][j]);

        dict->insert( ( "eimp_c" + idx + "_ms" ), bMains[ i ] );
        dict->insert( ( "eimp_c" + idx + "_cv" ), fCntVol[ i ] );
        dict->insert( ( "eimp_c" + idx + "_fuse" ), bFuse[ i ] );
        dict->insert( ( "eimp_c" + idx + "_batt" ), bBatt[ i ] );
        dict->insert( ( "eimp_c" + idx + "_conv" ), bConv[ i ] );
        dict->insert( ( "eimp_c" + idx + "_heat" ), bHeat[ i ] );

        dict->insert( ( "eimp_u" + idx + "_pf" ), bPants[ i ][ 0 ] );
        dict->insert( ( "eimp_u" + idx + "_pr" ), bPants[ i ][ 1 ] );
        dict->insert( ( "eimp_u" + idx + "_comp_a" ), bComp[ i ][ 0 ] );
        dict->insert( ( "eimp_u" + idx + "_comp_w" ), bComp[ i ][ 1 ] );
    }

	dict->insert( "compressors_no", (int)bCompressors.size() );
	for (int i = 0; i < bCompressors.size(); i++)
	{
        auto const idx { std::to_string( i + 1 ) };
        dict->insert("compressors_" + idx + "_allow", std::get<0>(bCompressors[i]));
		dict->insert("compressors_" + idx + "_work", std::get<1>(bCompressors[i]));
		dict->insert("compressors_" + idx + "_car_no", std::get<2>(bCompressors[i]));
	}


	bool kier = (DynamicObject->DirectionGet() * mvOccupied->CabOccupied > 0);
	TDynamicObject *p = DynamicObject->GetFirstDynamic(mvOccupied->CabOccupied < 0 ? end::rear : end::front, 4);
	int in = 0;
	while (p && in < 8)
	{
		if (p->MoverParameters->eimc[eimc_p_Pmax] > 1)
		{
			in++;
			dict->insert(("eimp_c" + std::to_string(in) + "_invno"), p->MoverParameters->InvertersNo);
			for (int j = 0; j < p->MoverParameters->InvertersNo; j++) {
				dict->insert(("eimp_c" + std::to_string(in) + "_inv" + std::to_string(j + 1) + "_act"), p->MoverParameters->Inverters[j].IsActive);
				dict->insert(("eimp_c" + std::to_string(in) + "_inv" + std::to_string(j + 1) + "_error"), p->MoverParameters->Inverters[j].Error);
				dict->insert(("eimp_c" + std::to_string(in) + "_inv" + std::to_string(j + 1) + "_allow"), p->MoverParameters->Inverters[j].Activate);
			}
		}
		p = (kier ? p->Next(4) : p->Prev(4));
	}
    for( int i = 0; i < 20; ++i ) {
        for( int j = 0; j < 6; ++j ) {
            dict->insert( ( "eimp_pn" + std::to_string( i + 1 ) + "_" + TXTP[ j ] ), fPress[ i ][ j ] );
        }
		for ( int j = 0; j < 2; ++j)  {
			dict->insert( ( "brakes_" + std::to_string( i + 1 ) + "_" + TXTB[ j ] ), bBrakes[ i ][ j ] );
		}
    }
    // multi-unit state data
    dict->insert( "car_no", iCarNo );
    dict->insert( "power_no", iPowerNo );
    dict->insert( "unit_no", iUnitNo );

    for( int i = 0; i < 20; i++ ) {
        auto const caridx { std::to_string( i + 1 ) };
        dict->insert( ( "doors_" + caridx ), bDoors[ i ][ 0 ] );
        dict->insert( ( "doors_l_" + caridx ), bDoors[ i ][ 1 ] );
        dict->insert( ( "doors_r_" + caridx ), bDoors[ i ][ 2 ] );
        dict->insert( ( "doorstep_l_" + caridx ), bDoors[ i ][ 3 ] );
        dict->insert( ( "doorstep_r_" + caridx ), bDoors[ i ][ 4 ] );
        dict->insert( ( "doors_no_" + caridx ), iDoorNo[ i ] );
        dict->insert( ( "code_" + caridx ), ( std::to_string( iUnits[ i ] ) + cCode[ i ] ) );
        dict->insert( ( "car_name" + caridx ), asCarName[ i ] );
        dict->insert( ( "slip_" + caridx ), bSlip[ i ] );
    }
    // ai state data
    auto const *driver { (
        DynamicObject->ctOwner != nullptr ?
            DynamicObject->ctOwner :
            DynamicObject->Mechanik ) };

    dict->insert( "velocity_desired", driver->VelDesired );
    dict->insert( "velroad", driver->VelRoad );
    dict->insert( "vellimitlast", driver->VelLimitLast );
    dict->insert( "velsignallast", driver->VelSignalLast );
    dict->insert( "velsignalnext", driver->VelSignalNext );
    dict->insert( "velnext", driver->VelNext );
    dict->insert( "actualproximitydist", driver->ActualProximityDist );
    // train data
    driver->TrainTimetable().serialize( dict );
    dict->insert( "train_atpassengerstop", driver->IsAtPassengerStop );
    dict->insert( "train_length", driver->fLength );
    // world state data
    dict->insert( "scenario", Global.SceneryFile );
    dict->insert( "hours", static_cast<int>( simulation::Time.data().wHour ) );
    dict->insert( "minutes", static_cast<int>( simulation::Time.data().wMinute ) );
    dict->insert( "seconds", static_cast<int>( simulation::Time.second() ) );
    dict->insert( "air_temperature", Global.AirTemperature );
    dict->insert( "light_level", Global.fLuminance - std::max( 0.f, Global.Overcast - 1.f ) );

    return dict;
}

TTrain::state_t
TTrain::get_state() const {

	return {
		btLampkaSHP.GetValue(),
		btLampkaCzuwaka.GetValue(),
		btLampkaRadioStop.GetValue(),
		btLampkaOpory.GetValue(),
		btLampkaWylSzybki.GetValue(),
		btLampkaPrzekRozn.GetValue(),
		btLampkaNadmSil.GetValue(),
		btLampkaStyczn.GetValue(),
		btLampkaPoslizg.GetValue(),
		btLampkaNadmPrzetw.GetValue(),
		btLampkaPrzetwOff.GetValue(),
		btLampkaNadmSpr.GetValue(),
		btLampkaNadmWent.GetValue(),
		btLampkaWysRozr.GetValue(),
		btLampkaOgrzewanieSkladu.GetValue(),
		static_cast<std::uint8_t>(iCabn),
		btHaslerBrakes.GetValue(),
		btHaslerCurrent.GetValue(),
		mvOccupied->SecuritySystem.is_beeping(),
		btLampkaHVoltageB.GetValue(),
		fTachoVelocity,
		static_cast<float>(mvOccupied->Compressor),
		static_cast<float>(mvOccupied->PipePress),
		static_cast<float>(mvOccupied->BrakePress),
		static_cast<float>(mvPantographUnit->PantPress),
		fHVoltage,
		{ fHCurrent[(mvControlled->TrainType & dt_EZT) ? 0 : 1], fHCurrent[2], fHCurrent[3] },
		ggLVoltage.GetValue(),
		mvOccupied->DistCounter,
		static_cast<std::uint8_t>(RadioChannel()),
		btLampkaSpringBrakeActive.GetValue(),
		btLampkaNapNastHam.GetValue(),
		mvOccupied->DirActive > 0,
		mvOccupied->DirActive < 0,
		mvOccupied->Doors.instances[mvOccupied->CabOccupied < 0 ? side::right : side::left].open_permit,
		mvOccupied->Doors.instances[mvOccupied->CabOccupied < 0 ? side::right : side::left].is_open,
		mvOccupied->Doors.instances[mvOccupied->CabOccupied < 0 ? side::left : side::right].open_permit,
		mvOccupied->Doors.instances[mvOccupied->CabOccupied < 0 ? side::left : side::right].is_open,
		mvOccupied->Doors.step_enabled,
		mvOccupied->Power24vIsAvailable,
		0,
		mvOccupied->LockPipe
    };
}

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<int>(
            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<int>( 100.0 * mvOccupied->fBrakeCtrlPos ) };
    mvOccupied->BrakeLevelSet( Position );

    if( static_cast<int>( 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;
}


// command handlers
void TTrain::OnCommand_aidriverenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // on press
        if( Train->DynamicObject->Mechanik == nullptr ) { return; }

        if( true == Train->DynamicObject->Mechanik->AIControllFlag ) {
            //żeby nie trzeba było rozłączać dla zresetowania
            Train->DynamicObject->Mechanik->TakeControl( false );
        }
        Train->DynamicObject->Mechanik->TakeControl( true );
    }
}

void TTrain::OnCommand_aidriverdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // on press
        if( Train->DynamicObject->Mechanik )
            Train->DynamicObject->Mechanik->TakeControl( false );
    }
}

auto const EU07_CONTROLLER_BASERETURNDELAY { 0.5f };
auto const EU07_CONTROLLER_KEYBOARDETURNDELAY { 1.5f };

void TTrain::OnCommand_jointcontrollerset( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        // value controls brake in range 0-0.5, master controller in range 0.5-1.0
        if( Command.param1 >= 0.5 ) {
            Train->set_master_controller( 
                ( Command.param1 * 2 - 1 )
                * ( Train->mvControlled->CoupledCtrl ?
                        Train->mvControlled->MainCtrlPosNo + Train->mvControlled->ScndCtrlPosNo :
                        Train->mvControlled->MainCtrlPosNo ) );
            Train->m_mastercontrollerinuse = true;
            Train->mvOccupied->LocalBrakePosA = 0;
        }
        else {
            Train->mvOccupied->LocalBrakePosA = (
                clamp(
                    1.0 - ( Command.param1 * 2 ),
                    0.0, 1.0 ) );
            if( Train->mvControlled->MainCtrlPowerPos() > 0 ) {
                Train->set_master_controller( Train->mvControlled->MainCtrlNoPowerPos() );
            }
        }
    }
    else {
        // release
        Train->m_mastercontrollerinuse = false;
        Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_BASERETURNDELAY; // NOTE: keyboard return delay is omitted for other input sources
    }
}

void TTrain::OnCommand_mastercontrollerincrease( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        if( ( Train->ggJointCtrl.SubModel != nullptr )
         && ( Train->mvOccupied->LocalBrakePosA > 0.0 ) ) {
            OnCommand_independentbrakedecrease( Train, Command );
        }
        else {
            Train->mvControlled->IncMainCtrl( 1 );
            Train->m_mastercontrollerinuse = true;
        }
    }
    else if (Command.action == GLFW_RELEASE) {
        // release
        Train->m_mastercontrollerinuse = false;
        Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_KEYBOARDETURNDELAY + EU07_CONTROLLER_BASERETURNDELAY;
    }
}

void TTrain::OnCommand_mastercontrollerincreasefast( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        if( ( Train->ggJointCtrl.SubModel != nullptr )
         && ( Train->mvOccupied->LocalBrakePosA > 0.0 ) ) {
            OnCommand_independentbrakedecreasefast( Train, Command );
        }
        else {
            Train->mvControlled->IncMainCtrl( Train->mvControlled->MainCtrlPosNo );
            Train->m_mastercontrollerinuse = true;
        }
    }
    else {
        // release
        Train->m_mastercontrollerinuse = false;
        Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_KEYBOARDETURNDELAY + EU07_CONTROLLER_BASERETURNDELAY;
    }
}

void TTrain::OnCommand_mastercontrollerdecrease( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        if( ( Train->ggJointCtrl.SubModel != nullptr )
         && ( Train->mvControlled->IsMainCtrlNoPowerPos() ) ) {
            OnCommand_independentbrakeincrease( Train, Command );
        }
        else {
            Train->mvControlled->DecMainCtrl( 1 );
            Train->m_mastercontrollerinuse = true;
        }
    }
    else if (Command.action == GLFW_RELEASE) {
        // release
        Train->m_mastercontrollerinuse = false;
        Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_KEYBOARDETURNDELAY + EU07_CONTROLLER_BASERETURNDELAY;
    }
}

void TTrain::OnCommand_mastercontrollerdecreasefast( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        if( ( Train->ggJointCtrl.SubModel != nullptr )
         && ( Train->mvControlled->IsMainCtrlNoPowerPos() ) ) {
            OnCommand_independentbrakeincreasefast( Train, Command );
        }
        else {
            Train->mvControlled->DecMainCtrl( Train->mvControlled->MainCtrlPowerPos() );
            Train->m_mastercontrollerinuse = true;
        }
    }
    else {
        // release
        Train->m_mastercontrollerinuse = false;
        Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_KEYBOARDETURNDELAY + EU07_CONTROLLER_BASERETURNDELAY;
    }
}

void TTrain::OnCommand_mastercontrollerset( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        Train->set_master_controller( Command.param1 );
        Train->m_mastercontrollerinuse = true;
    }
    else {
        // release
        Train->m_mastercontrollerinuse = false;
        Train->m_mastercontrollerreturndelay = EU07_CONTROLLER_BASERETURNDELAY; // NOTE: keyboard return delay is omitted for other input sources
    }
}

void TTrain::OnCommand_secondcontrollerincrease( TTrain *Train, command_data const &Command ) {

    if( ( Train->mvControlled->EngineType == TEngineType::DieselElectric )
     && ( true == Train->mvControlled->ShuntModeAllow )
     && ( true == Train->mvControlled->ShuntMode ) ) {
        if( Command.action != GLFW_RELEASE ) {
            Train->mvControlled->AnPos = clamp(
                Train->mvControlled->AnPos + 0.025,
                0.0, 1.0 );
        }
    }
    else {
        // regular mode
        // push or pushtoggle control type
        if( Train->ggScndCtrl.is_push() ) {
            if( Command.action == GLFW_PRESS ) {
                // activate on press
                Train->mvControlled->IncScndCtrl( 1 );
            }
        }
        // toggle control type
        else {
            if( Command.action != GLFW_RELEASE ) {
                Train->mvControlled->IncScndCtrl( 1 );
            }
        }
        // HACK: potentially animate push or pushtoggle control
        if( Train->ggScndCtrl.is_push() ) {
            auto const activeposition { Train->ggScndCtrl.is_toggle() ? 1.f : 1.f };
            auto const neutralposition { Train->ggScndCtrl.is_toggle() ? 0.5f : 0.f };
            Train->ggScndCtrl.UpdateValue(
                ( ( Command.action == GLFW_RELEASE ) ? neutralposition : activeposition ),
                Train->dsbSwitch );
        }
        // potentially animate tempomat button
        if( ( Train->ggScndCtrlButton.is_push() )
         && ( Train->mvControlled->ScndCtrlPos <= 1 ) ) {
            Train->ggScndCtrlButton.UpdateValue(
                ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ),
                Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_secondcontrollerincreasefast( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        if( ( Train->mvControlled->EngineType == TEngineType::DieselElectric )
         && ( true == Train->mvControlled->ShuntMode ) ) {
            Train->mvControlled->AnPos = 1.0;
        }
        else {
            Train->mvControlled->IncScndCtrl( 2 );
        }
    }
}

void TTrain::OnCommand_notchingrelaytoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->mvOccupied->AutoRelayFlag ) {
            // turn on
            Train->mvOccupied->AutoRelaySwitch( true );
        }
        else {
            //turn off
            Train->mvOccupied->AutoRelaySwitch( false );
        }
    }
}

void TTrain::OnCommand_tempomattoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggScndCtrlButton.is_push() ) {
        // impulse switch
        if( Command.action == GLFW_RELEASE ) {
            // just move the button(s) back to default position
            // visual feedback
            Train->ggScndCtrlButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->ggScndCtrlOffButton.UpdateValue( 0.0, Train->dsbSwitch );
            return;
        }
        // glfw_press
        if( Train->mvControlled->ScndCtrlPos == 0 ) {
            // turn on if it's not active
            Train->mvControlled->IncScndCtrl( 1 );
            // visual feedback
            Train->ggScndCtrlButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // otherwise turn off
            Train->mvControlled->DecScndCtrl( 2 );
            // visual feedback
            if( Train->m_controlmapper.contains( "tempomatoff_sw:" ) ) {
                Train->ggScndCtrlOffButton.UpdateValue( 1.0, Train->dsbSwitch );
            }
            else {
                Train->ggScndCtrlButton.UpdateValue( 1.0, Train->dsbSwitch );
            }
        }
    }
    else {
        // two-state switch
        if( Command.action == GLFW_RELEASE ) { return; }

        if( Train->mvControlled->ScndCtrlPos == 0 ) {
            // turn on
            Train->mvControlled->IncScndCtrl( 1 );
            // visual feedback
            Train->ggScndCtrlButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            //turn off
            Train->mvControlled->DecScndCtrl( 2 );
            // visual feedback
            Train->ggScndCtrlButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_distancecounteractivate( TTrain *Train, command_data const &Command ) {
    // NOTE: distance meter activation button is presumed to be of impulse type
    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggDistanceCounterButton.UpdateValue( 1.0, Train->dsbSwitch );
        // activate or start anew
        Train->m_distancecounter = 0.f;
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggDistanceCounterButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_mucurrentindicatorothersourceactivate( TTrain *Train, command_data const &Command ) {

    if( Train->ggNextCurrentButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Current Indicator Source switch is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // turn on
        Train->ShowNextCurrent = true;
        // visual feedback
        Train->ggNextCurrentButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
    else if( Command.action == GLFW_RELEASE ) {
        //turn off
        Train->ShowNextCurrent = false;
        // visual feedback
        Train->ggNextCurrentButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_secondcontrollerdecrease( TTrain *Train, command_data const &Command ) {

    if( ( Train->mvControlled->EngineType == TEngineType::DieselElectric )
     && ( true == Train->mvControlled->ShuntMode ) ) {
        if( Command.action != GLFW_RELEASE ) {
            Train->mvControlled->AnPos = clamp(
                Train->mvControlled->AnPos - 0.025,
                0.0, 1.0 );
        }
    }
    else {
        // regular mode
        // push or pushtoggle control type
        if( Train->ggScndCtrl.is_push() ) {
            // basic push control can't decrease state, but pushtoggle can
            if( true == Train->ggScndCtrl.is_toggle() ) {
                if( Command.action == GLFW_PRESS ) {
                    // activate on press
                    Train->mvControlled->DecScndCtrl( 1 );
                }
            }
        }
        // toggle control type
        else {
            if( Command.action != GLFW_RELEASE ) {
                Train->mvControlled->DecScndCtrl( 1 );
            }
        }
        // HACK: potentially animate push or pushtoggle control
        if( Train->ggScndCtrl.is_push() ) {
            auto const activeposition { Train->ggScndCtrl.is_toggle() ? 0.f : 1.f };
            auto const neutralposition { Train->ggScndCtrl.is_toggle() ? 0.5f : 0.f };
            Train->ggScndCtrl.UpdateValue(
                ( ( Command.action == GLFW_RELEASE ) ? neutralposition : activeposition ),
                Train->dsbSwitch );
        }
        // potentially animate tempomat button
        if( ( Train->ggScndCtrlButton.is_push() )
         && ( Train->mvControlled->ScndCtrlPos <= 1 ) ) {
            if( Train->m_controlmapper.contains( "tempomatoff_sw:" ) ) {
                Train->ggScndCtrlOffButton.UpdateValue(
                    ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ),
                    Train->dsbSwitch );
            }
            else {
                Train->ggScndCtrlButton.UpdateValue(
                    ( ( Command.action == GLFW_RELEASE ) ? 0.f : 1.f ),
                    Train->dsbSwitch );
            }
        }
    }
}

void TTrain::OnCommand_secondcontrollerdecreasefast( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        if( ( Train->mvControlled->EngineType == TEngineType::DieselElectric )
         && ( true == Train->mvControlled->ShuntMode ) ) {
            Train->mvControlled->AnPos = 0.0;
        }
        else {
            Train->mvControlled->DecScndCtrl( 2 );
        }
    }
}

void TTrain::OnCommand_secondcontrollerset( TTrain *Train, command_data const &Command ) {

    auto const targetposition{ std::min<int>( Command.param1, Train->mvControlled->ScndCtrlPosNo ) };
    // HACK: potentially animate push or pushtoggle control
    if( Train->ggScndCtrl.is_push() ) {
        auto const activeposition {
            Train->ggScndCtrl.is_toggle() ?
                ( targetposition < Train->mvControlled->ScndCtrlPos ? 0.f :
                  targetposition > Train->mvControlled->ScndCtrlPos ? 1.f :
                  Train->ggScndCtrl.GetDesiredValue() ) : // leave the control in its current position if it hits the limit
                ( targetposition == 0 ? 0.f : 1.f ) };
        auto const neutralposition { Train->ggScndCtrl.is_toggle() ? 0.5f : 0.f };
        Train->ggScndCtrl.UpdateValue(
            ( ( Command.action == GLFW_RELEASE ) ? neutralposition : activeposition ),
            Train->dsbSwitch );
    }
    // update control value
    if( Command.action != GLFW_RELEASE ) {
        // on press or hold
        while( ( targetposition < Train->mvControlled->GetVirtualScndPos() )
            && ( true == Train->mvControlled->DecScndCtrl( 1 ) ) ) {
            // all work is done in the header
            ;
        }
        while( ( targetposition > Train->mvControlled->GetVirtualScndPos() )
            && ( true == Train->mvControlled->IncScndCtrl( 1 ) ) ) {
            // all work is done in the header
            ;
        }
    }
}

void TTrain::OnCommand_independentbrakeincrease( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {

        if( Train->mvOccupied->LocalBrake != TLocalBrake::ManualBrake ) {
            if( ( Train->ggJointCtrl.SubModel != nullptr )
             && ( Train->mvOccupied->MainCtrlPos > 0 ) ) {
                OnCommand_mastercontrollerdecrease( Train, Command );
            }
            else {
                Train->mvOccupied->IncLocalBrakeLevel( (Train->ggJointCtrl.SubModel == nullptr) ?
                    (Global.brake_speed * Command.time_delta * LocalBrakePosNo) : 1 );
                if( Train->ggJointCtrl.SubModel != nullptr ) {
                    Train->m_mastercontrollerinuse = true;
                }
            }
        }
    }
}

void TTrain::OnCommand_independentbrakeincreasefast( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {

        if( Train->mvOccupied->LocalBrake != TLocalBrake::ManualBrake ) {
            if( ( Train->ggJointCtrl.SubModel != nullptr )
                && ( Train->mvOccupied->MainCtrlPos > 0 ) ) {
                OnCommand_mastercontrollerdecreasefast( Train, Command );
            }
            else {
                Train->mvOccupied->IncLocalBrakeLevel( LocalBrakePosNo );
                if( Train->ggJointCtrl.SubModel != nullptr ) {
                    Train->m_mastercontrollerinuse = true;
                }
            }
        }
    }
}

void TTrain::OnCommand_independentbrakedecrease( TTrain *Train, command_data const &Command ) {

	if( Command.action != GLFW_RELEASE ) {

        if( ( Train->mvOccupied->LocalBrake != TLocalBrake::ManualBrake )
            // Ra 1014-06: AI potrafi zahamować pomocniczym mimo jego braku - odhamować jakoś trzeba
            // TODO: sort AI out so it doesn't do things it doesn't have equipment for
         || ( Train->mvOccupied->LocalBrakePosA > 0 ) ) {
            if( ( Train->ggJointCtrl.SubModel != nullptr )
             && ( Train->mvOccupied->LocalBrakePosA == 0.0 ) ) {
                OnCommand_mastercontrollerincrease( Train, Command );
            }
            else {
                Train->mvOccupied->DecLocalBrakeLevel(
                            (Train->ggJointCtrl.SubModel == nullptr) ?
                            (Global.brake_speed * Command.time_delta * LocalBrakePosNo) : 1);
                if( Train->ggJointCtrl.SubModel != nullptr ) {
                    Train->m_mastercontrollerinuse = true;
                }
            }
        }
    }
}

void TTrain::OnCommand_independentbrakedecreasefast( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {

        if( ( Train->mvOccupied->LocalBrake != TLocalBrake::ManualBrake )
            // Ra 1014-06: AI potrafi zahamować pomocniczym mimo jego braku - odhamować jakoś trzeba
            // TODO: sort AI out so it doesn't do things it doesn't have equipment for
         || ( Train->mvOccupied->LocalBrakePosA > 0 ) ) {
            if( ( Train->ggJointCtrl.SubModel != nullptr )
             && ( Train->mvOccupied->LocalBrakePosA == 0.0 ) ) {
                OnCommand_mastercontrollerincreasefast( Train, Command );
            }
            else {
                Train->mvOccupied->DecLocalBrakeLevel( LocalBrakePosNo );
                if( Train->ggJointCtrl.SubModel != nullptr ) {
                    Train->m_mastercontrollerinuse = true;
                }
            }
        }
    }
}

void TTrain::OnCommand_independentbrakeset( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {

        Train->mvOccupied->LocalBrakePosA = (
            clamp(
                Command.param1,
                0.0, 1.0 ) );
    }
/*
    Train->mvControlled->LocalBrakePos = (
        std::round(
            interpolate<double>(
                0.0,
                LocalBrakePosNo,
                clamp(
                    Command.param1,
                    0.0, 1.0 ) ) ) );
*/
}

void TTrain::OnCommand_independentbrakebailoff( TTrain *Train, command_data const &Command ) {

    if( false == Command.freefly ) {
        // TODO: check if this set of conditions can be simplified.
        // it'd be more flexible to have an attribute indicating whether bail off position is supported
        if( ( Train->mvControlled->TrainType != dt_EZT )
         && ( ( Train->mvControlled->EngineType == TEngineType::ElectricSeriesMotor )
           || ( Train->mvControlled->EngineType == TEngineType::DieselElectric )
           || ( Train->mvControlled->EngineType == TEngineType::ElectricInductionMotor ) )
         && ( Train->mvOccupied->BrakeCtrlPosNo > 0 ) ) {

            if( Command.action == GLFW_PRESS ) {
                // press or hold
                // visual feedback
                Train->ggReleaserButton.UpdateValue( 1.0, Train->dsbSwitch );

                Train->mvOccupied->BrakeReleaser( 1 );
            }
            else if( Command.action == GLFW_RELEASE ) {
                // release
                // visual feedback
                Train->ggReleaserButton.UpdateValue( 0.0, Train->dsbSwitch );

                Train->mvOccupied->BrakeReleaser( 0 );
            }
        }
    }
    else {
        // car brake handling, while in walk mode
		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle != nullptr ) {
            if( Command.action == GLFW_PRESS ) {
                // press or hold
                vehicle->MoverParameters->BrakeReleaser( 1 );
            }
            else if( Command.action == GLFW_RELEASE ) {
                // release
                vehicle->MoverParameters->BrakeReleaser( 0 );
            }
        }
    }
}

void TTrain::OnCommand_universalbrakebutton1(TTrain *Train, command_data const &Command) {

			if (Command.action == GLFW_PRESS) {
				// press or hold
				// visual feedback
				Train->ggUniveralBrakeButton1.UpdateValue(1.0, Train->dsbSwitch);

				Train->mvOccupied->UniversalBrakeButton(0,1);
			}
			else if (Command.action == GLFW_RELEASE) {
				// release
				// visual feedback
				Train->ggUniveralBrakeButton1.UpdateValue(0.0, Train->dsbSwitch);

				Train->mvOccupied->UniversalBrakeButton(0,0);
			}
}

void TTrain::OnCommand_universalbrakebutton2(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		// press or hold
		// visual feedback
		Train->ggUniveralBrakeButton2.UpdateValue(1.0, Train->dsbSwitch);

		Train->mvOccupied->UniversalBrakeButton(1, 1);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggUniveralBrakeButton2.UpdateValue(0.0, Train->dsbSwitch);

		Train->mvOccupied->UniversalBrakeButton(1, 0);
	}
}

void TTrain::OnCommand_universalbrakebutton3(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		// press or hold
		// visual feedback
		Train->ggUniveralBrakeButton3.UpdateValue(1.0, Train->dsbSwitch);

		Train->mvOccupied->UniversalBrakeButton(2, 1);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggUniveralBrakeButton3.UpdateValue(0.0, Train->dsbSwitch);

		Train->mvOccupied->UniversalBrakeButton(2, 0);
	}
}

void TTrain::OnCommand_trainbrakeincrease( TTrain *Train, command_data const &Command ) {
	if (Command.action == GLFW_REPEAT && Train->mvOccupied->BrakeHandle == TBrakeHandle::FV4a)
		Train->mvOccupied->BrakeLevelAdd( Global.brake_speed * Command.time_delta * Train->mvOccupied->BrakeCtrlPosNo );
	else if (Command.action == GLFW_PRESS && Train->mvOccupied->BrakeHandle != TBrakeHandle::FV4a)
		Train->set_train_brake( Train->mvOccupied->fBrakeCtrlPos + Global.fBrakeStep );
}

void TTrain::OnCommand_trainbrakedecrease( TTrain *Train, command_data const &Command ) {
	if (Command.action == GLFW_REPEAT && Train->mvOccupied->BrakeHandle == TBrakeHandle::FV4a)
		Train->mvOccupied->BrakeLevelAdd( -Global.brake_speed * Command.time_delta * Train->mvOccupied->BrakeCtrlPosNo );
	else if (Command.action == GLFW_PRESS && Train->mvOccupied->BrakeHandle != TBrakeHandle::FV4a)
		Train->set_train_brake( Train->mvOccupied->fBrakeCtrlPos - Global.fBrakeStep );
    else if (Command.action == GLFW_RELEASE) {
        // release
        Train->zero_charging_train_brake();
    }
}

void TTrain::OnCommand_trainbrakeset( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // press or hold
        Train->mvOccupied->BrakeLevelSet(
            interpolate(
                Train->mvOccupied->Handle->GetPos( bh_MIN ),
                Train->mvOccupied->Handle->GetPos( bh_MAX ),
                clamp(
                    Command.param1,
                    0.0, 1.0 ) ) );
    } else {
        // release
        Train->zero_charging_train_brake();
    }
}

void TTrain::OnCommand_trainbrakecharging( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {
        // press or hold
        Train->set_train_brake( -1 );
    }
    else {
        // release
        Train->zero_charging_train_brake();
    }
}

void TTrain::OnCommand_trainbrakerelease( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->set_train_brake( 0 );
    }
}

void TTrain::OnCommand_trainbrakefirstservice( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->set_train_brake( 1 );
    }
}

void TTrain::OnCommand_trainbrakeservice( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->set_train_brake( (
            Train->mvOccupied->BrakeCtrlPosNo / 2
            + ( Train->mvOccupied->BrakeHandle == TBrakeHandle::FV4a ?
                1 :
                0 ) ) );
    }
}

void TTrain::OnCommand_trainbrakefullservice( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->set_train_brake( Train->mvOccupied->BrakeCtrlPosNo - 1 );
    }
}

void TTrain::OnCommand_trainbrakehandleoff( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->set_train_brake( Train->mvOccupied->Handle->GetPos( bh_NP ) );
    }
}

void TTrain::OnCommand_trainbrakeemergency( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->set_train_brake( Train->mvOccupied->Handle->GetPos( bh_EB ) );
/*
        if( Train->mvOccupied->BrakeCtrlPosNo <= 0.1 ) {
            // hamulec bezpieczeństwa dla wagonów
            Train->mvOccupied->RadioStopFlag = true;
        }
*/
    }
}

void TTrain::OnCommand_trainbrakebasepressureincrease( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {

        switch( Train->mvOccupied->BrakeHandle ) {
            case TBrakeHandle::FV4a: {
                Train->mvOccupied->BrakeCtrlPos2 = clamp( Train->mvOccupied->BrakeCtrlPos2 - 0.01, -1.5, 2.0 );
                break;
            }
            default: {
                Train->mvOccupied->BrakeLevelAdd( 0.01 );
                break;
            }
        }
    }
}

void TTrain::OnCommand_trainbrakebasepressuredecrease( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {

        switch( Train->mvOccupied->BrakeHandle ) {
            case TBrakeHandle::FV4a: {
                Train->mvOccupied->BrakeCtrlPos2 = clamp( Train->mvOccupied->BrakeCtrlPos2 + 0.01, -1.5, 2.0 );
                break;
            }
            default: {
                Train->mvOccupied->BrakeLevelAdd( -0.01 );
                break;
            }
        }
    }
}

void TTrain::OnCommand_trainbrakebasepressurereset( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->mvOccupied->BrakeCtrlPos2 = 0;
    }
}

void TTrain::OnCommand_trainbrakeoperationtoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle == nullptr ) { return; }

        vehicle->MoverParameters->Hamulec->SetBrakeStatus( vehicle->MoverParameters->Hamulec->GetBrakeStatus() ^ b_dmg );
    }
}

void TTrain::OnCommand_manualbrakeincrease( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {

		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle == nullptr ) { return; }

        if( ( vehicle->MoverParameters->LocalBrake == TLocalBrake::ManualBrake )
         || ( vehicle->MoverParameters->MBrake == true ) ) {

            vehicle->MoverParameters->IncManualBrakeLevel( 1 );
        }
    }
}

void TTrain::OnCommand_manualbrakedecrease( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_RELEASE ) {

		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle == nullptr ) { return; }

        if( ( vehicle->MoverParameters->LocalBrake == TLocalBrake::ManualBrake )
         || ( vehicle->MoverParameters->MBrake == true ) ) {

            vehicle->MoverParameters->DecManualBrakeLevel( 1 );
        }
    }
}

void TTrain::OnCommand_alarmchaintoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        if( false == Train->mvOccupied->AlarmChainFlag ) {
            // pull
            Train->mvOccupied->AlarmChainSwitch( true );
            // visual feedback
            Train->ggAlarmChain.UpdateValue( 1.0 );
        }
        else {
            // release
            Train->mvOccupied->AlarmChainSwitch( false );
            // visual feedback
            Train->ggAlarmChain.UpdateValue( 0.0 );
        }
    }
}

void TTrain::OnCommand_wheelspinbrakeactivate( TTrain *Train, command_data const &Command ) {

    // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
    if( Train->ggAntiSlipButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Wheelspin Brake button is missing, or wasn't defined" );
        }
        return;
    }

    if( Train->mvOccupied->BrakeSystem != TBrakeSystem::ElectroPneumatic ) {
        // standard behaviour
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggAntiSlipButton.UpdateValue( 1.0, Train->dsbSwitch );

            // NOTE: system activation is (repeatedly) done in the train update routine
        }
        else if( Command.action == GLFW_RELEASE ) {
            // visual feedback
            Train->ggAntiSlipButton.UpdateValue( 0.0 );
        }
    }
    else {
        // electro-pneumatic, custom case
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggAntiSlipButton.UpdateValue( 1.0, Train->dsbPneumaticSwitch );

            if( ( Train->mvOccupied->BrakeHandle == TBrakeHandle::St113 )
             && ( Train->mvControlled->EpFuse == true ) ) {
                Train->mvOccupied->SwitchEPBrake( 1 );
            }
        }
        else if( Command.action == GLFW_RELEASE ) {
            // visual feedback
            Train->ggAntiSlipButton.UpdateValue( 0.0 );

            Train->mvOccupied->SwitchEPBrake( 0 );
        }
    }
}

void TTrain::OnCommand_sandboxactivate( TTrain *Train, command_data const &Command ) {

    // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
    if( Train->ggSandButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Sandbox activation button is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggSandButton.UpdateValue( 1.0, Train->dsbSwitch );

        Train->mvControlled->SandboxManual( true );
    }
    else if( Command.action == GLFW_RELEASE) {
        // visual feedback
        Train->ggSandButton.UpdateValue( 0.0 );

        Train->mvControlled->SandboxManual( false );
    }
}

void TTrain::OnCommand_autosandboxtoggle(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		if (false == Train->mvOccupied->SandDoseAutoAllow) {
			// turn on
			OnCommand_autosandboxactivate(Train, Command);
		}
		else {
			//turn off
			OnCommand_autosandboxdeactivate(Train, Command);
		}
	}
};

void TTrain::OnCommand_autosandboxactivate(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SandboxAutoAllow(true);
		Train->ggAutoSandButton.UpdateValue(1.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_autosandboxdeactivate(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SandboxAutoAllow(false);
		Train->ggAutoSandButton.UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_epbrakecontroltoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    auto const ispush{ ( static_cast<int>( Train->ggEPFuseButton.type() ) & static_cast<int>( TGaugeType::push ) ) != 0 };
    auto const istoggle{ ( static_cast<int>( Train->ggEPFuseButton.type() ) & static_cast<int>( TGaugeType::toggle ) ) != 0 };

    if( Command.action == GLFW_PRESS ) {
        if( istoggle ) {
            // switch state
            if( false == Train->mvOccupied->EpFuse ) {
                // turn on
                if( Train->mvOccupied->EpFuseSwitch( true ) ) {
                    // audio feedback
                    if( Train->dsbPneumaticSwitch ) {
                        Train->dsbPneumaticSwitch->play();
                    }
                };
            }
            else {
                //turn off
                Train->mvOccupied->EpFuseSwitch( false );
            }
        }
        else if( ispush ) {
            // potentially turn on
            if( Train->mvOccupied->EpFuseSwitch( true ) ) {
                // audio feedback
                if( Train->dsbPneumaticSwitch ) {
                    Train->dsbPneumaticSwitch->play();
                }
            };
        }
        // visual feedback
        Train->ggEPFuseButton.UpdateValue( (
            ispush ? 1.0f : // push or pushtoggle
            Train->mvOccupied->EpFuse ? 1.0f : 0.0f ), // toggle
            Train->dsbSwitch );
    }
    else if( Command.action == GLFW_RELEASE ) {
        if( ispush ) {
            // return the switch to neutral position
            Train->ggEPFuseButton.UpdateValue( 0.0f, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_trainbrakeoperationmodeincrease(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( ( Train->mvOccupied->BrakeOpModeFlag << 1 ) & Train->mvOccupied->BrakeOpModes ) != 0 ) {
            // next mode
            Train->mvOccupied->BrakeOpModeFlag <<= 1;
			// visual feedback
            Train->ggBrakeOperationModeCtrl.UpdateValue(
                Train->mvOccupied->BrakeOpModeFlag > 0 ?
                    std::log2( Train->mvOccupied->BrakeOpModeFlag ) :
                    0 ); // audio fallback
        }
	}
}

void TTrain::OnCommand_trainbrakeoperationmodedecrease(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( ( Train->mvOccupied->BrakeOpModeFlag >> 1 ) & Train->mvOccupied->BrakeOpModes ) != 0 ) {
			// previous mode
			Train->mvOccupied->BrakeOpModeFlag >>= 1;
			// visual feedback
            Train->ggBrakeOperationModeCtrl.UpdateValue(
                Train->mvOccupied->BrakeOpModeFlag > 0 ?
                    std::log2( Train->mvOccupied->BrakeOpModeFlag ) :
                    0 );
		}
	}
}

void TTrain::OnCommand_brakeactingspeedincrease( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle == nullptr ) { return; }

        if( ( vehicle->MoverParameters->BrakeDelayFlag & bdelay_M ) != 0 ) {
            // can't speed it up any more than this
            return;
        }
        auto const fasterbrakesetting = (
            vehicle->MoverParameters->BrakeDelayFlag < bdelay_R ?
                vehicle->MoverParameters->BrakeDelayFlag << 1 :
                vehicle->MoverParameters->BrakeDelayFlag | bdelay_M );

        Train->set_train_brake_speed( vehicle, fasterbrakesetting );
    }
}

void TTrain::OnCommand_brakeactingspeeddecrease( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle == nullptr ) { return; }

        if( vehicle->MoverParameters->BrakeDelayFlag == bdelay_G ) {
            // can't slow it down any more than this
            return;
        }
        auto const slowerbrakesetting = (
            vehicle->MoverParameters->BrakeDelayFlag < bdelay_M ?
                vehicle->MoverParameters->BrakeDelayFlag >> 1 :
                vehicle->MoverParameters->BrakeDelayFlag ^ bdelay_M );

        Train->set_train_brake_speed( vehicle, slowerbrakesetting );
    }
}

void TTrain::OnCommand_brakeactingspeedsetcargo( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle == nullptr ) { return; }

        Train->set_train_brake_speed( vehicle, bdelay_G );
    }
}

void TTrain::OnCommand_brakeactingspeedsetpassenger( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle == nullptr ) { return; }

        Train->set_train_brake_speed( vehicle, bdelay_P );
    }
}

void TTrain::OnCommand_brakeactingspeedsetrapid( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

		auto *vehicle{ Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle == nullptr ) { return; }

        Train->set_train_brake_speed( vehicle, bdelay_R );
    }
}

void TTrain::OnCommand_brakeloadcompensationincrease( TTrain *Train, command_data const &Command ) {

    if( ( true == Command.freefly )
     && ( Command.action == GLFW_PRESS ) ) {
		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle != nullptr ) {
            vehicle->MoverParameters->IncBrakeMult();
        }
    }
}

void TTrain::OnCommand_brakeloadcompensationdecrease( TTrain *Train, command_data const &Command ) {

    if( ( true == Command.freefly )
     && ( Command.action == GLFW_PRESS ) ) {
		auto *vehicle { Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
        if( vehicle != nullptr ) {
            vehicle->MoverParameters->DecBrakeMult();
        }
    }
}

void TTrain::OnCommand_mubrakingindicatortoggle( TTrain *Train, command_data const &Command ) {

    if( Train->ggSignallingButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Braking Indicator switch is missing, or wasn't defined" );
        }
        return;
    }
    if( Train->mvControlled->TrainType != dt_EZT ) {
        //
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->mvControlled->Signalling) {
            // turn on
            Train->mvControlled->Signalling = true;
            // visual feedback
            Train->ggSignallingButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            //turn off
            Train->mvControlled->Signalling = false;
            // visual feedback
            Train->ggSignallingButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_reverserincrease( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        // HACK: master controller position isn't set in occupied vehicle in E(D)MUs
        // so we do a manual check in relevant vehicle here
        if( false == Train->mvControlled->EIMDirectionChangeAllow() ) { return; }

        if( Train->mvOccupied->DirectionForward() ) {
            // aktualizacja skrajnych pojazdów w składzie
            if( ( Train->mvOccupied->DirActive )
             && ( Train->DynamicObject->Mechanik ) ) {

                Train->DynamicObject->Mechanik->DirectionChange();
            }
        }
    }
}

void TTrain::OnCommand_reverserdecrease( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        // HACK: master controller position isn't set in occupied vehicle in E(D)MUs
        // so we do a manual check in relevant vehicle here
        if( false == Train->mvControlled->EIMDirectionChangeAllow() ) { return; }

        if( Train->mvOccupied->DirectionBackward() ) {
            // aktualizacja skrajnych pojazdów w składzie
            if( ( Train->mvOccupied->DirActive )
             && ( Train->DynamicObject->Mechanik ) ) {

                Train->DynamicObject->Mechanik->DirectionChange();;
            }
        }
    }
}

void TTrain::OnCommand_reverserforwardhigh( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        // HACK: master controller position isn't set in occupied vehicle in E(D)MUs
        // so we do a manual check in relevant vehicle here
        if( false == Train->mvControlled->EIMDirectionChangeAllow() ) { return; }

        // HACK: try to move the reverser one position back, in case it's set to "high forward"
        OnCommand_reverserdecrease( Train, Command );

        if( Train->mvOccupied->DirActive < 1 ) {

            while( ( Train->mvOccupied->DirActive < 1 )
                && ( true == Train->mvOccupied->DirectionForward() ) ) {
                // all work is done in the header
            }
            // aktualizacja skrajnych pojazdów w składzie
            if( ( Train->mvOccupied->DirActive == 1 )
             && ( Train->DynamicObject->Mechanik ) ) {

                Train->DynamicObject->Mechanik->DirectionChange();
            }
        }
    OnCommand_reverserincrease( Train, Command );
    }
}

void TTrain::OnCommand_reverserforward( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        // HACK: master controller position isn't set in occupied vehicle in E(D)MUs
        // so we do a manual check in relevant vehicle here
        if( false == Train->mvControlled->EIMDirectionChangeAllow() ) { return; }

        // HACK: try to move the reverser one position back, in case it's set to "high forward"
        //OnCommand_reverserdecrease( Train, Command );
		// visual feedback
		Train->ggDirForwardButton.UpdateValue(1.0, Train->dsbSwitch);

        if( Train->mvOccupied->DirActive == 0 ) {

            while( ( Train->mvOccupied->DirActive < 1 )
                && ( true == Train->mvOccupied->DirectionForward() ) ) {
                // all work is done in the header
            }
            // aktualizacja skrajnych pojazdów w składzie
            if( ( Train->mvOccupied->DirActive == 1 )
             && ( Train->DynamicObject->Mechanik ) ) {

                Train->DynamicObject->Mechanik->DirectionChange();
            }
        }
    }
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggDirForwardButton.UpdateValue(0.0, Train->dsbSwitch);
	}
}

void TTrain::OnCommand_reverserneutral( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        // HACK: master controller position isn't set in occupied vehicle in E(D)MUs
        // so we do a manual check in relevant vehicle here
        if( false == Train->mvControlled->EIMDirectionChangeAllow() ) { return; }
		// visual feedback
		Train->ggDirNeutralButton.UpdateValue(1.0, Train->dsbSwitch);
        while( ( Train->mvOccupied->DirActive < 0 )
            && ( true == Train->mvOccupied->DirectionForward() ) ) {
            // all work is done in the header
        }
        while( ( Train->mvOccupied->DirActive > 0 )
            && ( true == Train->mvOccupied->DirectionBackward() ) ) {
            // all work is done in the header
        }
    }
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggDirNeutralButton.UpdateValue(0.0, Train->dsbSwitch);
	}
}

void TTrain::OnCommand_reverserbackward( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        // HACK: master controller position isn't set in occupied vehicle in E(D)MUs
        // so we do a manual check in relevant vehicle here
        if( false == Train->mvControlled->EIMDirectionChangeAllow() ) { return; }
        Train->ggDirBackwardButton.UpdateValue(1.0, Train->dsbSwitch);
        if( Train->mvOccupied->DirActive == 0 ) {

            while( ( Train->mvOccupied->DirActive > -1 )
                && ( true == Train->mvOccupied->DirectionBackward() ) ) {
                // all work is done in the header
            }
            // aktualizacja skrajnych pojazdów w składzie
            if( ( Train->mvOccupied->DirActive == -1 )
             && ( Train->DynamicObject->Mechanik ) ) {

                Train->DynamicObject->Mechanik->DirectionChange();
            }
        }
    }
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggDirBackwardButton.UpdateValue(0.0, Train->dsbSwitch);
	}
}

void TTrain::OnCommand_alerteracknowledge( TTrain *Train, command_data const &Command ) {
    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggSecurityResetButton.UpdateValue( 1.0, Train->dsbSwitch );

        if (Train->mvOccupied->TrainType == dt_EZT || Train->mvOccupied->DirActive != 0)
			Train->mvOccupied->SecuritySystem.acknowledge_press();
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggSecurityResetButton.UpdateValue( 0.0 );

        if (Train->mvOccupied->TrainType == dt_EZT || Train->mvOccupied->DirActive != 0)
			Train->mvOccupied->SecuritySystem.acknowledge_release();
    }
}

void TTrain::OnCommand_cabsignalacknowledge( TTrain *Train, command_data const &Command ) {
	// TODO: visual feedback
	if( Command.action == GLFW_PRESS ) {
		Train->mvOccupied->SecuritySystem.cabsignal_reset();
	}
}

void TTrain::OnCommand_batterytoggle( TTrain *Train, command_data const &Command )
{
    if( Command.action != GLFW_REPEAT ) {
        // keep the switch from flipping back and forth if key is held down
        if( false == Train->mvOccupied->Power24vIsAvailable ) {
            // turn on
            OnCommand_batteryenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_batterydisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_batteryenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggBatteryButton.UpdateValue( 1.0f, Train->dsbSwitch );
        Train->ggBatteryOnButton.UpdateValue( 1.0f, Train->dsbSwitch );

        Train->mvOccupied->BatterySwitch( true );

        // side-effects
        if( Train->mvOccupied->LightsPosNo > 0 ) {
            Train->Dynamic()->SetLights();
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        if( Train->ggBatteryButton.type() == TGaugeType::push ) {
            // return the switch to neutral position
            Train->ggBatteryButton.UpdateValue( 0.5f );
        }
        Train->ggBatteryOnButton.UpdateValue( 0.0f, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_batterydisable( TTrain *Train, command_data const &Command ) {
    // TBD, TODO: ewentualnie zablokować z FIZ, np. w samochodach się nie odłącza akumulatora
    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggBatteryButton.UpdateValue( 0.0f, Train->dsbSwitch );
        Train->ggBatteryOffButton.UpdateValue( 1.0f, Train->dsbSwitch );

        Train->mvOccupied->BatterySwitch( false );
    }
    else if( Command.action == GLFW_RELEASE ) {
        if( Train->ggBatteryButton.type() == TGaugeType::push ) {
            // return the switch to neutral position
            Train->ggBatteryButton.UpdateValue( 0.5f );
        }
        Train->ggBatteryOffButton.UpdateValue( 0.0f, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_pantographtogglefront( TTrain *Train, command_data const &Command ) {

    // HACK: presence of pantograph selector prevents manual operation of the individual valves
    if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const &pantograph { Train->mvPantographUnit->Pantographs[ end::front ] };
        auto const state {
            pantograph.valve.is_enabled
         || pantograph.is_active }; // fallback for impulse switches
        if( state ) {
            OnCommand_pantographlowerfront( Train, Command );
        }
        else {
            OnCommand_pantographraisefront( Train, Command );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // impulse switches return automatically to neutral position
        if( Train->mvOccupied->PantSwitchType == "impulse" ) {
            auto const ismanual { Train->iCabn == 0 };
            Train->mvOccupied->OperatePantographValve(
                end::front,
                operation_t::none,
                ( ismanual ?
                    range_t::local :
                    range_t::consist ) );
        }
    }
}

void TTrain::OnCommand_pantographtogglerear( TTrain *Train, command_data const &Command ) {

    // HACK: presence of pantograph selector prevents manual operation of the individual valves
    if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const &pantograph { Train->mvPantographUnit->Pantographs[ end::rear ] };
        auto const state {
            pantograph.valve.is_enabled
         || pantograph.is_active }; // fallback for impulse switches
        if( state ) {
            OnCommand_pantographlowerrear( Train, Command );
        }
        else {
            OnCommand_pantographraiserear( Train, Command );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // impulse switches return automatically to neutral position
        if( Train->mvOccupied->PantSwitchType == "impulse" ) {
            auto const ismanual { Train->iCabn == 0 };
            Train->mvOccupied->OperatePantographValve(
                end::rear,
                operation_t::none,
                ( ismanual ?
                    range_t::local :
                    range_t::consist ) );
        }
    }
}

void TTrain::OnCommand_pantographraisefront( TTrain *Train, command_data const &Command ) {

    // HACK: presence of pantograph selector prevents manual operation of the individual valves
    if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; }
    // prevent operation without submodel outside of engine compartment
    if( ( Train->iCabn != 0 )
     && ( false == Train->m_controlmapper.contains( "pantfront_sw:" ) ) ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        // HACK: don't propagate pantograph commands issued from engine compartment, these are presumed to be manually moved levers
        auto const ismanual { Train->iCabn == 0 };
        Train->mvOccupied->OperatePantographValve(
            end::front,
            ( Train->mvOccupied->PantSwitchType == "impulse" ?
                operation_t::enable_on :
                operation_t::enable ),
            ( ismanual ?
                range_t::local :
                range_t::consist ) );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch
        OnCommand_pantographtogglefront( Train, Command );
    }
}

void TTrain::OnCommand_pantographraiserear( TTrain *Train, command_data const &Command ) {

    // HACK: presence of pantograph selector prevents manual operation of the individual valves
    if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; }
    // prevent operation without submodel outside of engine compartment
    if( ( Train->iCabn != 0 )
     && ( false == Train->m_controlmapper.contains( "pantrear_sw:" ) ) ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        // HACK: don't propagate pantograph commands issued from engine compartment, these are presumed to be manually moved levers
        auto const ismanual { Train->iCabn == 0 };
        Train->mvOccupied->OperatePantographValve(
            end::rear,
            ( Train->mvOccupied->PantSwitchType == "impulse" ?
                operation_t::enable_on :
                operation_t::enable ),
            ( ismanual ?
                range_t::local :
                range_t::consist ) );
   }
    else if( Command.action == GLFW_RELEASE ) {
        // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch
        OnCommand_pantographtogglerear( Train, Command );
    }
}

void TTrain::OnCommand_pantographlowerfront( TTrain *Train, command_data const &Command ) {

    // HACK: presence of pantograph selector prevents manual operation of the individual valves
    if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; }
    // prevent operation without submodel outside of engine compartment
    if( ( Train->iCabn != 0 )
     && ( false == Train->m_controlmapper.contains(
         Train->mvOccupied->PantSwitchType == "impulse" ?
            "pantfrontoff_sw:" :
            "pantfront_sw:" ) ) ) {
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        // HACK: don't propagate pantograph commands issued from engine compartment, these are presumed to be manually moved levers
        auto const ismanual { Train->iCabn == 0 };
        Train->mvOccupied->OperatePantographValve(
            end::front,
            ( Train->mvOccupied->PantSwitchType == "impulse" ?
                operation_t::disable_on :
                operation_t::disable ),
            ( ismanual ?
                range_t::local :
                range_t::consist ) );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch
        OnCommand_pantographtogglefront( Train, Command );
    }
}

void TTrain::OnCommand_pantographlowerrear( TTrain *Train, command_data const &Command ) {

    // HACK: presence of pantograph selector prevents manual operation of the individual valves
    if( Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; }

    if( ( Train->iCabn != 0 )
     && ( false == Train->m_controlmapper.contains(
         Train->mvOccupied->PantSwitchType == "impulse" ?
            "pantrearoff_sw:" :
            "pantrear_sw:" ) ) ) {
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        // HACK: don't propagate pantograph commands issued from engine compartment, these are presumed to be manually moved levers
        auto const ismanual { Train->iCabn == 0 };
        Train->mvOccupied->OperatePantographValve(
            end::rear,
            ( Train->mvOccupied->PantSwitchType == "impulse" ?
                operation_t::disable_on :
                operation_t::disable ),
            ( ismanual ?
                range_t::local :
                range_t::consist ) );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch
        OnCommand_pantographtogglerear( Train, Command );
    }
}

void TTrain::OnCommand_pantographlowerall( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggPantAllDownButton.SubModel == nullptr ) {
        // TODO: expand definition of cab controls so we can know if the control is present without testing for presence of 3d switch
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Lower All Pantographs switch is missing, or wasn't defined" );
        }
        return;
    }

    if( Train->ggPantAllDownButton.type() == TGaugeType::toggle ) {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            Train->mvPantographUnit->DropAllPantographs( false == Train->mvPantographUnit->PantAllDown );
            // visual feedback
            Train->ggPantAllDownButton.UpdateValue( ( Train->mvPantographUnit->PantAllDown ? 1.0 : 0.0 ), Train->dsbSwitch );
        }
    }
    else {
        // impulse switch
        Train->mvControlled->DropAllPantographs( Command.action == GLFW_PRESS );
        // visual feedback
        Train->ggPantAllDownButton.UpdateValue( ( Command.action == GLFW_PRESS ? 1.0 : 0.0 ), Train->dsbSwitch );
    }
}

void TTrain::OnCommand_pantographselectnext( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_PRESS ) { return; }

    if( false == Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; }

    Train->change_pantograph_selection( 1 );
}

void TTrain::OnCommand_pantographselectprevious( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_PRESS )  { return; }

    if( false == Train->m_controlmapper.contains( "pantselect_sw:" ) ) { return; }

    Train->change_pantograph_selection( -1 );
}

void TTrain::OnCommand_pantographtoggleselected( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const state {
            Train->mvPantographUnit->PantsValve.is_enabled
         || Train->mvPantographUnit->PantsValve.is_active }; // fallback for impulse switches
        if( state ) {
            OnCommand_pantographlowerselected( Train, Command );
        }
        else {
            OnCommand_pantographraiseselected( Train, Command );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // impulse switches return automatically to neutral position
        if( Train->m_controlmapper.contains( "pantselectedoff_sw:" ) ) {
            // two buttons setup
            if( Train->ggPantSelectedButton.type() != TGaugeType::toggle ) {
                Train->mvOccupied->OperatePantographsValve( operation_t::enable_off );
                // visual feedback
                Train->ggPantSelectedButton.UpdateValue( 0.0, Train->dsbSwitch );
            }
            if( Train->ggPantSelectedDownButton.type() != TGaugeType::toggle ) {
                Train->mvOccupied->OperatePantographsValve( operation_t::disable_off );
                // visual feedback
                Train->ggPantSelectedDownButton.UpdateValue( 0.0, Train->dsbSwitch );
            }
        }
        else {
            if( Train->ggPantSelectedButton.type() != TGaugeType::toggle ) {
                // special case, just one impulse switch controlling both states
                // with neutral position mid-way
                Train->mvOccupied->OperatePantographsValve( operation_t::none );
                // visual feedback
                Train->ggPantSelectedButton.UpdateValue( 0.5, Train->dsbSwitch );
            }
        }
    }
}

void TTrain::OnCommand_pantographraiseselected( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // raise selected
        Train->mvOccupied->OperatePantographsValve(
            Train->ggPantSelectedButton.type() != TGaugeType::toggle ?
                operation_t::enable_on :
                operation_t::enable );
        // visual feedback
        Train->ggPantSelectedButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch
        OnCommand_pantographtoggleselected( Train, Command );
    }
}

void TTrain::OnCommand_pantographlowerselected( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // lower selected
        Train->mvOccupied->OperatePantographsValve(
            Train->ggPantSelectedDownButton.type() != TGaugeType::toggle ?
                operation_t::disable_on :
                operation_t::disable );
        // visual feedback
        if( Train->m_controlmapper.contains( "pantselectedoff_sw:" ) ) {
            // two button setup
            Train->ggPantSelectedDownButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // single button
            Train->ggPantSelectedButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch
        OnCommand_pantographtoggleselected( Train, Command );
    }
}

void TTrain::update_pantograph_valves() {

    auto const &presets { mvOccupied->PantsPreset.first };
    auto &selection { mvOccupied->PantsPreset.second[ cab_to_end() ] };

    auto const preset { presets[ selection ] - '0' };
    auto const swapends { cab_to_end() != end::front };
    // check desired states for both pantographs; value: whether the pantograph should be raised
    auto const frontstate { preset & ( swapends ? 2 : 1 ) };
    auto const rearstate { preset & ( swapends ? 1 : 2 ) };
    mvOccupied->OperatePantographValve( end::front, ( frontstate ? operation_t::enable : operation_t::disable ) );
    mvOccupied->OperatePantographValve( end::rear, ( rearstate ? operation_t::enable : operation_t::disable ) );
}

void TTrain::change_pantograph_selection( int const Change ) {

    auto const &presets { mvOccupied->PantsPreset.first };
    auto &selection { mvOccupied->PantsPreset.second[ cab_to_end() ] };
    auto const initialstate { selection };
    selection = clamp<int>( selection + Change, 0, std::max<int>( presets.size() - 1, 0 ) );

    if( selection == initialstate ) { return; } // no change, nothing to do

    // potentially adjust pantograph valves to match the new state
    if( false == m_controlmapper.contains( "pantvalves_sw:" ) ) {
        update_pantograph_valves();
    }
}

void TTrain::OnCommand_pantographvalvesupdate( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // implement action
        Train->update_pantograph_valves();
        // visual feedback
        Train->ggPantValvesButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        // NOTE: pantvalves_sw: is a specialized button, with no toggle behavior support
        Train->ggPantValvesButton.UpdateValue( 0.5, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_pantographvalvesoff( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // implement action
        Train->mvOccupied->OperatePantographValve( end::front, operation_t::disable );
        Train->mvOccupied->OperatePantographValve( end::rear, operation_t::disable );
        // visual feedback
        Train->ggPantValvesButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        // NOTE: pantvalves_sw: is a specialized button, with no toggle behavior support
        Train->ggPantValvesButton.UpdateValue( 0.5, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_pantographcompressorvalvetoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only react to press
        if( Train->mvControlled->bPantKurek3 == false ) {
            // connect pantographs with primary tank
            OnCommand_pantographcompressorvalveenable( Train, Command );
        }
        else {
            // connect pantograps with pantograph compressor
            OnCommand_pantographcompressorvalvedisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_pantographcompressorvalveenable( TTrain *Train, command_data const &Command ) {

    auto const valveispresent {
        ( Train->ggPantCompressorValve.SubModel != nullptr )
     || ( ( Train->mvOccupied == Train->mvPantographUnit )
       && ( Train->iCabn == 0 ) ) };

    if( false == valveispresent ) {
        // tylko w maszynowym, unless actual device is present
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only react to press
        // connect pantographs with primary tank
        Train->mvControlled->bPantKurek3 = true;
        // visual feedback:
        Train->ggPantCompressorValve.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_pantographcompressorvalvedisable( TTrain *Train, command_data const &Command ) {

    auto const valveispresent {
        ( Train->ggPantCompressorValve.SubModel != nullptr )
     || ( ( Train->mvOccupied == Train->mvPantographUnit )
       && ( Train->iCabn == 0 ) ) };

    if( false == valveispresent ) {
        // tylko w maszynowym, unless actual device is present
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only react to press
        // connect pantograps with pantograph compressor
        Train->mvControlled->bPantKurek3 = false;
        // visual feedback:
        Train->ggPantCompressorValve.UpdateValue( 1.0 );
    }
}

void TTrain::OnCommand_pantographcompressoractivate( TTrain *Train, command_data const &Command ) {

    // tylko w maszynowym, unless actual device is present
    auto const switchispresent {
        ( Train->m_controlmapper.contains( "pantcompressor_sw:" ) )
     || ( ( Train->mvOccupied == Train->mvPantographUnit )
       && ( Train->iCabn == 0 ) ) };
    if( false == switchispresent ) {
        return;
    }

    if( Command.action != GLFW_RELEASE ) {
        // press or hold to activate
        if( ( Train->mvPantographUnit->PantPress < 4.8 )
         && ( true == Train->mvPantographUnit->Power24vIsAvailable ) ) {
            // needs live power source and low enough pressure to work
            Train->mvPantographUnit->PantCompFlag = true;
        }
        // visual feedback
        Train->ggPantCompressorButton.UpdateValue( 1.0 );
    }
    else {
        // release to disable
        Train->mvPantographUnit->PantCompFlag = false;
        // visual feedback
        Train->ggPantCompressorButton.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_linebreakertoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // press or hold...
        if( Train->m_linebreakerstate == 0 ) {
            // ...to close the circuit
            // NOTE: bit of a dirty shortcut here
            OnCommand_linebreakerclose( Train, Command );
        }
        else if( Train->m_linebreakerstate == 1 ) {
            // ...to open the circuit
            OnCommand_linebreakeropen( Train, Command );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // release...
        if( ( Train->ggMainOnButton.SubModel != nullptr )
         || ( Train->ggMainButton.type() != TGaugeType::toggle ) ) {
            // only impulse switches react to release events
            // NOTE: we presume dedicated state switch is of impulse type
            if( Train->m_linebreakerstate == 0 ) {
                // ...after opening circuit, or holding for too short time to close it
                OnCommand_linebreakeropen( Train, Command );
            }
            else {
                // ...after closing the circuit
                // NOTE: bit of a dirty shortcut here
                OnCommand_linebreakerclose( Train, Command );
            }
        }
        // HACK: ignition key ignores lack of submodel, so we can start vehicles without any modeled controls
        Train->ggIgnitionKey.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_linebreakeropen( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        if( Train->ggMainOffButton.SubModel != nullptr ) {
            Train->ggMainOffButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else if( Train->ggMainButton.SubModel != nullptr ) {
            Train->ggMainButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
        else if( Train->ggMainOnButton.SubModel != nullptr ) {
            // NOTE: legacy behaviour, for vehicles equipped only with impulse close switch
            // it doesn't make any real sense to animate this one, but some people can't get over how there's no visual reaction to their keypress
            Train->ggMainOnButton.UpdateValue( 1.0, Train->dsbSwitch );
            return;
        }
        // play sound immediately when the switch is hit, not after release
        Train->fMainRelayTimer = 0.0f;

        if( Train->m_linebreakerstate == 0 ) { return; } // already in the desired state

        if( true == Train->mvControlled->MainSwitch( false ) ) {
            Train->m_linebreakerstate = 0;
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        // we don't exactly know which of the two buttons was used, so reset both
        // for setup with two separate swiches
        if( Train->ggMainOnButton.SubModel != nullptr ) {
            Train->ggMainOnButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
        if( Train->ggMainOffButton.SubModel != nullptr ) {
            Train->ggMainOffButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
        // and the two-state switch too, for good measure
        if( Train->ggMainButton.SubModel != nullptr ) {
            Train->ggMainButton.UpdateValue( (
                Train->ggMainButton.type() != TGaugeType::toggle ?
                    0.5 :
                    0.0 ),
                Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_linebreakerclose( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        if( Train->ggMainOnButton.SubModel != nullptr ) {
            // two separate switches to close and break the circuit
            Train->ggMainOnButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else if( Train->ggMainButton.SubModel != nullptr ) {
            // single two-state switch
            Train->ggMainButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // no switch capable of doing the job
            // HACK: ignition key ignores lack of submodel, so we can start vehicles without any modeled controls
            Train->ggIgnitionKey.UpdateValue( 1.0 );
            return;
        }
        // the actual closing of the line breaker is handled in the train update routine
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        if( Train->ggMainOnButton.SubModel != nullptr ) {
            // setup with two separate switches
            Train->ggMainOnButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
        else if( Train->ggMainButton.SubModel != nullptr ) {
            if( Train->ggMainButton.type() != TGaugeType::toggle ) {
                Train->ggMainButton.UpdateValue( 0.5, Train->dsbSwitch );
            }
        }

        if( Train->m_linebreakerstate == 1 ) { return; } // already in the desired state

        if( Train->m_linebreakerstate == 2 ) {
            // we don't need to start the diesel twice, but the other types (with impulse switch setup) still need to be launched
            // NOTE: this behaviour should depend on MainOnButton presence and type_delayed
            // TODO: change it when/if vehicle definition files get their proper switch types
            if( Train->mvControlled->EngineType == TEngineType::ElectricSeriesMotor ) {
                // try to finalize state change of the line breaker, set the state based on the outcome
                Train->m_linebreakerstate = (
                    Train->mvControlled->MainSwitch( true ) ?
                        1 :
                        0 );
            }
        }
        // on button release reset the closing timer
        Train->fMainRelayTimer = 0.0f;
    }
}

void TTrain::OnCommand_fuelpumptoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggFuelPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no off button so we always try to turn it on
        OnCommand_fuelpumpenable( Train, Command );
    }
    else {
        // two-state switch
        if( Command.action == GLFW_RELEASE ) { return; }

        if( false == Train->mvControlled->FuelPump.is_enabled ) {
            // turn on
            OnCommand_fuelpumpenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_fuelpumpdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_fuelpumpenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggFuelPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggFuelPumpButton.UpdateValue( 1.0, Train->dsbSwitch );
            Train->mvControlled->FuelPumpSwitch( true );
        }
        else if( Command.action == GLFW_RELEASE ) {
            // visual feedback
            Train->ggFuelPumpButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->mvControlled->FuelPumpSwitch( false );
        }
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggFuelPumpButton.UpdateValue( 1.0, Train->dsbSwitch );
            Train->mvControlled->FuelPumpSwitch( true );
            Train->mvControlled->FuelPumpSwitchOff( false );
        }
    }
}

void TTrain::OnCommand_fuelpumpdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggFuelPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no disable return type switch
        return;
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggFuelPumpButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->mvControlled->FuelPumpSwitch( false );
            Train->mvControlled->FuelPumpSwitchOff( true );
        }
    }
}

void TTrain::OnCommand_oilpumptoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggOilPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no off button so we always try to turn it on
        OnCommand_oilpumpenable( Train, Command );
    }
    else {
        // two-state switch
        if( Command.action == GLFW_RELEASE ) { return; }

        if( false == Train->mvControlled->OilPump.is_enabled ) {
            // turn on
            OnCommand_oilpumpenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_oilpumpdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_oilpumpenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggOilPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggOilPumpButton.UpdateValue( 1.0, Train->dsbSwitch );
            Train->mvControlled->OilPumpSwitch( true );
        }
        else if( Command.action == GLFW_RELEASE ) {
            // visual feedback
            Train->ggOilPumpButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->mvControlled->OilPumpSwitch( false );
        }
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggOilPumpButton.UpdateValue( 1.0, Train->dsbSwitch );
            Train->mvControlled->OilPumpSwitch( true );
            Train->mvControlled->OilPumpSwitchOff( false );
        }
    }
}

void TTrain::OnCommand_oilpumpdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggOilPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no disable return type switch
        return;
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggOilPumpButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->mvControlled->OilPumpSwitch( false );
            Train->mvControlled->OilPumpSwitchOff( true );
        }
    }
}

void TTrain::OnCommand_waterheaterbreakertoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->mvControlled->WaterHeater.breaker ) {
            // turn on
            OnCommand_waterheaterbreakerclose( Train, Command );
        }
        else {
            //turn off
            OnCommand_waterheaterbreakeropen( Train, Command );
        }
    }
}

void TTrain::OnCommand_waterheaterbreakerclose( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggWaterHeaterBreakerButton.UpdateValue( 1.0, Train->dsbSwitch );

        if( true == Train->mvControlled->WaterHeater.breaker ) { return; } // already enabled

        Train->mvControlled->WaterHeaterBreakerSwitch( true );
    }
}

void TTrain::OnCommand_waterheaterbreakeropen( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggWaterHeaterBreakerButton.UpdateValue( 0.0, Train->dsbSwitch );

        if( false == Train->mvControlled->WaterHeater.breaker ) { return; } // already enabled

        Train->mvControlled->WaterHeaterBreakerSwitch( false );
    }
}

void TTrain::OnCommand_waterheatertoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->mvControlled->WaterHeater.is_enabled ) {
            // turn on
            OnCommand_waterheaterenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_waterheaterdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_waterheaterenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggWaterHeaterButton.UpdateValue( 1.0, Train->dsbSwitch );

        if( true == Train->mvControlled->WaterHeater.is_enabled ) { return; } // already enabled

        Train->mvControlled->WaterHeaterSwitch( true );
    }
}

void TTrain::OnCommand_waterheaterdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggWaterHeaterButton.UpdateValue( 0.0, Train->dsbSwitch );

        if( false == Train->mvControlled->WaterHeater.is_enabled ) { return; } // already disabled

        Train->mvControlled->WaterHeaterSwitch( false );
    }
}

void TTrain::OnCommand_waterpumpbreakertoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->mvControlled->WaterPump.breaker ) {
            // turn on
            OnCommand_waterpumpbreakerclose( Train, Command );
        }
        else {
            //turn off
            OnCommand_waterpumpbreakeropen( Train, Command );
        }
    }
}

void TTrain::OnCommand_waterpumpbreakerclose( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggWaterPumpBreakerButton.UpdateValue( 1.0, Train->dsbSwitch );

        if( true == Train->mvControlled->WaterPump.breaker ) { return; } // already enabled

        Train->mvControlled->WaterPumpBreakerSwitch( true );
    }
}

void TTrain::OnCommand_waterpumpbreakeropen( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggWaterPumpBreakerButton.UpdateValue( 0.0, Train->dsbSwitch );

        if( false == Train->mvControlled->WaterPump.breaker ) { return; } // already enabled

        Train->mvControlled->WaterPumpBreakerSwitch( false );
    }
}

void TTrain::OnCommand_waterpumptoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggWaterPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no off button so we always try to turn it on
        OnCommand_waterpumpenable( Train, Command );
    }
    else {
        // two-state switch
        if( Command.action == GLFW_RELEASE ) { return; }

        if( false == Train->mvControlled->WaterPump.is_enabled ) {
            // turn on
            OnCommand_waterpumpenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_waterpumpdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_waterpumpenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggWaterPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggWaterPumpButton.UpdateValue( 1.0, Train->dsbSwitch );
            Train->mvControlled->WaterPumpSwitch( true );
        }
        else if( Command.action == GLFW_RELEASE ) {
            // visual feedback
            Train->ggWaterPumpButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->mvControlled->WaterPumpSwitch( false );
        }
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggWaterPumpButton.UpdateValue( 1.0, Train->dsbSwitch );
            Train->mvControlled->WaterPumpSwitch( true );
            Train->mvControlled->WaterPumpSwitchOff( false );
        }
    }
}

void TTrain::OnCommand_waterpumpdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggWaterPumpButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no disable return type switch
        return;
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggWaterPumpButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->mvControlled->WaterPumpSwitch( false );
            Train->mvControlled->WaterPumpSwitchOff( true );
        }
    }
}

void TTrain::OnCommand_watercircuitslinktoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->mvControlled->WaterCircuitsLink ) {
            // turn on
            OnCommand_watercircuitslinkenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_watercircuitslinkdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_watercircuitslinkenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggWaterCircuitsLinkButton.UpdateValue( 1.0, Train->dsbSwitch );

        if( true == Train->mvControlled->WaterCircuitsLink ) { return; } // already enabled

        Train->mvControlled->WaterCircuitsLinkSwitch( true );
    }
}

void TTrain::OnCommand_watercircuitslinkdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggWaterCircuitsLinkButton.UpdateValue( 0.0, Train->dsbSwitch );

        if( false == Train->mvControlled->WaterCircuitsLink ) { return; } // already disabled

        Train->mvControlled->WaterCircuitsLinkSwitch( false );
    }
}

void TTrain::OnCommand_convertertoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const overloadrelayisopen { (
            Train->Dynamic()->Mechanik != nullptr ?
                Train->Dynamic()->Mechanik->IsAnyConverterOverloadRelayOpen :
                Train->mvOccupied->ConvOvldFlag ) };

        if( Train->mvOccupied->ConvSwitchType != "impulse" ?
                Train->ggConverterButton.GetValue() < 0.5 :
                ( ( false == Train->mvOccupied->Power110vIsAvailable )
               && ( false == overloadrelayisopen ) ) ) {
            // turn on
            OnCommand_converterenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_converterdisable( Train, Command );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // on button release...
        if( Train->mvOccupied->ConvSwitchType == "impulse" ) {
            // ...return switches to start position if applicable
            Train->ggConverterButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->ggConverterOffButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_converterenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggConverterButton.UpdateValue( 1.0, Train->dsbSwitch );

        // impulse type switch has no effect if there's no power
        // NOTE: this is most likely setup wrong, but the whole thing is smoke and mirrors anyway
        if( ( Train->mvOccupied->ConvSwitchType != "impulse" )
         || ( Train->mvControlled->Mains ) ) {
            // won't start if the line breaker button is still held
            Train->mvOccupied->ConverterSwitch( true );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // potentially reset impulse switch position, using shared code branch
        OnCommand_convertertoggle( Train, Command );
    }
}

void TTrain::OnCommand_converterdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggConverterButton.UpdateValue( 0.0, Train->dsbSwitch );
        if( Train->ggConverterOffButton.SubModel != nullptr ) {
            Train->ggConverterOffButton.UpdateValue( 1.0, Train->dsbSwitch );
        }

        Train->mvOccupied->ConverterSwitch( false );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // potentially reset impulse switch position, using shared code branch
        OnCommand_convertertoggle( Train, Command );
    }
}

void TTrain::OnCommand_convertertogglelocal( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->ConverterStart == start_t::automatic ) {
        // let the automatic thing do its automatic thing...
        return;
    }
    if( Train->ggConverterLocalButton.SubModel == nullptr ) {
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( false == Train->mvOccupied->ConverterAllowLocal )
         && ( Train->ggConverterLocalButton.GetValue() < 0.5 ) ) {
            // turn on
            // visual feedback
            Train->ggConverterLocalButton.UpdateValue( 1.0, Train->dsbSwitch );
            // effect
            Train->mvOccupied->ConverterAllowLocal = true;
/*
            if( true == Train->mvControlled->ConverterSwitch( true, range::local ) ) {
                // side effects
                // control the compressor, if it's paired with the converter
                if( Train->mvControlled->CompressorPower == 2 ) {
                    // hunter-091012: tak jest poprawnie
                    Train->mvControlled->CompressorSwitch( true, range::local );
                }
            }
*/
        }
        else {
            //turn off
            // visual feedback
            Train->ggConverterLocalButton.UpdateValue( 0.0, Train->dsbSwitch );
            // effect
            Train->mvOccupied->ConverterAllowLocal = false;
/*
            if( true == Train->mvControlled->ConverterSwitch( false, range::local ) ) {
                // side effects
                // control the compressor, if it's paired with the converter
                if( Train->mvControlled->CompressorPower == 2 ) {
                    // hunter-091012: tak jest poprawnie
                    Train->mvControlled->CompressorSwitch( false, range::local );
                }
                // if there's no (low voltage) power source left, drop pantographs
                if( false == Train->mvControlled->Battery ) {
                    Train->mvControlled->PantFront( false, range::local );
                    Train->mvControlled->PantRear( false, range::local );
                }
            }
*/
        }
    }
}

void TTrain::OnCommand_converteroverloadrelayreset( TTrain *Train, command_data const &Command ) {

    if( Train->ggConverterFuseButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Converter Overload Relay Reset button is missing, or wasn't defined" );
        }
//        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggConverterFuseButton.UpdateValue( 1.0, Train->dsbSwitch );

        Train->mvControlled->RelayReset( relay_t::primaryconverteroverload );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggConverterFuseButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_compressortoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const compressorisenabled { (
            Train->Dynamic()->Mechanik ?
                Train->Dynamic()->Mechanik->IsAnyCompressorEnabled :
                Train->mvOccupied->CompressorAllow ) };

        if( false == compressorisenabled ) {
            // turn on
            OnCommand_compressorenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_compressordisable( Train, Command );
        }
    }
/*
    // disabled because we don't have yet support for compressor switch type definition
    else if( Command.action == GLFW_RELEASE ) {
        // on button release...
        if( Train->mvOccupied->CompSwitchType == "impulse" ) {
            // ...return switches to start position if applicable
            Train->ggCompressorButton.UpdateValue( 0.0, Train->dsbSwitch );
            Train->ggCompressorOffButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
*/
}

void TTrain::OnCommand_compressorenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggCompressorButton.UpdateValue( 1.0, Train->dsbSwitch );

        // impulse type switch has no effect if there's no power
        // NOTE: this is most likely setup wrong, but the whole thing is smoke and mirrors anyway
//        if( ( Train->mvOccupied->CompSwitchType != "impulse" )
//         || ( Train->mvControlled->Mains ) ) {

            Train->mvOccupied->CompressorSwitch( true );
//        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // potentially reset impulse switch position, using shared code branch
        OnCommand_compressortoggle( Train, Command );
    }

}

void TTrain::OnCommand_compressordisable( TTrain *Train, command_data const &Command ) {

    if( Train->mvControlled->CompressorPower >= 2 ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggCompressorButton.UpdateValue( 0.0, Train->dsbSwitch );
/*
        if( Train->ggCompressorOffButton.SubModel != nullptr ) {
            Train->ggCompressorOffButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
*/
        Train->mvOccupied->CompressorSwitch( false );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // potentially reset impulse switch position, using shared code branch
        OnCommand_compressortoggle( Train, Command );
    }
}

void TTrain::OnCommand_compressortogglelocal( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->CompressorPower >= 2 ) { return; }
    if( Train->ggCompressorLocalButton.SubModel == nullptr ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->mvOccupied->CompressorAllowLocal ) {
            // turn on
            // visual feedback
            Train->ggCompressorLocalButton.UpdateValue( 1.0, Train->dsbSwitch );
            // effect
            Train->mvOccupied->CompressorAllowLocal = true;
        }
        else {
            // turn off
            // visual feedback
            Train->ggCompressorLocalButton.UpdateValue( 0.0, Train->dsbSwitch );
            // effect
            Train->mvOccupied->CompressorAllowLocal = false;
        }
    }
}

void TTrain::OnCommand_compressorpresetactivatenext(TTrain *Train, command_data const &Command) {

	if (Train->mvOccupied->CompressorListPosNo == 0) { return; }
    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggCompressorListButton.type() == TGaugeType::push ) {
        // impulse switch
        if( Train->mvOccupied->CompressorListPosNo < Train->mvOccupied->CompressorListDefPos + 1 ) { return; }

        Train->mvOccupied->ChangeCompressorPreset( (
            Command.action == GLFW_PRESS ?
                Train->mvOccupied->CompressorListDefPos + 1 :
                Train->mvOccupied->CompressorListDefPos ) );
        // visual feedback
        Train->ggCompressorListButton.UpdateValue( Train->mvOccupied->CompressorListPos - 1, Train->dsbSwitch );
    }
    else {
        // multi-state switch
        if( Command.action == GLFW_RELEASE ) { return; }

        if( ( Train->mvOccupied->CompressorListPos < Train->mvOccupied->CompressorListPosNo )
         || ( true == Train->mvOccupied->CompressorListWrap ) ) {
            // active light preset is stored as value in range 1-LigthPosNo
            Train->mvOccupied->ChangeCompressorPreset( (
                Train->mvOccupied->CompressorListPos < Train->mvOccupied->CompressorListPosNo ?
                    Train->mvOccupied->CompressorListPos + 1 :
                    1 ) ); // wrap mode
            // visual feedback
            Train->ggCompressorListButton.UpdateValue( Train->mvOccupied->CompressorListPos - 1, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_compressorpresetactivateprevious(TTrain *Train, command_data const &Command) {

	if (Train->mvOccupied->CompressorListPosNo == 0) { return; }
	if (Command.action != GLFW_PRESS) { return; } // one change per key press

    if( Train->ggCompressorListButton.type() == TGaugeType::push ) {
        // impulse switch toggles only between positions 'default' and 'default+1'
        return;
    } 

	if ((Train->mvOccupied->CompressorListPos > 1)
		|| (true == Train->mvOccupied->CompressorListWrap)) {
		// active light preset is stored as value in range 1-LigthPosNo
		Train->mvOccupied->ChangeCompressorPreset( (
			Train->mvOccupied->CompressorListPos > 1 ?
			Train->mvOccupied->CompressorListPos - 1 :
			Train->mvOccupied->CompressorListPosNo) ); // wrap mode

		// visual feedback
		if (Train->ggCompressorListButton.SubModel != nullptr) {
			Train->ggCompressorListButton.UpdateValue(Train->mvOccupied->CompressorListPos - 1, Train->dsbSwitch);
		}
	}
}

void TTrain::OnCommand_compressorpresetactivatedefault(TTrain *Train, command_data const &Command) {

	if (Train->mvOccupied->CompressorListPosNo == 0) { return; }
	if (Command.action != GLFW_PRESS) { return; } // one change per key press

	Train->mvOccupied->ChangeCompressorPreset( Train->mvOccupied->CompressorListDefPos );

    // visual feedback
	if (Train->ggCompressorListButton.SubModel != nullptr) {
			Train->ggCompressorListButton.UpdateValue(Train->mvOccupied->CompressorListPos - 1, Train->dsbSwitch);
	}
}

void TTrain::OnCommand_motorblowerstogglefront( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggMotorBlowersFrontButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no off button so we always try to turn it on
        OnCommand_motorblowersenablefront( Train, Command );
    }
    else {
        // two-state switch
        if( Command.action == GLFW_RELEASE ) { return; }

        if( false == Train->mvControlled->MotorBlowers[end::front].is_enabled ) {
            // turn on
            OnCommand_motorblowersenablefront( Train, Command );
        }
        else {
            //turn off
            OnCommand_motorblowersdisablefront( Train, Command );
        }
    }
}

void TTrain::OnCommand_motorblowersenablefront( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggMotorBlowersFrontButton.type() == TGaugeType::push ) {
        // impulse switch
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggMotorBlowersFrontButton.UpdateValue( 1.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitch( true, end::front );
        }
        else if( Command.action == GLFW_RELEASE ) {
            // visual feedback
            Train->ggMotorBlowersFrontButton.UpdateValue( 0.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitch( false, end::front );
        }
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggMotorBlowersFrontButton.UpdateValue( 1.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitch( true, end::front );
            Train->mvControlled->MotorBlowersSwitchOff( false, end::front );
        }
    }
}

void TTrain::OnCommand_motorblowersdisablefront( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggMotorBlowersFrontButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no disable return type switch
        return;
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggMotorBlowersFrontButton.UpdateValue( 0.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitch( false, end::front );
            Train->mvControlled->MotorBlowersSwitchOff( true, end::front );
        }
    }
}

void TTrain::OnCommand_motorblowerstogglerear( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggMotorBlowersRearButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no off button so we always try to turn it on
        OnCommand_motorblowersenablerear( Train, Command );
    }
    else {
        // two-state switch
        if( Command.action == GLFW_RELEASE ) { return; }

        if( false == Train->mvControlled->MotorBlowers[ end::rear ].is_enabled ) {
            // turn on
            OnCommand_motorblowersenablerear( Train, Command );
        }
        else {
            //turn off
            OnCommand_motorblowersdisablerear( Train, Command );
        }
    }
}

void TTrain::OnCommand_motorblowersenablerear( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggMotorBlowersRearButton.type() == TGaugeType::push ) {
        // impulse switch
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggMotorBlowersRearButton.UpdateValue( 1.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitch( true, end::rear );
        }
        else if( Command.action == GLFW_RELEASE ) {
            // visual feedback
            Train->ggMotorBlowersRearButton.UpdateValue( 0.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitch( false, end::rear );
        }
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggMotorBlowersRearButton.UpdateValue( 1.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitch( true, end::rear );
            Train->mvControlled->MotorBlowersSwitchOff( false, end::rear );
        }
    }
}

void TTrain::OnCommand_motorblowersdisablerear( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggMotorBlowersRearButton.type() == TGaugeType::push ) {
        // impulse switch
        // currently there's no disable return type switch
        return;
    }
    else {
        // two-state switch, only cares about press events
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggMotorBlowersRearButton.UpdateValue( 0.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitch( false, end::rear );
            Train->mvControlled->MotorBlowersSwitchOff( true, end::rear );
        }
    }
}

void TTrain::OnCommand_motorblowersdisableall( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    if( Train->ggMotorBlowersAllOffButton.type() == TGaugeType::push ) {
        // impulse switch
        if( Command.action == GLFW_PRESS ) {
            // visual feedback
            Train->ggMotorBlowersAllOffButton.UpdateValue( 1.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitchOff( true, end::front );
            Train->mvControlled->MotorBlowersSwitchOff( true, end::rear );
        }
        else if( Command.action == GLFW_RELEASE ) {
            // visual feedback
            Train->ggMotorBlowersAllOffButton.UpdateValue( 0.f, Train->dsbSwitch );
            Train->mvControlled->MotorBlowersSwitchOff( false, end::front );
            Train->mvControlled->MotorBlowersSwitchOff( false, end::rear );
        }
    }
    else {
        // two-state switch, only cares about press events
        // NOTE: generally this switch doesn't come in two-state form
        if( Command.action == GLFW_PRESS ) {
            if( Train->ggMotorBlowersAllOffButton.GetDesiredValue() < 0.5f ) {
                // switch is off, activate
                Train->mvControlled->MotorBlowersSwitchOff( true, end::front );
                Train->mvControlled->MotorBlowersSwitchOff( true, end::rear );
                // visual feedback
                Train->ggMotorBlowersRearButton.UpdateValue( 1.f, Train->dsbSwitch );
            }
            else {
                // deactivate
                Train->mvControlled->MotorBlowersSwitchOff( false, end::front );
                Train->mvControlled->MotorBlowersSwitchOff( false, end::rear );
                // visual feedback
                Train->ggMotorBlowersRearButton.UpdateValue( 0.f, Train->dsbSwitch );
            }
        }
    }
}

void TTrain::OnCommand_coolingfanstoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_PRESS ) { return; }

    Train->mvControlled->RVentForceOn = ( !Train->mvControlled->RVentForceOn );
}

void TTrain::OnCommand_motorconnectorsopen( TTrain *Train, command_data const &Command ) {

    // TODO: don't rely on presense of 3d model to determine presence of the switch
    if( Train->ggStLinOffButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Open Motor Power Connectors button is missing, or wasn't defined" );
        }
        return;
    }
    // HACK: because we don't have modeled actual circuits this is a simplification of the real mechanics
    // namely, pressing the button will flip it in the entire unit, which isn't exactly physically possible
    if( Command.action == GLFW_PRESS ) {
        // button works while it's held down but we can ignore repeats
        if( false == Train->mvControlled->StLinSwitchOff ) {
            // open the connectors
            // visual feedback
            Train->ggStLinOffButton.UpdateValue( 1.0, Train->dsbSwitch );

            Train->mvControlled->StLinSwitchOff = true;
            Train->set_paired_open_motor_connectors_button( true );
        }
        else {
            // potentially close the connectors
            OnCommand_motorconnectorsclose( Train, Command );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // button released
        if( Train->mvControlled->StLinSwitchType != "toggle" ) {
            // default button type (impulse) ceases its work on button release
            // visual feedback
            Train->ggStLinOffButton.UpdateValue( 0.0, Train->dsbSwitch );

            Train->mvControlled->StLinSwitchOff = false;
            Train->set_paired_open_motor_connectors_button( false );
        }
    }
}

void TTrain::OnCommand_motorconnectorsclose( TTrain *Train, command_data const &Command ) {

    // TODO: don't rely on presense of 3d model to determine presence of the switch
    if( Train->ggStLinOffButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Open Motor Power Connectors button is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        if( Train->mvControlled->StLinSwitchType == "toggle" ) {
            // default type of button (impulse) has only one effect on press, but the toggle type can toggle the state
            // visual feedback
            Train->ggStLinOffButton.UpdateValue( 0.0, Train->dsbSwitch );
        }

        if( false == Train->mvControlled->StLinSwitchOff ) { return; } // already closed

        Train->mvControlled->StLinSwitchOff = false;
        Train->set_paired_open_motor_connectors_button( false );
    }
}

void TTrain::OnCommand_motordisconnect( TTrain *Train, command_data const &Command ) {

    if( ( Train->mvControlled->TrainType == dt_EZT ?
            ( Train->mvControlled != Train->mvOccupied ) :
            ( Train->iCabn != 0 ) ) ) {
        // tylko w maszynowym
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        Train->mvControlled->CutOffEngine();
    }
}

void TTrain::OnCommand_motoroverloadrelaythresholdtoggle( TTrain *Train, command_data const &Command )
{
    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( true == Train->mvControlled->ShuntModeAllow ?
                ( false == Train->mvControlled->ShuntMode ) :
                ( false == Train->mvControlled->MotorOverloadRelayHighThreshold ) ) ) {
            // turn on
            OnCommand_motoroverloadrelaythresholdsethigh( Train, Command );
        }
        else {
            //turn off
            OnCommand_motoroverloadrelaythresholdsetlow( Train, Command );
        }
    }
}

void TTrain::OnCommand_motoroverloadrelaythresholdsetlow( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        Train->mvControlled->CurrentSwitch( false );
        // visual feedback
        Train->ggMaxCurrentCtrl.UpdateValue( Train->mvControlled->MotorOverloadRelayHighThreshold ? 1 : 0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_motoroverloadrelaythresholdsethigh( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        Train->mvControlled->CurrentSwitch( true );
        // visual feedback
        Train->ggMaxCurrentCtrl.UpdateValue( Train->mvControlled->MotorOverloadRelayHighThreshold ? 1 : 0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_motoroverloadrelayreset( TTrain *Train, command_data const &Command ) {

    if( Train->ggFuseButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Motor Overload Relay Reset button is missing, or wasn't defined" );
        }
//        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggFuseButton.UpdateValue( 1.0, Train->dsbSwitch );

        Train->mvControlled->FuseOn();
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggFuseButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_universalrelayreset( TTrain *Train, command_data const &Command ) {

    auto const itemindex = static_cast<int>( Command.command ) - static_cast<int>( user_command::universalrelayreset1 );
    auto &item = Train->ggRelayResetButtons[ itemindex ];

    // NOTE: relay reset switches are impulse-only
    if( Command.action == GLFW_PRESS ) {
        Train->mvOccupied->UniversalResetButton( itemindex );
        // visual feedback
        item.UpdateValue( 1.0 );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        item.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_lightspresetactivatenext( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo == 0 ) {
        // no preset selector
        return;
    }
    if( Command.action != GLFW_PRESS ) {
        // one change per key press
        return;
    }

    if( ( Train->mvOccupied->LightsPos < Train->mvOccupied->LightsPosNo )
     || ( true == Train->mvOccupied->LightsWrap ) ) {
        // active light preset is stored as value in range 1-LigthPosNo
        auto const restartcycle { Train->mvOccupied->LightsPos == Train->mvOccupied->LightsPosNo };
        Train->mvOccupied->LightsPos = (
            false == restartcycle ?
                Train->mvOccupied->LightsPos + 1 :
                1 ); // wrap mode

        Train->Dynamic()->SetLights();
        // visual feedback
        if( Train->ggLightsButton.SubModel != nullptr ) {
            // HACK: skip submodel animation when restarting cycle, since it plays in the 'wrong' direction
            if( false == restartcycle ) {
                Train->ggLightsButton.UpdateValue( Train->mvOccupied->LightsPos - 1, Train->dsbSwitch );
            }
            else {
                Train->ggLightsButton.PutValue( Train->mvOccupied->LightsPos - 1 );
            }
        }
    }
}

void TTrain::OnCommand_lightspresetactivateprevious( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo == 0 ) {
        // no preset selector
        return;
    }
    if( Command.action != GLFW_PRESS ) {
        // one change per key press
        return;
    }

    if( ( Train->mvOccupied->LightsPos > 1 )
     || ( true == Train->mvOccupied->LightsWrap ) ) {
        // active light preset is stored as value in range 1-LigthPosNo
        auto const restartcycle { Train->mvOccupied->LightsPos == 1 };
        Train->mvOccupied->LightsPos = (
            false == restartcycle ?
                Train->mvOccupied->LightsPos - 1 :
                Train->mvOccupied->LightsPosNo ); // wrap mode

        Train->Dynamic()->SetLights();
        // visual feedback
        if( Train->ggLightsButton.SubModel != nullptr ) {
            // HACK: skip submodel animation when restarting cycle, since it plays in the 'wrong' direction
            if( false == restartcycle ) {
                Train->ggLightsButton.UpdateValue( Train->mvOccupied->LightsPos - 1, Train->dsbSwitch );
            }
            else {
                Train->ggLightsButton.PutValue( Train->mvOccupied->LightsPos - 1 );
            }
        }
    }
}

void TTrain::OnCommand_headlighttoggleleft( TTrain *Train, command_data const &Command ) {

    auto const vehicleend { Train->cab_to_end() };

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_left ) == 0 ) {
            // turn on
            OnCommand_headlightenableleft( Train, Command );
        }
        else {
            //turn off
            OnCommand_headlightdisableleft( Train, Command );
        }
    }
}

void TTrain::OnCommand_headlightenableleft( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggLeftLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        // implementation
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_left ) == 0 ) {
            Train->mvOccupied->iLights[ vehicleend ] ^= light::headlight_left;
        }
        // if the light is controlled by 3-way switch, disable marker light
        if( Train->ggLeftEndLightButton.SubModel == nullptr ) {
            Train->mvOccupied->iLights[ vehicleend ] &= ~light::redmarker_left;
        }
    }
}

void TTrain::OnCommand_headlightdisableleft( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        int const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_left ) == 0 ) { return; } // already disabled

        Train->mvOccupied->iLights[ vehicleend ] ^= light::headlight_left;
        // visual feedback
        Train->ggLeftLightButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_headlighttoggleright( TTrain *Train, command_data const &Command ) {

    auto const vehicleend { Train->cab_to_end() };

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_right ) == 0 ) {
            // turn on
            OnCommand_headlightenableright( Train, Command );
        }
        else {
            //turn off
            OnCommand_headlightdisableright( Train, Command );
        }
    }
}

void TTrain::OnCommand_headlightenableright( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->ggRightLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        // implementation
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_right ) == 0 ) {
            Train->mvOccupied->iLights[ vehicleend ] ^= light::headlight_right;
        }
        // if the light is controlled by 3-way switch, disable marker light
        if( Train->ggRightEndLightButton.SubModel == nullptr ) {
            Train->mvOccupied->iLights[ vehicleend ] &= ~light::redmarker_right;
        }
    }
}

void TTrain::OnCommand_headlightdisableright( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_right ) == 0 ) { return; } // already disabled

        Train->mvOccupied->iLights[ vehicleend ] ^= light::headlight_right;
        // visual feedback
        Train->ggRightLightButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_headlighttoggleupper( TTrain *Train, command_data const &Command ) {

    auto const vehicleend { Train->cab_to_end() };

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_upper ) == 0 ) {
            // turn on
            OnCommand_headlightenableupper( Train, Command );
        }
        else {
            //turn off
            OnCommand_headlightdisableupper( Train, Command );
        }
    }
}

void TTrain::OnCommand_headlightenableupper( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_upper ) != 0 ) { return; } // already enabled

        Train->mvOccupied->iLights[ vehicleend ] ^= light::headlight_upper;
        // visual feedback
        Train->ggUpperLightButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_headlightdisableupper( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::headlight_upper ) == 0 ) { return; } // already disabled

        Train->mvOccupied->iLights[ vehicleend ] ^= light::headlight_upper;
        // visual feedback
        Train->ggUpperLightButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_redmarkertoggleleft( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::redmarker_left ) == 0 ) {
            // turn on
            OnCommand_redmarkerenableleft( Train, Command );
        }
        else {
            //turn off
            OnCommand_redmarkerdisableleft( Train, Command );
        }
    }
}

void TTrain::OnCommand_redmarkerenableleft( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::redmarker_left ) != 0 ) { return; } // already enabled

        Train->mvOccupied->iLights[ vehicleend ] ^= light::redmarker_left;
        // visual feedback
        if( Train->ggLeftEndLightButton.SubModel != nullptr ) {
            Train->ggLeftEndLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // we interpret lack of dedicated switch as a sign the light is controlled with 3-way switch
            // this is crude, but for now will do
            Train->ggLeftLightButton.UpdateValue( -1.0, Train->dsbSwitch );
            // if the light is controlled by 3-way switch, disable the headlight
            Train->mvOccupied->iLights[ vehicleend ] &= ~light::headlight_left;
        }
    }
}

void TTrain::OnCommand_redmarkerdisableleft( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::redmarker_left ) == 0 ) { return; } // already disabled

        Train->mvOccupied->iLights[ vehicleend ] ^= light::redmarker_left;
        // visual feedback
        if( Train->ggLeftEndLightButton.SubModel != nullptr ) {
            Train->ggLeftEndLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
        else {
            // we interpret lack of dedicated switch as a sign the light is controlled with 3-way switch
            // this is crude, but for now will do
            Train->ggLeftLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_redmarkertoggleright( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::redmarker_right ) == 0 ) {
            // turn on
            OnCommand_redmarkerenableright( Train, Command );
        }
        else {
            //turn off
            OnCommand_redmarkerdisableright( Train, Command );
        }
    }
}

void TTrain::OnCommand_redmarkerenableright( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::redmarker_right ) != 0 ) { return; } // already enabled

        Train->mvOccupied->iLights[ vehicleend ] ^= light::redmarker_right;
        // visual feedback
        if( Train->ggRightEndLightButton.SubModel != nullptr ) {
            Train->ggRightEndLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // we interpret lack of dedicated switch as a sign the light is controlled with 3-way switch
            // this is crude, but for now will do
            Train->ggRightLightButton.UpdateValue( -1.0, Train->dsbSwitch );
            // if the light is controlled by 3-way switch, disable the headlight
            Train->mvOccupied->iLights[ vehicleend ] &= ~light::headlight_right;
        }
    }
}

void TTrain::OnCommand_redmarkerdisableright( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleend { Train->cab_to_end() };

        if( ( Train->mvOccupied->iLights[ vehicleend ] & light::redmarker_right ) == 0 ) { return; } // already disabled

        Train->mvOccupied->iLights[ vehicleend ] ^= light::redmarker_right;
        // visual feedback
        if( Train->ggRightEndLightButton.SubModel != nullptr ) {
            Train->ggRightEndLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
        else {
            // we interpret lack of dedicated switch as a sign the light is controlled with 3-way switch
            // this is crude, but for now will do
            Train->ggRightLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_headlighttogglerearleft( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // NOTE: we toggle the light on opposite side, as 'rear right' is 'front left' on the rear end etc
        auto const vehicleotherend { (
            Train->cab_to_end() == end::front ?
                end::rear :
                end::front ) };
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( Train->mvOccupied->iLights[ vehicleotherend ] & light::headlight_right ) == 0 ) {
            // turn on
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::headlight_right;
            // visual feedback
            Train->ggRearLeftLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            //turn off
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::headlight_right;
            // visual feedback
            Train->ggRearLeftLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_headlighttogglerearright( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // NOTE: we toggle the light on opposite side, as 'rear right' is 'front left' on the rear end etc
        auto const vehicleotherend { (
            Train->cab_to_end() == end::front ?
                end::rear :
                end::front ) };
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( Train->mvOccupied->iLights[ vehicleotherend ] & light::headlight_left ) == 0 ) {
            // turn on
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::headlight_left;
            // visual feedback
            Train->ggRearRightLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            //turn off
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::headlight_left;
            // visual feedback
            Train->ggRearRightLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_headlighttogglerearupper( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        auto const vehicleotherend { (
            Train->cab_to_end() == end::front ?
                end::rear :
                end::front ) };
        if( ( Train->mvOccupied->iLights[ vehicleotherend ] & light::headlight_upper ) == 0 ) {
            // turn on
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::headlight_upper;
            // visual feedback
            Train->ggRearUpperLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            //turn off
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::headlight_upper;
            // visual feedback
            Train->ggRearUpperLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_redmarkertogglerearleft( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // NOTE: we toggle the light on opposite side, as 'rear right' is 'front left' on the rear end etc
        auto const vehicleotherend { (
            Train->cab_to_end() == end::front ?
                end::rear :
                end::front ) };
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( Train->mvOccupied->iLights[ vehicleotherend ] & light::redmarker_right ) == 0 ) {
            // turn on
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::redmarker_right;
            // visual feedback
            Train->ggRearLeftEndLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            //turn off
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::redmarker_right;
            // visual feedback
            Train->ggRearLeftEndLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_redmarkertogglerearright( TTrain *Train, command_data const &Command ) {

    if( Train->mvOccupied->LightsPosNo > 0 ) {
        // lights are controlled by preset selector
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // NOTE: we toggle the light on opposite side, as 'rear right' is 'front left' on the rear end etc
        auto const vehicleotherend { (
            Train->cab_to_end() == end::front ?
                end::rear :
                end::front ) };
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( ( Train->mvOccupied->iLights[ vehicleotherend ] & light::redmarker_left ) == 0 ) {
            // turn on
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::redmarker_left;
            // visual feedback
            Train->ggRearRightEndLightButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            //turn off
            Train->mvOccupied->iLights[ vehicleotherend ] ^= light::redmarker_left;
            // visual feedback
            Train->ggRearRightEndLightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_redmarkerstoggle( TTrain *Train, command_data const &Command ) {

    if( ( true == Command.freefly )
     && ( Command.action == GLFW_PRESS ) ) {

        auto *vehicle { std::get<TDynamicObject *>( simulation::Region->find_vehicle( Command.location, 10, false, true ) ) };

        if( vehicle == nullptr ) { return; }

        int const CouplNr {
            clamp(
                vehicle->DirectionGet()
                * ( Math3D::LengthSquared3( vehicle->HeadPosition() - Command.location ) > Math3D::LengthSquared3( vehicle->RearPosition() - Command.location ) ?
                     1 :
                    -1 ),
                0, 1 ) }; // z [-1,1] zrobić [0,1]

        auto const lightset { light::redmarker_left | light::redmarker_right };

        vehicle->MoverParameters->iLights[ CouplNr ] = (
            false == TestFlag( vehicle->MoverParameters->iLights[ CouplNr ], lightset ) ?
                vehicle->MoverParameters->iLights[ CouplNr ] |= lightset : // turn signals on
                vehicle->MoverParameters->iLights[ CouplNr ] ^= lightset ); // turn signals off
    }
}

void TTrain::OnCommand_endsignalstoggle( TTrain *Train, command_data const &Command ) {

    if( ( true == Command.freefly )
     && ( Command.action == GLFW_PRESS ) ) {

        auto *vehicle { std::get<TDynamicObject *>( simulation::Region->find_vehicle( Command.location, 10, false, true ) ) };

        if( vehicle == nullptr ) { return; }

        int const CouplNr {
            clamp(
                vehicle->DirectionGet()
                * ( Math3D::LengthSquared3( vehicle->HeadPosition() - Command.location ) > Math3D::LengthSquared3( vehicle->RearPosition() - Command.location ) ?
                     1 :
                    -1 ),
                0, 1 ) }; // z [-1,1] zrobić [0,1]

        auto const lightset { light::rearendsignals };

        vehicle->MoverParameters->iLights[ CouplNr ] = (
            false == TestFlag( vehicle->MoverParameters->iLights[ CouplNr ], lightset ) ?
                vehicle->MoverParameters->iLights[ CouplNr ] |= lightset : // turn signals on
                vehicle->MoverParameters->iLights[ CouplNr ] ^= lightset ); // turn signals off
    }
}

void TTrain::OnCommand_headlightsdimtoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->DynamicObject->DimHeadlights ) {
            // turn on
            OnCommand_headlightsdimenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_headlightsdimdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_headlightsdimenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( Train->ggDimHeadlightsButton.SubModel == nullptr ) {
            // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
            WriteLog( "Dim Headlights switch is missing, or wasn't defined" );
            return;
        }
        // visual feedback
        Train->ggDimHeadlightsButton.UpdateValue( 1.0, Train->dsbSwitch );

        if( true == Train->DynamicObject->DimHeadlights ) { return; } // already enabled

        Train->DynamicObject->DimHeadlights = true;
    }
}

void TTrain::OnCommand_headlightsdimdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( Train->ggDimHeadlightsButton.SubModel == nullptr ) {
            // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
            WriteLog( "Dim Headlights switch is missing, or wasn't defined" );
            return;
        }
        // visual feedback
        Train->ggDimHeadlightsButton.UpdateValue( 0.0, Train->dsbSwitch );

        if( false == Train->DynamicObject->DimHeadlights ) { return; } // already enabled

        Train->DynamicObject->DimHeadlights = false;
    }
}

void TTrain::OnCommand_interiorlighttoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->Cabine[Train->iCabn].bLight ) {
            // turn on
            OnCommand_interiorlightenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_interiorlightdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_interiorlightenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->m_controlmapper.contains( "cablight_sw:" ) ) {
            // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
            WriteLog( "Interior Light switch is missing, or wasn't defined" );
            return;
        }
        // store lighting switch states
        if( false == Train->DynamicObject->JointCabs ) {
            // vehicles with separate cabs get separate lighting switch states
            Train->Cabine[ Train->iCabn ].bLight = true;
        }
        else {
            // joint virtual cabs share lighting switch states
            for( auto &cab : Train->Cabine ) {
                cab.bLight = true;
            }
        }
    }
}

void TTrain::OnCommand_interiorlightdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->m_controlmapper.contains( "cablight_sw:" ) ) {
            // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
            WriteLog( "Interior Light switch is missing, or wasn't defined" );
            return;
        }
        // store lighting switch states
        if( false == Train->DynamicObject->JointCabs ) {
            // vehicles with separate cabs get separate lighting switch states
            Train->Cabine[ Train->iCabn ].bLight = false;
        }
        else {
            // joint virtual cabs share lighting switch states
            for( auto &cab : Train->Cabine ) {
                cab.bLight = false;
            }
        }
    }
}

void TTrain::OnCommand_interiorlightdimtoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->Cabine[ Train->iCabn ].bLightDim ) {
            // turn on
            OnCommand_interiorlightdimenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_interiorlightdimdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_interiorlightdimenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( Train->ggCabLightDimButton.SubModel == nullptr ) {
            // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
            WriteLog( "Dim Interior Light switch is missing, or wasn't defined" );
            return;
        }
        // visual feedback
        Train->ggCabLightDimButton.UpdateValue( 1.0, Train->dsbSwitch );
        // store lighting switch states
        if( false == Train->DynamicObject->JointCabs ) {
            // vehicles with separate cabs get separate lighting switch states
            Train->Cabine[ Train->iCabn ].bLightDim = true;
        }
        else {
            // joint virtual cabs share lighting switch states
            for( auto &cab : Train->Cabine ) {
                cab.bLightDim = true;
            }
        }
    }
}

void TTrain::OnCommand_interiorlightdimdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( Train->ggCabLightDimButton.SubModel == nullptr ) {
            // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
            WriteLog( "Dim Interior Light switch is missing, or wasn't defined" );
            return;
        }
        // visual feedback
        Train->ggCabLightDimButton.UpdateValue( 0.0, Train->dsbSwitch );
        // store lighting switch states
        if( false == Train->DynamicObject->JointCabs ) {
            // vehicles with separate cabs get separate lighting switch states
            Train->Cabine[ Train->iCabn ].bLightDim = false;
        }
        else {
            // joint virtual cabs share lighting switch states
            for( auto &cab : Train->Cabine ) {
                cab.bLightDim = false;
            }
        }
    }
}

void TTrain::OnCommand_compartmentlightstoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }

    // keep the switch from flipping back and forth if key is held down
    if( ( false == Train->mvOccupied->CompartmentLights.is_active )
     && ( false == Train->mvOccupied->CompartmentLights.is_enabled ) ) {
        // turn on
        OnCommand_compartmentlightsenable( Train, Command );
    }
    else {
        //turn off
        OnCommand_compartmentlightsdisable( Train, Command );
    }
}

void TTrain::OnCommand_compartmentlightsenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        Train->mvOccupied->CompartmentLightsSwitch( true );
        if( Train->m_controlmapper.contains( "compartmentlights_sw:" ) ) {
            auto const istoggle{ ( static_cast<int>( Train->ggCompartmentLightsButton.type() ) & static_cast<int>( TGaugeType::toggle ) ) != 0 };
            if( istoggle ) {
                Train->mvOccupied->CompartmentLightsSwitchOff( false );
            }
        }
        // visual feedback
        if( Train->m_controlmapper.contains( "compartmentlights_sw:" ) ) {
            Train->ggCompartmentLightsButton.UpdateValue( 1.0f, Train->dsbSwitch );
        }
        if( Train->m_controlmapper.contains( "compartmentlightson_sw:" ) ) {
            Train->ggCompartmentLightsOnButton.UpdateValue( 1.0f, Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        if( Train->m_controlmapper.contains( "compartmentlights_sw:" ) ) {
            if( Train->ggCompartmentLightsButton.type() == TGaugeType::push ) {
                // return the switch to neutral position
                Train->mvOccupied->CompartmentLightsSwitch( false );
                Train->mvOccupied->CompartmentLightsSwitchOff( false );
                Train->ggCompartmentLightsButton.UpdateValue( 0.5f );
            }
        }
        if( Train->m_controlmapper.contains( "compartmentlightson_sw:" ) ) {
            Train->mvOccupied->CompartmentLightsSwitch( false );
            Train->ggCompartmentLightsOnButton.UpdateValue( 0.0f, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_compartmentlightsdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        Train->mvOccupied->CompartmentLightsSwitchOff( true );
        if( Train->m_controlmapper.contains( "compartmentlights_sw:" ) ) {
            auto const istoggle{ ( static_cast<int>( Train->ggCompartmentLightsButton.type() ) & static_cast<int>( TGaugeType::toggle ) ) != 0 };
            if( istoggle ) {
                Train->mvOccupied->CompartmentLightsSwitch( false );
            }
        }
        // visual feedback
        if( Train->m_controlmapper.contains( "compartmentlights_sw:" ) ) {
            Train->ggCompartmentLightsButton.UpdateValue( 0.0f, Train->dsbSwitch );
        }
        if( Train->m_controlmapper.contains( "compartmentlightsoff_sw:" ) ) {
            Train->ggCompartmentLightsOffButton.UpdateValue( 1.0f, Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        if( Train->m_controlmapper.contains( "compartmentlights_sw:" ) ) {
            if( Train->ggCompartmentLightsButton.type() == TGaugeType::push ) {
                // return the switch to neutral position
                Train->mvOccupied->CompartmentLightsSwitch( false );
                Train->mvOccupied->CompartmentLightsSwitchOff( false );
                Train->ggCompartmentLightsButton.UpdateValue( 0.5f );
            }
        }
        if( Train->m_controlmapper.contains( "compartmentlightsoff_sw:" ) ) {
            Train->mvOccupied->CompartmentLightsSwitchOff( false );
            Train->ggCompartmentLightsOffButton.UpdateValue( 0.0f, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_instrumentlighttoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( false == Train->InstrumentLightActive ) {
            // turn on
            OnCommand_instrumentlightenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_instrumentlightdisable( Train, Command );
        }
    }
}

void TTrain::OnCommand_instrumentlightenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( Train->ggInstrumentLightButton.SubModel == nullptr ) {
            // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
            WriteLog( "Instrument Light switch is missing, or wasn't defined" );
            return;
        }
        // visual feedback
        Train->ggInstrumentLightButton.UpdateValue( 1.0, Train->dsbSwitch );

        if( true == Train->InstrumentLightActive ) { return; } // already enabled

        Train->InstrumentLightActive = true;
    }
}

void TTrain::OnCommand_instrumentlightdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the switch doesn't flip back and forth if key is held down
        if( Train->ggInstrumentLightButton.SubModel == nullptr ) {
            // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
            WriteLog( "Instrument Light switch is missing, or wasn't defined" );
            return;
        }
        // visual feedback
        Train->ggInstrumentLightButton.UpdateValue( 0.0, Train->dsbSwitch );

        if( false == Train->InstrumentLightActive ) { return; } // already disabled

        Train->InstrumentLightActive = false;
    }
}

void TTrain::OnCommand_dashboardlighttoggle( TTrain *Train, command_data const &Command ) {
    // only reacting to press, so the switch doesn't flip back and forth if key is held down
    if( Command.action != GLFW_PRESS ) { return; }

    if( Train->ggDashboardLightButton.SubModel == nullptr ) {
        // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
        WriteLog( "Dashboard Light switch is missing, or wasn't defined" );
        return;
    }

    if( false == Train->DashboardLightActive ) {
        // turn on
        Train->DashboardLightActive = true;
        // visual feedback
        Train->ggDashboardLightButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
    else {
        //turn off
        Train->DashboardLightActive = false;
        // visual feedback
        Train->ggDashboardLightButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_timetablelighttoggle( TTrain *Train, command_data const &Command ) {
    // only reacting to press, so the switch doesn't flip back and forth if key is held down
    if( Command.action != GLFW_PRESS ) { return; }

    if( Train->ggTimetableLightButton.SubModel == nullptr ) {
        // TODO: proper control deviced definition for the interiors, that doesn't hinge of presence of 3d submodels
        WriteLog( "Timetable Light switch is missing, or wasn't defined" );
        return;
    }

    if( false == Train->TimetableLightActive ) {
        // turn on
        Train->TimetableLightActive = true;
        // visual feedback
        Train->ggTimetableLightButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
    else {
        //turn off
        Train->TimetableLightActive = false;
        // visual feedback
        Train->ggTimetableLightButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_heatingtoggle( TTrain *Train, command_data const &Command ) {

    if( Train->ggTrainHeatingButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Train Heating switch is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // ignore repeats so the switch doesn't flip back and forth if key is held down
        if( false == Train->mvControlled->HeatingAllow ) {
            // turn on
            OnCommand_heatingenable( Train, Command );
        }
        else {
            //turn off
            OnCommand_heatingdisable( Train, Command );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {

        if( Train->ggTrainHeatingButton.type() == TGaugeType::push ) {
            // impulse switch
            // visual feedback
            Train->ggTrainHeatingButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_heatingenable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->mvOccupied->HeatingSwitch( true );
        // visual feedback
        Train->ggTrainHeatingButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_heatingdisable( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->mvOccupied->HeatingSwitch( false );
        // visual feedback
        Train->ggTrainHeatingButton.UpdateValue( 
            ( Train->ggTrainHeatingButton.type() == TGaugeType::push ?
                1.0 :
                0.0 ),
            Train->dsbSwitch );
    }
}

void TTrain::OnCommand_generictoggle( TTrain *Train, command_data const &Command ) {

    auto const itemindex = static_cast<int>( Command.command ) - static_cast<int>( user_command::generictoggle0 );
    auto &item = Train->ggUniversals[ itemindex ];
/*
    if( item.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Train generic item " + std::to_string( itemindex ) + " is missing, or wasn't defined" );
        }
        return;
    }
*/

    if( Command.action == GLFW_PRESS ) {

        if( item.type() == TGaugeType::push ) {
            // impulse switch
            // turn on
            // visual feedback
            item.UpdateValue( 1.0 );
        }
        else {
            // two-state switch
            if( item.GetDesiredValue() < 0.5 ) {
                // turn on
                // visual feedback
                item.UpdateValue( 1.0 );
            }
            else {
                // turn off
                // visual feedback
                item.UpdateValue( 0.0 );
            }
        }
    }
    else if( Command.action == GLFW_RELEASE ) {

        if( item.type() == TGaugeType::push ) {
            // impulse switch
            // turn off
            // visual feedback
            item.UpdateValue( 0.0 );
        }
    }
}

void TTrain::OnCommand_springbraketoggle(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		if (false == Train->mvOccupied->SpringBrake.Activate) {
			// turn on
			OnCommand_springbrakeenable(Train, Command);
		}
		else {
			//turn off
			OnCommand_springbrakedisable(Train, Command);
		}
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggSpringBrakeOffButton.UpdateValue(0.0, Train->dsbSwitch);
		Train->ggSpringBrakeOnButton.UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_springbrakeenable(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpringBrakeActivate(true);
		// visual feedback
		Train->ggSpringBrakeOnButton.UpdateValue(1.0, Train->dsbSwitch);
		Train->ggSpringBrakeOffButton.UpdateValue(0.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggSpringBrakeOnButton.UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_springbrakedisable(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpringBrakeActivate(false);
		// visual feedback
		Train->ggSpringBrakeOffButton.UpdateValue(1.0, Train->dsbSwitch);
		Train->ggSpringBrakeOnButton.UpdateValue(0.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggSpringBrakeOffButton.UpdateValue(0.0, Train->dsbSwitch);
	}
};
void TTrain::OnCommand_springbrakeshutofftoggle(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		if (false == Train->mvOccupied->SpringBrake.ShuttOff) {
			// turn on
			OnCommand_springbrakeshutoffenable(Train, Command);
		}
		else {
			//turn off
			OnCommand_springbrakeshutoffdisable(Train, Command);
		}
	}
};

void TTrain::OnCommand_springbrakeshutoffenable(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpringBrakeShutOff(true);
	}
};

void TTrain::OnCommand_springbrakeshutoffdisable(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpringBrakeShutOff(false);
	}
};

void TTrain::OnCommand_springbrakerelease(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		
		auto *vehicle{ Train->find_nearest_consist_vehicle(Command.freefly, Command.location) };
		if (vehicle == nullptr) { return; }
		Train->mvOccupied->SpringBrakeRelease();
	}
};

void TTrain::OnCommand_speedcontrolincrease(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpeedCtrlInc();
		// visual feedback
		Train->ggSpeedControlIncreaseButton.UpdateValue(1.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggSpeedControlIncreaseButton.UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_speedcontroldecrease(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpeedCtrlDec();
		// visual feedback
		Train->ggSpeedControlDecreaseButton.UpdateValue(1.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggSpeedControlDecreaseButton.UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_speedcontrolpowerincrease(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpeedCtrlPowerInc();
		// visual feedback
		Train->ggSpeedControlPowerIncreaseButton.UpdateValue(1.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggSpeedControlPowerIncreaseButton.UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_speedcontrolpowerdecrease(TTrain *Train, command_data const &Command) {
	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpeedCtrlPowerDec();
		// visual feedback
		Train->ggSpeedControlPowerDecreaseButton.UpdateValue(1.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggSpeedControlPowerDecreaseButton.UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_speedcontrolbutton(TTrain *Train, command_data const &Command) {

	auto const itemindex = static_cast<int>(Command.command) - static_cast<int>(user_command::speedcontrolbutton0);
	auto &item = Train->ggSpeedCtrlButtons[itemindex];

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		Train->mvOccupied->SpeedCtrlButton(itemindex);
		// visual feedback
		Train->ggSpeedCtrlButtons[itemindex].UpdateValue(1.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggSpeedCtrlButtons[itemindex].UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_inverterenable(TTrain *Train, command_data const &Command) {

	auto itemindex = static_cast<int>(Command.command) - static_cast<int>(user_command::inverterenable1);
	auto &item = Train->ggInverterEnableButtons[itemindex];

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		bool kier = (Train->DynamicObject->DirectionGet() * Train->mvOccupied->CabOccupied > 0);
		int flag = Train->DynamicObject->MoverParameters->InverterControlCouplerFlag;
		TDynamicObject *p = Train->DynamicObject->GetFirstDynamic(Train->mvOccupied->CabOccupied < 0 ? end::rear : end::front, flag);
		while (p)
		{
			if (p->MoverParameters->eimc[eimc_p_Pmax] > 1)
			{
				if (itemindex < p->MoverParameters->InvertersNo)
				{
					p->MoverParameters->Inverters[itemindex].Activate = true;
					break;
				}
				else
				{
					itemindex -= p->MoverParameters->InvertersNo;
				}
			
			}
			p = (kier ? p->Next(flag) : p->Prev(flag));
		}
		// visual feedback
		Train->ggInverterEnableButtons[itemindex].UpdateValue(1.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggInverterEnableButtons[itemindex].UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_inverterdisable(TTrain *Train, command_data const &Command) {

	auto itemindex = static_cast<int>(Command.command) - static_cast<int>(user_command::inverterdisable1);
	auto &item = Train->ggInverterDisableButtons[itemindex];

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		bool kier = (Train->DynamicObject->DirectionGet() * Train->mvOccupied->CabOccupied > 0);
		int flag = Train->DynamicObject->MoverParameters->InverterControlCouplerFlag;
		TDynamicObject *p = Train->DynamicObject->GetFirstDynamic(Train->mvOccupied->CabOccupied < 0 ? end::rear : end::front, flag);
		while (p)
		{
			if (p->MoverParameters->eimc[eimc_p_Pmax] > 1)
			{
				if (itemindex < p->MoverParameters->InvertersNo)
				{
					p->MoverParameters->Inverters[itemindex].Activate = false;
					break;
				}
				else
				{
					itemindex -= p->MoverParameters->InvertersNo;
				}

			}
			p = (kier ? p->Next(flag) : p->Prev(flag));
		}
		// visual feedback
		Train->ggInverterDisableButtons[itemindex].UpdateValue(1.0, Train->dsbSwitch);
	}
	else if (Command.action == GLFW_RELEASE) {
		// release
		// visual feedback
		Train->ggInverterDisableButtons[itemindex].UpdateValue(0.0, Train->dsbSwitch);
	}
};

void TTrain::OnCommand_invertertoggle(TTrain *Train, command_data const &Command) {

	auto itemindex = static_cast<int>(Command.command) - static_cast<int>(user_command::invertertoggle1);
	auto &item = Train->ggInverterToggleButtons[itemindex];

	if (Command.action == GLFW_PRESS) {
		// only reacting to press, so the switch doesn't flip back and forth if key is held down
		bool kier = (Train->DynamicObject->DirectionGet() * Train->mvOccupied->CabOccupied > 0);
		int flag = Train->DynamicObject->MoverParameters->InverterControlCouplerFlag;
		TDynamicObject *p = Train->DynamicObject->GetFirstDynamic(Train->mvOccupied->CabOccupied < 0 ? end::rear : end::front, flag);
		while (p)
		{
			if (p->MoverParameters->eimc[eimc_p_Pmax] > 1)
			{
				if (itemindex < p->MoverParameters->InvertersNo)
				{
					p->MoverParameters->Inverters[itemindex].Activate = !p->MoverParameters->Inverters[itemindex].Activate;
					// visual feedback
					Train->ggInverterToggleButtons[itemindex].UpdateValue(p->MoverParameters->Inverters[itemindex].Activate ? 1.0 : 0.0, Train->dsbSwitch);
					break;
				}
				else
				{
					itemindex -= p->MoverParameters->InvertersNo;
				}

			}
			p = (kier ? p->Next(flag) : p->Prev(flag));
		}
	}
};

void TTrain::OnCommand_doorlocktoggle(TTrain *Train, command_data const &Command) {

    if( Train->ggDoorSignallingButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Door Lock switch is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the sound can loop uninterrupted
        if( false == Train->mvOccupied->Doors.lock_enabled ) {
            // turn on
            // TODO: door lock command to send through consist
            Train->mvOccupied->LockDoors( true );
            // visual feedback
            Train->ggDoorSignallingButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // turn off
            // TODO: door lock command to send through consist
            Train->mvOccupied->LockDoors( false );
            // visual feedback
            Train->ggDoorSignallingButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_doortoggleleft( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // NOTE: test how the door state check works with consists where the occupied vehicle doesn't have opening doors
        if( false == (
                ( Train->ggDoorLeftButton.GetDesiredValue() > 0.5 )
             || ( Train->ggDoorLeftOnButton.GetDesiredValue() > 0.5 ) ) ) {
            // open
            OnCommand_dooropenleft( Train, Command );
        }
        else {
            // close
            if( ( Train->ggDoorAllOffButton.SubModel != nullptr )
             && ( Train->ggDoorLeftOffButton.SubModel == nullptr ) ) {
                // OnCommand_doorcloseall( Train, Command );
                // if two-button setup lacks dedicated closing button require the user to press appropriate button manually
                return;
            }
            else {
                OnCommand_doorcloseleft( Train, Command );
            }
        }
    }
    else if( Command.action == GLFW_RELEASE ) {

        if( true == (
              ( Train->ggDoorLeftButton.GetDesiredValue() > 0.5 )
           || ( Train->ggDoorLeftOnButton.GetDesiredValue() > 0.5 ) ) ) {
            // open
            if( ( Train->mvOccupied->Doors.has_autowarning )
             && ( Train->mvOccupied->DepartureSignal ) ) {
                // complete closing the doors
                if( ( Train->ggDoorAllOffButton.SubModel != nullptr )
                 && ( Train->ggDoorLeftOffButton.SubModel == nullptr ) ) {
                    // OnCommand_doorcloseall( Train, Command );
                    // if two-button setup lacks dedicated closing button require the user to press appropriate button manually
                    return;
                }
                else {
                    OnCommand_doorcloseleft( Train, Command );
                }
            }
            else {
                OnCommand_dooropenleft( Train, Command );
            }
        }
        else {
            // close
            if( ( Train->ggDoorAllOffButton.SubModel != nullptr )
             && ( Train->ggDoorLeftOffButton.SubModel == nullptr ) ) {
                // OnCommand_doorcloseall( Train, Command );
                // if two-button setup lacks dedicated closing button require the user to press appropriate button manually
                return;
            }
            else {
                OnCommand_doorcloseleft( Train, Command );
            }
        }
        // visual feedback
        // dedicated closing buttons are presumed to be impulse switches and return automatically to neutral position
        // NOTE: temporary arrangement, can be removed when LD system is in place
        if( Train->ggDoorLeftOffButton.SubModel )
            Train->ggDoorLeftOffButton.UpdateValue( 0.0, Train->dsbSwitch );
        if( Train->ggDoorLeftOnButton.SubModel )
            Train->ggDoorLeftOnButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_doorpermitleft( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }
    if( false == Train->mvOccupied->Doors.permit_presets.empty() ) { return; }

    auto const side { (
    Train->cab_to_end() == end::front ?
        side::left :
        side::right ) };

    if( Command.action == GLFW_PRESS ) {

        if( Train->ggDoorLeftPermitButton.is_push() ) {
            // impulse switch
            Train->mvOccupied->PermitDoors( side );
            // visual feedback
            Train->ggDoorLeftPermitButton.UpdateValue( 1.0, Train->dsbSwitch );
            // start potential timer for remote door control
            Train->m_doorpermittimers[ side ] = Train->mvOccupied->DoorsOpenWithPermitAfter;
        }
        else {
            // two-state switch
            auto const newstate { !( Train->ggDoorLeftPermitButton.GetDesiredValue() > 0.5 ) };

            Train->mvOccupied->PermitDoors( side, newstate );
            // visual feedback
            Train->ggDoorLeftPermitButton.UpdateValue( ( newstate ? 1.0 : 0.0 ), Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {

        if( Train->ggDoorLeftPermitButton.is_push() ) {
            // impulse switch
            // visual feedback
            Train->ggDoorLeftPermitButton.UpdateValue( 0.0, Train->dsbSwitch );
            // reset potential remote door control timer
            Train->m_doorpermittimers[ side ] = -1.f;
        }
    }
}

void TTrain::OnCommand_doorpermitright( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_REPEAT ) { return; }
    if( false == Train->mvOccupied->Doors.permit_presets.empty() ) { return; }

    auto const side { (
        Train->cab_to_end() == end::front ?
            side::right :
            side::left ) };

    if( Command.action == GLFW_PRESS ) {

        if( Train->ggDoorRightPermitButton.type() == TGaugeType::push ) {
            // impulse switch
            Train->mvOccupied->PermitDoors( side );
            // visual feedback
            Train->ggDoorRightPermitButton.UpdateValue( 1.0, Train->dsbSwitch );
            // start potential timer for remote door control
            Train->m_doorpermittimers[ side ] = Train->mvOccupied->DoorsOpenWithPermitAfter;
        }
        else {
            // two-state switch
            auto const newstate { !( Train->ggDoorRightPermitButton.GetDesiredValue() > 0.5 ) };

            Train->mvOccupied->PermitDoors( side, newstate );
            // visual feedback
            Train->ggDoorRightPermitButton.UpdateValue( ( newstate ? 1.0 : 0.0 ), Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {

        if( Train->ggDoorRightPermitButton.type() == TGaugeType::push ) {
            // impulse switch
            // visual feedback
            Train->ggDoorRightPermitButton.UpdateValue( 0.0, Train->dsbSwitch );
            // reset potential remote door control timer
            Train->m_doorpermittimers[ side ] = -1.f;
        }
    }
}

void TTrain::OnCommand_doorpermitpresetactivatenext( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->mvOccupied->ChangeDoorPermitPreset( 1 );
        // visual feedback
        Train->ggDoorPermitPresetButton.UpdateValue( Train->mvOccupied->Doors.permit_preset, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_doorpermitpresetactivateprevious( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {

        Train->mvOccupied->ChangeDoorPermitPreset( -1 );
        // visual feedback
        Train->ggDoorPermitPresetButton.UpdateValue( Train->mvOccupied->Doors.permit_preset, Train->dsbSwitch );
    }
}


void TTrain::OnCommand_dooropenleft( TTrain *Train, command_data const &Command ) {

    auto const remoteopencontrol {
        ( Train->mvOccupied->Doors.open_control == control_t::driver )
     || ( Train->mvOccupied->Doors.open_control == control_t::mixed ) };

    if( false == remoteopencontrol ) { return; }

    if( ( Train->ggDoorLeftOnButton.SubModel == nullptr )
     && ( Train->ggDoorLeftButton.SubModel == nullptr ) ) {

        return;
    }

    if( Command.action == GLFW_PRESS ) {
        Train->mvOccupied->OperateDoors(
            ( Train->cab_to_end() == end::front ?
                side::left :
                side::right ),
            true );
        // visual feedback
        if( Train->ggDoorLeftOnButton.SubModel != nullptr ) {
            // two separate impulse switches
            Train->ggDoorLeftOnButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // single two-state switch
            Train->ggDoorLeftButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        if( Train->ggDoorLeftOnButton.SubModel != nullptr ) {
            // two separate impulse switches
            Train->ggDoorLeftOnButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_doorcloseleft( TTrain *Train, command_data const &Command ) {

    auto const remoteclosecontrol {
        ( Train->mvOccupied->Doors.close_control == control_t::driver )
     || ( Train->mvOccupied->Doors.close_control == control_t::mixed ) };

    if( false == remoteclosecontrol ) { return; }

    if( ( Train->ggDoorLeftOffButton.SubModel == nullptr )
     && ( Train->ggDoorLeftButton.SubModel == nullptr ) ) {

        return;
    }

    if( Command.action == GLFW_PRESS ) {

        if( Train->mvOccupied->Doors.has_autowarning ) {
            // automatic departure signal delays actual door closing until the button is released
            Train->mvOccupied->signal_departure( true );
        }
        else {
            // TODO: move door opening/closing to the update, so the switch animation doesn't hinge on door working
            Train->mvOccupied->OperateDoors(
                ( Train->cab_to_end() == end::front ?
                    side::left :
                    side::right ),
                false );
        }
        // visual feedback
        if( Train->ggDoorLeftOffButton.SubModel != nullptr ) {
            // two separate switches to open and close the door
            Train->ggDoorLeftOffButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // single two-state switch
            Train->ggDoorLeftButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {

        if( Train->mvOccupied->Doors.has_autowarning ) {
            // automatic departure signal delays actual door closing until the button is released
            Train->mvOccupied->signal_departure( false );
            // now we can actually close the door
            Train->mvOccupied->OperateDoors(
                ( Train->cab_to_end() == end::front ?
                    side::left :
                    side::right ),
                false );
        }
        // visual feedback
        // dedicated closing buttons are presumed to be impulse switches and return automatically to neutral position
        if( Train->ggDoorLeftOffButton.SubModel )
            Train->ggDoorLeftOffButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_doortoggleright( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        // NOTE: test how the door state check works with consists where the occupied vehicle doesn't have opening doors
        if( false == (
                ( Train->ggDoorRightButton.GetDesiredValue() > 0.5 )
             || ( Train->ggDoorRightOnButton.GetDesiredValue() > 0.5 ) ) ) {
            // open
            OnCommand_dooropenright( Train, Command );
        }
        else {
            // close
            if( ( Train->ggDoorAllOffButton.SubModel != nullptr )
             && ( Train->ggDoorRightOffButton.SubModel == nullptr ) ) {
                // OnCommand_doorcloseall( Train, Command );
                // if two-button setup lacks dedicated closing button require the user to press appropriate button manually
                return;
            }
            else {
                OnCommand_doorcloseright( Train, Command );
            }
        }
    }
    else if( Command.action == GLFW_RELEASE ) {

        if( true == (
                ( Train->ggDoorRightButton.GetDesiredValue() > 0.5 )
             || ( Train->ggDoorRightOnButton.GetDesiredValue() > 0.5 ) ) ) {
            // open
            if( ( Train->mvOccupied->Doors.has_autowarning )
             && ( Train->mvOccupied->DepartureSignal ) ) {
                // complete closing the doors
                if( ( Train->ggDoorAllOffButton.SubModel != nullptr )
                 && ( Train->ggDoorRightOffButton.SubModel == nullptr ) ) {
                    // OnCommand_doorcloseall( Train, Command );
                    // if two-button setup lacks dedicated closing button require the user to press appropriate button manually
                    return;
                }
                else {
                    OnCommand_doorcloseright( Train, Command );
                }
            }
            else {
                OnCommand_dooropenright( Train, Command );
            }
        }
        else {
            // close
            if( ( Train->ggDoorAllOffButton.SubModel != nullptr )
             && ( Train->ggDoorRightOffButton.SubModel == nullptr ) ) {
                // OnCommand_doorcloseall( Train, Command );
                // if two-button setup lacks dedicated closing button require the user to press appropriate button manually
                return;
            }
            else {
                OnCommand_doorcloseright( Train, Command );
            }
        }
        // visual feedback
        // dedicated closing buttons are presumed to be impulse switches and return automatically to neutral position
        // NOTE: temporary arrangement, can be removed when LD system is in place
        if( Train->ggDoorRightOffButton.SubModel )
            Train->ggDoorRightOffButton.UpdateValue( 0.0, Train->dsbSwitch );
        if( Train->ggDoorRightOnButton.SubModel )
            Train->ggDoorRightOnButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_dooropenright( TTrain *Train, command_data const &Command ) {

    auto const remoteopencontrol {
        ( Train->mvOccupied->Doors.open_control == control_t::driver )
     || ( Train->mvOccupied->Doors.open_control == control_t::mixed ) };

    if( false == remoteopencontrol ) { return; }

    if( ( Train->ggDoorRightOnButton.SubModel == nullptr )
     && ( Train->ggDoorRightButton.SubModel == nullptr ) ) {

        return;
    }

    if( Command.action == GLFW_PRESS ) {

        Train->mvOccupied->OperateDoors(
            ( Train->cab_to_end() == end::front ?
                side::right :
                side::left ),
            true );
        // visual feedback
        if( Train->ggDoorRightOnButton.SubModel != nullptr ) {
            // two separate impulse switches
            Train->ggDoorRightOnButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // single two-state switch
            Train->ggDoorRightButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        if( Train->ggDoorRightOnButton.SubModel != nullptr ) {
            // two separate impulse switches
            Train->ggDoorRightOnButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
}

void TTrain::OnCommand_doorcloseright( TTrain *Train, command_data const &Command ) {

    auto const remoteclosecontrol {
        ( Train->mvOccupied->Doors.close_control == control_t::driver )
     || ( Train->mvOccupied->Doors.close_control == control_t::mixed ) };

    if( false == remoteclosecontrol ) { return; }

    if( ( Train->ggDoorRightOffButton.SubModel == nullptr )
     && ( Train->ggDoorRightButton.SubModel == nullptr ) ) {

        return;
    }

    if( Command.action == GLFW_PRESS ) {

        if( Train->mvOccupied->Doors.has_autowarning ) {
            // automatic departure signal delays actual door closing until the button is released
            Train->mvOccupied->signal_departure( true );
        }
        else {
            Train->mvOccupied->OperateDoors(
                ( Train->cab_to_end() == end::front ?
                    side::right :
                    side::left ),
                false );
        }
        // visual feedback
        if( Train->ggDoorRightOffButton.SubModel != nullptr ) {
            // two separate switches to open and close the door
            Train->ggDoorRightOffButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
        else {
            // single two-state switch
            Train->ggDoorRightButton.UpdateValue( 0.0, Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {

        if( Train->mvOccupied->Doors.has_autowarning ) {
            // automatic departure signal delays actual door closing until the button is released
            Train->mvOccupied->signal_departure( false );
            // now we can actually close the door
            Train->mvOccupied->OperateDoors(
                ( Train->cab_to_end() == end::front ?
                    side::right :
                    side::left ),
                false );
        }
        // visual feedback
        // dedicated closing buttons are presumed to be impulse switches and return automatically to neutral position
        if( Train->ggDoorRightOffButton.SubModel )
            Train->ggDoorRightOffButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_dooropenall( TTrain *Train, command_data const &Command ) {

    auto const remoteopencontrol {
        ( Train->mvOccupied->Doors.open_control == control_t::driver )
     || ( Train->mvOccupied->Doors.open_control == control_t::mixed ) };

    if( false == remoteopencontrol ) { return; }

    if( Train->ggDoorAllOnButton.SubModel == nullptr ) {
        // TODO: expand definition of cab controls so we can know if the control is present without testing for presence of 3d switch
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Open All Doors switch is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {

        Train->mvOccupied->OperateDoors( side::right, true );
        Train->mvOccupied->OperateDoors( side::left, true );
        // visual feedback
        Train->ggDoorAllOnButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggDoorAllOnButton.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_doorcloseall( TTrain *Train, command_data const &Command ) {

    auto const remoteclosecontrol {
        ( Train->mvOccupied->Doors.close_control == control_t::driver )
     || ( Train->mvOccupied->Doors.close_control == control_t::mixed ) };

    if( false == remoteclosecontrol ) { return; }

    if( Train->ggDoorAllOffButton.SubModel == nullptr ) {
        // TODO: expand definition of cab controls so we can know if the control is present without testing for presence of 3d switch
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Close All Doors switch is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {

        if( Train->mvOccupied->Doors.has_autowarning ) {
            Train->mvOccupied->signal_departure( true );
        }
        if( Train->ggDoorAllOffButton.type() != TGaugeType::push_delayed ) {
            // delays the action until the button is released
            Train->mvOccupied->OperateDoors( side::right, false );
            Train->mvOccupied->OperateDoors( side::left, false );
        }
        // visual feedback
        Train->ggDoorLeftButton.UpdateValue( 0.0, Train->dsbSwitch );
        Train->ggDoorRightButton.UpdateValue( 0.0, Train->dsbSwitch );
        if( Train->ggDoorAllOffButton.SubModel )
            Train->ggDoorAllOffButton.UpdateValue( 1.0, Train->dsbSwitch );
    }
    else if( Command.action == GLFW_RELEASE ) {
        if( Train->mvOccupied->Doors.has_autowarning ) {
            Train->mvOccupied->signal_departure( false );
        }
        if( Train->ggDoorAllOffButton.type() == TGaugeType::push_delayed ) {
            // now we can actually close the door
            Train->mvOccupied->OperateDoors( side::right, false );
            Train->mvOccupied->OperateDoors( side::left, false );
        }
        // visual feedback
        if( Train->ggDoorAllOffButton.SubModel )
            Train->ggDoorAllOffButton.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_doorsteptoggle( TTrain *Train, command_data const &Command ) {
    // TODO: move logic/visualization code to the gauge, on_command() should return hint whether it should invoke a reaction
    if( Command.action == GLFW_PRESS ) {
        // effect
        if( false == Train->ggDoorStepButton.is_delayed() ) {
            Train->mvOccupied->PermitDoorStep( false == Train->mvOccupied->Doors.step_enabled );
        }
        // visual feedback
        auto const isactive { (
            Train->ggDoorStepButton.is_push() // always press push button
         || Train->mvOccupied->Doors.step_enabled ) }; // for toggle buttons indicate item state
        Train->ggDoorStepButton.UpdateValue( isactive ? 1 : 0 );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // effect
        if( Train->ggDoorStepButton.is_delayed() ) {
            Train->mvOccupied->PermitDoorStep( false == Train->mvOccupied->Doors.step_enabled );
        }
        // visual feedback
        if( Train->ggDoorStepButton.is_push() ) {
            Train->ggDoorStepButton.UpdateValue( 0 );
        }
    }
}

void TTrain::OnCommand_doormodetoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        Train->mvOccupied->ChangeDoorControlMode( false == Train->mvOccupied->Doors.remote_only );
    }
}

void TTrain::OnCommand_nearestcarcouplingincrease( TTrain *Train, command_data const &Command ) {

	if( ( true == Command.freefly )
     && ( Command.action == GLFW_PRESS ) ) {
        // tryb freefly, press only
        auto coupler { -1 };
		auto *vehicle { Train->DynamicObject->ABuScanNearestObject( Command.location, Train->DynamicObject->GetTrack(), 1, 1500, coupler ) };
        if( vehicle == nullptr )
			vehicle = Train->DynamicObject->ABuScanNearestObject( Command.location, Train->DynamicObject->GetTrack(), -1, 1500, coupler );

        if( ( coupler != -1 )
         && ( vehicle != nullptr ) ) {

            vehicle->couple( coupler );
        }
        if( Train->DynamicObject->Mechanik ) {
            // aktualizacja flag kierunku w składzie
            Train->DynamicObject->Mechanik->CheckVehicles( Connect );
        }
    }
}

void TTrain::OnCommand_nearestcarcouplingdisconnect( TTrain *Train, command_data const &Command ) {

	if( ( true == Command.freefly )
     && ( Command.action == GLFW_PRESS ) ) {
        // tryb freefly, press only
        auto coupler { -1 };
		auto *vehicle { Train->DynamicObject->ABuScanNearestObject( Command.location, Train->DynamicObject->GetTrack(), 1, 1500, coupler ) };
        if( vehicle == nullptr )
			vehicle = Train->DynamicObject->ABuScanNearestObject( Command.location, Train->DynamicObject->GetTrack(), -1, 1500, coupler );

        if( ( coupler != -1 )
         && ( vehicle != nullptr ) ) {

            vehicle->uncouple( coupler );
        }
        if( Train->DynamicObject->Mechanik ) {
            // aktualizacja flag kierunku w składzie
            Train->DynamicObject->Mechanik->CheckVehicles( Disconnect );
        }
    }
}

void TTrain::OnCommand_nearestcarcoupleradapterattach( TTrain *Train, command_data const &Command ) {

    if( ( true == Command.freefly )
     && ( Command.action == GLFW_PRESS ) ) {
        // tryb freefly, press only
        auto *vehicle { std::get<TDynamicObject *>( simulation::Region->find_vehicle( Command.location, 50, false, true ) ) };
        if( vehicle == nullptr ) { return; }

        auto const coupler = (
            glm::length2( glm::vec3 { vehicle->CouplerPosition( end::front ) } - Command.location ) < glm::length2( glm::vec3 { vehicle->CouplerPosition( end::rear ) } - Command.location ) ?
                end::front :
                end::rear );

        vehicle->attach_coupler_adapter( coupler );
    }
}

void TTrain::OnCommand_nearestcarcoupleradapterremove( TTrain *Train, command_data const &Command ) {

    if( ( true == Command.freefly )
     && ( Command.action == GLFW_PRESS ) ) {
        // tryb freefly, press only
        auto *vehicle { std::get<TDynamicObject *>( simulation::Region->find_vehicle( Command.location, 50, false, true ) ) };
        if( vehicle == nullptr ) { return; }

        auto const coupler = (
            glm::length2( glm::vec3 { vehicle->CouplerPosition( end::front ) } - Command.location ) < glm::length2( glm::vec3 { vehicle->CouplerPosition( end::rear ) } - Command.location ) ?
                end::front :
                end::rear );

        vehicle->remove_coupler_adapter( coupler );
    }
}

void TTrain::OnCommand_occupiedcarcouplingdisconnect( TTrain *Train, command_data const &Command ) {

//    if( false == Train->m_controlmapper.contains( "couplingdisconnect_sw:" ) ) { return; }

    if( Command.action == GLFW_PRESS ) {
        // visual feedback
        Train->m_couplingdisconnect = true;

        if( Train->iCabn == 0 ) { return; }

        if( Train->DynamicObject ) {
            Train->DynamicObject->uncouple( Train->cab_to_end() );
            if( Train->DynamicObject->Mechanik ) {
                // aktualizacja flag kierunku w składzie
                Train->DynamicObject->Mechanik->CheckVehicles( Disconnect );
            }
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->m_couplingdisconnect = false;
    }
}

void TTrain::OnCommand_departureannounce( TTrain *Train, command_data const &Command ) {

    if( Train->ggDepartureSignalButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Departure Signal button is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only reacting to press, so the sound can loop uninterrupted
        if( false == Train->mvOccupied->DepartureSignal ) {
            // turn on
            Train->mvOccupied->signal_departure( true );
            // visual feedback
            Train->ggDepartureSignalButton.UpdateValue( 1.0, Train->dsbSwitch );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // turn off
        Train->mvOccupied->signal_departure( false );
        // visual feedback
        Train->ggDepartureSignalButton.UpdateValue( 0.0, Train->dsbSwitch );
    }
}

void TTrain::OnCommand_hornlowactivate( TTrain *Train, command_data const &Command ) {

    if( ( Train->ggHornButton.SubModel == nullptr )
     && ( Train->ggHornLowButton.SubModel == nullptr ) ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Horn button is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only need to react to press, sound will continue until stopped
        if( false == TestFlag( Train->mvOccupied->WarningSignal, 1 ) ) {
            // turn on
            Train->mvOccupied->WarningSignal |= 1;
/*
            if( true == TestFlag( Train->mvOccupied->WarningSignal, 2 ) ) {
                // low and high horn are treated as mutually exclusive
                Train->mvControlled->WarningSignal &= ~2;
            }
*/
            // visual feedback
            Train->ggHornButton.UpdateValue( -1.0 );
            Train->ggHornLowButton.UpdateValue( 1.0 );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // turn off
/*
        // NOTE: we turn off both low and high horn, due to unreliability of release event when shift key is involved
        Train->mvOccupied->WarningSignal &= ~( 1 | 2 );
*/
        Train->mvOccupied->WarningSignal &= ~1;
        // visual feedback
        Train->ggHornButton.UpdateValue( 0.0 );
        Train->ggHornLowButton.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_hornhighactivate( TTrain *Train, command_data const &Command ) {

    if( ( Train->ggHornButton.SubModel == nullptr )
     && ( Train->ggHornHighButton.SubModel == nullptr ) ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Horn button is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only need to react to press, sound will continue until stopped
        if( false == TestFlag( Train->mvOccupied->WarningSignal, 2 ) ) {
            // turn on
            Train->mvOccupied->WarningSignal |= 2;
/*
            if( true == TestFlag( Train->mvOccupied->WarningSignal, 1 ) ) {
                // low and high horn are treated as mutually exclusive
                Train->mvControlled->WarningSignal &= ~1;
            }
*/
            // visual feedback
            Train->ggHornButton.UpdateValue( 1.0 );
            Train->ggHornHighButton.UpdateValue( 1.0 );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // turn off
/*
        // NOTE: we turn off both low and high horn, due to unreliability of release event when shift key is involved
        Train->mvOccupied->WarningSignal &= ~( 1 | 2 );
*/
        Train->mvOccupied->WarningSignal &= ~2;
        // visual feedback
        Train->ggHornButton.UpdateValue( 0.0 );
        Train->ggHornHighButton.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_whistleactivate( TTrain *Train, command_data const &Command ) {

    if( Train->ggWhistleButton.SubModel == nullptr ) {
        if( Command.action == GLFW_PRESS ) {
            WriteLog( "Whistle button is missing, or wasn't defined" );
        }
        return;
    }

    if( Command.action == GLFW_PRESS ) {
        // only need to react to press, sound will continue until stopped
        if( false == TestFlag( Train->mvOccupied->WarningSignal, 4 ) ) {
            // turn on
            Train->mvOccupied->WarningSignal |= 4;
            // visual feedback
            Train->ggWhistleButton.UpdateValue( 1.0 );
        }
    }
    else if( Command.action == GLFW_RELEASE ) {
        // turn off
        Train->mvOccupied->WarningSignal &= ~4;
        // visual feedback
        Train->ggWhistleButton.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_radiotoggle( TTrain *Train, command_data const &Command ) {

    if( Command.action != GLFW_PRESS ) { return; }

    // NOTE: we ignore the lack of 3d model to allow system reset after receiving radio-stop signal
/*
    if( false == Train->m_controlmapper.contains( "radio_sw:" ) ) {
        return;
    }
*/
    // only reacting to press, so the sound can loop uninterrupted
    if( false == Train->mvOccupied->Radio ) {
        // turn on
        Train->mvOccupied->Radio = true;
    }
    else {
        // turn off
        Train->mvOccupied->Radio = false;
    }
}

void TTrain::OnCommand_radiochannelincrease( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        Train->RadioChannel() = clamp( Train->RadioChannel() + 1, 1, 10 );
        // visual feedback
        Train->ggRadioChannelSelector.UpdateValue( Train->RadioChannel() - 1 );
        Train->ggRadioChannelNext.UpdateValue( 1.0 );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggRadioChannelNext.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_radiochanneldecrease( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        Train->RadioChannel() = clamp( Train->RadioChannel() - 1, 1, 10 );
        // visual feedback
        Train->ggRadioChannelSelector.UpdateValue( Train->RadioChannel() - 1 );
        Train->ggRadioChannelPrevious.UpdateValue( 1.0 );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggRadioChannelPrevious.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_radiostopsend( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        if( ( true == Train->mvOccupied->Radio )
         && ( Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable ) ) {
            simulation::Region->RadioStop( Train->Dynamic()->GetPosition() );
        }
        // visual feedback
        Train->ggRadioStop.UpdateValue( 1.0 );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggRadioStop.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_radiostoptest( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        if( ( Train->RadioChannel() == 10 )
         && ( true == Train->mvOccupied->Radio )
         && ( Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable ) ) {
            Train->Dynamic()->RadioStop();
        }
        // visual feedback
        Train->ggRadioTest.UpdateValue( 1.0 );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggRadioTest.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_radiocall3send( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        if( ( Train->RadioChannel() != 10 )
         && ( true == Train->mvOccupied->Radio )
         && ( Train->mvOccupied->Power24vIsAvailable || Train->mvOccupied->Power110vIsAvailable) ) {
            simulation::Events.queue_receivers( radio_message::call3, Train->Dynamic()->GetPosition() );
        }
        // visual feedback
        Train->ggRadioCall3.UpdateValue( 1.0 );
    }
    else if( Command.action == GLFW_RELEASE ) {
        // visual feedback
        Train->ggRadioCall3.UpdateValue( 0.0 );
    }
}

void TTrain::OnCommand_radiovolumeincrease(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		Global.RadioVolume = clamp(Global.RadioVolume + 0.125, 0.0, 1.0);
		// visual feedback
		Train->ggRadioVolumeSelector.UpdateValue(Global.RadioVolume);
		Train->ggRadioVolumeNext.UpdateValue(1.0);
	}
	else if (Command.action == GLFW_RELEASE) {
		// visual feedback
		Train->ggRadioVolumeNext.UpdateValue(0.0);
	}
}

void TTrain::OnCommand_radiovolumedecrease(TTrain *Train, command_data const &Command) {

	if (Command.action == GLFW_PRESS) {
		Global.RadioVolume = clamp(Global.RadioVolume - 0.125, 0.0, 1.0);
		// visual feedback
		Train->ggRadioVolumeSelector.UpdateValue(Global.RadioVolume);
		Train->ggRadioVolumePrevious.UpdateValue(1.0);
	}
	else if (Command.action == GLFW_RELEASE) {
		// visual feedback
		Train->ggRadioVolumePrevious.UpdateValue(0.0);
	}
}


void TTrain::OnCommand_cabchangeforward( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        auto const *owner { (
            Train->DynamicObject->ctOwner != nullptr ?
                Train->DynamicObject->ctOwner :
                Train->DynamicObject->Mechanik ) };
        auto const movedirection { 1 };
        if( false == Train->CabChange( movedirection ) ) {
            auto const exitdirection { (
                movedirection > 0 ?
                    end::front :
                    end::rear ) };
            if( TestFlag( Train->mvOccupied->Couplers[ exitdirection ].CouplingFlag, coupling::gangway ) ) {
                // przejscie do nastepnego pojazdu
                auto *targetvehicle = (
                    exitdirection == end::front ?
                        Train->DynamicObject->PrevConnected() :
                        Train->DynamicObject->NextConnected() );
                targetvehicle->MoverParameters->CabOccupied = (
                    Train->mvOccupied->Neighbours[ exitdirection ].vehicle_end ?
                        -1 :
                         1 );
                Train->MoveToVehicle( targetvehicle );
            }
        }
        // HACK: match consist door permit state with the preset in the active cab
        if( Train->ggDoorPermitPresetButton.SubModel != nullptr ) {
            Train->mvOccupied->ChangeDoorPermitPreset( 0 );
        }
        // HACK: update lights state
        if( Train->mvOccupied->LightsPosNo > 0 ) {
            Train->DynamicObject->SetLights();
        }
    }
}

void TTrain::OnCommand_cabchangebackward( TTrain *Train, command_data const &Command ) {

    if( Command.action == GLFW_PRESS ) {
        auto const *owner { (
            Train->DynamicObject->ctOwner != nullptr ?
                Train->DynamicObject->ctOwner :
                Train->DynamicObject->Mechanik ) };
        auto const movedirection { -1 };
        if( false == Train->CabChange( movedirection ) ) {
            // current vehicle doesn't extend any farther in this direction, check if we there's one connected we can move to
            auto const exitdirection { (
                movedirection > 0 ?
                    end::front :
                    end::rear ) };
            if( TestFlag( Train->mvOccupied->Couplers[ exitdirection ].CouplingFlag, coupling::gangway ) ) {
                // przejscie do nastepnego pojazdu
                auto *targetvehicle = (
                    exitdirection == end::front ?
                        Train->DynamicObject->PrevConnected() :
                        Train->DynamicObject->NextConnected() );
                targetvehicle->MoverParameters->CabOccupied = (
                    Train->mvOccupied->Neighbours[ exitdirection ].vehicle_end ?
                        -1 :
                         1 );
                Train->MoveToVehicle( targetvehicle );
            }
        }
        // HACK: match consist door permit state with the preset in the active cab
        if( Train->ggDoorPermitPresetButton.SubModel != nullptr ) {
            Train->mvOccupied->ChangeDoorPermitPreset( 0 );
        }
        // HACK: update lights state
        if( Train->mvOccupied->LightsPosNo > 0 ) {
            Train->DynamicObject->SetLights();
        }
    }
}

void TTrain::OnCommand_vehiclemoveforwards(TTrain *Train, const command_data &Command) {
	if (Command.action == GLFW_RELEASE || !DebugModeFlag)
		return;

	Train->DynamicObject->move_set(100.0);
}

void TTrain::OnCommand_vehiclemovebackwards(TTrain *Train, const command_data &Command) {
	if (Command.action == GLFW_RELEASE || !DebugModeFlag)
		return;

	Train->DynamicObject->move_set(-100.0);
}

void TTrain::OnCommand_vehicleboost(TTrain *Train, const command_data &Command) {
	if (Command.action == GLFW_RELEASE || !DebugModeFlag)
		return;

	double boost = Command.param1 != 0.0 ? Command.param1 : 2.78;

    if( Train->DynamicObject == nullptr ) { return; }

    auto *vehicle { Train->DynamicObject };
	while( vehicle ) {
		vehicle->MoverParameters->V += vehicle->DirectionGet() * boost;
		vehicle = vehicle->Next(); // pozostałe też
	}
	vehicle = Train->DynamicObject->Prev();
	while( vehicle ) {
		vehicle->MoverParameters->V += vehicle->DirectionGet() * boost;
		vehicle = vehicle->Prev(); // w drugą stronę też
	}
}

// cab movement update, fixed step part
void TTrain::UpdateCab() {

    // Ra: przesiadka, jeśli AI zmieniło kabinę (a człon?)...
    if( ( DynamicObject->Mechanik ) // może nie być?
     && ( DynamicObject->Mechanik->AIControllFlag ) ) { 
        
        if( iCabn != ( // numer kabiny (-1: kabina B)
                mvOccupied->CabOccupied == -1 ?
                    2 :
                    mvOccupied->CabOccupied ) ) {

            InitializeCab(
                mvOccupied->CabOccupied,
                mvOccupied->TypeName + ".mmd" );
        }
    }
    iCabn = ( mvOccupied->CabOccupied == -1 ?
        2 :
        mvOccupied->CabOccupied );
}

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<side>( idx ), true );
        }
    }
    // 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_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<std::size_t>( 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<std::size_t>( 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 };
    fTachoVelocity = static_cast<float>( std::min( std::abs(11.31 * mvControlled->WheelDiameter * mvControlled->nrot), mvControlled->Vmax * 1.05) );
    { // 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 > 1) // jedzie
                fTachoVelocityJump = fTachoVelocity + (2.0 - LocalRandom(3) + LocalRandom(3)) * 0.5;
            else
                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;
			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] = Max0R(fEIMParams[1 + in][0], 0);
                fEIMParams[1 + in][2] = -Min0R(fEIMParams[1 + in][0], 0);
                fEIMParams[1 + in][3] = p->MoverParameters->eimv[eimv_Fmax] / Max0R(p->MoverParameters->eimv[eimv_Fful], 1);
                fEIMParams[1 + in][4] = Max0R(fEIMParams[1 + in][3], 0);
                fEIMParams[1 + in][5] = -Min0R(fEIMParams[1 + in][3], 0);
                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] = Max0R(fEIMParams[0][3], 0);
    fEIMParams[0][5] = -Min0R(fEIMParams[0][3], 0);
    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 );
    }

    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)
             || (true == mvControlled->Mains) ) ?
                true :
                false ) );
        btLampkaWylSzybkiOff.Turn(
            ( ( ( m_linebreakerstate == 2 )
             || ( true == mvControlled->Mains ) ) ?
                false :
                true ) );
        btLampkaMainBreakerReady.Turn(
            ( ( ( mvControlled->MainsInitTimeCountdown > 0.0 )
             || ( m_linebreakerstate == 2 )
             || ( true == mvControlled->Mains ) ) ?
                false :
                true ) );
        btLampkaMainBreakerBlinkingIfReady.Turn(
            ( ( (m_linebreakerstate == 2)
             || (true == mvControlled->Mains)
             || ( ( mvControlled->MainsInitTimeCountdown < 0.0 ) && ( simulation::Time.data().wMilliseconds > 500 ) ) ) ?
                true :
                false ) );

        btLampkaPrzetw.Turn( mvOccupied->Power110vIsAvailable );
        btLampkaPrzetwOff.Turn( false == mvOccupied->Power110vIsAvailable );
        btLampkaNadmPrzetw.Turn( Dynamic()->Mechanik ? Dynamic()->Mechanik->IsAnyConverterOverloadRelayOpen : mvControlled->ConvOvldFlag );

        btLampkaOpory.Turn(
            mvControlled->StLinFlag ?
                mvControlled->ResistorsFlagCheck() :
                false );

        btLampkaBezoporowa.Turn(
            ( true == mvControlled->ResistorsFlagCheck() )
         || ( mvControlled->MainCtrlActualPos == 0 ) ); // do EU04

        btLampkaStyczn.Turn(
            ( ( mvControlled->StLinFlag ) || ( mvControlled->ControlPressureSwitch ) ) ?
                false :
                ( mvControlled->BrakePress < 1.0 ) ); // mozna prowadzic rozruch

        btLampkaPrzekRozn.Turn(
            ( ( mvControlled->GroundRelay ) || ( mvControlled->ControlPressureSwitch ) ) ?
                false :
                ( mvControlled->BrakePress < 1.0 ) ); // relay is off and needs a reset

        btLampkaNadmSil.Turn(
            ( ( false == mvControlled->FuseFlagCheck() ) || ( mvControlled->ControlPressureSwitch ) ) ?
                false :
                ( mvControlled->BrakePress < 1.0 ) ); // relay is off and needs a reset

        if( ( ( mvControlled->CabOccupied ==  1 ) && ( TestFlag( mvControlled->Couplers[ end::rear  ].CouplingFlag, coupling::control ) ) )
         || ( ( mvControlled->CabOccupied == -1 ) && ( TestFlag( mvControlled->Couplers[ end::front ].CouplingFlag, coupling::control ) ) ) ) {
            btLampkaUkrotnienie.Turn( true );
        }
        else {
            btLampkaUkrotnienie.Turn( false );
        }

        //         if
        //         ((TestFlag(mvControlled->BrakeStatus,+b_Rused+b_Ractive)))//Lampka drugiego stopnia hamowania
        btLampkaHamPosp.Turn((TestFlag(mvOccupied->Hamulec->GetBrakeStatus(), 1))); // lampka drugiego stopnia hamowania
        //TODO: youBy wyciągnąć flagę wysokiego stopnia

        // hunter-121211: lampka zanikowo-pradowego wentylatorow:
        btLampkaNadmWent.Turn( ( mvControlled->RventRot < 5.0 ) && ( mvControlled->ResistorsFlagCheck() ) );
        //-------

        btLampkaWysRozr.Turn(!(mvControlled->Imax < mvControlled->ImaxHi));

        if( ( false == mvControlled->DelayCtrlFlag )
         && ( ( mvControlled->ScndCtrlActualPos > 0 )
         || ( ( mvControlled->RList[ mvControlled->MainCtrlActualPos ].ScndAct != 0 )
           && ( mvControlled->RList[ mvControlled->MainCtrlActualPos ].ScndAct != 255 ) ) ) ) {
            btLampkaBoczniki.Turn( true );
        }
        else {
            btLampkaBoczniki.Turn( false );
        }

        btLampkaNapNastHam.Turn(mvControlled->DirActive != 0); // napiecie na nastawniku hamulcowym
        btLampkaSprezarka.Turn(mvControlled->CompressorFlag); // mutopsitka dziala
        btLampkaSprezarkaOff.Turn( false == mvControlled->CompressorFlag );
        btLampkaFuelPumpOff.Turn( false == mvControlled->FuelPump.is_active );
        // boczniki
        unsigned char scp; // Ra: dopisałem "unsigned"
        // Ra: w SU45 boczniki wchodzą na MainCtrlPos, a nie na MainCtrlActualPos
        // - pokićkał ktoś?
        scp = mvControlled->RList[mvControlled->MainCtrlPos].ScndAct;
        scp = (scp == 255 ? 0 : scp); // Ra: whatta hella is this?
        if ((mvControlled->ScndCtrlPos > 0) || (mvControlled->ScndInMain != 0) && (scp > 0))
        { // boczniki pojedynczo
            btLampkaBocznik1.Turn( true );
            btLampkaBocznik2.Turn(mvControlled->ScndCtrlPos > 1);
            btLampkaBocznik3.Turn(mvControlled->ScndCtrlPos > 2);
            btLampkaBocznik4.Turn(mvControlled->ScndCtrlPos > 3);
        }
        else
        { // wyłączone wszystkie cztery
            btLampkaBocznik1.Turn( false );
            btLampkaBocznik2.Turn( false );
            btLampkaBocznik3.Turn( false );
            btLampkaBocznik4.Turn( false );
        }

        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 ) && ( false == mvControlled->DynamicBrakeFlag ) );
                break;
            }
        }
        // KURS90
        btLampkaMaxSila.Turn(abs(mvControlled->Im) >= 350);
        btLampkaPrzekrMaxSila.Turn(abs(mvControlled->Im) >= 450);
        btLampkaRadio.Turn(mvOccupied->Radio);
        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
        btLampkaForward.Turn(mvControlled->DirActive > 0); // jazda do przodu
        btLampkaBackward.Turn(mvControlled->DirActive < 0); // jazda do tyłu
        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 );
        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 );
        btLampkaUkrotnienie.Turn( false );
        btLampkaHamPosp.Turn( false );
        btLampkaBoczniki.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 );
        btLampkaMaxSila.Turn( false );
        btLampkaPrzekrMaxSila.Turn( false );
        btLampkaRadio.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 );
        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<double>(LocalBrakePosNo)
            */
            1 };
        ggJointCtrl.UpdateValue(
            ( mvOccupied->LocalBrakePosA > 0.0 ? mvOccupied->LocalBrakePosA * LocalBrakePosNo * -1 * brakerangemultiplier :
                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 = clamp<double>( b, -2.0, 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 = clamp<double>( b, -1.0, 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 = clamp<double>( 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
/*         || ( Global.bMWDmasterEnable && Global.bMWDBreakEnable )*/ ) ) {
            // Ra: nie najlepsze miejsce, ale na początek gdzieś to dać trzeba
            // Firleju: dlatego kasujemy i zastepujemy funkcją w Console
            auto const b = clamp<double>(
                Console::AnalogCalibrateGet( 1 ),
                0.0,
                1.0 );
            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();
    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();
    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();
    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();

    ggLightsButton.Update();
    ggUpperLightButton.Update();
    ggLeftLightButton.Update();
    ggRightLightButton.Update();
    ggLeftEndLightButton.Update();
    ggRightEndLightButton.Update();
    // hunter-230112
    ggRearUpperLightButton.Update();
    ggRearLeftLightButton.Update();
    ggRearRightLightButton.Update();
    ggRearLeftEndLightButton.Update();
    ggRearRightEndLightButton.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();

    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);

    // sounds
    update_sounds( Deltatime );

    return true; //(DynamicObject->Update(dt));
} // koniec update

void
TTrain::update_sounds( double const Deltatime ) {

    double volume { 0.0 };
    double const brakevolumescale { 0.5 };

    // Winger-160404 - syczenie pomocniczego (luzowanie)
    if( m_lastlocalbrakepressure != -1.f ) {
        // calculate rate of pressure drop in local brake cylinder, once it's been initialized
        auto const brakepressuredifference { mvOccupied->LocBrakePress - m_lastlocalbrakepressure };
        m_localbrakepressurechange = interpolate<float>( m_localbrakepressurechange, 10 * ( brakepressuredifference / Deltatime ), 0.1f );
    }
    m_lastlocalbrakepressure = mvOccupied->LocBrakePress;
    // local brake, release
    if( rsSBHiss ) {
        if( ( m_localbrakepressurechange < -0.05f )
         && ( mvOccupied->LocBrakePress > mvOccupied->BrakePress - 0.05 ) ) {
            rsSBHiss->gain( clamp( rsSBHiss->m_amplitudeoffset + rsSBHiss->m_amplitudefactor * -m_localbrakepressurechange * 0.05, 0.0, 1.5 ) );
            rsSBHiss->play( sound_flags::exclusive | sound_flags::looping );
        }
        else {
            // don't stop the sound too abruptly
            volume = std::max( 0.0, rsSBHiss->gain() - 0.1 * Deltatime );
            rsSBHiss->gain( volume );
            if( volume < 0.05 ) {
                rsSBHiss->stop();
            }
        }
    }
    // local brake, engage
    if( rsSBHissU ) {
        if( m_localbrakepressurechange > 0.05f ) {
            rsSBHissU->gain( clamp( rsSBHissU->m_amplitudeoffset + rsSBHissU->m_amplitudefactor * m_localbrakepressurechange * 0.05, 0.0, 1.5 ) );
            rsSBHissU->play( sound_flags::exclusive | sound_flags::looping );
        }
        else {
            // don't stop the sound too abruptly
            volume = std::max( 0.0, rsSBHissU->gain() - 0.1 * Deltatime );
            rsSBHissU->gain( volume );
            if( volume < 0.05 ) {
                rsSBHissU->stop();
            }
        }
    }

    // McZapkie-280302 - syczenie
    // TODO: softer volume reduction than plain abrupt stop, perhaps as reusable wrapper?
    if( ( mvOccupied->BrakeHandle == TBrakeHandle::FV4a )
     || ( mvOccupied->BrakeHandle == TBrakeHandle::FVel6 ) ) {
        // upuszczanie z PG
        if( rsHiss ) {
            fPPress = interpolate( fPPress, static_cast<float>( mvOccupied->Handle->GetSound( s_fv4a_b ) ), 0.05f );
            volume = (
                fPPress > 0 ?
                    rsHiss->m_amplitudefactor * fPPress * 0.25 + rsHiss->m_amplitudeoffset :
                    0 );
            if( volume * brakevolumescale > 0.05 ) {
                rsHiss->gain( volume * brakevolumescale );
                rsHiss->play( sound_flags::exclusive | sound_flags::looping );
            }
            else {
                rsHiss->stop();
            }
        }
        // napelnianie PG
        if( rsHissU ) {
            fNPress = interpolate( fNPress, static_cast<float>( mvOccupied->Handle->GetSound( s_fv4a_u ) ), 0.25f );
            volume = (
                fNPress > 0 ?
                    rsHissU->m_amplitudefactor * fNPress + rsHissU->m_amplitudeoffset :
                    0 );
            if( volume * brakevolumescale > 0.05 ) {
                rsHissU->gain( volume * brakevolumescale );
                rsHissU->play( sound_flags::exclusive | sound_flags::looping );
            }
            else {
                rsHissU->stop();
            }
        }
        // upuszczanie przy naglym
        if( rsHissE ) {
            volume = mvOccupied->Handle->GetSound( s_fv4a_e ) * rsHissE->m_amplitudefactor + rsHissE->m_amplitudeoffset;
            if( volume * brakevolumescale > 0.05 ) {
                rsHissE->gain( volume * brakevolumescale );
                rsHissE->play( sound_flags::exclusive | sound_flags::looping );
            }
            else {
                rsHissE->stop();
            }
        }
        // upuszczanie sterujacego fala
        if( rsHissX ) {
            volume = mvOccupied->Handle->GetSound( s_fv4a_x ) * rsHissX->m_amplitudefactor + rsHissX->m_amplitudeoffset;
            if( volume * brakevolumescale > 0.05 ) {
                rsHissX->gain( volume * brakevolumescale );
                rsHissX->play( sound_flags::exclusive | sound_flags::looping );
            }
            else {
                rsHissX->stop();
            }
        }
        // upuszczanie z czasowego 
        if( rsHissT ) {
            volume = mvOccupied->Handle->GetSound( s_fv4a_t ) * rsHissT->m_amplitudefactor + +rsHissT->m_amplitudeoffset;
            if( volume * brakevolumescale > 0.05 ) {
                rsHissT->gain( volume * brakevolumescale );
                rsHissT->play( sound_flags::exclusive | sound_flags::looping );
            }
            else {
                rsHissT->stop();
            }
        }

    } else {
        // jesli nie FV4a
        // upuszczanie z PG
        if( rsHiss ) {
            fPPress = ( 4.0f * fPPress + std::max( 0.0, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f );
            volume = (
                fPPress > 0.0f ?
                    2.0 * rsHiss->m_amplitudefactor * fPPress + rsHiss->m_amplitudeoffset :
                    0.0 );
            if( volume > 0.05 ) {
                rsHiss->gain( volume );
                rsHiss->play( sound_flags::exclusive | sound_flags::looping );
            }
            else {
                rsHiss->stop();
            }
        }
        // napelnianie PG
        if( rsHissU ) {
            fNPress = ( 4.0f * fNPress + Min0R( 0.0, mvOccupied->dpMainValve ) ) / ( 4.0f + 1.0f );
            volume = (
                fNPress < 0.0f ?
                    -1.0 * rsHissU->m_amplitudefactor * fNPress + rsHissU->m_amplitudeoffset :
                     0.0 );
            if( volume > 0.01 ) {
                rsHissU->gain( volume );
                rsHissU->play( sound_flags::exclusive | sound_flags::looping );
            }
            else {
                rsHissU->stop();
            }
        }
    } // koniec nie FV4a

    // brakes
    if( rsBrake ) {
        if( ( mvOccupied->UnitBrakeForce > 10.0 )
         && ( mvOccupied->Vel > 0.05 ) ) {

            auto const brakeforceratio{
                clamp(
                    mvOccupied->UnitBrakeForce / std::max( 1.0, mvOccupied->BrakeForceR( 1.0, mvOccupied->Vel ) / ( mvOccupied->NAxles * std::max( 1, mvOccupied->NBpA ) ) ),
                    0.0, 1.0 ) };
            // HACK: in external view mute the sound rather than stop it, in case there's an opening bookend it'd (re)play on sound restart after returning inside
            volume = (
                FreeFlyModeFlag ?
                    0.0 :
                    rsBrake->m_amplitudeoffset
                    + std::sqrt( brakeforceratio * interpolate( 0.4, 1.0, ( mvOccupied->Vel / ( 1 + mvOccupied->Vmax ) ) ) ) * rsBrake->m_amplitudefactor );
            rsBrake->pitch( rsBrake->m_frequencyoffset + mvOccupied->Vel * rsBrake->m_frequencyfactor );
            rsBrake->gain( volume );
            rsBrake->play( sound_flags::exclusive | sound_flags::looping );
        }
        else {
            rsBrake->stop();
        }
    }

    // ambient sound
    // since it's typically ticking of the clock we can center it on tachometer or on middle of compartment bounding area
    if( rsFadeSound ) {
        rsFadeSound->play( sound_flags::exclusive | sound_flags::looping );
    }

    if( dsbSlipAlarm ) {
        // alarm przy poslizgu dla 181/182 - BOMBARDIER
        if( ( mvControlled->SlippingWheels )
         && ( DynamicObject->GetVelocity() > 1.0 ) ) {
            dsbSlipAlarm->play( sound_flags::exclusive | sound_flags::looping );
        }
        else {
            dsbSlipAlarm->stop();
        }
    }
    // szum w czasie jazdy
    if( rsRunningNoise ) {
        if( ( false == FreeFlyModeFlag )
         && ( false == Global.CabWindowOpen )
         && ( DynamicObject->GetVelocity() > 0.5 ) ) {

            update_sounds_runningnoise( *rsRunningNoise );
        }
        else {
            // don't play the optional ending sound if the listener switches views
            rsRunningNoise->stop( true == FreeFlyModeFlag );
        }
    }
    // hunting oscillation noise
    if( rsHuntingNoise ) {
        if( ( false == FreeFlyModeFlag )
         && ( false == Global.CabWindowOpen )
         && ( DynamicObject->GetVelocity() > 0.5 )
         && ( DynamicObject->IsHunting ) ) {

            update_sounds_runningnoise( *rsHuntingNoise );
            // modify calculated sound volume by hunting amount
            auto const huntingamount =
                interpolate(
                    0.0, 1.0,
                    clamp(
                    ( mvOccupied->Vel - DynamicObject->HuntingShake.fadein_begin ) / ( DynamicObject->HuntingShake.fadein_end - DynamicObject->HuntingShake.fadein_begin ),
                        0.0, 1.0 ) );

            rsHuntingNoise->gain( rsHuntingNoise->gain() * huntingamount );
        }
        else {
            // don't play the optional ending sound if the listener switches views
            rsHuntingNoise->stop( true == FreeFlyModeFlag );
        }
    }
    // rain sound
    if( m_rainsound ) {
        if( ( false == FreeFlyModeFlag )
         && ( false == Global.CabWindowOpen )
         && ( Global.Weather == "rain:" ) ) {
            if( m_rainsound->is_combined() ) {
                m_rainsound->pitch( Global.Overcast - 1.0 );
            }
            m_rainsound->gain( m_rainsound->m_amplitudeoffset + m_rainsound->m_amplitudefactor * 1.f );
            m_rainsound->play( sound_flags::exclusive | sound_flags::looping );
        }
        else {
            m_rainsound->stop();
        }
    }

    if( dsbHasler ) {
        if( fTachoCount >= 3.f ) {
            auto const frequency{ (
                true == dsbHasler->is_combined() ?
                    fTachoVelocity * 0.01 :
                    dsbHasler->m_frequencyoffset + dsbHasler->m_frequencyfactor ) };
            dsbHasler->pitch( frequency );
            dsbHasler->gain( dsbHasler->m_amplitudeoffset + dsbHasler->m_amplitudefactor );
            dsbHasler->play( sound_flags::exclusive | sound_flags::looping );
        }
        else if( fTachoCount < 1.f ) {
            dsbHasler->stop();
        }
    }

    // power-reliant sounds
    if( mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable ) {
        // McZapkie-141102: SHP i czuwak, TODO: sygnalizacja kabinowa
            // hunter-091012: rozdzielenie alarmow
        if( mvOccupied->SecuritySystem.is_beeping() ) {
            if( dsbBuzzer && false == dsbBuzzer->is_playing() ) {
                dsbBuzzer->pitch( dsbBuzzer->m_frequencyoffset + dsbBuzzer->m_frequencyfactor );
                dsbBuzzer->gain( dsbBuzzer->m_amplitudeoffset + dsbBuzzer->m_amplitudefactor );
                dsbBuzzer->play( sound_flags::looping );
#ifdef _WIN32
                Console::BitsSet( 1 << 14 ); // ustawienie bitu 16 na PoKeys
#endif
            }
	    }
        else {
            if( dsbBuzzer && true == dsbBuzzer->is_playing() ) {
                dsbBuzzer->stop();
#ifdef _WIN32
                Console::BitsClear( 1 << 14 ); // ustawienie bitu 16 na PoKeys
#endif
            }
        }
        // distance meter alert
        if( m_distancecounterclear ) {
            auto const *owner{ (
                DynamicObject->ctOwner != nullptr ?
                    DynamicObject->ctOwner :
                    DynamicObject->Mechanik ) };
            if( m_distancecounter > owner->fLength ) {
                // play assigned sound if the train travelled its full length since meter activation
                // TBD: check all combinations of directions and active cab
                m_distancecounter = -1.f; // turn off the meter after its task is done
                m_distancecounterclear->pitch( m_distancecounterclear->m_frequencyoffset + m_distancecounterclear->m_frequencyfactor );
                m_distancecounterclear->gain( m_distancecounterclear->m_amplitudeoffset + m_distancecounterclear->m_amplitudefactor );
                m_distancecounterclear->play( sound_flags::exclusive );
            }
        }
    }
    else {
        // stop power-reliant sounds if power is cut
        if( dsbBuzzer ) {
            if( true == dsbBuzzer->is_playing() ) {
                dsbBuzzer->stop();
#ifdef _WIN32
                Console::BitsClear( 1 << 14 ); // ustawienie bitu 16 na PoKeys
#endif
            }
        }
        if( m_distancecounterclear ) {
            m_distancecounterclear->stop();
        }
    }

    update_sounds_radio();
}

void TTrain::update_sounds_runningnoise( sound_source &Sound ) {
    // frequency calculation
    auto const normalizer { (
        true == Sound.is_combined() ?
            mvOccupied->Vmax * 0.01f :
            1.f ) };
    auto const frequency {
        Sound.m_frequencyoffset
        + Sound.m_frequencyfactor * mvOccupied->Vel * normalizer };

    // volume calculation
    auto volume =
        Sound.m_amplitudeoffset
        + Sound.m_amplitudefactor * interpolate(
            mvOccupied->Vel / ( 1 + mvOccupied->Vmax ), 1.0,
            0.5 ); // scale base volume between 0.5-1.0
    if( std::abs( mvOccupied->nrot ) > 0.01 ) {
        // hamulce wzmagaja halas
        auto const brakeforceratio { (
        clamp(
            mvOccupied->UnitBrakeForce / std::max( 1.0, mvOccupied->BrakeForceR( 1.0, mvOccupied->Vel ) / ( mvOccupied->NAxles * std::max( 1, mvOccupied->NBpA ) ) ),
            0.0, 1.0 ) ) };

        volume *= 1 + 0.125 * brakeforceratio;
    }
    // scale volume by track quality
    // TODO: track quality and/or environment factors as separate subroutine
    volume *=
        interpolate(
            0.8, 1.2,
            clamp(
                DynamicObject->MyTrack->iQualityFlag / 20.0,
                0.0, 1.0 ) );
    // for single sample sounds muffle the playback at low speeds
    if( false == Sound.is_combined() ) {
        volume *=
            interpolate(
                0.0, 1.0,
                clamp(
                    mvOccupied->Vel / 25.0,
                    0.0, 1.0 ) );
    }

    if( volume > 0.05 ) {
        Sound
            .pitch( frequency )
            .gain( volume )
            .play( sound_flags::exclusive | sound_flags::looping );
    }
    else {
        Sound.stop();
    }
}

void TTrain::update_sounds_radio() {

    if( false == m_radiomessages.empty() ) {
        // erase completed radio messages from the list
        m_radiomessages.erase(
            std::remove_if(
                std::begin( m_radiomessages ), std::end( m_radiomessages ),
                []( auto const &source ) {
                    return ( false == source.second->is_playing() ); } ),
            std::end( m_radiomessages ) );
    }
    // adjust audibility of remaining messages based on current radio conditions
    auto const radioenabled { ( true == mvOccupied->Radio ) && ( mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable ) };
    for( auto &message : m_radiomessages ) {
        auto const volume {
            ( true == radioenabled )
         && ( Dynamic()->Mechanik != nullptr )
         && ( message.first == RadioChannel() ) ?
                Global.RadioVolume :
                0.0 };
        message.second->gain( volume );
    }
    // radiostop
    if( m_radiostop ) {
        if( ( true == radioenabled )
         && ( true == mvOccupied->RadioStopFlag ) ) {
            m_radiostop->play( sound_flags::exclusive | sound_flags::looping );
        }
        else {
            m_radiostop->stop();
        }
    }
}

void TTrain::update_screens(double dt) {
    for (auto &screen : m_screens) {
        if (screen.updatetimecounter >= 0)
            screen.updatetimecounter += dt;

        if (screen.updatetimecounter <= screen.updatetime)
            continue;

        screen.updatetimecounter = screen.updatetime > 0 ? 0 : -1;

        auto state_dict = GetTrainState(screen.parameters);

        state_dict->insert("touches", *screen.touch_list);
        screen.touch_list->clear();

        Application.request({ screen.script, state_dict, screen.rt } );
    }
}

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; }
}

bool TTrain::CabChange(int iDirection)
{ // McZapkie-090902: zmiana kabiny 1->0->2 i z powrotem
    if( ( DynamicObject->Mechanik == nullptr )
     || ( true == DynamicObject->Mechanik->AIControllFlag ) ) {
        // jeśli prowadzi AI albo jest w innym członie
        // jak AI prowadzi, to nie można mu mieszać
        if (std::abs(mvOccupied->CabOccupied + iDirection) > 1)
            return false; // ewentualna zmiana pojazdu
        mvOccupied->CabOccupied += iDirection;
    }
    else
    { // jeśli pojazd prowadzony ręcznie albo wcale (wagon)
        mvOccupied->CabDeactivisation();
        if( mvOccupied->ChangeCab( iDirection ) ) {
            if( InitializeCab( mvOccupied->CabOccupied, mvOccupied->TypeName + ".mmd" ) ) {
                // zmiana kabiny w ramach tego samego pojazdu
                mvOccupied->CabActivisation(); // załączenie rozrządu (wirtualne kabiny)
                DynamicObject->Mechanik->DirectionChange();
                return true; // udało się zmienić kabinę
            }
        }
        // aktywizacja poprzedniej, bo jeszcze nie wiadomo, czy jakiś pojazd jest
        mvOccupied->CabActivisation();
    }
    return false; // ewentualna zmiana pojazdu
}

// McZapkie-310302
// wczytywanie pliku z danymi multimedialnymi (dzwieki, kontrolki, kabiny)
bool TTrain::LoadMMediaFile(std::string const &asFileName)
{
    // initialize sounds so potential entries from previous vehicle don't stick around
    std::unordered_map<
        std::string,
        std::tuple<std::optional<sound_source> &, sound_placement, float, sound_type, int, double>>
        internalsounds = {
            {"ctrl:", {dsbNastawnikJazdy, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"ctrlscnd:", {dsbNastawnikBocz, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"reverserkey:", {dsbReverserKey, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"buzzer:", {dsbBuzzer, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"radiostop:", {m_radiostop, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"slipalarm:", {dsbSlipAlarm, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"distancecounter:", {m_distancecounterclear, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"tachoclock:", {dsbHasler, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"switch:", {dsbSwitch, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"pneumaticswitch:", {dsbPneumaticSwitch, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"airsound:", {rsHiss, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}},
            {"airsound2:", {rsHissU, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}},
            {"airsound3:", {rsHissE, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}},
            {"airsound4:", {rsHissX, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}},
            {"airsound5:", {rsHissT, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}},
            {"localbrakesound:", {rsSBHiss, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}},
            {"localbrakesound2:", {rsSBHissU, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, sound_parameters::amplitude, 100.0}},
            {"brakesound:", {rsBrake, sound_placement::internal, -1, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, 100.0}},
            {"fadesound:", {rsFadeSound, sound_placement::internal, EU07_SOUND_CABCONTROLSCUTOFFRANGE, sound_type::single, 0, 100.0}},
            {"runningnoise:", {rsRunningNoise, sound_placement::internal, EU07_SOUND_GLOBALRANGE, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax }},
            {"huntingnoise:", {rsHuntingNoise, sound_placement::internal, EU07_SOUND_GLOBALRANGE, sound_type::single, sound_parameters::amplitude | sound_parameters::frequency, mvOccupied->Vmax }},
            {"rainsound:", {m_rainsound, sound_placement::internal, -1, sound_type::single, 0, 100.0}},
        };
    for( auto &soundconfig : internalsounds ) {
        std::get<std::optional<sound_source> &>( soundconfig.second ).reset();
    }
    // NOTE: since radiosound is an incomplete template not using std::optional it gets a special treatment
    m_radiosound.owner( DynamicObject );
	CabSoundLocations.clear();

    cParser parser( asFileName, cParser::buffer_FILE, DynamicObject->asBaseDir );
    // NOTE: yaml-style comments are disabled until conflict in use of # is resolved
    // parser.addCommentStyle( "#", "\n" );
    std::string token;
    do
    {
        token = "";
        parser.getTokens();
        parser >> token;
    } while ((token != "") && (token != "internaldata:"));

    if (token == "internaldata:") {

        do {
            token = "";
            parser.getTokens();
            parser >> token;

            auto lookup { internalsounds.find( token ) };
            if( lookup == internalsounds.end() ) { continue; }

            auto const soundconfig { lookup->second };
            sound_source sound {
                std::get<sound_placement>( soundconfig ),
                std::get<float>( soundconfig ) };
            sound.deserialize(
                parser,
                std::get<sound_type>( soundconfig ),
                std::get<int>( soundconfig ),
                std::get<double>( soundconfig ) );
            sound.owner( DynamicObject );
            std::get<std::optional<sound_source> &>( soundconfig ) = sound;
        } while( token != "" );


        // assign default samples to sound emitters which weren't included in the config file
        if( !m_rainsound ) {
            sound_source rainsound;
            rainsound.deserialize( "rainsound_default", sound_type::single );
            rainsound.owner( DynamicObject );
            m_rainsound = rainsound;
        }
        if (!rsSBHiss) {
            // fallback for vehicles without defined local brake hiss sound
            rsSBHiss = rsHiss;
        }
        if (!rsSBHissU) {
            // fallback for vehicles without defined local brake hiss sound
            rsSBHissU = rsHissU;
        }
        if (rsBrake) {
            rsBrake->m_frequencyfactor /= (1 + mvOccupied->Vmax);
        }
        if (rsRunningNoise) {
            rsRunningNoise->m_frequencyfactor /= (1 + mvOccupied->Vmax);
        }
        if (rsHuntingNoise) {
            rsHuntingNoise->m_frequencyfactor /= (1 + mvOccupied->Vmax);
        }
    }
	auto const nullvector{ glm::vec3() };
	std::vector<std::reference_wrapper<std::optional<sound_source>>> sounds = {
		dsbReverserKey, dsbNastawnikJazdy, dsbNastawnikBocz,
		dsbSwitch, dsbPneumaticSwitch,
		rsHiss, rsHissU, rsHissE, rsHissX, rsHissT, rsSBHiss, rsSBHissU,
		rsFadeSound, rsRunningNoise, rsHuntingNoise,
		dsbHasler, dsbBuzzer, dsbSlipAlarm, m_distancecounterclear, m_rainsound, m_radiostop
	};
	for (auto &sound : sounds) {
		if (sound.get()) {
			CabSoundLocations.emplace_back(sound, sound.get()->offset());
		}
	}

    return true;
}

bool TTrain::InitializeCab(int NewCabNo, std::string const &asFileName)
{
    m_controlmapper.clear();
    // clear python screens
    m_screens.clear();
    // reset sound positions
    auto const nullvector { glm::vec3() };
    std::vector<std::reference_wrapper<std::optional<sound_source>>> sounds = {
        dsbReverserKey, dsbNastawnikJazdy, dsbNastawnikBocz,
        dsbSwitch, dsbPneumaticSwitch,
        rsHiss, rsHissU, rsHissE, rsHissX, rsHissT, rsSBHiss, rsSBHissU,
        rsFadeSound, rsRunningNoise, rsHuntingNoise,
        dsbHasler, dsbBuzzer, dsbSlipAlarm, m_distancecounterclear, m_rainsound, m_radiostop
    };
    for( auto &sound : sounds ) {
        if( sound.get() ) {
            sound.get()->offset( nullvector );
        }
    }
    m_radiosound.offset( nullvector );
	for (auto &sound : CabSoundLocations) {
		if ((sound.first.get())
			&& (sound.first.get()->offset() == nullvector)) {
			sound.first.get()->offset(sound.second);
		}
	}
    // 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 = nullptr; // likwidacja wskaźnika na dotychczasową kabinę
    switch (NewCabNo)
    { // ustalenie numeru kabiny do wczytania
    case -1:
        cabindex = 2;
        break;
    case 1:
        cabindex = 1;
        break;
    case 0:
        cabindex = 0;
        break;
    }
    iCabn = cabindex;

    std::string cabstr("cab" + std::to_string(cabindex) + "definition:");

    cParser parser( asFileName, cParser::buffer_FILE, DynamicObject->asBaseDir );
    // NOTE: yaml-style comments are disabled until conflict in use of # is resolved
    // parser.addCommentStyle( "#", "\n" );
    std::string token;
    do
    {
        // szukanie kabiny
        token = "";
        parser.getTokens();
        parser >> token;
    } while ((token != "") && (token != cabstr));

    if (token == cabstr)
    {
        // jeśli znaleziony wpis kabiny
        Cabine[cabindex].Load(parser);
        // NOTE: the position and angle definitions depend on strict entry order
        // TODO: refactor into more flexible arrangement
        parser.getTokens();
        parser >> token;
        if( token == std::string( "driver" + std::to_string( cabindex ) + "angle:" ) ) {
            // camera view angle
            parser.getTokens( 2, false );
            // angle is specified in degrees but internally stored in radians
            glm::vec2 viewangle;
            parser
                >> viewangle.y // yaw first, then pitch
                >> viewangle.x;
            pMechViewAngle = glm::radians( viewangle );

            Global.pCamera.Angle.x = pMechViewAngle.x;
            Global.pCamera.Angle.y = pMechViewAngle.y;

            parser.getTokens();
            parser >> token;
        }
        if (token == std::string("driver" + std::to_string(cabindex) + "pos:"))
        {
            // pozycja poczatkowa maszynisty
            parser.getTokens(3, false);
            parser
                >> pMechOffset.x
                >> pMechOffset.y
                >> pMechOffset.z;
            pMechSittingPosition = pMechOffset;

            parser.getTokens();
            parser >> token;
        }
        // ABu: pozycja siedzaca mechanika
        if (token == std::string("driver" + std::to_string(cabindex) + "sitpos:"))
        {
            // ABu 180404 pozycja siedzaca maszynisty
            parser.getTokens(3, false);
            parser
                >> pMechSittingPosition.x
                >> pMechSittingPosition.y
                >> pMechSittingPosition.z;

            parser.getTokens();
            parser >> token;
        }
        // else parse=false;
        do {
            if( parse == true ) {
                token = "";
                parser.getTokens();
                parser >> token;
            }
            else {
                parse = true;
            }
            // inicjacja kabiny
            // Ra 2014-08: zmieniamy zasady - zamiast przypisywać submodel do
            // istniejących obiektów animujących
            // będziemy teraz uaktywniać obiekty animujące z tablicy i podawać im
            // submodel oraz wskaźnik na parametr
            if (token == std::string("cab" + std::to_string(cabindex) + "model:"))
            {
                // model kabiny
                parser.getTokens();
                parser >> token;
				std::replace(token.begin(), token.end(), '\\', '/');
                if (token != "none")
                {
                    // bieżąca sciezka do tekstur to dynamic/...
                    Global.asCurrentTexturePath = DynamicObject->asBaseDir;
                    // szukaj kabinę jako oddzielny model
                    // name can contain leading slash, erase it to avoid creation of double slashes when the name is combined with current directory
                    replace_slashes( token );
                    erase_leading_slashes( token );
                    if( token[ 0 ] == '/' ) {
                        token.erase( 0, 1 );
                    }
					TModel3d *kabina = TModelsManager::GetModel(DynamicObject->asBaseDir + token, true, true,
					    (Global.network_servers.empty() && !Global.network_client) ? 0 : id());
                    // z powrotem defaultowa sciezka do tekstur
                    Global.asCurrentTexturePath = szTexturePath;
                    // if (DynamicObject->mdKabina!=k)
                    if (kabina != nullptr)
                    {
                        DynamicObject->mdKabina = kabina; // nowa kabina
                    }
                    //(mdKabina) może zostać to samo po przejściu do innego członu bez
                    // zmiany kabiny, przy powrocie musi być wiązanie ponowne
                    // else
                    // break; //wyjście z pętli, bo model zostaje bez zmian
                }
                else if (cabindex == 1) {
                    // model tylko, gdy nie ma kabiny 1
                    // McZapkie-170103: szukaj elementy kabiny w glownym modelu
                    DynamicObject->mdKabina = DynamicObject->mdModel;
                }
                clear_cab_controls();
            }
/*
            if (nullptr == DynamicObject->mdKabina)
            {
                // don't bother with other parts until the cab is initialised
                continue;
            }
*/
            else if (true == initialize_gauge(parser, token, cabindex))
            {
                // matched the token, grab the next one
                continue;
            }
            else if (true == initialize_button(parser, token, cabindex))
            {
                // matched the token, grab the next one
                continue;
            }
            // TODO: add "pydestination:"
            else if (token == "pyscreen:")
            {
                screen_entry screen;
                screen.deserialize(parser);
                if ((false == screen.script.empty()) && (substr_path(screen.script).empty()))
                {
                    screen.script = DynamicObject->asBaseDir + screen.script;
                }

                opengl_texture *tex = nullptr;
                TSubModel *submodel = nullptr;
                if (screen.target != "none")
                {
                    submodel = (DynamicObject->mdKabina ?
                                    DynamicObject->mdKabina->GetFromName(screen.target) :
                                    DynamicObject->mdLowPolyInt ?
                                    DynamicObject->mdLowPolyInt->GetFromName(screen.target) :
                                    nullptr);
                    if (submodel == nullptr)
                    {
                        WriteLog("Python Screen: submodel " + screen.target +
                                 " not found - Ignoring screen");
                        continue;
                    }
                    auto const material{submodel->GetMaterial()};
                    if (material <= 0)
                    {
                        // sub model nie posiada tekstury lub tekstura wymienna - nie obslugiwana
                        WriteLog("Python Screen: invalid texture id " + std::to_string(material) +
                                 " - Ignoring screen");
                        continue;
                    }

                    tex = &GfxRenderer->Texture(GfxRenderer->Material(material).textures[0]);
                }
                else
                {
                    // TODO: fix leak
                    tex = new opengl_texture();
                    tex->make_stub();
                }

                tex->create(true); // make the surface static so it doesn't get destroyed by garbage
                                   // collector if the user spends long time outside cab
                // TBD, TODO: keep texture handles around, so we can undo the static switch when the
                // user changes cabs?
                auto rt = std::make_shared<python_rt>();
                rt->shared_tex = tex->id;

                // record renderer and material binding for future update requests
                m_screens.emplace_back(screen);
                m_screens.back().rt = rt;

                m_screens.back().touch_list = std::make_shared<std::vector<glm::vec2>>();
                if (submodel)
                    submodel->screen_touch_list = m_screens.back().touch_list;

                if (Global.python_displaywindows)
                    m_screens.back().viewer = std::make_unique<python_screen_viewer>(rt, m_screens.back().touch_list, m_screens.back().script);
            }
            else if (token == "pyscreenupdatetime:") {
                parser.getTokens();
                parser >> ScreenUpdateRate;
            }
            // btLampkaUnknown.Init("unknown",mdKabina,false);
        } while ( ( token != "" )
// TODO: enable full per-cab deserialization when/if .mmd files get proper per-cab switch configuration
//               && ( token != "cab1definition:" )
//               && ( token != "cab2definition:" )
               && ( token != "cab0definition:" ) );
		for (auto &screen : m_screens)
		{
			if (screen.updatetime > 0) {
				screen.updatetime = std::max((int)screen.updatetime, Global.PythonScreenUpdateRate) * 0.001;
			}
			if (screen.updatetime == 0) {
				screen.updatetime = std::max(Global.PythonScreenUpdateRate, ScreenUpdateRate) * 0.001;
			}
			if (screen.updatetime < -1) {
				screen.updatetime = -screen.updatetime * 0.001;
			}
		}
    }
    else
    {
        return false;
    }
/*
    if (DynamicObject->mdKabina)
    {
*/
        // configure placement of sound emitters which aren't bound with any device model, and weren't placed manually
        auto const caboffset { glm::dvec3 { ( Cabine[ cabindex ].CabPos1 + Cabine[ cabindex ].CabPos2 ) * 0.5 } +glm::dvec3 { 0, 1, 0 } };
        // NOTE: since radiosound is an incomplete template not using std::optional it gets a special treatment
        if( m_radiosound.offset() == nullvector ) {
            m_radiosound.offset( btLampkaRadio.model_offset() );
        }
        if( m_radiosound.offset() == nullvector ) {
            m_radiosound.offset( caboffset );
        }
        std::vector<std::pair<std::reference_wrapper<std::optional<sound_source>>, glm::vec3>>
            soundlocations = {
                {dsbReverserKey, ggDirKey.model_offset()},
                {dsbNastawnikJazdy, ggJointCtrl.model_offset()},
                {dsbNastawnikJazdy, ggMainCtrl.model_offset()}, // NOTE: fallback for vehicles without universal controller
                {dsbNastawnikBocz, ggScndCtrl.model_offset()},
                {dsbSwitch, caboffset},
                {dsbPneumaticSwitch, caboffset},
                {rsHiss, ggBrakeCtrl.model_offset()},
                {rsHissU, ggBrakeCtrl.model_offset()},
                {rsHissE, ggBrakeCtrl.model_offset()},
                {rsHissX, ggBrakeCtrl.model_offset()},
                {rsHissT, ggBrakeCtrl.model_offset()},
                {rsSBHiss, ggLocalBrake.model_offset()},
                {rsSBHiss, ggBrakeCtrl.model_offset()}, // NOTE: fallback if the local brake model can't be located
                {rsSBHissU, ggLocalBrake.model_offset()},
                {rsSBHissU, ggBrakeCtrl.model_offset()}, // NOTE: fallback if the local brake model can't be located
                {rsFadeSound, caboffset},
                {rsRunningNoise, caboffset},
                {rsHuntingNoise, caboffset},
                {dsbHasler, caboffset},
                {dsbBuzzer, btLampkaCzuwaka.model_offset()},
                {dsbSlipAlarm, caboffset},
                {m_distancecounterclear, btLampkaCzuwaka.model_offset()},
                {m_rainsound, caboffset},
                {m_radiostop, m_radiosound.offset()},
            };
        for( auto & sound : soundlocations ) {
            if( ( sound.first.get() )
             && ( sound.first.get()->offset() == nullvector ) ) {
                sound.first.get()->offset( sound.second );
            }
        }
        // second pass, in case some items received no positioning due to missing submodels etc
        for( auto & sound : soundlocations ) {
            if( ( sound.first.get() )
             && ( sound.first.get()->offset() == nullvector ) ) {
                sound.first.get()->offset( caboffset );
            }
        }

        if( DynamicObject->mdKabina )
            DynamicObject->mdKabina->Init(); // obrócenie modelu oraz optymalizacja, również zapisanie binarnego

        set_cab_controls( NewCabNo < 0 ? 2 : NewCabNo );
/*
        return true;
    }
    return (token == "none");
*/
    return true;
}

Math3D::vector3 TTrain::MirrorPosition(bool lewe)
{ // zwraca współrzędne widoku kamery z lusterka
    auto const shiftdirection { ( lewe ? -1 : 1 ) * ( iCabn == 2 ? 1 : -1 ) };

    return DynamicObject->mMatrix
        * Math3D::vector3(
            mvOccupied->Dim.W * ( 0.5 * shiftdirection ) + ( 0.2 * shiftdirection ),
            1.5 + Cabine[iCabn].CabPos1.y,
            interpolate( Cabine[ iCabn ].CabPos1.z , Cabine[ iCabn ].CabPos2.z, 0.5 ) );
};

void TTrain::DynamicSet(TDynamicObject *d)
{ // taka proteza: chcę podłączyć
    // kabinę EN57 bezpośrednio z
    // silnikowym, aby nie robić tego
    // przez ukrotnienie
    // drugi silnikowy i tak musi być ukrotniony, podobnie jak kolejna jednostka
    // problem się robi ze światłami, które będą zapalane w silnikowym, ale muszą
    // świecić się w rozrządczych
    // dla EZT światła czołowe będą "zapalane w silnikowym", ale widziane z
    // rozrządczych
    // również wczytywanie MMD powinno dotyczyć aktualnego członu
    // problematyczna może być kwestia wybranej kabiny (w silnikowym...)
    // jeśli silnikowy będzie zapięty odwrotnie (tzn. -1), to i tak powinno
    // jeździć dobrze
    // również hamowanie wykonuje się zaworem w członie, a nie w silnikowym...
    DynamicObject = d; // jedyne miejsce zmiany
    mvOccupied = mvControlled = ( d ? DynamicObject->MoverParameters : nullptr ); // albo silnikowy w EZT

    if( DynamicObject == nullptr ) { return; }

    mvControlled = DynamicObject->FindPowered()->MoverParameters;
    mvSecond = NULL; // gdyby się nic nie znalazło
    if (mvOccupied->Power > 1.0) // dwuczłonowe lub ukrotnienia, żeby nie szukać każdorazowo
        if (mvOccupied->Couplers[1].Connected ?
                mvOccupied->Couplers[1].AllowedFlag & coupling::control :
                false)
        { // gdy jest człon od sprzęgu 1, a sprzęg łączony
            // warsztatowo (powiedzmy)
            if (mvOccupied->Couplers[1].Connected->Power > 1.0) // ten drugi ma moc
                mvSecond =
                    (TMoverParameters *)mvOccupied->Couplers[1].Connected; // wskaźnik na drugiego
        }
        else if (mvOccupied->Couplers[0].Connected ?
                     mvOccupied->Couplers[0].AllowedFlag & coupling::control :
                     false)
        { // gdy jest człon od sprzęgu 0, a sprzęg łączony
            // warsztatowo (powiedzmy)
            if (mvOccupied->Couplers[0].Connected->Power > 1.0) // ale ten drugi ma moc
                mvSecond =
                    (TMoverParameters *)mvOccupied->Couplers[0].Connected; // wskaźnik na drugiego
        }
    // cache nearest unit equipped with pantographs
    {
        auto *lookup { DynamicObject->FindPantographCarrier() };
        // HACK: set pointer to existing vehicle to avoid error checking all over the place
        mvPantographUnit = (
            lookup != nullptr ?
                lookup->MoverParameters :
                mvControlled );
    }
};

void
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

		if (!Dynamic()->Mechanik || !Dynamic()->Mechanik->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;
            Dynamic()->MechInside = false;
			Dynamic()->Controller = AIdriver;

			Dynamic()->bDisplayCab = false;
			Dynamic()->ABuSetModelShake( {} );

            if( Dynamic()->Mechanik )
                Dynamic()->Mechanik->MoveTo( target );

			target_train->Occupied()->LimPipePress = target_train->Occupied()->PipePress;
			target_train->Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny)
			target_train->Dynamic()->MechInside = true;
            if( target_train->Dynamic()->Mechanik ) {
                target_train->Dynamic()->Controller = target_train->Dynamic()->Mechanik->AIControllFlag;
                target_train->Dynamic()->Mechanik->DirectionChange();
            }
            else {
                target_train->Dynamic()->Controller = Humandriver;
            }
        } else {
			target_train->Dynamic()->bDisplayCab = false;
			target_train->Dynamic()->ABuSetModelShake( {} );
		}

		target_train->Dynamic()->ABuSetModelShake( {} ); // zerowanie przesunięcia przed powrotem?

		// potentially move player
		if (simulation::Train == this) {
			simulation::Train = target_train;
            // our local driver may potentially be in external view mode, in which case we shouldn't activate cab visualization
            target_train->Dynamic()->bDisplayCab |= !FreeFlyModeFlag;
        }

		// delete this TTrain
		pending_delete = true;
	} else {
		// move this TTrain to other dynamic

		// remove TTrain from global list, we're going to change dynamic anyway
		simulation::Trains.detach(Dynamic()->name());

		if (!Dynamic()->Mechanik || !Dynamic()->Mechanik->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;
            Dynamic()->MechInside = false;
			Dynamic()->Controller = AIdriver;

			Dynamic()->bDisplayCab = false;
			Dynamic()->ABuSetModelShake( {} );

            if( Dynamic()->Mechanik )
                Dynamic()->Mechanik->MoveTo( target );

            DynamicSet( target );

            Dynamic()->MechInside = true;
            if( Dynamic()->Mechanik ) {
                Dynamic()->Controller = Dynamic()->Mechanik->AIControllFlag;
                Dynamic()->Mechanik->DirectionChange();
            }
            else {
                Dynamic()->Controller = Humandriver;
            }

			Occupied()->LimPipePress = Occupied()->PipePress;
			Occupied()->CabActivisation( true ); // załączenie rozrządu (wirtualne kabiny)
		} else {
			Dynamic()->bDisplayCab = false;
			Dynamic()->ABuSetModelShake( {} );

			DynamicSet(target);
		}

        {
            auto const filename { Occupied()->TypeName + ".mmd" };
            LoadMMediaFile( filename );
            InitializeCab(
                Occupied()->CabActive,
                filename );
        }

		Dynamic()->ABuSetModelShake( {} ); // zerowanie przesunięcia przed powrotem?

        if( simulation::Train == this ) {
            // our local driver may potentially be in external view mode, in which case we shouldn't activate cab visualization
            Dynamic()->bDisplayCab |= !FreeFlyModeFlag;
        }

		// add it back with updated dynamic name
		simulation::Trains.insert(this);
	}
}

// checks whether specified point is within boundaries of the active cab
bool
TTrain::point_inside( Math3D::vector3 const Point ) const {

    return ( Point.x >= Cabine[ iCabn ].CabPos1.x )       && ( Point.x <= Cabine[ iCabn ].CabPos2.x )
        && ( Point.y >= Cabine[ iCabn ].CabPos1.y + 0.5 ) && ( Point.y <= Cabine[ iCabn ].CabPos2.y + 1.8 )
        && ( Point.z >= Cabine[ iCabn ].CabPos1.z )       && ( Point.z <= Cabine[ iCabn ].CabPos2.z );
}

Math3D::vector3
TTrain::clamp_inside( Math3D::vector3 const &Point ) const {

    if( DebugModeFlag ) { return Point; }

    return {
        clamp( Point.x, Cabine[ iCabn ].CabPos1.x, Cabine[ iCabn ].CabPos2.x ),
        clamp( Point.y, Cabine[ iCabn ].CabPos1.y + 0.5, Cabine[ iCabn ].CabPos2.y + 1.8 ),
        clamp( Point.z, Cabine[ iCabn ].CabPos1.z, Cabine[ iCabn ].CabPos2.z ) };
}

const TTrain::screenentry_sequence& TTrain::get_screens() {
	update_screens(Timer::GetDeltaTime());
	return m_screens;
}

void
TTrain::radio_message( sound_source *Message, int const Channel ) {

    auto const soundrange { Message->range() };
    if( ( soundrange > 0 )
     && ( glm::length2( Message->location() - glm::dvec3 { DynamicObject->GetPosition() } ) > ( soundrange * soundrange ) ) ) {
        // skip message playback if the receiver is outside of the emitter's range
        return;
    }
    // NOTE: we initiate playback of all sounds in range, in case the user switches on the radio or tunes to the right channel mid-play
    m_radiomessages.emplace_back(
        Channel,
        std::make_shared<sound_source>( m_radiosound ) );
    // assign sound to the template and play it
    auto &message = *( m_radiomessages.back().second.get() );
    auto const radioenabled { ( true == mvOccupied->Radio ) && ( mvOccupied->Power24vIsAvailable || mvOccupied->Power110vIsAvailable ) };
    auto const volume {
        ( true == radioenabled )
     && ( Dynamic()->Mechanik != nullptr )
     && ( Channel == RadioChannel() ) ?
            1.0 :
            0.0 };
    message
        .copy_sounds( *Message )
        .gain( volume )
        .play();
}

// clears state of all cabin controls
void TTrain::clear_cab_controls()
{
    // indicators exposed to custom control devices
    btLampkaSHP.Clear(0);
    btLampkaCzuwaka.Clear(1);
    btLampkaOpory.Clear(2);
    btLampkaWylSzybki.Clear(3);
    btLampkaNadmSil.Clear(4);
    btLampkaStyczn.Clear(5);
    btLampkaPoslizg.Clear(6);
    btLampkaNadmPrzetw.Clear(((mvControlled->TrainType & dt_EZT) != 0) ? -1 : 7); // EN57 nie ma tej lampki
    btLampkaPrzetwOff.Clear(((mvControlled->TrainType & dt_EZT) != 0) ? 7 : -1 ); // za to ma tę
    btLampkaNadmSpr.Clear(8);
    btLampkaNadmWent.Clear(9);
    btLampkaWysRozr.Clear(((mvControlled->TrainType & dt_ET22) != 0) ? -1 : 10); // ET22 nie ma tej lampki
    btLampkaOgrzewanieSkladu.Clear(11);
    btHaslerBrakes.Clear(12); // ciśnienie w cylindrach do odbijania na haslerze
    btHaslerCurrent.Clear(13); // prąd na silnikach do odbijania na haslerze
    // Numer 14 jest używany dla buczka SHP w update_sounds()
    // Jeśli ustawiamy nową wartość dla PoKeys wolna jest 15

    // other cab controls
    // TODO: arrange in more readable manner, and eventually refactor
    ggJointCtrl.Clear();
    ggMainCtrl.Clear();
    ggMainCtrlAct.Clear();
    ggScndCtrl.Clear();
    ggScndCtrlButton.Clear();
    ggScndCtrlOffButton.Clear();
    ggDistanceCounterButton.Clear();
    ggDirKey.Clear();
    ggDirForwardButton.Clear();
    ggDirNeutralButton.Clear();
    ggDirBackwardButton.Clear();
    ggBrakeCtrl.Clear();
    ggLocalBrake.Clear();
    ggAlarmChain.Clear();
    ggBrakeProfileCtrl.Clear();
    ggBrakeProfileG.Clear();
    ggBrakeProfileR.Clear();
	ggBrakeOperationModeCtrl.Clear();
    ggMaxCurrentCtrl.Clear();
    ggMainOffButton.Clear();
    ggMainOnButton.Clear();
    ggSecurityResetButton.Clear();
    ggReleaserButton.Clear();
	ggSpringBrakeOnButton.Clear();
	ggSpringBrakeOffButton.Clear();
	ggUniveralBrakeButton1.Clear();
	ggUniveralBrakeButton2.Clear();
	ggUniveralBrakeButton3.Clear();
    ggEPFuseButton.Clear();
    ggSandButton.Clear();
    ggAutoSandButton.Clear();
    ggAntiSlipButton.Clear();
    ggHornButton.Clear();
    ggHornLowButton.Clear();
    ggHornHighButton.Clear();
    ggWhistleButton.Clear();
	ggHelperButton.Clear();
    ggNextCurrentButton.Clear();
	ggSpeedControlIncreaseButton.Clear();
	ggSpeedControlDecreaseButton.Clear();
	ggSpeedControlPowerIncreaseButton.Clear();
	ggSpeedControlPowerDecreaseButton.Clear();
	for (auto &speedctrlbutton : ggSpeedCtrlButtons) {
		speedctrlbutton.Clear();
	}
    for( auto &universal : ggUniversals ) {
        universal.Clear();
    }
	for (auto &item : ggInverterEnableButtons) {
		item.Clear();
	}
	for (auto &item : ggInverterDisableButtons) {
		item.Clear();
	}
	for (auto &item : ggInverterToggleButtons) {
		item.Clear();
	}
    for( auto &relayresetbutton : ggRelayResetButtons ) {
        relayresetbutton.Clear();
    }
    ggInstrumentLightButton.Clear();
    ggDashboardLightButton.Clear();
    ggTimetableLightButton.Clear();
    // hunter-091012
    ggCabLightDimButton.Clear();
    ggCompartmentLightsButton.Clear();
    ggCompartmentLightsOnButton.Clear();
    ggCompartmentLightsOffButton.Clear();
    ggBatteryButton.Clear();
    ggBatteryOnButton.Clear();
    ggBatteryOffButton.Clear();
    //-------
    ggFuseButton.Clear();
    ggConverterFuseButton.Clear();
    ggStLinOffButton.Clear();
    ggRadioChannelSelector.Clear();
    ggRadioChannelPrevious.Clear();
    ggRadioChannelNext.Clear();
    ggRadioStop.Clear();
    ggRadioTest.Clear();
    ggRadioCall3.Clear();
	ggRadioVolumeSelector.Clear();
	ggRadioVolumePrevious.Clear();
	ggRadioVolumeNext.Clear();
    ggDoorLeftPermitButton.Clear();
    ggDoorRightPermitButton.Clear();
    ggDoorPermitPresetButton.Clear();
    ggDoorLeftButton.Clear();
    ggDoorRightButton.Clear();
    ggDoorLeftOnButton.Clear();
    ggDoorRightOnButton.Clear();
    ggDoorLeftOffButton.Clear();
    ggDoorRightOffButton.Clear();
    ggDoorAllOnButton.Clear();
    ggDoorAllOffButton.Clear();
    ggTrainHeatingButton.Clear();
    ggSignallingButton.Clear();
    ggDoorSignallingButton.Clear();
    ggDoorStepButton.Clear();
    ggDepartureSignalButton.Clear();
    ggCompressorButton.Clear();
    ggCompressorLocalButton.Clear();
    ggConverterButton.Clear();
    ggConverterOffButton.Clear();
    ggConverterLocalButton.Clear();
    ggMainButton.Clear();
/*
    ggPantFrontButton.Clear();
    ggPantRearButton.Clear();
    ggPantFrontButtonOff.Clear();
    ggPantRearButtonOff.Clear();
*/
    ggPantAllDownButton.Clear();
    ggPantSelectedButton.Clear();
    ggPantSelectedDownButton.Clear();
    ggPantValvesButton.Clear();
    ggPantCompressorButton.Clear();
    ggPantCompressorValve.Clear();
    ggI1B.Clear();
    ggI2B.Clear();
    ggI3B.Clear();
    ggItotalB.Clear();
    ggOilPressB.Clear();
    ggWater1TempB.Clear();

    ggClockSInd.Clear();
    ggClockMInd.Clear();
    ggClockHInd.Clear();
    ggEngineVoltage.Clear();
    ggLVoltage.Clear();
    ggMainGearStatus.Clear();
    ggIgnitionKey.Clear();

    ggWaterPumpBreakerButton.Clear();
    ggWaterPumpButton.Clear();
    ggWaterHeaterBreakerButton.Clear();
    ggWaterHeaterButton.Clear();
    ggWaterCircuitsLinkButton.Clear();
    ggFuelPumpButton.Clear();
    ggOilPumpButton.Clear();
    ggMotorBlowersFrontButton.Clear();
    ggMotorBlowersRearButton.Clear();
    ggMotorBlowersAllOffButton.Clear();

    btLampkaPrzetw.Clear();
    btLampkaPrzetwB.Clear();
    btLampkaPrzetwBOff.Clear();
    btLampkaPrzekRozn.Clear();
    btLampkaPrzekRoznPom.Clear();
    btLampkaUkrotnienie.Clear();
    btLampkaHamPosp.Clear();
    btLampkaWylSzybkiOff.Clear();
    btLampkaWylSzybkiB.Clear();
    btLampkaWylSzybkiBOff.Clear();
    btLampkaMainBreakerReady.Clear();
    btLampkaMainBreakerBlinkingIfReady.Clear();
    btLampkaBezoporowa.Clear();
    btLampkaBezoporowaB.Clear();
    btLampkaMaxSila.Clear();
    btLampkaPrzekrMaxSila.Clear();
    btLampkaRadio.Clear();
    btLampkaRadioStop.Clear();
    btLampkaHamulecReczny.Clear();
    btLampkaBlokadaDrzwi.Clear();
    btLampkaDoorLockOff.Clear();
    for( auto &universal : btUniversals ) {
        universal.Clear();
    }
    btInstrumentLight.Clear();
    btDashboardLight.Clear();
    btTimetableLight.Clear();
    btLampkaWentZaluzje.Clear();
    btLampkaDoorLeft.Clear();
    btLampkaDoorRight.Clear();
    btLampkaDepartureSignal.Clear();
    btLampkaRezerwa.Clear();
    btLampkaBoczniki.Clear();
    btLampkaBocznik1.Clear();
    btLampkaBocznik2.Clear();
    btLampkaBocznik3.Clear();
    btLampkaBocznik4.Clear();
    btLampkaRadiotelefon.Clear();
    btLampkaHamienie.Clear();
    btLampkaBrakingOff.Clear();
    btLampkaED.Clear();
    btLampkaBrakeProfileG.Clear();
    btLampkaBrakeProfileP.Clear();
    btLampkaBrakeProfileR.Clear();
	btLampkaSpringBrakeActive.Clear();
	btLampkaSpringBrakeInactive.Clear();
    btLampkaSprezarka.Clear();
    btLampkaSprezarkaB.Clear();
    btLampkaSprezarkaOff.Clear();
    btLampkaSprezarkaBOff.Clear();
    btLampkaFuelPumpOff.Clear();
    btLampkaNapNastHam.Clear();
    btLampkaOporyB.Clear();
    btLampkaStycznB.Clear();
    btLampkaHamowanie1zes.Clear();
    btLampkaHamowanie2zes.Clear();
    btLampkaNadmPrzetwB.Clear();
    btLampkaHVoltageB.Clear();
    btLampkaForward.Clear();
    btLampkaBackward.Clear();
    // light indicators
    btLampkaUpperLight.Clear();
    btLampkaLeftLight.Clear();
    btLampkaRightLight.Clear();
    btLampkaLeftEndLight.Clear();
    btLampkaRightEndLight.Clear();
    btLampkaRearUpperLight.Clear();
    btLampkaRearLeftLight.Clear();
    btLampkaRearRightLight.Clear();
    btLampkaRearLeftEndLight.Clear();
    btLampkaRearRightEndLight.Clear();
    // others
    btLampkaMalfunction.Clear();
    btLampkaMalfunctionB.Clear();
    btLampkaMotorBlowers.Clear();
    btLampkaCoolingFans.Clear();
    btLampkaTempomat.Clear();
    btLampkaDistanceCounter.Clear();

    ggLeftLightButton.Clear();
    ggRightLightButton.Clear();
    ggUpperLightButton.Clear();
    ggDimHeadlightsButton.Clear();
    ggLeftEndLightButton.Clear();
    ggRightEndLightButton.Clear();
    ggLightsButton.Clear();
    // hunter-230112
    ggRearLeftLightButton.Clear();
    ggRearRightLightButton.Clear();
    ggRearUpperLightButton.Clear();
    ggRearLeftEndLightButton.Clear();
    ggRearRightEndLightButton.Clear();
}

// NOTE: we can get rid of this function once we have per-cab persistent state
void TTrain::set_cab_controls( int const Cab ) {
    // switches
    // battery
    ggBatteryButton.PutValue(
        ( ggBatteryButton.type() == TGaugeType::push ? 0.5f :
          mvOccupied->Power24vIsAvailable ? 1.f :
          0.f ) );
    // line breaker
    if( ggMainButton.SubModel != nullptr ) { // instead of single main button there can be on/off pair
        ggMainButton.PutValue(
            ( ggMainButton.type() == TGaugeType::push ? 0.5f :
                m_linebreakerstate > 0 ? 1.f :
                0.f ) );
    }
    // motor connectors
    ggStLinOffButton.PutValue(
        ( mvControlled->StLinSwitchOff ?
            1.f :
            0.f ) );
    // radio
    ggRadioChannelSelector.PutValue( ( Dynamic()->Mechanik ? Dynamic()->Mechanik->iRadioChannel : 1 ) - 1 );
    // pantographs
/*
    if( mvOccupied->PantSwitchType != "impulse" ) {
        if( ggPantFrontButton.SubModel ) {
            ggPantFrontButton.PutValue(
                ( mvControlled->Pantographs[end::front].valve.is_enabled ?
                    1.f :
                    0.f ) );
        }
        if( ggPantFrontButtonOff.SubModel ) {
            ggPantFrontButtonOff.PutValue(
                ( mvControlled->Pantographs[end::front].valve.is_disabled ?
                    1.f :
                    0.f ) );
        }
    }
    if( mvOccupied->PantSwitchType != "impulse" ) {
        if( ggPantRearButton.SubModel ) {
            ggPantRearButton.PutValue(
                ( mvControlled->Pantographs[end::rear].valve.is_enabled ?
                    1.f :
                    0.f ) );
        }
        if( ggPantRearButtonOff.SubModel ) {
            ggPantRearButtonOff.PutValue(
                ( mvControlled->Pantographs[end::rear].valve.is_disabled ?
                    1.f :
                    0.f ) );
        }
    }
*/
    // front/end pantograph selection is relative to occupied cab
    if( ggPantSelectedButton.type() == TGaugeType::toggle ) {
        ggPantSelectedButton.PutValue(
            ( mvPantographUnit->PantsValve.is_enabled ?
                1.f :
                0.f ) );
    }
    else {
        if( false == m_controlmapper.contains( "pantselectedoff_sw:" ) ) {
            // single impulse switch arrangement, with neutral position mid-way
            ggPantSelectedButton.PutValue( 0.5f );
        }
    }
    if( ggPantSelectedDownButton.type() == TGaugeType::toggle ) {
        ggPantSelectedDownButton.PutValue(
            ( mvPantographUnit->PantsValve.is_disabled ?
                1.f :
                0.f ) );
    }
    ggPantValvesButton.PutValue( 0.5f );
    // auxiliary compressor
    ggPantCompressorValve.PutValue(
        mvControlled->bPantKurek3 ?
            0.f : // default setting is pantographs connected with primary tank
            1.f );
    ggPantCompressorButton.PutValue(
        mvPantographUnit->PantCompFlag ?
            1.f :
            0.f );
    // converter
    if( mvOccupied->ConvSwitchType != "impulse" ) {
        ggConverterButton.PutValue(
            mvControlled->ConverterAllow ?
                1.f :
                0.f );
    }
    ggConverterLocalButton.PutValue(
        mvControlled->ConverterAllowLocal ?
            1.f :
            0.f );
    // compressor
    ggCompressorButton.PutValue(
        mvControlled->CompressorAllow ?
            1.f :
            0.f );
    ggCompressorLocalButton.PutValue(
        mvControlled->CompressorAllowLocal ?
            1.f :
            0.f );
	ggCompressorListButton.PutValue(mvOccupied->CompressorListPos - 1);
    // motor overload relay threshold / shunt mode
    ggMaxCurrentCtrl.PutValue(
        ( true == mvControlled->ShuntModeAllow ?
            ( true == mvControlled->ShuntMode ?
                1.f :
                0.f ) :
            ( mvControlled->MotorOverloadRelayHighThreshold ?
                1.f :
                0.f ) ) );
    // lights
    ggLightsButton.PutValue( mvOccupied->LightsPos - 1 );

    auto const vehicleend { cab_to_end( Cab ) };

    if( ( mvOccupied->iLights[ vehicleend ] & light::headlight_left ) != 0 ) {
        ggLeftLightButton.PutValue( 1.f );
    }
    if( ( mvOccupied->iLights[ vehicleend ] & light::headlight_right ) != 0 ) {
        ggRightLightButton.PutValue( 1.f );
    }
    if( ( mvOccupied->iLights[ vehicleend ] & light::headlight_upper ) != 0 ) {
        ggUpperLightButton.PutValue( 1.f );
    }
    if( ( mvOccupied->iLights[ vehicleend ] & light::redmarker_left ) != 0 ) {
        if( ggLeftEndLightButton.SubModel != nullptr ) {
            ggLeftEndLightButton.PutValue( 1.f );
        }
        else {
            ggLeftLightButton.PutValue( -1.f );
        }
    }
    if( ( mvOccupied->iLights[ vehicleend ] & light::redmarker_right ) != 0 ) {
        if( ggRightEndLightButton.SubModel != nullptr ) {
            ggRightEndLightButton.PutValue( 1.f );
        }
        else {
            ggRightLightButton.PutValue( -1.f );
        }
    }
    if( true == DynamicObject->DimHeadlights ) {
        ggDimHeadlightsButton.PutValue( 1.f );
    }
    // cab lights
    if( true == Cabine[Cab].bLightDim ) {
        ggCabLightDimButton.PutValue( 1.f );
    }
    // compartment lights
    ggCompartmentLightsButton.PutValue(
        ( ggCompartmentLightsButton.type() == TGaugeType::push ? 0.5f :
          mvOccupied->CompartmentLights.is_enabled ? 1.f :
          0.f ) );
    // instrument lights
    ggInstrumentLightButton.PutValue( (
        InstrumentLightActive ?
            1.f :
            0.f ) );
    ggDashboardLightButton.PutValue( (
        DashboardLightActive ?
            1.f :
            0.f ) );
    ggTimetableLightButton.PutValue( (
        TimetableLightActive ?
            1.f :
            0.f ) );
    // doors permits
    if( false == ggDoorLeftPermitButton.is_push() ) {
        ggDoorLeftPermitButton.PutValue( mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::left : side::right ) ].open_permit ? 1.f : 0.f );
    }
    if( false == ggDoorRightPermitButton.is_push() ) {
        ggDoorRightPermitButton.PutValue( mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::right : side::left ) ].open_permit ? 1.f : 0.f );
    }
    ggDoorPermitPresetButton.PutValue( mvOccupied->Doors.permit_preset );
    // door controls
    ggDoorLeftButton.PutValue( mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::left : side::right ) ].is_closed ? 0.f : 1.f );
    ggDoorRightButton.PutValue( mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::right : side::left ) ].is_closed ? 0.f : 1.f );
    // door lock
    ggDoorSignallingButton.PutValue(
        mvOccupied->Doors.lock_enabled ?
            1.f :
            0.f );
    // door step
    if( false == ggDoorStepButton.is_push() ) {
        ggDoorStepButton.PutValue(
            mvOccupied->Doors.step_enabled ?
                1.f :
                0.f );
    }
    // heating
    if( false == ggTrainHeatingButton.is_push() ) {
        ggTrainHeatingButton.PutValue(
            mvControlled->Heating ?
                1.f :
                0.f );
    }
    // brake acting time
    if( ggBrakeProfileCtrl.SubModel != nullptr ) {
        ggBrakeProfileCtrl.PutValue(
            ( ( mvOccupied->BrakeDelayFlag & bdelay_R ) != 0 ?
                2.f :
                mvOccupied->BrakeDelayFlag - 1 ) );
    }
    if( ggBrakeProfileG.SubModel != nullptr ) {
        ggBrakeProfileG.PutValue(
            mvOccupied->BrakeDelayFlag == bdelay_G ?
                1.f :
                0.f );
    }
    if( ggBrakeProfileR.SubModel != nullptr ) {
        ggBrakeProfileR.PutValue(
            ( mvOccupied->BrakeDelayFlag & bdelay_R ) != 0 ?
                1.f :
                0.f );
    }
	if (ggBrakeOperationModeCtrl.SubModel != nullptr) {
		ggBrakeOperationModeCtrl.PutValue(
			(mvOccupied->BrakeOpModeFlag > 0 ?
				std::log2(mvOccupied->BrakeOpModeFlag) :
				0));
	}
    // alarm chain
    ggAlarmChain.PutValue(
        mvControlled->AlarmChainFlag ?
            1.f :
            0.f );
    // brake signalling
    ggSignallingButton.PutValue(
        mvControlled->Signalling ?
            1.f :
            0.f );
    // multiple-unit current indicator source
    ggNextCurrentButton.PutValue(
        ShowNextCurrent ?
            1.f :
            0.f );
    // water pump
    ggWaterPumpBreakerButton.PutValue(
        mvControlled->WaterPump.breaker ?
            1.f :
            0.f );
    if( ggWaterPumpButton.type() != TGaugeType::push ) {
        ggWaterPumpButton.PutValue(
            mvControlled->WaterPump.is_enabled ?
                1.f :
                0.f );
    }
    // water heater
    ggWaterHeaterBreakerButton.PutValue(
        mvControlled->WaterHeater.breaker ?
            1.f :
            0.f );
    ggWaterHeaterButton.PutValue(
        mvControlled->WaterHeater.is_enabled ?
            1.f :
            0.f );
    ggWaterCircuitsLinkButton.PutValue(
        mvControlled->WaterCircuitsLink ?
            1.f :
            0.f );
    // fuel pump
    if( ggFuelPumpButton.type() != TGaugeType::push ) {
        ggFuelPumpButton.PutValue(
            mvControlled->FuelPump.is_enabled ?
                1.f :
                0.f );
    }
    // oil pump
    if( ggOilPumpButton.type() != TGaugeType::push ) {
        ggOilPumpButton.PutValue(
            mvControlled->OilPump.is_enabled ?
            1.f :
            0.f );
    }
    // traction motor fans
    if( ggMotorBlowersFrontButton.type() != TGaugeType::push ) {
        ggMotorBlowersFrontButton.PutValue(
            mvControlled->MotorBlowers[end::front].is_enabled ?
            1.f :
            0.f );
    }
    if( ggMotorBlowersRearButton.type() != TGaugeType::push ) {
        ggMotorBlowersRearButton.PutValue(
            mvControlled->MotorBlowers[end::rear].is_enabled ?
            1.f :
            0.f );
    }
    if( ggMotorBlowersAllOffButton.type() != TGaugeType::push ) {
        ggMotorBlowersAllOffButton.PutValue(
            ( mvControlled->MotorBlowers[end::front].is_disabled
		   || mvControlled->MotorBlowers[end::rear].is_disabled ) ?
                1.f :
                0.f );
    }
    // second controller
    if( ggScndCtrl.is_push() ) {
        ggScndCtrl.PutValue(
            ggScndCtrl.is_toggle() ?
                0.5f : // pushtoggle is two-way control with neutral position in the middle
                0.f ); // push is on/off control, active while held down, due to legacy use
    }
    // tempomat
    if( false == ggScndCtrlButton.is_push() ) {
        ggScndCtrlButton.PutValue(
            ( mvControlled->ScndCtrlPos > 0 ) ?
                1.f :
                0.f );
    }
    // sandbox
    if( ggAutoSandButton.type() != TGaugeType::push ) {
        ggAutoSandButton.PutValue(
            mvControlled->SandDoseAutoAllow ?
                1.f :
                0.f );
     }
    // radio
    ggRadioVolumeSelector.PutValue( Global.RadioVolume );

	//finding each inverter - not so optimal, but action ins performed only during changing cabin
	bool kier = (DynamicObject->DirectionGet() * mvOccupied->CabOccupied > 0);
	int flag = DynamicObject->MoverParameters->InverterControlCouplerFlag;
	int itemstart = 0;
	for (auto &item : ggInverterToggleButtons) //for each button
	{
		int itemindex = itemstart;
		itemstart++;
		TDynamicObject *p = DynamicObject->GetFirstDynamic(mvOccupied->CabOccupied < 0 ? end::rear : end::front, flag);
		while (p)
		{
			if (p->MoverParameters->eimc[eimc_p_Pmax] > 1)
			{
				if (itemindex < p->MoverParameters->InvertersNo)
				{
					// visual feedback
					ggInverterToggleButtons[itemindex].PutValue(p->MoverParameters->Inverters[itemindex].Activate ? 1.0 : 0.0);
					break;
				}
				else
				{
					itemindex -= p->MoverParameters->InvertersNo;
				}

			}
			p = (kier ? p->Next(flag) : p->Prev(flag));
		}
	}
       
    // we reset all indicators, as they're set during the update pass
    // TODO: when cleaning up break setting indicator state into a separate function, so we can reuse it
}

// initializes a button matching provided label. returns: true if the label was found, false otherwise
// TODO: refactor the cabin controls into some sensible structure
bool TTrain::initialize_button(cParser &Parser, std::string const &Label, int const Cabindex) {

    std::unordered_map<std::string, TButton &> const lights = {
        { "i-maxft:", btLampkaMaxSila },
        { "i-maxftt:", btLampkaPrzekrMaxSila },
        { "i-radio:", btLampkaRadio },
        { "i-radiostop:", btLampkaRadioStop },
        { "i-manual_brake:", btLampkaHamulecReczny },
        { "i-door_blocked:", btLampkaBlokadaDrzwi },
        { "i-door_blockedoff:", btLampkaDoorLockOff },
        { "i-slippery:", btLampkaPoslizg },
        { "i-contactors:", btLampkaStyczn },
        { "i-conv_ovld:", btLampkaNadmPrzetw },
        { "i-converter:", btLampkaPrzetw },
        { "i-converteroff:", btLampkaPrzetwOff },
        { "i-converterb:", btLampkaPrzetwB },
        { "i-converterboff:", btLampkaPrzetwBOff },
        { "i-diff_relay:", btLampkaPrzekRozn },
        { "i-diff_relay2:", btLampkaPrzekRoznPom },
        { "i-motor_ovld:", btLampkaNadmSil },
        { "i-train_controll:", btLampkaUkrotnienie },
        { "i-brake_delay_r:", btLampkaHamPosp },
        { "i-mainbreaker:", btLampkaWylSzybki },
        { "i-mainbreakerb:", btLampkaWylSzybkiB },
        { "i-mainbreakeroff:", btLampkaWylSzybkiOff },
        { "i-mainbreakerboff:", btLampkaWylSzybkiBOff },
        { "i-mainbreakerready:", btLampkaMainBreakerReady },
        { "i-mainbreakerblinking:", btLampkaMainBreakerBlinkingIfReady },
        { "i-vent_ovld:", btLampkaNadmWent },
        { "i-comp_ovld:", btLampkaNadmSpr },
        { "i-resistors:", btLampkaOpory },
        { "i-no_resistors:", btLampkaBezoporowa },
        { "i-no_resistors_b:", btLampkaBezoporowaB },
        { "i-highcurrent:", btLampkaWysRozr },
        { "i-vent_trim:", btLampkaWentZaluzje },
        { "i-motorblowers:", btLampkaMotorBlowers },
        { "i-coolingfans:", btLampkaCoolingFans },
        { "i-tempomat:", btLampkaTempomat },
        { "i-distancecounter:", btLampkaDistanceCounter },
        { "i-trainheating:", btLampkaOgrzewanieSkladu },
        { "i-security_aware:", btLampkaCzuwaka },
        { "i-security_cabsignal:", btLampkaSHP },
		{ "i-security_aware_cabsignal:", btLampkaCzuwakaSHP },
        { "i-door_left:", btLampkaDoorLeft },
        { "i-door_right:", btLampkaDoorRight },
        { "i-departure_signal:", btLampkaDepartureSignal },
        { "i-reserve:", btLampkaRezerwa },
        { "i-scnd:", btLampkaBoczniki },
        { "i-scnd1:", btLampkaBocznik1 },
        { "i-scnd2:", btLampkaBocznik2 },
        { "i-scnd3:", btLampkaBocznik3 },
        { "i-scnd4:", btLampkaBocznik4 },
        { "i-braking:", btLampkaHamienie },
        { "i-brakingoff:", btLampkaBrakingOff },
        { "i-dynamicbrake:", btLampkaED },
        { "i-brakeprofileg:", btLampkaBrakeProfileG },
        { "i-brakeprofilep:", btLampkaBrakeProfileP },
        { "i-brakeprofiler:", btLampkaBrakeProfileR },
		{ "i-springbrakeactive:", btLampkaSpringBrakeActive },
		{ "i-springbrakeinactive:", btLampkaSpringBrakeInactive },
        { "i-braking-ezt:", btLampkaHamowanie1zes },
        { "i-braking-ezt2:", btLampkaHamowanie2zes },
        { "i-compressor:", btLampkaSprezarka },
        { "i-compressorb:", btLampkaSprezarkaB },
        { "i-compressoroff:", btLampkaSprezarkaOff },
        { "i-compressorboff:", btLampkaSprezarkaBOff },
        { "i-fuelpumpoff:", btLampkaFuelPumpOff },
        { "i-voltbrake:", btLampkaNapNastHam },
        { "i-resistorsb:", btLampkaOporyB },
        { "i-contactorsb:", btLampkaStycznB },
        { "i-conv_ovldb:", btLampkaNadmPrzetwB },
        { "i-hvoltageb:", btLampkaHVoltageB },
        { "i-malfunction:", btLampkaMalfunction },
        { "i-malfunctionb:", btLampkaMalfunctionB },
        { "i-forward:", btLampkaForward },
        { "i-backward:", btLampkaBackward },
        { "i-upperlight:", btLampkaUpperLight },
        { "i-leftlight:", btLampkaLeftLight },
        { "i-rightlight:", btLampkaRightLight },
        { "i-leftend:", btLampkaLeftEndLight },
        { "i-rightend:", btLampkaRightEndLight },
        { "i-rearupperlight:", btLampkaRearUpperLight },
        { "i-rearleftlight:", btLampkaRearLeftLight },
        { "i-rearrightlight:", btLampkaRearRightLight },
        { "i-rearleftend:", btLampkaRearLeftEndLight },
        { "i-rearrightend:",  btLampkaRearRightEndLight },
        { "i-dashboardlight:",  btDashboardLight },
        { "i-timetablelight:",  btTimetableLight },
        { "i-universal0:", btUniversals[ 0 ] },
        { "i-universal1:", btUniversals[ 1 ] },
        { "i-universal2:", btUniversals[ 2 ] },
        { "i-universal3:", btUniversals[ 3 ] },
        { "i-universal4:", btUniversals[ 4 ] },
        { "i-universal5:", btUniversals[ 5 ] },
        { "i-universal6:", btUniversals[ 6 ] },
        { "i-universal7:", btUniversals[ 7 ] },
        { "i-universal8:", btUniversals[ 8 ] },
        { "i-universal9:", btUniversals[ 9 ] }
    };
    {
        auto lookup = lights.find( Label );
        if( lookup != lights.end() ) {
            lookup->second.Load( Parser, DynamicObject );
            return true;
        }
    }
    // TODO: move viable dedicated lights to the automatic light array
    std::unordered_map<std::string, bool const *> const autolights = {
        { "i-doors:", &m_doors },
        { "i-doorpermit_left:",  &mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::left : side::right ) ].open_permit },
        { "i-doorpermit_right:", &mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::right : side::left ) ].open_permit },
        { "i-doorpermit_any:", &m_doorpermits },
        { "i-doorstep:", &mvOccupied->Doors.step_enabled },
        { "i-mainpipelock:", &mvOccupied->LockPipe },
        { "i-battery:", &mvOccupied->Power24vIsAvailable },
        { "i-cablight:", &Cabine[ iCabn ].bLight },
    };
    {
        auto lookup = autolights.find( Label );
        if( lookup != autolights.end() ) {
            auto &button = Cabine[ Cabindex ].Button( -1 ); // pierwsza wolna lampka
            button.Load( Parser, DynamicObject );
            button.AssignBool( lookup->second );
            return true;
        }
    }
    // custom lights
    if( Label == "i-instrumentlight:" ) {
        btInstrumentLight.Load( Parser, DynamicObject );
        InstrumentLightType = 0;
    }
    else if( Label == "i-instrumentlight_m:" ) {
        btInstrumentLight.Load( Parser, DynamicObject );
        InstrumentLightType = 1;
    }
    else if( Label == "i-instrumentlight_c:" ) {
        btInstrumentLight.Load( Parser, DynamicObject );
        InstrumentLightType = 2;
    }
    else if( Label == "i-instrumentlight_a:" ) {
        btInstrumentLight.Load( Parser, DynamicObject );
        InstrumentLightType = 3;
    }
    else if( Label == "i-instrumentlight_l:" ) {
        btInstrumentLight.Load( Parser, DynamicObject );
        InstrumentLightType = 4;
    }
    else if (Label == "i-doors:")
    {
        int i = Parser.getToken<int>() - 1;
        auto &button = Cabine[Cabindex].Button(-1); // pierwsza wolna lampka
        button.Load(Parser, DynamicObject);
        button.AssignBool(bDoors[0] + 3 * i);
    }
    else
    {
        // failed to match the label
        return false;
    }

    return true;
}

// initializes a gauge matching provided label. returns: true if the label was found, false otherwise
// TODO: refactor the cabin controls into some sensible structure
bool TTrain::initialize_gauge(cParser &Parser, std::string const &Label, int const Cabindex) {

    std::unordered_map<std::string, TGauge &> const gauges = {
        { "jointctrl:", ggJointCtrl },
        { "mainctrl:", ggMainCtrl },
        { "scndctrl:", ggScndCtrl },
        { "dirkey:" , ggDirKey },
        { "brakectrl:", ggBrakeCtrl },
        { "localbrake:", ggLocalBrake },
        { "alarmchain:", ggAlarmChain },
        { "brakeprofile_sw:", ggBrakeProfileCtrl },
        { "brakeprofileg_sw:", ggBrakeProfileG },
        { "brakeprofiler_sw:", ggBrakeProfileR },
		{ "brakeopmode_sw:", ggBrakeOperationModeCtrl },
        { "maxcurrent_sw:", ggMaxCurrentCtrl },
        { "main_off_bt:", ggMainOffButton },
        { "main_on_bt:", ggMainOnButton },
        { "security_reset_bt:", ggSecurityResetButton },
        { "releaser_bt:", ggReleaserButton },
		{ "springbrakeon_bt:", ggSpringBrakeOnButton },
		{ "springbrakeoff_bt:", ggSpringBrakeOffButton },
		{ "universalbrake1_bt:", ggUniveralBrakeButton1 },
		{ "universalbrake2_bt:", ggUniveralBrakeButton2 },
		{ "universalbrake3_bt:", ggUniveralBrakeButton3 },
		{ "epbrake_bt:", ggEPFuseButton },
        { "sand_bt:", ggSandButton },
		{ "autosandallow_sw:", ggAutoSandButton },
        { "antislip_bt:", ggAntiSlipButton },
        { "horn_bt:", ggHornButton },
        { "hornlow_bt:", ggHornLowButton },
        { "hornhigh_bt:", ggHornHighButton },
        { "whistle_bt:", ggWhistleButton },
		{ "helper_bt:", ggHelperButton },
        { "fuse_bt:", ggFuseButton },
        { "converterfuse_bt:", ggConverterFuseButton },
        { "stlinoff_bt:", ggStLinOffButton },
        { "doorpermitpreset_sw:", ggDoorPermitPresetButton },
        { "door_left_sw:", ggDoorLeftButton },
        { "door_right_sw:", ggDoorRightButton },
        { "doorlefton_sw:", ggDoorLeftOnButton },
        { "doorrighton_sw:", ggDoorRightOnButton },
        { "doorleftoff_sw:", ggDoorLeftOffButton },
        { "doorrightoff_sw:", ggDoorRightOffButton },
        { "doorallon_sw:", ggDoorAllOnButton },
        { "departure_signal_bt:", ggDepartureSignalButton },
        { "upperlight_sw:", ggUpperLightButton },
        { "leftlight_sw:", ggLeftLightButton },
        { "rightlight_sw:", ggRightLightButton },
        { "dimheadlights_sw:", ggDimHeadlightsButton },
        { "leftend_sw:", ggLeftEndLightButton },
        { "rightend_sw:", ggRightEndLightButton },
        { "lights_sw:", ggLightsButton },
        { "rearupperlight_sw:", ggRearUpperLightButton },
        { "rearleftlight_sw:", ggRearLeftLightButton },
        { "rearrightlight_sw:", ggRearRightLightButton },
        { "rearleftend_sw:", ggRearLeftEndLightButton },
        { "rearrightend_sw:",  ggRearRightEndLightButton },
        { "compressor_sw:", ggCompressorButton },
        { "compressorlocal_sw:", ggCompressorLocalButton },
		{ "compressorlist_sw:", ggCompressorListButton },
        { "converter_sw:", ggConverterButton },
        { "converterlocal_sw:", ggConverterLocalButton },
        { "converteroff_sw:", ggConverterOffButton },
        { "main_sw:", ggMainButton },
        { "waterpumpbreaker_sw:", ggWaterPumpBreakerButton },
        { "waterpump_sw:", ggWaterPumpButton },
        { "waterheaterbreaker_sw:", ggWaterHeaterBreakerButton },
        { "waterheater_sw:", ggWaterHeaterButton },
        { "water1tempb:", ggWater1TempB },
        { "watercircuitslink_sw:", ggWaterCircuitsLinkButton },
        { "fuelpump_sw:", ggFuelPumpButton },
        { "oilpump_sw:", ggOilPumpButton },
        { "oilpressb:", ggOilPressB },
        { "motorblowersfront_sw:", ggMotorBlowersFrontButton },
        { "motorblowersrear_sw:", ggMotorBlowersRearButton },
        { "motorblowersalloff_sw:", ggMotorBlowersAllOffButton },
        { "radiochannel_sw:", ggRadioChannelSelector },
        { "radiochannelprev_sw:", ggRadioChannelPrevious },
        { "radiochannelnext_sw:", ggRadioChannelNext },
        { "radiostop_sw:", ggRadioStop },
        { "radiotest_sw:", ggRadioTest },
        { "radiocall3_sw:", ggRadioCall3 },
		{ "radiovolume_sw:", ggRadioVolumeSelector },
		{ "radiovolumeprev_sw:", ggRadioVolumePrevious },
		{ "radiovolumenext_sw:", ggRadioVolumeNext },
/*
        { "pantfront_sw:", ggPantFrontButton },
        { "pantrear_sw:", ggPantRearButton },
        { "pantfrontoff_sw:", ggPantFrontButtonOff },
        { "pantrearoff_sw:", ggPantRearButtonOff },
*/
        { "pantalloff_sw:", ggPantAllDownButton },
        { "pantselected_sw:", ggPantSelectedButton },
        { "pantselectedoff_sw:", ggPantSelectedDownButton },
        { "pantvalves_sw:", ggPantValvesButton },
        { "pantcompressor_sw:", ggPantCompressorButton },
        { "pantcompressorvalve_sw:", ggPantCompressorValve },
        { "trainheating_sw:", ggTrainHeatingButton },
        { "signalling_sw:", ggSignallingButton },
        { "door_signalling_sw:", ggDoorSignallingButton },
        { "nextcurrent_sw:", ggNextCurrentButton },
        { "instrumentlight_sw:", ggInstrumentLightButton },
        { "dashboardlight_sw:", ggDashboardLightButton },
        { "timetablelight_sw:", ggTimetableLightButton },
        { "cablightdim_sw:", ggCabLightDimButton },
        { "compartmentlights_sw:", ggCompartmentLightsButton },
        { "compartmentlightson_sw:", ggCompartmentLightsOnButton },
        { "compartmentlightsoff_sw:", ggCompartmentLightsOffButton },
        { "battery_sw:", ggBatteryButton },
        { "batteryon_sw:", ggBatteryOnButton },
        { "batteryoff_sw:", ggBatteryOffButton },
        { "distancecounter_sw:", ggDistanceCounterButton },
        { "relayreset1_bt:", ggRelayResetButtons[ 0 ] },
        { "relayreset2_bt:", ggRelayResetButtons[ 1 ] },
        { "relayreset3_bt:", ggRelayResetButtons[ 2 ] },
        { "universal0:", ggUniversals[ 0 ] },
        { "universal1:", ggUniversals[ 1 ] },
        { "universal2:", ggUniversals[ 2 ] },
        { "universal3:", ggUniversals[ 3 ] },
        { "universal4:", ggUniversals[ 4 ] },
        { "universal5:", ggUniversals[ 5 ] },
        { "universal6:", ggUniversals[ 6 ] },
        { "universal7:", ggUniversals[ 7 ] },
        { "universal8:", ggUniversals[ 8 ] },
        { "universal9:", ggUniversals[ 9 ] },
		{ "inverterenable1_bt:", ggInverterEnableButtons[0] },
		{ "inverterenable2_bt:", ggInverterEnableButtons[1] },
		{ "inverterenable3_bt:", ggInverterEnableButtons[2] },
		{ "inverterenable4_bt:", ggInverterEnableButtons[3] },
		{ "inverterenable5_bt:", ggInverterEnableButtons[4] },
		{ "inverterenable6_bt:", ggInverterEnableButtons[5] },
		{ "inverterenable7_bt:", ggInverterEnableButtons[6] },
		{ "inverterenable8_bt:", ggInverterEnableButtons[7] },
		{ "inverterenable9_bt:", ggInverterEnableButtons[8] },
		{ "inverterenable10_bt:", ggInverterEnableButtons[9] },
		{ "inverterenable11_bt:", ggInverterEnableButtons[10] },
		{ "inverterenable12_bt:", ggInverterEnableButtons[11] },
		{ "inverterdisable1_bt:", ggInverterDisableButtons[0] },
		{ "inverterdisable2_bt:", ggInverterDisableButtons[1] },
		{ "inverterdisable3_bt:", ggInverterDisableButtons[2] },
		{ "inverterdisable4_bt:", ggInverterDisableButtons[3] },
		{ "inverterdisable5_bt:", ggInverterDisableButtons[4] },
		{ "inverterdisable6_bt:", ggInverterDisableButtons[5] },
		{ "inverterdisable7_bt:", ggInverterDisableButtons[6] },
		{ "inverterdisable8_bt:", ggInverterDisableButtons[7] },
		{ "inverterdisable9_bt:", ggInverterDisableButtons[8] },
		{ "inverterdisable10_bt:", ggInverterDisableButtons[9] },
		{ "inverterdisable11_bt:", ggInverterDisableButtons[10] },
		{ "inverterdisable12_bt:", ggInverterDisableButtons[11] },
		{ "invertertoggle1_bt:", ggInverterToggleButtons[0] },
		{ "invertertoggle2_bt:", ggInverterToggleButtons[1] },
		{ "invertertoggle3_bt:", ggInverterToggleButtons[2] },
		{ "invertertoggle4_bt:", ggInverterToggleButtons[3] },
		{ "invertertoggle5_bt:", ggInverterToggleButtons[4] },
		{ "invertertoggle6_bt:", ggInverterToggleButtons[5] },
		{ "invertertoggle7_bt:", ggInverterToggleButtons[6] },
		{ "invertertoggle8_bt:", ggInverterToggleButtons[7] },
		{ "invertertoggle9_bt:", ggInverterToggleButtons[8] },
		{ "invertertoggle10_bt:", ggInverterToggleButtons[9] },
		{ "invertertoggle11_bt:", ggInverterToggleButtons[10] },
		{ "invertertoggle12_bt:", ggInverterToggleButtons[11] },
    };
    {
        auto const lookup { gauges.find( Label ) };
        if( lookup != gauges.end() ) {
            lookup->second.Load( Parser, DynamicObject );
            m_controlmapper.insert( lookup->second, lookup->first );
            return true;
        }
    }
    // dedicated gauges with state-driven optional submodel
    // TODO: move viable gauges here
    // TODO: convert dedicated gauges to auto-allocated ones, replace dedicated references in command handlers to mapper lookups
    std::unordered_map<std::string, std::tuple<TGauge &, bool const *> > const stategauges = {
        { "tempomat_sw:", { ggScndCtrlButton, &mvOccupied->SpeedCtrlUnit.IsActive } },
        { "tempomatoff_sw:", { ggScndCtrlOffButton, &mvOccupied->SpeedCtrlUnit.IsActive } },
        { "speedinc_bt:", { ggSpeedControlIncreaseButton, &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speeddec_bt:", { ggSpeedControlDecreaseButton, &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedctrlpowerinc_bt:", { ggSpeedControlPowerIncreaseButton, &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedctrlpowerdec_bt:", { ggSpeedControlPowerDecreaseButton, &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton0:", { ggSpeedCtrlButtons[ 0 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton1:", { ggSpeedCtrlButtons[ 1 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton2:", { ggSpeedCtrlButtons[ 2 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton3:", { ggSpeedCtrlButtons[ 3 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton4:", { ggSpeedCtrlButtons[ 4 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton5:", { ggSpeedCtrlButtons[ 5 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton6:", { ggSpeedCtrlButtons[ 6 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton7:", { ggSpeedCtrlButtons[ 7 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton8:", { ggSpeedCtrlButtons[ 8 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
		{ "speedbutton9:", { ggSpeedCtrlButtons[ 9 ], &mvOccupied->SpeedCtrlUnit.IsActive } },
        { "doorleftpermit_sw:", { ggDoorLeftPermitButton, &mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::left : side::right ) ].open_permit } },
        { "doorrightpermit_sw:", { ggDoorRightPermitButton, &mvOccupied->Doors.instances[ ( cab_to_end() == end::front ? side::right : side::left ) ].open_permit } },
        { "dooralloff_sw:", { ggDoorAllOffButton, &m_doors } },
        { "doorstep_sw:", { ggDoorStepButton, &mvOccupied->Doors.step_enabled } },
        { "dirforward_bt:", { ggDirForwardButton, &m_dirforward } },
		{ "dirneutral_bt:", { ggDirNeutralButton, &m_dirneutral } },
		{ "dirbackward_bt:", { ggDirBackwardButton, &m_dirbackward } },
    };
    {
        auto const lookup { stategauges.find( Label ) };
        if( lookup != stategauges.end() ) {
            auto &gauge { std::get<TGauge &>( lookup->second ) };
            gauge.Load( Parser, DynamicObject );
            gauge.AssignState( std::get<bool const *>( lookup->second ) );
            m_controlmapper.insert( gauge, lookup->first );
            return true;
        }
    }
    // TODO: move viable dedicated gauges to the automatic array
    std::unordered_map<std::string, bool *> const autoboolgauges = {
        { "doormode_sw:", &mvOccupied->Doors.remote_only },
        { "coolingfans_sw:", &mvControlled->RVentForceOn },
        { "pantfront_sw:", &mvPantographUnit->Pantographs[end::front].valve.is_enabled },
        { "pantrear_sw:", &mvPantographUnit->Pantographs[end::rear].valve.is_enabled },
        { "pantfrontoff_sw:", &mvPantographUnit->Pantographs[end::front].valve.is_disabled },
        { "pantrearoff_sw:", &mvPantographUnit->Pantographs[end::rear].valve.is_disabled },
        { "radio_sw:", &mvOccupied->Radio },
        { "cablight_sw:", &Cabine[ iCabn ].bLight },
        { "springbraketoggle_bt:", &mvOccupied->SpringBrake.Activate },
        { "couplingdisconnect_sw:", &m_couplingdisconnect },
    };
    {
        auto lookup = autoboolgauges.find( Label );
        if( lookup != autoboolgauges.end() ) {
            auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna lampka
            gauge.Load( Parser, DynamicObject );
            gauge.AssignBool( lookup->second );
            m_controlmapper.insert( gauge, lookup->first );
            return true;
        }
    }
    // TODO: move viable dedicated gauges to the automatic array
    std::unordered_map<std::string, int *> const autointgauges = {
        { "manualbrake:", &mvOccupied->ManualBrakePos },
        { "pantselect_sw:", &mvOccupied->PantsPreset.second[cab_to_end()] },
    };
    {
        auto lookup = autointgauges.find( Label );
        if( lookup != autointgauges.end() ) {
            auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna lampka
            gauge.Load( Parser, DynamicObject );
            gauge.AssignInt( lookup->second );
            m_controlmapper.insert( gauge, lookup->first );
            return true;
        }
    }

    // ABu 090305: uniwersalne przyciski lub inne rzeczy
    if( Label == "mainctrlact:" ) {
        ggMainCtrlAct.Load( Parser, DynamicObject);
    }
    // SEKCJA WSKAZNIKOW
    else if ((Label == "tachometer:") || (Label == "tachometerb:"))
    {
        // predkosciomierz wskaźnikowy z szarpaniem
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(&fTachoVelocityJump);
        // bind tachometer sound location to the meter
        if( dsbHasler
         && dsbHasler->offset() == glm::vec3() ) {
            dsbHasler->offset( gauge.model_offset() );
        }
    }
    else if (Label == "tachometern:")
    {
        // predkosciomierz wskaźnikowy bez szarpania
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(&fTachoVelocity);
        // bind tachometer sound location to the meter
        if( dsbHasler
         && dsbHasler->offset() == glm::vec3() ) {
            dsbHasler->offset( gauge.model_offset() );
        }
    }
    else if (Label == "tachometerd:")
    {
        // predkosciomierz cyfrowy
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(&fTachoVelocity);
        // bind tachometer sound location to the meter
        if( dsbHasler
         && dsbHasler->offset() == glm::vec3() ) {
            dsbHasler->offset( gauge.model_offset() );
        }
    }
    else if ((Label == "hvcurrent1:") || (Label == "hvcurrent1b:"))
    {
        // 1szy amperomierz
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(fHCurrent + 1);
    }
    else if ((Label == "hvcurrent2:") || (Label == "hvcurrent2b:"))
    {
        // 2gi amperomierz
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(fHCurrent + 2);
    }
    else if ((Label == "hvcurrent3:") || (Label == "hvcurrent3b:"))
    {
        // 3ci amperomierz
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałska
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(fHCurrent + 3);
    }
    else if ((Label == "hvcurrent:") || (Label == "hvcurrentb:"))
    {
        // amperomierz calkowitego pradu
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(fHCurrent);
    }
    else if (Label == "eimscreen:")
    {
        // amperomierz calkowitego pradu
        int i, j;
        Parser.getTokens(2, false);
        Parser >> i >> j;
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(&fEIMParams[i][j]);
    }
    else if (Label == "brakes:")
    {
        // specified pipe pressure of specified consist vehicle
        int i, j;
        Parser.getTokens(2, false);
        Parser >> i >> j;
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject, 0.1);
        gauge.AssignFloat(&fPress[clamp(i, 1, 20) - 1][clamp(j, 0, 3)]);
    }
    else if ((Label == "brakepress:") || (Label == "brakepressb:"))
    {
        // manometr cylindrow hamulcowych
        // Ra 2014-08: przeniesione do TCab
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject, 0.1);
        gauge.AssignDouble(&mvOccupied->BrakePress);
    }
    else if ((Label == "pipepress:") || (Label == "pipepressb:"))
    {
        // manometr przewodu hamulcowego
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject, 0.1);
        gauge.AssignDouble(&mvOccupied->PipePress);
    }
    else if( Label == "scndpress:" ) {
        // manometr przewodu hamulcowego
        auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
        gauge.Load( Parser, DynamicObject, 0.1 );
        gauge.AssignDouble( &mvOccupied->ScndPipePress );
    }
    else if (Label == "limpipepress:")
    {
        // manometr zbiornika sterujacego zaworu maszynisty
        auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
        gauge.Load( Parser, DynamicObject, 0.1 );
        gauge.AssignDouble( &m_brakehandlecp );
    }
    else if (Label == "cntrlpress:")
    {
        // manometr zbiornika kontrolnego/rorz�du
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject, 0.1);
        gauge.AssignDouble(&mvPantographUnit->PantPress);
    }
	else if (Label == "springbrakepress:")
	{
		// manometr cylindra hamulca sprężynowego
		auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
		gauge.Load(Parser, DynamicObject, 0.1);
		gauge.AssignDouble(&mvOccupied->SpringBrake.SBP);
	}
	else if (Label == "epctrlvalue:")
	{
		// wskazowka sterowania sila hamulca ep
		auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
		gauge.Load(Parser, DynamicObject, 0.1);
		gauge.AssignDouble(&mvOccupied->EpForce);
	}
    else if ((Label == "compressor:") || (Label == "compressorb:"))
    {
        // manometr sprezarki/zbiornika glownego
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject, 0.1);
        gauge.AssignDouble(&mvOccupied->Compressor);
    }
    else if( Label == "oilpress:" ) {
        // oil pressure
        auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
        gauge.Load( Parser, DynamicObject );
        gauge.AssignFloat( &mvControlled->OilPump.pressure );
    }
    else if( Label == "oiltemp:" ) {
        // oil temperature
        auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
        gauge.Load( Parser, DynamicObject );
        gauge.AssignFloat( &mvControlled->dizel_heat.To );
    }
    else if( Label == "water1temp:" ) {
        // main circuit water temperature
        auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
        gauge.Load( Parser, DynamicObject );
        gauge.AssignFloat( &mvControlled->dizel_heat.temperatura1 );
    }
    else if( Label == "water2temp:" ) {
        // auxiliary circuit water temperature
        auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
        gauge.Load( Parser, DynamicObject );
        gauge.AssignFloat( &mvControlled->dizel_heat.temperatura2 );
    }
    else if( Label == "pantpress:" ) {
        // pantograph tank pressure
        auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
        gauge.Load( Parser, DynamicObject, 0.1 );
        gauge.AssignDouble( &mvPantographUnit->PantPress );
    }
    // yB - dla drugiej sekcji
    else if (Label == "hvbcurrent1:")
    {
        // 1szy amperomierz
        ggI1B.Load(Parser, DynamicObject);
    }
    else if (Label == "hvbcurrent2:")
    {
        // 2gi amperomierz
        ggI2B.Load(Parser, DynamicObject);
    }
    else if (Label == "hvbcurrent3:")
    {
        // 3ci amperomierz
        ggI3B.Load(Parser, DynamicObject);
    }
    else if (Label == "hvbcurrent:")
    {
        // amperomierz calkowitego pradu
        ggItotalB.Load(Parser, DynamicObject);
    }
    //*************************************************************
    else if (Label == "clock:")
    {
        // zegar analogowy
        if (Parser.getToken<std::string>() == "analog")
        {
            if( DynamicObject->mdKabina ) {
                // McZapkie-300302: zegarek
                ggClockSInd.Init( DynamicObject->mdKabina->GetFromName( "ClockShand" ), nullptr, TGaugeAnimation::gt_Rotate, 1.0 / 60.0 );
                ggClockMInd.Init( DynamicObject->mdKabina->GetFromName( "ClockMhand" ), nullptr, TGaugeAnimation::gt_Rotate, 1.0 / 60.0 );
                ggClockHInd.Init( DynamicObject->mdKabina->GetFromName( "ClockHhand" ), nullptr, TGaugeAnimation::gt_Rotate, 1.0 / 12.0 );
            }
        }
    }
    else if( Label == "clock_seconds:" ) {
        ggClockSInd.Load( Parser, DynamicObject );
    }
    else if (Label == "evoltage:")
    {
        // woltomierz napiecia silnikow
        ggEngineVoltage.Load(Parser, DynamicObject);
    }
    else if (Label == "hvoltage:")
    {
        // woltomierz wysokiego napiecia
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(&fHVoltage);
    }
    else if (Label == "lvoltage:")
    {
        // woltomierz niskiego napiecia
        ggLVoltage.Load(Parser, DynamicObject);
    }
    else if (Label == "enrot1m:")
    {
        // obrotomierz
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(fEngine + 1);
    } // ggEnrot1m.Load(Parser,DynamicObject->mdKabina);
    else if (Label == "enrot2m:")
    {
        // obrotomierz
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(fEngine + 2);
    } // ggEnrot2m.Load(Parser,DynamicObject->mdKabina);
    else if (Label == "enrot3m:")
    { // obrotomierz
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignFloat(fEngine + 3);
    } // ggEnrot3m.Load(Parser,DynamicObject->mdKabina);
    else if (Label == "engageratio:")
    {
        // np. ciśnienie sterownika sprzęgła
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignDouble(&mvControlled->dizel_engage);
    } // ggEngageRatio.Load(Parser,DynamicObject->mdKabina);
    else if (Label == "maingearstatus:")
    {
        // np. ciśnienie sterownika skrzyni biegów
        ggMainGearStatus.Load(Parser, DynamicObject);
    }
    else if (Label == "ignitionkey:")
    {
        ggIgnitionKey.Load(Parser, DynamicObject);
    }
    else if (Label == "distcounter:")
    {
        // Ra 2014-07: licznik kilometrów
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignDouble(&mvControlled->DistCounter);
    }
    else if( Label == "shuntmodepower:" ) {
        // shunt mode power slider
        auto &gauge = Cabine[Cabindex].Gauge(-1); // pierwsza wolna gałka
        gauge.Load(Parser, DynamicObject);
        gauge.AssignDouble(&mvControlled->AnPos);
        m_controlmapper.insert( gauge, "shuntmodepower:" );
    }
    else if( Label == "heatingvoltage:" ) {
        if( mvControlled->HeatingPowerSource.SourceType == TPowerSource::Generator ) {
            auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
            gauge.Load( Parser, DynamicObject );
            gauge.AssignDouble( &(mvControlled->HeatingPowerSource.EngineGenerator.voltage) );
        }
    }
    else if( Label == "heatingcurrent:" ) {
        auto &gauge = Cabine[ Cabindex ].Gauge( -1 ); // pierwsza wolna gałka
        gauge.Load( Parser, DynamicObject );
        gauge.AssignDouble( &( mvControlled->TotalCurrent ) );
    }
    else
    {
        // failed to match the label
        return false;
    }

    return true;
}

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::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
		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;
}