-- a basic pressure washer specialization for mobile pressure washer mods
--
-- put together by alfalfa6945
-- version 0.1.0.3

MobileWasher = {}

function MobileWasher.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(FillUnit, specializations)
end

function MobileWasher.registerFunctions(vehicleType)
	SpecializationUtil.registerFunction(vehicleType, "getIsPlayerInRange", MobileWasher.getIsPlayerInRange)
	SpecializationUtil.registerFunction(vehicleType, "isInActionDistance", MobileWasher.isInActionDistance)
	SpecializationUtil.registerFunction(vehicleType, "setIsTurnedOn", MobileWasher.setIsTurnedOn)
	SpecializationUtil.registerFunction(vehicleType, "onDeactivateObject", MobileWasher.onDeactivateObject)
	SpecializationUtil.registerFunction(vehicleType, "onHandtoolDelete", MobileWasher.onHandtoolDelete)
	SpecializationUtil.registerFunction(vehicleType, "getWaterFillLevel", MobileWasher.getWaterFillLevel)
	SpecializationUtil.registerFunction(vehicleType, "updateWorkSound", MobileWasher.updateWorkSound)
	SpecializationUtil.registerFunction(vehicleType, "createInfoOverlay", MobileWasher.createInfoOverlay)
	SpecializationUtil.registerFunction(vehicleType, "getDirtInformations", MobileWasher.getDirtInformations)
	SpecializationUtil.registerFunction(vehicleType, "loadXMLvalue", MobileWasher.loadXMLvalue)
end

function MobileWasher.registerEventListeners(vehicleType)
	for _, spec in pairs({"onLoad", "onDelete", "onUpdate", "onReadStream", "onWriteStream"}) do
		SpecializationUtil.registerEventListener(vehicleType, spec, MobileWasher)
	end
end

function MobileWasher:onLoad(savegame)
	self.spec_mobileWasher = {}
	local spec = self.spec_mobileWasher
    if self.isClient then
		local soundKey = "vehicle.highPressureWasher.sounds"
		spec.samples = {}
		spec.samples.motorIdle = g_soundManager:loadSampleFromXML(self.xmlFile, soundKey, "motorIdle", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self)
		spec.samples.motorStart = g_soundManager:loadSampleFromXML(self.xmlFile, soundKey, "motorStart", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self)
		spec.samples.motorStop = g_soundManager:loadSampleFromXML(self.xmlFile, soundKey, "motorStop", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self)
		spec.secondarySamples = {}
		spec.secondarySamples.motorIdleWork = g_soundManager:loadSampleFromXML(self.xmlFile, soundKey, "motorIdleWork", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self)
		spec.secondarySamples.motorStartWork = g_soundManager:loadSampleFromXML(self.xmlFile, soundKey, "motorStartWork", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self)
		spec.secondarySamples.motorStopWork = g_soundManager:loadSampleFromXML(self.xmlFile, soundKey, "motorStopWork", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self)
    end
	if next(spec.secondarySamples) == nil then
		spec.secondarySamples = nil
	end
	local playerKey = "vehicle.highPressureWasher.player"
	local playerTrigger = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, playerKey.."#playerTrigger"), self.i3dMappings)
	if playerTrigger ~= nil and getRigidBodyType(playerTrigger) == "Kinematic" then
		if getCollisionMask(playerTrigger) ~= 1048576 then
			setCollisionMask(playerTrigger, 1048576)
		end
		spec.activatableMW = MobileWasherActivatable:new(self, playerTrigger)
		spec.lastInRangePosition = {0,0,0}
		spec.messageShown = false
		spec.actionRadius = Utils.getNoNil(getXMLFloat(self.xmlFile, playerKey.."#playerLanceDistance"), 15)
	end
	spec.lanceXMLFilename = getXMLString(self.xmlFile, "vehicle.highPressureWasher.handtool#filename")
	if not string.match(spec.lanceXMLFilename, '^.-data') then
		spec.lanceXMLFilename = self.baseDirectory..spec.lanceXMLFilename
	end
	spec.lanceNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.highPressureWasher.lance#node"), self.i3dMappings)
	spec.exhaustParticleSystems = {}
	local i = 0
	while true do
		local key = string.format("vehicle.highPressureWasher.exhaustParticleSystems.exhaustParticleSystem(%d)", i)
		if not hasXMLProperty(self.xmlFile, key) then
            break
        end
		local ps = {}
		ParticleUtil.loadParticleSystem(self.xmlFile, ps, key, self.components, false, nil, self.baseDirectory)
        ps.minScale = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#minScale"), 0.1)
        ps.maxScale = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#maxScale"), 0.5)
		if ps.shape ~= nil and getHasShaderParameter(ps.shape, "psColor") then
			ps.color = ConfigurationUtil.getColorFromString(Utils.getNoNil(getXMLString(self.xmlFile, key.."#particleColor"), "0.0384 0.0359 0.0627 2.0"))
			setShaderParameter(ps.shape, "psColor", ps.color[1], ps.color[2], ps.color[3], ps.color[4], false)
		end
		local scale = StringUtil.getVectorNFromString(getXMLString(self.xmlFile, key.."#scale"), 3)
		if scale ~= nil then
			setScale(ps.emitterShape, unpack(scale))
		end
		local spriteScaleX = getXMLFloat(self.xmlFile, key.."#spriteScaleX")
		local spriteScaleY = getXMLFloat(self.xmlFile, key.."#spriteScaleY")
		if spriteScaleX ~= nil then
			ParticleUtil.setParticleSystemSpriteScaleX(ps, spriteScaleX)
		end
		if spriteScaleY ~= nil then
			ParticleUtil.setParticleSystemSpriteScaleY(ps, spriteScaleY)
		end
		ps.scaleFactor = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#scaleFactor"), 1) / 1000
		ps.currentScale = ps.minScale
        table.insert(spec.exhaustParticleSystems, ps)
		i = i + 1
    end
    if next(spec.exhaustParticleSystems) == nil then
        spec.exhaustParticleSystems = nil
    end
	spec.exhaustEffects = {}
    i = 0
    while true do
        local key = string.format("vehicle.highPressureWasher.exhaustEffects.exhaustEffect(%d)", i)
        if not hasXMLProperty(self.xmlFile, key) then
            break
        end
        local linkNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, key.."#node"), self.i3dMappings)
        local filename = getXMLString(self.xmlFile, key .. "#filename")
        if filename ~= nil and linkNode ~= nil then
            local i3dNode = g_i3DManager:loadSharedI3DFile(filename, self.baseDirectory, false, false, false)
            if i3dNode ~= 0 then
                local node = getChildAt(i3dNode, 0)
                if getHasShaderParameter(node, "param") then
                    local effect = {}
                    effect.effectNode = node
                    effect.node = linkNode
                    effect.filename = filename
                    link(effect.node, effect.effectNode)
                    setVisibility(effect.effectNode, false)
                    delete(i3dNode)
					effect.exhaustColor = ConfigurationUtil.getColorFromString(Utils.getNoNil(getXMLString(self.xmlFile, key.."#exhaustColor"), "0.0384 0.0359 0.0627 2.0"))
					effect.minExhaustAlpha = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#minExhaustAlpha"), 0.1)
					effect.maxExhaustAlpha = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#maxExhaustAlpha"), 2.0)
					effect.alphaScale = effect.minExhaustAlpha
					effect.colorScaleFactor = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#colorScaleFactor"), 1) / 100
					effect.xzRotationsOffset = StringUtil.getRadiansFromString(Utils.getNoNil(getXMLString(self.xmlFile, key.."#xzRotationsOffset"), "0 0"), 2)
					effect.xRot = effect.xzRotationsOffset[1]
					effect.zRot = effect.xzRotationsOffset[2]
					effect.scaleFactor = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#scaleFactor"), 1) / 1000
					effect.minScale = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#minScale"), 0.2)
					effect.maxScale = Utils.getNoNil(getXMLFloat(self.xmlFile, key.."#maxScale"), 0.35)
					effect.currentScale = effect.minScale
					table.insert(spec.exhaustEffects, effect)
                end
            end
        end
        i = i + 1
    end
	spec.isTurnedOn = false
	local fillTypes = self:getFillUnits()
	if next(fillTypes) ~= nil then
		local fillType
		for i = 1, #fillTypes do
			fillType = self:getFillUnitFirstSupportedFillType(i)
			if fillType == g_fillTypeManager:getFillTypeIndexByName("water") then
				spec.waterFillUnitIndex = i
			elseif fillType == g_fillTypeManager:getFillTypeIndexByName("diesel") then
				spec.fuelFillUnitIndex = i
			end
		end
	end
	spec.fillTypeUnitDesc = g_i18n:getText("unit_liter")
	spec.waterFillTypeDesc = g_i18n:getText("fillType_water")
	spec.FuelFillTypeDesc = g_i18n:getText("fillType_fuel")
	spec.cleanliness = g_i18n:getText("statistic_cleanliness")
	local consumerKey = "vehicle.highPressureWasher.consumerConfiguration"
	spec.displayFillLevels = Utils.getNoNil(getXMLBool(self.xmlFile, consumerKey.."#displayFillLevels"), true)
	spec.showFuelLevel = Utils.getNoNil(getXMLBool(self.xmlFile, consumerKey.."#showFuelLevel"), true)
	spec.fuelUsage = Utils.getNoNil(getXMLFloat(self.xmlFile, consumerKey..".fuel#usage"), 1) / (60 * 60 * 1000)
	spec.waterUsage = Utils.getNoNil(getXMLFloat(self.xmlFile, consumerKey..".water#usage"), 1000) / (60 * 60 * 1000)
	spec.workingFactor = Utils.getNoNil(getXMLFloat(self.xmlFile, consumerKey..".motorWorkingFactor#workingFactor"), 20) / 100
	spec.workSoundChanged = false
	if not g_dedicatedServerInfo then
		self:loadXMLvalue()
		local uiScale = g_gameSettings:getValue("uiScale")
		local _, height = getNormalizedScreenValues(1 * uiScale, spec.textScaleSize * uiScale)
		spec.textSize = height
	end
end

function MobileWasher:loadXMLvalue()
	local spec = self.spec_mobileWasher
	local path = getUserProfileAppPath()
	local file, xmlFile = path.."modsSettings/mobileWasherConfig.xml"
	if fileExists(file) then
		xmlFile = loadXMLFile("mobileWasherXML", file, "MobileWasher")
	else
		xmlFile = createXMLFile("mobileWasherXML", file, "MobileWasher")
		setXMLBool(xmlFile, "mobileWasher.displayCleanlinessPercentage", true)
		setXMLInt(xmlFile, "mobileWasher.textScaleSize", 24)
		setXMLString(xmlFile, "mobileWasher.textColor", "0.991 0.3865 0.01 1")
		saveXMLFile(xmlFile)
	end
	if xmlFile then
		spec.displayCleanlinessPercentage = getXMLBool(xmlFile, "mobileWasher.displayCleanlinessPercentage")
		spec.textScaleSize = getXMLInt(xmlFile, "mobileWasher.textScaleSize")
		spec.textColor = ConfigurationUtil.getColorFromString(getXMLString(xmlFile, "mobileWasher.textColor"))
		delete(xmlFile)
	end
end

function MobileWasher:onDelete()
	local spec = self.spec_mobileWasher
    if self.isClient then
		g_soundManager:deleteSamples(spec.samples)
		if spec.secondarySamples ~= nil then
			g_soundManager:deleteSamples(spec.secondarySamples)
		end
	end
    if spec.activatableMW ~= nil then
        spec.activatableMW:delete()
    end
	if spec.exhaustParticleSystems ~= nil then
        ParticleUtil.deleteParticleSystems(spec.exhaustParticleSystems)
    end
end

function MobileWasher:onReadStream(streamId, connection)
    if connection:getIsServer() then
        local isTurnedOn = streamReadBool(streamId)
        if isTurnedOn then
			local player = NetworkUtil.readNodeObject(streamId)
            if player ~= nil then
                self:setIsTurnedOn(isTurnedOn, player, true)
            end
        end
    end
end

function MobileWasher:onWriteStream(streamId, connection)
    if not connection:getIsServer() then
		local spec = self.spec_mobileWasher
        streamWriteBool(streamId, spec.isTurnedOn)
        if spec.isTurnedOn then
			NetworkUtil.writeNodeObject(streamId, spec.currentPlayer)
        end
    end
end

function MobileWasher:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local spec = self.spec_mobileWasher
	if self.isClient then
		if spec.currentPlayer ~= nil then
			local isPlayerInRange = self:getIsPlayerInRange(spec.actionRadius, spec.currentPlayer)
			if isPlayerInRange then
				spec.lastInRangePosition = {getTranslation(spec.currentPlayer.rootNode)}
			else
				local kx, _, kz = getWorldTranslation(self.rootNode)
				local px, py, pz = getWorldTranslation(spec.currentPlayer.rootNode)
				local len = MathUtil.vector2Length(px-kx, pz-kz)
				local x,y,z = unpack(spec.lastInRangePosition)
				x = kx + ((px-kx) / len) * (spec.actionRadius-0.00001*dt)
				z = kz + ((pz-kz) / len) * (spec.actionRadius-0.00001*dt)
				spec.currentPlayer:moveToAbsoluteInternal(x, py, z)
				spec.lastInRangePosition = {x,y,z}
				if not spec.messageShown and spec.currentPlayer == g_currentMission.player then
					g_currentMission:showBlinkingWarning(g_i18n:getText("warning_hpwRangeRestriction"), 6000)
					spec.messageShown = true
				end
            end
			if spec.currentPlayer == g_currentMission.player then
				if spec.displayFillLevels then
					local fuelFillLevel = self:getFillUnitFillLevel(spec.fuelFillUnitIndex)
					local waterFillLevel = self:getFillUnitFillLevel(spec.waterFillUnitIndex)
					local showFuel = ""
					if spec.showFuelLevel then
						showFuel = string.format("%s: %.2f %s", spec.FuelFillTypeDesc, fuelFillLevel, spec.fillTypeUnitDesc)
					end
					g_currentMission:addExtraPrintText(string.format("%s: %.1f %s", spec.waterFillTypeDesc, waterFillLevel, spec.fillTypeUnitDesc) .."     ".. showFuel)
				end
			end
        end
		if spec.isTurnedOn then
			local tool = spec.currentPlayer.baseInformation.currentHandtool
			local soundChanged = tool:isBeingUsed()
			if spec.workSoundChanged ~= soundChanged then
				spec.workSoundChanged = soundChanged
				if spec.secondarySamples ~= nil then
					self:updateWorkSound(spec.workSoundChanged)
				end
			end
			if soundChanged then
				for _, effect in pairs(spec.exhaustEffects) do
					if effect.currentScale ~= effect.maxScale then
						effect.currentScale = math.min(effect.currentScale + effect.scaleFactor, effect.maxScale)
						setShaderParameter(effect.effectNode, "param", effect.xRot, effect.zRot, 0, effect.currentScale, false)
					end
					local color = effect.exhaustColor
					if effect.alphaScale ~= effect.maxExhaustAlpha then
						effect.alphaScale = math.min(effect.alphaScale + effect.colorScaleFactor, effect.maxExhaustAlpha)
						setShaderParameter(effect.effectNode, "exhaustColor", color[1], color[2], color[3], effect.alphaScale, false)
					end
				end
				if spec.exhaustParticleSystems ~= nil then
					for _, ps in pairs(spec.exhaustParticleSystems) do
						if ps.currentScale ~= ps.maxScale then
							ps.currentScale = math.min(ps.currentScale + ps.scaleFactor, ps.maxScale)
						end
						ParticleUtil.setEmitCountScale(ps, ps.currentScale)
					end
				end
			elseif not soundChanged then
				for _, effect in pairs(spec.exhaustEffects) do
					if effect.currentScale ~= effect.minScale then
						effect.currentScale = math.max(effect.currentScale - effect.scaleFactor, effect.minScale)
						setShaderParameter(effect.effectNode, "param", effect.xRot, effect.zRot, 0, effect.currentScale, false)
					end
					local color = effect.exhaustColor
					if effect.alphaScale ~= effect.minExhaustAlpha then
						effect.alphaScale = math.max(effect.alphaScale - effect.colorScaleFactor, effect.minExhaustAlpha)
						setShaderParameter(effect.effectNode, "exhaustColor", color[1], color[2], color[3], effect.alphaScale, false)
					end
				end
				if spec.exhaustParticleSystems ~= nil then
					for _, ps in pairs(spec.exhaustParticleSystems) do
						if ps.currentScale ~= ps.minScale then
							ps.currentScale = math.max(ps.currentScale - ps.scaleFactor, ps.minScale)
						end
						ParticleUtil.setEmitCountScale(ps, ps.currentScale)
					end
				end
			end
			local usageFactor = 1.0
			if g_currentMission.missionInfo.fuelUsageLow then
				usageFactor = 0.7
			end
			local damage = self:getVehicleDamage()
			if damage > 0 then
				usageFactor = usageFactor * (1 + damage * Motorized.DAMAGED_USAGE_INCREASE)
			end
			local waterUsageFactor = usageFactor
			if tool:isBeingUsed() then
				usageFactor = usageFactor + spec.workingFactor
				local waterUsed = waterUsageFactor * (spec.waterUsage * dt)
				if waterUsed ~= 0 then
					local waterFillType = self:getFillUnitLastValidFillType(spec.waterFillUnitIndex)
					self:addFillUnitFillLevel(self:getOwnerFarmId(), spec.waterFillUnitIndex, -waterUsed, waterFillType, ToolType.UNDEFINED)
				end
				if spec.displayCleanlinessPercentage and tool.foundVehicle ~= nil then
					self:createInfoOverlay(tool.foundVehicle)
				end
			end
			local used = usageFactor * (spec.fuelUsage * dt)
			if used ~= 0 then
				local fillType = self:getFillUnitLastValidFillType(spec.fuelFillUnitIndex)
                local stats = g_currentMission:farmStats(self:getOwnerFarmId())
                stats:updateStats("fuelUsage", used)
				self:addFillUnitFillLevel(self:getOwnerFarmId(), spec.fuelFillUnitIndex, -used, fillType, ToolType.UNDEFINED)
			end
			if self:getFillUnitFillLevel(spec.fuelFillUnitIndex) <= 0 then
                self:setIsTurnedOn(false, nil)
            end
		end
    end
	if self.isServer then
        if spec.isTurnedOn then
			if self.lastMovedDistance > 0.001 then
				self:setIsTurnedOn(false, nil)
			end
		end
	end
	if spec.currentPlayer ~= nil then
		self:raiseActive()
	end
end

function MobileWasher:updateWorkSound(isWashing)
	local spec = self.spec_mobileWasher
	if isWashing then
		g_soundManager:stopSample(spec.secondarySamples.motorStopWork)
		g_soundManager:playSample(spec.secondarySamples.motorStartWork)
		g_soundManager:playSample(spec.secondarySamples.motorIdleWork, 0, spec.secondarySamples.motorStartWork)
	else
		g_soundManager:stopSample(spec.secondarySamples.motorStartWork)
		g_soundManager:playSample(spec.secondarySamples.motorStopWork)
		g_soundManager:stopSample(spec.secondarySamples.motorIdleWork)
	end
end

function MobileWasher:onDeactivateObject()
	local spec = self.spec_mobileWasher
	if self.isClient then
		g_soundManager:stopSample(spec.samples.motorStart)
		g_soundManager:playSample(spec.samples.motorStop)
		g_soundManager:stopSample(spec.samples.motorIdle)
		if spec.secondarySamples ~= nil then
			self:updateWorkSound(false)
		end
    end
    spec.isTurnedOn = false
    setVisibility(spec.lanceNode, true)
    if spec.currentPlayer ~= nil then
		if spec.currentPlayer.hasHPWLance then
			spec.currentPlayer.hasHPWLance = false
			spec.currentPlayer:unequipHandtool()
		end
    end
	if spec.activatableMW ~= nil and spec.activatableMW.objectActivated then
		spec.activatableMW:onActivateObject()
	end
    spec.currentPlayer = nil
end

function MobileWasher:onHandtoolDelete()
	local spec = self.spec_mobileWasher
	if spec.currentPlayer ~= nil then
		spec.currentPlayer.hasHPWLance = false
		self:setIsTurnedOn(false, nil)
	end
end

function MobileWasher:getWaterFillLevel()
	local spec = self.spec_mobileWasher
	return self:getFillUnitFillLevel(spec.waterFillUnitIndex)
end

function MobileWasher:getIsPlayerInRange(distance, player)
    if self.rootNode ~= 0 then
        distance = Utils.getNoNil(distance, 10)
        if player == nil then
            for _, player in pairs(g_currentMission.players) do
                if self:isInActionDistance(player, self.rootNode, distance) then
                    return true, player
                end
            end
        else
            return self:isInActionDistance(player, self.rootNode, distance), player
        end
    end
    return false, nil
end

function MobileWasher:isInActionDistance(player, refNode, distance)
    local x,_,z = getWorldTranslation(refNode)
    local px,_,pz = getWorldTranslation(player.rootNode)
    local dx,dz = px-x, pz-z
    if dx*dx + dz*dz < distance*distance then
        return true
    end
    return false
end

function MobileWasher:setIsTurnedOn(isTurnedOn, player, noEventSend)
	MobileWasherTurnOnEvent.sendEvent(self, isTurnedOn, player, noEventSend)
	local spec = self.spec_mobileWasher
	if spec.isTurnedOn ~= isTurnedOn then
        if isTurnedOn then
            spec.isTurnedOn = isTurnedOn
            spec.currentPlayer = player
			spec.currentPlayer:equipHandtool(spec.lanceXMLFilename, false, true)
			if spec.currentPlayer:hasHandtoolEquipped() then
				local tool = spec.currentPlayer.baseInformation.currentHandtool
				tool.delete = Utils.appendedFunction(tool.delete, MobileWasher.appendedDelete)
				tool.owner = self
				tool.update = MobileWasher.overwriteUpdate
			end
			spec.currentPlayer.hasHPWLance = true
			if self.isClient then
				setVisibility(spec.lanceNode, false)
				g_soundManager:stopSample(spec.samples.motorStop)
				g_soundManager:playSample(spec.samples.motorStart)
				g_soundManager:playSample(spec.samples.motorIdle, 0, spec.samples.motorStart)
			end
			self:raiseActive()
		else
			self:onDeactivateObject()
		end
		if self.isClient then
			if spec.exhaustEffects ~= nil then
				for _, effect in pairs(spec.exhaustEffects) do
					setVisibility(effect.effectNode, isTurnedOn)
					effect.currentScale = effect.minScale
					setShaderParameter(effect.effectNode, "param", effect.xRot, effect.zRot, 0, effect.currentScale, false)
					local color = effect.exhaustColor
					effect.alphaScale = effect.minExhaustAlpha
					setShaderParameter(effect.effectNode, "exhaustColor", color[1], color[2], color[3], effect.alphaScale, false)
				end
			end
			if spec.exhaustParticleSystems ~= nil then
                for _, ps in pairs(spec.exhaustParticleSystems) do
					ps.currentScale = ps.minScale
                    ParticleUtil.setEmittingState(ps, isTurnedOn)
                end
            end
		end
	end
end

function MobileWasher:appendedDelete()
	self.owner:onHandtoolDelete()
end

function MobileWasher:overwriteUpdate(dt, allowInput)
	HighPressureWasherLance:superClass().update(self, dt)
	if allowInput then
		if self.activatePressed then
			local hasWater = self.owner:getWaterFillLevel() > 0
			if not hasWater then
				self.activatePressed = hasWater
				g_currentMission:showBlinkingWarning(string.format(g_i18n:getText("fieldJob_desc_fillTheUnit"), g_i18n:getText("fillType_water")), 2000)
			end
		end
		self:setIsWashing(self.activatePressed, false, false)
	end
	if self.isServer then
		if self.doWashing then
			self.foundVehicle = nil
			self:cleanVehicle(self.player.cameraNode, dt)
			if self.lanceRaycastNode ~= nil then
				self:cleanVehicle(self.lanceRaycastNode, dt)
			end
		end
	end
	self.activatePressed = false
	self:raiseActive()
end

function MobileWasher:createInfoOverlay(vehicle)
	local spec = self.spec_mobileWasher
	local displayedText
	local px, py, pz
	setTextAlignment(RenderText.ALIGN_CENTER)
	setTextBold(true)
	setTextColor(unpack(spec.textColor))
	if vehicle ~= nil then
		local x, y, z = getWorldTranslation(vehicle.rootNode)
		px, py, pz = project(x, y, z)
		local percentage = string.format("%0.f", self:getDirtInformations(vehicle))
		displayedText = spec.cleanliness ..": \n" ..percentage .."%"
	end
	renderText(px, py, spec.textSize, displayedText)
	setTextAlignment(RenderText.ALIGN_LEFT)
	setTextBold(false)
end

function MobileWasher:getDirtInformations(vehicle)
	local spec = vehicle.spec_washable
	local amount = 0
	if spec ~= nil then
		if spec.washableNodes ~= nil then
			for _, nodeData in ipairs(spec.washableNodes) do
				amount = amount + vehicle:getNodeDirtAmount(nodeData)
			end
			amount = 1 - (amount / #spec.washableNodes)
		end
	end
	return amount * 100
end

MobileWasherActivatable = {}

local MobileWasherActivatable_mt = Class(MobileWasherActivatable)

function MobileWasherActivatable:new(vehicle, trigger)
    local spec = vehicle.spec_mobileWasher
	if spec ~= nil and trigger ~= nil then
        local self = {}
        setmetatable(self, MobileWasherActivatable_mt)
        self.vehicle = vehicle
        self.spec = spec
		self.playerTrigger = trigger
		addTrigger(self.playerTrigger, "playerTriggerCallback", self)
		self.activateText = "unknown"
		self.currentPlayer = nil
		self.isEnabled = true
		self.objectActivated = false
		self.MWtypeDesc = g_i18n:getText("typeDesc_highPressureWasher")
		return self
    else
        return nil
    end
end

function MobileWasherActivatable:getIsActivatable()
    self:updateActivateText()
	return self.isEnabled and g_currentMission.controlPlayer and (self.vehicle:getActiveFarm() == 0 or g_currentMission:getFarmId() == self.vehicle:getActiveFarm())
end

function MobileWasherActivatable:drawActivate()
end

function MobileWasherActivatable:onActivateObject()
    if self.spec ~= nil and self.vehicle ~= nil then 
		if self.vehicle:getFillUnitFillLevel(self.spec.fuelFillUnitIndex) > 0 then
			self.objectActivated = not self.objectActivated
			if self.currentPlayer ~= nil then
				if self.objectActivated then
					self.vehicle:setIsTurnedOn(true, self.currentPlayer, true)
				else
					self.vehicle:setIsTurnedOn(false, nil)
				end
				self:updateActivateText()
			else
				self.vehicle:setIsTurnedOn(false, nil)
			end
		else
			g_currentMission:showBlinkingWarning(string.format(g_i18n:getText("fieldJob_desc_fillTheUnit"), g_i18n:getText("fillType_fuel")), 2000)
		end
	end
end

function MobileWasherActivatable:updateActivateText()
	if self.objectActivated then
        self.activateText = string.format(g_i18n:getText("action_turnOffOBJECT"), self.MWtypeDesc)
    else
        self.activateText = string.format(g_i18n:getText("action_turnOnOBJECT"), self.MWtypeDesc)
    end
end

function MobileWasherActivatable:shouldRemoveActivatable()
	return false
end

function MobileWasherActivatable:delete()
    if self.objectActivated then
        g_currentMission:removeActivatableObject(self)
        self.objectActivated = false
        self.currentPlayer = nil
    end
    if self.playerTrigger ~= nil then
        removeTrigger(self.playerTrigger)
        self.playerTrigger = nil
    end
end

function MobileWasherActivatable:playerTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay)
    if self.isEnabled then
        if onEnter or onLeave then
            if g_currentMission.player ~= nil and otherId == g_currentMission.player.rootNode then
				if self.spec.currentPlayer ~= nil and otherId ~= self.spec.currentPlayer.rootNode then
					return
				end
                if onEnter then
					g_currentMission:addActivatableObject(self)
					self.currentPlayer = g_currentMission.player
                elseif onLeave then
					g_currentMission:removeActivatableObject(self)
					self.currentPlayer = nil
                end
            end
        end
    end
end

MobileWasherTurnOnEvent = {}
MobileWasherTurnOnEvent_mt = Class(MobileWasherTurnOnEvent, Event)
InitEventClass(MobileWasherTurnOnEvent, "MobileWasherTurnOnEvent")

function MobileWasherTurnOnEvent:emptyNew()
    local self = Event:new(MobileWasherTurnOnEvent_mt)
    return self
end

function MobileWasherTurnOnEvent:new(object, isTurnedOn, player)
    local self = MobileWasherTurnOnEvent:emptyNew()
    self.object = object
    self.isTurnedOn = isTurnedOn
    self.player = player
    return self
end

function MobileWasherTurnOnEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.isTurnedOn = streamReadBool(streamId)
    if self.isTurnedOn then
        self.player = NetworkUtil.readNodeObject(streamId)
    end
    self:run(connection)
end

function MobileWasherTurnOnEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteBool(streamId, self.isTurnedOn)
    if self.isTurnedOn then
        NetworkUtil.writeNodeObject(streamId, self.player)
    end
end

function MobileWasherTurnOnEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
    self.object:setIsTurnedOn(self.isTurnedOn, self.player, true)
end

function MobileWasherTurnOnEvent.sendEvent(object, isTurnedOn, player, noEventSend)
    if isTurnedOn ~= object.isTurnedOn then
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(MobileWasherTurnOnEvent:new(object, isTurnedOn, player), nil, nil, object)
            else
                g_client:getServerConnection():sendEvent(MobileWasherTurnOnEvent:new(object, isTurnedOn, player))
            end
        end
    end
end