--
-- Dashboard Extension for FS25
--
-- Jason06 / Glowins Modschmiede
--
DashboardLive = {}

if DashboardLive.MOD_NAME == nil then
	DashboardLive.MOD_NAME = g_currentModName
	DashboardLive.MOD_PATH = g_currentModDirectory
	DashboardLive.INT_PATH = "notLoaded"
	DashboardLive.MODSETTINGSDIR = g_currentModSettingsDirectory
	createFolder(DashboardLive.MODSETTINGSDIR)
end

source(DashboardLive.MOD_PATH.."tools/gmsDebug.lua")
GMSDebug:init(DashboardLive.MOD_NAME, true, 2)
GMSDebug:enableConsoleCommands("dblDebug")

source(DashboardLive.MOD_PATH.."utils/DashboardUtils.lua")

DashboardLive.scale = 0.1
DashboardLive.minimapConfig = {}

DashboardLive.vis_partly = false

-- Console

function DashboardLive:editParameter(scale)
	local scale = tonumber(scale) or DashboardLive.scale
	print("DBL Parameter: Scale = "..tostring(scale))
	DashboardLive.scale = scale
end
addConsoleCommand("dblParameter", "DBL: Change scale parameter", "editParameter", DashboardLive)

-- Standards / Basics

function DashboardLive.prerequisitesPresent(specializations)
  return SpecializationUtil.hasSpecialization(Dashboard, specializations) and SpecializationUtil.hasSpecialization(Motorized, specializations)
end

function DashboardLive.initSpecialization()
    local schema = Vehicle.xmlSchema
    schema:register(XMLValueType.STRING, Dashboard.GROUP_XML_KEY .. "#dbl", "DashboardLive command")
    schema:register(XMLValueType.STRING, Dashboard.GROUP_XML_KEY .. "#op", "DashboardLive operator")
	schema:register(XMLValueType.INT, Dashboard.GROUP_XML_KEY .. "#page", "DashboardLive page")
	schema:register(XMLValueType.INT, Dashboard.GROUP_XML_KEY .. "#group", "DashboardLive pages group")
	schema:register(XMLValueType.BOOL, Dashboard.GROUP_XML_KEY .. "#dblActiveWithoutImplement", "return 'true' without implement")
	schema:register(XMLValueType.VECTOR_N, Dashboard.GROUP_XML_KEY .. "#dblAttacherJointIndices")
	schema:register(XMLValueType.STRING, Dashboard.GROUP_XML_KEY .. "#dblJointSide", "joint filter: front or back")
	schema:register(XMLValueType.STRING, Dashboard.GROUP_XML_KEY .. "#dblJointType", "joint filter: jointType")
	schema:register(XMLValueType.VECTOR_N, Dashboard.GROUP_XML_KEY .. "#dblSelection")
	schema:register(XMLValueType.VECTOR_N, Dashboard.GROUP_XML_KEY .. "#dblSelectionGroup")
	schema:register(XMLValueType.STRING, Dashboard.GROUP_XML_KEY .. "#dblRidgeMarker", "Ridgemarker state")
	schema:register(XMLValueType.STRING, Dashboard.GROUP_XML_KEY .. "#dblOption", "DBL Option")
	schema:register(XMLValueType.STRING, Dashboard.GROUP_XML_KEY .. "#dblTrailer", "DBL Trailer")
	dbgprint("initSpecialization : DashboardLive group options registered", 2)
	
	Dashboard.registerDashboardXMLPaths(schema, "vehicle.dashboard.dashboardLive", "dbl.base dbl.fillLevel dbl.fillType dbl.vca dbl.hlm dbl.gps dbl.gps_lane dbl.gps_width dbl.proseed dbl.selector")
	DashboardLive.DBL_XML_KEY = "vehicle.dashboard.dashboardLive.dashboard(?)"
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#cmd", "DashboardLive command")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#joints")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#jointSide", "joint filter: front or back")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#jointType", "joint filter: jointType")
	schema:register(XMLValueType.VECTOR_N, DashboardLive.DBL_XML_KEY .. "#selection")
	schema:register(XMLValueType.VECTOR_N, DashboardLive.DBL_XML_KEY .. "#selectionGroup")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#state", "state")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#stateText", "OBSOLETE: stateText")
	schema:register(XMLValueType.INT, DashboardLive.DBL_XML_KEY .. "#trailer", "trailer number")
	schema:register(XMLValueType.INT, DashboardLive.DBL_XML_KEY .. "#partition", "trailer partition")
	schema:register(XMLValueType.INT, DashboardLive.DBL_XML_KEY .. "#page", "choosen page")
	schema:register(XMLValueType.INT, DashboardLive.DBL_XML_KEY .. "#group", "choosen page group")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#option", "Option")
	schema:register(XMLValueType.FLOAT, DashboardLive.DBL_XML_KEY .. "#scale", "Minimap minimum scale factor")
	schema:register(XMLValueType.FLOAT, DashboardLive.DBL_XML_KEY .. "#factor", "Factor")
	schema:register(XMLValueType.INT, DashboardLive.DBL_XML_KEY .. "#min", "Minimum")
	schema:register(XMLValueType.INT, DashboardLive.DBL_XML_KEY .. "#max", "Maximum")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#cond", "condition command")
	schema:register(XMLValueType.FLOAT, DashboardLive.DBL_XML_KEY .. "#condValue", "condition value")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#baseColorDarkMode", "Base color for dark mode")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#emitColorDarkMode", "Emit color for dark mode")
	schema:register(XMLValueType.FLOAT, DashboardLive.DBL_XML_KEY .. "#intensityDarkMode", "Intensity for dark mode")
	
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#audioFile", "Path to audio file")
	schema:register(XMLValueType.STRING, DashboardLive.DBL_XML_KEY .. "#audioName", "Unique name of sound sample")
	schema:register(XMLValueType.INT, DashboardLive.DBL_XML_KEY .. "#loop", "repeat sound n times")
	schema:register(XMLValueType.FLOAT, DashboardLive.DBL_XML_KEY .. "#volume", "sound volume")
	schema:register(XMLValueType.BOOL, DashboardLive.DBL_XML_KEY .. "#outside", "hearable from outside?")
	schema:register(XMLValueType.FLOAT, DashboardLive.DBL_XML_KEY .. "#distance", "hearable distance")
	dbgprint("initSpecialization : DashboardLive element options registered", 2)
	
	local COMPOUND_GROUP_XML_KEY = "dashboardCompounds.group(?)"
	Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_GROUP_XML_KEY .. "#name", "Dashboard group name")
	Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_GROUP_XML_KEY .. "#dbl", "DashboardLive command")
    Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_GROUP_XML_KEY .. "#op", "DashboardLive operator")
	Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_GROUP_XML_KEY .. "#page", "DashboardLive page")
	Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_GROUP_XML_KEY .. "#group", "DashboardLive pages group")
	Dashboard.compoundsXMLSchema:register(XMLValueType.BOOL, COMPOUND_GROUP_XML_KEY .. "#dblActiveWithoutImplement", "return 'true' without implement")
	Dashboard.compoundsXMLSchema:register(XMLValueType.VECTOR_N, COMPOUND_GROUP_XML_KEY .. "#dblAttacherJointIndices")
	Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_GROUP_XML_KEY .. "#dblJointSide", "joint filter: front or back")
	Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_GROUP_XML_KEY .. "#dblJointType", "joint filter: jointType")
	Dashboard.compoundsXMLSchema:register(XMLValueType.VECTOR_N, COMPOUND_GROUP_XML_KEY .. "#dblSelection")
	Dashboard.compoundsXMLSchema:register(XMLValueType.VECTOR_N, COMPOUND_GROUP_XML_KEY .. "#dblSelectionGroup")
	Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_GROUP_XML_KEY .. "#dblRidgeMarker", "Ridgemarker state")
	Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_GROUP_XML_KEY .. "#dblOption", "DBL Option")
	Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_GROUP_XML_KEY .. "#dblTrailer", "DBL Trailer")
	dbgprint("initSpecialization : DashboardLive group options registered", 2)

	local COMPOUND_ANIMATION_XML_KEY = "dashboardCompounds.animation(?)"
	AnimatedVehicle.registerAnimationXMLPaths(Dashboard.compoundsXMLSchema, COMPOUND_ANIMATION_XML_KEY)
	Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#slopeCompensationNodeIndex", "Index in the XML of the slope compensation node")
	Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#startSlopeCompensationLevel", "Start slope compensation level")
	Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#endSlopeCompensationLevel", "End slope compensation level")
	Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#wheelIndex", "Wheel index [1..n]")
	Dashboard.compoundsXMLSchema:register(XMLValueType.ANGLE, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#startSteeringAngle", "Start steering angle")
	Dashboard.compoundsXMLSchema:register(XMLValueType.ANGLE, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#endSteeringAngle", "End steering angle")
	Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#startBrakeFactor", "Start brake force factor")
	Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#endBrakeFactor", "End brake force factor")
	Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#startTorqueDirection", "Start torque direction")
	Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#endTorqueDirection", "End torque direction")
	Dashboard.compoundsXMLSchema:register(XMLValueType.NODE_INDEX, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#startReferencePoint", "Start reference point")
	Dashboard.compoundsXMLSchema:register(XMLValueType.NODE_INDEX, COMPOUND_ANIMATION_XML_KEY .. ".part(?)#endReferencePoint", "End reference point")
	dbgprint("initSpecialization : DashboardLive animation options registered", 2)
	
	local COMPOUND_XML_KEY = "dashboardCompounds.dashboardCompound(?).dashboard(?)"
	for i = 1,2 do
		if i == 2 then COMPOUND_XML_KEY = "dashboardCompounds.dashboardCompound(?).configuration(?).dashboard(?)" end
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#cmd", "DashboardLive command")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#joints")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#jointSide", "joint filter: front or back")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#jointType", "joint filter: jointType")
		Dashboard.compoundsXMLSchema:register(XMLValueType.VECTOR_N, COMPOUND_XML_KEY .. "#selection")
		Dashboard.compoundsXMLSchema:register(XMLValueType.VECTOR_N, COMPOUND_XML_KEY .. "#selectionGroup")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#state", "state")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#stateText", "OBSOLETE: stateText")
		Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_XML_KEY .. "#trailer", "trailer number")
		Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_XML_KEY .. "#partition", "trailer partition")
		Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_XML_KEY .. "#page", "choosen page")
		Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_XML_KEY .. "#group", "choosen page group")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#option", "Option")
		Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_XML_KEY .. "#scale", "Minimap minimum scale factor")
		Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_XML_KEY .. "#factor", "Factor")
		Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_XML_KEY .. "#min", "Minimum")
		Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_XML_KEY .. "#max", "Maximum")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#cond", "condition command")
		Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_XML_KEY .. "#condValue", "condition value")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#baseColorDarkMode", "Base color for dark mode")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#emitColorDarkMode", "Emit color for dark mode")
		Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_XML_KEY .. "#intensityDarkMode", "Intensity for dark mode")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#audioFile", "Path to audio file")
		Dashboard.compoundsXMLSchema:register(XMLValueType.STRING, COMPOUND_XML_KEY .. "#audioName", "Unique name of sound sample")
		Dashboard.compoundsXMLSchema:register(XMLValueType.INT, COMPOUND_XML_KEY .. "#loop", "repeat sound n times")
		Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_XML_KEY .. "#volume", "sound volume")
		Dashboard.compoundsXMLSchema:register(XMLValueType.BOOL, COMPOUND_XML_KEY .. "#outside", "hearable from outside?")
		Dashboard.compoundsXMLSchema:register(XMLValueType.FLOAT, COMPOUND_XML_KEY .. "#distance", "hearable distance")
	end
	dbgprint("initSpecialization : DashboardLive compound options registered", 2)
end

function DashboardLive.registerEventListeners(vehicleType)
	SpecializationUtil.registerEventListener(vehicleType, "onLoad", DashboardLive)
	SpecializationUtil.registerEventListener(vehicleType, "onPreLoad", DashboardLive)
    SpecializationUtil.registerEventListener(vehicleType, "onPostLoad", DashboardLive)
    SpecializationUtil.registerEventListener(vehicleType, "onRegisterDashboardValueTypes", DashboardLive)
    SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", DashboardLive)
 	SpecializationUtil.registerEventListener(vehicleType, "onReadStream", DashboardLive)
	SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", DashboardLive)
	SpecializationUtil.registerEventListener(vehicleType, "onReadUpdateStream", DashboardLive)
	SpecializationUtil.registerEventListener(vehicleType, "onWriteUpdateStream", DashboardLive)
	SpecializationUtil.registerEventListener(vehicleType, "onUpdate", DashboardLive)
	SpecializationUtil.registerEventListener(vehicleType, "onDraw", DashboardLive)
	SpecializationUtil.registerEventListener(vehicleType, "onPostAttachImplement", DashboardLive)
end

function DashboardLive.registerOverwrittenFunctions(vehicleType)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadDashboardGroupFromXML", DashboardLive.loadDashboardGroupFromXML)
    SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsDashboardGroupActive", DashboardLive.getIsDashboardGroupActive)
end

function DashboardLive:onPreLoad(savegame)
	self.spec_DashboardLive = self["spec_"..DashboardLive.MOD_NAME..".DashboardLive"]
end

function DashboardLive:onLoad(savegame)
	local spec = self.spec_DashboardLive
	
	-- management data
	spec.dirtyFlag = self:getNextDirtyFlag()
	spec.maxPage = 1
	spec.actPageGroup = 1
	spec.maxPageGroup = 1
	spec.pageGroups = {}
	spec.pageGroups[1] = {}
	spec.pageGroups[1].pages = {}
	spec.pageGroups[1].pages[1] = true
	spec.pageGroups[1].actPage = 1
	spec.updateTimer = 0
	spec.compoundGroupsLoaded = false
		
	-- zoom data
	spec.zoomPerm = {}
	spec.fovLast = {}
	spec.zoomCount = 0
	
	--miniMap
	spec.zoomValue = 1
	spec.orientations = {"rotate", "north", "overview"}
	spec.orientation = "rotate"
	
	-- selector data
	spec.selectorActive = 0
	
	-- dark mode
	spec.darkModeExists = false
	spec.darkMode = false
	spec.darkModeLast = false
	spec.isDirty = false
	
	-- engine data
	spec.motorTemperature = 20
	spec.fanEnabled = false
	spec.fanEnabledLast = false
	spec.lastFuelUsage = 0
	spec.lastDefUsage = 0
	spec.lastAirUsage = 0
	
	-- discharge state
	spec.currentDischargeState = 0
	spec.lastDischargeState = 0
end

function DashboardLive:onRegisterDashboardValueTypes()
	dbgprint("onRegisterDashboardValueTypes : "..self:getName(), 2)
	
	local dblValueType 
	-- page
	dblValueType = DashboardValueType.new("dbl", "page")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLivePage)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesPage)
	self:registerDashboardValueType(dblValueType)
	
	-- base
	dblValueType = DashboardValueType.new("dbl", "base")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveBase)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesBase)
	self:registerDashboardValueType(dblValueType)
	
	-- miniMap
	dblValueType = DashboardValueType.new("dbl", "miniMap")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveMiniMap)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesMiniMap)
	self:registerDashboardValueType(dblValueType)
	
	-- combine
	dblValueType = DashboardValueType.new("dbl", "combine")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveCombine)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesCombine)
	self:registerDashboardValueType(dblValueType)
	
	-- rda
	dblValueType = DashboardValueType.new("dbl", "rda")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveRDA)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesRDA)
	self:registerDashboardValueType(dblValueType)
	
	-- vca
	dblValueType = DashboardValueType.new("dbl", "vca")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveVCA)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesVCA)
	self:registerDashboardValueType(dblValueType)
	
	-- cruiseControl
	dblValueType = DashboardValueType.new("dbl", "cc")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveCC)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesCC)
	self:registerDashboardValueType(dblValueType)
	
	-- hlm
	dblValueType = DashboardValueType.new("dbl", "hlm")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveHLM)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesHLM)
	self:registerDashboardValueType(dblValueType)
	
	-- gps
	dblValueType = DashboardValueType.new("dbl", "gps")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveGPS)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesGPS)
	self:registerDashboardValueType(dblValueType)
	
	-- gpsLane
	dblValueType = DashboardValueType.new("dbl", "gpsLane")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveGPSLane)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesGPSNumbers)
	self:registerDashboardValueType(dblValueType)
	
	-- gpsWidth
	dblValueType = DashboardValueType.new("dbl", "gpsWidth")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveGPSWidth)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesGPSNumbers)
	self:registerDashboardValueType(dblValueType)
	
	-- ps
	dblValueType = DashboardValueType.new("dbl", "ps")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLivePS)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesPS)
	self:registerDashboardValueType(dblValueType)

	-- selection
	dblValueType = DashboardValueType.new("dbl", "selection")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveSelection)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesSelection)
	self:registerDashboardValueType(dblValueType)

	-- baler
	dblValueType = DashboardValueType.new("dbl", "baler")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveBaler)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesBaler)
	self:registerDashboardValueType(dblValueType)
	
	-- lock steering axle by Ifko|nator
	dblValueType = DashboardValueType.new("dbl", "lockSteeringAxle")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveLSA)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesLSA)
	self:registerDashboardValueType(dblValueType)
	
	-- combineXP by yumi
	dblValueType = DashboardValueType.new("dbl", "combineXP")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveCXP)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesCXP)
	self:registerDashboardValueType(dblValueType)
	
	-- frontLoader
	dblValueType = DashboardValueType.new("dbl", "frontLoader")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveFrontloader)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesFrontloader)
	self:registerDashboardValueType(dblValueType)
	
	-- precision Farming
	dblValueType = DashboardValueType.new("dbl", "precfarming")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLivePrecisionFarming)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesPrecisionFarming)
	self:registerDashboardValueType(dblValueType)
	
	-- CVTaddon
	dblValueType = DashboardValueType.new("dbl", "cvt")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveCVT)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesCVT)
	self:registerDashboardValueType(dblValueType)
	
	-- Realistic Damage System (RDS)
	dblValueType = DashboardValueType.new("dbl", "rds")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLiveRDS)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesRDS)
	self:registerDashboardValueType(dblValueType)
	
	-- print
	dblValueType = DashboardValueType.new("dbl", "print")
	dblValueType:setXMLKey("vehicle.dashboard.dashboardLive")
	dblValueType:setValue(self, DashboardLive.getDashboardLivePrint)
	dblValueType:setAdditionalFunctions(DashboardLive.getDBLAttributesPrint)
	self:registerDashboardValueType(dblValueType)
end

function DashboardLive:onPostLoad(savegame)
    local spec = self.spec_DashboardLive
	dbgprint("onPostLoad : "..self:getName(), 2)

	-- Check if Mod GuidanceSteering exists
	spec.modGuidanceSteeringFound = self.spec_globalPositioningSystem ~= nil
	
	-- Check if Mod VCA exists
	spec.modVCAFound = self.vcaGetState ~= nil
	
	-- Check if Mod EV exists
	spec.modEVFound = FS25_EnhancedVehicle ~= nil and FS25_EnhancedVehicle.FS25_EnhancedVehicle ~= nil and FS25_EnhancedVehicle.FS25_EnhancedVehicle.onActionCall ~= nil
	
	-- Check if Mod SpeedControl exists
	spec.modSpeedControlFound = self.speedControl ~= nil 
	
	--Check if Mod HeadlandManagement exists
	spec.modHLMFound = self.spec_HeadlandManagement ~= nil
	
	-- solve mod conflict with CameraZoomExtension by Ifko: detect if mod exists in the game
	spec.CZEexists = self.spec_cameraZoomExtension ~= nil
	
	DashboardLive.createDashboardPages(self)
end

function DashboardLive.createDashboardPages(self)
	local spec = self.spec_DashboardLive
    local dashboard = self.spec_dashboard
    
    for _, group in pairs(dashboard.groups) do
    	if group.dblPage ~= nil then
			spec.maxPageGroup = math.max(spec.maxPageGroup, group.dblPageGroup)
    		spec.maxPage = math.max(spec.maxPage, group.dblPage)
    		if spec.pageGroups[group.dblPageGroup] == nil then 
    			spec.pageGroups[group.dblPageGroup] = {}
    		end
    		if spec.pageGroups[group.dblPageGroup].pages == nil then
    			spec.pageGroups[group.dblPageGroup].pages = {}
    		end
    		if spec.pageGroups[group.dblPageGroup].actPage == nil then
    			spec.pageGroups[group.dblPageGroup].actPage = 1
    		end
    		spec.pageGroups[group.dblPageGroup].pages[group.dblPage] = true
    		dbgprint("createDashboardPages : pages found in group "..group.name, 2)
    		dbgprint("createDashboardPages : maxPageGroup set to "..tostring(spec.maxPageGroup), 2)
    		dbgprint("createDashboardPages : maxPage set to "..tostring(spec.maxPage), 2)
    	else
    		dbgprint("createDashboardPages : no pages found in group "..group.name, 2)
    	end
    end
    if spec ~= nil then dbgprint_r(spec.pageGroups, 4, 3) end
end

-- Network stuff to synchronize engine data not synced by the game itself

function DashboardLive:onReadStream(streamId, connection)
	local spec = self.spec_DashboardLive
	spec.motorTemperature = streamReadFloat32(streamId)
	spec.fanEnabled = streamReadBool(streamId)
	spec.lastFuelUsage = streamReadFloat32(streamId)
	spec.lastDefUsage = streamReadFloat32(streamId)
	spec.lastAirUsage = streamReadFloat32(streamId)
end

function DashboardLive:onWriteStream(streamId, connection)
	local spec = self.spec_DashboardLive
	streamWriteFloat32(streamId, spec.motorTemperature)
	streamWriteBool(streamId, spec.fanEnabled)
	streamWriteFloat32(streamId, spec.lastFuelUsage)
	streamWriteFloat32(streamId, spec.lastDefUsage)
	streamWriteFloat32(streamId, spec.lastAirUsage)
end
	
function DashboardLive:onReadUpdateStream(streamId, timestamp, connection)
	if connection:getIsServer() then
		local spec = self.spec_DashboardLive
		if streamReadBool(streamId) then
			spec.motorTemperature = streamReadFloat32(streamId)
			spec.fanEnabled = streamReadBool(streamId)
			spec.lastFuelUsage = streamReadFloat32(streamId)
			spec.lastDefUsage = streamReadFloat32(streamId)
			spec.lastAirUsage = streamReadFloat32(streamId)
			spec.currentDischargeState = streamReadInt8(streamId)
		end
	end
end

function DashboardLive:onWriteUpdateStream(streamId, connection, dirtyMask)
	if not connection:getIsServer() then
		local spec = self.spec_DashboardLive
		if streamWriteBool(streamId, bitAND(dirtyMask, spec.dirtyFlag) ~= 0) then
			streamWriteFloat32(streamId, spec.motorTemperature)
			streamWriteBool(streamId, spec.fanEnabled)
			streamWriteFloat32(streamId, spec.lastFuelUsage)
			streamWriteFloat32(streamId, spec.lastDefUsage)
			streamWriteFloat32(streamId, spec.lastAirUsage)
			streamWriteInt8(streamId, spec.currentDischargeState)
			self.spec_motorized.motorTemperature.valueSend = spec.motorTemperature
		end
	end
end

-- inputBindings / inputActions
	
function DashboardLive:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
	dbgprint("onRegisterActionEvents", 4)
	if self.isClient then
		local spec = self.spec_DashboardLive
		spec.actionEvents = {} 
		if spec ~= nil then
			local actionEventId
			local sp = spec.maxPage > 1
			local sg = spec.maxPageGroup > 1
			if sg then
				-- Syntax: self:addActionEvent(actionEventsTable, inputAction, target, newCallback, triggerUp, triggerDown, triggerAlways, startActive, callbackState, customIconName, ignoreCollisions, reportAnyDeviceCollision)
				_, actionEventId = self:addActionEvent(spec.actionEvents, 'DBL_PAGEGRPUP', self, DashboardLive.CHANGEPAGE, false, true, false, true)
				g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL)
				g_inputBinding:setActionEventTextVisibility(actionEventId, sg)
				_, actionEventId = self:addActionEvent(spec.actionEvents, 'DBL_PAGEGRPDN', self, DashboardLive.CHANGEPAGE, false, true, false, true)
				g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL)
				g_inputBinding:setActionEventTextVisibility(actionEventId, sg)
			end
			if sp then
				_, actionEventId = self:addActionEvent(spec.actionEvents, 'DBL_PAGEUP', self, DashboardLive.CHANGEPAGE, false, true, false, true)
				g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL)
				g_inputBinding:setActionEventTextVisibility(actionEventId, sp)
				_, actionEventId = self:addActionEvent(spec.actionEvents, 'DBL_PAGEDN', self, DashboardLive.CHANGEPAGE, false, true, false, true)
				g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL)
				g_inputBinding:setActionEventTextVisibility(actionEventId, sp)
			end
		end	
		-- solve mod conflict with CameraZoomExtension by Ifko: disable temporary zoom of dbl
		if not spec.CZEexists then
			self:addActionEvent(spec.actionEvents, 'DBL_ZOOM', self, DashboardLive.ZOOM, true, true, false, true)	
		end
		self:addActionEvent(spec.actionEvents, 'DBL_ZOOM_PERM', self, DashboardLive.ZOOM, false, true, false, true)
		self:addActionEvent(spec.actionEvents, 'DBL_HUDVISIBILITY_FULL', self, DashboardLive.HUDVISIBILITY, false, true, false, true)
		self:addActionEvent(spec.actionEvents, 'DBL_HUDVISIBILITY_PART', self, DashboardLive.HUDVISIBILITY, false, true, false, true)
		self:addActionEvent(spec.actionEvents, 'DBL_MAPORIENTATION', self, DashboardLive.MAPORIENTATION, false, true, false, true)	
		self:addActionEvent(spec.actionEvents, 'DBL_RADIO_VOL_UP', self, DashboardLive.RADIO, false, true, false, true)
		self:addActionEvent(spec.actionEvents, 'DBL_RADIO_VOL_DOWN', self, DashboardLive.RADIO, false, true, false, true)
		if spec.darkModeExists then
			self:addActionEvent(spec.actionEvents, 'DBL_DARKMODE', self, DashboardLive.DARKMODE, false, true, false, true)		
		end
	end
end

function DashboardLive:CHANGEPAGE(actionName, keyStatus)
	dbgprint("CHANGEPAGE: "..tostring(actionName), 2)
	--	local spec = self.spec_DashboardLive doesn't work reliably with actions, Giants alone knows why...
	local spec = g_currentMission.hud.controlledVehicle.spec_DashboardLive
	if actionName == "DBL_PAGEGRPUP" then
		local pageGroupNum = spec.actPageGroup + 1
		while spec.pageGroups[pageGroupNum] == nil do
			pageGroupNum = pageGroupNum + 1
			if pageGroupNum > spec.maxPageGroup then pageGroupNum = 1 end
		end
		spec.actPageGroup = pageGroupNum
		dbgprint("CHANGEPAGE : NewPageGroup = "..tostring(spec.actPageGroup), 2)
	end
	if actionName == "DBL_PAGEGRPDN" then
		local pageGroupNum = spec.actPageGroup - 1
		while spec.pageGroups[pageGroupNum] == nil do
			pageGroupNum = pageGroupNum - 1
			if pageGroupNum < 1 then pageGroupNum = spec.maxPageGroup end
		end
		spec.actPageGroup = pageGroupNum
		dbgprint("CHANGEPAGE : NewPageGroup = "..tostring(spec.actPageGroup), 2)
	end
	if actionName == "DBL_PAGEUP" then
		local pageNum = spec.pageGroups[spec.actPageGroup].actPage + 1
		while not spec.pageGroups[spec.actPageGroup].pages[pageNum] do
			pageNum = pageNum + 1
			if pageNum > spec.maxPage then pageNum = 1 end
		end
		spec.pageGroups[spec.actPageGroup].actPage = pageNum
		dbgprint("CHANGEPAGE : NewPage = "..tostring(spec.pageGroups[spec.actPageGroup].actPage), 2)
	end
	if actionName == "DBL_PAGEDN" then
		local pageNum = spec.pageGroups[spec.actPageGroup].actPage - 1
		while not spec.pageGroups[spec.actPageGroup].pages[pageNum] do
			pageNum = pageNum - 1
			if pageNum < 1 then pageNum = spec.maxPage end
		end
		spec.pageGroups[spec.actPageGroup].actPage = pageNum
		dbgprint("CHANGEPAGE : NewPage = "..tostring(spec.pageGroups[spec.actPageGroup].actPage), 2)
	end
	spec.isDirty = true
end

function DashboardLive:MAPORIENTATION(actionName, keyStatus)
	dbgprint("MAPORIENTATION: "..tostring(actionName), 2)
	--	local spec = self.spec_DashboardLive doesn't work reliably with actions, Giants alone knows why...
	local spec = g_currentMission.hud.controlledVehicle.spec_DashboardLive
	local index = 1
	while spec.orientation ~= spec.orientations[index] do
		index = index + 1
		if spec.orientations[index] == nil then
			Logging.xmlFatal(vehicle.xmlFile, "Map orientation mismatch")
			break
		end
	end
	index = index + 1
	if spec.orientations[index] == nil then index = 1 end
	spec.orientation = spec.orientations[index]
	dbgprint("MAPORIENTATION: set to "..tostring(spec.orientation), 2)
end

function DashboardLive:ZOOM(actionName, keyStatus)
	dbgprint("ZOOM: "..tostring(actionName), 2)
	dbgprint("ZOOM: keyStatus = "..tostring(keyStatus), 2)	
--	local spec = self.spec_DashboardLive doesn't work reliably with actions, Giants alone knows why...
	local spec = g_currentMission.hud.controlledVehicle.spec_DashboardLive
	local actCamNode = g_cameraManager.activeCameraNode
	local zoomPressed = keyStatus == 1 and actionName == "DBL_ZOOM"
	
	if actionName == "DBL_ZOOM_PERM" then
		if spec.zoomPerm[actCamNode] == nil then 
			spec.zoomPerm[actCamNode] = false 
		end
		spec.zoomPerm[actCamNode] = not spec.zoomPerm[actCamNode]
		-- solve mod conflict with CameraZoomExtension by Ifko: disable Ifko's zoom while DBLs permanent zoom is active
		if spec.CZEexists then
			if spec.zoomPerm[actCamNode] then
				spec.zoomCount = spec.zoomCount + 1
				SpecializationUtil.removeEventListener(g_currentMission.hud.controlledVehicle, "onUpdate", FS25_cameraZoomExtension.CameraZoomExtension)
			else
				spec.zoomCount = spec.zoomCount - 1
				if spec.zoomCount == 0 then
					SpecializationUtil.registerEventListener(g_currentMission.hud.controlledVehicle, "onUpdate", FS25_cameraZoomExtension.CameraZoomExtension)
				end
			end
		end
	end
	
	-- zoom
	
	if zoomPressed or spec.zoomPerm[actCamNode] then
		dbgprint("ZOOM : Zooming in", 2)
		if spec.fovLast[actCamNode] == nil then
			spec.fovLast[actCamNode] = math.deg(getFovY(actCamNode))
			dbgprint("ZOOM : setting fovLast to "..tostring(spec.fovLast[actCamNode]), 2)
		end
		if spec.fovLast[actCamNode] ~= nil then
			setFovY(actCamNode, math.rad(20))
		end
				
	elseif not zoomPressed and not spec.zoomPerm[actCamNode] then
		dbgprint("ZOOM : Zooming out", 2)
		if spec.fovLast[actCamNode] == nil then 
			spec.fovLast[actCamNode] = 50 
			dbgprint("ZOOM :: ERROR: zoom is active, but fovLast not set! Falling back to 50", 1)
		end
		dbgprint("ZOOM : setting fov to "..tostring(spec.fovLast[actCamNode]), 2)
		setFovY(g_cameraManager.activeCameraNode, math.rad(spec.fovLast[actCamNode]))
		spec.fovLast[actCamNode] = nil
	end
end

function DashboardLive:HUDVISIBILITY(actionName, keyStatus)
	dbgprint("HUDVISIBILITY", 2)
	--	local spec = self.spec_DashboardLive doesn't work reliably with actions, Giants alone knows why...
	if actionName == "DBL_HUDVISIBILITY_PART" then
		DashboardLive.vis_partly =  g_currentMission.hud:getIsVisible()
		g_currentMission.hud:setIsVisible(not DashboardLive.vis_partly)
	elseif actionName == "DBL_HUDVISIBILITY_FULL" then
		DashboardLive.vis_partly = false
		g_currentMission.hud:consoleCommandToggleVisibility()
	end
end

function DashboardLive:DARKMODE(actionName, keyStatus)
	dbgprint("DARKMODE", 2)
	--	local spec = self.spec_DashboardLive doesn't work reliably with actions, Giants alone knows why...
	local spec = g_currentMission.hud.controlledVehicle.spec_DashboardLive
	if spec.darkMode == spec.darkModeLast then
		spec.darkMode = not spec.darkMode
	else
		dbgprint("Toggle Dark Mode: Skipped because last status was not synchronized completely", 1)
	end
	spec.isDirty = true
	dbgprint("DARKMODE: set to "..tostring(spec.darkMode), 2)
end

function DashboardLive:RADIO(actionName, keyStatus)
	dbgprint("RADIO", 2)
	local volume = g_gameSettings:getValue("radioVolume")
	if actionName == "DBL_RADIO_VOL_UP" then
		volume = volume + 0.1
		if volume > 0.9 then volume = 1 end
	elseif actionName == "DBL_RADIO_VOL_DOWN" then
		volume = volume - 0.1
		if volume < 0.1 then volume = 0 end
	end
	g_gameSettings:setValue("radioVolume", volume, true)
	g_soundMixer:setAudioGroupVolumeFactor(4, volume)
	dbgprint("RADIO: Volume set to "..tostring(volume), 1)
end
	
-- Main script
-- ===========

-- Debug stuff

function DashboardLive:onPostAttachImplement(implement, x, jointDescIndex)
	-- implement - attacherJoint
	dbgprint("Implement "..implement:getFullName().." attached to "..self:getFullName().." at index "..tostring(jointDescIndex), 2)
	if implement.getAllowsLowering ~= nil then
		dbgprint("Implement is lowerable: "..tostring(implement:getAllowsLowering()), 2)
	end
	if implement.spec_pickup ~= nil then
		dbgprint("Implement has pickup", 2)
	end
	--dbgprint_r(implement, 4, 0)
end

-- Supporting functions

local function trim(text, textLength, alignment)
	dbgprint("trim: text = "..tostring(text).." / alignment = "..tostring(alignment), 4)
	text = text or " "

	-- converter
	local ch_Ae = string.char(195, 132)
	local ch_Oe = string.char(195, 150)
	local ch_Ue = string.char(195, 156)
	local ch_ae = string.char(195, 164)
	local ch_oe = string.char(195, 182)
	local ch_ue = string.char(195, 188)
	text = string.gsub(text, ch_Ae, "Ae")
	text = string.gsub(text, ch_Oe, "Oe")
	text = string.gsub(text, ch_Ue, "Ue")
	text = string.gsub(text, ch_ae, "ae")
	text = string.gsub(text, ch_oe, "oe")
	text = string.gsub(text, ch_ue, "ue")
	
	dbgprint("trim: converted text = "..tostring(text), 4)
	
	local l = string.len(text)
	if l == textLength then
		return text
	elseif l < textLength then
		local diff = textLength - l
		local newText	 
		if alignment == RenderText.ALIGN_LEFT then
			newText = text..string.rep(" ", math.floor(diff))
		elseif alignment == RenderText.ALIGN_RIGHT then
			newText = string.rep(" ", math.floor(diff))..text
		else
			newText = string.rep(" ", math.floor(diff/2))..text..string.rep(" ", math.floor(diff/2))
		end
		if string.len(newText) < textLength then
			newText = newText .. " "
		end
		return newText
	elseif l > textLength then
		return string.sub(text, 1, textLength)
	end
end

local function lower(text)
	if text ~= nil then
		return string.lower(text)
	else
		return nil
	end
end

local function findSpecialization(device, specName, iteration, iterationStep)
	iterationStep = iterationStep or 0 -- initialization
	
	if (iteration == nil or iteration == iterationStep) and device ~= nil and device[specName] ~= nil then
		return device[specName], device
		
	elseif (iteration == nil or iterationStep < iteration) and device ~= nil and device.getAttachedImplements ~= nil then
		local implements = device:getAttachedImplements()
		for _,implement in pairs(implements) do
			local spec, device = findSpecialization(implement.object, specName, iteration, iterationStep + 1)
			if spec ~= nil then 
				return spec, device
			end
		end
	else 
		return nil, nil
	end
end

local function findLastSpecialization(device, specName, lastDeviceSpec, lastDevice)
	if device ~= nil and device[specName] ~= nil then
		lastDeviceSpec, lastDevice = device[specName], device
	end
	if device ~= nil and device.getAttachedImplements ~= nil then
		local implements = device:getAttachedImplements()
		for _,implement in pairs(implements) do
			lastDeviceSpec, lastDevice = findLastSpecialization(implement.object, specName, lastDeviceSpec, lastDevice)
		end
	end
	return lastDeviceSpec, lastDevice
end

local function findPTOStatus(device)
	if device ~= nil and device.getIsPowerTakeOffActive ~= nil and device:getIsPowerTakeOffActive() then
		return true
	elseif device.getAttachedImplements ~= nil then
		local implements = device:getAttachedImplements()
		for _,implement in pairs(implements) do
			local device = implement.object
			return findPTOStatus(device)
		end
		return false
	else 
		return false
	end
end

-- recursive search through all attached vehicles including rootVehicle
-- usage: call getIndexOfActiveImplement(rootVehicle)
local function getIndexOfActiveImplement(rootVehicle, level)
	
	local level = level or 1
	local returnVal = 0
	local returnSign = 1
	
	if rootVehicle ~= nil and not rootVehicle:getIsActiveForInput() and rootVehicle.spec_attacherJoints ~= nil and rootVehicle.spec_attacherJoints.attacherJoints ~= nil and rootVehicle.steeringAxleNode ~= nil then
	
		for _,impl in pairs(rootVehicle.spec_attacherJoints.attachedImplements) do
			
			-- called from rootVehicle
			if level == 1 then
				local jointDescIndex = impl.jointDescIndex
				local jointDesc = rootVehicle.spec_attacherJoints.attacherJoints[jointDescIndex]
				local wx, wy, wz = getWorldTranslation(jointDesc.jointTransform)
				local _, _, lz = worldToLocal(rootVehicle.steeringAxleNode, wx, wy, wz)
				if lz > 0 then 
					returnSign = 1
				else 
					returnSign = -1
				end 
			end
			
			if impl.object:getIsActiveForInput() then
				returnVal = level
			else
				returnVal = getIndexOfActiveImplement(impl.object, level+1)
			end
			-- found active implement? --> exit recursion
			if returnVal ~= 0 then break end
		
		end		
	end

	return returnVal * returnSign
end

local function getChoosenFillLevelState(device, ftPartition, ftType)
	local fillLevel = {abs = nil, pct = nil, max = nil, maxKg = nil, absKg = nil, pctKg = nil}
	local fillUnits = device:getFillUnits() or {}
	dbgprint("getChoosenFillLevelState: fillUnits = "..tostring(fillUnits), 4)
	
	for i = 1,#fillUnits do
		if ftPartition ~= 0 then i = ftPartition end
		local fillUnit = device:getFillUnitByIndex(i)
		dbgprint("getChoosenFillLevelState: fillUnit = "..tostring(fillUnit), 4)
		if fillUnit == nil then break end
		
		local ftIndex = device:getFillUnitFillType(i)
		local ftCategory = g_fillTypeManager.categoryNameToFillTypes[ftType]
		if ftType == "ALL" or ftIndex == g_fillTypeManager.nameToIndex[ftType] or ftCategory ~= nil and ftCategory[ftIndex] then
			if fillLevel.pct == nil then fillLevel.pct, fillLevel.abs, fillLevel.max, fillLevel.absKg = 0, 0, 0, 0 end
			-- we cannot just sum up percentages... 50% + 50% <> 100%
			--fillLevel.pct = fillLevel.pct + device:getFillUnitFillLevelPercentage(i)
			fillLevel.abs = fillLevel.abs + device:getFillUnitFillLevel(i)
			fillLevel.max = fillLevel.max + device:getFillUnitCapacity(i)
			-- so lets calculate it on our own.
			if fillLevel.max > 0 then
				fillLevel.pct = fillLevel.abs / fillLevel.max
			end
			local fillTypeDesc = g_fillTypeManager:getFillTypeByIndex(ftIndex)
			if fillTypeDesc ~= nil then
				fillLevel.absKg = fillLevel.absKg + device:getFillUnitFillLevel(i) * fillTypeDesc.massPerLiter * 1000
			else
				fillLevel.absKg = filllevel.absKg + device:getFillUnitFillLevel(i)
			end
		end
		if ftPartition ~= 0 then break end
	end
	return fillLevel
end

local function getChoosenAttacherState(device, ftType)
	local fillLevel = {abs = nil, pct = nil, max = nil, maxKg = nil, absKg = nil, pctKg = nil}
	local fillUnits = device.spec_dynamicMountAttacher.dynamicMountedObjects or {}
	dbgprint("getChoosenAttacherState: fillUnits = "..tostring(fillUnits), 4)
	
	for i,fillUnit in pairs(fillUnits) do
		dbgprint("getChoosenFillLevelState: fillUnit = "..tostring(fillUnit), 4)
		if fillUnit == nil then break end
		
		local ftIndex = fillUnit.fillType
		local ftCategory = g_fillTypeManager.categoryNameToFillTypes[ftType]
		if ftType == "ALL" or ftIndex == g_fillTypeManager.nameToIndex[ftType] or ftCategory ~= nil and ftCategory[ftIndex] then
			if fillLevel.pct == nil then fillLevel.pct, fillLevel.abs, fillLevel.max, fillLevel.absKg = 0, 0, 0, 0 end
			-- we cannot just sum up percentages... 50% + 50% <> 100%
			--fillLevel.pct = fillLevel.pct + device:getFillUnitFillLevelPercentage(i)
			fillLevel.abs = fillUnit.fillLevel ~= nil and fillLevel.abs + fillUnit.fillLevel or fillLevel.abs
			fillLevel.max = fillUnit.fillLevel ~= nil and fillLevel.max + fillUnit.fillLevel or fillLevel.max
			-- so lets calculate it on our own. (so we should not have a divide by zero problem...)
			fillLevel.pct = fillLevel.max ~= 0 and fillLevel.abs / fillLevel.max or 0
			local fillTypeDesc = g_fillTypeManager:getFillTypeByIndex(ftIndex)
			if fillTypeDesc ~= nil then
				fillLevel.absKg = fillUnit.fillLevel ~= nil and fillLevel.absKg + fillUnit.fillLevel * fillTypeDesc.massPerLiter * 1000 or fillLevel.absKg
			else
				fillLevel.absKg = fillUnit.fillLevel ~= nil and fillLevel.absKg + fillUnit.fillLevel or fillLevel.absKg
			end
		end
	end
	return fillLevel
end

local function recursiveTrailerSearch(vehicle, trailer, step)
	dbgprint("recursiveTrailerSearch", 4)
	local _, specVehicle = findSpecialization(vehicle, "spec_fillUnit", trailer)
	return specVehicle
end

-- returns fillLevel {pct, abs, max, absKg}
-- param vehicle - vehicle reference
-- param ftIndex - index of fillVolume: 1 - root/first trailer/implement, 2 - first/second trailer/implement, 3 - root/first and first/second trailer or implement
-- param ftType  - fillType
local function getFillLevelTable(vehicle, ftIndex, ftPartition, ftType)
	dbgprint("getFillLevelTable", 4)
	local fillLevelTable = {abs = nil, pct = nil, max = nil, maxKg = nil, absKg = nil, pctKg = nil}
	
	if ftType == nil then ftType = "ALL" end
	
	if ftType ~= "ALL" and g_fillTypeManager.nameToIndex[ftType] == nil and g_fillTypeManager.nameToCategoryIndex[ftType] == nil then
		Logging.xmlWarning(vehicle.xmlFile, "Given fillType "..tostring(ftType).." not known!")
		return fillLevelTable
	end
	
	local _, device = findSpecialization(vehicle, "spec_fillUnit", ftIndex)
	if device ~= nil then
		fillLevelTable = getChoosenFillLevelState(device, ftPartition, ftType)
	else
		_, device = findSpecialization(vehicle, "spec_dynamicMountAttacher", ftIndex)
		if device ~= nil then 
			fillLevelTable = getChoosenAttacherState(device, ftType)
		end
	end
	return fillLevelTable
end

local function recursiveCheck(implement, checkFunc, search, getCheckedImplement, iteration, iterationStep)
	if implement.object == nil then return false end
	
	if type(search)=="number" then 
		iteration = search
		search = false
	elseif iteration == nil then
		search = true
	end
	
	iterationStep = iterationStep or 0 -- only implements here, so we start at 0
	dbgprint("recursiveCheck : iteration: "..tostring(iteration), 4)
	
	local checkResult = false
	local checkFuncOrig = checkFunc
	if type(checkFunc)=="string" then
		checkFunc = implement.object[checkFunc]
	end
	if search or iterationStep == iteration then checkResult = checkFunc ~= nil and checkFunc(implement.object, false) end
	local returnImplement = checkResult and implement or nil
	
	if not checkResult and implement.object.spec_attacherJoints ~= nil and (search or iterationStep < iteration) and (iteration == nil or iterationStep < iteration) then
		local attachedImplements = implement.object.spec_attacherJoints.attachedImplements
		if attachedImplements ~= nil and attachedImplements[1]~=nil then 
			checkResult, returnImplement = recursiveCheck(attachedImplements[1], checkFuncOrig, search, getCheckedImplement, iteration, iterationStep + 1)
		end
	end
	if getCheckedImplement then
		return checkResult, returnImplement
	else
		return checkResult
	end
end

local function getIsFoldable(device)
	local spec = device.spec_foldable
	return spec ~= nil and spec.foldingParts ~= nil and #spec.foldingParts > 0
end

-- from ExtendedSprayerHUDExtension - can be accessed directly?
local function getFillTypeSourceVehicle(sprayer)
    -- check the valid sprayer if he has a fill type source to consume from, otherwise hide the display
    if sprayer:getFillUnitFillLevel(sprayer:getSprayerFillUnitIndex()) <= 0 then
        local spec = sprayer.spec_sprayer
        for _, supportedSprayType in ipairs(spec.supportedSprayTypes) do
            for _, src in ipairs(spec.fillTypeSources[supportedSprayType]) do
                local vehicle = src.vehicle
                if vehicle:getFillUnitFillType(src.fillUnitIndex) == supportedSprayType and vehicle:getFillUnitFillLevel(src.fillUnitIndex) > 0 then
                    return vehicle, src.fillUnitIndex
                end
            end
        end
    end

    return sprayer, sprayer:getSprayerFillUnitIndex()
end

local function jointsToTable(jointsRaw)
	local joints 
	if type(jointsRaw) == "number" then
		joints = {}
		joints[1] = jointsRaw
	elseif type(jointsRaw) == "table" then
		joints = jointsRaw
	elseif type(jointsRaw) == "string" then
		joints = string.split(jointsRaw, " ")
	end
	return joints
end

-- handle nearly all about attachers and attached vehicles
local function getAttachedStatus(vehicle, element, mode, default)
	if element.dblAttacherJointIndices == nil then
		if element.attacherJointIndices ~= nil then
			element.dblAttacherJointIndices = element.attacherJointIndices
		else
			--dbgprint("getAttachedStatus: No attacherJointIndex given for DashboardLive attacher command "..tostring(mode), 4)
			element.dblAttacherJointIndices = {}
		end
	end
	
	local resultValue
	local result = default or false
	local noImplement = true
	local jointExists = false
	
	local joints 
	if type(element.dblAttacherJointIndices) == "number" then
		joints = {}
		joints[1] = element.dblAttacherJointIndices
	elseif type(element.dblAttacherJointIndices) == "table" then
		joints = element.dblAttacherJointIndices
	else
		joints = string.split(element.dblAttacherJointIndices, " ")
	end
	
	local andMode = element.dblOption ~= nil and string.find(string.lower(element.dblOption), "all") ~= nil
	local orMode = element.dblOption ~= nil and string.find(string.lower(element.dblOption), "any") ~= nil
	local firstRun = true
	
	local t = element.dblTrailer
    if t ~= nil then 
    	t = t - 1
    	if t < 0 then t = 0 end
    end
	
    for _, jointIndex in ipairs(joints) do
    	dbgprint("jointIndex: "..tostring(tonumber(jointIndex)), 4)
    	local implement
    	if tonumber(jointIndex) == 0 then
    		implement = {}
    		implement.object = vehicle
    	elseif jointIndex == "S" then
    		implement = {}
    		implement.object = vehicle:getSelectedVehicle()
    	else
    		implement = vehicle:getImplementFromAttacherJointIndex(tonumber(jointIndex)) 
    	end
    	jointExists = vehicle:getAttacherJointByJointDescIndex(tonumber(jointIndex)) ~= nil
    	dbgprint("mode: "..tostring(mode).." / jointExists: "..tostring(jointExists).." / implement: "..tostring(implement), 4)
    	--dbgprint_r(implement, 4, 1)
    	
    	if implement ~= nil then
    		-- detect and skip dolly or hookLiftTrailer
    		if t ~= nil then
				if implement.object ~= nil and
					(
						implement.object.typeName == "dolly"
					or	implement.object.typeName == "hookLiftTrailer"
					) 
				then
					t = t + 1
				end
			end
			    		
    		if mode == "hasspec" then
				resultValue = false
				local options = element.dblOption
				local option = string.split(options, " ")
				for _, c in ipairs(option) do
					local spec = findSpecialization(implement.object, c, t)
					resultValue = resultValue or spec ~= nil
				end
				dbgprint(implement.object:getFullName().." hasSpec "..tostring(options)..": "..tostring(resultValue), 4)
				
			elseif mode == "hastypedesc" then
				resultValue = false
				local _, vehicle = findSpecialization(implement.object, "spec_attachable", t)
				local options = element.dblOption
				if vehicle ~= nil and options ~= nil then
					local option = string.split(options, " ")
					for _, c in ipairs(option) do
						local typeDesc = vehicle.typeDesc or ""
						local typeDescI18n = g_i18n.texts["typeDesc_"..c]
						resultValue = resultValue or typeDesc == typeDescI18n
					end
				end
				dbgprint(implement.object:getFullName().." hasTypeDesc "..tostring(options)..": "..tostring(resultValue), 4)
				
            elseif mode == "lifted" then
            	resultValue = not recursiveCheck(implement, "getIsLowered", true, false, t)
            	dbgprint(implement.object:getFullName().." lifted: "..tostring(resultValue), 4)
            	
            elseif mode == "lowered" then
            	resultValue = recursiveCheck(implement, "getIsLowered", true, false, t)
            	dbgprint(implement.object:getFullName().." lowered: "..tostring(resultValue), 4)
            	
            elseif mode == "lowerable" then
				resultValue = recursiveCheck(implement, "getAllowsLowering", true, false, t)
				dbgprint(implement.object:getFullName().." lowerable: "..tostring(resultValue), 4)
				
			elseif mode == "lowering" or mode == "lifting" then
				if vehicle.spec_attacherJoints ~= nil and vehicle.spec_attacherJoints.attacherJoints ~= nil then
					local joint = vehicle.spec_attacherJoints.attacherJoints[tonumber(jointIndex)]
					
					local animationRunning = false
					local loweringRange = true
					local spec = findSpecialization(implement.object, "spec_foldable", t)
					if spec ~= nil and spec.foldMiddleAnimTime ~= nil and (spec.foldAnimTime <= (spec.turnOnFoldMinLimit or 0) or spec.foldAnimTime >= (spec.turnOnFoldMaxLimit or 1)) then 
            			loweringRange = false
            		end
					
					if mode == "lowering" then 
						resultValue = joint ~= nil and joint.isMoving and joint.moveDown and loweringRange
					else
						resultValue = joint ~= nil and joint.isMoving and not joint.moveDown and loweringRange
					end
				else
					resultValue = false
				end
			
			elseif mode == "pto" then
				resultValue = findPTOStatus(implement.object)
				
			elseif mode == "ptorpm" and vehicle.spec_motorized ~= nil and vehicle.spec_motorized.motor ~= nil then
				if findPTOStatus(implement.object) then
						resultValue = vehicle.spec_motorized.motor:getLastModulatedMotorRpm()
				else
					resultValue = 0
				end
				
            elseif mode == "foldable" then
            	local foldable = recursiveCheck(implement, getIsFoldable, t == nil, false, t) --isFoldable(implement, true)
				resultValue = foldable or false
				dbgprint(implement.object:getFullName().." foldable: "..tostring(resultValue), 4)
				
			elseif mode == "folded" then
				local foldable, foldableImplement = recursiveCheck(implement, getIsFoldable, t == nil, true, t) --isFoldable(implement, true, true, t)
				--local implement = subImplement or implement
				resultValue = foldable and foldableImplement.object.getIsUnfolded ~= nil and not foldableImplement.object:getIsUnfolded() and (foldableImplement.object.spec_foldable.foldAnimTime == 1 or foldableImplement.object.spec_foldable.foldAnimTime == 0) or false
            	dbgprint(implement.object:getFullName().." folded: "..tostring(resultValue), 4)
            	
            elseif mode == "unfolded" then
            	local foldable, foldableImplement = recursiveCheck(implement, getIsFoldable, t == nil, true, t) --isFoldable(implement, true, true, t)
            	resultValue = foldable and foldableImplement.object.getIsUnfolded ~= nil and foldableImplement.object:getIsUnfolded() or false
            	dbgprint(implement.object:getFullName().." unfolded: "..tostring(resultValue), 4)
            	
            elseif mode == "unfolding" or mode == "folding" then
            	local foldable, foldableImplement = recursiveCheck(implement, getIsFoldable, t == nil, true, t) --isFoldable(implement, true, true, t)
            	if not foldable then 
            		resultValue = false
            	else
					local spec = foldableImplement ~= nil and foldableImplement.object ~= nil and foldableImplement.object.spec_foldable or nil
					local unfolded = foldableImplement ~= nil and foldableImplement.object ~= nil and foldableImplement.object.getIsUnfolded ~= nil and foldableImplement.object:getIsUnfolded()
					if mode == "folding" then
						resultValue = spec ~= nil and not unfolded and spec.foldMoveDirection == 1 and spec.foldAnimTime > 0 and spec.foldAnimTime < 1
					else
						resultValue = spec ~= nil and not unfolded and spec.foldMoveDirection == -1 and spec.foldAnimTime > 0 and spec.foldAnimTime < 1
					end
					dbgprint(implement.object:getFullName().." "..mode..": "..tostring(resultValue), 4)
				end
				
            elseif mode == "unfoldingstate" then
            	local spec = findSpecialization(implement.object, "spec_foldable", t)
            	local foldable = recursiveCheck(implement, getIsFoldable, t == nil, false, t) --isFoldable(implement, true, false, t)
            	if foldable and spec.foldAnimTime >= 0 and spec.foldAnimTime <= 1 then 
            		resultValue = 1 - spec.foldAnimTime
            	else
            		resultValue = 0
            	end
               	dbgprint(implement.object:getFullName().." unfoldingState: "..tostring(resultValue), 4)
             
            elseif mode == "foldingstate" then
            	local spec = findSpecialization(implement.object, "spec_foldable", t)
            	local foldable = recursiveCheck(implement, getIsFoldable, t == nil, false, t) --isFoldable(implement, true, false, t)
            	if foldable and spec.foldAnimTime >= 0 and spec.foldAnimTime <= 1 then 
            		resultValue = spec.foldAnimTime
            	else
            		resultValue = 0
            	end
               	dbgprint(implement.object:getFullName().." foldingState: "..tostring(resultValue), 4)
               	  	
            elseif mode == "tipping" then
            	local specTR = findSpecialization(implement.object, "spec_trailer", t)
            	resultValue = specTR ~= nil and specTR:getTipState() > 0
            	
            elseif mode == "tippingstate" then
            	local _, specImplement = findSpecialization(implement.object, "spec_trailer", t)

            	if specImplement ~= nil and specImplement.spec_trailer:getTipState() > 0 then
            		local specTR = specImplement.spec_trailer
            		local tipSide = specTR.tipSides[specTR.currentTipSideIndex]
            		resultValue = tipSide ~= nil and specImplement:getAnimationTime(tipSide.animation.name) or 0
            	else
            		resultValue = 0
            	end
            	dbgprint(implement.object:getFullName().." tippingState (trailer "..tostring(t).."): "..tostring(resultValue), 4)
            	
			elseif mode == "tipside" or mode == "tipsidetext" then
				local s = element.dblStateText or element.dblState
				local specTR = findSpecialization(implement.object, "spec_trailer", t)     
				if specTR ~= nil then
					local trailerTipSide = specTR.preferedTipSideIndex or 0
					local trailerTipSideName = specTR.tipSides ~= nil and specTR.tipSides[trailerTipSide] ~= nil and specTR.tipSides[trailerTipSide].name or " "    	
					if mode == "tipside" and s ~= nil then 
						local fullState = "info_tipSide"..tostring(s)
						local fullStateName = g_i18n.texts[fullState]
						resultValue = fullStateName == trailerTipSideName
						dbgprint("tipSide found for trailer: "..tostring(t).." / tipSide: "..tostring(trailerStateName), 4) 
					elseif mode == "tipsidetext" then
						local len = string.len(element.textMask or "00.0")
						local alignment = element.textAlignment or RenderText.ALIGN_RIGHT
						resultValue = trim(trailerTipSideName, len, alignment)
						dbgprint("tipSideText found for trailer: "..tostring(t).." / tipSide: "..tostring(returnValue), 4) 
					end
				else 
					dbgprint(tostring(mode).." not found for trailer: "..tostring(t), 4)
					if mode == "tipsidetext" then
						resultValue = ""
					else
						resultValue = false
					end
				end
            
            elseif mode == "ridgemarker" then
            	local specRM = findSpecialization(implement.object, "spec_ridgeMarker")
            	resultValue = specRM ~= nil and specRM.ridgeMarkerState or 0
            
            elseif mode == "filllevel" then
            	local o, p = lower(element.dblOption), element.dblPartition
				local maxValue, pctValue, absValue, maxKGValue,absKGValue, pctKGValue
				local fillLevel = getFillLevelTable(implement.object, t, p)
				--dbgprint_r(fillLevel, 4, 2)
				
				if fillLevel.abs == nil then 
					maxValue, absValue, pctValue, absKGValue = 0, 0, 0, 0
				else
					maxValue, absValue, pctValue, absKGValue = fillLevel.max, fillLevel.abs, fillLevel.pct, fillLevel.absKg
				end
				if fillLevel.maxKg == math.huge then -- in case the trailer does not define a fill limit
					pctKGValue, maxKGValue = 0, 0
				else
					pctKGValue, maxKGValue = fillLevel.pctKg, fillLevel.maxKg
				end
				
				if o == "percent" then
					element.valueFactor = 100
					resultValue = pctValue
				elseif o == "max" then
					--element.valueFactor = 1
					resultValue = maxValue
				elseif o == "abskg" then
					resultValue = absKGValue
				elseif o == "percentkg" then
					element.valueFactor = 100
					resultValue = pctKGValue
				elseif o == "maxkg" then
					resultValue = maxKGValue
				else
					--element.valueFactor = 1
					resultValue = absValue
				end
				
			elseif mode == "balesize" or mode == "isroundbale" then
				local specBaler = findSpecialization(implement.object,"spec_baler")
				local options = lower(element.dblOption)
				if options == nil then options = "selected" end
				local baleTypeDef  
				if specBaler ~= nil and specBaler.currentBaleTypeIndex ~= nil and options == "current" then
					baleTypeDef = specBaler.baleTypes[specBaler.currentBaleTypeIndex]
				elseif specBaler ~= nil and specBaler.preSelectedBaleTypeIndex ~= nil and options == "selected" then
					baleTypeDef = specBaler.baleTypes[specBaler.preSelectedBaleTypeIndex]
				end
				if baleTypeDef ~= nil then
					if mode == "isroundbale" then 
						dbgprint("DBL isRoundBale: " .. tostring(baleTypeDef.isRoundBale),4)
						resultValue = baleTypeDef.isRoundBale
					elseif baleTypeDef.isRoundBale then
						dbgprint("DBL baleSize roundBale: " .. tostring(baleTypeDef.diameter) .. "("..options..")",4)
						resultValue = baleTypeDef.diameter * 100
					else
						dbgprint("DBL baleSize squareBale: " .. tostring(baleTypeDef.length) .. "("..options..")",4)
						resultValue = baleTypeDef.length * 100
					end
				end
			elseif mode == "balecountanz" or mode == "balecounttotal" then
				local specBaleCounter = findSpecialization(implement.object,"spec_baleCounter")	
				resultValue = 0
				if specBaleCounter ~= nil then 
					if mode == "balecountanz" then
						resultValue = specBaleCounter.sessionCounter
						dbgprint(implement.object:getFullName().." baleCountAnz: "..tostring(resultValue), 4)	
					else
						resultValue = specBaleCounter.lifetimeCounter
						dbgprint(implement.object:getFullName().." baleCountTotal: "..tostring(resultValue), 4)
					end
				end
--[[
			elseif mode == "wrappedbalecountanz" or mode == "wrappedbalecounttotal" then
				local specBaleCounter = findSpecialization(implement.object,"spec_wrappedBaleCounter")	
				resultValue = 0
				if specBaleCounter ~= nil then 
					if mode == "wrappedbalecountanz" then
						resultValue = specBaleCounter.sessionCounter
						dbgprint(implement.object:getFullName().." wrappedBaleCountAnz: "..tostring(resultValue), 4)	
					else
						resultValue = specBaleCounter.lifetimeCounter
						dbgprint(implement.object:getFullName().." wrappedBaleCountTotal: "..tostring(resultValue), 4)
					end
				end
--]]
			elseif mode == "locksteeringaxle" then --lockSteeringAxles by Ifko|nator, www.lsfarming-mods.com
				local c = element.dblCommand
				local specLSA = findSpecialization(implement.object, "spec_lockSteeringAxles", t)
				if specLSA ~= nil and c == "found" then
					resultValue = specLSA.foundSteeringAxle
				elseif specLSA ~= nil and c == "locked" then
					resultValue = specLSA.lockSteeringAxle
				else
					resultValue = false
				end
				dbgprint(implement.object:getFullName().." : lockSteeringAxles ("..tostring(c).."), trailer "..tostring(t)..": "..tostring(resultValue), 4)
			
			-- frontloader
			elseif mode == "toolrotation" or mode=="istoolrotation" then
				local factor = element.dblFactor or 1
				local specCyl = findSpecialization(implement.object, "spec_cylindered",t)
				local s = element.dblStateText or element.dblState
				dbgprint(implement.object:getFullName().." : frontLoader - " .. mode .. " - " .. s,3)
				resultValue = 0
				if specCyl ~= nil then
					for toolIndex, tool in ipairs(specCyl.movingTools) do
						if toolIndex == tonumber(element.dblOption) then
							local origin = tool.rotMax or 0
							local originDeg = math.deg(origin) * -1
							local rot = math.deg(tool.curRot[tool.rotationAxis]) * factor * -1 -- - originDeg
							if s=="origin" then rot = rot - originDeg end
							if element.dblCommand == "toolrotation" then
								resultValue = rot
							elseif element.dblCommand == "istoolrotation" then
								if element.dblMin ~= nil and element.dblMax ~= nil then
									resultValue = rot >= element.dblMin and rot <=element.dblMax
								else
									print("Warning: valueType=\"base\" cmd=\"isToolRotation\": Missing value for min or max")
									resultValue = false
								end
							end
						end
					end
				end
			elseif mode == "tooltranslation" or mode=="istooltranslation" then
				local factor = element.dblFactor or 1
				local specCyl = findSpecialization(implement.object, "spec_cylindered",t)
				local s = element.dblStateText or element.dblState
				dbgprint(implement.object:getFullName().." : frontLoader - " .. mode .. " - " .. s,3)
				resultValue = 0
				if specCyl ~= nil then
					for toolIndex, tool in ipairs(specCyl.movingTools) do
						if toolIndex == tonumber(element.dblOption) then
							local origin = tool.transMax or 0
							local trans = tool.curTrans[tool.translationAxis] * factor
							if element.dblCommand == "tooltranslation" then
								resultValue = trans
							elseif element.dblCommand == "istooltranslation" then
								if element.dblMin ~= nil and element.dblMax ~= nil then
									resultValue = trans >= element.dblMin and trans <=element.dblMax
								else
									print("Warning: valueType=\"base\" cmd=\"istooltranslation\": Missing value for min or max")
									resultValue = false
								end
							end
						end
					end
				end
	
			elseif mode=="swathstate" then
				local specWM =  findSpecialization(implement.object, "spec_workMode",t)
				local states
				local s = element.dblStateText or element.dblState
				if type(s) == "number" then
					states = {}
					states[1] = s
				elseif type(s) == "table" then
					states = s
				else
					states = string.split(s, " ")
				end
				resultValue = false
				for _, state in ipairs(states) do
					if tonumber(state) ~= nil and specWM ~= nil then
						resultValue = resultValue or specWM.state == tonumber(state)
					end
				end
			elseif mode=="mpconditioner" then
				resultValue = false
				local specMPConverter = findSpecialization(implement.object, "spec_mower",t)
				if specMPConverter ~= nil and specMPConverter.currentConverter ~= nil then
					resultValue = specMPConverter.currentConverter == "MOWERCONDITIONER"
				end
			elseif mode=="seedtype" then
				resultValue = ""
				local specS = findSpecialization(implement.object, "spec_sowingMachine")
				if specS ~= nil then
					local fillType = g_fruitTypeManager:getFillTypeByFruitTypeIndex(specS.seeds[specS.currentSeed])
					local len = string.len(element.textMask or "xxxx")
					local alignment = element.textAlignment
					resultValue = trim(fillType.title, len, alignment)
				end
				
			elseif mode == "coveropen" then
				local coverSpec = findSpecialization(implement.object, "spec_cover", t)
				resultValue = coverSpec ~= nil and coverSpec.state > 0 or false
			
            elseif mode == "connected" then
            	resultValue = true
            	if tonumber(t) ~= nil and t > 0 then
            		if implement.object ~= nil and implement.object.spec_attacherJoints ~= nil then
            			if implement.object.spec_attacherJoints.attachedImplements ~= nil and #implement.object.spec_attacherJoints.attachedImplements < 1 then
            				resultValue = false
            			end
            		end
            		dbgprint("AttacherJoint #"..tostring(jointIndex).."(trailer = "..tostring(t+1)..") connected: "..tostring(resultValue), 4)
            	end
              	dbgprint("AttacherJoint #"..tostring(jointIndex).."connected: "..tostring(resultValue), 4)
            	
            elseif mode == "disconnected" then
            	dbgprint("AttacherJoint #"..tostring(jointIndex).." not disconnected", 4)
            	noImplement = false
            end
            
            if andMode and not firstRun then
            	result = resultValue and result
            elseif orMode then
            	result = resultValue or result
            else 
            	result = resultValue
            	firstRun = false
            end
        end
        dbgprint("result / noImplement: "..tostring(result).." / "..tostring(noImplement), 4)
    end
    if mode == "disconnected" then
        dbgprint("Disconnected!", 4)
        return noImplement and jointExists
    end
    dbgprint("returnValue: "..tostring(result), 4)
    return result
end

-- Append schema definitions to registerDashboardXMLPath function 
function DashboardLive.addDarkModeToRegisterDashboardXMLPaths(schema, basePath, availableValueTypes)
	dbgprint("addDarkModeToRegisterDashboardXMLPaths : registerDashboardXMLPaths appended to "..basePath, 2)
	schema:register(XMLValueType.STRING, basePath .. ".dashboard(?)#baseColorDarkMode", "Base color for dark mode")
	schema:register(XMLValueType.STRING, basePath .. ".dashboard(?)#emitColorDarkMode", "Emit color for dark mode")
	schema:register(XMLValueType.FLOAT, basePath .. ".dashboard(?)#intensityDarkMode", "Intensity for dark mode")
	schema:register(XMLValueType.STRING, basePath .. ".dashboard(?)#textColorDarkMode", "Text color for dark mode")
	schema:register(XMLValueType.STRING, basePath .. ".dashboard(?)#hiddenColorDarkMode", "Hidden color for dark mode")
	schema:register(XMLValueType.STRING, basePath .. ".dashboard(?)#numberColorDarkMode", "Number color for dark mode")
end
Dashboard.registerDashboardXMLPaths = Utils.appendedFunction(Dashboard.registerDashboardXMLPaths, DashboardLive.addDarkModeToRegisterDashboardXMLPaths)

-- Overwritten function loadEmitterDashboardFromXML to enable dark mode setting
function DashboardLive.addDarkModeToLoadEmitterDashboardFromXML(self, superfunc, xmlFile, key, dashboard, ...)
	local returnValue = superfunc(self, xmlFile, key, dashboard, ...)
	local specDBL = self.spec_DashboardLive
	local spec = self.spec_dashboard

	-- Back up light mode values
	dashboard.baseColorLM = dashboard.baseColor
	dashboard.emitColorLM = dashboard.emitColor
	dashboard.intensityLM = dashboard.intensity
	-- Read dark mode values
	dashboard.baseColorDM = Dashboard.getDashboardColor(xmlFile, xmlFile:getValue(key .. "#baseColorDarkMode"))
	dashboard.emitColorDM = Dashboard.getDashboardColor(xmlFile, xmlFile:getValue(key .. "#emitColorDarkMode"))
	dashboard.intensityDM = xmlFile:getValue(key .. "#intensityDarkMode")

	if dashboard.baseColorDM ~= nil or dashboard.emitColorDM ~= nil or dashboard.intensityDM ~= nil then
		dbgprint("loadEmitterDashboardFromXML : Setting dark mode for "..self:getName(), 2)
		dbgprint("loadEmitterDashboardFromXML : key = "..tostring(key), 2)
		dbgprint("loadEmitterDashboardFromXML : dashboard = "..tostring(dashboard), 2)
		specDBL.darkModeExists = "true"
	end	
	return returnValue
end
Dashboard.TYPE_DATA[Dashboard.TYPES.EMITTER].loadFunc = Utils.overwrittenFunction(Dashboard.TYPE_DATA[Dashboard.TYPES.EMITTER].loadFunc, DashboardLive.addDarkModeToLoadEmitterDashboardFromXML)

-- Overwritten function loadTextDashboardFromXML to enable dark mode setting
function DashboardLive:addDarkModeToLoadTextDashboardFromXML(superfunc, xmlFile, key, dashboard, ...)
	local returnValue = superfunc(self, xmlFile, key, dashboard, ...)
	local spec = self.spec_DashboardLive

	-- solve mod conflict with realdashboard mod
	if string.find(key, "vehicle.realDashboard.dashboards") == nil then
	
		-- Back up light mode values
		dashboard.textColorLM = dashboard.textColor
		dashboard.hiddenColorDM = dashboard.hiddenColor
		-- Read dark mode values
		dashboard.textColorDM = Dashboard.getDashboardColor(xmlFile, xmlFile:getValue(key .. "#textColorDarkMode"))
		dashboard.hiddenColorDM = Dashboard.getDashboardColor(xmlFile, xmlFile:getValue(key .. "#hiddenColorDarkMode"))
	
		if dashboard.textColorDM ~= nil or dashboard.hiddenColorDM ~= nil then
			dbgprint("loadTextDashboardFromXML : Setting dark mode for "..self:getName(), 2)
			spec.darkModeExists = "true"
		end
	end
	
	return returnValue
end
Dashboard.TYPE_DATA[Dashboard.TYPES.TEXT].loadFunc = Utils.overwrittenFunction(Dashboard.TYPE_DATA[Dashboard.TYPES.TEXT].loadFunc, DashboardLive.addDarkModeToLoadTextDashboardFromXML)

-- Overwritten function loadNumberDashboardFromXML to enable dark mode setting
function DashboardLive:addDarkModeToLoadNumberDashboardFromXML(superfunc, xmlFile, key, dashboard, ...)
	local returnValue = superfunc(self, xmlFile, key, dashboard, ...)
	local spec = self.spec_DashboardLive
	
	-- Back up light mode values
	dashboard.numberColorLM = dashboard.numberColor
	-- Read dark mode values
	dashboard.numberColorDM = Dashboard.getDashboardColor(xmlFile, xmlFile:getValue(key .. "#numberColorDarkMode"))
	
	if dashboard.numberColorDM ~= nil then
		dbgprint("loadNumberDashboardFromXML : Setting dark mode for "..self:getName(), 2)
		spec.darkModeExists = "true"
	end
	
	return returnValue
end
Dashboard.TYPE_DATA[Dashboard.TYPES.NUMBER].loadFunc = Utils.overwrittenFunction(Dashboard.TYPE_DATA[Dashboard.TYPES.NUMBER].loadFunc, DashboardLive.addDarkModeToLoadNumberDashboardFromXML)

-- Prepended function defaultEmitterDashboardStateFunc to enable dark mode
function DashboardLive:addDarkModeToDefaultEmitterDashboardStateFunc(dashboard, newValue, minValue, maxValue, isActive)
	local spec = self.spec_DashboardLive
	if spec ~= nil and spec.darkMode ~= spec.darkModeLast then
		if spec.darkMode then
			dbgprint("switching to dark mode: "..tostring(self:getName()), 2)
			if dashboard.baseColorDM ~= nil then dashboard.baseColor = dashboard.baseColorDM end
			if dashboard.emitColorDM ~= nil then dashboard.emitColor = dashboard.emitColorDM end
			if dashboard.intensityDM ~= nil then dashboard.intensity = dashboard.intensityDM end
		else	
			dbgprint("switching to light mode: "..tostring(self:getName()), 2)
			if dashboard.baseColorLM ~= nil then dashboard.baseColor = dashboard.baseColorLM end
			if dashboard.emitColorLM ~= nil then dashboard.emitColor = dashboard.emitColorLM end
			if dashboard.intensityLM ~= nil then dashboard.intensity = dashboard.intensityLM end
		end
		local intensity = dashboard.intensity or 1
		if dashboard.baseColor ~= nil then 
			setShaderParameter(dashboard.node, "baseColor", dashboard.baseColor[1], dashboard.baseColor[2], dashboard.baseColor[3], intensity, false)
		end
		if dashboard.emitColor ~= nil then
			setShaderParameter(dashboard.node, "emitColor", dashboard.emitColor[1], dashboard.emitColor[2], dashboard.emitColor[3], intensity, false)
		end
	end
end
Dashboard.TYPE_DATA[Dashboard.TYPES.EMITTER].updateFunc = Utils.prependedFunction(Dashboard.TYPE_DATA[Dashboard.TYPES.EMITTER].updateFunc, DashboardLive.addDarkModeToDefaultEmitterDashboardStateFunc)

-- Prepended function defaultTextDashboardStateFunc to enable dark mode
function DashboardLive:addDarkModeToDefaultTextDashboardStateFunc(dashboard, newValue, minValue, maxValue, isActive)
	local spec = self.spec_DashboardLive
	if spec ~= nil and spec.darkMode ~= spec.darkModeLast then
		if spec.darkMode then
			dbgprint("switching to dark mode: "..tostring(self:getName()), 2)
			if dashboard.textColorDM ~= nil then dashboard.textColor = dashboard.textColorDM end
			if dashboard.hiddenColorDM ~= nil then dashboard.hiddenColor = dashboard.hiddenColorDM end
		else	
			dbgprint("switching to light mode: "..tostring(self:getName()), 2)
			if dashboard.textColorLM ~= nil then dashboard.textColor = dashboard.textColorLM end
			if dashboard.hiddenColorLM ~= nil then dashboard.hiddenColor = dashboard.hiddenColorLM end
		end
		if dashboard.textColor ~= nil then
			dashboard.characterLine:setColor(dashboard.textColor, dashboard.hiddenColor, dashboard.emissiveScale)
		end
	end
end
Dashboard.TYPE_DATA[Dashboard.TYPES.TEXT].updateFunc = Utils.prependedFunction(Dashboard.TYPE_DATA[Dashboard.TYPES.TEXT].updateFunc, DashboardLive.addDarkModeToDefaultTextDashboardStateFunc)

-- Prepended function defaultNumberDashboardStateFunc to enable dark mode
function DashboardLive:addDarkModeToDefaultNumberDashboardStateFunc(dashboard, newValue, minValue, maxValue, isActive)
	local spec = self.spec_DashboardLive
	if spec ~= nil and spec.darkMode ~= spec.darkModeLast then
		if spec.darkMode then
			dbgprint("defaultNumberDashboardStateFunc : switching to dark mode: "..tostring(self:getName()), 2)
			if dashboard.numberColorDM ~= nil then dashboard.numberColor = dashboard.numberColorDM end
		else	
			dbgprint("defaultNumberDashboardStateFunc : switching to light mode: "..tostring(self:getName()), 2)
			if dashboard.numberColorLM ~= nil then dashboard.numberColor = dashboard.numberColorLM end
		end
		if dashboard.numberColor ~= nil then
			for _, numberNode in pairs(dashboard.numberNodes) do
				dashboard.fontMaterial:setFontCharacterColor(numberNode, dashboard.numberColor[1], dashboard.numberColor[2], dashboard.numberColor[3], 1, dashboard.emissiveScale)
            end
        end
	end
end
Dashboard.TYPE_DATA[Dashboard.TYPES.NUMBER].updateFunc = Utils.prependedFunction(Dashboard.TYPE_DATA[Dashboard.TYPES.NUMBER].updateFunc, DashboardLive.addDarkModeToDefaultNumberDashboardStateFunc)

-- displayType="AUDIO"
function DashboardLive.initAudioDashboardSchema(...)
	-- already set by initSpecialization
end

function DashboardLive:loadAudioDashboardFromXML(xmlFile, key, dashboard)
	dbgprint("Audio: loadAudioDashboardFromXML", 2)
    dashboard.dblAudioFile = xmlFile:getValue(key .. "#audioFile")
    if dashboard.dblAudioFile == nil then
    	Logging.xmlError(self.xmlFile, "Audio Dashboard without soundFile!")
    	return false
    end
    dbgprint("loadAudioDashboardFromXML : audioFile: "..tostring(dashboard.dblAudioFile), 2)
    
	dashboard.dblAudioName = xmlFile:getValue(key .. "#audioName")
    if dashboard.dblAudioName == nil then
    	Logging.xmlError(self.xmlFile, "Audio Dashboard without unique audio name!")
    	return false
    end
    dbgprint("loadAudioDashboardFromXML : audioName: "..tostring(dashboard.dblAudioName), 2)
    
    local baseDirectory = self.baseDirectory
    if baseDirectory == "" then -- vanilla has no audios, so switch to DBL_VV-Path
    	baseDirectory = DashboardLive.INT_PATH or ""
    	dbgprint("loadAudioDashboardFromXML : set baseDirectory to "..tostring(DashboardLive.INT_PATH), 2)
    end
    
    local audioFile = Utils.getFilename(dashboard.dblAudioFile, baseDirectory)
    if audioFile == nil or not fileExists(audioFile) then
    	audioFile = Utils.getFilename(dashboard.dblAudioFile, DashboardLive.INT_PATH)
    end
    if audioFile == nil or not fileExists(audioFile) then
    	Logging.xmlWarning(self.xmlFile, "Audio file not found for audio dashboard "..tostring(dashboard.dblAudioName).."!")
    	return false
    end
    
    dashboard.dblAudioOutside = xmlFile:getValue(key .. "#outside") or false
    dashboard.dblAudioDistance = xmlFile:getValue(key .. "#distance") or 5.0
    
    dashboard.dblAudioSample = createSample(dashboard.dblAudioName)
	loadSample(dashboard.dblAudioSample, audioFile, false)
	dbgprint("loadAudioDashboardFromXML : sample loaded: id="..tostring(dashboard.dblAudioSample), 2)
    	
    dashboard.dblAudioLoop = xmlFile:getValue(key .. "#loop", 1)
    dbgprint("loadAudioDashboardFromXML : loop: "..tostring(dashboard.dblAudioLoop), 2)
    
    dashboard.dblAudioVolume = xmlFile:getValue(key .. "#volume", 1)
    dbgprint("loadAudioDashboardFromXML : volume: "..tostring(dashboard.dblAudioVolume), 2)
    
    return true
end

function DashboardLive.defaultAudioStateFunc(self, dashboard, newValue, minValue, maxValue, isActive)
	dbgprint("defaultAudioStateFunc : newValue = "..tostring(newValue), 4)
	dbgprint("defaultAudioStateFunc : isActive = "..tostring(isActive), 4)
	
	if type(newValue) == "number" then
        newValue = newValue > 0.5 and true or false
    end
	if newValue == nil then
        newValue = isActive
    else
        newValue = newValue and isActive
    end
    
    if self == g_currentMission.hud.controlledVehicle then
		if newValue and not dashboard.played and not isSamplePlaying(dashboard.dblAudioSample) then
			dashboard.played = true
			playSample(dashboard.dblAudioSample, dashboard.dblAudioLoop, dashboard.dblAudioVolume, 0, 0, 0)
		end
		if not newValue then
			stopSample(dashboard.dblAudioSample, 0, 0)
			dashboard.played = false
		end
	elseif dashboard.dblAudioOutside then
		if newValue and not dashboard.played and g_localPlayer ~= nil and not isSamplePlaying(dashboard.dblAudioSample) then
			local playerX, _, playerZ = localToWorld(g_localPlayer.capsuleController.rootNode, 0, 0, 0)
			--local playerX = g_currentMission.player.baseInformation.lastPositionX
			--local playerZ = g_currentMission.player.baseInformation.lastPositionZ
			local vehicleX, _, vehicleZ = localToWorld(self.rootNode, 0, 0, 0)
			local distance = math.sqrt((playerX-vehicleX)^2 + (playerZ-vehicleZ)^2)
			if distance < dashboard.dblAudioDistance then
				local volume = dashboard.dblAudioVolume * (1 - distance / dashboard.dblAudioDistance)
				dashboard.played = true
				playSample(dashboard.dblAudioSample, dashboard.dblAudioLoop, volume, 0, 0, 0)
			end
		end
		if not newValue then
			stopSample(dashboard.dblAudioSample, 0, 0)
			dashboard.played = false
		end
	end
end
Dashboard.registerDisplayType(Dashboard.TYPES.AUDIO, false, DashboardLive.initAudioDashboardSchema, DashboardLive.loadAudioDashboardFromXML, DashboardLive.defaultAudioStateFunc)

-- identify joints with mapping options
local function jointMapping(vehicle, jointIndices, jointSide, jointType)
	dbgprint("jointMapping : jointSide = "..tostring(jointSide).." / jointType = "..tostring(jointType), 2)
	dbgprint("jointMapping : jointIndices before: "..tostring(jointIndices), 2)
	local jointSpec = vehicle.spec_attacherJoints
	local sideOk
	local typeOk
	
	if jointSpec ~= nil and (jointSide ~= nil or jointType ~= nil) then
		for index, attacherJoint in pairs(jointSpec.attacherJoints) do
			sideOk = nil
			typeOk = nil
			if jointSide ~= nil then
				local wx, wy, wz = getWorldTranslation(attacherJoint.jointTransform)
				local _, _, lz = worldToLocal(vehicle.steeringAxleNode, wx, wy, wz)
				sideOk = (lz > 0 and string.lower(jointSide) == "front") or (lz < 0 and string.lower(jointSide) == "back")
			end
			
			if jointType ~= nil then
				typeOk = attacherJoint.jointType == AttacherJoints.jointTypeNameToInt[string.lower(jointType)]
			end
			
			if (sideOk == nil or sideOk == true) and (typeOk == nil or typeOk == true) then				
				if jointIndices == nil then 
					jointIndices = tostring(index)
				elseif type(jointIndices) == "table" then 
					table.insert(jointIndices, index)
				else
					jointIndices = jointIndices.." "..tostring(index)
				end
			end
		end
	end
	dbgprint("jointMapping : jointIndices after: "..tostring(jointIndices), 2)
	if type(jointIndices) == "table" then
		--dbgprint_r(jointIndices, 2, 1)
	end
	return jointIndices
end

-- GROUPS

function DashboardLive:loadDashboardGroupFromXML(superFunc, xmlFile, key, group)	
    dbgprint("loadDashboardGroupFromXML : "..self:getName()..": key: "..tostring(key), 2)
    dbgprint("loadDashboardGroupFromXML : filename: "..tostring(xmlFile.filename), 2)

	if string.sub(key, 1, 18) ~= "dashboardCompounds" then
--  *******
		dbgprint("loadDashboardGroupFromXML : superFunc...", 2)
		if not superFunc(self, xmlFile, key, group) then
			dbgprint("loadDashboardGroupFromXML : superfunc failed for group "..tostring(group.name), 2)
			return false
		end
--	*******
	end
	
    dbgprint("loadDashboardGroupFromXML : overwritten part...", 2)
    if group.name == nil then
    	group.name = xmlFile:getValue(key .. "#name")
    end
    
    group.dblCommand = lower(xmlFile:getValue(key .. "#dbl"))
    if group.dblCommand ~= nil then
		dbgprint("loadDashboardGroupFromXML : dblCommand: "..tostring(group.dblCommand), 2)
		
		if group.dblCommand == "page" then
			group.dblPage = xmlFile:getValue(key .. "#page")
			group.dblPageGroup = xmlFile:getValue(key .. "#group") or 1
			dbgprint("loadDashboardGroupFromXML : group: "..tostring(group.dblPageGroup).." / page: "..tostring(group.dblPage), 2)
		end
		
		if group.dblCommand == "darkmode" then
			local spec = self.spec_DashboardLive
			if spec ~= nil then spec.darkModeExists = "true" end
		end
		
		group.dblOperator = lower(xmlFile:getValue(key .. "#op", "and"))
		dbgprint("loadDashboardGroupFromXML : dblOperator: "..tostring(group.dblOperator), 2)
		
		group.dblOption = xmlFile:getValue(key .. "#dblOption")
		dbgprint("loadDashboardGroupFromXML : dblOption: "..tostring(group.dblOption), 2)
		
		group.dblTrailer = xmlFile:getValue(key .. "#dblTrailer")
		dbgprint("loadDashboardGroupFromXML : dblTrailer: "..tostring(group.dblTrailer), 2)
		
		group.dblActiveWithoutImplement = xmlFile:getValue(key.. "#dblActiveWithoutImplement", false)
		dbgprint("loadDashboardGroupFromXML : dblActiveWithoutImplement: "..tostring(group.dblActiveWithoutImplement), 2)
		
		group.dblAttacherJointIndices = xmlFile:getValue(key .. "#dblAttacherJointIndices", nil, true)
		local jointSide = xmlFile:getValue(key .. "#dblJointSide")
		dbgprint("loadDashboardGroupFromXML : dblJointSide: "..tostring(jointSide), 2)
		local jointType = xmlFile:getValue(key .. "#dblJointType")
		dbgprint("loadDashboardGroupFromXML : dblJointType: "..tostring(jointType), 2)
		group.dblAttacherJointIndices = jointMapping(self, group.dblAttacherJointIndices, jointSide, jointType)
		dbgprint("loadDashboardGroupFromXML : dblAttacherJointIndices: "..tostring(group.dblAttacherJointIndices), 2)
		
		group.dblSelection = xmlFile:getValue(key .. "#dblSelection", nil, true)
		dbgprint("loadDashboardGroupFromXML : dblSelection: "..tostring(group.dblSelection), 2)
		
		group.dblSelectionGroup = xmlFile:getValue(key .. "#dblSelectionGroup", nil, true)
		dbgprint("loadDashboardGroupFromXML : dblSelectionGroup: "..tostring(group.dblSelectionGroup), 2)
		
		group.dblRidgeMarker = xmlFile:getValue(key .. "#dblRidgeMarker")
		dbgprint("loadDashboardGroupFromXML : dblRidgeMarker: "..tostring(group.dblRidgeMarker), 2)
	else
		dbgprint("loadDashboardGroupFromXML : no dbl command given", 2)
	end
    
    return true
end

function DashboardLive:getIsDashboardGroupActive(superFunc, group)
    local spec = self.spec_DashboardLive
    local specCS = self.spec_crabSteering
    local specWM = self.spec_workMode
    local specRM = self.spec_ridgeMarker
    
	local returnValue = false
	
	-- command given?
	if group.dblCommand == nil then 
		return superFunc(self, group)

	-- page
	elseif group.dblCommand == "page" and group.dblPage ~= nil and group.dblPageGroup ~= nil then 
		if group.dblPage and group.dblPage > 0 then 
			returnValue = spec.pageGroups[group.dblPageGroup] ~= nil and group.dblPage == spec.pageGroups[group.dblPageGroup].actPage
		else
			returnValue = group.dblPageGroup == spec.actPageGroup
		end
		
	elseif group.dblCommand == "darkmode" then
		returnValue = spec.darkMode
		
	elseif group.dblCommand == "lightmode" then
		returnValue = not spec.darkMode
	
	-- vanilla game selector
	elseif group.dblCommand == "base_selector" and group.dblSelection ~= nil then
		local dblOpt = group.dblSelection
		local selectorActive = false
		if type(dblOpt) == "number" and dblOpt == -100 then
			returnValue = spec.selectorActive < 0
		elseif type(dblOpt) == "number" and dblOpt == 100 then
			returnValue = spec.selectorActive > 0
		else
			for _,selector in ipairs(dblOpt) do
				if selector == spec.selectorActive then selectorActive = true end
			end
			returnValue = selectorActive
		end
		
	-- vanilla game selector group
	elseif group.dblCommand == "base_selectorgroup" then
		local dblOpt = group.dblSelectionGroup
		local groupActive = false
		if dblOpt ~= "" then
			for _,selGroup in ipairs(dblOpt) do
				if selGroup == spec.selectorGroup then groupActive = true end
			end
		end
		returnValue = groupActive
		
	-- vanilla game implements
	elseif group.dblCommand == "base_disconnected" then
		returnValue = getAttachedStatus(self, group, "disconnected")
	
	elseif group.dblCommand == "base_connected" then
		returnValue = getAttachedStatus(self, group, "connected")
		
	elseif group.dblCommand == "base_lifted" then
		returnValue = getAttachedStatus(self, group, "lifted", group.dblActiveWithoutImplement)
		
	elseif group.dblCommand == "base_lowered" then
		returnValue = getAttachedStatus(self, group, "lowered", group.dblActiveWithoutImplement)
	
	elseif group.dblCommand == "base_lowerable" then
		returnValue = getAttachedStatus(self, group, "lowerable", group.dblActiveWithoutImplement)
	
	elseif group.dblCommand == "base_pto" then
		returnValue = getAttachedStatus(self, group, "pto", group.dblActiveWithoutImplement)
	
	elseif group.dblCommand == "base_foldable" then
		returnValue = getAttachedStatus(self, group, "foldable", group.dblActiveWithoutImplement)
	
	elseif group.dblCommand == "base_folded" then
		returnValue = getAttachedStatus(self, group, "folded", group.dblActiveWithoutImplement)	
	
	elseif group.dblCommand == "base_unfolded" then
		returnValue = getAttachedStatus(self, group, "unfolded", group.dblActiveWithoutImplement)	
		
	--ph
	elseif group.dblCommand == "base_hasspec" then
		returnValue = getAttachedStatus(self, group, "hasspec",false)
		
	elseif group.dblCommand == "base_hastypedesc" then
		returnValue = getAttachedStatus(self, group, "hastypedesc",false)

	elseif specCS ~= nil and group.dblCommand == "base_steering" then
		local dblOpt = group.dblOption
		if dblOpt == "" or tonumber(dblOpt) == nil then
			Logging.xmlWarning(vehicle.xmlFile, "No steering mode number given for DashboardLive steering command")
			return false
		end
		returnValue = specCS.state == tonumber(dblOpt)

	elseif specWM ~= nil and group.dblCommand == "base_swath" then
		local dblOpt = group.dblOption
		if dblOpt == "" or tonumber(dblOpt) == nil then
			Logging.xmlWarning(vehicle.xmlFile, "No work mode number given for DashboardLive swath command")
			return false
		end
		returnValue = specWM.state == tonumber(dblOpt)
		
	-- vanilla game ridgeMarker
	elseif specRM ~= nil and group.dblCommand == "base_ridgemarker" then
		returnValue = group.dblRidgeMarker == specRM.ridgeMarkerState
		
	-- VCA / EV
	elseif group.dblCommand == "vca_park" or group.dblCommand == "ev_park" then
		returnValue = (spec.modVCAFound and self:vcaGetState("handbrake"))
					or(spec.modEVFound and self.vData.is[13])
	
	elseif group.dblCommand == "vca_diff_front" or group.dblCommand == "ev_diff_front" then
		returnValue = (spec.modVCAFound and self:vcaGetState("diffLockFront"))
					or(spec.modEVFound and self.vData.is[1])
	
	elseif group.dblCommand == "vca_diff_back" or group.dblCommand == "ev_diff_back"then
		returnValue = (spec.modVCAFound and self:vcaGetState("diffLockBack"))
					or(spec.modEVFound and self.vData.is[2])
	
	elseif group.dblCommand == "vca_diff" or group.dblCommand == "ev_diff" then
		returnValue = (spec.modVCAFound and (self:vcaGetState("diffLockFront") or self:vcaGetState("diffLockBack")))
					or(spec.modEVFound and (self.vData.is[1] or self.vData.is[2]))
	
	elseif group.dblCommand == "vca_diff_awd" or group.dblCommand == "ev_diff_awd" then
		returnValue = (spec.modVCAFound and self:vcaGetState("diffLockAWD"))
					or(spec.modEVFound and self.vData.is[3]==1)
		
	elseif group.dblCommand == "vca_diff_awdf" then
		returnValue = spec.modVCAFound and self:vcaGetState("diffFrontAdv")
	
	-- VCA / keep speed
	elseif group.dblCommand == "vca_ks" then
		returnValue = spec.modVCAFound and self:vcaGetState("ksIsOn")
	
	-- Headland Management
	elseif group.dblCommand == "hlm_active_field" then
		returnValue = spec.modHLMFound and self.spec_HeadlandManagement.isOn and not self.spec_HeadlandManagement.isActive
	
	elseif group.dblCommand == "hlm_active_headland" then
		returnValue = spec.modHLMFound and self.spec_HeadlandManagement.isOn and self.spec_HeadlandManagement.isActive
	
	elseif group.dblCommand == "hlm_on" then
		returnValue = spec.modHLMFound and self.spec_HeadlandManagement.isOn
		
	-- Guidance Steering
	elseif group.dblCommand == "gps_on" then
		local gsSpec = self.spec_globalPositioningSystem
		local hlmSpec = self.spec_HeadlandManagement
		returnValue = spec.modGuidanceSteeringFound and gsSpec ~= nil and gsSpec.lastInputValues ~= nil and gsSpec.lastInputValues.guidanceIsActive
		returnValue = returnValue or (spec.modVCAFound and self:vcaGetState("snapDirection") ~= 0) 
		returnValue = returnValue or (hlmSpec ~= nil and hlmSpec.exists and hlmSpec.isOn and hlmSpec.contour ~= 0)

	elseif group.dblCommand == "gps_active" then
		local gsSpec = self.spec_globalPositioningSystem
		local hlmSpec = self.spec_HeadlandManagement
		returnValue = spec.modGuidanceSteeringFound and gsSpec ~= nil and gsSpec.lastInputValues ~= nil and gsSpec.lastInputValues.guidanceSteeringIsActive
		returnValue = returnValue or (spec.modVCAFound and self:vcaGetState("snapIsOn")) 
		returnValue = returnValue or (spec.modEVFound and self.vData.is[5])
		returnValue = returnValue or (hlmSpec ~= nil and hlmSpec.exists and hlmSpec.isOn and not hlmSpec.isActive and hlmSpec.contour ~= 0 and not contourSetActive)
		
	elseif group.dblCommand == "gps_lane+" then
		local spec = self.spec_DashboardLive
		local gsSpec = self.spec_globalPositioningSystem
		returnValue = spec.modGuidanceSteeringFound and gsSpec ~= nil and gsSpec.lastInputValues ~= nil and gsSpec.lastInputValues.guidanceIsActive
		returnValue = returnValue and gsSpec.guidanceData ~= nil and gsSpec.guidanceData.currentLane ~= nil and gsSpec.guidanceData.currentLane >= 0	

	elseif group.dblCommand == "gps_lane-" then
		local spec = self.spec_DashboardLive
		local gsSpec = self.spec_globalPositioningSystem
		returnValue = spec.modGuidanceSteeringFound and gsSpec ~= nil and gsSpec.lastInputValues ~= nil and gsSpec.lastInputValues.guidanceIsActive
		returnValue = returnValue and gsSpec.guidanceData ~= nil and gsSpec.guidanceData.currentLane ~= nil and gsSpec.guidanceData.currentLane < 0	
	end
	
    if group.dblOperator == "and" or group.dblCommand == "page" then 
    	return superFunc(self, group) and returnValue
    else
    	return superFunc(self, group) or returnValue
    end
end

-- ELEMENTS

-- readAttributes
-- page
function DashboardLive.getDBLAttributesPage(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblPage = lower(xmlFile:getValue(key .. "#page"))
	dbgprint("getDBLAttributesPage : page: "..tostring(dashboard.dblPage), 2)
	
	dashboard.dblPageGroup = lower(xmlFile:getValue(key .. "#group"))
	dbgprint("getDBLAttributesPage : group: "..tostring(dashboard.dblPageGroup), 2)
	
	return true
end

-- base
function DashboardLive.getDBLAttributesBase(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)

	local min = xmlFile:getValue(key .. "#min")
	local max = xmlFile:getValue(key .. "#max")
	local factor = xmlFile:getValue(key .. "#factor")
	if min ~= nil then dashboard.dblMin = min end
    if max ~= nil then dashboard.dblMax = max end
    if factor ~= nil then dashboard.dblFactor = factor end
	
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd"))
    dbgprint("getDBLAttributesBase : command: "..tostring(dashboard.dblCommand), 2)

	if dashboard.dblCommand == nil then 
		dashboard.dblCommand = ""
		dbgprint("getDBLAttributesBase : cmd is empty", 2)
    	return true
    end
	
    dashboard.dblAttacherJointIndices = xmlFile:getValue(key .. "#joints")
	local jointSide = xmlFile:getValue(key .. "#jointSide")
	dbgprint("getDBLAttributesBase : jointSide: "..tostring(jointSide), 2)
	local jointType = xmlFile:getValue(key .. "#jointType")
	dbgprint("getDBLAttributesBase : jointType: "..tostring(jointType), 2)
	dashboard.dblAttacherJointIndices = jointMapping(self, dashboard.dblAttacherJointIndices, jointSide, jointType)
	dbgprint("getDBLAttributesBase : joints: "..tostring(dashboard.dblAttacherJointIndices), 2)
	
	dashboard.dblState = xmlFile:getValue(key .. "#state") -- swath state, ridgemarker state, crabsteering state...
	dbgprint("getDBLAttributesBase : state: "..tostring(dashboard.dblState), 2)
	
	dashboard.dblStateText = xmlFile:getValue(key .. "#stateText") -- tipSide
	dbgprint("getDBLAttributesBase : stateText: "..tostring(dashboard.dblStateText), 2)
	
	dashboard.dblOption = xmlFile:getValue(key .. "#option") -- nil or 'default'
	dbgprint("getDBLAttributesBase : option: "..tostring(dashboard.dblOption), 2)
	
	dashboard.dblTrailer = xmlFile:getValue(key .. "#trailer") -- trailer
	dbgprint("getDBLAttributesBase : trailer: "..tostring(dashboard.dblTrailer), 2)
	
	dashboard.dblPartition = xmlFile:getValue(key .. "#partition", 0) -- trailer partition
	dbgprint("getDBLAttributesBase : partition: "..tostring(dashboard.dblPartition), 2)
	
	dashboard.dblCond = xmlFile:getValue(key .. "#cond")
	dbgprint("getDBLAttributesBase : cond: "..tostring(dashboard.dblCond), 2)
	dashboard.dblCondValue = xmlFile:getValue(key .. "#condValue")
	dbgprint("getDBLAttributesBase : condValue: "..tostring(dashboard.dblCondValue), 2)
	if dashboard.dblCond ~= nil and dashboard.dblCond ~= "not" and dashboard.dblCondValue == nil then
		Logging.xmlError(self.xmlFile, "No value given for comparation")
		return false
	end
	
	if dashboard.dblCommand == "filllevel" and dashboard.dblOption == "percent" then
    	dashboard.dblMin = dashboard.dblMin or 0
    	dashboard.dblMax = dashboard.dblMax or 100
	end
	
	-- warnings or infos to special constellations
	if dashboard.dblCommand == "liftstate" then
		if dashboard.dblAttacherJointIndices ~= nil then 
			local joints = jointsToTable(dashboard.dblAttacherJointIndices)
			if #joints > 1 then
				Logging.xmlInfo(self.xmlFile, "command `liftstate` to show state of 3P-Joint should apply to only one attacherJoint at a time, please ensure that this condition is met")
			end
		else
			Logging.xmlInfo(self.xmlFile, "command `liftstate` without given attacherJoint")
			return false
		end
	end
	return true
end

-- minimap
function DashboardLive.getDBLAttributesMiniMap(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd")) or "map"
	dashboard.scale = xmlFile:getValue(key .. "#scale") or DashboardLive.scale
	dashboard.node = xmlFile:getValue(key .. "#node", nil, components, i3dMappings, true)
	dbgprint("getDBLAttributesMiniMap: node = "..tostring(dashboard.node).." / command = "..tostring(dashboard.dblCommand).." / scale = "..tostring(dashboard.scale), 2)

	local mapTexture = g_currentMission.mapImageFilename
	local mapName = g_currentMission.missionInfo.map.title
	local customMap = DashboardLive.MODSETTINGSDIR..mapName
	local customMapFile = customMap.."/overview.dds"
	
	createFolder(customMap)
	
	if fileExists(customMapFile) then
		dbgprint("getDBLAttributesMiniMap: Custom miniMap configuration found. Set Texture to "..customMapFile, 2)
		mapTexture = customMapFile
	end

	if dashboard.node == nil then
		Logging.xmlWarning(self.xmlFile, "Missing 'node' for dashboard '%s'", key)
		return false
	elseif dashboard.dblCommand == "map" then
		local materialId = getMaterial(dashboard.node, 0)
		materialId = setMaterialDiffuseMapFromFile(materialId, mapTexture, true, true, false)
		setMaterial(dashboard.node, materialId, 0)
		dbgprint("getDBLAttributesMiniMap: MiniMap Material set to "..tostring(materialId).." / texture set to "..mapTexture, 2)
	end

	return true
end

-- combine
function DashboardLive.getDBLAttributesCombine(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)

	local min = xmlFile:getValue(key .. "#min")
	local max = xmlFile:getValue(key .. "#max")
	local factor = xmlFile:getValue(key .. "#factor")
	if min ~= nil then dashboard.dblMin = min end
    if max ~= nil then dashboard.dblMax = max end
    if factor ~= nil then dashboard.dblFactor = factor end
	
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd"))
    dbgprint("getDBLAttributesBase : command: "..tostring(dashboard.dblCommand), 2)

	dashboard.dblState = xmlFile:getValue(key .. "#state") -- swath state, ridgemarker state, ...
	dbgprint("getDBLAttributesBase : state: "..tostring(dashboard.dblState), 2)
	
	dashboard.dblStateText = xmlFile:getValue(key .. "#stateText") -- tipSide
	dbgprint("getDBLAttributesBase : stateText: "..tostring(dashboard.dblStateText), 2)
	
	dashboard.dblFactor = xmlFile:getValue(key .. "#factor", 1)
	dbgprint("getDBLAttributesBase : factor: "..tostring(dashboard.dblFactor), 2)
	
	return true
end

-- rda
function DashboardLive.getDBLAttributesRDA(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd"))
    dbgprint("getDBLAttributesRDA : cmd: "..tostring(dashboard.dblCommand), 2)
    if dashboard.dblCommand == nil then 
    	Logging.xmlWarning(self.xmlFile, "No '#cmd' given for valueType 'rda'")
    	return false
    end
    
    dashboard.dblOption = lower(xmlFile:getValue(key .. "#option"))
    dbgprint("getDBLAttributesRDA : option: "..tostring(dashboard.dblOption), 2)
    
    dashboard.dblFactor = xmlFile:getValue(key .. "#factor") or 1
    dbgprint("getDBLAttributesRDA : factor: "..tostring(dashboard.dblFactor), 2)

	return true
end

-- vca
function DashboardLive.getDBLAttributesVCA(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd"))
    dbgprint("getDBLAttributesVCA : cmd: "..tostring(dashboard.dblCommand), 2)
    
    if dashboard.dblCommand == nil then 
    	Logging.xmlWarning(self.xmlFile, "No '#cmd' given for valueType 'vca'")
    	return false
    end
    
    dashboard.dblCond = xmlFile:getValue(key .. "#cond")
	dbgprint("getDBLAttributesBase : cond: "..tostring(dashboard.dblCond), 2)
	
	dashboard.dblCondValue = xmlFile:getValue(key .. "#condValue")
	dbgprint("getDBLAttributesBase : condValue: "..tostring(dashboard.dblCondValue), 2)
	if dashboard.dblCond ~= nil and dashboard.dblCond ~= "not" and dashboard.dblCondValue == nil then
		Logging.xmlError(self.xmlFile, "No value given for comparation")
		return false
	end

	return true
end

-- extendedCruiseControl / speedControl
function DashboardLive.getDBLAttributesCC(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd"))
    dbgprint("getDBLAttributesECC : cmd: "..tostring(dashboard.dblCommand), 2)
    
    if dashboard.dblCommand == nil then 
    	Logging.xmlWarning(self.xmlFile, "No '#cmd' given for valueType 'cc'")
    	return false
    end
    
    dashboard.dblState = xmlFile:getValue(key .. "#state")
    dbgprint("getDBLAttributesECC : state: "..tostring(dashboard.dblState), 2)

	return true
end

-- hlm
function DashboardLive.getDBLAttributesHLM(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd", ""))
    dbgprint("getDBLAttributesHLM : cmd: "..tostring(dashboard.dblCommand), 2)
	
	dashboard.dblOption = lower(xmlFile:getValue(key .. "#option"))
    dbgprint("getDBLAttributesHLM : option: "..tostring(dashboard.dblOption), 2)
    
	dashboard.dblCond = xmlFile:getValue(key .. "#cond")
	dbgprint("getDBLAttributesBase : cond: "..tostring(dashboard.dblCond), 2)
	
	dashboard.dblCondValue = xmlFile:getValue(key .. "#condValue")
	dbgprint("getDBLAttributesBase : condValue: "..tostring(dashboard.dblCondValue), 2)
	if dashboard.dblCond ~= nil and dashboard.dblCond ~= "not" and dashboard.dblCondValue == nil then
		Logging.xmlError(self.xmlFile, "No value given for comparation")
		return false
	end
    
	return true
end

-- gps
function DashboardLive.getDBLAttributesGPS(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)

	local min = xmlFile:getValue(key .. "#min")
	local max = xmlFile:getValue(key .. "#max")
	local factor = xmlFile:getValue(key .. "#factor")
	if min ~= nil then dashboard.dblMin = min end
    if max ~= nil then dashboard.dblMax = max end
    if factor ~= nil then dashboard.dblFactor = factor end
    
	dashboard.dblOption = lower(xmlFile:getValue(key .. "#option", "on")) -- 'on' or 'active'
    dbgprint("getDBLAttributesGPS : option: "..tostring(dashboard.dblOption), 2)
	
	dashboard.dblCond = xmlFile:getValue(key .. "#cond")
	dbgprint("getDBLAttributesBase : cond: "..tostring(dashboard.dblCond), 2)
	dashboard.dblCondValue = xmlFile:getValue(key .. "#condValue")
	dbgprint("getDBLAttributesBase : condValue: "..tostring(dashboard.dblCondValue), 2)
	if dashboard.dblCond ~= nil and dashboard.dblCond ~= "not" and dashboard.dblCondValue == nil then
		Logging.xmlError(self.xmlFile, "No value given for comparation")
		return false
	end

	return true
end

function DashboardLive.getDBLAttributesGPSNumbers(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	
	local min = xmlFile:getValue(key .. "#min")
	local max = xmlFile:getValue(key .. "#max")
	local factor = xmlFile:getValue(key .. "#factor")
	if min ~= nil then dashboard.dblMin = min end
    if max ~= nil then dashboard.dblMax = max end
    if factor ~= nil then dashboard.dblFactor = factor end
    
	dashboard.dblFactor = xmlFile:getValue(key .. "#factor", "1")
    dbgprint("getDBLAttributesNumbers : factor: "..tostring(dashboard.dblFactor), 2)
    
    dashboard.dblOption = xmlFile:getValue(key .. "#option")
	dbgprint("getDBLAttributesNumbers : option: "..tostring(dashboard.dblOption), 2)

	return true
end

-- ps
function DashboardLive.getDBLAttributesPS(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)

	local min = xmlFile:getValue(key .. "#min")
	local max = xmlFile:getValue(key .. "#max")
	local factor = xmlFile:getValue(key .. "#factor")
	if min ~= nil then dashboard.dblMin = min end
    if max ~= nil then dashboard.dblMax = max end
    if factor ~= nil then dashboard.dblFactor = factor end
    
	dashboard.dblOption = lower(xmlFile:getValue(key .. "#option", "mode"))
	dashboard.dblState = xmlFile:getValue(key .. "#state", "")
    dbgprint("getDBLAttributesPS : option: "..tostring(dashboard.dblOption).." / state: "..tostring(dashboard.dblState), 2)

	return true
end

-- selector
function DashboardLive.getDBLAttributesSelection(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblSelection = xmlFile:getValue(key .. "#selection", nil, true)
	dbgprint("getDBLAttributesSelection : selection: "..tostring(dashboard.dblSelection), 2)
	
	dashboard.dblSelectionGroup = xmlFile:getValue(key .. "#selectionGroup", nil, true)
	dbgprint("getDBLAttributesSelection : selectionGroup: "..tostring(dashboard.dblSelectionGroup), 2)
	
	if dashboard.dblSelection == nil and dashboard.dblSelectionGroup == nil then 
		Logging.xmlWarning(self.xmlFile, "Neither '#selection' nor '#selectionGroup' given for valueType 'selector'")
		return false
	end
	if dashboard.dblSelection ~= nil and dashboard.dblSelectionGroup ~= nil then 
		Logging.xmlWarning(self.xmlFile, "'#selection' and '#selectionGroup' given for valueType 'selector'")
		return false
	end
	
	return true
end

-- baler
function DashboardLive.getDBLAttributesBaler(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd"))
    dbgprint("getDBLAttributesBaler : command: "..tostring(dashboard.dblCommand), 2)
    
	dashboard.dblAttacherJointIndices = xmlFile:getValue(key .. "#joints")
	local jointSide = xmlFile:getValue(key .. "#jointSide")
	dbgprint("getDBLAttributesBaler : jointSide: "..tostring(jointSide), 2)
	local jointType = xmlFile:getValue(key .. "#jointType")
	dbgprint("getDBLAttributesBaler : jointType: "..tostring(jointType), 2)
	dashboard.dblAttacherJointIndices = jointMapping(self, dashboard.dblAttacherJointIndices, jointSide, jointType)
	dbgprint("getDBLAttributesBaler : joints: "..tostring(dashboard.dblAttacherJointIndices), 2)
	
	
	dashboard.dblOption = lower(xmlFile:getValue(key .. "#option", "selected")) -- 'selected' or 'current'
	
	return true
end

-- lock steering axles
function DashboardLive.getDBLAttributesLSA(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd"))
	dbgprint("getDBLAttributesLSA : command: "..tostring(dashboard.dblCommand), 2)
	
	dashboard.dblAttacherJointIndices = xmlFile:getValue(key .. "#joints")
	local jointSide = xmlFile:getValue(key .. "#jointSide")
	dbgprint("getDBLAttributesLSA : jointSide: "..tostring(jointSide), 2)
	local jointType = xmlFile:getValue(key .. "#jointType")
	dbgprint("getDBLAttributesLSA : jointType: "..tostring(jointType), 2)
	dashboard.dblAttacherJointIndices = jointMapping(self, dashboard.dblAttacherJointIndices, jointSide, jointType)
	dbgprint("getDBLAttributesLSA : joints: "..tostring(dashboard.dblAttacherJointIndices), 2)
	
	dashboard.dblTrailer = xmlFile:getValue(key .. "#trailer")
	dbgprint("getDBLAttributesBase : trailer: "..tostring(dashboard.dblTrailer), 2)
	
	return true
end

-- combineXP by yumi
function DashboardLive.getDBLAttributesCXP(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd"))
	dbgprint("getDBLAttributesCXP : command: "..tostring(dashboard.dblCommand), 2)
	
	dashboard.dblFactor = xmlFile:getValue(key .. "#factor", 100)
	dbgprint("getDBLAttributesCXP : factor: "..tostring(dashboard.dblFactor), 2)
	
	return true
end

-- print
function DashboardLive.getDBLAttributesPrint(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblOption = xmlFile:getValue(key .. "#option", "")
	dbgprint("getDBLAttributePrint : option: "..tostring(dashboard.dblOption), 2)
	
	return true
end

-- frontLoader
function DashboardLive.getDBLAttributesFrontloader(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd", "toolrotation")) -- rotation,  minmax
    dbgprint("getDBLAttributesFrontloader : command: "..tostring(dashboard.dblCommand), 2)
    
	dashboard.dblAttacherJointIndices = xmlFile:getValue(key .. "#joints")
	local jointSide = xmlFile:getValue(key .. "#jointSide")
	dbgprint("getDBLAttributesFrontloader : jointSide: "..tostring(jointSide), 2)
	local jointType = xmlFile:getValue(key .. "#jointType")
	dbgprint("getDBLAttributesFrontloader : jointType: "..tostring(jointType), 2)
	dashboard.dblAttacherJointIndices = jointMapping(self, dashboard.dblAttacherJointIndices, jointSide, jointType)
	dbgprint("getDBLAttributesFrontloader : joints: "..tostring(dashboard.dblAttacherJointIndices), 2)

	dashboard.dblOption = xmlFile:getValue(key .. "#option", "1") -- number of tool

	dashboard.dblFactor = xmlFile:getValue(key .. "#factor", "1") -- factor

	dashboard.dblStateText = xmlFile:getValue(key .. "#stateText","origin")
	dashboard.dblState = xmlFile:getValue(key .. "#state","origin")

	local min = xmlFile:getValue(key .. "#min")
	local max = xmlFile:getValue(key .. "#max")
	
	if min ~= nil then dashboard.dblMin = min end
    if max ~= nil then dashboard.dblMax = max end
    
	
	return true
end

-- precisionFarming
function DashboardLive.getDBLAttributesPrecisionFarming(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd", "")) -- rotation,  minmax
    dbgprint("getDBLAttributesPrecisionFarming : command: "..tostring(dashboard.dblCommand), 2)
    
	dashboard.dblAttacherJointIndices = xmlFile:getValue(key .. "#joints")
	local jointSide = xmlFile:getValue(key .. "#jointSide")
	dbgprint("getDBLAttributesPrecisionFarming : jointSide: "..tostring(jointSide), 2)
	local jointType = xmlFile:getValue(key .. "#jointType")
	dbgprint("getDBLAttributesPrecisionFarming : jointType: "..tostring(jointType), 2)
	dashboard.dblAttacherJointIndices = jointMapping(self, dashboard.dblAttacherJointIndices, jointSide, jointType)
	dbgprint("getDBLAttributesPrecisionFarming : joints: "..tostring(dashboard.dblAttacherJointIndices), 2)

	dashboard.dblOption = lower(xmlFile:getValue(key .. "#option"))
	
	dashboard.dblTrailer = xmlFile:getValue(key .. "#trailer") -- number of tool

	dashboard.dblFactor = xmlFile:getValue(key .. "#factor", "1") -- factor

	local min = xmlFile:getValue(key .. "#min")
	local max = xmlFile:getValue(key .. "#max")
	
	if min ~= nil then dashboard.dblMin = min end
    if max ~= nil then dashboard.dblMax = max end

	return true
end

-- CVTaddon
function DashboardLive.getDBLAttributesCVT(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd", ""))
    dbgprint("getDBLAttributesCVT : command: "..tostring(dashboard.dblCommand), 2)
    
    dashboard.dblState = xmlFile:getValue(key .. "#state")
	dbgprint("getDBLAttributesCVT : state: "..tostring(dashboard.dblState), 2)
	
	dashboard.dblCond = xmlFile:getValue(key .. "#cond")
	dbgprint("getDBLAttributesBase : cond: "..tostring(dashboard.dblCond), 2)
	dashboard.dblCondValue = xmlFile:getValue(key .. "#condValue")
	dbgprint("getDBLAttributesBase : condValue: "..tostring(dashboard.dblCondValue), 2)
	if dashboard.dblCond ~= nil and dashboard.dblCond ~= "not" and dashboard.dblCondValue == nil then
		Logging.xmlError(self.xmlFile, "No value given for comparation")
		return false
	end
	
	return true
end

-- Realistic Damage System
function DashboardLive.getDBLAttributesRDS(self, xmlFile, key, dashboard, components, i3dMappings, parentNode)
	dashboard.dblCommand = lower(xmlFile:getValue(key .. "#cmd", ""))
    dbgprint("getDBLAttributesRDS : command: "..tostring(dashboard.dblCommand), 2)
    
    dashboard.dblState = xmlFile:getValue(key .. "#state")
	dbgprint("getDBLAttributesRDS : state: "..tostring(dashboard.dblState), 2)
	
	dashboard.dblCond = xmlFile:getValue(key .. "#cond")
	dbgprint("getDBLAttributesBase : cond: "..tostring(dashboard.dblCond), 2)
	dashboard.dblCondValue = xmlFile:getValue(key .. "#condValue")
	dbgprint("getDBLAttributesBase : condValue: "..tostring(dashboard.dblCondValue), 2)
	if dashboard.dblCond ~= nil and dashboard.dblCond ~= "not" and dashboard.dblCondValue == nil then
		Logging.xmlError(self.xmlFile, "No value given for comparation")
		return false
	end
	
	return true
end

-- get states
function DashboardLive.getDashboardLivePage(self, dashboard)
	dbgprint("getDashboardLivePage : dblPage: "..tostring(dashboard.dblPage)..", dblPageGroup: "..tostring(dashboard.dblPageGroup), 4)
	local spec = self.spec_DashboardLive
	local groupNum = tonumber(dashboard.dblPageGroup)
	local pageNum = tonumber(dashboard.dblPage)
	local returnValue = false
	
	if groupNum ~= nil and pageNum == nil then
		returnValue = groupNum == spec.actPageGroup
	elseif pageNum ~= nil then
		groupNum = groupNum or 1 -- it makes no sense without given group, but maybe there aren't any groups so let set it to 1 in his case
		returnValue = pageNum == spec.pageGroups[groupNum].actPage
	end
	
	return returnValue
end

function DashboardLive.getDashboardLiveBase(self, dashboard)
	dbgprint("getDashboardLiveBase : dblCommand: "..tostring(dashboard.dblCommand), 4)
	if dashboard.dblCommand ~= nil then
		local specWM = self.spec_workMode
		local specRM = self.spec_ridgeMarker
		local specMO = self.spec_motorized
		local specCS = self.spec_crabSteering
		local specPI = self.spec_pipe
		local cmds, j, s, o = dashboard.dblCommand, dashboard.dblAttacherJointIndices, dashboard.dblStateText or dashboard.dblState, dashboard.dblOption
		local cmd = string.split(cmds, " ")
		local returnValue = false
		
		for _, c in ipairs(cmd) do
			-- joint states
			if c == "disconnected" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "disconnected")
			
			elseif c == "connected" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "connected")
	
			elseif c == "lifted" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "lifted", o == "default")
				
			elseif c == "lifting" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "lifting")
				
			elseif c == "lowering" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "lowering")
	
			elseif c == "lowered" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "lowered", o == "default")

			elseif c == "lowerable" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "lowerable", o == "default")

			elseif c == "pto" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "pto", o == "default")
				
			elseif c == "ptorpm" then
				if not dashboard.dblFactor then dashboard.dblFactor = 0.625 end
				returnValue = returnValue or getAttachedStatus(self, dashboard, "ptorpm", o == "default")

			elseif c == "foldable" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "foldable", o == "default")

			elseif c == "folded" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "folded", o == "default")

			elseif c == "unfolded" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "unfolded", o == "default")
			
			elseif c == "folding" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "folding")
			
			elseif c == "unfolding" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "unfolding")
			
			elseif c == "tipping" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "tipping", o == "default")
				
			elseif c == "swath" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "swathstate", o == "default")
				
			elseif c == "mpconditioner" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "mpconditioner", o == "default")
				
			elseif c == "seedtype" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "seedtype", o == "default")
				
			elseif c == "coveropen" then
				returnValue = returnValue or getAttachedStatus(self, dashboard, "coveropen")

			end
		end
		
		-- player name
		if cmds == "playername" then
			local len = string.len(dashboard.textMask or "xxxx")
			local alignment = dashboard.textAlignment or "LEFT"
	
			returnValue = trim(g_currentMission.playerNickname, len, alignment)
		end
		
		-- cultivator state
		if cmds == "cultivator" then 
			local spec = findSpecialization(self, "spec_cultivator") 
			if spec ~= nil then
				if s == "deepmode" then
					returnValue = spec.isSubsoiler and spec.useDeepMode
				elseif s == "normalmode" then
					returnValue = not spec.isSubsoiler and spec.useDeepMode
				elseif s == "shallowmode" then
					returnValue = not spec.isSubsoiler and not spec.useDeepMode
				end
			end
		end
		
		-- crabSteering
		if cmds == "crabsteering" and specCS ~= nil then
			local dblState = dashboard.dblState
			if dblState ~= nil and tonumber(dblState) ~= nil then
				returnValue = specCS.state == tonumber(dblState)
			else
				Logging.xmlWarning(self.xmlFile, "No steering mode number given for DashboardLive crabSteering command")
			end
		end
		
		-- fillLevel	
		if cmds == "filllevel" then
			returnValue = getAttachedStatus(self, dashboard, "filllevel", 0)
			
		-- fillLevel of overload target
		elseif cmds == "targetfilllevel" then
			if o ~= nil then
				returnValue = 0
			end
			
			if specPI ~= nil then
				dbgprint("targetFillLevel: specPI exists", 2)
				local targetId = specPI.nearestObjectInTriggers.objectId
				if targetId ~= nil then
					local target = NetworkUtil.getObject(targetId)		
					local unit = specPI.nearestObjectInTriggers.fillUnitIndex
					local fillUnit = target.spec_fillUnit.fillUnits[unit]
					
					if o == "abs" then
						returnValue = math.floor(fillUnit.fillLevel)
					elseif o == "max" then
						returnValue = math.floor(fillUnit.capacity)
					elseif o == "percent" then
						local akt = math.floor(fillUnit.fillLevel)
						local max = math.floor(fillUnit.capacity)
						returnValue = max ~= 0 and math.floor((akt/max)*100)/100 or 0
					elseif o == "name" then
						returnValue = target.getFullName ~= nil and target:getFullName() or "unknown"
					elseif o == "overloading" then
						returnValue = specPI.nearestObjectInTriggers.isDischargeObject
					else
						returnValue = true
					end
				end
			end	
		
		-- fillType (text or icon)
		elseif cmds == "filltype" then
			dbgprint("fillType: option = "..tostring(dashboard.dblOption), 4)
			returnValue = false
			local t = dashboard.dblTrailer
			local _, device = findSpecialization(self, "spec_fillUnit", t)
			if device ~= nil then
				local o = lower(dashboard.dblOption)
				local p = dashboard.dblPartition or 1
				local fillUnit = device:getFillUnitByIndex(p)
				if fillUnit ~= nil then
					local fillTypeIndex = fillUnit.fillType
					
					if o == "name" then
						local ftName = g_fillTypeManager:getFillTypeTitleByIndex(fillTypeIndex)
						dbgprint("fillType: Name set to "..ftName, 4)
						returnValue = ftName
						
					elseif o == "icon" then
						local ftPath = g_fillTypeManager.fillTypes[fillTypeIndex] ~= nil and g_fillTypeManager.fillTypes[fillTypeIndex].hudOverlayFilename
						dbgprint("fillType: Path is "..tostring(ftPath), 4)
						if ftPath ~= nil and ftPath ~= "" then
							if ftPath ~= dashboard.ftPath then
								local materialId = getMaterial(dashboard.node, 0)
								materialId = setMaterialDiffuseMapFromFile(materialId, ftPath, true, true, false)
								setMaterial(dashboard.node, materialId, 0)
								dashboard.ftPath = ftPath
								dbgprint("fillType: Icon texture set to "..ftPath, 4)
							end
							returnValue = true
						end
					end
				end
			end
			
		-- hasSpec	
		elseif cmds == "hasspec" then
			returnValue = getAttachedStatus(self, dashboard, "hasspec", false)
			
		-- hasTypeDesc
		elseif cmds == "hastypedesc" then
			returnValue = getAttachedStatus(self, dashboard, "hastypedesc", false)
			
		-- tippingState
		elseif cmds == "tippingstate" then
			returnValue = getAttachedStatus(self, dashboard, "tippingstate", 0)
			
		-- ridgeMarker
		elseif cmds == "ridgemarker" then
			if s == "" or tonumber(s) == nil then
				Logging.xmlWarning(self.xmlFile, "No ridgeMarker state given for DashboardLive ridgeMarker command")
				returnValue = false
			end
			returnValue = getAttachedStatus(self, dashboard, "ridgemarker") == tonumber(s)
		
		-- foldingState
		elseif cmds == "foldingstate" then
			returnValue = getAttachedStatus(self, dashboard, "foldingstate", 0)
			
		elseif cmds == "unfoldingstate" then
			returnValue = getAttachedStatus(self, dashboard, "unfoldingstate", 0)
		
		-- lowering state
		elseif cmds == "liftstate" and self.spec_attacherJoints ~= nil then
			dbgprint("getDashboardLiveBase : liftstate:", 4)
			local joints = jointsToTable(j)
			returnValue = 0
			for i, jointIndex in ipairs(joints) do
				local attacherJoint = self.spec_attacherJoints.attacherJoints[tonumber(jointIndex)]
				if attacherJoint ~= nil and attacherJoint.moveAlpha ~= nil then
					returnValue = math.max(returnValue, 1 - attacherJoint.moveAlpha)
					dbgprint("getDashboardLiveBase : liftstate "..tostring(i)..": "..tostring(returnValue), 4)
					dbgrender("liftstate: "..tostring(returnValue), 1+2*i, 3)
				end
			end
			
		-- tipSide / tipSideText
		elseif cmds == "tipside" or cmds == "tipsidetext" then
			returnValue = getAttachedStatus(self, dashboard, cmds, 0)
			
		-- variable workwidth
		elseif cmds == "workwidth" then 
			local specVW = findSpecialization(self, "spec_variableWorkWidth", dashboard.dblTrailer)
			if specVW ~= nil then
				local isLeft = lower(o) == "left"
				returnValue = specVW:getVariableWorkWidth(isLeft)
			else
				returnValue = 1
			end
		
		-- real clock
		elseif cmds == "realclock" then
			returnValue = getDate("%T")
			
		-- heading
		elseif cmds == "heading" or cmds == "headingtext1" or cmds == "headingtext2" then
			local x1, y1, z1 = localToWorld(self.rootNode, 0, 0, 0)
			local x2, y2, z2 = localToWorld(self.rootNode, 0, 0, 1)
			local dx, dz = x2 - x1, z2 - z1
			local heading = math.floor(180 - (180 / math.pi) * math.atan2(dx, dz))
			if cmds == "heading" then
				returnValue = heading
			elseif cmds == "headingtext2" then
				local headingTexts = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"}
				local index = math.floor(((heading + 22.5) % 360) * 8 / 360) + 1
				dbgprint("heading: "..tostring(heading).." / index: "..tostring(index), 4)
				returnValue = headingTexts[index]
			else
				local headingTexts = {"N", "E", "S", "W"}
				local index = math.floor(((heading + 45) % 360) * 4 / 360) + 1
				returnValue = headingTexts[index]
			end

		-- field number
		elseif cmds == "fieldnumber" then
			local fieldNum = 0
			local x, _, z = getWorldTranslation(self.rootNode)
			local farmland = g_farmlandManager:getFarmlandAtWorldPosition(x, z)
			if farmland ~= nil then 
				fieldNum = farmland.id 
			end
			returnValue = fieldNum
			dbgprint("fieldnumber: "..tostring(returnValue), 4)
			
		elseif cmds == "motorfan" then
			if specMO ~= nil then
				local specFan = specMO.motorFan
				if specFan ~= nil then
					returnValue = specFan.enabled
				end
			end
		elseif cmds == "radio" then
			if o == "volume" then
				returnValue = g_gameSettings:getValue("radioVolume")
			elseif o == "station" then
				local senderName = g_soundPlayer.currentChannelName
				local len = string.len(dashboard.textMask or "xxxx")
				local alignment = dashboard.textAlignment or "LEFT"
				returnValue = trim(senderName, len, alignment)
			else
				returnValue = g_soundPlayer.isPlaying
			end
			
		-- empty command is allowed here to add symbols (EMITTER) in off-state, too
		elseif cmds == "" then
			returnValue = true
		end
		
		if dashboard.dblFactor ~= nil and type(returnValue) == "number" then
			returnValue = returnValue * dashboard.dblFactor
		end
		if dashboard.dblMin ~= nil and type(returnValue) == "number" then
			returnValue = math.max(returnValue, dashboard.dblMin)
		end
		if dashboard.dblMax ~= nil and type(returnValue) == "number" then
			returnValue = math.min(returnValue, dashboard.dblMax)
		end
		if dashboard.dblCond ~= nil and type(returnValue) == "number" and type(dashboard.dblCondValue) == "number" then
			local cond = dashboard.dblCond
			local value = dashboard.dblCondValue
			if cond == "less" then
				returnValue = (returnValue < value)
			elseif cond == "lessequal" then
				returnValue = (returnValue <= value)
			elseif cond == "more" then
				returnValue = (returnValue > value)
			elseif cond == "moreequal" then
				returnValue = (returnValue >= value)
			elseif cond == "equal" then
				returnValue = (returnValue == value)
			end
		end
		if dashboard.dblCond ~= nil and type(returnValue) == "boolean" then
			if dashboard.dblCond == "not" then
				returnValue = not returnValue
			end
		end
			
		return returnValue
	end
	
	return false
end

function DashboardLive.getDashboardLiveMiniMap(self, dashboard)
	dbgprint("getDashboardLiveMiniMap : dblCommand: "..tostring(dashboard.dblCommand), 4)
	
	local spec = self.spec_DashboardLive
	if spec == nil or dashboard.node == nil then 
		print("no dashboard node for minimap given")
		return false 
	end
	
	local cmd = dashboard.dblCommand
	
	-- position
	local node = self.steeringAxleNode or self.rootNode
	local x, _, z = localToWorld(node, 0, 0, 0)
	local xf, _, zf = localToWorld(node, 0, 0, 1)
	local dx, dz = xf - x, zf - z
	local quotient = 2 * g_currentMission.mapWidth

	-- heading
	local heading = math.atan2(dx, dz) + math.pi

	if cmd == "map" then
		if self == g_currentMission.hud.controlledVehicle then
			-- zoom
			local speed = self:getLastSpeed()
			local width = g_currentMission.mapWidth
			local scale = DashboardLive.scale
			local zoomFactor = math.clamp(speed / 50, 0, 1)
			local zoomTarget
	
			-- zoom-in on field
			local onField = FSDensityMapUtil.getFieldDataAtWorldPosition(x, 0, z)
			if onField then 
				zoomTarget = 0.33 --* (width/2048) 
			else
				zoomTarget = 1
			end
	
			--smooth zoom
			if zoomTarget - 0.02 > spec.zoomValue then
				spec.zoomValue = spec.zoomValue + 0.02
			elseif zoomTarget + 0.02 < spec.zoomValue then
				spec.zoomValue = spec.zoomValue - 0.02
			end
			local zoom = (spec.zoomValue + 0.25 * zoomFactor) / (width/2048)
	
			--orientations
			if spec.orientation == "north" then
				heading = 0
			elseif spec.orientation == "overview" then
				heading = 0
				zoom = (2048/width)
				scale = 0.5
				--x = 0
				--z = 0
			end
	
			setShaderParameter(dashboard.node, "map", x/quotient, -z/quotient, scale * zoom, heading)
			dbgprint("getDashboardLiveMiniMap : Finished with Pos "..tostring(x/quotient)..","..tostring(z/quotient).." / scale "..tostring(scale * zoom).. " / heading "..tostring(heading), 4)
		end
		return true
		
	elseif cmd == "posmarker" then
		if self == g_currentMission.hud.controlledVehicle then
			if spec.orientation == "rotate" then 
				heading = 0
			end			
			setRotation(dashboard.node, 0, heading, 0)
			dbgprint("getDashboardLiveMiniMap : Finished with heading "..tostring(heading), 4)
		end
		return true
	end
	return false
end

function DashboardLive.getDashboardLiveCombine(self, dashboard)
	dbgprint("getDashboardLiveCombine : dblCommand: "..tostring(dashboard.dblCommand), 4)
	local spec = self.spec_combine
	if dashboard.dblCommand ~= nil and spec ~= nil then
		
		local c = dashboard.dblCommand
		local s = dashboard.dblStateText or dashboard.dblState
		
		if c == "chopper" then
			if s == "enabled" then
				return not spec.isSwathActive
			elseif s == "active" then
				return spec.chopperPSenabled
			end
			
		elseif c == "swath" then
			if s == "enabled" then 
				return spec.isSwathActive
			elseif s == "active" then
				return spec.strawPSenabled
			end
			
		elseif c == "filling" then
			return spec.isFilling
		
		elseif c == "hectars" then
			return spec.workedHectars
			
		elseif c == "cutheight" then
			local specCutter = findSpecialization(self, "spec_cutter")
			if specCutter ~= nil then
				return specCutter.currentCutHeight
			end
		
		elseif c == "pipestate" then
			local specPipe = self.spec_pipe
			if specPipe ~= nil and s ~= nil and tonumber(s) ~= nil then
				return specPipe.currentState == tonumber(s)
			end
			
		elseif c == "pipefolding" then
			local specPipe = self.spec_pipe
			if specPipe ~= nil then
				return specPipe.currentState ~= specPipe.targetState
			end
		
		elseif c == "pipefoldingstate" then
			local specPipe = self.spec_pipe
			if specPipe ~= nil then
				local returnValue = specPipe:getAnimationTime(specPipe.animation.name) * dashboard.dblFactor
				dbgprint("pipeFoldingState: "..tostring(returnValue), 4)
				return returnValue
			end		
			
		elseif c == "overloading" then
			local spec_dis = self.spec_dischargeable
			if spec_dis ~= nil then
				if s ~= nil and tonumber(s) ~= nil then
					return spec_dis:getDischargeState() == tonumber(s)
				else
					return spec_dis:getDischargeState() > 0
				end
			end
		end
	end
	
	--return false
end

function DashboardLive.getDashboardLiveRDA(self, dashboard)
	dbgprint("getDashboardLiveRDA : dblCommand: "..tostring(dashboard.dblCommand), 4)
	local specRDA = self.spec_tirePressure
	
	if specRDA ~= nil and dashboard.dblCommand ~= nil then
		local c = dashboard.dblCommand
		local o = dashboard.dblOption
		local factor = dashboard.dblFactor
		
		if c == "inflating" then
			return specRDA.isInflating
			
		elseif c == "pressure" then
			if o == "target" then
				return specRDA.inflationPressureTarget * factor
			elseif o == "min" then
				return specRDA.pressureMin * factor
			elseif o == "max" then
				return specRDA.pressureMax * factor
			else
				return specRDA.inflationPressure * factor
			end
			
		elseif c == "maxSpeed" then
			return specRDA.maxSpeed
		end
	end
end

function DashboardLive.getDashboardLiveVCA(self, dashboard)
	dbgprint("getDashboardLiveVCA : dblCommand: "..tostring(dashboard.dblCommand), 4)
	
	local returnValue = false
	if dashboard.dblCommand ~= nil then
		local spec = self.spec_DashboardLive
		local c = dashboard.dblCommand

		if c == "park" then
			if (spec.modVCAFound and self:vcaGetState("handbrake")) or (spec.modEVFound and self.vData.is[13]) then 
				returnValue = true
			end
		elseif c == "diff_front" then
			returnValue = (spec.modVCAFound and self:vcaGetState("diffLockFront")) or (spec.modEVFound and self.vData.is[1])
	
		elseif c == "diff_back" then
			returnValue = (spec.modVCAFound and self:vcaGetState("diffLockBack")) or (spec.modEVFound and self.vData.is[2])
	
		elseif c == "diff" then
			returnValue = (spec.modVCAFound and (self:vcaGetState("diffLockFront") or self:vcaGetState("diffLockBack"))) 
					or (spec.modEVFound and (self.vData.is[1] or self.vData.is[2]))
	
		elseif c == "diff_awd" then
			returnValue = (spec.modVCAFound and self:vcaGetState("diffLockAWD")) or (spec.modEVFound and self.vData.is[3]==1)
		
		elseif c == "diff_awdf" then
			returnValue = spec.modVCAFound and self:vcaGetState("diffFrontAdv")
	
		elseif c == "ks" then
			returnValue = spec.modVCAFound and self:vcaGetState("ksIsOn")
			
		elseif c == "ksvalue" then
			returnValue = spec.modVCAFound and self:vcaGetState("ksIsOn") and math.floor(self:vcaGetState("keepSpeed") * 10) / 10 or 0
			
		elseif c == "slip" then
			local slipVCA = spec.modVCAFound and self.spec_vca.wheelSlip ~= nil and (self.spec_vca.wheelSlip - 1) * 100 or 0
			local slipREA = self.spec_wheels ~= nil and self.spec_wheels.SlipSmoothed ~= nil and self.spec_wheels.SlipSmoothed or 0
			returnValue = math.max(slipVCA, slipREA)
			
		elseif c == "speed2" then
			returnValue = spec.modVCAFound and self:vcaGetState("ccSpeed2")
			
		elseif c == "speed3" then
			returnValue = spec.modVCAFound and self:vcaGetState("ccSpeed3")
		end
	end
	
	if dashboard.dblCond ~= nil and type(returnValue) == "number" and type(dashboard.dblCondValue) == "number" then
		local cond = dashboard.dblCond
		local value = dashboard.dblCondValue
		if cond == "less" then
			returnValue = (returnValue < value)
		elseif cond == "lessequal" then
			returnValue = (returnValue <= value)
		elseif cond == "more" then
			returnValue = (returnValue > value)
		elseif cond == "moreequal" then
			returnValue = (returnValue >= value)
		elseif cond == "equal" then
			returnValue = (returnValue == value)
		end
	end
		
	if dashboard.dblCond ~= nil and type(returnValue) == "boolean" then
		if dashboard.dblCond == "not" then
			returnValue = not returnValue
		end
	end
	
	dbgprint("getDashboardLiveVCA : result: "..tostring(returnValue), 4)
	return returnValue
end

function DashboardLive.getDashboardLiveCC(self, dashboard)
	dbgprint("getDashboardLiveCC : dblCommand: "..tostring(dashboard.dblCommand).." / dblState: "..tostring(dashboard.dblState), 4)
	local spec = self.spec_DashboardLive
	local specECC = self.spec_extendedCruiseControl
	local state = tonumber(dashboard.dblState)
	local mode = 0
	
	if specECC ~= nil then 
		mode = 1
	elseif spec.modSpeedControlFound then
	 	mode = 2
	 elseif self.spec_drivable ~= nil then
	 	mode = 3
	end
	
	if dashboard.dblCommand == "active" then
		if mode == 1 then
			if state ~= nil then
				return specECC.activeSpeedGroup == state
			else 
				return specECC.activeSpeedGroup
			end		
		elseif mode == 2 then
			local specCC = self.speedControl
			if state ~= nil then
				return specCC.currentKey == state
			else
				return specCC.currentKey
			end
		elseif mode == 3 then
			local specCC = self.spec_drivable.cruiseControl
			if state ~= nil then 
				return specCC.state and state == 3
			else
				return specCC.state and 3
			end
		end
	end	
	
	if dashboard.dblCommand == "speed" and state ~= nil then
		if mode == 1 then
			return specECC.cruiseSpeedGroups[state].forward
		elseif mode == 2 then
			local specCC = self.speedControl
			return specCC.keys[state].speed
		elseif mode == 3 then
			local specCC = self.spec_drivable.cruiseControl
			return state == 3 and specCC.speed
		end
	end
	
	return false
end

function DashboardLive.getDashboardLiveHLM(self, dashboard)
	dbgprint("getDashboardLiveHLM : dblOption: "..tostring(dashboard.dblOption), 4)
	local spec = self.spec_DashboardLive
	local specHLM = self.spec_HeadlandManagement
	
	local c = dashboard.dblCommand
	local o = dashboard.dblOption
	local returnValue = false

	if specHLM ~= nil and specHLM.exists then
	
		if c == "" or c == "mode" then
			if o == "field" then
				returnValue = specHLM.isOn and not specHLM.isActive and (specHLM.contour == 0 or specHLM.contour == nil)
			elseif o == "headland" then
				returnValue = specHLM.isOn and specHLM.isActive
			elseif o == "contour" then
				returnValue = specHLM.isOn and not specHLM.isActive and (specHLM.contour ~= 0 and specHLM.contour ~= nil)
			else
				returnValue = specHLM.isOn
				if dashboard.dblCond ~= nil and type(returnValue) == "boolean" then
					if dashboard.dblCond == "not" then
						returnValue = not returnValue
					end
				end
			end
		end
		if c == "automatic" then
			local headlandAutomatic	  = not specHLM.autoOverride and (specHLM.useHLMTriggerF or specHLM.useHLMTriggerB)
			local headlandAutomaticGS = not specHLM.autoOverride and (specHLM.modGuidanceSteeringFound and specHLM.useGuidanceSteeringTrigger) 
			local headlandAutomaticResume = specHLM.autoResume and not specHLM.autoOverride 
		
			if o == "field" then
				returnValue = specHLM.isOn and not specHLM.isActive and (headlandAutomatic or headlandAutomaticGS)
			elseif o == "headland" then
				returnValue = specHLM.isOn and specHLM.isActive and headlandAutomaticResume
			elseif o == "override" then
				returnValue = specHLM.isOn and specHLM.autoOverride
			end
		end
	end	

	return returnValue
end

function DashboardLive.getDashboardLiveGPS(self, dashboard)
	dbgprint("getDashboardLiveGPS : dblOption: "..tostring(dashboard.dblOption), 4)
	local spec = self.spec_DashboardLive
	local specAI = self.spec_aiAutomaticSteering
	local specGS = self.spec_globalPositioningSystem
	local specHLM = self.spec_HeadlandManagement
	local o = dashboard.dblOption
	
	local returnValue = false
	
	if o == "on" then
		--returnValue = specAI ~= nil and specAI.steeringFieldCourse ~= nil
		returnValue = specAI ~= nil and self:getAIAutomaticSteeringState() > 1
		returnValue = returnValue or specGS ~= nil and specGS.lastInputValues ~= nil and specGS.lastInputValues.guidanceIsActive
		returnValue = returnValue or (spec.modVCAFound and self:vcaGetState("snapDirection") ~= 0) 
		returnValue = returnValue or (spec.modEVFound and self.vData.is[5])
		
		if dashboard.dblCond ~= nil and type(returnValue) == "boolean" then
			if dashboard.dblCond == "not" then
				returnValue = not returnValue
			end
		end
	
	elseif o == "active" then
		returnValue = specAI ~= nil and self:getAIAutomaticSteeringState() > 2
		returnValue = returnValue or (specGS ~= nil and specGS.lastInputValues ~= nil and specGS.lastInputValues.guidanceSteeringIsActive)
		returnValue = returnValue or (spec.modVCAFound and self:vcaGetState("snapIsOn")) 
		returnValue = returnValue or (spec.modEVFound and self.vData.is[5])
		
		if dashboard.dblCond ~= nil and type(returnValue) == "boolean" then
			if dashboard.dblCond == "not" then
				returnValue = not returnValue
			end
		end

	elseif o == "lane+" then
		returnValue = specGS ~= nil and specGS.lastInputValues ~= nil and specGS.lastInputValues.guidanceIsActive
		returnValue = returnValue and specGS.guidanceData ~= nil and specGS.guidanceData.currentLane ~= nil and specGS.guidanceData.currentLane >= 0	

	elseif o == "lane-" then
		returnValue = specGS ~= nil and specGS.lastInputValues ~= nil and specGS.lastInputValues.guidanceIsActive
		returnValue = returnValue and specGS.guidanceData ~= nil and specGS.guidanceData.currentLane ~= nil and specGS.guidanceData.currentLane < 0
	end	
	
	return returnValue
end

function DashboardLive.getDashboardLiveGPSLane(self, dashboard)
	dbgprint("getDashboardLiveGPS : dblOption: "..tostring(dashboard.dblOption), 4)
	local o = string.lower(dashboard.dblOption or "")
	local spec = self.spec_DashboardLive
	local specGS = self.spec_globalPositioningSystem
	local returnValue = 0
	
	local factor = dashboard.dblFactor or 1
	if spec.modGuidanceSteeringFound and specGS ~= nil and specGS.guidanceData ~= nil and specGS.guidanceData.currentLane ~= nil then
		returnValue = math.abs(specGS.guidanceData.currentLane) * factor
	end
	
	if o == "delta" or o == "dir" or o == "dirleft" or o == "dirright" then
		local gsValue = specGS ~= nil 
						and specGS.guidanceData ~= nil 
						and specGS.guidanceData.alphaRad ~= nil 
						and specGS.guidanceData.snapDirectionMultiplier ~= nil 
						and math.floor(specGS.guidanceData.alphaRad * specGS.guidanceData.snapDirectionMultiplier * 100) / 100 
						or 0
		
		if o == "delta" then
			returnValue = gsValue * factor
		end
		
		if o == "dir" and gsValue < 0 then
			returnValue = -1
		elseif o == "dir" and gsValue > 0 then
			returnValue = 1
		elseif o == "dir" then
			returnValue = 0
		end
		
		if o == "dirleft" then
			returnValue = gsValue < -0.02
		elseif o == "dirright" then
			returnValue = gsValue > 0.02
		end		
	end
	
	if o == "headingdelta" then
		local x1, y1, z1 = localToWorld(self.rootNode, 0, 0, 0)
		local x2, y2, z2 = localToWorld(self.rootNode, 0, 0, 1)
		local dx, dz = x2 - x1, z2 - z1
		
		local heading = math.floor(180 - (180 / math.pi) * math.atan2(dx, dz))
		local snapAngle = 0
		
		-- we need to find the snap angle, specific to the Guidance Mod used
		-- Guidance Steering
		if specGS ~= nil and specGS.lastInputValues ~= nil and specGS.lastInputValues.guidanceIsActive then
			if specGS.guidanceData ~= nil and specGS.guidanceData.snapDirection ~= nil then
				local lineDirX, lineDirZ = unpack(specGS.guidanceData.snapDirection)
				snapAngle = -math.deg(math.atan(lineDirX/lineDirZ))
			end
		-- VCA
		elseif (spec.modVCAFound and self:vcaGetState("snapDirection") ~= 0)  then
			local curSnapAngle, _, curSnapOffset = self:vcaGetCurrentSnapAngle( math.atan2(dx, dz) )
			snapAngle = 180 - math.deg(curSnapAngle)
		end

		local offset = heading - snapAngle
		if offset > 180 then 
			offset = offset - 360
		end
		if offset > 90 then
			offset = offset - 180
		end
		if offset < -90 then
			offset = offset + 180
		end
		returnValue = offset * factor
	end
	
	if dashboard.dblMin ~= nil and type(returnValue) == "number" then
		returnValue = math.max(returnValue, dashboard.dblMin)
	end
	if dashboard.dblMax ~= nil and type(returnValue) == "number" then
		returnValue = math.min(returnValue, dashboard.dblMax)
	end
	
	return returnValue
end

function DashboardLive.getDashboardLiveGPSWidth(self, dashboard)
	dbgprint("getDashboardLiveGPSWidth : dblOption: "..tostring(dashboard.dblOption), 4)
	local spec = self.spec_DashboardLive
	local specAI = self.spec_aiAutomaticSteering
	local specGS = self.spec_globalPositioningSystem
	local returnValue = 0
	local factor = dashboard.dblFactor or 1
	
	if spec.modGuidanceSteeringFound and specGS ~= nil and specGS.guidanceData ~= nil and specGS.guidanceData.width ~= nil then
		returnValue = specGS.guidanceData.width * factor
	end
	if spec.modVCAFound and self:vcaGetState("snapDirection") ~= 0 then 
		returnValue = self.spec_vca.snapDistance * factor
	end
	if specAI ~= nil and returnValue == 0 then
		returnValue = self:getAttacherToolWorkingWidth() * factor
	end
	if dashboard.dblMin ~= nil and type(returnValue) == "number" then
		returnValue = math.max(returnValue, dashboard.dblMin)
	end
	if dashboard.dblMax ~= nil and type(returnValue) == "number" then
		returnValue = math.min(returnValue, dashboard.dblMax)
	end
	dbgprint("getDashboardLiveGPSWidth : returnValue: "..tostring(returnValue), 4)
	return returnValue
end
		
function DashboardLive.getDashboardLivePS(self, dashboard)
	dbgprint("getDashboardLivePS : running", 4)
	local o, s = dashboard.dblOption, dashboard.dblState
	local specPS = findSpecialization(self, "spec_proSeedTramLines")
	local specSE = findSpecialization(self, "spec_proSeedSowingExtension")
	local returnValue = " "
	
	if specPS ~= nil and specSE ~= nil then
		if o == "mode" then
			if tonumber(s) ~= nil then
				returnValue = specPS.tramLineMode == tonumber(s)
			elseif FS22_proSeed ~= nil and FS22_proSeed.ProSeedTramLines ~= nil then
				local mode = specPS.tramLineMode
				local text = FS22_proSeed.ProSeedTramLines.TRAMLINE_MODE_TO_KEY[mode]
				local len = string.len(dashboard.textMask or "xxxx")
				returnValue = trim(g_i18n.modEnvironments["FS22_proSeed"]:getText(("info_mode_%s"):format(text)), len)
			end
		elseif o == "distance" then
			returnValue = specPS.tramLineDistance
		elseif o == "lanedrive" then
			returnValue = specPS.currentLane
		elseif o == "lanefull" then
			local maxLine = specPS.tramLinePeriodicSequence
			if maxLine == 2 and specPS.tramLineDistanceMultiplier == 1 then maxLine = 1 end
			returnValue = maxLine
		elseif o == "tram" then
			returnValue = specPS.createTramLines
		elseif o == "fert" then
			returnValue = specSE.allowFertilizer
		elseif o == "areawork" then
			returnValue = specSE.sessionHectares
		elseif o == "areafield" then
			returnValue = specSE.totalHectares
		elseif o == "timeuse" then
			returnValue = specSE.hectarePerHour
		elseif o == "seeduse" then
			returnValue = specSE.seedUsage
		elseif o == "segment" then
			local state = tonumber(s) or 0
			returnValue = specPS.shutoffMode == state
		elseif o == "tramtype" then
			returnValue = specPS.createPreMarkedTramLines
		elseif o == "audio" then
			returnValue = specSE.allowSound
		end	
		if dashboard.dblFactor ~= nil and type(returnValue) == "number" then
			returnValue = returnValue * dashboard.dblFactor
		end
		if dashboard.dblMin ~= nil and type(returnValue) == "number" then
			returnValue = math.max(returnValue, dashboard.dblMin)
		end
		if dashboard.dblMax ~= nil and type(returnValue) == "number" then
			returnValue = math.min(returnValue, dashboard.dblMax)
		end
	elseif o == "tram" or o == "fert" or o == "segment" or o == "tramtype" or o == "audio" then
		returnValue = false
	end
	return returnValue
end

function DashboardLive.getDashboardLiveSelection(self, dashboard)
	dbgprint("getDashboardLiveSelection : dblSelection, dblSelectionGroup: "..tostring(dashboard.dblSelection)..", "..tostring(dashboard.dblSelectionGroup), 4)

	local spec = self.spec_DashboardLive
	local s, g = dashboard.dblSelection, dashboard.dblSelectionGroup
	
-- vanilla game selector
	if s ~= nil then
		local selectorActive = false
		if type(s) == "number" and s == -100 then
			return spec.selectorActive < 0
		elseif type(s) == "number" and s == 100 then
			return spec.selectorActive > 0
		elseif type(s) == "number" then
			return spec.selectorActive == s
		else
			for _,selector in ipairs(s) do
				if selector == spec.selectorActive then selectorActive = true end
			end
			return selectorActive
		end
-- vanilla game selector group
	elseif g ~= nil then
		local groupActive = false
		if type(g) == "number" then
			return spec.selectorGroup == g
		else
			for _,selGroup in ipairs(g) do
				if selGroup == spec.selectorGroup then groupActive = true end
			end
		end
		return groupActive
	end
	return false
end		

function DashboardLive.getDashboardLiveBaler(self, dashboard)
	dbgprint("getDashboardLiveBaler : dblCommand: "..tostring(dashboard.dblCommand), 4)
	local spec = self.spec_DashboardLive
	local c = dashboard.dblCommand
	if c == "isroundbale" then
		return getAttachedStatus(self, dashboard, "isroundbale", 0)
	elseif c == "balesize" then
		return getAttachedStatus(self, dashboard, "balesize", 0)
	elseif c == "balecountanz" then
		return getAttachedStatus(self, dashboard, "balecountanz", 0)
	elseif c == "balecounttotal" then
		return getAttachedStatus(self, dashboard, "balecounttotal", 0)
	elseif c == "wrappedbalecountanz" then
		return getAttachedStatus(self, dashboard, "wrappedbalecountanz", 0)
	elseif c == "wrappedbalecounttotal" then
		return getAttachedStatus(self, dashboard, "wrappedbalecounttotal", 0)
	end
end

function DashboardLive.getDashboardLiveLSA(self, dashboard)
	local returnValue = getAttachedStatus(self, dashboard, "locksteeringaxle", false)
	dbgprint("getDashboardLiveLSA : returnValue: "..tostring(returnValue), 4)
	return returnValue
end

function DashboardLive.getDashboardLiveCXP(self, dashboard)
	dbgprint("getDashboardLiveCXP : dblCommand: "..tostring(dashboard.dblCommand), 4)
	local specXP = findSpecialization(self, "spec_xpCombine")
	local c, f = dashboard.dblCommand, dashboard.dblFactor
	if specXP ~= nil and specXP.mrCombineLimiter ~= nil then
		local returnValue
		local mr = specXP.mrCombineLimiter
		if c == "tonperhour" then
			returnValue = mr.tonPerHour
		elseif c == "engineload" then
			returnValue = mr.engineLoad * mr.loadMultiplier * f
		elseif c == "yield" then
			returnValue = mr.yield
			if returnValue ~= returnValue then
				returnValue = 0
			end
		elseif c == "highmoisture" then
			returnValue = mr.highMoisture
		end
		dbgprint("combineXP returnValue: "..tostring(mr[c]), 4)
		return returnValue
	elseif c == "highmoisture" then
		dbgprint("combineXP returnValue ("..tostring(self:getFullName()).."): false (spec not found)", 4)
		return false
	end
	dbgprint("combineXP returnValue ("..tostring(self:getFullName()).."): none (spec not found)", 4)
end

function DashboardLive.getDashboardLivePrint(self, dashboard)
	dbgprint("getDashboardLivePrint : dblOption: "..tostring(dashboard.dblOption), 4)
	
	local len = string.len(dashboard.textMask or "xxxx")
	local alignment = dashboard.textAlignment or "LEFT"
	
	return trim(dashboard.dblOption or "", len, alignment)
end

function DashboardLive.getDashboardLiveFrontloader(self, dashboard)
	dbgprint("getDashboardLiveFrontloader : dblCommand: "..tostring(dashboard.dblCommand), 4)
	local c = dashboard.dblCommand
	local returnValue = 0
	if c == "toolrotation" or c == "tooltranslation" or c == "istoolrotation" or c == "istooltranslation" then
		returnValue = getAttachedStatus(self, dashboard, c)
		dbgprint("getDashboardLiveFrontloader : "..c..": returnValue: "..tostring(returnValue), 4)
	end
	return returnValue
end

function DashboardLive.getDashboardLivePrecisionFarming(self, dashboard)
	dbgprint("getDashboardLivePrecisionFarming : dblCommand: "..tostring(dashboard.dblCommand), 4)
	
	-- lets find any attached vehicle with a extendedSprayer specialization.
	-- in the end, we can only deal with one of them (same as precision farming dlc content)	
	local c = dashboard.dblCommand
	local o = dashboard.dblOption
	local t = dashboard.dblTrailer
	
	local returnValue = 0
	
	local specCropSensor
	if tonumber(t) ~= nil then 
		specCropSensor = findSpecialization(self, "spec_FS25_precisionFarming.cropSensor", t or 1)
	else
		specCropSensor = findLastSpecialization(self, "spec_FS25_precisionFarming.cropSensor")
	end
	
	if c == "cropsensor" then
		if specCropSensor ~= nil then
			dbgprint("cropSensor: returnValue: "..tostring(specCropSensor.isActive), 4)
			return specCropSensor.isActive
		else
			dbgprint("cropSensor: returnValue: set to false", 4)
			return false
		end
	end
	
	local specExtendedSprayer, pfVehicle
	if tonumber(t) ~= nil then
		specExtendedSprayer, pfVehicle = findSpecialization(self, "spec_FS25_precisionFarming.extendedSprayer", t)
	else
		specExtendedSprayer, pfVehicle = findLastSpecialization(self, "spec_FS25_precisionFarming.extendedSprayer")
	end
	if specExtendedSprayer ~= nil then
		dbgprint("found spec spec_FS25_precisionFarming.extendedSprayer in "..tostring(pfVehicle:getName()), 4)

		local sourceVehicle, fillUnitIndex = FS25_precisionFarming.ExtendedSprayer.getFillTypeSourceVehicle(pfVehicle)
		local hasLimeLoaded, hasFertilizerLoaded = FS25_precisionFarming.ExtendedSprayer.getCurrentSprayerMode(pfVehicle)
	
		local returnValueFormat = "%.2f t/ha"
		
		-- soil type 
		if c == "soiltype" then
			if specExtendedSprayer.lastTouchedSoilType ~= 0 and specExtendedSprayer.soilMap ~= nil then
				local soilType = specExtendedSprayer.soilMap:getSoilTypeByIndex(specExtendedSprayer.lastTouchedSoilType)
				if soilType ~= nil and soilType.name ~= nil then
					--local len = string.len(dashboard.textMask or "xxxx")
					--local alignment = dashboard.textAlignment or RenderText.ALIGN_RIGHT
					--return trim(soilType.name, len, alignment)
					returnValue = soilType.name
				else 
					returnValue = false
				end
			end
		end	
		
		local sprayAmountAutoMode = specExtendedSprayer.sprayAmountAutoMode
		-- sprayAmountAutoMode
		if c == "sprayamountautomode" then
			return sprayAmountAutoMode
		end	
		
		local sprayFillType = sourceVehicle:getFillUnitFillType(fillUnitIndex)
		local fillTypeDesc = g_fillTypeManager:getFillTypeByIndex(sprayFillType)
		local massPerLiter = (fillTypeDesc.massPerLiter / FillTypeManager.MASS_SCALE)
		local applicationRate = 0
		
		-- lime values
		if hasLimeLoaded and (c == "phactual" or c == "phtarget" or c == "phchanged" or c == "applicationrate") then
		
			local phMap = specExtendedSprayer.pHMap
            local phActualInt = specExtendedSprayer.phActualValue 
            local phTargetInt = specExtendedSprayer.phTargetValue 
            
            local phActual = phMap:getPhValueFromInternalValue(phActualInt) or 0
            if c == "phactual" then
            	returnValue = phActual
            end
            
            local phTarget = phMap:getPhValueFromInternalValue(phTargetInt)	or 0		
			if c == "phtarget" then
            	returnValue = phTarget
            end
            
			local phChanged = 0
			if sprayAmountAutoMode then
                phChanged = phTarget - phActual
				applicationRate = specExtendedSprayer.lastLitersPerHectar * massPerLiter
			else 
				local requiredLitersPerHa = phMap:getLimeUsageByStateChange(specExtendedSprayer.sprayAmountManual)
            	phChanged = phMap:getPhValueFromChangedStates(specExtendedSprayer.sprayAmountManual)
				applicationRate = requiredLitersPerHa * massPerLiter
			end
			if c == "applicationrate" then
				returnValue = applicationRate
			end
			if c == "phchanged" then
				returnValue = phChanged
			end
			
            dbgrender("phActual: "..tostring(phActual),13,3)
            dbgrender("phTarget: "..tostring(phTarget),14,3)
            dbgrender("phChanged: "..tostring(phChanged),15,3)
            dbgrender("sprayAmountAutoMode: "..tostring(sprayAmountAutoMode),16,3)
			dbgrender("applicationRate: "..tostring(applicationRate),17,3)
		
		-- fertilizer part
		elseif hasFertilizerLoaded and (c == "nactual" or c == "ntarget" or c == "nchanged" or c == "applicationrate") then
			
			local nitrogenMap = specExtendedSprayer.nitrogenMap
			local nActualInt = specExtendedSprayer.nActualValue
			local nTargetInt = specExtendedSprayer.nTargetValue
			
			local nActual = nitrogenMap:getNitrogenValueFromInternalValue(nActualInt) or 0
			if c == "nactual" then 
				returnValue = nActual
			end
			
			local nTarget = nitrogenMap:getNitrogenValueFromInternalValue(nTargetInt) or 0
			if c == "ntarget" then
				returnValue = nTarget
			end	
			
			local nitrogenChanged = 0
			if sprayAmountAutoMode then
                nitrogenChanged = nTarget - nActual
			else 
            	nitrogenChanged = nitrogenMap:getNitrogenFromChangedStates(specExtendedSprayer.sprayAmountManual) or 0
			end
			if c == "nchanged" then
				returnValue = nitrogenChanged
			end
			
			local litersPerHectar
			if sprayAmountAutoMode then
				litersPerHectar = specExtendedSprayer.lastLitersPerHectar
			else
				litersPerHectar = nitrogenMap:getFertilizerUsageByStateChange(specExtendedSprayer.sprayAmountManual, sprayFillType)
			end
			
			if specExtendedSprayer.isSolidFertilizerSprayer then
				returnValueFormat = "%d kg/ha"
				applicationRate = litersPerHectar * massPerLiter * 1000
			elseif specExtendedSprayer.isLiquidFertilizerSprayer then
				returnValueFormat = "%d l/ha"
				applicationRate = litersPerHectar
			elseif specExtendedSprayer.isSlurryTanker then
				returnValueFormat = "%.1f mÂ³/ha"
				applicationRate = litersPerHectar / 1000
			elseif specExtendedSprayer.isManureSpreader then
				returnValueFormat = "%.1f t/ha"
				applicationRate = litersPerHectar * massPerLiter
			end
			if c == "applicationrate" then
				returnValue = applicationRate
			end
			
            dbgrender("nActual: "..tostring(nActual),13,3)
            dbgrender("nTarget: "..tostring(nTarget),14,3)
            dbgrender("nChanged: "..tostring(nitrogenChanged),15,3)
            dbgrender("sprayAmountAutoMode: "..tostring(sprayAmountAutoMode),16,3)
            dbgrender("litersPerHectar: "..tostring(litersPerHectar),17,3)
			dbgrender("applicationRate: "..tostring(applicationRate),18,3)
        end
		
		if o == "text" and type(returnValue) == "number" then
			dbgprint("returnValueFormat: "..tostring(returnValueFormat), 4)
			dbgprint("returnValue: "..tostring(returnValue), 4)
			returnValue = string.format(returnValueFormat, tostring(returnValue))
		end
	
		if dashboard.textMask ~= nil and returnValue ~= nil then
			local len = string.len(dashboard.textMask)
			local alignment = dashboard.textAlignment or RenderText.ALIGN_RIGHT
			dbgprint("trimmed returnValue: "..tostring(returnValue), 4)
			returnValue = trim(tostring(returnValue), len, alignment)
		end
		dbgrender("returnValue: "..tostring(returnValue), 19, 3)
	end
	return returnValue
end

function DashboardLive.getDashboardLiveCVT(self, dashboard)
	dbgprint("getDashboardLiveCVT : dblCommand: "..tostring(dashboard.dblCommand), 4)
	dbgprint("getDashboardLiveCVT : dblState: "..tostring(dashboard.dblState), 4)
	local c = dashboard.dblCommand
	local s = dashboard.dblState
	local returnValue = false
	
	local spec = self.spec_CVTaddon
	if spec ~= nil and type(c)=="string" then
		local cvtValueFunc = "forDBL_"..c
		local cvtValue = spec[cvtValueFunc]
		dbgprint("cvtValue = "..tostring(cvtValue), 4)
		if s ~= nil then
			if tonumber(s) ~= nil then
				returnValue = tostring(cvtValue) == tostring(s)
			else
				local states = string.split(tostring(s), " ")
				if states ~= nil and type(states) == "table" then
					for _, state in pairs(states) do
						returnValue = returnValue or (tostring(cvtValue) == tostring(state))
					end
				end
			end
		else 
			returnValue = cvtValue or false
		end
		
		if dashboard.dblCond ~= nil and type(returnValue) == "number" and type(dashboard.dblCondValue) == "number" then
			local cond = dashboard.dblCond
			local value = dashboard.dblCondValue
			if cond == "less" then
				returnValue = (returnValue < value)
			elseif cond == "lessequal" then
				returnValue = (returnValue <= value)
			elseif cond == "more" then
				returnValue = (returnValue > value)
			elseif cond == "moreequal" then
				returnValue = (returnValue >= value)
			elseif cond == "equal" then
				returnValue = (returnValue == value)
			end
		end
		if dashboard.dblCond ~= nil and type(returnValue) == "boolean" then
			if dashboard.dblCond == "not" then
				returnValue = not returnValue
			end
		end
	end
	
	dbgprint("getDashboardLiveCVT : returnValue: "..tostring(returnValue), 4)
	return returnValue
end

function DashboardLive.getDashboardLiveRDS(self, dashboard)
	dbgprint("getDashboardLiveRDS : dblCommand: "..tostring(dashboard.dblCommand), 4)
	dbgprint("getDashboardLiveRDS : dblState: "..tostring(dashboard.dblState), 4)
	local c = dashboard.dblCommand
	local s = dashboard.dblState
	local returnValue = false
	
	local spec = self.spec_RealisticDamageSystem
	if spec ~= nil and type(c)=="string" then
		local rdsValueFunc = "forDBL_"..c
		local rdsValue = spec[rdsValueFunc]
		if s ~= nil then
			if tonumber(s) ~= nil then
				returnValue = tostring(rdsValue) == tostring(s)
			else
				local states = string.split(tostring(s), " ")
				if states ~= nil and type(states) == "table" then
					for _, state in pairs(states) do
						returnValue = returnValue or (tostring(rdsValue) == tostring(state))
					end
				end
			end
		else 
			returnValue = rdsValue or false
		end
		
		if dashboard.dblCond ~= nil and type(returnValue) == "number" and type(dashboard.dblCondValue) == "number" then
			local cond = dashboard.dblCond
			local value = dashboard.dblCondValue
			if cond == "less" then
				returnValue = (returnValue < value)
			elseif cond == "lessequal" then
				returnValue = (returnValue <= value)
			elseif cond == "more" then
				returnValue = (returnValue > value)
			elseif cond == "moreequal" then
				returnValue = (returnValue >= value)
			elseif cond == "equal" then
				returnValue = (returnValue == value)
			end
		end
		if dashboard.dblCond ~= nil and type(returnValue) == "boolean" then
			if dashboard.dblCond == "not" then
				returnValue = not returnValue
			end
		end
	end
	
	dbgprint("getDashboardLiveRDS : returnValue: "..tostring(returnValue), 4)
	return returnValue
end

function DashboardLive:onUpdate(dt)
	local spec = self.spec_DashboardLive
	local specDis = self.spec_dischargeable
	local dspec = self.spec_dashboard
	local mspec = self.spec_motorized
	
	
	-- get active vehicle
	if self:getIsActiveForInput(true) then
		spec.selectorActive = getIndexOfActiveImplement(self:getRootVehicle())
		spec.selectorGroup = self.currentSelection.subIndex or 0
	end
	

	
	-- sync server to client data
	if self.isServer then
		local setDirty = false
		
		-- sync currentDischargeState with server
		if specDis ~= nil then
			spec.currentDischargeState = specDis.currentDischargeState
			if spec.currentDischargeState ~= spec.lastDischargeState then
				spec.lastDischargeState = spec.currentDischargeState
				setDirty = true
			end
		end
	
		-- sync motor temperature
		if self.getIsMotorStarted ~= nil and self:getIsMotorStarted() then
			spec.motorTemperature = mspec.motorTemperature.value
			spec.fanEnabled = mspec.motorFan.enabled
			spec.lastFuelUsage = mspec.lastFuelUsage
			spec.lastDefUsage = mspec.lastDefUsage
			spec.lastAirUsage = mspec.lastAirUsage
			
			if spec.motorTemperature ~= self.spec_motorized.motorTemperature.valueSend then
				setDirty = true
			end
			
			if spec.fanEnabled ~= spec.fanEnabledLast then
				spec.fanEnabledLast = spec.fanEnabled
				setDirty = true
			end
			
		end
		if setDirty then
			self:raiseDirtyFlags(spec.dirtyFlag)
		end
	end
		
	-- sync client from server data
	if self.isClient and not self.isServer then
	
		-- sync motor data
		if self.getIsMotorStarted ~= nil and self:getIsMotorStarted() then
			mspec.motorTemperature.value = spec.motorTemperature
			mspec.motorFan.enabled = spec.fanEnabled
			mspec.lastFuelUsage = spec.lastFuelUsage
			mspec.lastDefUsage = spec.lastDefUsage
			mspec.lastAirUsage = spec.lastAirUsage
		end
		
		-- sync currentDischargeState from server
		if specDis ~= nil then
			specDis.currentDischargeState = spec.currentDischargeState
		end
	end
		
	-- switch light/dark mode
	if spec.isDirty then
	
		-- force update of all dashboards
		self:updateDashboards(dspec.groupDashboards, dt, true)
		self:updateDashboards(dspec.tickDashboards, dt, true)
		self:updateDashboards(dspec.criticalDashboards, dt, true)
		for _, dashboards in pairs(dspec.dashboardsByValueType) do
			self:updateDashboards(dashboards, dt, true)
		end
	
		spec.isDirty = false
		spec.darkModeLast = spec.darkMode
	end
end

function DashboardLive:onDraw()
	local spec = self.spec_DashboardLive

-- Debug informations
	if self.spec_combine ~= nil then
		dbgrender("chopper: "..tostring(self.spec_combine.chopperPSenabled), 23, 3)
		dbgrender("swath: "..tostring(self.spec_combine.strawPSenabled), 24, 3)
		dbgrender("filling: "..tostring(self.spec_combine.isFilling), 25, 3)
	end
	if self.spec_dischargeable ~= nil then
		dbgrenderTable(self.spec_dischargeable, 0, 3)
		dbgrender("dischargeState: "..tostring(self.spec_dischargeable:getDischargeState()), 26, 3)
	end
	if self.spec_globalPositioningSystem ~= nil then
		dbgrenderTable(self.spec_globalPositioningSystem.guidanceData, 1, 3)
	end
	if g_currentMission.hud.controlledVehicle == self then
		local spec = self.spec_DashboardLive
		dbgrender("fovLast: "..tostring(spec.fovLast), 20, 3)
		dbgrender("zoomPerm: "..tostring(spec.zoomPerm), 21, 3)
	end
end

DashboardLiveKeepActive = {}
function DashboardLiveKeepActive:update(dt)
	g_inputBinding:registerActionEvent('DBL_HUDVISIBILITY_FULL', self, DashboardLive.HUDVISIBILITY, false, true, false, true)
	g_inputBinding:registerActionEvent('DBL_HUDVISIBILITY_PART', self, DashboardLive.HUDVISIBILITY, false, true, false, true)

	-- enable crosshair for InteractiveControl if it's present and the hud is invisible
	if g_currentMission.interactiveControl ~= nil and not g_currentMission.hud:getIsVisible() then
		if g_currentMission.interactiveControl:isInteractiveControlActivated() then
			setTextColor(0.5, 0.5, 0.5, 0.5)
			renderText(0.496, 0.495, 0.018, "+")
		end
		
		local vehicle = g_currentMission.hud.controlledVehicle
		if vehicle ~= nil then
			local icspec = vehicle.spec_interactiveControl
			if vehicle.isClient and icspec ~= nil then
				local isIndoor = vehicle:isIndoorActive()
				local isOutdoor = vehicle:isOutdoorActive()
				vehicle:updateInteractiveController(isIndoor, isOutdoor, vehicle:getIsActiveForInput(true))
			end
		end
	end
end
addModEventListener(DashboardLiveKeepActive)
