--
-- BaleGrab
-- This is the specialization for automatic bale loaders
--
-- @author  Manuel Leithner
-- @date  13/05/12
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

BaleGrab = {};
BaleGrab.GRAB_BALE = 1;
BaleGrab.GRABBED_BALE = 2;
BaleGrab.CHANGE_GRAB_BALE = 3;
BaleGrab.CHANGE_GRABBED_BALE = 4;
BaleGrab.CHANGE_MOVE_TO_POSITION = 5;
BaleGrab.CHANGE_MOVE_TO_WORK = 6;
BaleGrab.CHANGE_MOVE_TO_TRANSPORT = 7;
BaleGrab.CHANGE_EMPTY_NONE = 8;
BaleGrab.CHANGE_EMPTY_START = 9;
BaleGrab.CHANGE_EMPTY_IN_PROCESS = 10;
BaleGrab.CHANGE_EMPTY_CANCEL = 11;
BaleGrab.EMPTY_NONE = 12;
BaleGrab.EMPTY_OPENING = 13;
BaleGrab.EMPTY_IN_PROCESS = 14;
BaleGrab.EMPTY_CLOSING = 15;
BaleGrab.CHANGE_BUTTON_EMPTY = 16;
BaleGrab.CHANGE_BUTTON_EMPTY_ABORT = 17;
BaleGrab.CHANGE_BUTTON_WORK_TRANSPORT = 18;

BaleGrab.STATE_MAX = 30;

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

function BaleGrab:load(xmlFile)

    self.doStateChange = BaleGrab.doStateChange;
	self.lockBales = BaleGrab.lockBales;

    self.balesToLoad = {};
	self.loadedBalesToLoad = {};
    self.balesToMount = {};

	self.loadedBales = {};
	self.loadedBalesCount = 0;
	self.onBaleTrigger = BaleGrab.onBaleTrigger;
	self.baleTrigger = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleTrigger#index"));
	addTrigger(self.baleTrigger, "onBaleTrigger", self);
	self.baleLock = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleTrigger#baleLock"));
	self.lockState = false;

	self.baleFallingTime = 0;

    self.isInWorkPosition = false;
    self.grabberIsMoving = false;
    self.allowGrabbing = false;

    self.emptyState = BaleGrab.EMPTY_NONE;

    self.fillLevel = 0;
    self.fillLevelMax = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.capacity"), 0);

    self.baleGrabber = {};
    self.baleGrabber.grabNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.baleGrabber#grabNode"));

	self.speedLimit = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleGrabber#speedLimit"), 12);
	self.printSpeedWarning = 0;

	self.freeBaleSliders = {};
    local i=0;
    while true do
        local key = string.format("vehicle.baleSlider.slider(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
        local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#node"));
        if node ~= nil then
            local entry = {};
            entry.node = node;
			entry.animId = BaleGrab.STATE_MAX + i;
			entry.animation = getXMLString(xmlFile, key.."#animation");
            table.insert(self.freeBaleSliders, entry);
        end;
        i = i + 1;
    end;
	self.busyBaleSliders = {};
	self.baleSliderNumber = 0;
	self.baleSliderCapacity = table.getn(self.freeBaleSliders);

    self.baleGrabSound = {};
    local baleGrabSoundFile = getXMLString(xmlFile, "vehicle.baleGrabSound#file");
    if baleGrabSoundFile ~= nil then
		baleGrabSoundFile = Utils.getFilename(baleGrabSoundFile, self.baseDirectory);
        self.baleGrabSound.sample = createSample("baleDropSound");
        loadSample(self.baleGrabSound.sample,  baleGrabSoundFile, false);
        local pitch = getXMLFloat(xmlFile, "vehicle.baleDropSound#pitchOffset");
        if pitch ~= nil then
            setSamplePitch(self.baleGrabSound.sample, pitch);
        end;
        self.baleGrabSound.volume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleGrabSound#volume"), 1);
    end;

	local sliderSound = getXMLString(xmlFile, "vehicle.baleSlider#sound");
	if sliderSound ~= nil and sliderSound ~= "" then
		sliderSound = Utils.getFilename(sliderSound, self.baseDirectory);
		self.baleSliderSound = createSample("baleSliderSound");
		self.baleSliderSoundEnabled = false;
		loadSample(self.baleSliderSound, sliderSound, false);
		self.baleSliderSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleSlider#pitchOffset"), 1);
		self.baleSliderSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.baleSlider#volume"), 1);
		setSampleVolume(self.baleSliderSound, self.baleSliderSoundVolume);
	end;

    self.baleGrabParticleSystems = {};
    local psName = "vehicle.baleGrabParticleSystem";
    Utils.loadParticleSystem(xmlFile, self.baleGrabParticleSystems, psName, self.components, false, nil, self.baseDirectory);
    self.baleGrabParticleSystemDisableTime = 0;
    self.baleGrabParticleSystemDisableDuration = Utils.getNoNil(getXMLFloat(xmlFile, psName.."#disableDuration"), 0.6)*1000;

    self.workTransportButton = InputBinding.IMPLEMENT_EXTRA;
    self.emptyAbortButton = InputBinding.IMPLEMENT_EXTRA2;
    self.emptyButton = InputBinding.IMPLEMENT_EXTRA3;

    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 filename = getXMLString(xmlFile, key.."#filename");
        if filename ~= nil then
			filename = Utils.getFilename(filename, self.baseDirectory);
            table.insert(self.baleTypes, filename);
        end;
        i = i + 1;
    end;
    if table.getn(self.baleTypes) == 0 then
        table.insert(self.baleTypes, "data/maps/models/objects/strawbale/strawbaleBaler.i3d");
    end;

	self.attachedJoint = nil;
	self.doJointSearch = false;

end;

function BaleGrab:delete()

	self:lockBales(false, true);

	for k, slider in ipairs(self.busyBaleSliders) do
		local bale = networkGetObject(slider.currentBale);
		if bale ~= nil then
			if bale.nodeId ~= 0 then
				bale:unmount();
			end;
		end;
	end;

	if self.baleSliderSound ~= nil then
        delete(self.baleSliderSound);
		self.baleSliderSound = nil;
    end;

	if self.baleTrigger ~= nil then
        removeTrigger(self.baleTrigger);
		self.baleTrigger = nil;
    end;

    if (self.baleGrabParticleSystems) then
        Utils.deleteParticleSystem(self.baleGrabParticleSystems);
		self.baleGrabParticleSystems = nil;
    end;

    if self.baleGrabSound.sample ~= nil then
        delete(self.baleGrabSound.sample);
        self.baleGrabSound.sample = nil;
    end;
end;

function BaleGrab:readStream(streamId, connection)

    self.isInWorkPosition = streamReadBool(streamId);

    if self.isInWorkPosition then
        BaleGrab.moveToWorkPosition(self);
    end;

    local emptyState = streamReadUIntN(streamId, 4);

    -- read bale at bale grabber
    if streamReadBool(streamId) then
        local baleServerId = streamReadInt32(streamId);
        self.baleGrabber.currentBale = baleServerId;
        self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=self.baleGrabber.grabNode, trans={0,0,0}, rot={0,0,0} };
    end;

    -- read bales at start bale places
    local busySliderCount = streamReadUIntN(streamId, 3);
    for i=1, busySliderCount do
        local baleServerId = streamReadInt32(streamId);
		local animTime = streamReadFloat32(streamId);
		local sliderId = streamReadUIntN(streamId,  3);
		local usedSlider = nil;
		for k, slider in pairs(self.freeBaleSliders) do
			if k == sliderId then
				table.remove(self.freeBaleSliders, k);
				table.insert(self.busyBaleSliders, slider);
				usedSlider = slider;
				break;
			end;
		end;
		self:playAnimation(usedSlider.animation, 1, nil, true);
		self:setAnimationStopTime(usedSlider.animation, animTime);

        self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=usedSlider.currentBale, trans={0,0,0}, rot={0,0,0} };
    end;

	local baleNum = streamReadInt8(streamId);
	if baleNum > 0 then
		for i=1, baleNum do
			local baleServerId = streamReadInt32(streamId);
			local x = streamReadFloat32(streamId);
			local y = streamReadFloat32(streamId);
			local z = streamReadFloat32(streamId);
			local xRot = streamReadFloat32(streamId);
			local yRot = streamReadFloat32(streamId);
			local zRot = streamReadFloat32(streamId);
			self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=self.baleLock, trans={x,y,z}, rot={xRot,yRot,zRot}, addToList=true};
		end;
		self:lockBales(true, true);
	end;

    if emptyState >= BaleGrab.EMPTY_NONE then
        self:doStateChange(BaleGrab.CHANGE_EMPTY_NONE);
        AnimatedVehicle.updateAnimations(self, 99999999);
        if emptyState >= BaleGrab.EMPTY_OPENING then
            self:doStateChange(BaleGrab.CHANGE_EMPTY_START);
            AnimatedVehicle.updateAnimations(self, 99999999);
            if emptyState >= BaleGrab.EMPTY_IN_PROCESS then
                self:doStateChange(BaleGrab.CHANGE_EMPTY_IN_PROCESS);
                AnimatedVehicle.updateAnimations(self, 99999999);
                if emptyState == BaleGrab.EMPTY_CLOSING then
                    self:doStateChange(BaleGrab.CHANGE_EMPTY_CANCEL);
                    AnimatedVehicle.updateAnimations(self, 99999999);
                end;
            end;
        end;
    end;

	AnimatedVehicle.updateAnimations(self, 99999999);
    self.emptyState = emptyState;
end;

function BaleGrab:writeStream(streamId, connection)

    streamWriteBool(streamId, self.isInWorkPosition);
    streamWriteUIntN(streamId, self.emptyState, 4);

    -- write bale at bale grabber
    if streamWriteBool(streamId, self.baleGrabber.currentBale ~= nil) then
        streamWriteInt32(streamId, self.baleGrabber.currentBale);
    end;

	streamWriteUIntN(streamId, table.getn(self.busyBaleSliders), 3);

    for sliderId, slider in ipairs(self.busyBaleSliders) do
		streamWriteInt32(streamId, slider.currentBale);
		local animTime = self:getAnimationTime(slider.animation);
		streamWriteFloat32(streamId, animTime);
		streamWriteUIntN(streamId, sliderId, 3);
    end;

	-- counting bales
	baleNum = 0;
	for _, _ in pairs(self.loadedBales) do
		baleNum = baleNum + 1;
	end;
	if not self.lockState then
		baleNum = 0;
	end;
	streamWriteInt8(streamId, baleNum);

	if baleNum > 0 then
		for _, item in pairs(self.loadedBales) do
			local bale = item.object;
			if bale ~= nil then
				local serverId = networkGetObjectId(bale);
				streamWriteInt32(streamId, serverId);
				local x,y,z = getTranslation(bale.nodeId);
				local xRot, yRot, zRot = getRotation(bale.nodeId);
				streamWriteFloat32(streamId, x);
				streamWriteFloat32(streamId, y);
				streamWriteFloat32(streamId, z);
				streamWriteFloat32(streamId, xRot);
				streamWriteFloat32(streamId, yRot);
				streamWriteFloat32(streamId, zRot);
			end;
		end;
	end;
end;

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

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

function BaleGrab:update(dt)

    if self.firstTimeRun then
        for k,v in pairs(self.balesToLoad) do
            local baleObject = Bale:new(self.isServer, self.isClient);
            local x,y,z = unpack(v.translation);
            local rx,ry,rz = unpack(v.rotation);
            baleObject:load(v.filename, x,y,z,rx,ry,rz);
            baleObject:mount(self, v.parentNode, x,y,z, rx,ry,rz);
            baleObject:register();
			v.bale.currentBale = networkGetObjectId(baleObject);
            self.balesToLoad[k] = nil;
        end;

		for k,v in pairs(self.loadedBalesToLoad) do
            local baleObject = Bale:new(self.isServer, self.isClient);
            local x,y,z = unpack(v.translation);
            local rx,ry,rz = unpack(v.rotation);
            baleObject:load(v.filename, x,y,z,rx,ry,rz);
            baleObject:mount(self, v.parentNode, x,y,z, rx,ry,rz);
            baleObject:register();
			baleObject:unmount();

			self.loadedBales[baleObject.nodeId] = {object=baleObject, mass=0.02};
            self.loadedBalesToLoad[k] = nil;
			self.fillLevel = self.fillLevel + 1;
        end;


        for k, baleToMount in pairs(self.balesToMount) do
            local bale = networkGetObject(baleToMount.serverId);
            if bale ~= nil then
                local x,y,z = unpack(baleToMount.trans);
                local rx,ry,rz = unpack(baleToMount.rot);
                bale:mount(self, baleToMount.linkNode, x,y,z, rx,ry,rz);
                self.balesToMount[k] = nil;
				if baleToMount.addToList then
					self.loadedBales[bale.nodeId] = {object=bale, mass=0.02};
				end;
            end;
        end;
    end;

    if self:getIsActive() then
	
		if self.doJointSearch then
			local implementIndex = self.attacherVehicle:getImplementIndexByObject(self);
			local implement = self.attacherVehicle.attachedImplements[implementIndex];
			local jointDescIndex = implement.jointDescIndex;
			self.attachedJoint = self.attacherVehicle.attacherJoints[jointDescIndex];
			self.doJointSearch = false;
		end;

		if self.isServer then
			if self.attachedJoint ~= nil then
				if self:getIsAnimationPlaying("baleGrabberTransportToWork") then
					setJointFrame(self.attachedJoint.jointIndex, 1, self.attacherJoint.node);
				end;
			end;
		end;


        if self:getIsActiveForInput() and self.isClient then
            if InputBinding.hasEvent(self.workTransportButton) then
                g_client:getServerConnection():sendEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_BUTTON_WORK_TRANSPORT));
			elseif InputBinding.hasEvent(self.emptyButton) then
				g_client:getServerConnection():sendEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_BUTTON_EMPTY));
			elseif InputBinding.hasEvent(self.emptyAbortButton) then
                g_client:getServerConnection():sendEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_BUTTON_EMPTY_ABORT));
            end;
        end;

        if self.isClient then
            if self.baleGrabParticleSystemDisableTime ~= 0 and self.baleGrabParticleSystemDisableTime < self.time then
                Utils.setEmittingState(self.baleGrabParticleSystems, false);
                self.baleGrabParticleSystemDisableTime = 0;
            end;
        end;

        -- check if grabber is still moving
        if self.grabberIsMoving then
            if not self:getIsAnimationPlaying("baleGrabberTransportToWork") then
                self.grabberIsMoving = false;
            end;
        end;

        self.allowGrabbing = false;
        if self.isInWorkPosition and not self.grabberIsMoving and self.grabberMoveState == nil and self.baleSliderNumber < self.baleSliderCapacity and self.emptyState == BaleGrab.EMPTY_NONE and self.fillLevel < self.fillLevelMax - self.baleSliderNumber then
            self.allowGrabbing = true;
        end;

		if self.isServer then
			local isLocked = (((self.lastSpeed*3600 > self.speedLimit and self.grabberMoveState == nil) or not self.isInWorkPosition) and self.emptyState ~= BaleGrab.EMPTY_IN_PROCESS) and self.time > self.baleFallingTime;
			self:lockBales(isLocked, false);
		end;

		if self.isClient and self.allowGrabbing then
			if not self.baleSliderSoundEnabled and self:getIsActiveForSound() then
				playSample(self.baleSliderSound, 0, self.baleSliderSoundVolume, 0);
				setSamplePitch(self.baleSliderSound, self.baleSliderSoundPitchOffset);
				self.baleSliderSoundEnabled = true;
			end;
		end;

		local nearestBale = nil;
		local showMessage = false;

		if self.allowGrabbing then
			if self.baleGrabber.grabNode ~= nil and self.baleGrabber.currentBale == nil then
				nearestBale, showMessage = BaleGrab.getBaleInRange(self, self.baleGrabber.grabNode);

				if self.isInWorkPosition and showMessage and self.lastSpeed*3600 > self.speedLimit then
					self.printSpeedWarning = self.time + 500;
					self.allowGrabbing = false;
				end;
			end;
		end;


        if self.isServer then
            if self.allowGrabbing then
                if self.baleGrabber.grabNode ~= nil and self.baleGrabber.currentBale == nil then
                    -- find nearest bale
                    --local nearestBale, showMessage = BaleGrab.getBaleInRange(self, self.baleGrabber.grabNode);
                    if nearestBale ~= nil then
                        g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_GRAB_BALE, networkGetObjectId(nearestBale)), true, nil, self);
					end;
                end;
            end;
------------------------------------------------------------------------			
			if not self.isInWorkPosition then
				for k, slider in pairs(self.busyBaleSliders) do
					if not self:getIsAnimationPlaying(slider.animation) then
						if self:getAnimationTime(slider.animation) == 1 then
							g_server:broadcastEvent(BaleGrabStateEvent:new(self, slider.animId), true, nil, self);
							break;
						else
							self:playAnimation(slider.animation, 1, self:getAnimationTime(slider.animation), true);
						end;
					end;
				end;
			end;
----------------------------------------------------------------------			
			
			-- for k, slider in pairs(self.busyBaleSliders) do
				-- if not self:getIsAnimationPlaying(slider.animation) then
					-- if self:getAnimationTime(slider.animation) == 1 then
						-- g_server:broadcastEvent(BaleGrabStateEvent:new(self, slider.animId), true, nil, self);
						-- break;
					-- else
						-- self:playAnimation(slider.animation, 1, self:getAnimationTime(slider.animation), true);
					-- end;
				-- end;
			-- end;
            if self.grabberMoveState ~= nil then
                if self.grabberMoveState == BaleGrab.GRAB_BALE then
                    if not self:getIsAnimationPlaying("baleGrabberMoveToSlider") then
                        --BaleGrab.CHANGE_GRABBED_BALE
						local usedSlider = nil;
						for k, slider in pairs(self.freeBaleSliders) do
							usedSlider = slider;
							break;
						end;
                        g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_GRABBED_BALE, nil, usedSlider.animId), true, nil, self);
                    end;
				 elseif self.grabberMoveState == BaleGrab.GRABBED_BALE then
                    if not self:getIsAnimationPlaying(self.freeBaleSliders[1].animation) then
                        --BaleGrab.CHANGE_MOVE_TO_POSITION
                        g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_MOVE_TO_POSITION), true, nil, self);
                    end;
				end;
            end;
			if self.emptyState ~= BaleGrab.EMPTY_NONE then
                if self.emptyState == BaleGrab.EMPTY_OPENING then
                    if not self:getIsAnimationPlaying("unloading") then
                        g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_EMPTY_IN_PROCESS), true, nil, self);
                    end;
                elseif self.emptyState == BaleGrab.EMPTY_CLOSING then
                    if not self:getIsAnimationPlaying("unloading") then
                        g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_EMPTY_NONE), true, nil, self);
                    end;
				elseif self.emptyState == BaleGrab.EMPTY_IN_PROCESS then
					self.emptyBaleForce = 0.0002;
					local dirX, dirY, dirZ = localDirectionToWorld(self.baleLock, 0,0,-1);
					dirX = dirX * self.emptyBaleForce;
					dirY = dirY * self.emptyBaleForce;
					dirZ = dirZ * self.emptyBaleForce;
					for id, bale in pairs(self.loadedBales) do
						if g_currentMission:getNodeObject(id) ~= nil then
							local vx, vy, vz = getLinearVelocity(id);
							local dot = vx*dirX + vy*dirY + vz*dirZ;
							local v = dot/self.emptyBaleForce;

							if v < 1 then
								local scale = dt*bale.mass;
								addForce(id, dirX*dt, dirY*dt, dirZ*dt, 0, 0, 0, true);
							end;
						else
							self.loadedBales[id] = nil;
							self.fillLevel = math.max(self.fillLevel - 1, 0);
						end;
					end;
				end;
			end;
        end;
	else
		if self.isServer and self.fillLevel > 0 then
			local worldX,worldY,worldZ = localDirectionToWorld(self.components[2].node, 0, -1.5*dt/1000, 0);
			addForce(self.components[2].node, worldX, worldY, worldZ, 0, 0, 0, true);
		end;
    end;
end;

function BaleGrab:draw()
    if self:getIsActiveForInput(true) then
        if self.emptyState == BaleGrab.EMPTY_NONE then
            if self.grabberMoveState == nil then
                if self.isInWorkPosition then
                    g_currentMission:addHelpButtonText(g_i18n:getText("OpenGrab"), self.workTransportButton);
                else
                    g_currentMission:addHelpButtonText(g_i18n:getText("CloseGrab"), self.workTransportButton);
                end;
            end;

            if BaleGrab.getAllowsStartUnloading(self) then
                g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_UNLOAD"), self.emptyButton);
            end;
        else
            g_currentMission:addHelpButtonText(g_i18n:getText("BALELOADER_CLOSE"), self.emptyAbortButton);
        end;
    end

	if self.printSpeedWarning > self.time then
		g_currentMission:addWarning(g_i18n:getText("Dont_drive_to_fast") .. "\n" .. string.format(g_i18n:getText("Cruise_control_levelN"), tostring(1), ""), 0.07+0.022, 0.019+0.029);
	end;
end

function BaleGrab.getBaleInRange(self, refNode)
	local px, py, pz = getWorldTranslation(refNode);
	local showMessage = false;
	local showMessageDistance = 5;
	local nearestDistance = 1;
	local nearestBale = nil;

	for index, item in pairs(g_currentMission.itemsToSave) do
		if item.item:isa(Bale) then
			-- TODO make with enum of bale types instead of filenames
			for _, filename in pairs(self.baleTypes) do
				if item.item.i3dFilename == filename then
					local vx, vy, vz = getWorldTranslation(item.item.nodeId);
					local distance = Utils.vector3Length(px-vx, py-vy, pz-vz);
					if distance < nearestDistance then
						nearestDistance = distance;
						nearestBale = item.item;
					end;
					if distance < showMessageDistance then
						if self.loadedBales[item.item.nodeId] == nil then
							showMessage = true;
						end;
					end;
					break;
				end;
			end;
		end;
	end;

	return nearestBale, showMessage;
end;

function BaleGrab:onDetach()
	self:lockBales(false, true);
    if self.deactivateOnDetach then
        BaleGrab.onDeactivate(self);
    else
        BaleGrab.onDeactivateSounds(self)
    end;

	self.attachedJoint = nil;
end;

function BaleGrab:onAttach(attacherVehicle)
	self.doJointSearch = true;
end;

function BaleGrab:onDeactivate()
	if self.baleGrabParticleSystems ~= nil then
		Utils.setEmittingState(self.baleGrabParticleSystems, false);
	end;
    BaleGrab.onDeactivateSounds(self)
end;

function BaleGrab:onDeactivateSounds()
	if self.baleSliderSound ~= nil and self.baleSliderSoundEnabled then
        stopSample(self.baleSliderSound);
        self.baleSliderSoundEnabled = false;
    end;
end;

function BaleGrab:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)

	self.fillLevel = 0;
    local i=0;
    while true do
        local baleKey = key..string.format(".bale(%d)", i);
        if not hasXMLProperty(xmlFile, baleKey) then
            break;
        end;
        local filename = getXMLString(xmlFile, baleKey.."#filename");
        if filename ~= nil then
            filename = Utils.convertFromNetworkFilename(filename);
			local translation = {0,0,0};
			local rotation = {0,0,0};
			local usedSlider = nil;
			for k, slider in pairs(self.freeBaleSliders) do
				table.remove(self.freeBaleSliders, k);
				table.insert(self.busyBaleSliders, slider);
				usedSlider = slider;
				break;
			end;
			local stopTime = getXMLFloat(xmlFile, baleKey.."#time");
			if stopTime ~= nil then
				self:playAnimation(usedSlider.animation, 1, nil, true);
				self:setAnimationStopTime(usedSlider.animation, stopTime);
			end;
			self.baleSliderNumber = self.baleSliderNumber + 1;

			table.insert(self.balesToLoad, {parentNode=usedSlider.node, filename=filename, bale=usedSlider, translation=translation, rotation=rotation});
        end;
        i = i +1;
    end;

	i=0;
    while true do
        local baleKey = key..string.format(".loadedBale(%d)", i);
        if not hasXMLProperty(xmlFile, baleKey) then
            break;
        end;
        local filename = getXMLString(xmlFile, baleKey.."#filename");
        if filename ~= nil then
            filename = Utils.convertFromNetworkFilename(filename);
			local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, baleKey.."#position"));
            local xRot,yRot,zRot = Utils.getVectorFromString(getXMLString(xmlFile, baleKey.."#rotation"));

			table.insert(self.loadedBalesToLoad, {parentNode=self.baleLock, filename=filename, translation={x,y,z}, rotation={xRot, yRot, zRot}});
        end;
        i = i +1;
    end;

	local inWorkPosition = Utils.getNoNil(getXMLBool(xmlFile, key .. "#isInWorkPosition"), false);

	AnimatedVehicle.updateAnimations(self, 99999999);

	if inWorkPosition then
		self:doStateChange(BaleGrab.CHANGE_MOVE_TO_WORK);
	else
		self:doStateChange(BaleGrab.CHANGE_MOVE_TO_TRANSPORT);
	end;

    return BaseMission.VEHICLE_LOAD_OK;
end

function BaleGrab:getSaveAttributesAndNodes(nodeIdent)

	local attributes = 'isInWorkPosition="'.. tostring(self.isInWorkPosition) ..'"';

    local nodes = "";
	local baleNum = 0;

    for i, slider in ipairs(self.busyBaleSliders) do
        local bale = networkGetObject(slider.currentBale);
        if bale ~= nil then
            if baleNum > 0 then
                nodes = nodes.."\n";
            end;
			local animTime = self:getAnimationTime(slider.animation);
            nodes = nodes..nodeIdent..'<bale filename="'..Utils.encodeToHTML(Utils.convertToNetworkFilename(bale.i3dFilename))..'" time="'..animTime..'" />';
            baleNum = baleNum+1;
        end;
    end;

	if self.lockState then
		baleNum = 0;
		for _, item in pairs(self.loadedBales) do
			local bale = item.object;
			if bale ~= nil then
				local nodeId = bale.nodeId;
				if baleNum > 0 then
					nodes = nodes.."\n";
				end;

				local x,y,z = getTranslation(nodeId);
                local rx,ry,rz = getRotation(nodeId);
				nodes = nodes..nodeIdent..'<loadedBale filename="'..Utils.encodeToHTML(Utils.convertToNetworkFilename(bale.i3dFilename))..'" position="'..x..' '..y..' '..z..'" rotation="'..rx..' '..ry..' '..rz..'" />';
				baleNum = baleNum+1;
			end;
		end;
	end;


    return attributes,nodes;
end

function BaleGrab:doStateChange(id, nearestBaleServerId, sliderId)

    if id == BaleGrab.CHANGE_EMPTY_START then
        self:playAnimation("unloading", 1, Utils.clamp(self:getAnimationTime("unloading"), 0, 1), true);
        self.emptyState = BaleGrab.EMPTY_OPENING;
    elseif id == BaleGrab.CHANGE_EMPTY_CANCEL then
        self:playAnimation("unloading", -1, Utils.clamp(self:getAnimationTime("unloading"), 0, 1), true);
        self.emptyState = BaleGrab.EMPTY_CLOSING;
	elseif id == BaleGrab.CHANGE_EMPTY_IN_PROCESS then
		self.emptyState = BaleGrab.EMPTY_IN_PROCESS;
	elseif id == BaleGrab.CHANGE_EMPTY_NONE then
		self.emptyState = BaleGrab.EMPTY_NONE;
    elseif id == BaleGrab.CHANGE_MOVE_TO_TRANSPORT then
        if self.isInWorkPosition then
            self.grabberIsMoving = true;
            self.isInWorkPosition = false;
            -- move to transport position
            BaleGrab.moveToTransportPosition(self);
			if self.baleSliderSoundEnabled then
				stopSample(self.baleSliderSound);
				self.baleSliderSoundEnabled = false;
			end;
        end;
    elseif id == BaleGrab.CHANGE_MOVE_TO_WORK then
        if not self.isInWorkPosition then
            self.grabberIsMoving = true;
            self.isInWorkPosition = true;
            -- move to work position
            BaleGrab.moveToWorkPosition(self);
        end;
    elseif id == BaleGrab.CHANGE_GRAB_BALE then
		-- ballen gesammelt und nach oben geschoben
        local bale = networkGetObject(nearestBaleServerId);
        self.baleGrabber.currentBale = nearestBaleServerId;
		self.baleSliderNumber = self.baleSliderNumber + 1;
        if bale ~= nil then
            bale:mount(self, self.baleGrabber.grabNode, 0,0,0, 0,0,0);
            self.balesToMount[nearestBaleServerId] = nil;
        else
            self.balesToMount[nearestBaleServerId] = {serverId=nearestBaleServerId, linkNode=self.baleGrabber.grabNode, trans={0,0,0}, rot={0,0,0} };
        end;
        self.grabberMoveState = BaleGrab.GRAB_BALE;
        self:playAnimation("baleGrabberMoveToSlider", 1, nil, true);

        if self.isClient then
            if self.baleGrabSound.sample ~= nil and self:getIsActiveForSound() then
                playSample(self.baleGrabSound.sample, 1, self.baleGrabSound.volume, 0);
            end;
            Utils.setEmittingState(self.baleGrabParticleSystems, true);
            self.baleGrabParticleSystemDisableTime = self.time + self.baleGrabParticleSystemDisableDuration;
        end;
	elseif id == BaleGrab.CHANGE_GRABBED_BALE then
		self:playAnimation("baleGrabberMoveToSlider", -9999999, nil, true);
		local usedSlider = nil;

		for k, slider in pairs(self.freeBaleSliders) do
			if slider.animId == sliderId then
				table.remove(self.freeBaleSliders, k);
				table.insert(self.busyBaleSliders, slider);
				usedSlider = slider;
				break;
			end;
		end;

		usedSlider.currentBale = self.baleGrabber.currentBale;
		self.baleGrabber.currentBale = nil;

		local attachNode = usedSlider.node;
		local bale = networkGetObject(usedSlider.currentBale);
		if bale ~= nil then
			bale:mount(self, attachNode, 0,0,0, 0,0,0);
			self.balesToMount[usedSlider.currentBale] = nil;
		else
			self.balesToMount[usedSlider.currentBale] = {serverId=usedSlider.currentBale, linkNode=attachNode, trans={0,0,0}, rot={0,0,0} };
		end;

		--self.grabberMoveState = BaleGrab.GRABBED_BALE;
		self.grabberMoveState = nil;
		-- self:playAnimation(usedSlider.animation, 1, nil, true);

    elseif id == BaleGrab.CHANGE_BUTTON_EMPTY then
        -- Server only code
        assert(self.isServer);
        if self.emptyState == BaleGrab.EMPTY_NONE then
            --BaleGrab.CHANGE_EMPTY_START
            if BaleGrab.getAllowsStartUnloading(self) then
                g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_EMPTY_START), true, nil, self);
            end;
        end;
    elseif id == BaleGrab.CHANGE_BUTTON_EMPTY_ABORT then
        -- Server only code
        assert(self.isServer);
        if self.emptyState ~= BaleGrab.EMPTY_NONE then
			--BaleGrab.CHANGE_EMPTY_CANCEL
			g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_EMPTY_CANCEL), true, nil, self);
        end;
    elseif id == BaleGrab.CHANGE_BUTTON_WORK_TRANSPORT then
        -- Server only code
        assert(self.isServer);
        if self.emptyState == BaleGrab.EMPTY_NONE and self.grabberMoveState == nil then
            if self.isInWorkPosition then
                g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_MOVE_TO_TRANSPORT), true, nil, self);
            else
                g_server:broadcastEvent(BaleGrabStateEvent:new(self, BaleGrab.CHANGE_MOVE_TO_WORK), true, nil, self);
            end;
        end;
	elseif id >= BaleGrab.STATE_MAX then
		local usedSlider = nil;
		for k, slider in pairs(self.busyBaleSliders) do
			if slider.animId == id then
				table.remove(self.busyBaleSliders, k);
				table.insert(self.freeBaleSliders, slider);
				usedSlider = slider;
				break;
			end;
		end;
		self.baleSliderNumber = self.baleSliderNumber - 1;
		self.baleFallingTime = self.time + 2000;
		local bale = networkGetObject(usedSlider.currentBale);
		if bale ~= nil then
			bale:unmount();
		end;
		self.balesToMount[usedSlider.currentBale] = nil;

		self:playAnimation(usedSlider.animation, -9999999, nil, true);
		usedSlider.currentBale = nil;
    end;
end;

function BaleGrab.updateBalePlacesAnimations(self)

	for _, slider in pairs(self.busyBaleSliders) do
		self:playAnimation(slider.animation, 20, nil, true);
		AnimatedVehicle.updateAnimations(self, 99999999);
	end;

end;

function BaleGrab.getAllowsStartUnloading(self)
    return self.fillLevel > 0 and not self.grabberIsMoving and self.grabberMoveState == nil and self.emptyState == BaleGrab.EMPTY_NONE;
end;

function BaleGrab.moveToWorkPosition(self)
    self:playAnimation("baleGrabberTransportToWork", 1, Utils.clamp(self:getAnimationTime("baleGrabberTransportToWork"), 0, 1), true);
end;

function BaleGrab.moveToTransportPosition(self)
    self:playAnimation("baleGrabberTransportToWork", -1, Utils.clamp(self:getAnimationTime("baleGrabberTransportToWork"), 0, 1), true);
end;

function BaleGrab:lockBales(doLock, noEventSend)

	if self.lockState ~= doLock then
		if self.isServer and noEventSend == nil or noEventSend == false then
			g_server:broadcastEvent(BaleGrabLockEvent:new(self, doLock), nil, nil, self);
		end;

		if doLock then
			for _, bale in pairs(self.loadedBales) do
				if bale.object.nodeId ~= 0 then
					local x,y,z = getWorldTranslation(bale.object.nodeId);
					x,y,z = worldToLocal(self.baleLock, x,y,z);
					local upX, upY, upZ = localDirectionToWorld(bale.object.nodeId, 0, 1, 0);
					local dirX,dirY,dirZ = localDirectionToWorld(bale.object.nodeId, 0, 0, 1);
					bale.object:mount(self, self.baleLock, x,y,z, 0,0,0);
					Utils.setWorldDirection(bale.object.nodeId, dirX, dirY, dirZ, upX, upY, upZ);
				end;
			end;
		else
			for _, bale in pairs(self.loadedBales) do
				if bale.object.nodeId ~= 0 then
					bale.object:unmount();
				end;
			end;
		end;
	end;
	self.lockState = doLock;
end;


function BaleGrab:onBaleTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if onEnter or onLeave then
        if otherId ~= 0 then
            local object = g_currentMission:getNodeObject(otherId);
			if object ~= nil then
                if object:isa(Bale) then
					if onEnter then
						if self.loadedBales[otherId] == nil then
							local mass = getMass(otherId);
							self.loadedBales[otherId] = {object=object, mass=mass};
							setMass(otherId, 0.01);
							self.fillLevel = self.fillLevel + 1;
						end;
					else
						local bale = self.loadedBales[otherId];
						if bale ~= nil then
							setMass(otherId, bale.mass);
							self.loadedBales[otherId] = nil;
							self.fillLevel = math.max(self.fillLevel - 1, 0);
						end;
					end;
				end;
			end;
		end;
	end;
end;


BaleGrabStateEvent = {};
BaleGrabStateEvent_mt = Class(BaleGrabStateEvent, Event);

InitEventClass(BaleGrabStateEvent, "BaleGrabStateEvent");

function BaleGrabStateEvent:emptyNew()
    local self = Event:new(BaleGrabStateEvent_mt);
    return self;
end;

function BaleGrabStateEvent:new(object, stateId, nearestBaleServerId, sliderId)
    local self = BaleGrabStateEvent:emptyNew()
    self.object = object;
    self.stateId = stateId;
	self.sliderId = sliderId;
    assert(nearestBaleServerId ~= nil or self.stateId ~= BaleGrab.CHANGE_GRAB_BALE);
    self.nearestBaleServerId = nearestBaleServerId;
    return self;
end;

function BaleGrabStateEvent:readStream(streamId, connection)
    self.object = networkGetObject(streamReadInt32(streamId));

    self.stateId = streamReadInt8(streamId);
    if self.stateId == BaleGrab.CHANGE_GRAB_BALE then
        self.nearestBaleServerId = streamReadInt32(streamId);
    end;

	if self.stateId == BaleGrab.CHANGE_GRABBED_BALE then
		self.sliderId = streamReadInt32(streamId);
	end;
    self:run(connection);
end;

function BaleGrabStateEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
    streamWriteInt8(streamId, self.stateId);
    if self.stateId == BaleGrab.CHANGE_GRAB_BALE then
        streamWriteInt32(streamId, self.nearestBaleServerId);
    end;

	if self.stateId == BaleGrab.CHANGE_GRABBED_BALE then
		streamWriteInt32(streamId, self.sliderId);
	end;
end;

function BaleGrabStateEvent:run(connection)
    self.object:doStateChange(self.stateId, self.nearestBaleServerId, self.sliderId);
end;

BaleGrabLockEvent = {};
BaleGrabLockEvent_mt = Class(BaleGrabLockEvent, Event);

InitEventClass(BaleGrabLockEvent, "BaleGrabLockEvent");

function BaleGrabLockEvent:emptyNew()
    local self = Event:new(BaleGrabLockEvent_mt);
    return self;
end;

function BaleGrabLockEvent:new(object, isLocked)
    local self = BaleGrabLockEvent:emptyNew()
    self.object = object;
    self.isLocked = isLocked;
    return self;
end;

function BaleGrabLockEvent:readStream(streamId, connection)
    self.object = networkGetObject(streamReadInt32(streamId));
    self.isLocked = streamReadBool(streamId);
    self:run(connection);
end;

function BaleGrabLockEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
    streamWriteBool(streamId, self.isLocked);
end;

function BaleGrabLockEvent:run(connection)
    self.object:lockBales(self.isLocked, true);
end;