--[[
 ReceivingHopper

 @author:  	Manuel Leithner
 @edit: 	Ifko[nator] (Kevin Ifkovits)
 @date: 	20.01.2017
 @version: 	1.3

 Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

 Changelog:
 
	v1.0 @08.05.2016 - intial release in FS 17 | by GIANTS Software GmbH
	---------------------------------------------------------------------------------------------------
	v1.1 @07.01.2017 - first lua change for the Fliegl PFS 16000 Mod | edit by Ifko[nator]
	---------------------------------------------------------------------------------------------------
	v1.2 @10.01.2017 - second lua change, now the Fliegl PFS 16000 Mod will create 'real' straw, grass, hay and silage bales | edit by Ifko[nator]
	---------------------------------------------------------------------------------------------------
	v1.3 @20.01.2017 - thrid lua change, added function to allow the pallet pusher automaticly push the pallet's/bale's out, when they are full | edit by Ifko[nator]
					 - added function for green and red beacon light
					 - now the Motor of the Fliegl PFS 1600 will not longer stopped, if Player is far away
	---------------------------------------------------------------------------------------------------
]]

FlieglPFS16000 = {};

function FlieglPFS16000.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Fillable, specializations);
end;

function FlieglPFS16000:preLoad(savegame)
	local fillTypes = getXMLString(self.xmlFile, "vehicle.fillUnits.fillUnit#fillTypes");

	if fillTypes ~= nil then
		local newTypesStr = "";
		local types = Utils.splitString(" ", fillTypes);
	
		for _, fillType in pairs(types) do
			local fillTypeCheck = FillUtil.fillTypeNameToInt[fillType];
		
			if fillTypeCheck ~= nil then
				if newTypesStr ~= "" then 
					fillType = " " .. fillType; 
				end;
				
				newTypesStr = newTypesStr .. fillType;
			else
				setXMLString(self.xmlFile, "vehicle.fillUnits.fillUnit#fillTypes", newTypesStr);
			end;
		end;
	end;
end;

function FlieglPFS16000:load(savegame)
    self.setUnitFillLevel = Utils.appendedFunction(self.setUnitFillLevel, FlieglPFS16000.setUnitFillLevel);
    
	self.createBox = FlieglPFS16000.createBox;
	self.createBale = FlieglPFS16000.createBale;
	self.setAutomaticPalletPusher = FlieglPFS16000.setAutomaticPalletPusher;
    self.onBoxTriggerCallback = FlieglPFS16000.onBoxTriggerCallback;
    self.findFillableRaycastCallback = FlieglPFS16000.findFillableRaycastCallback;
	
    self.setCreateBoxes = Utils.overwrittenFunction(self.setCreateBoxes, FlieglPFS16000.setCreateBoxes);
	self.getIsFoldAllowed = Utils.overwrittenFunction(self.getIsFoldAllowed, FlieglPFS16000.getIsFoldAllowed);
	self.getIsTurnedOnAllowed = Utils.overwrittenFunction(self.getIsTurnedOnAllowed, FlieglPFS16000.getIsTurnedOnAllowed);

    self.flieglPFS16000 = {};
    self.flieglPFS16000.unloadingDelay = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.flieglPFS16000#unloadingDelay"), 0) * 1000;
    self.flieglPFS16000.unloadingStartTime = -1;
    self.flieglPFS16000.fillUnitIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.flieglPFS16000#fillUnitIndex"), 1);
    self.flieglPFS16000.unloadInfoIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.flieglPFS16000#unloadInfoIndex"), 1);
    self.flieglPFS16000.dischargeInfoIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.flieglPFS16000#dischargeInfoIndex"), 1);

    local triggerId = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.tipTrigger#index"));
    
	if triggerId and triggerId ~= 0 then
        self.flieglPFS16000.tipTrigger = ReceivingTipTrigger:new(self.isServer, self.isClient);
        self.flieglPFS16000.tipTrigger:load(triggerId, self);
        self.flieglPFS16000.tipTrigger:register(true)
        self.flieglPFS16000.tipTrigger:addUpdateEventListener(self)
    end;

    self.flieglPFS16000.boxSpawnPlace = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.boxTrigger#boxSpawnPlaceIndex"));
    self.flieglPFS16000.boxTriggerNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.boxTrigger#index"));
	
	self.useRealBeaconLights = g_gameSettings:getValue("realBeaconLights");
	
	self.flieglPFS16000.redBeaconLightRotNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.beaconLights.red#rotIndex"));
	self.flieglPFS16000.redBeaconLightCorona = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.beaconLights.red#coronaIndex"));
	self.flieglPFS16000.redBeaconLightRealLight = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.beaconLights.red#realLightIndex"));
	self.flieglPFS16000.redBeaconLightSpeed = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.flieglPFS16000.beaconLights.red#speed"), 0.015);
	
	if self.flieglPFS16000.redBeaconLightSpeed > 0 then
        local rot = math.random(0, math.pi * 2);
		
		if self.flieglPFS16000.redBeaconLightRotNode ~= nil then
            setRotation(self.flieglPFS16000.redBeaconLightRotNode, 0, rot, 0);
        end;
    end;
	
    if self.flieglPFS16000.redBeaconLightCorona ~= nil then
        setVisibility(self.flieglPFS16000.redBeaconLightCorona, false);
    end;
	
    if self.flieglPFS16000.redBeaconLightRealLight ~= nil then
        setVisibility(self.flieglPFS16000.redBeaconLightRealLight, false);
    end;
	
	self.flieglPFS16000.greenBeaconLightRotNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.beaconLights.green#rotIndex"));
	self.flieglPFS16000.greenBeaconLightCorona = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.beaconLights.green#coronaIndex"));
	self.flieglPFS16000.greenBeaconLightRealLight = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.beaconLights.green#realLightIndex"));
	self.flieglPFS16000.greenBeaconLightSpeed = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.flieglPFS16000.beaconLights.green#speed"), 0.015);
	
	if self.flieglPFS16000.greenBeaconLightSpeed > 0 then
        local rot = math.random(0, math.pi * 2);
		
		if self.flieglPFS16000.greenBeaconLightRotNode ~= nil then
            setRotation(self.flieglPFS16000.greenBeaconLightRotNode, 0, rot, 0);
        end;
    end;
	
    if self.flieglPFS16000.greenBeaconLightCorona ~= nil then
        setVisibility(self.flieglPFS16000.greenBeaconLightCorona, false);
    end;
	
    if self.flieglPFS16000.greenBeaconLightRealLight ~= nil then
        setVisibility(self.flieglPFS16000.greenBeaconLightRealLight, false);
    end;
	
	if self.flieglPFS16000.boxTriggerNode and self.isServer then
       addTrigger(self.flieglPFS16000.boxTriggerNode, "onBoxTriggerCallback", self);
    end;

    self.flieglPFS16000.isOverloading = false;
    self.flieglPFS16000.boxes = {};
	
    local boxesCount = 0;
    
	while true do
        local baseName = "vehicle.flieglPFS16000.boxTrigger.box(" .. tostring(boxesCount) .. ")";
        if not hasXMLProperty(self.xmlFile, baseName) then
            break;
        end;

        local fillTypeString = getXMLString(self.xmlFile, baseName .. "#fillType");
        local filename = getXMLString(self.xmlFile, baseName .. "#filename");
        local fillType = FillUtil.fillTypeNameToInt[fillTypeString];
        
		if fillType then
            self.flieglPFS16000.boxes[fillType] = filename;
        end;
		
        boxesCount = boxesCount + 1;
    end;

    if self.isClient then
		self.flieglPFS16000.turnedOnRotationNodes = Utils.loadRotationNodes(self.xmlFile, {}, "vehicle.turnedOnRotationNodes.turnedOnRotationNode", "flieglPFS16000", self.components); 
        self.flieglPFS16000.fillScrollerNodes = Utils.loadScrollers(self.components, self.xmlFile, "vehicle.flieglPFS16000.fillScrollerNodes.fillScrollerNode", {}, false);
        self.flieglPFS16000.currentBoxFillType = nil;
    end;

	self.flieglPFS16000.litersPerMs = 0;
    self.flieglPFS16000.litersPerMinutePaletts = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.flieglPFS16000.boxTrigger#litersPerMinutePaletts"), 2000) / 60 / 1000;
	self.flieglPFS16000.litersPerMinuteBales = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.flieglPFS16000.boxTrigger#litersPerMinuteBales"), 4000) / 60 / 1000;
   
	self.flieglPFS16000.raycastNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.raycastNode#index"));
    self.flieglPFS16000.raycastMaxDistance = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.flieglPFS16000.raycastNode#raycastLength"), 10);
    
	self.flieglPFS16000.objectsInBoxPlace = 0;
    self.flieglPFS16000.timeUntilNextBox = 3000;
    self.flieglPFS16000.nextBoxWaitTime = 3000;
	self.flieglPFS16000.currentBox = nil;
    
	self.flieglPFS16000.createBoxes = false;
	self.flieglPFS16000.automaticPalletPusher = false;
	
	self.flieglPFS16000.brakeForce = self.brakeForce;
	
	self.flieglPFS16000.colliNode = Utils.indexToObject(self.components, "0>");
	self.flieglPFS16000.liquidTank = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.flieglPFS16000.liquidTank#index"));

	if self.flieglPFS16000.liquidTank then
		setVisibility(self.flieglPFS16000.liquidTank, false);
	end;
	
	self.flieglPFS16000.fillLevelHud = VehicleHudUtils.loadHud(self, self.xmlFile, "fillLevel");
	self.flieglPFS16000.objectFillLevelHud = VehicleHudUtils.loadHud(self, self.xmlFile, "objectFillLevel");
	
	if self.flieglPFS16000.sampleFill == nil then
        local linkNode = Utils.indexToObject(self.components, Utils.getNoNil(getXMLString(self.xmlFile, "vehicle.fillSound#linkNode"), "0>"));
        
		self.flieglPFS16000.sampleFill = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.fillSound", nil, self.baseDirectory, linkNode);
		self.flieglPFS16000.sampleWarning = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.warningSound", nil, self.baseDirectory, linkNode);
    end;
	
	self.flieglPFS16000.lastFillFillevel  = 0;
	self.flieglPFS16000.lastObjectFillFillevel = 0;
end;

function FlieglPFS16000:getIsFoldAllowed(superFunc, onAiTurnOn)
   return self.turnOnVehicle and self:getIsTurnedOn() and not self.flieglPFS16000.automaticPalletPusher;
end;

function FlieglPFS16000:getIsTurnedOnAllowed(superFunc, isTurnedOn)
    if self.turnOnVehicle.isAlwaysTurnedOn then
        return false;
    end;

    if self.getIsMotorStarted ~= nil then
        return self:getIsMotorStarted();
	end;
	
    return true;
end

function FlieglPFS16000:delete()
    if self.flieglPFS16000.tipTrigger then
        self.flieglPFS16000.tipTrigger:delete();
    end;

    if self.isServer then
        if self.flieglPFS16000.boxTriggerNode then
            removeTrigger(self.flieglPFS16000.boxTriggerNode);
        end;
    end;

	if self.isClient then
		if self.flieglPFS16000.sampleFill then	
			SoundUtil.deleteSample(self.flieglPFS16000.sampleFill);
		end;
		
		if self.flieglPFS16000.sampleWarning then
			SoundUtil.deleteSample(self.flieglPFS16000.sampleWarning);
		end;
    end;
end;

function FlieglPFS16000:readStream(streamId, connection)
    if connection:getIsServer() then
		local tipTriggerId = streamReadInt32(streamId);
	
        self.flieglPFS16000.isOverloading = streamReadBool(streamId);
        self.flieglPFS16000.tipTrigger:readStream(streamId, connection);
        
		g_client:finishRegisterObject(self.flieglPFS16000.tipTrigger, tipTriggerId);
    end;
end;

function FlieglPFS16000:writeStream(streamId, connection)
    if not connection:getIsServer() then
        streamWriteBool(streamId, self.flieglPFS16000.isOverloading);
        streamWriteInt32(streamId, self.flieglPFS16000.tipTrigger.id);
        
		self.flieglPFS16000.tipTrigger:writeStream(streamId, connection);
        
		g_server:registerObjectInStream(connection, self.flieglPFS16000.tipTrigger);
    end;
end;

function FlieglPFS16000:readUpdateStream(streamId, timestamp, connection)
    if connection:getIsServer() then
        self.flieglPFS16000.isOverloading = streamReadBool(streamId);
    end;
end;

function FlieglPFS16000:writeUpdateStream(streamId, connection, dirtyMask)
    if not connection:getIsServer() then
        streamWriteBool(streamId, self.flieglPFS16000.isOverloading)
    end;
end;

function FlieglPFS16000:mouseEvent(posX, posY, isDown, isUp, button)
end;

function FlieglPFS16000:keyEvent(unicode, sym, modifier, isDown)
end;

function FlieglPFS16000:update(dt)
    if self.isClient then
		Utils.updateRotationNodes(self, self.flieglPFS16000.turnedOnRotationNodes, dt, self.flieglPFS16000.isOverloading);
	
		if self:getIsTurnedOn() and self.attacherVehicle then
			self:setIsTurnedOn(false);
		end;
	
		if self.turnOnVehicle and self:getIsTurnedOn() then
			if self:getIsActiveForInput() then
				if InputBinding.hasEvent(InputBinding.TOGGLE_TIPSTATE) then
					self:setCreateBoxes(not self.flieglPFS16000.createBoxes);
				end;
				
				if InputBinding.hasEvent(InputBinding.TOGGLE_AI) then
					self:setAutomaticPalletPusher(not self.flieglPFS16000.automaticPalletPusher);
				end;
			end;
		
			if self.fillUnits and self.flieglPFS16000.isOverloading then
				for _, fillUnit in pairs(self.fillUnits) do
                    local fillVolume = self.fillVolumes[fillUnit.fillVolumeIndex];
                    
					if fillVolume then
                        if fillVolume.scrollSpeedLoad[1] ~= 0 or fillVolume.scrollSpeedLoad[2] ~= 0 or fillVolume.scrollSpeedLoad[3] ~= 0 then
                            fillVolume.uvPosition[1] = fillVolume.uvPosition[1] + fillVolume.scrollSpeedLoad[1] * dt;
                            fillVolume.uvPosition[2] = fillVolume.uvPosition[2] + fillVolume.scrollSpeedLoad[2] * dt;
                            fillVolume.uvPosition[3] = fillVolume.uvPosition[3] + fillVolume.scrollSpeedLoad[3] * dt;
                            
							setShaderParameter(fillVolume.volume, "uvOffset", fillVolume.uvPosition[1], fillVolume.uvPosition[2], fillVolume.uvPosition[3], 0, false);
                        end;
                    end;
                end;
            end;
			
			for _, wheel in pairs(self.wheels) do
				setWheelShapeProps(wheel.node, wheel.wheelShape, 0, 99999999, 0, 99999999);
				setWheelShapeTireFriction(wheel.node, wheel.wheelShape, 99999999, 99999999, 99999999, 99999999);
				computeWheelShapeTireForces(wheel.node, wheel.wheelShape, 99999999, 99999999, 99999999);
			end;
			
			self.brakeForce = 99999999;
			
			Drivable.updateVehiclePhysics(self, 0, 0, 0, 0, true, dt);
		else
			self.brakeForce = self.flieglPFS16000.brakeForce;
		end;
    end;
	
	if self.fillUnits and self.flieglPFS16000.liquidTank then
		if self:getFillLevel() > 0 then	
			for _, fillUnit in pairs(self.fillUnits) do
				setVisibility(self.flieglPFS16000.liquidTank, FillUtil.categoryToFillTypes[FillUtil.FILLTYPE_CATEGORY_LIQUID][fillUnit.currentFillType] ~= nil);
			end;
		else
			setVisibility(self.flieglPFS16000.liquidTank, false);
		end;
	end;
	
	if self.flieglPFS16000.objectFillLevelHud then 
		local fillLevel = 0;
		local capacity = 0;
		local fillType = self:getUnitFillType(self.flieglPFS16000.fillUnitIndex);
		local fillTypeIsForBale = FillUtil.categoryToFillTypes[FillUtil.FILLTYPE_CATEGORY_WINDROW][fillType] ~= nil or fillType == FillUtil.FILLTYPE_SILAGE;
		
		if self.flieglPFS16000.currentBox then
			fillLevel = self.flieglPFS16000.currentBox:getFillLevel() - 1;
			
			if not fillTypeIsForBale then
				capacity = self.flieglPFS16000.currentBox.capacity;
			else
				capacity = 4000;
				
				if fillType == FillUtil.FILLTYPE_SILAGE then
					capacity = 6000;
				end;
			end;
			
			if self.flieglPFS16000.currentBox:getFillLevel() == capacity then
				fillLevel = self.flieglPFS16000.currentBox:getFillLevel();
			end;
		end;
		
		if self.flieglPFS16000.lastObjectFillFillevel ~= fillLevel then
			VehicleHudUtils.setHudValue(self, self.flieglPFS16000.objectFillLevelHud,  math.ceil(fillLevel), capacity);
		
			self.flieglPFS16000.lastObjectFillFillevel = fillLevel;
		end;
	end;
	
	if self.flieglPFS16000.fillLevelHud then 
		local fillLevel = self:getFillLevel() - 1;
		
		if self:getFillLevel() == self:getCapacity() then
			fillLevel = self:getFillLevel();
		end;
		
		if self.flieglPFS16000.lastFillFillevel ~= fillLevel then	
			VehicleHudUtils.setHudValue(self, self.flieglPFS16000.fillLevelHud,  math.ceil(fillLevel), self:getCapacity());
		
			self.flieglPFS16000.lastFillFillevel = fillLevel;
		end;
	end;
	
	if self.foldAnimTime > 0 and self.foldAnimTime < 1 then
		if self.flieglPFS16000.redBeaconLightSpeed > 0 then
			if self.flieglPFS16000.redBeaconLightRotNode ~= nil then
				local _, lightRotY, _ = getRotation(self.flieglPFS16000.redBeaconLightRotNode);
			
				setRotation(self.flieglPFS16000.redBeaconLightRotNode, 0, lightRotY + self.flieglPFS16000.redBeaconLightSpeed * dt, 0);
			end;
		end;
		
		if self.flieglPFS16000.redBeaconLightCorona ~= nil then
			setVisibility(self.flieglPFS16000.redBeaconLightCorona, true);
		end;
		
		if self.flieglPFS16000.redBeaconLightRealLight ~= nil and self.useRealBeaconLights then
			setVisibility(self.flieglPFS16000.redBeaconLightRealLight, true);
		end;
	else
		if self.flieglPFS16000.redBeaconLightCorona ~= nil then
			setVisibility(self.flieglPFS16000.redBeaconLightCorona, false);
		end;
		
		if self.flieglPFS16000.redBeaconLightRealLight ~= nil and self.useRealBeaconLights then
			setVisibility(self.flieglPFS16000.redBeaconLightRealLight, false);
		end;
	end;
	
	if self.flieglPFS16000.isOverloading and self.foldAnimTime == 0 then
		if self.flieglPFS16000.greenBeaconLightSpeed > 0 then
			if self.flieglPFS16000.greenBeaconLightRotNode ~= nil then
				local _, lightRotY, _ = getRotation(self.flieglPFS16000.greenBeaconLightRotNode);
				
				setRotation(self.flieglPFS16000.greenBeaconLightRotNode, 0, lightRotY + self.flieglPFS16000.greenBeaconLightSpeed * dt, 0);
			end;
		end;
		
		if self.flieglPFS16000.greenBeaconLightCorona ~= nil then
			setVisibility(self.flieglPFS16000.greenBeaconLightCorona, true);
		end;
		
		if self.flieglPFS16000.greenBeaconLightRealLight ~= nil and self.useRealBeaconLights then
			setVisibility(self.flieglPFS16000.greenBeaconLightRealLight, true);
		end;
	else
		if self.flieglPFS16000.greenBeaconLightCorona ~= nil then
			setVisibility(self.flieglPFS16000.greenBeaconLightCorona, false);
		end;
		
		if self.flieglPFS16000.greenBeaconLightRealLight ~= nil and self.useRealBeaconLights then
			setVisibility(self.flieglPFS16000.greenBeaconLightRealLight, false);
		end;
	end;
end;

function FlieglPFS16000:updateTick(dt)
    if self.isServer then
        if self.motorStopTimer < self.motorStopTimerDuration then
			self.motorStopTimer = self.motorStopTimerDuration;
		end;
		
		self.flieglPFS16000.isOverloading = false;
        
		local fillLevel = self:getUnitFillLevel(self.flieglPFS16000.fillUnitIndex);
        
		if fillLevel > 0 then
			local capacity = 0;
		
			if self.turnOnVehicle and self:getIsTurnedOn() then
				local fillType = self:getUnitFillType(self.flieglPFS16000.fillUnitIndex);
				local fillTypeIsForBale = FillUtil.categoryToFillTypes[FillUtil.FILLTYPE_CATEGORY_WINDROW][fillType] ~= nil or fillType == FillUtil.FILLTYPE_SILAGE;

				if self.flieglPFS16000.currentBox then
					if not fillTypeIsForBale then
						self.flieglPFS16000.litersPerMs = self.flieglPFS16000.litersPerMinutePaletts;
						
						capacity = self.flieglPFS16000.currentBox:getCapacity();
					else
						self.flieglPFS16000.litersPerMs = self.flieglPFS16000.litersPerMinuteBales;
						
						capacity = 4000;
						
						if fillType == FillUtil.FILLTYPE_SILAGE then
							capacity = 6000;
							
							self.flieglPFS16000.litersPerMs = self.flieglPFS16000.litersPerMinuteBales * 1.3;
						end;
					end;
					
					if self.flieglPFS16000.currentBox:getFillLevel() < (capacity - 0.0001) then
						local delta = math.min(self.flieglPFS16000.litersPerMs * dt, fillLevel, (capacity - 0.0001) - self.flieglPFS16000.currentBox:getFillLevel());

						self.flieglPFS16000.currentBox:setFillLevel(self.flieglPFS16000.currentBox:getFillLevel() + delta);
						self:setUnitFillLevel(self.flieglPFS16000.fillUnitIndex, fillLevel - delta, fillType, false, self.fillVolumeUnloadInfos[self.flieglPFS16000.unloadInfoIndex]);
						self.flieglPFS16000.isOverloading = true;
					else
						self.flieglPFS16000.currentBox.fillLevel = capacity;
						
						if self.flieglPFS16000.automaticPalletPusher then					
							if self.foldAnimTime == 0 then
								self:setFoldState(self:getToggledFoldDirection(), true);
							elseif self.foldAnimTime == 1 then
								self:setFoldState(self:getToggledFoldDirection(), false);
							end;
						end;
					end;
				else
					if self.flieglPFS16000.objectsInBoxPlace == 0 then
						self.fillableObject = nil;
						
						if self.flieglPFS16000.raycastNode then
							local x,y,z = getWorldTranslation(self.flieglPFS16000.raycastNode);
							local dx,dy,dz = localDirectionToWorld(self.flieglPFS16000.raycastNode, 0, -1, 0);
							
							raycastAll(x,y,z, dx,dy,dz, "findFillableRaycastCallback", self.flieglPFS16000.raycastMaxDistance, self);
						end
	
						if self.fillableObject == nil then
							self.flieglPFS16000.timeUntilNextBox = self.flieglPFS16000.timeUntilNextBox - dt;
							
							if self.flieglPFS16000.timeUntilNextBox < 0 then
								if g_currentMission:getCanAddLimitedObject(FSBaseMission.LIMITED_OBJECT_TYPE_PALLET) then
									if fillTypeIsForBale then
										self:createBale();
									else	
										self:createBox();
									end;
								else
									if self.flieglPFS16000.createBoxes then
										g_currentMission:showBlinkingWarning(g_i18n:getText("warning_tooManyPallets"), 2000);
									end;
								end;
								
								self.flieglPFS16000.timeUntilNextBox = self.flieglPFS16000.nextBoxWaitTime;
							end;
						else
							local object = self.fillableObject;
							
							if object:allowFillType(fillType) then
								if self.flieglPFS16000.unloadingStartTime < 0 then
									self.flieglPFS16000.unloadingStartTime = g_currentMission.time;
								end;
	
								local freeCapacity = 0;
								local capacity = 0;
								
								if not fillTypeIsForBale then
									capacity = object:getCapacity();
								else
									capacity = 4000
						
									if fillType == FillUtil.FILLTYPE_SILAGE then
										capacity = 6000;
									end;
								end;
								
								freeCapacity = capacity - object:getFillLevel();
								
								if freeCapacity > 0 then
									self.flieglPFS16000.isOverloading = true;
								end;
	
								if self.flieglPFS16000.unloadingStartTime + self.flieglPFS16000.unloadingDelay < g_currentMission.time then
									local delta = math.min(fillLevel, freeCapacity, self.flieglPFS16000.litersPerMs * dt);
									
									if math.abs(delta) > 0 then
										self:setUnitFillLevel(self.flieglPFS16000.fillUnitIndex, fillLevel - delta, fillType, false, self.fillVolumeUnloadInfos[self.flieglPFS16000.unloadInfoIndex]);
										object:setFillLevel(object:getFillLevel() + delta, fillType, false, self.fillVolumeDischargeInfos[self.flieglPFS16000.dischargeInfoIndex]);
									else
										self.flieglPFS16000.unloadingStartTime = -1;
									end;
								end;
							end;
						end;
					else
						self.flieglPFS16000.timeUntilNextBox = self.flieglPFS16000.nextBoxWaitTime;
					end;
				end;
			end;
        end;
    end;

    if self.isClient then
		Utils.updateScrollers(self.flieglPFS16000.fillScrollerNodes, dt, self.flieglPFS16000.isOverloading, self.flieglPFS16000.isOverloading);

		if self.flieglPFS16000.sampleFill then
			if self.isFilling then
				if self:getIsActiveForSound(true) then
					SoundUtil.playSample(self.flieglPFS16000.sampleFill, 0, 0, nil);
					SoundUtil.stop3DSample(self.flieglPFS16000.sampleFill);
				else
					SoundUtil.stopSample(self.flieglPFS16000.sampleFill);
					SoundUtil.play3DSample(self.flieglPFS16000.sampleFill);
				end;
			else
				SoundUtil.stopSample(self.flieglPFS16000.sampleFill);
				SoundUtil.stop3DSample(self.flieglPFS16000.sampleFill);
			end;
		end;
		
		if self.flieglPFS16000.sampleWarning then
			if self.foldAnimTime > 0 and self.foldAnimTime < 1 then
				if self:getIsActiveForSound(true) then
					SoundUtil.playSample(self.flieglPFS16000.sampleWarning, 0, 0, nil);
					SoundUtil.stop3DSample(self.flieglPFS16000.sampleWarning);
				else
					SoundUtil.stopSample(self.flieglPFS16000.sampleWarning);
					SoundUtil.play3DSample(self.flieglPFS16000.sampleWarning);
				end;
			else
				SoundUtil.stopSample(self.flieglPFS16000.sampleWarning);
				SoundUtil.stop3DSample(self.flieglPFS16000.sampleWarning);
			end;
		end;
    end;
end;

function FlieglPFS16000:draw()
    if self.isClient then
        if self:getIsActiveForInput() then
			if self:getIsTurnedOnAllowed() and not self:getIsTurnedOn() and InputBinding.hasEvent(self.turnOnVehicle.toggleTurnOnInputBinding) and self.attacherVehicle then
				g_currentMission:showBlinkingWarning(g_i18n:getText("warning_pleaseDetachFirstTractor"), 2000);
			end;
			
			if self.turnOnVehicle and self:getIsTurnedOn() then
				local fillType = self:getUnitFillType(self.flieglPFS16000.fillUnitIndex);
				local fillTypeIsForBale = FillUtil.categoryToFillTypes[FillUtil.FILLTYPE_CATEGORY_WINDROW][fillType] ~= nil or fillType == FillUtil.FILLTYPE_SILAGE;
				
				if self.flieglPFS16000.createBoxes then
					if not fillTypeIsForBale then
						g_currentMission:addHelpButtonText(g_i18n:getText("action_disablePalletSpawning"), InputBinding.TOGGLE_TIPSTATE, nil, GS_PRIO_NORMAL);
					else
						g_currentMission:addHelpButtonText(g_i18n:getText("action_disableBaleSpawning"), InputBinding.TOGGLE_TIPSTATE, nil, GS_PRIO_NORMAL);
					end;
				else
					if not fillTypeIsForBale then
						g_currentMission:addHelpButtonText(g_i18n:getText("action_enablePalletSpawning"), InputBinding.TOGGLE_TIPSTATE, nil, GS_PRIO_NORMAL);
					else
						g_currentMission:addHelpButtonText(g_i18n:getText("action_enableBaleSpawning"), InputBinding.TOGGLE_TIPSTATE, nil, GS_PRIO_NORMAL);
					end;
				end;
				
				if self.flieglPFS16000.automaticPalletPusher then
					if not fillTypeIsForBale then
						g_currentMission:addHelpButtonText(g_i18n:getText("action_disableAutomaticPalletPusher"), InputBinding.TOGGLE_AI, nil, GS_PRIO_NORMAL);
					else
						g_currentMission:addHelpButtonText(g_i18n:getText("action_disableAutomaticBalePusher"), InputBinding.TOGGLE_AI, nil, GS_PRIO_NORMAL);
					end;
				else
					if not fillTypeIsForBale then
						g_currentMission:addHelpButtonText(g_i18n:getText("action_enableAutomaticPalletPusher"), InputBinding.TOGGLE_AI, nil, GS_PRIO_NORMAL);
					else
						g_currentMission:addHelpButtonText(g_i18n:getText("action_enableAutomaticBalePusher"), InputBinding.TOGGLE_AI, nil, GS_PRIO_NORMAL);
					end;
				end;
				
				if not fillTypeIsForBale then
					self.posDirectionText = "action_unfoldPalletPusher";
					self.negDirectionText = "action_foldPalletPusher";
				else
					self.posDirectionText = "action_unfoldBalePusher";
					self.negDirectionText = "action_foldBalePusher";
				end;
			end;
        end;
    end;
end;

function FlieglPFS16000:setUnitFillLevel(fillUnitIndex, fillLevel, fillType, force, fillInfo)
    if fillLevel > 0 and self.isClient then
		self.flieglPFS16000.currentBoxFillType = fillType;
    end;
end;

function FlieglPFS16000:setCreateBoxes(superFunc, state, noEventSend)
    if superFunc then
        superFunc(self, state, noEventSend);
    end;

    ReceivingHopperSetCreateBoxesEvent.sendEvent(self, state, noEventSend);
    
	self.flieglPFS16000.createBoxes = state;
end;

function FlieglPFS16000:setAutomaticPalletPusher(state, noEventSend)
	FlieglPFS16000SetAutomaticPalletPusherEvent.sendEvent(self, state, noEventSend);
    
	self.flieglPFS16000.automaticPalletPusher = state;
end;

function FlieglPFS16000:createBox()
    if self.isServer then
		if self.turnOnVehicle and self:getIsTurnedOn() and self.flieglPFS16000.createBoxes then
			local fillType = self:getUnitFillType(self.flieglPFS16000.fillUnitIndex);
			local i3dFilename = Utils.getFilename(self.flieglPFS16000.boxes[fillType], self.baseDirectory);
			local x, y, z = getWorldTranslation(self.flieglPFS16000.boxSpawnPlace);
			
			setRotation(self.flieglPFS16000.boxSpawnPlace, 0, math.rad(90), 0);
			
			local rx, ry, rz = getWorldRotation(self.flieglPFS16000.boxSpawnPlace);
			
			if string.find(string.upper(i3dFilename), "BALE") then
				y = y + 0.15;
				x = x + 0.25;
			end;
			
			self.flieglPFS16000.currentBox = FillablePallet:new(self.isServer, self.isClient);
			self.flieglPFS16000.currentBox:load(i3dFilename, x, y, z, rx, ry, rz);
			self.flieglPFS16000.currentBox:register();
        end;
    end;
end;

function FlieglPFS16000:createBale()
    if self.isServer then
		if self.turnOnVehicle and self:getIsTurnedOn() and self.flieglPFS16000.createBoxes then
			--## yes, this funcion uses the sames variable's, (self.flieglPFS16000.currentBox), as the function to create boxes above. It's not required here to create new variable's, because it will NEVER run both of them at the same time!
			--## new variable's here would only force many unnecessary change's at the whole lua file!
			
			local fillType = self:getUnitFillType(self.flieglPFS16000.fillUnitIndex);
			local i3dFilename = Utils.getFilename(self.flieglPFS16000.boxes[fillType], self.baseDirectory);
			local x, y, z = getWorldTranslation(self.flieglPFS16000.boxSpawnPlace);
			
			setRotation(self.flieglPFS16000.boxSpawnPlace, 0, 0, 0);
			
			local rx, ry, rz = getWorldRotation(self.flieglPFS16000.boxSpawnPlace);
			
			if fillType ~= FillUtil.FILLTYPE_SILAGE then
				y = y + 0.55;
			else
				y = y + 0.15;
				x = x - 0.25;
			end;
			
			self.flieglPFS16000.currentBox = Bale:new(self.isServer, self.isClient);
			self.flieglPFS16000.currentBox:load(i3dFilename, x, y, z, rx, ry, rz, 0);
			self.flieglPFS16000.currentBox:register();
        end;
    end;
end;

function FlieglPFS16000:onBoxTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if onEnter then
        local object = g_currentMission:getNodeObject(otherShapeId);
        
		if object then
            self.flieglPFS16000.objectsInBoxPlace = self.flieglPFS16000.objectsInBoxPlace + 1;
            
			local fillType = self:getUnitFillType(self.flieglPFS16000.fillUnitIndex);
            
			if self.flieglPFS16000.currentBox == nil and object:getFillType() == fillType then
				self.flieglPFS16000.currentBox = object;
            end;
        end;
    elseif onLeave then
        local object = g_currentMission:getNodeObject(otherShapeId);
        
		if object then
            self.flieglPFS16000.objectsInBoxPlace = math.max(0, self.flieglPFS16000.objectsInBoxPlace - 1);
        end;
		
		if object == self.flieglPFS16000.currentBox then
            self.flieglPFS16000.currentBox = nil;
        end;
    end;
end;

function FlieglPFS16000:findFillableRaycastCallback(transformId, x, y, z, distance)
    local vehicle = g_currentMission.nodeToVehicle[transformId];
    
	if vehicle then
        if vehicle.exactFillRootNode == transformId then
            self.fillableObject = vehicle;
            
			return false;
        elseif vehicle.shovel then
            self.fillableObject = vehicle;
            
			return false;
        end;
    end;
    
	return true;
end;


-----------------------------------------------------------------------------------------------------------
--## event for switch between automatic and manual pallet pusher

FlieglPFS16000SetAutomaticPalletPusherEvent = {};
FlieglPFS16000SetAutomaticPalletPusherEvent_mt = Class(FlieglPFS16000SetAutomaticPalletPusherEvent, Event);

InitEventClass(FlieglPFS16000SetAutomaticPalletPusherEvent, "FlieglPFS16000SetAutomaticPalletPusherEvent");

function FlieglPFS16000SetAutomaticPalletPusherEvent:emptyNew()
    local self = Event:new(FlieglPFS16000SetAutomaticPalletPusherEvent_mt);
    
	return self;
end;

function FlieglPFS16000SetAutomaticPalletPusherEvent:new(object, state)
    local self = FlieglPFS16000SetAutomaticPalletPusherEvent:emptyNew()
    
	self.object = object;
    self.state = state;
    
	return self;
end;

function FlieglPFS16000SetAutomaticPalletPusherEvent:readStream(streamId, connection)
    self.object = readNetworkNodeObject(streamId);
    self.state = streamReadBool(streamId);
    self:run(connection);
end;

function FlieglPFS16000SetAutomaticPalletPusherEvent:writeStream(streamId, connection)
    writeNetworkNodeObject(streamId, self.object);
    streamWriteBool(streamId, self.state);
end;

function FlieglPFS16000SetAutomaticPalletPusherEvent:run(connection)
    self.object:setCreateBoxes(self.state, true);
    
	if not connection:getIsServer() then
        g_server:broadcastEvent(FlieglPFS16000SetAutomaticPalletPusherEvent:new(self.object, self.state), nil, connection, self.object);
    end;
end;

function FlieglPFS16000SetAutomaticPalletPusherEvent.sendEvent(vehicle, state, noEventSend)
    if state ~= vehicle.state then
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(FlieglPFS16000SetAutomaticPalletPusherEvent:new(vehicle, state), nil, nil, vehicle);
            else
                g_client:getServerConnection():sendEvent(FlieglPFS16000SetAutomaticPalletPusherEvent:new(vehicle, state));
            end;
        end;
    end;
end;