-- Baler.lua LS2015 --
-- Edit: Rockstar94 --

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");

Baler = {};

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

function Baler.initSpecialization()
    WorkArea.registerAreaType("baler");
end;
	
function Baler.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Fillable, specializations) and SpecializationUtil.hasSpecialization(WorkArea, specializations) and SpecializationUtil.hasSpecialization(TurnOnVehicle, specializations) and SpecializationUtil.hasSpecialization(Pickup, specializations);
end;
	
function Baler:preLoad(xmlFile)
	self.loadWorkAreaFromXML = Utils.overwrittenFunction(self.loadWorkAreaFromXML, Baler.loadWorkAreaFromXML);
end
	
function Baler:load(xmlFile)
	self.slomaParticleSystems = {};
	local i=0;
	while true do
		local baseName = string.format("vehicle.slomaParticleSystems.slomaParticleSystem(%d)", i);

		local particleSystem = {};
		particleSystem.ps = {};
		local ps = Utils.loadParticleSystem(xmlFile, particleSystem.ps, baseName, self.components, false, nil, self.baseDirectory)
		if ps == nil then
			break;
		end;
		table.insert(self.slomaParticleSystems, particleSystem);
		i = i+1;
	end;
		
	self.trawaParticleSystems = {};
	local i=0;
	while true do
		local baseName = string.format("vehicle.trawaParticleSystems.trawaParticleSystem(%d)", i);

		local particleSystem = {};
		particleSystem.ps = {};
		local ps = Utils.loadParticleSystem(xmlFile, particleSystem.ps, baseName, self.components, false, nil, self.baseDirectory)
		if ps == nil then
			break;
		end;
		table.insert(self.trawaParticleSystems, particleSystem);
		i = i+1;
	end;

	self.baleStarNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleStar#index"));
	self.baleStarSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleStar#speed"), 1);

	self.doCheckSpeedLimit = Utils.overwrittenFunction(self.doCheckSpeedLimit, Baler.doCheckSpeedLimit);
	self.allowPickingUp = Utils.overwrittenFunction(self.allowPickingUp, Baler.allowPickingUp);
		
	self.getIsTurnedOnAllowed = Utils.overwrittenFunction(self.getIsTurnedOnAllowed, Baler.getIsTurnedOnAllowed);
		
	self.getIsFoldAllowed = Utils.overwrittenFunction(self.getIsFoldAllowed, Baler.getIsFoldAllowed);
	self.setFillLevel = Utils.appendedFunction(self.setFillLevel, Baler.setFillLevel);
	self.isUnloadingAllowed = Baler.isUnloadingAllowed;
	self.getTimeFromLevel = Baler.getTimeFromLevel;
	self.moveBales = SpecializationUtil.callSpecializationsFunction("moveBales");
	self.moveBale = SpecializationUtil.callSpecializationsFunction("moveBale");
	self.allowFillType = Baler.allowFillType;
	self.setIsUnloadingBale = Baler.setIsUnloadingBale;
	self.dropBale = Baler.dropBale;
	self.createBale = Baler.createBale;
	self.setBaleTime = Baler.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;
        end;
    end;
    self.baleAnimRoot, self.baleAnimRootComponent = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleAnimation#node"));
    if self.baleAnimRoot == nil then
        self.baleAnimRoot = self.components[1].node;
        self.baleAnimRootComponent = self.components[1].node;
    end
	
    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
                    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;
	
    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 isRoundBale = Utils.getNoNil(getXMLBool(xmlFile, key.."#isRoundBale"), false);
        local width = Utils.round(Utils.getNoNil(getXMLFloat(xmlFile, key.."#width"), 1.2), 2);
        local height = Utils.round(Utils.getNoNil(getXMLFloat(xmlFile, key.."#height"), 0.9), 2);
        local length = Utils.round(Utils.getNoNil(getXMLFloat(xmlFile, key.."#length"), 2.4), 2);
        local diameter = Utils.round(Utils.getNoNil(getXMLFloat(xmlFile, key.."#diameter"), 1.8), 2);
        table.insert(self.baleTypes, {isRoundBale=isRoundBale, width=width, height=height, length=length, diameter=diameter});
        i = i + 1;
    end;
    self.currentBaleTypeId = 1;
	
    if table.getn(self.baleTypes) == 0 then
        self.baleTypes = nil;
    end;
	
    if self.isClient then
	    self.sampleBalerStop = Utils.loadSample(xmlFile, {}, "vehicle.balerStopSound", nil, self.baseDirectory);
        self.sampleBaler = Utils.loadSample(xmlFile, {}, "vehicle.balerSound", nil, self.baseDirectory);
        self.sampleBalerAlarm = Utils.loadSample(xmlFile, {}, "vehicle.balerAlarm", nil, self.baseDirectory);
        self.sampleBalerEject = Utils.loadSample(xmlFile, {}, "vehicle.balerBaleEject", nil, self.baseDirectory);
        self.sampleBalerDoor = Utils.loadSample(xmlFile, {}, "vehicle.balerDoor", nil, self.baseDirectory);
        self.sampleBalerKnotCleaning = Utils.loadSample(xmlFile, {}, "vehicle.balerKnotCleaning", nil, self.baseDirectory);
        self.balerKnotCleaningTime = 100000;
	
        self.balerUVScrollParts = Utils.loadScrollers(self.components, xmlFile, "vehicle.balerUVScrollParts.balerUVScrollPart", {}, false);
        self.balerTurnedOnRotationNodes = Utils.loadRotationNodes(xmlFile, {}, "vehicle.turnedOnRotationNodes.turnedOnRotationNode", "baler", self.components);
    end;
	
    self.balingAnimationName = Utils.getNoNil(getXMLString(xmlFile, "vehicle.balingAnimation#name"), "");
    if self.playAnimation == nil or self.getIsAnimationPlaying == nil then
        self.balingAnimationName = "";
    end;
	
	self.balerUnloadingState = Baler.UNLOADING_CLOSED;
	self.balerPickupFillTypes = {};
		
	self.bales = {};
	self.hasBaler = true;
		
	self.knotingAnimation = getXMLString(xmlFile, "vehicle.knotingAnimation#name");
	self.knotingAnimationSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.knotingAnimation#speed"), 1);
		
	self.dummyBale = {};
	self.dummyBale.scaleNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleAnimation#scaleNode"));
	self.dummyBale.baleNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleAnimation#baleNode"));
	self.dummyBale.currentBaleFillType = Fillable.FILLTYPE_UNKNOWN;
	self.dummyBale.currentBale = nil;
		
	self.allowsBaleUnloading = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.baleUnloading#allowed"), false);
	self.baleUnloadingTime = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleUnloading#time"), 4) * 1000;
	self.baleFoldThreshold = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleUnloading#foldThreshold"), 0.25) * self:getCapacity();
		
	self.isBaleUnloading = false;
	self.isBalerSpeedLimitActive = false;
end;
	
function Baler: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 Baler:delete()
	for k, v in pairs(self.slomaParticleSystems) do
		Utils.deleteParticleSystem(v.ps)
	end;
		
	for k, v in pairs(self.trawaParticleSystems) do
		Utils.deleteParticleSystem(v.ps)
	end;
	
    for k, bale in pairs(self.bales) do
        self:dropBale(k);
    end;
	
    if self.dummyBale.currentBale ~= nil then
        delete(self.dummyBale.currentBale);
        self.dummyBale.currentBale = nil;
    end;
	
    if self.isClient then
        Utils.deleteSample(self.sampleBalerStop);
        Utils.deleteSample(self.sampleBaler);
        Utils.deleteSample(self.sampleBalerAlarm);
        Utils.deleteSample(self.sampleBalerDoor);
        Utils.deleteSample(self.sampleBalerEject);
        Utils.deleteSample(self.sampleBalerKnotCleaning);
    end;
end;
	
function Baler:readStream(streamId, connection)
    if self.baleUnloadAnimationName ~= nil then
        local state = streamReadUIntN(streamId, 7);
        local animTime = streamReadFloat32(streamId);
        if state == Baler.UNLOADING_CLOSED or state == Baler.UNLOADING_CLOSING  then
            self:setIsUnloadingBale(false, true);
            self:setRealAnimationTime(self.baleCloseAnimationName, animTime);
        elseif state == Baler.UNLOADING_OPEN or state == Baler.UNLOADING_OPENING then
            self:setIsUnloadingBale(true, true);
            self:setRealAnimationTime(self.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;
end;
	
function Baler:writeStream(streamId, connection)
    if self.baleUnloadAnimationName ~= nil then
        streamWriteUIntN(streamId, self.balerUnloadingState, 7);
        local animTime = 0;
        if self.balerUnloadingState == Baler.UNLOADING_CLOSED or self.balerUnloadingState == Baler.UNLOADING_CLOSING  then
            animTime = self:getRealAnimationTime(self.baleCloseAnimationName);
        elseif self.balerUnloadingState == Baler.UNLOADING_OPEN or self.balerUnloadingState == Baler.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.fillType);
        streamWriteFloat32(streamId, bale.fillLevel);
        if self.baleAnimCurve ~= nil then
            streamWriteFloat32(streamId, bale.time);
        end;
    end;
end;
	
function Baler:readUpdateStream(streamId, timestamp, connection)
end;
	
function Baler:writeUpdateStream(streamId, connection, dirtyMask)
end;
	
function Baler:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
    if not resetVehicles then
        local numBales = getXMLInt(xmlFile, key.."#numBales");
        if numBales ~= nil then
            self.balesToLoad = {};
            for i=1, numBales do
                local baleKey = key..string.format(".bale(%d)", i-1);

                local bale = {};
                local fillTypeStr = getXMLString(xmlFile, baleKey.."#fillType");
                local fillType = Fillable.fillTypeNameToInt[fillTypeStr];
                bale.fillType = fillType;
                bale.fillLevel = getXMLFloat(xmlFile, baleKey.."#fillLevel");
                bale.baleTime = getXMLFloat(xmlFile, baleKey.."#baleTime");
                table.insert(self.balesToLoad, bale);
            end;
        end;
    end;
    return BaseMission.VEHICLE_LOAD_OK;
end
	
function Baler:getSaveAttributesAndNodes(nodeIdent)
    local attributes = 'numBales="'..table.getn(self.bales)..'"';
    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 Baler:mouseEvent(posX, posY, isDown, isUp, button)
end;
	
function Baler:keyEvent(unicode, sym, modifier, isDown)
end;
	
function Baler:update(dt)
    if self.firstTimeRun and self.balesToLoad ~= nil then
        for k,v in pairs(self.balesToLoad) do
            self:createBale(v.fillType, v.fillLevel);
            self:setBaleTime(k, v.baleTime, true);
        end;
        self.balesToLoad = nil;
    end;
	
    if self.isClient then
        Utils.updateRotationNodes(self, self.balerTurnedOnRotationNodes, dt, self:getIsActive() and self:getIsTurnedOn() );
        Utils.updateScrollers(self.balerUVScrollParts, dt, self:getIsActive() and self:getIsTurnedOn());
    end;
end;
	
function Baler:updateTick(dt)
	if not self.isTurnedOn then
		for k,v in ipairs(self.slomaParticleSystems) do
			Utils.setEmittingState(v.ps, false);
		end;
		for k,v in ipairs(self.trawaParticleSystems) do
			Utils.setEmittingState(v.ps, false);
		end;
	end;
	
    self.isBalerSpeedLimitActive = false;
    if self:getIsActive() then
        if self:getIsTurnedOn() then
            if self:allowPickingUp() then
                self.isBalerSpeedLimitActive = true;
                if self.isServer then
					for k,v in ipairs(self.slomaParticleSystems) do
						Utils.setEmittingState(v.ps, false);
					end;
					for k,v in ipairs(self.trawaParticleSystems) do
						Utils.setEmittingState(v.ps, false);
					end;
					self:setAnimationTime(11, 0);
                    local workAreasSend, _, _ = self:getTypedNetworkAreas(WorkArea.AREATYPE_BALER, false);
                    local totalArea =0;
                    local usedFillType = Fillable.FILLTYPE_UNKNOWN;
                    if table.getn(workAreasSend) > 0 then
                        totalArea, usedFillType = BalerAreaEvent.runLocally(workAreasSend, self.balerPickupFillTypes);

                        if totalArea > 0 then
                            g_server:broadcastEvent(BalerAreaEvent:new(workAreasSend, usedFillType));
                        end;
                    end;
					
                    if totalArea > 0 then
					self:setAnimationTime(11, 1);
                        local literPerSqm = FruitUtil.getFillTypeLiterPerSqm(usedFillType, 1);
                        local literPerPixel = g_currentMission:getFruitPixelsToSqm()*literPerSqm;
                        local deltaLevel = totalArea * literPerPixel * self.fillScale;

                        if self.baleUnloadAnimationName == nil then
                            local deltaTime = self:getTimeFromLevel(deltaLevel);
                            self:moveBales(deltaTime);
							if self.baleStarNode ~= nil then
								rotate(self.baleStarNode, self.baleStarSpeed * self.lastSpeedReal * dt ,0,0);
							end;
							if self.currentFillType == Fillable.FILLTYPE_GRASS_WINDROW or self.currentFillType == Fillable.FILLTYPE_DRYGRASS_WINDROW then
								for k,v in ipairs(self.trawaParticleSystems) do
									Utils.setEmittingState(v.ps, true);
								end;								
							else
								for k,v in ipairs(self.slomaParticleSystems) do
									Utils.setEmittingState(v.ps, true);
								end;
							end;
                        end;

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

                                    self:createBale(usedFillType, self:getCapacity());

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

                                    self:moveBale(numBales, self:getTimeFromLevel(restDeltaFillLevel), true);
                                    g_server:broadcastEvent(BalerCreateBaleEvent:new(self, usedFillType, bale.time), nil, nil, self);
                                elseif self.baleUnloadAnimationName ~= nil then

                                    self:createBale(usedFillType, self:getCapacity());
                                    g_server:broadcastEvent(BalerCreateBaleEvent:new(self, usedFillType, 0), nil, nil, self);
                                end;
                            end;
                        end;
                    end;
                end;
            end;

            if self.isClient and self:getIsActiveForSound() then
                if self.balerKnotCleaningTime <= g_currentMission.time then
                    Utils.playSample(self.sampleBalerKnotCleaning, 1, 0, nil);
                    self.balerKnotCleaningTime = g_currentMission.time + 120000;
                end;
                Utils.playSample(self.sampleBaler, 0, 0, nil);
            end;
	
        else
            if self.isBaleUnloading and self.isServer then
                local deltaTime = dt / self.baleUnloadingTime;
                self:moveBales(deltaTime);
            end;
        end;
	
        if self.isClient then
            if not self:getIsTurnedOn() then
                Utils.stopSample(self.sampleBalerKnotCleaning);
                Utils.stopSample(self.sampleBaler);
            end;

            if self:getIsTurnedOn() and self.fillLevel > (self:getCapacity() * 0.88) and self.fillLevel < self:getCapacity() then
                if self:getIsActiveForSound() then
                    Utils.playSample(self.sampleBalerAlarm, 0, 0, nil);
                end;
            else
                Utils.stopSample(self.sampleBalerAlarm);
            end;
        end;
	
        if self.balerUnloadingState == Baler.UNLOADING_OPENING then
            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 = Baler.UNLOADING_OPEN;

                    if self.isClient then
                        Utils.stopSample(self.sampleBalerEject);
                        Utils.stopSample(self.sampleBalerDoor);
                    end;
                end;
            end;
        elseif self.balerUnloadingState == Baler.UNLOADING_CLOSING then
            if not self:getIsAnimationPlaying(self.baleCloseAnimationName) then
                self.balerUnloadingState = Baler.UNLOADING_CLOSED;
                if self.isClient then
                    Utils.stopSample(self.sampleBalerDoor);
                end;
            end;
        end;
    end;
end;
	
function Baler:draw()
end;

function Baler:getIsFoldAllowed(superFunc)
    if (table.getn(self.bales) > 0 and self.fillLevel > self.baleFoldThreshold) or self:getIsTurnedOn() then
        return false;
    end;
	
    if superFunc ~= nil then
        return superFunc(self);
    end
	
    return true;
end
	
function Baler:onDeactivate()
    if self.balingAnimationName ~= "" then
        self:stopAnimation(self.balingAnimationName, true);
    end;
end;
	
function Baler:onDeactivateSounds()
    if self.isClient then
        Utils.stopSample(self.sampleBaler, true);
        Utils.stopSample(self.sampleBalerAlarm, true);
        Utils.stopSample(self.sampleBalerDoor, true);
        Utils.stopSample(self.sampleBalerEject, true);
        Utils.stopSample(self.sampleBalerKnotCleaning, true);
    end;
end;
	
function Baler:setFillLevel(fillLevel, fillType, force)
    if self.dummyBale.baleNode ~= nil and fillLevel > 0 and (self.dummyBale.currentBale == nil or self.dummyBale.currentBaleFillType ~= fillType) then
        if self.dummyBale.currentBale ~= nil then
            delete(self.dummyBale.currentBale);
            self.dummyBale.currentBale = nil;
        end;
		if self.currentFillType == Fillable.FILLTYPE_GRASS_WINDROW or self.currentFillType == Fillable.FILLTYPE_DRYGRASS_WINDROW then
			local t = self.baleTypes[self.currentBaleTypeId];
			local baleType = BaleUtil.getBale(fillType, t.width, t.height, t.length, t.diameter, t.isRoundBale);
			local baleRoot = Utils.loadSharedI3DFile("Baloti/BalotIarba.i3d", self.baseDirectory, false, false);
			local baleId = getChildAt(baleRoot, 0);
			setRigidBodyType(baleId, "NoRigidBody");
			link(self.dummyBale.baleNode, baleId);
			delete(baleRoot);
			self.dummyBale.currentBale = baleId;
			self.dummyBale.currentBaleFillType = fillType;
		else
			local t = self.baleTypes[self.currentBaleTypeId];
			local baleType = BaleUtil.getBale(fillType, t.width, t.height, t.length, t.diameter, t.isRoundBale);
			local baleRoot = Utils.loadSharedI3DFile("Baloti/BalotPaie.i3d", self.baseDirectory, false, false);
			local baleId = getChildAt(baleRoot, 0);
			setRigidBodyType(baleId, "NoRigidBody");
			link(self.dummyBale.baleNode, baleId);
			delete(baleRoot);
			self.dummyBale.currentBale = baleId;
			self.dummyBale.currentBaleFillType = fillType;
		end;
    end;
	
    if self.dummyBale.currentBale ~= nil then
        local percent = fillLevel / self:getCapacity();
        local y = 1;
        if getUserAttribute(self.dummyBale.currentBale, "isRoundbale") then
            y = percent;
        end;
        setScale(self.dummyBale.scaleNode, 1, y, percent);
    end;
end;
	
function Baler:onTurnedOn(noEventSend)
    if self.setFoldState ~= nil then
        self:setFoldState(-1);
    end;
    if self.balingAnimationName ~= "" then
        self:playAnimation(self.balingAnimationName, 1, self:getAnimationTime(self.balingAnimationName), true);
    end;
    if self.isClient then
        Utils.stopSample(self.sampleBalerStop);
    end;
end;
	
function Baler:onTurnedOff(noEventSend)
    if self.balingAnimationName ~= "" then
        self:stopAnimation(self.balingAnimationName, true);
    end;
    if self.isClient then
        if self:getIsActiveForSound() then
            Utils.playSample(self.sampleBalerStop, 1, 0, nil);
        end;
    end;
end;
	
function Baler:doCheckSpeedLimit(superFunc)
    local parent = true;
    if superFunc ~= nil then
        parent = superFunc(self);
    end
	
    return parent and self.isBalerSpeedLimitActive;
end;
	
function Baler:isUnloadingAllowed()
    if self.hasBaleWrapper == nil or not self.hasBaleWrapper then
        return not self.allowsBaleUnloading or (self.allowsBaleUnloading and not self:getIsTurnedOn() and not self.isBaleUnloading);
    end;
	
    return self:allowsGrabbingBale();
end;
	
function Baler:setIsUnloadingBale(isUnloadingBale, noEventSend)
    if self.baleUnloadAnimationName ~= nil then
        if isUnloadingBale then
            if self.balerUnloadingState ~= Baler.UNLOADING_OPENING then
                BalerSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend)
                self.balerUnloadingState = Baler.UNLOADING_OPENING;
                if self.isClient and self:getIsActiveForSound() then
                    Utils.playSample(self.sampleBalerEject, 1, 0, nil);
                    Utils.playSample(self.sampleBalerDoor, 1, 0, nil);
                end;
                self:playAnimation(self.baleUnloadAnimationName, self.baleUnloadAnimationSpeed, nil, true);
            end;
        else
            if self.balerUnloadingState ~= Baler.UNLOADING_CLOSING then
                BalerSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend)
                self.balerUnloadingState = Baler.UNLOADING_CLOSING;
                if self.isClient and self:getIsActiveForSound() then
                    Utils.playSample(self.sampleBalerDoor, 1, 0, nil);
                end;
                self:playAnimation(self.baleCloseAnimationName, self.baleCloseAnimationSpeed, nil, true);
            end;
        end;
    elseif self.allowsBaleUnloading then
        if isUnloadingBale then
            BalerSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend);
            self.isBaleUnloading = true;
        end;
    end;
end;
	
function Baler:getTimeFromLevel(level)
    if self.firstBaleMarker ~= nil then
        return level / self:getCapacity() * self.firstBaleMarker;
    end;
    return 0;
end;
	
function Baler:moveBales(dt)
    for i=table.getn(self.bales), 1, -1 do
        self:moveBale(i, dt);
    end;
end;
	
function Baler:moveBale(i, dt, noEventSend)
    local bale = self.bales[i];
    self:setBaleTime(i, bale.time + dt, noEventSend)
end;
	
function Baler:setBaleTime(i, baleTime, noEventSend)
    if self.baleAnimCurve ~= nil then
        local bale = self.bales[i];
        bale.time = baleTime;
        if self.isServer then
            local v = self.baleAnimCurve:get(bale.time);
            setTranslation(bale.baleJointNode, v[1], v[2], v[3]);
            setRotation(bale.baleJointNode, v[4], v[5], v[6]);
            setJointFrame(bale.baleJointIndex, 0, bale.baleJointNode);
        end;
        if bale.time >= 1 then
            self:dropBale(i);
        end;
        if table.getn(self.bales) == 0 then
            self.isBaleUnloading = false;
        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;
	
function Baler:allowFillType(fillType)
    return self.fillTypes[fillType] == true;
end;
	
function Baler:allowPickingUp(superFunc)
    if self.baleUnloadAnimationName ~= nil then
        if table.getn(self.bales) > 0 or self.balerUnloadingState ~= Baler.UNLOADING_CLOSED then
            return false;
        end;
    end;
	
    if superFunc ~= nil then
        return superFunc(self);
    end
    return true;
end;
	
function Baler:createBale(baleFillType, fillLevel)
	
    if self.knotingAnimation ~= nil then
        self:playAnimation(self.knotingAnimation, self.knotingAnimationSpeed, nil, true);
    end;
	
    if self.dummyBale.currentBale ~= nil then
        delete(self.dummyBale.currentBale);
        self.dummyBale.currentBale = nil;
    end;
	
    local t = self.baleTypes[self.currentBaleTypeId];
    local baleType = BaleUtil.getBale(baleFillType, t.width, t.height, t.length, t.diameter, t.isRoundBale);
	
	local bale = {};
	if self.currentFillType == Fillable.FILLTYPE_GRASS_WINDROW or self.currentFillType == Fillable.FILLTYPE_DRYGRASS_WINDROW then
		bale.filename = Utils.getFilename("Baloti/BalotIarba.i3d", self.baseDirectory);
		bale.time = 0;
		bale.fillType = baleFillType;
		bale.fillLevel = fillLevel;
	else
		bale.filename = Utils.getFilename("Baloti/BalotPaie.i3d", self.baseDirectory);
		bale.time = 0;
		bale.fillType = baleFillType;
		bale.fillLevel = fillLevel;
	end;
	
	if self.currentFillType == Fillable.FILLTYPE_GRASS_WINDROW or self.currentFillType == Fillable.FILLTYPE_DRYGRASS_WINDROW then
		if self.baleUnloadAnimationName ~= nil then
			local baleRoot = Utils.loadSharedI3DFile("Baloti/BalotIarba.i3d", self.baseDirectory, false, false);
			local baleId = getChildAt(baleRoot, 0);
			link(self.baleAnimRoot, baleId);
			delete(baleRoot);
			bale.id = baleId;
		end;
	else
		if self.baleUnloadAnimationName ~= nil then
			local baleRoot = Utils.loadSharedI3DFile("Baloti/BalotPaie.i3d", self.baseDirectory, false, false);
			local baleId = getChildAt(baleRoot, 0);
			link(self.baleAnimRoot, baleId);
			delete(baleRoot);
			bale.id = baleId;
		end;
	end;
	
    if self.isServer and self.baleUnloadAnimationName == nil then
        local x,y,z = getWorldTranslation(self.baleAnimRoot);
        local rx,ry,rz = getWorldRotation(self.baleAnimRoot);
	
        local baleObject = Bale:new(self.isServer, self.isClient);
        baleObject:load(bale.filename, x,y,z,rx,ry,rz, bale.fillLevel);
        baleObject:register();
	
        local baleJointNode = createTransformGroup("BaleJointTG");
        link(self.baleAnimRoot, baleJointNode);
        setTranslation(baleJointNode, 0,0,0);
        setRotation(baleJointNode, 0,0,0);
	
        local constr = JointConstructor:new();
        constr:setActors(self.baleAnimRootComponent, baleObject.nodeId);
        constr:setJointTransforms(baleJointNode, baleObject.nodeId);
        for i=1, 3 do
            constr:setRotationLimit(i-1, 0, 0);
            constr:setTranslationLimit(i-1, true, 0, 0);
        end;
        constr:setEnableCollision(false);
        local baleJointIndex = constr:finalize();
	
        g_currentMission:removeItemToSave(baleObject);
	
        bale.baleJointNode = baleJointNode;
        bale.baleJointIndex = baleJointIndex;
        bale.baleObject = baleObject;
    end;
	
    table.insert(self.bales, bale);
end;
	
function Baler:dropBale(baleIndex)
    local bale = self.bales[baleIndex];
	
    if self.isServer then
        local baleObject;
	
        if bale.baleJointIndex ~= nil then
            baleObject = bale.baleObject;
            removeJoint(bale.baleJointIndex);
            delete(bale.baleJointNode);
            g_currentMission:addItemToSave(bale.baleObject);
        else
            baleObject = Bale:new(self.isServer, self.isClient);
            local x,y,z = getWorldTranslation(bale.id);
            local rx,ry,rz = getWorldRotation(bale.id);
            baleObject:load(bale.filename, x,y,z,rx,ry,rz, bale.fillLevel);
            baleObject:register();
            delete(bale.id)
        end;
	
        if (not self.hasBaleWrapper or self.moveBaleToWrapper == nil) and baleObject.nodeId ~= nil then
            local x,y,z = getWorldTranslation(baleObject.nodeId)
            local vx,vy,vz = getVelocityAtWorldPos(self.baleAnimRootComponent, x,y,z);
            setLinearVelocity(baleObject.nodeId, vx,vy,vz);
        elseif self.moveBaleToWrapper ~= nil then
            self:moveBaleToWrapper(baleObject);
        end;
    end;
	
    Utils.releaseSharedI3DFile(bale.filename, nil, true);
    table.remove(self.bales, baleIndex);
	
    g_currentMission.missionStats:updateStats("baleCount", 1);
end;
	
function Baler:getIsTurnedOnAllowed(superFunc, isTurnedOn)
    if isTurnedOn and self.isBaleUnloading then
        return false;
    end;
	
    if superFunc ~= nil then
        return superFunc(self, isTurnedOn);
    end
	
    return true;
end;
	
function Baler:loadWorkAreaFromXML(superFunc, workArea, xmlFile, key)
    local retValue = true;
    if superFunc ~= nil then
        retValue = superFunc(self, workArea, xmlFile, key)
    end
	
    if workArea.type == WorkArea.AREATYPE_DEFAULT then
        workArea.type = WorkArea.AREATYPE_BALER;
    end;
	
    return retValue;
end;
	
function Baler.getDefaultSpeedLimit()
    return 25;
end;