--
-- Baler
-- Class for all Balers
--
-- @author  Stefan Geiger
-- @date  10/09/08
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

source("dataS/scripts/vehicles/specializations/SetTurnedOnEvent.lua");
source("dataS/scripts/vehicles/specializations/BalerSetIsUnloadingBaleEvent.lua");
source("dataS/scripts/vehicles/specializations/BalerSetBaleTimeEvent.lua");
source("dataS/scripts/vehicles/specializations/BalerCreateBaleEvent.lua");
source("dataS/scripts/vehicles/specializations/BalerAreaEvent.lua");

Prf180 = {};

Prf180.UNLOADING_CLOSED = 1;
Prf180.UNLOADING_OPENING = 2;
Prf180.UNLOADING_OPEN = 3;
Prf180.UNLOADING_CLOSING = 4;
 
function Prf180.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Fillable, specializations);
end;

function Prf180:load(xmlFile)

	self.setIsTurnedOn = SpecializationUtil.callSpecializationsFunction("setIsTurnedOn");
 
    self.isUnloadingAllowed = Baler.isUnloadingAllowed;
	self.getTimeFromLevel = Prf180.getTimeFromLevel;
	self.moveBales = SpecializationUtil.callSpecializationsFunction("moveBales");
	self.moveBale = SpecializationUtil.callSpecializationsFunction("moveBale");
	self.allowFillType = Prf180.allowFillType;
	self.allowPickingUp = Prf180.allowPickingUp;
	self.setIsUnloadingBale = Prf180.setIsUnloadingBale;
	self.dropBale = Prf180.dropBale;
	self.createBale = Prf180.createBale;
    self.setBaleTime = Prf180.setBaleTime;
	

	self.fillScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.fillScale#value"), 1);
 
	local firstBaleMarker = getXMLFloat(xmlFile, "vehicle.baleAnimation#firstBaleMarker");
	if firstBaleMarker ~= nil then
		local baleAnimCurve = AnimCurve:new(linearInterpolatorN);
		local keyI = 0;
		while true do
			local key = string.format("vehicle.baleAnimation.key(%d)", keyI);
			local t = getXMLFloat(xmlFile, key.."#time");
			local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#pos"));
			if x == nil or y == nil or z == nil then
				break;
			end;
			local rx, ry, rz = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rot"));
			rx = math.rad(Utils.getNoNil(rx, 0));
			ry = math.rad(Utils.getNoNil(ry, 0));
			rz = math.rad(Utils.getNoNil(rz, 0));
			baleAnimCurve:addKeyframe({ v={x, y, z, rx, ry, rz}, time = t});
			keyI = keyI +1;
		end;
		if keyI > 0 then
			self.baleAnimCurve = baleAnimCurve;
			self.firstBaleMarker = firstBaleMarker;
			--self.baleFilename = Utils.getNoNil(getXMLString(xmlFile, "vehicle.baleAnimation#filename"), "data/maps/models/objects/strawbale/strawbaleBaler.i3d");
		end;
	end;
	self.baleAnimRoot = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleAnimation#node")), self.components[1].node);

	-- there is no standard bale animation, load the unload animation (for round baler)
	if self.firstBaleMarker == nil then
		local unloadAnimationName = getXMLString(xmlFile, "vehicle.baleAnimation#unloadAnimationName");
		local closeAnimationName = getXMLString(xmlFile, "vehicle.baleAnimation#closeAnimationName");
		local unloadAnimationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleAnimation#unloadAnimationSpeed"), 1);
		local closeAnimationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleAnimation#closeAnimationSpeed"), 1);
		if unloadAnimationName ~= nil and closeAnimationName ~= nil then
			if self.playAnimation ~= nil and self.animations ~= nil then
				if self.animations[unloadAnimationName] ~= nil and self.animations[closeAnimationName] ~= nil then
					--print("has unload animation");
					self.baleUnloadAnimationName = unloadAnimationName;
					self.baleUnloadAnimationSpeed = unloadAnimationSpeed;

					self.baleCloseAnimationName = closeAnimationName;
					self.baleCloseAnimationSpeed = closeAnimationSpeed;

					self.baleDropAnimTime = getXMLFloat(xmlFile, "vehicle.baleAnimation#baleDropAnimTime");
					if self.baleDropAnimTime == nil then
						self.baleDropAnimTime = self:getAnimationDuration(self.baleUnloadAnimationName);
					else
						self.baleDropAnimTime = self.baleDropAnimTime * 1000;
					end;
				else
					print("Error: Failed to find unload animations '"..unloadAnimationName.."' and '"..closeAnimationName.."' in '"..self.configFileName.."'.");
				end;
			else
				print("Error: There is an unload animation in '"..self.configFileName.."' but it is not a AnimatedVehicle. Change to a vehicle type which has the AnimatedVehicle specialization.");
			end;
		end;
	end;
   
    self.baleTypes = {};
	local i = 0;
	while true do
		local key = string.format("vehicle.baleTypes.baleType(%d)", i);
		local t = getXMLString(xmlFile, key.."#fruitType");
		local filename = getXMLString(xmlFile, key.."#filename");
		if t==nil or filename==nil then
			break;
		end;
	
		local entry = {};
		entry.filename = filename;
		local desc = FruitUtil.fruitTypes[t];
		if desc ~= nil then
			self.baleTypes[desc.index] = entry;

			if self.defaultBaleType == nil then
				self.defaultBaleType = entry;
			end;
		end;
		i = i +1;
	end;
	if self.defaultBaleType == nil then
		self.baleTypes = nil;
	end;
 
	local balerSound = getXMLString(xmlFile, "vehicle.balerSound#file");
	if balerSound ~= nil and balerSound ~= "" then
		balerSound = Utils.getFilename(balerSound, self.baseDirectory);
		self.balerSound = createSample("balerSound");
		self.balerSoundEnabled = false;
		loadSample(self.balerSound, balerSound, false);
		self.balerSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerSound#pitchOffset"), 1);
		self.balerSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerSound#volume"), 1);
	end;
	
	local balerAlarm = getXMLString(xmlFile, "vehicle.balerAlarm#file");
    if balerAlarm ~= nil and balerAlarm ~= "" then
        balerAlarm = Utils.getFilename(balerAlarm, self.baseDirectory);
        self.balerAlarm = createSample("balerAlarm");
        self.balerAlarmEnabled = false;
        loadSample(self.balerAlarm, balerAlarm, false);
        self.balerAlarmPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerAlarm#pitchOffset"), 1);
        self.balerAlarmVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerAlarm#volume"), 1);
    end;

	local balerBaleEject = getXMLString(xmlFile, "vehicle.balerBaleEject#file");
	if balerBaleEject ~= nil and balerBaleEject ~= "" then
		balerBaleEject = Utils.getFilename(balerBaleEject, self.baseDirectory);
		self.balerBaleEject = createSample("balerBaleEject");
		self.balerBaleEjectEnabled = false;
		loadSample(self.balerBaleEject, balerBaleEject, false);
		self.balerBaleEjectPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerBaleEject#pitchOffset"), 1);
		self.balerBaleEjectVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerBaleEject#volume"), 1);
		setSamplePitch(self.balerBaleEject, self.balerBaleEjectPitchOffset);
	end;
	
	local balerDoor = getXMLString(xmlFile, "vehicle.balerDoor#file");
	if balerDoor ~= nil and balerDoor ~= "" then
		balerDoor = Utils.getFilename(balerDoor, self.baseDirectory);
		self.balerDoor = createSample("balerDoor");
		self.balerDoorEnabled = false;
		loadSample(self.balerDoor, balerDoor, false);
		self.balerDoorPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerDoor#pitchOffset"), 1);
		self.balerDoorVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerDoor#volume"), 1);
		setSamplePitch(self.balerDoor, self.balerDoorPitchOffset);
	end;

	local balerKnotCleaning = getXMLString(xmlFile, "vehicle.balerKnotCleaning#file");
	if balerKnotCleaning ~= nil and balerKnotCleaning ~= "" then
		balerKnotCleaning = Utils.getFilename(balerKnotCleaning, self.baseDirectory);
		self.balerKnotCleaning = createSample("balerKnotCleaning");
		self.balerKnotCleaningEnabled = false;
		self.balerKnotCleaningTime = 100000;
		loadSample(self.balerKnotCleaning, balerKnotCleaning, false);
		self.balerKnotCleaningPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerKnotCleaning#pitchOffset"), 1);
		self.balerKnotCleaningVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.balerKnotCleaning#volume"), 1);
		setSamplePitch(self.balerKnotCleaning, self.balerKnotCleaningPitchOffset);
	end;

	self.balerUVScrollParts = {};
	local i = 0;
	while true do
		local key = string.format("vehicle.balerUVScrollParts.balerUVScrollPart(%d)", i);
		if not hasXMLProperty(xmlFile, key) then
			break;
		end;
		local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index"));
		local speed = Utils.getVectorNFromString(getXMLString(xmlFile, key.."#speed"), 2);
		if node ~= nil and speed then
			table.insert(self.balerUVScrollParts, {node=node, speed=speed});
		end;
		i = i +1;
	end;

	self.baleLastPositionTime = 0;
	
	self.balerUnloadingState = Prf180.UNLOADING_CLOSED;

	self.bales = {};
	self.wasToFast = false;
	self.isTurnedOn = false;
    self.hasBaler = true;
	--self.pickupRotLimit = {0};
	
	local path = Utils.getFilename("textures/Balecounter_hud.dds", self.baseDirectory);
    self.balecounterHud = Overlay:new("counterOverlay", path, 0.8143, 0.8315, 0.176, 0.06638);
	
	self.baleCount = 0;
	self.lastBaleCount = 0;
	
	if self.isClient then
		self.wheatParticleSystems = {};
		local i=0;
		while true do
			local key = string.format("vehicle.wheatParticleSystems.wheatParticleSystem(%d)", i);
			local node = getXMLString(xmlFile, key.. "#node");
			if node == nil then
				break;
			end;
			node = Utils.indexToObject(self.components, node);
			if node ~= nil then
				local psFile = getXMLString(xmlFile, key.. "#file");
				local wheatParticleSystem = {};
				wheatParticleSystem.particleSystem = {};
				StaticParticleSystem.loadParticleSystem(xmlFile, wheatParticleSystem.particleSystem, key, self.components, false, nil, self.baseDirectory);
				table.insert(self.wheatParticleSystems, wheatParticleSystem);
			end;
			i = i+1;
		end;
		self.grassParticleSystems = {};
		local i=0;
		while true do
			local key = string.format("vehicle.grassParticleSystems.grassParticleSystem(%d)", i);
			local node = getXMLString(xmlFile, key.. "#node");
			if node == nil then
				break;
			end;
			node = Utils.indexToObject(self.components, node);
			if node ~= nil then
				local psFile = getXMLString(xmlFile, key.. "#file");
				local grassParticleSystem = {};
				grassParticleSystem.particleSystem = {};
				StaticParticleSystem.loadParticleSystem(xmlFile, grassParticleSystem.particleSystem, key, self.components, false, nil, self.baseDirectory);
				table.insert(self.grassParticleSystems, grassParticleSystem);
			end;
			i = i+1;
		end;
	end;
	
	local rotationPartNodeFillLevel = Utils.indexToObject(self.rootNode, getXMLString(xmlFile, "vehicle.rotationPartFillLevel#index"));
    if rotationPartNodeFillLevel ~= nil then
        self.rotationPartFillLevel = {};
        self.rotationPartFillLevel.node = rotationPartNodeFillLevel;

        local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.rotationPartFillLevel#minRot"));
        self.rotationPartFillLevel.minRot = {};
        self.rotationPartFillLevel.minRot[1] = Utils.degToRad(Utils.getNoNil(x, 0));
        self.rotationPartFillLevel.minRot[2] = Utils.degToRad(Utils.getNoNil(y, 0));
        self.rotationPartFillLevel.minRot[3] = Utils.degToRad(Utils.getNoNil(z, 0));

        x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.rotationPartFillLevel#maxRot"));
        self.rotationPartFillLevel.maxRot = {};
        self.rotationPartFillLevel.maxRot[1] = Utils.degToRad(Utils.getNoNil(x, 0));
        self.rotationPartFillLevel.maxRot[2] = Utils.degToRad(Utils.getNoNil(y, 0));
        self.rotationPartFillLevel.maxRot[3] = Utils.degToRad(Utils.getNoNil(z, 0));

        self.rotationPartFillLevel.rotTime = Utils.getNoNil(getXMLString(xmlFile, "vehicle.rotationPartFillLevel#rotTime"), 2)*1000;
        self.rotationPartFillLevel.touchRotLimit = Utils.degToRad(Utils.getNoNil(getXMLString(xmlFile, "vehicle.rotationPartFillLevel#touchRotLimit"), 10));
    end;
	
	self.SetDoorPanel = SpecializationUtil.callSpecializationsFunction("SetDoorPanel");
	self.SetHasRopes = SpecializationUtil.callSpecializationsFunction("SetHasRopes");
	self.DoorPanel = true;		
	self.Ropes = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.Ropes#index"));
	self.RopesCapacity = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.Ropes#Capacity"), 4);
	self.hasRopes = true;
	self.isLoaded = true;
    NoRopeSoundFile = Utils.getFilename("Sounds/NoRope.ogg", self.baseDirectory);
    self.NoRopeSoundId = createSample("NoRopeSound");
    loadSample(self.NoRopeSoundId, NoRopeSoundFile, false);
    self.NoRopePlaying = false;

	self.NoRopesBalesCount = 0;
	
	self.setOverFlow = SpecializationUtil.callSpecializationsFunction("setOverFlow");
	self.OverFlow = false;
	self.OverflowModel = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.OverflowModel#index"));
	self.speedViolationMaxTime = 5000;
	self.speedViolationTimer = self.speedViolationMaxTime;
	
end;

function Prf180:delete()
	if self.balerSound ~= nil then
		delete(self.balerSound);
		self.balerSoundEnabled = false;
	end;
	if self.balerAlarm ~= nil then
        delete(self.balerAlarm);
        self.balerAlarmEnabled = false;
    end;
 	if self.balerBaleEject ~= nil then
        delete(self.balerBaleEject);
        self.balerBaleEjectEnabled = false;
    end;
 	if self.balerDoor ~= nil then
        delete(self.balerDoor);
        self.balerDoorEnabled = false;
    end;
 	if self.balerKnotCleaning ~= nil then
        delete(self.balerKnotCleaning);
        self.balerKnotCleaningEnabled = false;
    end;
	if self.balecounterHud ~= nil then
		self.balecounterHud:delete();
	end;
	for k, wheatParticleSystem in pairs(self.wheatParticleSystems) do
		Utils.deleteParticleSystem(wheatParticleSystem);
	end;
	for k, grassParticleSystem in pairs(self.grassParticleSystems) do
		Utils.deleteParticleSystem(grassParticleSystem);
	end;
    if self.NoRopeSound ~= nil then
        delete(self.NoRopeSound);
    end;
end;

function Prf180:readStream(streamId, connection)
	local turnedOn = streamReadBool(streamId);
	self:setIsTurnedOn(turnedOn, true);
	
	if self.baleUnloadAnimationName ~= nil then
        local state = streamReadUIntN(streamId, 7);
        local animTime = streamReadFloat32(streamId);
        if state == Prf180.UNLOADING_CLOSED or state == Prf180.UNLOADING_CLOSING  then
            self:setIsUnloadingBale(false, true);
            self:setRealAnimationTime(self.baleCloseAnimationName, animTime);
        elseif state == Prf180.UNLOADING_OPEN or state == Prf180.UNLOADING_OPENING then
            self:setIsUnloadingBale(true, true);
            self:setRealAnimationTime(self.baleUnloadAnimationName, animTime);
        end
    end;
 
	local numBales = streamReadInt16(streamId);
	for i=1, numBales do
		local fruitType = streamReadInt8(streamId);
		Prf180.createBale(self, fruitType);
		if self.baleAnimCurve ~= nil then
			local baleTime = streamReadFloat32(streamId);
			Prf180.setBaleTime(self, i, baleTime);
		end;
	end;
	self.baleCount = streamReadInt32(streamId);
    self:SetDoorPanel(streamReadBool(streamId), true);
    self:SetHasRopes(streamReadBool(streamId), true);
	self.NoRopesBalesCount = streamReadInt32(streamId);
	self.OverFlow = streamReadBool(streamId);
end;

function Prf180:writeStream(streamId, connection)
	streamWriteBool(streamId, self.isTurnedOn);
 
    if self.baleUnloadAnimationName ~= nil then
        streamWriteUIntN(streamId, self.balerUnloadingState, 7);
        local animTime = 0;
        if self.balerUnloadingState == Prf180.UNLOADING_CLOSED or self.balerUnloadingState == Prf180.UNLOADING_CLOSING  then
            animTime = self:getRealAnimationTime(self.baleCloseAnimationName);
        elseif self.balerUnloadingState == Prf180.UNLOADING_OPEN or self.balerUnloadingState == Prf180.UNLOADING_OPENING then
            animTime = self:getRealAnimationTime(self.baleUnloadAnimationName);
        end
        streamWriteFloat32(streamId, animTime);
    end
 
    streamWriteUInt8(streamId, table.getn(self.bales));
    for i=1, table.getn(self.bales) do
        local bale = self.bales[i];
		streamWriteInt8(streamId, bale.fruitType);
		if self.baleAnimCurve ~= nil then
			streamWriteFloat32(streamId, bale.time);
		end;
    end;
	streamWriteInt32(streamId, self.baleCount);
    streamWriteBool(streamId, self.DoorPanel);
    streamWriteBool(streamId, self.isLoaded);
	streamWriteInt32(streamId, self.NoRopesBalesCount);
	streamWriteBool(streamId, self.OverFlow);
end;

function Prf180:readUpdateStream(streamId, timestamp, connection)

end;

function Prf180:writeUpdateStream(streamId, connection, dirtyMask)
 
end;
 
function Prf180:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	local numBales = getXMLInt(xmlFile, key.."#numBales");
	if numBales ~= nil then
		for i=1, numBales do
			local baleKey = key..string.format(".bale(%d)", i-1);

			local fruitType = getXMLString(xmlFile, baleKey.."#fruitType");
            local fillLevel = getXMLFloat(xmlFile, baleKey.."#fillLevel");
            local baleTime = getXMLFloat(xmlFile, baleKey.."#baleTime");
            if fruitType ~= nil and fillLevel ~= nil and (baleTime ~= nil or self.baleAnimCurve == nil) then
                local fruitTypeDesc = FruitUtil.fruitTypes[fruitType];
                if fruitTypeDesc ~= nil then
                    self:createBale(fruitTypeDesc, fillLevel);
                    if self.baleAnimCurve ~= nil then
                        self:setBaleTime(table.getn(self.bales), baleTime);
                    end;
                end;
            end;
		end;
	end;
	if not resetVehicles then --moje
		self.baleCount = Utils.getNoNil(getXMLInt(xmlFile, key.."#baleCount"), 0);
	end;
	local NoRopesBalesCount =  Utils.getNoNil(getXMLInt(xmlFile, key.."#NoRopesBalesCount"),0);
	self.NoRopesBalesCount = NoRopesBalesCount;
	self.OverFlow = Utils.getNoNil(getXMLBool(xmlFile, key.."#OverFlow"), false);
	return BaseMission.VEHICLE_LOAD_OK;
end

function Prf180:getSaveAttributesAndNodes(nodeIdent)

    local NoRopesBalesCount= Utils.getNoNil(self.NoRopesBalesCount, 0);
	local attributes = 'numBales="'..table.getn(self.bales)..'"baleCount="'.. tostring(self.baleCount) ..'"NoRopesBalesCount="'..string.format("%.1f",NoRopesBalesCount)..'"OverFlow="'..tostring(self.OverFlow)..'"';
	local nodes = "";
	local baleNum = 0;

	for i=1, table.getn(self.bales) do
		local bale = self.bales[i];
		local fruitType = "unknown";
		if bale.fruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
			fruitType = FruitUtil.fruitIndexToDesc[bale.fruitType].name;
		end;

        if baleNum>0 then
            nodes = nodes.."\n";
        end;
        nodes = nodes..nodeIdent..'<bale fruitType="'..fruitType..'"';
        if self.baleAnimCurve ~= nil then
            nodes = nodes..' baleTime="'..bale.time..'"';
        end;
        nodes = nodes..' />';
        baleNum = baleNum+1;
    end;
    return attributes,nodes;
end;

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

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

function Prf180:update(dt)

	if self:getIsActiveForInput() then
		if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA) then
			self:setIsTurnedOn(not self.isTurnedOn);
		end;

		if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA2) then
			if self.baleUnloadAnimationName ~= nil then
				if self.balerUnloadingState == Prf180.UNLOADING_CLOSED and self:isUnloadingAllowed() then
					if table.getn(self.bales) > 0 then
						self:setIsUnloadingBale(true);
					end;
				elseif self.balerUnloadingState == Prf180.UNLOADING_OPEN then
					self:setIsUnloadingBale(false);
				end;
			end;
		end;
	end;
	if self:getIsActiveForInput() then
		if InputBinding.hasEvent(InputBinding.Prf180_PICKUP) then
			if self.animationParts[1].clipEndTime then
				self:setAnimationTime(1, self.animationParts[1].offSet);
			elseif self.animationParts[1].clipStartTime then
				self:setAnimationTime(1, self.animationParts[1].animDuration);
			end;
		end;
		if InputBinding.hasEvent(InputBinding.Prf180_RESET) then
			self.baleCount = 0;
		end;
	end;
	
	--[[local newRot = Utils.getMovedLimitedValues(self.pickupRotLimit, {8}, {0}, 1, 2000, dt, not self.animationParts[1].clipEndTime);
	local jointDesc = self.componentJoints[1];
	setJointRotationLimit(jointDesc.jointIndex, 0, true, Utils.degToRad(-newRot[1]), Utils.degToRad(newRot[1]));
	self.pickupRotLimit = newRot;]]--
	
	if self.setAnimationTime ~= nil then
		if self.isTurnedOn then
			self:setAnimationTime(3, 1);
		else
			self:setAnimationTime(3, 0);
		end;
	end;

	if self.inrange then
		if InputBinding.hasEvent(InputBinding.Prf180_DOOR) then
			self:SetDoorPanel(not self.DoorPanel);
		end;
		if not self.isLoaded then
			if InputBinding.hasEvent(InputBinding.Prf180_RELOAD) then
				self:SetHasRopes(true);
			end;
		end;
	end;
	if self.NoRopesBalesCount >= self.RopesCapacity then
		self:SetHasRopes(false);
	end;		
	if self.inrange then
		if self.DoorPanel then
			self:setAnimationTime(4, self.animationParts[4].offSet);
			g_currentMission:addHelpButtonText(g_i18n:getText("Prf180_4"), InputBinding.Prf180_DOOR);
		else
			self:setAnimationTime(4, self.animationParts[4].animDuration);
			g_currentMission:addHelpButtonText(g_i18n:getText("Prf180_5"), InputBinding.Prf180_DOOR);
			if not self.isLoaded then
				g_currentMission:addHelpButtonText(g_i18n:getText("Prf180_6"), InputBinding.Prf180_RELOAD);
			end;
		end;
	end;

	if self.isClient then
		if self.OverFlow then
			setVisibility(self.OverflowModel, true);
			self:setIsTurnedOn(false);
			local totalArea =0;
			if self.inrange then
				g_currentMission:addHelpButtonText(g_i18n:getText("overflow"), InputBinding.Prf180_OVERFLOW);
				if InputBinding.hasEvent(InputBinding.Prf180_OVERFLOW) then
					self:setOverFlow(false);
				end;
			end;
		else
			setVisibility(self.OverflowModel, false);
		end;
		if self.isTurnedOn then
			if self.attacherVehicle~=nil and self:doCheckSpeedLimit() and self.attacherVehicle.lastSpeed * 3600 > 18 then
				self.speedViolationTimer = self.speedViolationTimer - dt;
				if self.speedViolationTimer < 0 and self.attacherVehicle~=nil then
					self:setOverFlow(true);
				end;
			else
				self.speedViolationTimer = self.speedViolationMaxTime;
			end;
		end;
	end;
end;

function Prf180:updateTick(dt)
	self.wasToFast = false;
	if self:getIsActive() then
	  if self.animationParts[1].clipEndTime then
		if self.isTurnedOn then
		  
			local toFast = self:doCheckSpeedLimit() and self.lastSpeed*3600 > 30;
			if not toFast then
				if self.isServer and self:allowPickingUp() then
					local totalArea =0;
					local usedFruitType = FruitUtil.FRUITTYPE_UNKNOWN;

					local fruitTypes = {};
					for fillType,enabled in pairs(self.fillTypes) do
						if enabled then
							local fruitType = FruitUtil.fillTypeToFruitType[fillType];
							if fruitType ~= nil and fruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
								table.insert(fruitTypes, fruitType);
							end;
						end;
					end;
					if table.getn(fruitTypes) > 0 then

						local cuttingAreasSend = {};
						for k, cuttingArea in pairs(self.cuttingAreas) do
							if self:getIsAreaActive(cuttingArea) then
								local x,y,z = getWorldTranslation(cuttingArea.start);
								local x1,y1,z1 = getWorldTranslation(cuttingArea.width);
								local x2,y2,z2 = getWorldTranslation(cuttingArea.height);

								table.insert(cuttingAreasSend, {x,z,x1,z1,x2,z2});

								--[[for fillType,v in pairs(self.fillTypes) do
									local fruitType = FruitUtil.fillTypeToFruitType[fillType];
									if fruitType ~= nil and fruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then

										local desc = FruitUtil.fruitIndexToDesc[fruitType];

										local fruitTypeToCut = fruitType;
										if desc.autoSeedFruitType ~= nil then
											fruitTypeToCut = desc.autoSeedFruitType;
											Utils.setGrowthStateAtCutLongArea(fruitType, x, z, x1, z1, x2, z2, desc.autoSeedFruitType, 1)
											Utils.setGrowthStateAtWindrowArea(fruitType, x, z, x1, z1, x2, z2, desc.autoSeedFruitType, 1)
										end;

										local area = Utils.updateFruitWindrowArea(fruitTypeToCut, x, z, x1, z1, x2, z2, 0)*g_currentMission.windrowCutLongRatio;
										area = area + Utils.updateFruitCutLongArea(fruitTypeToCut, x, z, x1, z1, x2, z2, 0);
										if area > 0 then
											totalArea = totalArea+area;
											usedFruitType = fruitType;
											table.insert(cuttingAreasSend, {x,z,x1,z1,x2,z2,fruitType});
										end;
									end;
								end;]]
							end;
						end;

						if table.getn(cuttingAreasSend) > 0 then

							totalArea, usedFruitType = BalerAreaEvent.runLocally(cuttingAreasSend, fruitTypes);
							if totalArea > 0 then
								if (table.getn(cuttingAreasSend) > 0) then
									g_server:broadcastEvent(BalerAreaEvent:new(cuttingAreasSend, fruitTypes));
								end;
							end;
						end;
					end;

					if totalArea > 0 then

						 local deltaLevel = totalArea * self.fillScale;

						if self.baleUnloadAnimationName == nil then
							-- move all bales
							local deltaTime = self:getTimeFromLevel(deltaLevel);
							self:moveBales(deltaTime);
						end;

						local usedFillType = FruitUtil.fruitTypeToFillType[usedFruitType];

						local oldFillLevel = self.fillLevel;
						self:setFillLevel(self.fillLevel+deltaLevel, usedFillType);
						if self.fillLevel == self.capacity then
							if self.baleTypes ~= nil then
								-- create bale
								if self.baleAnimCurve ~= nil then
									local restDeltaFillLevel = deltaLevel - (self.fillLevel-oldFillLevel)
									self:setFillLevel(restDeltaFillLevel, usedFillType);

									Prf180.createBale(self, usedFruitType);

									local numBales = table.getn(self.bales);
									local bale = self.bales[numBales]

									self:moveBale(numBales, self:getTimeFromLevel(restDeltaFillLevel), true);
									-- note: self.bales[numBales] can not be accessed anymore since the bale might be dropped already
									g_server:broadcastEvent(BalerCreateBaleEvent:new(self, usedFruitType, bale.time), nil, nil, self);
								elseif self.baleUnloadAnimationName ~= nil then

									Prf180.createBale(self, usedFruitType);
									g_server:broadcastEvent(BalerCreateBaleEvent:new(self, usedFruitType, 0), nil, nil, self);
								end;
							end;
						end;
						if usedFruitType == FruitUtil.FRUITTYPE_WHEAT or usedFruitType == FruitUtil.FRUITTYPE_BARLEY then
							for k, wheatParticleSystem in pairs(self.wheatParticleSystems) do
								Utils.setEmittingState(wheatParticleSystem.particleSystem, true);
							end;
						elseif usedFruitType == FruitUtil.FRUITTYPE_GRASS or usedFruitType == FruitUtil.FRUITTYPE_DRYGRASS then
							for k, grassParticleSystem in pairs(self.grassParticleSystems) do
								Utils.setEmittingState(grassParticleSystem.particleSystem, true);
							end;
						end;
					elseif totalArea == 0 then
						for k, wheatParticleSystem in pairs(self.wheatParticleSystems) do
							Utils.setEmittingState(wheatParticleSystem.particleSystem, false);
						end;
						for k, grassParticleSystem in pairs(self.grassParticleSystems) do
							Utils.setEmittingState(grassParticleSystem.particleSystem, false);
						end;
					end;
				end;
			else
				for k, wheatParticleSystem in pairs(self.wheatParticleSystems) do
					Utils.setEmittingState(wheatParticleSystem.particleSystem, false);
				end;
				for k, grassParticleSystem in pairs(self.grassParticleSystems) do
					Utils.setEmittingState(grassParticleSystem.particleSystem, false);
				end;
			end;

			if self.isClient then
				if self.balerKnotCleaning ~= nil and self.balerKnotCleaningTime <= self.time and self:getIsActiveForSound() then
					playSample(self.balerKnotCleaning, 1, self.balerKnotCleaningVolume, 0);
					setSamplePitch(self.balerKnotCleaning, self.balerKnotCleaningPitchOffset);
					self.balerKnotCleaningTime = self.time + 120000;
					self.balerKnotCleaningEnabled = true;
				end;

				if not self.balerSoundEnabled and self:getIsActiveForSound() then
					setSamplePitch(self.balerSound, self.balerSoundPitchOffset);
					playSample(self.balerSound, 0, self.balerSoundVolume, 0);
					self.balerSoundEnabled = true;
				end;
			end;

			self.wasToFast = toFast;
		else
			for k, wheatParticleSystem in pairs(self.wheatParticleSystems) do
				Utils.setEmittingState(wheatParticleSystem.particleSystem, false);
			end;
			for k, grassParticleSystem in pairs(self.grassParticleSystems) do
				Utils.setEmittingState(grassParticleSystem.particleSystem, false);
			end;
		end;
		if self.isClient then
			if not self.isTurnedOn and self.balerSoundEnabled then
				stopSample(self.balerSound);
				self.balerSoundEnabled = false;
			end;
		end;
	  else
			self:setIsTurnedOn(false);
			for k, wheatParticleSystem in pairs(self.wheatParticleSystems) do
				Utils.setEmittingState(wheatParticleSystem.particleSystem, false);
			end;
			for k, grassParticleSystem in pairs(self.grassParticleSystems) do
				Utils.setEmittingState(grassParticleSystem.particleSystem, false);
			end;
	  end;

		if self.balerUnloadingState == Prf180.UNLOADING_OPENING then

			if not self.balerBaleEjectEnabled and self:getIsActiveForSound() then
				playSample(self.balerBaleEject, 1, self.balerBaleEjectVolume, 0);
				self.balerBaleEjectEnabled = true;
			end;

			if not self.balerDoorEnabled and self:getIsActiveForSound() then
				playSample(self.balerDoor, 1, self.balerDoorVolume, 0);
				self.balerDoorEnabled = true;
			end;

			local isPlaying = self:getIsAnimationPlaying(self.baleUnloadAnimationName);
			local animTime = self:getRealAnimationTime(self.baleUnloadAnimationName);
			if not isPlaying or animTime >= self.baleDropAnimTime then
				if table.getn(self.bales) > 0 then
 					self:dropBale(1);
                    if self.isServer then
                        self:setFillLevel(0, self.currentFillType);
                    end;
                end;
				if not isPlaying then
					self.balerUnloadingState = Prf180.UNLOADING_OPEN;

					if self.balerBaleEjectEnabled then
						stopSample(self.balerBaleEject);
						self.balerBaleEjectEnabled = false;
					end;
					if self.balerDoorEnabled then
						stopSample(self.balerDoor);
						self.balerDoorEnabled = false;
					end;

				end;
			end;
		elseif self.balerUnloadingState == Prf180.UNLOADING_CLOSING then

			if not self.balerDoorEnabled and self:getIsActiveForSound() then
				playSample(self.balerDoor, 1, self.balerDoorVolume, 0);
				self.balerDoorEnabled = true;
			end;

			if not self:getIsAnimationPlaying(self.baleCloseAnimationName) then
				self.balerUnloadingState = Prf180.UNLOADING_CLOSED;
			end;

			if not self.balerDoorEnabled and self:getIsActiveForSound() then
				playSample(self.balerDoor, 1, self.balerDoorVolume, 0);
				self.balerDoorEnabled = true;
			end;

		elseif self.balerUnloadingState == Prf180.UNLOADING_CLOSING then
			if self.balerDoorEnabled then
				stopSample(self.balerDoor);
				self.balerDoorEnabled = false;
			end;
		end;
		if self.isServer then
			if self.time > self.baleLastPositionTime+100 then
				for i=1, table.getn(self.bales) do
					local bale = self.bales[i];
					bale.lastX, bale.lastY, bale.lastZ = getWorldTranslation(bale.id);
				end;
				self.baleLastPositionTime = self.time;
			end;
		end;
	end;
	if table.getn(self.bales) > self.lastBaleCount then
		self.baleCount = self.baleCount + 1;
		self.lastBaleCount = self.lastBaleCount + 1;
	elseif table.getn(self.bales) < self.lastBaleCount then
		self.lastBaleCount = self.lastBaleCount - 1;
	end;
	if self.rotationPartFillLevel ~= nil then
		local x, y, z = getRotation(self.rotationPartFillLevel.node);
		x = ((self.rotationPartFillLevel.maxRot[1] - self.rotationPartFillLevel.minRot[1]) / self.capacity) * self.fillLevel + self.rotationPartFillLevel.minRot[1];
		setRotation(self.rotationPartFillLevel.node, x, y ,z);
	end;
	if self.attacherVehicle and g_currentMission.player ~= nil then
		-- Getting the distance between the player and the implement 
		local nearestDistance = 4.0; --max distance allowed
		local px, py, pz = getWorldTranslation(self.rootNode); 
		local vx, vy, vz = getWorldTranslation(g_currentMission.player.rootNode);
		local distance = Utils.vector3Length(px-vx, py-vy, pz-vz);	
		if distance < nearestDistance then
			self.inrange = true; 
		else
			self.inrange = false; 
		end;
	end;
end;

function Prf180:draw()
	if self.isClient then
		if self.isTurnedOn then
			if self:doCheckSpeedLimit() and self.lastSpeed*3600 > 18 then
				g_currentMission:addWarning(g_i18n:getText("Prf180_8"));
			end;
		end;
		if self.baleUnloadAnimationName ~= nil then
     		if self.balerUnloadingState == Prf180.UNLOADING_CLOSED and self:isUnloadingAllowed() then
                if table.getn(self.bales) > 0 then
     				g_currentMission:addHelpButtonText(g_i18n:getText("baler_unload"), InputBinding.IMPLEMENT_EXTRA2);
                end;
            elseif self.balerUnloadingState == Prf180.UNLOADING_OPEN then
                g_currentMission:addHelpButtonText(g_i18n:getText("baler_unload_stop"), InputBinding.IMPLEMENT_EXTRA2);
            end;
        end;
		if self.animationParts[1].clipStartTime then
			g_currentMission:addHelpButtonText(g_i18n:getText("Prf180_1"), InputBinding.Prf180_PICKUP);
		elseif self.animationParts[1].clipEndTime then
			if self.isTurnedOn then
				g_currentMission:addHelpButtonText(string.format(g_i18n:getText("turn_off_OBJECT"), self.typeDesc), InputBinding.IMPLEMENT_EXTRA);
			else
				g_currentMission:addHelpButtonText(string.format(g_i18n:getText("turn_on_OBJECT"), self.typeDesc), InputBinding.IMPLEMENT_EXTRA);
			end;
			g_currentMission:addHelpButtonText(g_i18n:getText("Prf180_2"), InputBinding.Prf180_PICKUP);
			g_currentMission:addHelpButtonText(g_i18n:getText("Prf180_3"), InputBinding.Prf180_RESET);
			self.balecounterHud:render();
			setTextAlignment(RenderText.ALIGN_RIGHT);
			setTextBold(true);	
			setTextColor(1, 1, 1, 1);
			renderText(0.9653, 0.849, 0.032, tostring(self.baleCount));
		end;
		if not self.isLoaded then
			g_currentMission:addWarning(g_i18n:getText("Prf180_7"));
		end;
		if self.OverFlow then
			g_currentMission:addWarning(g_i18n:getText("Prf180_OVERFLOW"));
		end;		
	end;
end;

function Prf180:onAttach(attacherVehicle)
	self.attacherVehicle = attacherVehicle;
	self:setAnimationTime(2, self.animationParts[2].animDuration);
end;

function Prf180:onDetach()
	if self.deactivateOnDetach then
		Prf180.onDeactivate(self);
	else
		Prf180.onDeactivateSounds(self);
	end;
	self:setAnimationTime(2, self.animationParts[2].offSet);
end;

function Prf180:onLeave()
	if self.deactivateOnLeave then
		Prf180.onDeactivate(self);
	else
		Prf180.onDeactivateSounds(self);
	end;
end;

function Prf180:onDeactivate()
	for _, part in pairs(self.balerUVScrollParts) do
		setShaderParameter(part.node, "uvScrollSpeed", 0, 0, 0, 0, false);
	end;
	self.wasToFast = false;
	self.isTurnedOn = false;
	Prf180.onDeactivateSounds(self)
end;

function Prf180:onDeactivateSounds()
	if self.balerSoundEnabled then
		stopSample(self.balerSound);
		self.balerSoundEnabled = false;
	end;
	if self.balerAlarmEnabled then
        stopSample(self.balerAlarm);
        self.balerAlarmEnabled = false;
    end;
	if self.balerBaleEjectEnabled then
		stopSample(self.balerBaleEject);
		self.balerBaleEjectEnabled = false;
	end;
	if self.balerDoorEnabled then
		stopSample(self.balerDoor);
		self.balerDoorEnabled = false;
	end;
	if self.balerKnotCleaningEnabled then
		stopSample(self.balerKnotCleaning);
		self.balerKnotCleaningEnabled = false;
	end;
end;

function Prf180:setOverFlow(OverFlow, noEventSend)
	if OverFlow ~= self.OverFlow then
		self.OverFlow = OverFlow;
		if noEventSend == nil or noEventSend == false then
			if g_server ~= nil then
				g_server:broadcastEvent(setOverFlowEvent:new(self, OverFlow), nil, nil, self);
			else
				g_client:getServerConnection():sendEvent(setOverFlowEvent:new(self, OverFlow));
			end;
		end;
	end;
end;

function Prf180:SetDoorPanel(isDoorPanel,noEventSend)
	SetDoorPanelEvent.sendEvent(self, isDoorPanel, noEventSend);
	if isDoorPanel then
		self.DoorPanel = true;
	else
		self.DoorPanel = false;
	end;
end;

function Prf180:SetHasRopes(isLoaded,noEventSend)
	self.isLoaded = isLoaded;
	SetHasRopesEvent.sendEvent(self, self.isLoaded, noEventSend);
	if self.isLoaded then
		self.NoRopesBalesCount = 0;
		setVisibility(self.Ropes, true);
		if self.NoRopePlaying then
			stopSample(self.NoRopeSoundId);
			self.NoRopePlaying = false;
		end;
	else
		setVisibility(self.Ropes, false);	
		self:setIsTurnedOn(false);
		if self:getIsActiveForSound() then
			if not self.NoRopePlaying then
				playSample(self.NoRopeSoundId, 1, 1, 0);
				self.NoRopePlaying = true;
			end;
		end;
	end;
end;
 
function Prf180:setIsTurnedOn(turnedOn, noEventSend)
	SetTurnedOnEvent.sendEvent(self, turnedOn, noEventSend)
	self.isTurnedOn = turnedOn;
	for _, part in pairs(self.balerUVScrollParts) do
		if self.isTurnedOn then
			setShaderParameter(part.node, "uvScrollSpeed", part.speed[1], part.speed[2], 0, 0, false);
		else
			setShaderParameter(part.node, "uvScrollSpeed", 0, 0, 0, 0, false);
		end;
	end;
end;

function Prf180:setIsUnloadingBale(isUnloadingBale, noEventSend)
	if self.baleUnloadAnimationName ~= nil then
		if isUnloadingBale then
			if self.balerUnloadingState ~= Prf180.UNLOADING_OPENING then
				BalerSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend)
				self.balerUnloadingState = Prf180.UNLOADING_OPENING;
				self:playAnimation(self.baleUnloadAnimationName, self.baleUnloadAnimationSpeed, nil, true);
			end;
		else
			if self.balerUnloadingState ~= Prf180.UNLOADING_CLOSING then
				BalerSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend)
				self.balerUnloadingState = Prf180.UNLOADING_CLOSING;
				self:playAnimation(self.baleCloseAnimationName, self.baleCloseAnimationSpeed, nil, true);
			end;
		end;
	end;
end;

function Prf180:getTimeFromLevel(level)
	-- level = capacity -> time = firstBaleMarker
	-- level = 0           -> time = 0
	if self.firstBaleMarker ~= nil then
		return level / self.capacity * self.firstBaleMarker;
	end;
	return 0;
end;

function Prf180:moveBales(dt)
	for i=table.getn(self.bales), 1, -1 do
		self:moveBale(i, dt);
	end;
end;

function Prf180:moveBale(i, dt, noEventSend)
	local bale = self.bales[i];
	self:setBaleTime(i, bale.time + dt, noEventSend)
end;
 
function Prf180.setBaleTime(self, i, baleTime, noEventSend)
	if self.baleAnimCurve ~= nil then
		local bale = self.bales[i];
		bale.time = baleTime;
		local v = self.baleAnimCurve:get(bale.time);
		setTranslation(bale.id, v[1], v[2], v[3]);
		setRotation(bale.id, v[4], v[5], v[6]);
		if bale.time >= 1 then
			self:dropBale(i);
		end;
		if self.isServer then
			if noEventSend == nil or not noEventSend then
				g_server:broadcastEvent(BalerSetBaleTimeEvent:new(self, i, bale.time), nil, nil, self);
			end;
		end;
	end;
end;
 
	-- overwrite Fillable.allowFillType
function Prf180:allowFillType(fillType)
	return self.fillTypes[fillType] == true;
end;

function Prf180:allowPickingUp()
	if self.baleUnloadAnimationName == nil then
		return true;
	end;
	return table.getn(self.bales) == 0 and self.balerUnloadingState == Prf180.UNLOADING_CLOSED;
end;

function Prf180.createBale(self, usedFruitType)
	local baleType = self.baleTypes[usedFruitType];
	if baleType == nil then
		baleType = self.defaultBaleType;
	end;
	local baleRoot = Utils.loadSharedI3DFile(baleType.filename, self.baseDirectory);

	local baleId = getChildAt(baleRoot, 0);
	setRigidBodyType(baleId, "None");
	link(self.baleAnimRoot, baleId);
	delete(baleRoot);

	local bale = {};
	bale.id = baleId;
	bale.time = 0;
	bale.fruitType = usedFruitType;
	bale.filename = Utils.getFilename(baleType.filename, self.baseDirectory);
	bale.lastX, bale.lastY, bale.lastZ = getWorldTranslation(bale.id);
	table.insert(self.bales, bale);
	local i = table.getn(self.bales);
end;
 
function Prf180.dropBale(self, baleIndex)
	local bale = self.bales[baleIndex];
	local deltaRealTime = (self.time - self.baleLastPositionTime)/1000;
	local x,y,z = getWorldTranslation(bale.id);
	local rx,ry,rz = getWorldRotation(bale.id);
	--link(getRootNode(), bale.id);


	if self.isServer then
		local baleObject = Bale:new(self.isServer, self.isClient);
		baleObject:load(bale.filename, x,y,z,rx,ry,rz);
		baleObject:register();

		if not self.hasBaleWrapper or self.moveBaleToWrapper == nil then
		    -- release bale if there's no bale wrapper
		    local lx, ly, lz = bale.lastX, bale.lastY, bale.lastZ;
		    setLinearVelocity(baleObject.nodeId, (x-lx)/deltaRealTime, (y-ly)/deltaRealTime, (z-lz)/deltaRealTime);
		else
			-- move bale to wrapper
 			self:moveBaleToWrapper(baleObject);
		end;
	end;
	delete(bale.id);
	table.remove(self.bales, baleIndex);

	-- increase bale count if variable exists (baling mission)
	if g_currentMission.baleCount ~= nil then
		g_currentMission.baleCount = g_currentMission.baleCount + 1;
	end;
	if self.NoRopesBalesCount ~= nil then
		self.NoRopesBalesCount = self.NoRopesBalesCount +1;
	end;
end;