
-- Baler
-- Class for all Balers
--
-- @author  Stefan Geiger
-- @date  10/09/08
--
-- @history		27/10/13 -- Added support for multiple bale sizes - JoXXer, BJR-Modding
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

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

VariableChamberBaler = {};

VariableChamberBaler.UNLOADING_CLOSED = 1;
VariableChamberBaler.UNLOADING_OPENING = 2;
VariableChamberBaler.UNLOADING_OPEN = 3;
VariableChamberBaler.UNLOADING_CLOSING = 4;

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

function VariableChamberBaler:load(xmlFile)

	self.getIsAreaActive = Utils.overwrittenFunction(self.getIsAreaActive, VariableChamberBaler.getIsAreaActive);
    self.setIsTurnedOn = SpecializationUtil.callSpecializationsFunction("setIsTurnedOn");
    self.setBeltScroll = SpecializationUtil.callSpecializationsFunction("setBeltScroll");
    self.netBaleAnimation = SpecializationUtil.callSpecializationsFunction("netBaleAnimation");
	self.setPickup = SpecializationUtil.callSpecializationsFunction("setPickup");
	self.toggleParticles = SpecializationUtil.callSpecializationsFunction("toggleParticles");
	self.setBaleCount = SpecializationUtil.callSpecializationsFunction("setBaleCount");
	self.setCurrentBaleSize = SpecializationUtil.callSpecializationsFunction("setCurrentBaleSize");
	self.setShouldNet = SpecializationUtil.callSpecializationsFunction("setShouldNet");
	self.setDeltaLevel = SpecializationUtil.callSpecializationsFunction("setDeltaLevel");
	self.setVehicleIncreaseRpm = SpecializationUtil.callSpecializationsFunction("setVehicleIncreaseRpm");

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

    self.fillScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.fillScale#value"), 1);
	
	-- Load custom bale-gate animation
	self.baleGateAnimation = {};
    self.baleGateAnimation.animCharSet = 0;
    self.baleGateAnimationEnabled = false;

    local baleGateNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleGateAnimation#node"));

    if baleGateNode ~= nil then
        self.baleGateAnimation.animCharSet = getAnimCharacterSet(baleGateNode);
        if self.baleGateAnimation.animCharSet ~= 0 then
            self.baleGateAnimation.clip = getAnimClipIndex(self.baleGateAnimation.animCharSet, getXMLString(xmlFile, "vehicle.baleGateAnimation#animationClip"));
            if self.baleGateAnimation.clip >= 0 then
                assignAnimTrackClip(self.baleGateAnimation.animCharSet, 0, self.baleGateAnimation.clip);
                self.baleGateAnimation.speedScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleGateAnimation#speedScale"), 1);
                setAnimTrackSpeedScale(self.baleGateAnimation.animCharSet, self.baleGateAnimation.clip, self.baleGateAnimation.speedScale);
                setAnimTrackLoopState(self.baleGateAnimation.animCharSet, 0, false);
				self.baleGateAnimation.animDuration = getAnimClipDuration(self.baleGateAnimation.animCharSet, self.baleGateAnimation.clip);
            end;
        end;
    end;
	
	-- End bale-gate load
	self.baleAnimations = {};
	
    local i = 0;
    while true do
        local key = string.format("vehicle.baleAnimations.baleAnimation(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
		local entry = {};
		
        entry.baleAnimRoot = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#node")), self.components[1].node);

		local unloadAnimationName = getXMLString(xmlFile, key .. "#unloadAnimationName");
		local closeAnimationName = getXMLString(xmlFile, key .. "#closeAnimationName");
		local unloadAnimationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, key .. "#unloadAnimationSpeed"), 1);
		local closeAnimationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, key .. "#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");
					entry.baleUnloadAnimationName = unloadAnimationName;
					entry.baleUnloadAnimationSpeed = unloadAnimationSpeed;
		
					entry.baleCloseAnimationName = closeAnimationName;
					entry.baleCloseAnimationSpeed = closeAnimationSpeed;
		
					entry.baleDropAnimTime = getXMLFloat(xmlFile, key .. "#baleDropAnimTime");
					if entry.baleDropAnimTime == nil then
						entry.baleDropAnimTime = self:getAnimationDuration(entry.baleUnloadAnimationName);
					else
						entry.baleDropAnimTime = entry.baleDropAnimTime * 1000;
					end;
					
					local capacity = Utils.getNoNil(getXMLFloat(xmlFile, key .. "#capacity"), self.capacity);
					entry.capacity = capacity;
					
					local sizeString = getXMLString(xmlFile, key .. "#sizeString");
					entry.sizeString = sizeString;
				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;
		
		table.insert(self.baleAnimations, entry);
		
        i = i +1;
    end;
	
	-- Netting animation
	
	self.nettingAnimation = {};
	
    self.nettingAnimation.index = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.nettingAnimation#index"));
    self.nettingAnimation.speed = Utils.getVectorNFromString(getXMLString(xmlFile, "vehicle.nettingAnimation#speed"), 2);
	
	self.nettingAnimation.nettingTime = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.nettingAnimation#nettingTime"), 2000);
	
	self.nettingAnimation.time = 0;
	
	-- END netting animation

    -- Backward compatibility: Convert non-windrow fill types to the correspoding windrow fill types
    local convertToWindrowFillType = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.fillTypes#convertToWindrowFillType"), true)
    if convertToWindrowFillType then
        local fillTypesToAdd = {};
        for fillType, enabled in pairs(self.fillTypes) do
            if fillType ~= Fillable.FILLTYPE_UNKNOWN and enabled then
                local fruitType = FruitUtil.fillTypeToFruitType[fillType];
                if fruitType ~= nil and not FruitUtil.fillTypeIsWindrow[fillType] then
                    local windrowFillType = FruitUtil.fruitTypeToWindrowFillType[fruitType];
                    if windrowFillType ~= nil and not self.fillTypes[windrowFillType] then
                        self.fillTypes[fillType] = nil;
                        table.insert(fillTypesToAdd, windrowFillType);
                        print("Warning: converted non-windrow fill type to windrow fill type in "..self.configFileName);
                    end
                end
            end
        end
        for _,fillType in pairs(fillTypesToAdd) do
            self.fillTypes[fillType] = true;
        end
    end

    self.baleTypes = {};
    local i = 0;
    while true do
        local key = string.format("vehicle.baleTypes.baleType(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
		
        local filename180 = getXMLString(xmlFile, key.."#filename180");
        local filename160 = getXMLString(xmlFile, key.."#filename160");
        local filename140 = getXMLString(xmlFile, key.."#filename140");
        local filename125 = getXMLString(xmlFile, key.."#filename125");
        local filename110 = getXMLString(xmlFile, key.."#filename110");
        local filename100 = getXMLString(xmlFile, key.."#filename100");
        local filename90 = getXMLString(xmlFile, key.."#filename90");
		
        local fillTypeStr = getXMLString(xmlFile, key.."#fillType");
        local fruitTypeStr = getXMLString(xmlFile, key.."#fruitType");
        if fillTypeStr ~= nil or fruitTypeStr ~= nil then
            local fillType;
            if fillTypeStr ~= nil then
                fillType = Fillable.fillTypeNameToInt[fillTypeStr];
            else
                print("Warning: deprecated attribute baleType#fruitType. Use #fillType instead in "..self.configFileName);

                local fruitType = FruitUtil.fruitTypes[fruitTypeStr];
                if fruitType ~= nil then
                    fillType = FruitUtil.fruitTypeToWindrowFillType[fruitType];
                    if fillType == nil then
                        fillType = FruitUtil.fruitTypeToFillType[fruitType];
                    end
                end
            end

            if fillType ~= nil then
                local entry = {};
				
				entry.filenames = {};
				
				if filename180 ~= nil then
					table.insert(entry.filenames, filename180);
				end;
				if filename160 ~= nil then
					table.insert(entry.filenames, filename160);
				end;
				if filename140 ~= nil then
					table.insert(entry.filenames, filename140);
				end;
				if filename125 ~= nil then
					table.insert(entry.filenames, filename125);
				end;
				if filename110 ~= nil then
					table.insert(entry.filenames, filename110);
				end;
				if filename100 ~= nil then
					table.insert(entry.filenames, filename100);
				end;
				if filename90 ~= nil then
					table.insert(entry.filenames, filename90);
				end;
				
                self.baleTypes[fillType] = entry;

                if self.defaultBaleType == nil then
                    self.defaultBaleType = entry;
                end;
            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;

    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;
	
	-- Fill level indicator
	self.fillLevelIndicator = {};
	self.fillLevelIndicator.index = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.fillLevelIndicator#index"));
	if self.fillLevelIndicator.index ~= nil then
		self.fillLevelIndicator.maxRot = math.rad(Utils.getNoNil(getXMLFloat(xmlFile,  "vehicle.fillLevelIndicator#maxRot"), 0));
		self.fillLevelIndicator.minRot = math.rad(Utils.getNoNil(getXMLFloat(xmlFile,  "vehicle.fillLevelIndicator#minRot"), 0));
		self.fillLevelIndicator.degPerFill = (self.fillLevelIndicator.minRot - self.fillLevelIndicator.maxRot) / self.capacity;
	end;
	
	-- Pickup
	self.pickupNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pickup#index"));
	self.pickupGroundRefNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pickup#groundRef"));
	
    self.pickupAnimationName = Utils.getNoNil(getXMLString(xmlFile, "vehicle.pickup#animationName"), "");
    self.manualPickup = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.pickup#manual"), false);
	
    if self.playAnimation == nil or self.getIsAnimationPlaying == nil then
        self.pickupAnimationName = "";
    end;
    self.pickupAnimationLowerSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.pickup#lowerSpeed"), 1);
    self.pickupAnimationLiftSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.pickup#liftSpeed"), -self.pickupAnimationLowerSpeed);

	-- Compressor roll
	self.compressorRoll = {};
	self.compressorRoll.brackets = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.compressorRoll#rollBracket"));
	if self.compressorRoll.brackets ~= nil then
		self.compressorRoll.bracketMinRot = math.rad(Utils.getNoNil(getXMLFloat(xmlFile,  "vehicle.compressorRoll#bracketMinRot"), 0));
		self.compressorRoll.bracketMaxRot = math.rad(Utils.getNoNil(getXMLFloat(xmlFile,  "vehicle.compressorRoll#bracketMaxRot"), 0));
		
		self.compressorRoll.rods = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.compressorRoll#compressorRods"));
	end;
	
	-- Speed rotating parts
	self.speedRotatingParts = {};
	local i=0;
	while true do
		local baseName = string.format("vehicle.speedRotatingParts.speedRotatingPart(%d)", i);
		local index = getXMLString(xmlFile, baseName.. "#index");
		if index == nil then
			break;
		end;
		local node = Utils.indexToObject(self.components, index);
		if node ~= nil then
			local entry = {};
			entry.node = node;
			entry.rotationSpeedScale = 1.0/Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#radius"), 1);
			entry.needsDelta = Utils.getNoNil(getXMLBool(xmlFile, baseName.."#needsDelta"), false);
			
			table.insert(self.speedRotatingParts, entry);
		end;
		i = i+1;
	end;
	
	-- Rotating parts
	self.pickupRotatingParts = {};
	
    self.pickupRotatingParts.reel = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pickupRotatingParts#reel"));
    self.pickupRotatingParts.knives = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pickupRotatingParts#knives"));
    self.pickupRotatingParts.screwLeft = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pickupRotatingParts#screwLeft"));
    self.pickupRotatingParts.screwRight = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pickupRotatingParts#screwRight"));
    self.pickupRotatingParts.baleRoller = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pickupRotatingParts#baleRoller"));

	-- Particle systems
	self.grassParticleSystem = {};
	local grassPickupParticleBaseName = "vehicle.grassParticleSystems.grassPickupParticleSystem";
	
	self.grassParticleSystem.pickup = {};
	self.grassParticleSystem.pickup.particleSystem = {};
	local grassPickupParticleSystems = Utils.loadParticleSystem(xmlFile, self.grassParticleSystem.pickup.particleSystem, grassPickupParticleBaseName, self.components, false, nil, self.baseDirectory)
	if grassPickupParticleSystems ~= nil then
		self.grassParticleSystem.pickup.isEnabled = false;
		self.grassParticleSystem.pickup.dirtyFlag = self:getNextDirtyFlag();
	
	end;
	
	local grassWorkParticleBaseName = "vehicle.grassParticleSystems.grassWorkParticleSystem";
	
	self.grassParticleSystem.work = {};
	self.grassParticleSystem.work.particleSystem = {};
	local grassWorkParticleSystems = Utils.loadParticleSystem(xmlFile, self.grassParticleSystem.work.particleSystem, grassWorkParticleBaseName, self.components, false, nil, self.baseDirectory)
	if grassWorkParticleSystems ~= nil then
		self.grassParticleSystem.work.isEnabled = false;
		self.grassParticleSystem.work.dirtyFlag = self:getNextDirtyFlag();
	end;
	
	self.wheatParticleSystem = {};
	local wheatPickupParticleBaseName = "vehicle.wheatParticleSystems.wheatPickupParticleSystem";
	
	self.wheatParticleSystem.pickup = {};
	self.wheatParticleSystem.pickup.particleSystem = {};
	local wheatPickupParticleSystems = Utils.loadParticleSystem(xmlFile, self.wheatParticleSystem.pickup.particleSystem, wheatPickupParticleBaseName, self.components, false, nil, self.baseDirectory)
	if wheatPickupParticleSystems ~= nil then
		self.wheatParticleSystem.pickup.isEnabled = false;
		self.wheatParticleSystem.pickup.dirtyFlag = self:getNextDirtyFlag();
	end;
	
	local wheatWorkParticleBaseName = "vehicle.wheatParticleSystems.wheatWorkParticleSystem";
	
	self.wheatParticleSystem.work = {};
	self.wheatParticleSystem.work.particleSystem = {};
	local wheatWorkParticleSystems = Utils.loadParticleSystem(xmlFile, self.wheatParticleSystem.work.particleSystem, wheatWorkParticleBaseName, self.components, false, nil, self.baseDirectory)
	if wheatWorkParticleSystems ~= nil then
		self.wheatParticleSystem.work.isEnabled = false;
		self.wheatParticleSystem.work.dirtyFlag = self:getNextDirtyFlag();
	end;
	
	-- Balecounter
	--  HUD --
	self.file = Utils.getFilename("$dataS/menu/notificationBox_normal.png", self.baseDirectory);
	self.hudPosX = 0.6;
    self.hudPosY = 0.8892;
    self.hudWidth = 0.2;
    self.hudHeight = 0.0968;
    self.HUDJD864 = Overlay:new("HUDJD864", self.file, self.hudPosX, self.hudPosY, self.hudWidth, self.hudHeight);
    self.HUD = false;

	self.totalBalesCount = 0;
	self.currentBalesCount = 0;
	
    self.baleLastPositionTime = 0;

    self.balerUnloadingState = VariableChamberBaler.UNLOADING_CLOSED;

    self.balerPickupFillTypes = {};

    self.bales = {};
    self.wasToFast = false;
    self.isTurnedOn = false;
	self.hasBaler = true;
	self.isPickupDown = false;
	
	self.oldDelta = 0;
	self.oldFillType = 0;
	
	self.warningTime = 0;
	self.warningText = "";
	
	self.currentBaleSize = 1;
	
	self.shouldNet = false;
	self.oldBaleSize = 0;
	self.isNetting = false;
	
	self.saveMinimumRpm = 0;
	self.saveRPMIdleRot = 0;
	self.attacherVehicleCopy = nil;
end;

function VariableChamberBaler:postLoad(xmlFile)

    for fillType, enabled in pairs(self.fillTypes) do
        if enabled and fillType ~= Fillable.FILLTYPE_UNKNOWN then
            if FruitUtil.fillTypeIsWindrow[fillType] then
                table.insert(self.balerPickupFillTypes, fillType);
            end
        end
    end
end

function VariableChamberBaler: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;	
	
	-- Delete particles
	Utils.deleteParticleSystem(self.grassParticleSystem.pickup.particleSystem);
	Utils.deleteParticleSystem(self.wheatParticleSystem.pickup.particleSystem);
	Utils.deleteParticleSystem(self.grassParticleSystem.work.particleSystem);
	Utils.deleteParticleSystem(self.wheatParticleSystem.work.particleSystem);
	
end;

function VariableChamberBaler:readStream(streamId, connection)
    local turnedOn = streamReadBool(streamId);
    self:setIsTurnedOn(turnedOn, true);

    if self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName ~= nil then
        local state = streamReadUIntN(streamId, 7);
        local animTime = streamReadFloat32(streamId);
        if state == VariableChamberBaler.UNLOADING_CLOSED or state == VariableChamberBaler.UNLOADING_CLOSING  then
            self:setIsUnloadingBale(false, true);
            self:setRealAnimationTime(self.baleAnimations[self.currentBaleSize].baleCloseAnimationName, animTime);
        elseif state == VariableChamberBaler.UNLOADING_OPEN or state == VariableChamberBaler.UNLOADING_OPENING then
            self:setIsUnloadingBale(true, true);
            self:setRealAnimationTime(self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName, animTime);
        end
    end;

    local numBales = streamReadUInt8(streamId);
    for i=1, numBales do
        local fillType = streamReadInt8(streamId);
        local fillLevel = streamReadFloat32(streamId);
        self:createBale(fillType, fillLevel);
        if self.baleAnimCurve ~= nil then
            local baleTime = streamReadFloat32(streamId);
            self:setBaleTime(i, baleTime);
        end;
    end;
	
    self.totalBalesCount = streamReadInt32(streamId);
    self.currentBalesCount = streamReadInt32(streamId);
	self.currentBaleSize = streamReadInt8(streamId);
	
	local shouldNet = streamReadBool(streamId);
	self:setShouldNet(shouldNet, true);
end;

function VariableChamberBaler:writeStream(streamId, connection)
    streamWriteBool(streamId, self.isTurnedOn);

    if self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName ~= nil then
        streamWriteUIntN(streamId, self.balerUnloadingState, 7);
        local animTime = 0;
        if self.balerUnloadingState == VariableChamberBaler.UNLOADING_CLOSED or self.balerUnloadingState == VariableChamberBaler.UNLOADING_CLOSING  then
            animTime = self:getRealAnimationTime(self.baleAnimations[self.currentBaleSize].baleCloseAnimationName);
        elseif self.balerUnloadingState == VariableChamberBaler.UNLOADING_OPEN or self.balerUnloadingState == VariableChamberBaler.UNLOADING_OPENING then
            animTime = self:getRealAnimationTime(self.baleAnimations[self.currentBaleSize].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.fillType);
        streamWriteFloat32(streamId, bale.fillLevel);
        if self.baleAnimCurve ~= nil then
            streamWriteFloat32(streamId, bale.time);
        end;
    end;
	
    streamWriteInt32(streamId, self.totalBalesCount);
    streamWriteInt32(streamId, self.currentBalesCount);
	streamWriteInt8(streamId, self.currentBaleSize);
	streamWriteBool(streamId, self.shouldNet);
end;

function VariableChamberBaler:readUpdateStream(streamId, timestamp, connection)
	if connection:getIsServer() then
		-- Grass pickup particles readUpdate
		local hasGrassPickupUpdate = streamReadBool(streamId);
		if hasGrassPickupUpdate then
			local enabled = streamReadBool(streamId);
			Utils.setEmittingState(self.grassParticleSystem.pickup.particleSystem, enabled);
		end;
		-- Wheat pickup particles readUpdate
		local hasWheatPickupUpdate = streamReadBool(streamId);
		if hasWheatPickupUpdate then
			local enabled = streamReadBool(streamId);
			Utils.setEmittingState(self.wheatParticleSystem.pickup.particleSystem, enabled);
		end;
		-- Grass work particles readUpdate
		local hasGrassWorkUpdate = streamReadBool(streamId);
		if hasGrassWorkUpdate then
			local enabled = streamReadBool(streamId);
			Utils.setEmittingState(self.grassParticleSystem.work.particleSystem, enabled);
		end;
		-- Wheat work particles readUpdate
		local hasWheatWorkUpdate = streamReadBool(streamId);
		if hasWheatWorkUpdate then
			local enabled = streamReadBool(streamId);
			Utils.setEmittingState(self.wheatParticleSystem.work.particleSystem, enabled);
		end;
	end;
end;

function VariableChamberBaler:writeUpdateStream(streamId, connection, dirtyMask)
	if not connection:getIsServer() then
		-- Grass pickup particles writeUpdate
		if bitAND(dirtyMask, self.grassParticleSystem.pickup.dirtyFlag) ~= 0 then
			streamWriteBool(streamId, true);
			streamWriteBool(streamId, self.grassParticleSystem.pickup.isEnabled);
		else
			streamWriteBool(streamId, false);
		end;
		-- Wheat pickup particles writeUpdate
		if bitAND(dirtyMask, self.wheatParticleSystem.pickup.dirtyFlag) ~= 0 then
			streamWriteBool(streamId, true);
			streamWriteBool(streamId, self.wheatParticleSystem.pickup.isEnabled);
		else
			streamWriteBool(streamId, false);
		end;
		-- Grass work particles writeUpdate
		if bitAND(dirtyMask, self.grassParticleSystem.work.dirtyFlag) ~= 0 then
			streamWriteBool(streamId, true);
			streamWriteBool(streamId, self.grassParticleSystem.work.isEnabled);
		else
			streamWriteBool(streamId, false);
		end;
		-- Wheat work particles writeUpdate
		if bitAND(dirtyMask, self.wheatParticleSystem.work.dirtyFlag) ~= 0 then
			streamWriteBool(streamId, true);
			streamWriteBool(streamId, self.wheatParticleSystem.work.isEnabled);
		else
			streamWriteBool(streamId, false);
		end;
	end;
end;

function VariableChamberBaler: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 fillTypeStr = getXMLString(xmlFile, baleKey.."#fillType");
            local fillLevel = getXMLFloat(xmlFile, baleKey.."#fillLevel");
            local baleTime = getXMLFloat(xmlFile, baleKey.."#baleTime");
            if fillTypeStr ~= nil and fillLevel ~= nil and (baleTime ~= nil or self.baleAnimCurve == nil) then
                local fillType = Fillable.fillTypeNameToInt[fillTypeStr];
                if fillType ~= nil then
                    self:createBale(fillType, fillLevel);
                    if self.baleAnimCurve ~= nil then
                        self:setBaleTime(table.getn(self.bales), baleTime);
                    end;
                end;
            end;
        end;
    end;
	
	local totalBalesCount =  Utils.getNoNil(getXMLInt(xmlFile, key.."#totalBalesCount"),0);
	local netLeftCount =  Utils.getNoNil(getXMLInt(xmlFile, key.."#netLeftCount"),0);
	self.totalBalesCount = totalBalesCount;
	self.coverEdge.netLeftCount = netLeftCount;
	
	local currentBaleSize = Utils.getNoNil(getXMLInt(xmlFile, key.."#currentBaleSize"),1);
	self:setCurrentBaleSize(currentBaleSize, true);
	
    return BaseMission.VEHICLE_LOAD_OK;
end

function VariableChamberBaler:getSaveAttributesAndNodes(nodeIdent)

	local totalBalesCount= Utils.getNoNil(self.totalBalesCount, 0);
	local netLeftCount= Utils.getNoNil(self.coverEdge.netLeftCount, 0);
	
    local attributes = 'numBales="'..table.getn(self.bales)..'" totalBalesCount="'..string.format("%.1f",totalBalesCount)..'" netLeftCount="'..string.format("%.1f",netLeftCount)..'" currentBaleSize="' .. string.format("%.1f",self.currentBaleSize) .. '"';
	
    local nodes = "";
    local baleNum = 0;

    for i=1, table.getn(self.bales) do
        local bale = self.bales[i];
        local fillTypeStr = "unknown";
        if bale.fillType ~= Fillable.FILLTYPE_UNKNOWN then
            fillTypeStr = Fillable.fillTypeIntToName[bale.fillType];
        end;

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

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

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

function VariableChamberBaler:update(dt)
	-- Stop animation when appropriate
	if isAnimTrackEnabled(self.baleGateAnimation.animCharSet, 0) then
		if getAnimTrackTime(self.baleGateAnimation.animCharSet, 0) > self.baleGateAnimation.animDuration then
			setAnimTrackTime(self.baleGateAnimation.animCharSet, 0, self.baleGateAnimation.animDuration);
			disableAnimTrack(self.baleGateAnimation.animCharSet, 0);
		elseif getAnimTrackTime(self.baleGateAnimation.animCharSet, 0) < 0 then
			setAnimTrackTime(self.baleGateAnimation.animCharSet, 0, 0);
			disableAnimTrack(self.baleGateAnimation.animCharSet, 0);
		end;
	end;
	
    if self:getIsActiveForInput() then
		if self.manualPickup then
			if InputBinding.hasEvent(InputBinding.LOWER_IMPLEMENT) then
				self:setPickup(not self.isPickupDown);
			end;
		end;
		
		if InputBinding.hasEvent(InputBinding.ACTIVATE_OBJECT) then
			self.HUD = not self.HUD;
		end;
		
        if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA) then
			self:setIsTurnedOn(not self.isTurnedOn);
        end;
		
        if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA2) then
            if self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName ~= nil then
                if self.balerUnloadingState == VariableChamberBaler.UNLOADING_CLOSED and self:isUnloadingAllowed() then
                    if table.getn(self.bales) > 0 then
                        self:setIsUnloadingBale(true);
                    end;
                elseif self.balerUnloadingState == VariableChamberBaler.UNLOADING_OPEN  then
                    self:setIsUnloadingBale(false);
                end;
            end;
        end;
		
		if self.isTurnedOn then
			if table.getn(self.bales) == 0 then
				if InputBinding.hasEvent(InputBinding.BJR_REDUCE_BALE_SIZE) then
					if self.currentBaleSize < table.getn(self.baleAnimations) then
						if self.fillLevel < self.baleAnimations[self.currentBaleSize+1].capacity then
							self:setCurrentBaleSize(self.currentBaleSize+1);
						end;
					end;
				end;
				
				if InputBinding.hasEvent(InputBinding.BJR_INCREASE_BALE_SIZE) then
					if self.currentBaleSize > 1 then
						if self.fillLevel < self.baleAnimations[self.currentBaleSize-1].capacity then
							self:setCurrentBaleSize(self.currentBaleSize-1);
						end;
					end;
				end;
				
				if InputBinding.hasEvent(InputBinding.BJR_NET_BALE) then
					if self.fillLevel > 200 and self.shouldNet == false and self.coverEdge.isLoaded then
						self:setShouldNet(true);
					end;
				end;
			end;
		end;
    end;
end;

function VariableChamberBaler:updateTick(dt)
    self.wasToFast = false;
	
	-- PTO speed stuff
	local ptoFast = self:doCheckSpeedLimit() and self.lastSpeed*3600 > 30;
	
	if self.attachablePTO.attached ~= nil and not self.attachablePTO.attached then
		if self.isTurnedOn then
			self.warningTime = self.time + 1500;
			self.warningText = string.format(g_i18n:getText("ATTACH_PTO_WARNING"));
		end;
		self:setIsTurnedOn(false);
	end;
	
	if self.attachablePTO.attached ~= nil and not self.attachablePTO.attached and ptoFast then
		self.warningTime = self.time + 1500;
		self.warningText = string.format(g_i18n:getText("ATTACH_PTO_WARNING_SPEED"));
	end;
	
	
	if self.isServer then
		if self.oldDelta > 0 then
			self:setDeltaLevel(self.oldDelta - 0.5);
		else
			self:setDeltaLevel(0);
		end;
	end;
				
	
	if self.isClient then
		-- Set fillLevelIndicator
		if self.fillLevelIndicator.index ~= nil then
			local rotX, rotY, rotZ = getRotation(self.fillLevelIndicator.index);
			setRotation(self.fillLevelIndicator.index, self.fillLevelIndicator.degPerFill * self.fillLevel, 0, 0);
		end;
		
		if self.attacherVehicle  ~= nil then
			if self.attacherVehicle.motor ~= nil then
				self:setVehicleIncreaseRpm(dt, self.isTurnedOn);
			end;
		end;
		
		if self.isPickupDown and not self.isBlocked then
			for k,v in pairs(self.speedRotatingParts) do
				if v.needsDelta and self.oldDelta > 0 then
					rotate(v.node, v.rotationSpeedScale * self.lastSpeedReal * self.movingDirection * dt, 0, 0);
				elseif not v.needsDelta then
					rotate(v.node, v.rotationSpeedScale * self.lastSpeedReal * self.movingDirection * dt, 0, 0);
				end;
			end;
		end;

		if self.isTurnedOn then
			-- Animate rotating parts
			if self.pickupRotatingParts.reel ~= nil then
				rotate(self.pickupRotatingParts.reel, -0.01 * dt, 0, 0);
			end;
			if self.pickupRotatingParts.knives ~= nil then
				rotate(self.pickupRotatingParts.knives, 0.015 * dt, 0, 0);
			end;
			if self.pickupRotatingParts.screwLeft ~= nil then
				rotate(self.pickupRotatingParts.screwLeft, -0.01 * dt, 0, 0);
			end;
			if self.pickupRotatingParts.screwRight ~= nil then
				rotate(self.pickupRotatingParts.screwRight, -0.01 * dt, 0, 0);
			end;
			if self.fillLevel < self.capacity then
				if self.pickupRotatingParts.baleRoller ~= nil then
					rotate(self.pickupRotatingParts.baleRoller, 0.01 * dt, 0, 0);
				end;
			end;
			
			-- Toggle particles
			if self.oldDelta > 0 then
				self:toggleParticles(true, true);
			else
				self:toggleParticles(false, true);
			end;
		else
			self:toggleParticles(false, true);
			self:toggleParticles(false, false);
		end;
				
		if self.oldDelta > 0 then
			if self.compressorRoll.brackets ~= nil then
				local rotX, rotY, rotZ = getRotation(self.compressorRoll.brackets);
				if rotX > math.rad(-self.oldDelta) and rotX > self.compressorRoll.bracketMinRot then
					rotate(self.compressorRoll.brackets, -0.0002 * dt, 0, 0);
				elseif rotX < math.rad(self.oldDelta) and rotX < self.compressorRoll.bracketMaxRot then
					rotate(self.compressorRoll.brackets, 0.0002 * dt, 0, 0);
				end;
			end;
			if self.compressorRoll.rods ~= nil then
				local rotX, rotY, rotZ = getRotation(self.compressorRoll.rods);
				if rotX > math.rad(self.oldDelta*2) then
					rotate(self.compressorRoll.rods, -0.0003 * dt, 0, 0);
				elseif rotX < math.rad(self.oldDelta*2) then
					rotate(self.compressorRoll.rods, 0.0003 * dt, 0, 0);
				end;
			end;
		else 
			local rotX, rotY, rotZ = getRotation(self.compressorRoll.brackets);
			if rotX < self.compressorRoll.bracketMaxRot then
				rotate(self.compressorRoll.brackets, 0.0002 * dt, 0, 0);
			end;
			if self.compressorRoll.rods ~= nil then
				local rotX, rotY, rotZ = getRotation(self.compressorRoll.rods);
				if rotX < 0 then
					rotate(self.compressorRoll.rods, -0.0002 * dt, 0, 0);
				end;
			end;
		end;
		
		-- Level pickup with ground
		if self.pickupGroundRefNode ~= nil then
			
			local groundRefX, groundRefY, groundRefZ = getWorldTranslation(self.pickupGroundRefNode);
			local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, groundRefX, 0, groundRefZ);
			groundRefX, groundRefY, groundRefZ = worldToLocal(self.pickupGroundRefNode, groundRefX, terrainHeight, groundRefZ);
			
			local groundRefLocalX, groundRefLocalY, groundRefLocalZ = getTranslation(self.pickupGroundRefNode);
			
			local pickupRefX, pickupRefY, pickupRefZ = getTranslation(self.pickupNode);
			
			local deltaY = groundRefY - pickupRefY;
			local deltaZ = pickupRefZ - groundRefLocalZ;
			
			local tangent = math.atan2(deltaY, deltaZ);
			
			if self.isPickupDown and not self:getIsAnimationPlaying(self.pickupAnimationName) then
				setRotation(self.pickupNode, tangent + math.rad(135), 0, 0);
			else
				local pickupAnimation = self.animations[self.pickupAnimationName];
				pickupAnimation.parts[1].endRot = {tangent + math.rad(135), 0, 0};
			end;
		end;
    end;
	
    if self:getIsActive() then
		
        if self.isTurnedOn then
			
            local toFast = self:doCheckSpeedLimit() and self.lastSpeed*3600 > 40;
            if not toFast then
                if self.isServer and self:allowPickingUp() then

                    local totalArea = 0;
                    local usedFillType = Fillable.FILLTYPE_UNKNOWN;
					
					if self.fillLevel < self.capacity 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});
							end;
						end;

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

                    if totalArea > 0 then
                        local literPerSqm = FruitUtil.getFillTypeLiterPerSqm(usedFillType, 1);
                        local literPerPixel = g_currentMission:getFruitPixelsToSqm()*literPerSqm;
                        local deltaLevel = totalArea * literPerPixel * self.fillScale;
						
						self:setDeltaLevel(deltaLevel);
						
						if self.currentFillType ~= self.oldFillType then
							self.oldFillType = usedFillType;
							self:toggleParticles(true, false);
						end;
						
                        local oldFillLevel = self.fillLevel;
                        self:setFillLevel(self.fillLevel+deltaLevel, usedFillType);
                        if self.fillLevel >= self.capacity and self.coverEdge.isLoaded then
                            if self.baleTypes ~= nil then
                                -- create bale
								if self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName ~= nil then

									-- Start the nettingAnimation
									if self.nettingAnimation.time == 0 then
										self:netBaleAnimation(true);
										g_server:broadcastEvent(NetBaleEvent:new(self, true), nil, nil, vehicle);
									end;
                                end;
                            end;
                        end;
                    end;
                end;
            end;

            if self.isClient then
                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;
			
			if self.isServer then
				if self.shouldNet then
					self:setShouldNet(false);
					if self.baleTypes ~= nil then
						self.oldBaleSize = self.currentBaleSize;
						
						if self.fillLevel < (self.baleAnimations[7].capacity + (self.baleAnimations[6].capacity - self.baleAnimations[7].capacity)/2) then
							self:setCurrentBaleSize(7);
						elseif self.fillLevel < (self.baleAnimations[6].capacity + (self.baleAnimations[5].capacity - self.baleAnimations[6].capacity)/2) then
							self:setCurrentBaleSize(6);
						elseif self.fillLevel < (self.baleAnimations[5].capacity + (self.baleAnimations[4].capacity - self.baleAnimations[5].capacity)/2) then
							self:setCurrentBaleSize(5);
						elseif self.fillLevel < (self.baleAnimations[4].capacity + (self.baleAnimations[3].capacity - self.baleAnimations[4].capacity)/2) then
							self:setCurrentBaleSize(4);
						elseif self.fillLevel < (self.baleAnimations[3].capacity + (self.baleAnimations[2].capacity - self.baleAnimations[3].capacity)/2) then
							self:setCurrentBaleSize(3);
						elseif self.fillLevel < (self.baleAnimations[2].capacity + (self.baleAnimations[1].capacity - self.baleAnimations[2].capacity)/2) then
							self:setCurrentBaleSize(2);
						else
							self:setCurrentBaleSize(1);
						end;
						-- create bale
						if self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName ~= nil then
							
							-- Start the nettingAnimation
							if self.nettingAnimation.time == 0 and self.isNetting == false then
								self:netBaleAnimation(true);
								g_server:broadcastEvent(NetBaleEvent:new(self, true), nil, nil, vehicle);
							end;
						end;
					end;
				end;
				
				
				-- Runs when nettingAnimation is finished
				if self.nettingAnimation.time > 0 and self.nettingAnimation.time < self.time and self.isNetting then
					self:netBaleAnimation(false);
					g_server:broadcastEvent(NetBaleEvent:new(self, false), nil, nil, vehicle);
					self:createBale(self.currentFillType, self.capacity);
					g_server:broadcastEvent(BalerCreateBaleEvent:new(self, self.currentFillType, 0), nil, nil, self);
				end;
			
			end;
        end;
		
        if self.isClient then
            if not self.isTurnedOn and self.balerSoundEnabled then
                stopSample(self.balerSound);
                self.balerSoundEnabled = false;
            end;
        end;


        if self.isTurnedOn and self.fillLevel > (self.capacity * 0.88) and self.fillLevel < self.capacity then
            -- start alarm sound
            if not self.balerAlarmEnabled and self:getIsActiveForSound() then
                setSamplePitch(self.balerAlarm, self.balerAlarmPitchOffset);
                playSample(self.balerAlarm, 0, self.balerAlarmVolume, 0);
                self.balerAlarmEnabled = true;
            end;
        else
            -- stop alarm sound
            if self.balerAlarmEnabled then
                stopSample(self.balerAlarm);
                self.balerAlarmEnabled = false;
            end;
        end;

        if self.balerUnloadingState == VariableChamberBaler.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.baleAnimations[self.currentBaleSize].baleUnloadAnimationName);
            local animTime = self:getRealAnimationTime(self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName);
            if not isPlaying or animTime >= self.baleAnimations[self.currentBaleSize].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 = VariableChamberBaler.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 == VariableChamberBaler.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.baleAnimations[self.currentBaleSize].baleCloseAnimationName) then
                self.balerUnloadingState = VariableChamberBaler.UNLOADING_CLOSED;
				if self.isTurnedOn then
					self:setBeltScroll(true);
				end;
            end;

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

		elseif self.balerUnloadingState == VariableChamberBaler.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;
end;

function VariableChamberBaler:draw()
    if self.isClient then
        if self.wasToFast then
            g_currentMission:addWarning(g_i18n:getText("Dont_drive_to_fast") .. "\n" .. string.format(g_i18n:getText("Cruise_control_levelN"), "2"), 0.07+0.022, 0.019+0.029);
        end;
		
		if self.warningTime > self.time then
			g_currentMission:addWarning(self.warningText, 0.018, 0.033);
		end;
		
        if self:getIsActiveForInput(true) then

			if self.isPickupDown then
				g_currentMission:addHelpButtonText(string.format(g_i18n:getText("PICKUP_LIFT")), InputBinding.LOWER_IMPLEMENT);
			else
				g_currentMission:addHelpButtonText(string.format(g_i18n:getText("PICKUP_LOWER")), InputBinding.LOWER_IMPLEMENT);
			end;
			
            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;

            if self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName ~= nil then
                if self.balerUnloadingState == VariableChamberBaler.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 == VariableChamberBaler.UNLOADING_OPEN then
                    g_currentMission:addHelpButtonText(g_i18n:getText("baler_unload_stop"), InputBinding.IMPLEMENT_EXTRA2);
                end;
            end;
			
			if self.isTurnedOn then
				g_currentMission:addExtraPrintText(string.format(g_i18n:getText("BJR_CURRENT_BALE_SIZE"), self.baleAnimations[self.currentBaleSize].sizeString));
				
				if table.getn(self.bales) == 0 then
					if self.currentBaleSize < table.getn(self.baleAnimations) then
						if self.fillLevel < self.baleAnimations[self.currentBaleSize+1].capacity then
							g_currentMission:addHelpButtonText(string.format(g_i18n:getText("BJR_REDUCE_BALE_SIZE"), self.baleAnimations[self.currentBaleSize+1].sizeString), InputBinding.BJR_REDUCE_BALE_SIZE);
						end;
					end;
					
					if self.currentBaleSize > 1 then
						if self.fillLevel < self.baleAnimations[self.currentBaleSize-1].capacity then
							g_currentMission:addHelpButtonText(string.format(g_i18n:getText("BJR_INCREASE_BALE_SIZE"), self.baleAnimations[self.currentBaleSize-1].sizeString), InputBinding.BJR_INCREASE_BALE_SIZE);
						end;
					end;
					
					if self.fillLevel > 200 and self.coverEdge.isLoaded then
						g_currentMission:addHelpButtonText(string.format(g_i18n:getText("BJR_NET_BALE")), InputBinding.BJR_NET_BALE);
					end;
				end;
			end;
			
			setTextColor(unpack(g_currentMission.hudHelpTextColor));
			setTextBold(false);
			if self.HUD then
				self.HUDJD864:render();
				g_currentMission:addHelpButtonText(string.format(g_i18n:getText("BJR_HUD_OFF"), self.typeDesc), InputBinding.ACTIVATE_OBJECT);
				local lineHeight = g_currentMission.hudHelpTextLineSpacing + g_currentMission.hudHelpTextSize;
				local posY = ((self.hudPosY + self.hudHeight) - lineHeight) - 0.01;
				local posXLeft = self.hudPosX + 0.0085;
				local posXRight = self.hudPosX + (self.hudWidth - 0.015);
				
				renderText(posXLeft, posY, g_currentMission.hudHelpTextSize, string.format(g_i18n:getText("BJR_COUNTER_HEADER"), self.typeDesc));
				if self.currentBalesCount ~= nil then
					setTextAlignment(RenderText.ALIGN_LEFT);
					renderText(posXLeft, posY - (lineHeight*2 - 0.01), g_currentMission.hudHelpTextSize, string.format(g_i18n:getText("BJR_COUNTER_SESSION")));
					setTextAlignment(RenderText.ALIGN_RIGHT);
					renderText(posXRight, posY - (lineHeight*2 - 0.01), g_currentMission.hudHelpTextSize, string.format(self.currentBalesCount));
				end;
				if self.totalBalesCount ~= nil then
					setTextAlignment(RenderText.ALIGN_LEFT);
					renderText(posXLeft, posY - (lineHeight*3 - 0.01), g_currentMission.hudHelpTextSize, string.format(g_i18n:getText("BJR_COUNTER_TOTAL")));
					setTextAlignment(RenderText.ALIGN_RIGHT);
					renderText(posXRight, posY - (lineHeight*3 - 0.01), g_currentMission.hudHelpTextSize, string.format(self.totalBalesCount));
				end;
			else
				g_currentMission:addHelpButtonText(string.format(g_i18n:getText("BJR_HUD_ON"), self.typeDesc), InputBinding.ACTIVATE_OBJECT);
			end;
			setTextAlignment(RenderText.ALIGN_LEFT);
        end
    end;
end;

function VariableChamberBaler:onAttach(attacherVehicle)
	self.attacherVehicleCopy = attacherVehicle;
	
	if self.attacherVehicle.motor ~= nil then
		self.saveMinimumRpm = self.attacherVehicle.motor.minRpm;
	else
		if self.attacherVehicle.saveMinRpm ~= nil then
			self.saveMinimumRpm = self.attacherVehicle.saveMinimumRpm;
		else
			self.attacherVehicle.saveMinimumRpm  = 100;
		end;
	end;
	
	if self.attacherVehicle.RPMIndicator ~= nil then
		self.saveRPMIdleRot = self.attacherVehicle.RPMIndicator.idleRot;
	end;
end;

function VariableChamberBaler:onDetach()
    if self.deactivateOnDetach then
        VariableChamberBaler.onDeactivate(self);
    else
        VariableChamberBaler.onDeactivateSounds(self);
    end;
	
	self:setVehicleIncreaseRpm(nil, false);
	
	self.attacherVehicleCopy = nil;
end;

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

function VariableChamberBaler:onDeactivate()
	self:setBeltScroll(false);
	
    self.wasToFast = false;
    self.isTurnedOn = false;
    VariableChamberBaler.onDeactivateSounds(self)
	
	self:toggleParticles(false, true);
	self:toggleParticles(false, false);
	
	self.deactivateOnDetach = false;
end;

function VariableChamberBaler: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 VariableChamberBaler:setIsTurnedOn(isTurnedOn, noEventSend)
    SetTurnedOnEvent.sendEvent(self, isTurnedOn, noEventSend);
    self.isTurnedOn = isTurnedOn;
	
	if table.getn(self.bales) == 0 then
		self:setBeltScroll(isTurnedOn);
	end;

	self.deactivateOnDetach = isTurnedOn;
	
    if self.pickupAnimationName ~= "" and self.manualPickup == false then
        local animTime = nil;
        if self:getIsAnimationPlaying(self.pickupAnimationName) then
            animTime = self:getAnimationTime(self.pickupAnimationName);
        end;
        if isTurnedOn then
            self:playAnimation(self.pickupAnimationName, self.pickupAnimationLowerSpeed, animTime, true)
        else
            self:playAnimation(self.pickupAnimationName, self.pickupAnimationLiftSpeed, animTime, true)
        end;
    end;
end;

function VariableChamberBaler:toggleParticles(state, isPickup)
	-- Toggle pickup particle systems
	if isPickup then
		if self.currentFillType == Fillable.fillTypeNameToInt["grass_windrow"] or self.currentFillType == Fillable.fillTypeNameToInt["dryGrass_windrow"] then
			if self.grassParticleSystem.pickup.isEnabled ~= state then
				self.grassParticleSystem.pickup.isEnabled = state;
				self:raiseDirtyFlags(self.grassParticleSystem.pickup.dirtyFlag);
				if self.isClient then
					Utils.setEmittingState(self.grassParticleSystem.pickup.particleSystem, state);
				end;
			end;
			if self.wheatParticleSystem.pickup.isEnabled then
				self.wheatParticleSystem.pickup.isEnabled = false;
				self:raiseDirtyFlags(self.grassParticleSystem.pickup.dirtyFlag);
				if self.isClient then
					Utils.setEmittingState(self.wheatParticleSystem.pickup.particleSystem, false);
				end;
			end;
		elseif self.currentFillType == Fillable.fillTypeNameToInt["wheat_windrow"] or self.currentFillType == Fillable.fillTypeNameToInt["barley_windrow"] then
			if self.wheatParticleSystem.pickup.isEnabled ~= state then
				self.wheatParticleSystem.pickup.isEnabled = state;
				self:raiseDirtyFlags(self.wheatParticleSystem.pickup.dirtyFlag);
				if self.isClient then
					Utils.setEmittingState(self.wheatParticleSystem.pickup.particleSystem, state);
				end;
			end;
			if self.grassParticleSystem.pickup.isEnabled then
				self.grassParticleSystem.pickup.isEnabled = false;
				self:raiseDirtyFlags(self.wheatParticleSystem.pickup.dirtyFlag);
				if self.isClient then
					Utils.setEmittingState(self.grassParticleSystem.pickup.particleSystem, false);
				end;
			end;
		end;
	else -- Toggle work particle systems
		if self.oldFillType == Fillable.fillTypeNameToInt["grass_windrow"] or self.oldFillType == Fillable.fillTypeNameToInt["dryGrass_windrow"] then
			if self.grassParticleSystem.work.isEnabled ~= state then
				self.grassParticleSystem.work.isEnabled = state;
				self:raiseDirtyFlags(self.grassParticleSystem.work.dirtyFlag);
				if self.isClient then
					Utils.setEmittingState(self.grassParticleSystem.work.particleSystem, state);
				end;
			end;
			if self.wheatParticleSystem.work.isEnabled then
				self.wheatParticleSystem.work.isEnabled = false;
				self:raiseDirtyFlags(self.grassParticleSystem.work.dirtyFlag);
				if self.isClient then
					Utils.setEmittingState(self.wheatParticleSystem.work.particleSystem, false);
				end;
			end;
		elseif self.oldFillType == Fillable.fillTypeNameToInt["wheat_windrow"] or self.oldFillType == Fillable.fillTypeNameToInt["barley_windrow"] then
			if self.wheatParticleSystem.work.isEnabled ~= state then
				self.wheatParticleSystem.work.isEnabled = state;
				self:raiseDirtyFlags(self.wheatParticleSystem.work.dirtyFlag);
				if self.isClient then
					Utils.setEmittingState(self.wheatParticleSystem.work.particleSystem, state);
				end;
			end;
			if self.grassParticleSystem.work.isEnabled then
				self.grassParticleSystem.work.isEnabled = false;
				self:raiseDirtyFlags(self.wheatParticleSystem.work.dirtyFlag);
				if self.isClient then
					Utils.setEmittingState(self.grassParticleSystem.work.particleSystem, false);
				end;
			end;
		end;
	end;
end;

function VariableChamberBaler:setBeltScroll(shouldScroll)
    for _, part in pairs(self.balerUVScrollParts) do
		if shouldScroll 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;
	
	self:toggleParticles(shouldScroll, false);
end;

function VariableChamberBaler:netBaleAnimation(shouldNet)
	self.isNetting = shouldNet;
	
	if shouldNet then
		self.nettingAnimation.time = self.time + self.nettingAnimation.nettingTime;
		setShaderParameter(self.nettingAnimation.index, "uvScrollSpeed", self.nettingAnimation.speed[1], self.nettingAnimation.speed[2], 0, 0, false);
	else
		self.nettingAnimation.time = 0;
		setShaderParameter(self.nettingAnimation.index, "uvScrollSpeed", 0, 0, 0, 0, false);
	end;
	setVisibility(self.nettingAnimation.index, shouldNet);
	self:setBeltScroll(shouldNet);
	
	if not shouldNet then
		if self.currentBalesCount ~= nil and self.totalBalesCount ~= nil then
			self.currentBalesCount = self.currentBalesCount + 1;
			self.totalBalesCount = self.totalBalesCount + 1;
			if self.coverEdge.netLeftCount ~= nil then
				self.coverEdge.netLeftCount = self.coverEdge.netLeftCount - 1;
			end;
		end;
	end;
end;

function VariableChamberBaler:setShouldNet(shouldNet, noEventSend)
	SetShouldNetEvent.sendEvent(self, shouldNet, noEventSend);
	
	self.shouldNet = shouldNet;
end;

function VariableChamberBaler:setDeltaLevel(deltaLevel, noEventSend)
	SetDeltaLevelEvent.sendEvent(self, deltaLevel, noEventSend);
	
	self.oldDelta = deltaLevel;
end;

function VariableChamberBaler:setCurrentBaleSize(newSize, noEventSend)
	SetCurrentBaleSizeEvent.sendEvent(self, newSize, noEventSend);
	
	self.currentBaleSize = newSize;
	self.capacity = self.baleAnimations[newSize].capacity;
end;

function VariableChamberBaler:isUnloadingAllowed()
	if self.hasBaleWrapper == nil or not self.hasBaleWrapper then
		return true;
	end;

	return self:allowsGrabbingBale();
end;

function VariableChamberBaler:setIsUnloadingBale(isUnloadingBale, noEventSend)
    if self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName ~= nil then
        if isUnloadingBale then
            if self.balerUnloadingState ~= VariableChamberBaler.UNLOADING_OPENING then
                BalerSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend)
                self.balerUnloadingState = VariableChamberBaler.UNLOADING_OPENING;
                self:playAnimation(self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName, self.baleAnimations[self.currentBaleSize].baleUnloadAnimationSpeed, nil, true);
				
				-- Play custom animation
				setAnimTrackSpeedScale(self.baleGateAnimation.animCharSet, 0, self.baleGateAnimation.speedScale);
				enableAnimTrack(self.baleGateAnimation.animCharSet, 0);
            end;
        else
            if self.balerUnloadingState ~= VariableChamberBaler.UNLOADING_CLOSING then
                BalerSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend)
                self.balerUnloadingState = VariableChamberBaler.UNLOADING_CLOSING;
                self:playAnimation(self.baleAnimations[self.currentBaleSize].baleCloseAnimationName, self.baleAnimations[self.currentBaleSize].baleCloseAnimationSpeed, nil, true);
				
				-- Play custom animation
				setAnimTrackSpeedScale(self.baleGateAnimation.animCharSet, 0, -self.baleGateAnimation.speedScale);
				enableAnimTrack(self.baleGateAnimation.animCharSet, 0);
            end;
        end;
    end;
end;

function VariableChamberBaler:getTimeFromLevel(level)
    return 0;
end;

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

function VariableChamberBaler:moveBale(i, dt, noEventSend)
    local bale = self.bales[i];
    self:setBaleTime(i, bale.time + dt, noEventSend)
end;

function VariableChamberBaler:setBaleTime(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 VariableChamberBaler:allowFillType(fillType)
    return self.fillTypes[fillType] == true;
end;

function VariableChamberBaler:allowPickingUp()
    if self.baleAnimations[self.currentBaleSize].baleUnloadAnimationName == nil then
        return true;
    end;
    return table.getn(self.bales) == 0 and self.balerUnloadingState == VariableChamberBaler.UNLOADING_CLOSED;
end;

function VariableChamberBaler:createBale(baleFillType, fillLevel)
	-- Stop belts
	self:setBeltScroll(false);
	
    local baleType = self.baleTypes[baleFillType];
    if baleType == nil then
        baleType = self.defaultBaleType;
    end;
    local baleRoot = Utils.loadSharedI3DFile(baleType.filenames[self.currentBaleSize], self.baseDirectory, false, false);

    local baleId = getChildAt(baleRoot, 0);
    link(self.baleAnimations[self.currentBaleSize].baleAnimRoot, baleId);
    delete(baleRoot);

    local bale = {};
    bale.id = baleId;
    bale.time = 0;
    bale.fillType = baleFillType;
    bale.fillLevel = fillLevel;
    bale.filename = Utils.getFilename(baleType.filenames[self.currentBaleSize], self.baseDirectory);
    bale.lastX, bale.lastY, bale.lastZ = getWorldTranslation(bale.id);
    table.insert(self.bales, bale);
end;

function VariableChamberBaler:dropBale(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, bale.fillLevel);
        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.oldBaleSize > 0 then
		self:setCurrentBaleSize(self.oldBaleSize);
		self.oldBaleSize = 0;
	end;
end;


function VariableChamberBaler:setPickup(isPickupState, noEventSend)
	SetPickupEvent.sendEvent(self, isPickupState, noEventSend);
	-- Play pickup animation --
	self.isPickupDown = isPickupState;
	if self.isPickupDown then
		if self.pickupAnimationName ~= nil and self.playAnimation ~= nil then
            self:playAnimation(self.pickupAnimationName, self.pickupAnimationLowerSpeed, animTime, true)
			self.isPickupDown = true;
		end;
	else
		if self.pickupAnimationName ~= nil and self.playAnimation ~= nil then
            self:playAnimation(self.pickupAnimationName, self.pickupAnimationLiftSpeed, animTime, true)
			self.isPickupDown = false;
		end;
	end;
end;

function VariableChamberBaler:setVehicleIncreaseRpm(dt, isActive)
	if self.attacherVehicleCopy ~= nil and self.saveMinimumRpm ~= 0 and
		self.attacherVehicleCopy.motor ~= nil then
		if dt ~= nil then
			if isActive == true then
				self.attacherVehicleCopy.motor.minRpm = math.max(self.attacherVehicleCopy.motor.minRpm-(dt*2), -1200);
			else
				self.attacherVehicleCopy.motor.minRpm = math.min(self.attacherVehicleCopy.motor.minRpm+(dt*5), self.saveMinimumRpm);
			end;
			
			if self.attacherVehicleCopy.RPMIndicator ~= nil and self.attacherVehicleCopy.RPMIndicator.RPMDeg ~= nil and self.attacherVehicleCopy.movingDirection >= 0 then
				self.attacherVehicleCopy.RPMIndicator.idleRot = math.min(-self.attacherVehicleCopy.motor.minRpm * self.attacherVehicleCopy.RPMIndicator.RPMDeg, self.saveRPMIdleRot);
				self.attacherVehicleCopy.RPMIndicator.RPMDeg = (self.attacherVehicleCopy.RPMIndicator.maxRot - self.attacherVehicleCopy.RPMIndicator.idleRot) / self.attacherVehicleCopy.motor.maxRpm[3];
			end;
		else
			self.attacherVehicleCopy.motor.minRpm = self.saveMinimumRpm;
			if self.attacherVehicleCopy.RPMIndicator ~= nil and self.attacherVehicleCopy.RPMIndicator.RPMDeg ~= nil then
				self.attacherVehicleCopy.RPMIndicator.idleRot = self.saveRPMIdleRot;
				self.attacherVehicleCopy.RPMIndicator.RPMDeg = (self.attacherVehicleCopy.RPMIndicator.maxRot - self.saveRPMIdleRot) / self.attacherVehicleCopy.motor.maxRpm[3];
			end;
		end;
		if self.attacherVehicleCopy.isMotorStarted then
			local fuelUsed = 0.0000012*math.abs(self.attacherVehicleCopy.motor.minRpm);
			self.attacherVehicleCopy:setFuelFillLevel(self.attacherVehicleCopy.fuelFillLevel-fuelUsed);
			g_currentMission.missionStats.fuelUsageTotal = g_currentMission.missionStats.fuelUsageTotal + fuelUsed;
			g_currentMission.missionStats.fuelUsageSession = g_currentMission.missionStats.fuelUsageSession + fuelUsed;
		end;
	end;
end;

function VariableChamberBaler:getIsAreaActive(superFunc, area)
    if superFunc ~= nil then
			return superFunc(self, area) and self.isPickupDown;
    end;
	return self.isPickupDown;
end;




-- Events
SetPickupEvent = {};
SetPickupEvent_mt = Class(SetPickupEvent, Event);

InitEventClass(SetPickupEvent, "SetPickupEvent");

function SetPickupEvent:emptyNew()
    local self = Event:new(SetPickupEvent_mt);
    self.className="SetPickupEvent";
    return self;
end;

function SetPickupEvent:new(vehicle, isPickupState)
    local self = SetPickupEvent:emptyNew()
    self.vehicle = vehicle;
	self.isPickupState = isPickupState;
    return self;
end;

function SetPickupEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.isPickupState = streamReadBool(streamId);
    self.vehicle = networkGetObject(id);
    self:run(connection);
end;

function SetPickupEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));
	streamWriteBool(streamId, self.isPickupState);
end;

function SetPickupEvent:run(connection)   
	self.vehicle:setPickup(self.isPickupState, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(SetPickupEvent:new(self.vehicle, self.isPickupState), nil, connection, self.vehicle);
    end;
end;

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



SetCurrentBaleSizeEvent = {};
SetCurrentBaleSizeEvent_mt = Class(SetCurrentBaleSizeEvent, Event);

InitEventClass(SetCurrentBaleSizeEvent, "SetCurrentBaleSizeEvent");

function SetCurrentBaleSizeEvent:emptyNew()
    local self = Event:new(SetCurrentBaleSizeEvent_mt);
    self.className="SetCurrentBaleSizeEvent";
    return self;
end;

function SetCurrentBaleSizeEvent:new(vehicle, newSize)
    local self = SetCurrentBaleSizeEvent:emptyNew()
    self.vehicle = vehicle;
	self.newSize = newSize;
    return self;
end;

function SetCurrentBaleSizeEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.newSize = streamReadInt8(streamId);
    self.vehicle = networkGetObject(id);
    self:run(connection);
end;

function SetCurrentBaleSizeEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));
	streamWriteInt8(streamId, self.newSize);
end;

function SetCurrentBaleSizeEvent:run(connection)   
	self.vehicle:setCurrentBaleSize(self.newSize, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(SetCurrentBaleSizeEvent:new(self.vehicle, self.newSize), nil, connection, self.vehicle);
    end;
end;

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


-- ShouldNet event

SetShouldNetEvent = {};
SetShouldNetEvent_mt = Class(SetShouldNetEvent, Event);

InitEventClass(SetShouldNetEvent, "SetShouldNetEvent");

function SetShouldNetEvent:emptyNew()
    local self = Event:new(SetShouldNetEvent_mt);
    return self;
end;

function SetShouldNetEvent:new(vehicle, shouldNet)
    local self = SetShouldNetEvent:emptyNew()
    self.vehicle = vehicle;
	self.shouldNet = shouldNet;
    return self;
end;

function SetShouldNetEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.vehicle = networkGetObject(id);
	self.shouldNet = streamReadBool(streamId);
    self:run(connection);
end;

function SetShouldNetEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));	
	streamWriteBool(streamId, self.shouldNet);
end;

function SetShouldNetEvent:run(connection)
	self.vehicle:setShouldNet(self.shouldNet, true);
end;

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


-- ShouldNet event

SetDeltaLevelEvent = {};
SetDeltaLevelEvent_mt = Class(SetDeltaLevelEvent, Event);

InitEventClass(SetDeltaLevelEvent, "SetDeltaLevelEvent");

function SetDeltaLevelEvent:emptyNew()
    local self = Event:new(SetDeltaLevelEvent_mt);
    return self;
end;

function SetDeltaLevelEvent:new(vehicle, deltaLevel)
    local self = SetDeltaLevelEvent:emptyNew()
    self.vehicle = vehicle;
	self.deltaLevel = deltaLevel;
    return self;
end;

function SetDeltaLevelEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.vehicle = networkGetObject(id);
	self.deltaLevel = streamReadFloat32(streamId);
    self:run(connection);
end;

function SetDeltaLevelEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));	
	streamWriteFloat32(streamId, self.deltaLevel);
end;

function SetDeltaLevelEvent:run(connection)
	self.vehicle:setDeltaLevel(self.deltaLevel, true);
end;

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


-- NetBaleEvent event

NetBaleEvent = {};
NetBaleEvent_mt = Class(NetBaleEvent, Event);

InitEventClass(NetBaleEvent, "NetBaleEvent");

function NetBaleEvent:emptyNew()
    local self = Event:new(NetBaleEvent_mt);
    return self;
end;

function NetBaleEvent:new(vehicle, shouldNet)
    local self = NetBaleEvent:emptyNew()
    self.vehicle = vehicle;
	self.shouldNet = shouldNet;
    return self;
end;

function NetBaleEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.vehicle = networkGetObject(id);
	self.shouldNet = streamReadBool(streamId);
    self:run(connection);
end;

function NetBaleEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.vehicle));	
	streamWriteBool(streamId, self.shouldNet);
end;

function NetBaleEvent:run(connection)
	self.vehicle:netBaleAnimation(self.shouldNet, true);
end;