--[[
Orginal Script: HighPressureWasherPlaceable.lua by GIANTS Software GmbH

Rewritting for buyable vehilces: Ifko[nator]
Date:        13.09.2017
Version:     2.0

History:    V1.0 @ 14.02.2016 - initial release
            --------------------------------------------------------------------------------------
            V1.5 @ 21.02.2016 - added increased motor sound and darker exhaust effect when washing
            
            Bugfixes: 
                - for MP: The wash sound is not longer audible on the whole map
                - change the calculation of the water use
            --------------------------------------------------------------------------------------
			V2.0 @ 13.09.2017 - convert for FS 17

You must this HPW fillup with water and fuel to use!

Name: PressureWasher.lua
]]

PressureWasher = {};

function PressureWasher.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization((Fillable and WaterTrailer), specializations);
end;

function PressureWasher:load(savegame)
    self.isInActionDistance = PressureWasher.isInActionDistance;
    self.getIsPlayerInRange = PressureWasher.getIsPlayerInRange;
    self.setIsWashing = PressureWasher.setIsWashing;
    self.setIsTurnedOn = PressureWasher.setIsTurnedOn;
    self.onDeactivate = PressureWasher.onDeactivate;
    self.activateLance = PressureWasher.activateLance;
    self.deactivateLance = PressureWasher.deactivateLance;
    self.deleteLance = PressureWasher.deleteLance;
    self.drawLance = PressureWasher.drawLance;
    self.updateLance = PressureWasher.updateLance;
    self.getPressureWasherActiveForSound = PressureWasher.getPressureWasherActiveForSound;
    self.cleanVehicle = PressureWasher.cleanVehicle;
    self.updateTickLance = PressureWasher.updateTickLance;
    self.washRaycastCallback = PressureWasher.washRaycastCallback;
	
	local xmlFile = self.xmlFile;

    self.playerInRangeDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.playerInRangeDistance"), 3);
    self.washDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.washDistance"), 10);
    self.actionRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.actionRadius#distance"), 15);
    self.washMultiplier = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.washMultiplier"), 1);
    self.waterUsePerSecond = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.waterUsePerSecond"), 1) / 3000;
	self.cleanserUsePerSecond = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.cleanserUsePerSecond"), 1) / 3000;
    self.lanceNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.lance#index"));
    self.linkPosition = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, "vehicle.lance#position"), "0 0 0"), 3);
    self.linkRotation = Utils.getRadiansFromString(Utils.getNoNil(getXMLString(xmlFile, "vehicle.lance#rotation"), "0 0 0"), 3);
    self.lanceNodeParent = getParent(self.lanceNode);
    self.lanceRaycastNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.lance#raycastNode"));
    
    self.messageShown = false;

    if self.isClient then
        self.particleSystems = {};

        local emitterShape = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.emitterShape#node"));
		
		if emitterShape ~= nil then
			local particleType = getXMLString(xmlFile, "vehicle.emitterShape#particleType");
			local fillType = FillUtil.FILLTYPE_WATER;
			local particleSystem = MaterialUtil.getParticleSystem(fillType, particleType);
			
			if particleSystem ~= nil then
				table.insert(self.particleSystems, ParticleUtil.copyParticleSystem(xmlFile, "vehicle.emitterShape", particleSystem, emitterShape));
			end;
		end;

        self.waterEffects = EffectManager:loadEffect(xmlFile, "vehicle.waterEffect", self.rootNode, self);
        self.sampleCompressor = SoundUtil.loadSample(xmlFile, {}, "vehicle.compressorSound", nil, self.baseDirectory, self.rootNode);
        self.compressorPitchMin = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.compressorSound#pitchMin"), 0.5);
        self.sampleWashing = SoundUtil.loadSample(xmlFile, {}, "vehicle.washingSound", nil, self.rootNode);
        self.sampleSwitch = SoundUtil.loadSample(xmlFile, {}, "vehicle.switchSound", nil, self.rootNode);
        
        self.sampleCompressor.currentPitchOffset = self.sampleCompressor.pitchOffset;
        self.sampleCompressor.currentVolume = self.sampleCompressor.volume;
        self.pitch = self.sampleCompressor.currentPitchOffset;
        self.volume = self.sampleCompressor.currentVolume;
        
        self.kaercherExhaustEffects = {};
        
        local i = 0;
        
        while true do
            local key = string.format("vehicle.kaercherExhaustEffects.kaercherExhaustEffect(%d)", i);
            
            if not hasXMLProperty(xmlFile, key) then
                break;
            end
            
            local filename = getXMLString(xmlFile, key .. "#filename");
            
            if filename ~= nil then
                local effect = {};
            
                if self.kaercherExhaustEffects ~= nil and self.kaercherExhaustEffects[i+1] ~= nil then
                    effect = self.kaercherExhaustEffects[i+1];
                else
                    effect.node = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#index"));
                    effect.filename = filename;
                    
                    local i3dNode = Utils.loadSharedI3DFile(filename, self.baseDirectory, false, false, false);
                
                    if i3dNode ~= 0 then
                        effect.effectNode = getChildAt(i3dNode, 0);
                        link(effect.node, effect.effectNode);
                        setVisibility(effect.effectNode, false);
                        delete(i3dNode);
                        table.insert(self.kaercherExhaustEffects, effect);
                    end;
                end;
            end;
            
            i = i + 1;
        end;

        self.alpha = 1;
        self.size = 0.5;
        
        self.targets = {};

        IKUtil.loadIKChainTargets(xmlFile, "vehicle", self.components, self.targets);
    end;

    self.isPlayerInRange = false;
    self.isTurnedOn = false;
    self.doWashing = false;
	self.lastInRangePosition = {0, 0, 0};
    
    self.isTurningOff = false;
    self.turnOffTime = 0;
    self.turnOffDuration = 500;
end;

function PressureWasher:delete()
    self:setIsTurnedOn(false, nil, false);

    if self.isClient then
        if self.waterEffects ~= nil then
            EffectManager:deleteEffects(self.waterEffects);
        end;

        if self.particleSystems ~= nil then
            ParticleUtil.deleteParticleSystems(self.particleSystems);
        end;

        if self.sampleCompressor ~= nil then
            SoundUtil.deleteSample(self.sampleCompressor);
        end;

        if self.sampleWashing ~= nil then
            SoundUtil.deleteSample(self.sampleWashing);
        end;

        if self.sampleSwitch ~= nil then
            SoundUtil.deleteSample(self.sampleSwitch);
        end;
        
        if self.kaercherExhaustEffects ~= nil then
            for _, effect in pairs(self.kaercherExhaustEffects) do
                Utils.releaseSharedI3DFile(effect.filename, self.baseDirectory, true);
            end;
        end;
    end;

    unregisterObjectClassName(self);
end;

function PressureWasher:readStream(streamId, connection)
    if connection.getIsServer(connection) then
        local isTurnedOn = streamReadBool(streamId);
        
        if isTurnedOn then
            local player = networkGetObject(streamReadInt32(streamId));

            if player ~= nil then
                self:setIsTurnedOn(isTurnedOn, player, true);
            end;
        end;
    end;
end;

function PressureWasher:writeStream(streamId, connection)
    if not connection.getIsServer(connection) then
        streamWriteBool(streamId, self.isTurnedOn);
        
        if self.isTurnedOn then
            streamWriteInt32(streamId, networkGetObjectId(self.currentPlayer));
        end;
    end;
end;

function PressureWasher:activateHandtool(player)
    self:setIsTurnedOn(true, player, true);
end;

function PressureWasher:update(dt)
    local messageInRange;
    
    if self.currentPlayer ~= nil then
        local isPlayerInRange = self:getIsPlayerInRange(self.actionRadius, self.currentPlayer);
        
		messageInRange = self:getIsPlayerInRange(self.actionRadius + 1, self.currentPlayer);
        
        if isPlayerInRange then
            self.lastInRangePosition = {getTranslation(self.currentPlayer.rootNode)};
			
			self.messageShown = false;
        else
            local kx, _, kz = getWorldTranslation(self.rootNode);
            local px, _, pz = getWorldTranslation(self.currentPlayer.rootNode);
            local len = Utils.vector2Length(px - kx, pz - kz);
            local x, y, z = unpack(self.lastInRangePosition);
            x = kx + (px - kx) / len * (self.actionRadius - 0.00001 * dt);
            z = kz + (pz - kz) / len * (self.actionRadius - 0.00001 * dt);
            y = math.max(y, getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z));

            self.currentPlayer:moveToAbsoluteInternal(x, y, z);

            self.lastInRangePosition = {x, y, z};

            if not self.messageShown and self.currentPlayer == g_currentMission.player then
                g_currentMission:showBlinkingWarning(g_i18n:getText("warning_hpwRangeRestriction"), 6000);

                self.messageShown = true;
            end;
        end;
    end;
    
	local allowWashing = false;
	local hasCleanser = false;
	
    if self:getIsActiveForInput() or (messageInRange and self.currentPlayer == g_currentMission.player) then
		for _, fillUnit in pairs(self.fillUnits) do	
			if fillUnit.currentFillType == FillUtil.FILLTYPE_WATER then	
				local currentWaterFillLevel = g_i18n:formatNumber(fillUnit.fillLevel, 0);
			
				g_currentMission:addExtraPrintText(string.gsub(g_i18n:getText("CurrentWaterFillLevel"), "currentWaterFillLevel", currentWaterFillLevel));
				
				allowWashing = true;
			elseif fillUnit.currentFillType == FillUtil.FILLTYPE_CLEANSER then	
				local currentCleanserFillLevel = math.ceil(fillUnit.fillLevel);
				
				if not self.doWashing then
					self:setFillLevel(currentCleanserFillLevel, FillUtil.FILLTYPE_CLEANSER);
				end;
				
				currentCleanserFillLevel = g_i18n:formatNumber(currentCleanserFillLevel, 0);
				
				hasCleanser = true;
			end;
		end;
    end;
    
    if self.isTurnedOn then
        if self.doWashing then
            for _, fillUnit in pairs(self.fillUnits) do	
				if allowWashing then	
					if fillUnit.currentFillType == FillUtil.FILLTYPE_WATER then	
						self.foundVehicle = nil;
					
						self:cleanVehicle(self.currentPlayer.cameraNode, dt);
					
						if self.lanceRaycastNode ~= nil then
							self:cleanVehicle(self.lanceRaycastNode, dt);
						end;
		
						self:setFillLevel(fillUnit.fillLevel - (self.waterUsePerSecond * dt), FillUtil.FILLTYPE_WATER);
					elseif fillUnit.currentFillType == FillUtil.FILLTYPE_CLEANSER then
						if fillUnit.fillLevel > 0 then
							self:setFillLevel((fillUnit.fillLevel - 0.001) - (self.cleanserUsePerSecond * dt), FillUtil.FILLTYPE_CLEANSER);
						end;
					end;
				else
					self.doWashing = false;
				end;
            end;
        end;
		
		if self.currentPlayer == g_currentMission.player then
			
			if not allowWashing and InputBinding.isPressed(InputBinding.ACTIVATE_HANDTOOL) then
				g_currentMission:showBlinkingWarning(g_i18n:getText("HighPressureWasherWaterIsEmpty"), 2000);
			end;
		end;
        
        if self.isClient then
            if self.sampleCompressor ~= nil then    
                if self.doWashing then
                    local multiplier = 1.8;
                    
                    self.sampleCompressor.currentPitchOffset = math.min(self.sampleCompressor.currentPitchOffset + 0.015, self.sampleCompressor.pitchOffset * multiplier);
                    self.sampleCompressor.currentVolume = math.min(self.sampleCompressor.currentVolume + 0.015, self.sampleCompressor.volume * multiplier);
                    
                    self.pitch = Utils.lerp(self.compressorPitchMin, self.sampleCompressor.currentPitchOffset, Utils.clamp(self.sampleCompressor.pitchOffset * multiplier, 0, 1));
                    self.volume = Utils.lerp(0, self.sampleCompressor.currentVolume, Utils.clamp(self.sampleCompressor.volume * multiplier, 0, 1));
                    
                    self.alpha = math.min(self.alpha + 0.15, 2);
                    self.size = math.min(self.size + 0.015, 0.5);
                else
                    self.sampleCompressor.currentPitchOffset = math.max(self.sampleCompressor.currentPitchOffset - 0.035, self.sampleCompressor.pitchOffset);
                    self.sampleCompressor.currentVolume = math.max(self.sampleCompressor.currentVolume - 0.035, self.sampleCompressor.volume);
                    
                    self.pitch = Utils.lerp(self.compressorPitchMin, self.sampleCompressor.currentPitchOffset, Utils.clamp(self.sampleCompressor.pitchOffset, 0, 1));
                    self.volume = Utils.lerp(0, self.sampleCompressor.currentVolume, Utils.clamp(self.sampleCompressor.volume, 0, 1));
                    
                    self.alpha = math.max(self.alpha - 0.15, 1);
                    self.size = math.max(self.size - 0.015, 0.2);
                end;
                
                SoundUtil.setSamplePitch(self.sampleCompressor, self.pitch);
                SoundUtil.setSampleVolume(self.sampleCompressor, self.volume);
                
                if self:getPressureWasherActiveForSound() then
                    SoundUtil.playSample(self.sampleCompressor, 0, 0, 1);
                else
                    SoundUtil.play3DSample(self.sampleCompressor);
                end;
                
                for _, effect in pairs(self.kaercherExhaustEffects) do
                    setShaderParameter(effect.effectNode, "exhaustColor", 0, 0, 0, self.alpha, false);
                    setShaderParameter(effect.effectNode, "param", 0, 0, 0, self.size, false);
                end;
            end;
        end;
    end;

    if self.isTurningOff then
        if g_currentMission.time < self.turnOffTime then
            if self.sampleCompressor ~= nil then
                local pitch = Utils.lerp(self.compressorPitchMin, self.sampleCompressor.pitchOffset, Utils.clamp((self.turnOffTime - g_currentMission.time) / self.turnOffDuration, 0, 1));
                local volume = Utils.lerp(0, self.sampleCompressor.volume, Utils.clamp((self.turnOffTime - g_currentMission.time) / self.turnOffDuration, 0, 1));

                SoundUtil.setSamplePitch(self.sampleCompressor, 0);
                SoundUtil.setSampleVolume(self.sampleCompressor, 0);
            end;
        else
            self.isTurningOff = false;
		
			if self.sampleCompressor ~= nil then
				if self:getPressureWasherActiveForSound() then
					SoundUtil.stopSample(self.sampleCompressor);
				else
					SoundUtil.stop3DSample(self.sampleCompressor);
				end;
			end;
		end;
    end;

    local isPlayerInRange, player = self:getIsPlayerInRange(self.playerInRangeDistance);

    if isPlayerInRange and player == g_currentMission.player then
        self.playerInRange = player;
        self.isPlayerInRange = true;
        local activateText = "unknown";

        if self.isTurnedOn then
            activateText = g_i18n:getText("action_turnOffOBJECT");
            
            if InputBinding.hasEvent(InputBinding.TOGGLE_MOTOR_STATE) then
                self:setIsTurnedOn(false, nil);
            end;
        else
            activateText = g_i18n:getText("action_turnOnOBJECT");

			local coverIsClosed = Utils.getNoNil(not self.isCoverOpen ,true);
			
			
			if InputBinding.hasEvent(InputBinding.TOGGLE_MOTOR_STATE) then
				if coverIsClosed then	
					self:setIsTurnedOn(true, g_currentMission.player);
				else
					g_currentMission:showBlinkingWarning(g_i18n:getText("PleaseCloseFirstTheCover"), 2000);
				end;
			end;
        end;
        
        g_currentMission:addHelpButtonText(string.format(activateText, g_i18n:getText("typeDesc_highPressureWasher")), InputBinding.TOGGLE_MOTOR_STATE);
    else
        self.playerInRange = nil;
        self.isPlayerInRange = false;
    end;
end;

function PressureWasher:getIsActiveForInput(superFunc, onlyTrueIfSelected, askActivatable)
    if not self.isTurnedOn or not self.changeAllowed then
        if (self.attacherVehicle == nil or askActivatable) then 
            return superFunc(self, onlyTrueIfSelected) 
        end;
    else
        if g_gui.currentGui ~= nil or g_currentMission.isPlayerFrozen then
            return false;
        end;
        
        return true;
    end

end

function PressureWasher: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 PressureWasher:isInActionDistance(player, refNode, distance)
    local x, y, z = getWorldTranslation(refNode);
    local px, py, pz = getWorldTranslation(player.rootNode);
    local dx = px - x;
    local dz = pz - z;

    if dx * dx + dz * dz < distance * distance then
        return true;
    end;

    return false;
end;

function PressureWasher:cleanVehicle(node, dt)
    local x, y, z = getWorldTranslation(node);
    local dx, dy, dz = localDirectionToWorld(node, 0, 0, -1);
    local lastFoundVehicle = self.foundVehicle;
	local maxCleaningLevel = 0;
	local hasCleanser = false;
    
	raycastAll(x, y, z, dx, dy, dz, "washRaycastCallback", self.washDistance, self, 32 + 64 + 128 + 256 + 4096 + 8194);

    if self.foundVehicle ~= nil and lastFoundVehicle ~= self.foundVehicle then
        local currentDirtAmount = self.foundVehicle:getDirtAmount();
		
		for _, fillUnit in pairs(self.fillUnits) do	
			if fillUnit.currentFillType == FillUtil.FILLTYPE_CLEANSER then	
				hasCleanser = true;
			end;
		end;
		
		if hasCleanser then
			maxCleaningLevel = 0;
		else
			if currentDirtAmount > 0.45 then
				maxCleaningLevel = 0.45;
			else
				maxCleaningLevel = currentDirtAmount;
			end;
		end;
		
		currentDirtAmount = Utils.clamp(currentDirtAmount - self.washMultiplier * dt / self.foundVehicle.washDuration, maxCleaningLevel, 1);
		
		self.foundVehicle:setDirtAmount(currentDirtAmount, true);
		
		for wheelNumber, wheel in pairs(self.foundVehicle.wheels) do
			if wheel.washableDirtAmount ~= nil then
				--renderText(0.5, 0.5 - (wheelNumber * 0.02), 0.02, "wheel " .. wheelNumber .. " washableDirtAmount = " .. wheel.washableDirtAmount);
				
				if wheel.washableDirtAmount > currentDirtAmount then
					--## clean wheels, if the vehicle is not dirty! tire dirt modul ..
					
					for _, node in pairs(wheel.washableNodes) do
						local x,_,z,w = getShaderParameter(node, "RDT");
						
						if hasCleanser then
							maxCleaningLevel = 0;
						else
							if wheel.washableDirtAmount > 0.45 then
								maxCleaningLevel = 0.45;
							else
								maxCleaningLevel = wheel.washableDirtAmount;
							end;
						end;
						
						wheel.washableDirtAmount = Utils.clamp(wheel.washableDirtAmount - self.washMultiplier * dt / (self.foundVehicle.washDuration * 3.5), maxCleaningLevel, 1);
						
						setShaderParameter(node, "RDT", x, wheel.washableDirtAmount, z, w, false);
					end;
					
					wheel.washableDirtAmountSend = wheel.washableDirtAmount;
				end;
			end;
		end;
    end;
end;

function PressureWasher:setIsWashing(doWashing, force, noEventSend)
    HPWPlaceableStateEvent.sendEvent(self, doWashing, noEventSend);
    
    if self.doWashing ~= doWashing then
        if self.isClient then
           if self.particleSystems ~= nil then
				for _, ps in pairs(self.particleSystems) do
					ParticleUtil.setEmittingState(ps, doWashing and self:getFillLevel() > 0);
				end;
			end;

            if doWashing and self:getFillLevel() > 0 then
				EffectManager:setFillType(self.waterEffects, FillUtil.FILLTYPE_WATER);
                EffectManager:startEffects(self.waterEffects);

                if self.sampleWashing ~= nil then
                    if self:getPressureWasherActiveForSound() then
                        SoundUtil.playSample(self.sampleWashing, 0, 0, 1);
                    else
                        SoundUtil.play3DSample(self.sampleWashing);
                    end;
                end
            else
                if force and self:getFillLevel() > 0 then
                    EffectManager:resetEffects(self.waterEffects);
                else
                    EffectManager:stopEffects(self.waterEffects);
                end;

                if self.sampleWashing ~= nil then
                    SoundUtil.stopSample(self.sampleWashing, true);
                end;
            end;
        end;

        self.doWashing = doWashing;
    end;
end;

function PressureWasher:setIsTurnedOn(isTurnedOn, player, noEventSend)
    HPWPlaceableTurnOnEvent.sendEvent(self, isTurnedOn, player, noEventSend);
    
    if self.isTurnedOn ~= isTurnedOn then
        if isTurnedOn then
            self.isTurnedOn = isTurnedOn;
            self.currentPlayer = player;
            local tool = {node = self.lanceNode};

            link(player.toolsRootNode, tool.node);
            setVisibility(tool.node, false);
            setTranslation(tool.node, unpack(self.linkPosition));
            setRotation(tool.node, unpack(self.linkRotation));

            tool.update = self.updateLance;
            tool.updateTick = self.updateTickLance;
            tool.delete = self.deleteLance;
            tool.draw = self.drawLance;
            tool.onActivate = self.activateLance;
            tool.onDeactivate = self.deactivateLance;
            tool.targets = self.targets;
            tool.owner = self;
            tool.static = false;
            self.tool = tool;

            self.currentPlayer:setTool(tool);

            self.currentPlayer.hasHPWLance = true;

            if self.isClient then
                if self.sampleSwitch ~= nil then
                    if self:getPressureWasherActiveForSound() then
                        SoundUtil.playSample(self.sampleSwitch, 1, 0, nil);
                    else
                        SoundUtil.play3DSample(self.sampleSwitch);
                    end;
                end;

                if self.isTurningOff then
                    self.isTurningOff = false;
                end;

                setVisibility(self.lanceNode, true);
            end;
        else
            self:onDeactivate();
        end;

        for _, effect in pairs(self.kaercherExhaustEffects) do
            setVisibility(effect.effectNode, isTurnedOn);
        end;
    end;
end;

function PressureWasher:onDeactivate()
    if self.isClient and self.sampleSwitch ~= nil then
        if self:getPressureWasherActiveForSound() then
            SoundUtil.playSample(self.sampleSwitch, 1, 0, nil);
        else
            SoundUtil.play3DSample(self.sampleSwitch);
        end;
    end;

    self.isTurnedOn = false;

    setVisibility(self.lanceNode, true);
    self.setIsWashing(self, false, true, true);

    if self.currentPlayer ~= nil then
        self.currentPlayer:setToolById(0, true);

        self.currentPlayer.hasHPWLance = false;
    end;

    if self.isClient then
        if self.sampleWashing ~= nil then
            SoundUtil.stopSample(self.sampleWashing, true);
        end;

        self.isTurningOff = true;
        self.turnOffTime = g_currentMission.time + self.turnOffDuration;

        link(self.lanceNodeParent, self.lanceNode);
        setTranslation(self.lanceNode, 0, 0, 0);
        setRotation(self.lanceNode, 0, 0, 0);
    end;

    self.currentPlayer = nil; 
end;

function PressureWasher:getPressureWasherActiveForSound()
    return self.isTurnedOn and self.currentPlayer == g_currentMission.player and g_gui.currentGui == nil;
end;

function PressureWasher:washRaycastCallback(hitObjectId, x, y, z, distance)
    local vehicle = g_currentMission.nodeToVehicle[hitObjectId];

    if (vehicle and vehicle.getDirtAmount and vehicle.setDirtAmount and vehicle.washDuration) ~= nil then
        self.foundCoords = {x, y, z};
        
        self.foundVehicle = vehicle;

        return false;
    end;

    return true;
end;

function PressureWasher.activateLance(tool)
    setVisibility(tool.node, true); 
end;

function PressureWasher.deactivateLance(tool)
    tool.owner:setIsTurnedOn(false, nil);
end;

function PressureWasher.deleteLance(tool)
    tool.owner:setIsTurnedOn(false, nil);
end;

function PressureWasher.drawLance(tool)
    if tool.owner.currentPlayer == g_currentMission.player then
        g_currentMission:addHelpButtonText(g_i18n:getText("input_ACTIVATE_HANDTOOL"), InputBinding.ACTIVATE_HANDTOOL);
    end;
end;

function PressureWasher.updateLance(tool, dt, allowInput)
    if allowInput then
		local allowWashing = false;
		
		for _, fillUnit in pairs(tool.owner.fillUnits) do	
			if fillUnit.currentFillType == FillUtil.FILLTYPE_WATER then
				allowWashing = true;
			end;
		end;
	
        tool.owner:setIsWashing(InputBinding.isPressed(InputBinding.ACTIVATE_HANDTOOL) and allowWashing, false, false);
    end;
end;

--## unused functions

function PressureWasher:updateTick(dt)end;
function PressureWasher:mouseEvent(posX, posY, isDown, isUp, button)end;
function PressureWasher:keyEvent(unicode, sym, modifier, isDown)end;
function PressureWasher:draw()end;
function PressureWasher.updateTickLance(tool, dt, allowInput)end;