--
-- BaleLoader
-- This is the specialization for automatic bale loaders
--
-- @author  Stefan Geiger
-- @date  	12/10/09
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

-- @edit	Ifko[nator]
-- @date	19/08/2013

-- Works now for the squarebales from my (Ifko[nator]) squarebaler, the original squarebales and squarebales with the Boolean User Attribute "isSquarebale". 
 
source("dataS/scripts/vehicles/specializations/BaleLoaderStateEvent.lua");
Quaderballensammelwagen = {};
 
function Quaderballensammelwagen.prerequisitesPresent(specializations)
 
    return SpecializationUtil.hasSpecialization(Fillable, specializations);
end;
 
Quaderballensammelwagen.GRAB_MOVE_UP = 1;
Quaderballensammelwagen.GRAB_MOVE_DOWN = 2;
Quaderballensammelwagen.GRAB_DROP_BALE = 3;
 
Quaderballensammelwagen.EMPTY_NONE = 1;
Quaderballensammelwagen.EMPTY_TO_WORK = 2;
Quaderballensammelwagen.EMPTY_ROTATE_PLATFORM = 3;
Quaderballensammelwagen.EMPTY_ROTATE1 = 4;
Quaderballensammelwagen.EMPTY_CLOSE_GRIPPERS = 5;
Quaderballensammelwagen.EMPTY_HIDE_PUSHER1 = 6;
Quaderballensammelwagen.EMPTY_HIDE_PUSHER2 = 7;
Quaderballensammelwagen.EMPTY_ROTATE2 = 8;
Quaderballensammelwagen.EMPTY_WAIT_TO_DROP = 9;
Quaderballensammelwagen.EMPTY_WAIT_TO_SINK = 10;
Quaderballensammelwagen.EMPTY_SINK = 11;
Quaderballensammelwagen.EMPTY_CANCEL = 12;
Quaderballensammelwagen.EMPTY_WAIT_TO_REDO = 13;
 
Quaderballensammelwagen.CHANGE_DROP_BALES = 1;
Quaderballensammelwagen.CHANGE_SINK = 2;
Quaderballensammelwagen.CHANGE_EMPTY_REDO = 3;
Quaderballensammelwagen.CHANGE_EMPTY_START = 4;
Quaderballensammelwagen.CHANGE_EMPTY_CANCEL = 5;
Quaderballensammelwagen.CHANGE_MOVE_TO_WORK = 6;
Quaderballensammelwagen.CHANGE_MOVE_TO_TRANSPORT = 7;
Quaderballensammelwagen.CHANGE_GRAB_BALE = 8;
Quaderballensammelwagen.CHANGE_GRAB_MOVE_UP = 9;
Quaderballensammelwagen.CHANGE_GRAB_DROP_BALE = 10;
Quaderballensammelwagen.CHANGE_GRAB_MOVE_DOWN = 11;
Quaderballensammelwagen.CHANGE_FRONT_PUSHER = 12;
Quaderballensammelwagen.CHANGE_ROTATE_PLATFORM = 13;
Quaderballensammelwagen.CHANGE_EMPTY_ROTATE_PLATFORM = 14;
Quaderballensammelwagen.CHANGE_EMPTY_ROTATE1 = 15;
Quaderballensammelwagen.CHANGE_EMPTY_CLOSE_GRIPPERS = 16;
Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER1 = 17;
Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER2 = 18;
Quaderballensammelwagen.CHANGE_EMPTY_ROTATE2 = 19;
Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_DROP = 20;
Quaderballensammelwagen.CHANGE_EMPTY_STATE_NIL = 21;
Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_REDO = 22;
 
  
Quaderballensammelwagen.CHANGE_BUTTON_EMPTY = 23;
Quaderballensammelwagen.CHANGE_BUTTON_EMPTY_ABORT = 24;
Quaderballensammelwagen.CHANGE_BUTTON_WORK_TRANSPORT = 25;
  
  
  
-- emptying:                             user input state switch    emptyState                  state switch automatically performed
-- 1. move grabber to work position           CHANGE_EMPTY_START -> (EMPTY_TO_WORK)          -> CHANGE_EMPTY_ROTATE_PLATFORM ->
-- 2. rotate platform                                               (EMPTY_ROTATE_PLATFORM)  -> CHANGE_EMPTY_ROTATE1 ->
-- 3. rotate main to half rotation and move the bales to the back   (EMPTY_ROTATE1)          -> CHANGE_EMPTY_CLOSE_GRIPPERS ->
-- 4. close grippers                                                (EMPTY_CLOSE_GRIPPERS)   -> CHANGE_EMPTY_HIDE_PUSHER1 ->
-- 5. hide pushers part 1 (rotation)                                (EMPTY_HIDE_PUSHER1)     -> CHANGE_EMPTY_HIDE_PUSHER2 ->
-- 6. hide pushers part 2 (translation)                             (EMPTY_HIDE_PUSHER2)     -> CHANGE_EMPTY_ROTATE2 ->
-- 7. rotate main to full rotation                                  (EMPTY_ROTATE2)          -> CHANGE_EMPTY_WAIT_TO_DROP ->
-- 8. wait for user input                                           (EMPTY_WAIT_TO_DROP)
 
-- 2 options:
  
-- option 1:
-- 9a. rotate main to no rotation              CHANGE_EMPTY_CANCEL     -> (EMPTY_CANCEL)     -> CHANGE_EMPTY_WAIT_TO_REDO ->
-- 10a. wait for user input                                         (EMPTY_WAIT_TO_REDO)
--                                             CHANGE_EMPTY_REDO -> (EMPTY_ROTATE2)
 
-- option 2:
-- 9b.                                         CHANGE_DROP_BALES -> (EMPTY_WAIT_TO_SINK)
-- 10b. wait for user input
-- 11b. rotate main to no rotation             CHANGE_SINK       -> (EMPTY_SINK)             -> CHANGE_EMPTY_STATE_NIL
  
  
function Quaderballensammelwagen:load(xmlFile)
 
    self.doStateChange = Quaderballensammelwagen.doStateChange;
 
    self.balesToLoad = {}

    self.balesToMount = {};
  
    self.isInWorkPosition = false;
    self.grabberIsMoving = false;
    self.allowGrabbing = false;
	
    self.isloadFromAttributesAndNodes = false;
 
    self.rotatePlatformDirection = 0;
    self.frontBalePusherDirection = 0;
  
    self.emptyState = Quaderballensammelwagen.EMPTY_NONE;
 
    self.itemsToSave ={}
    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.startBalePlace = {};
    self.startBalePlace.bales = {};
    self.startBalePlace.node = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.balePlaces#startBalePlace"));
    if self.startBalePlace.node ~= nil then
--        if getNumOfChildren(self.startBalePlace.node) < 4 then
        if getNumOfChildren(self.startBalePlace.node) < 2 then
            self.startBalePlace.node = nil;
        else
            self.startBalePlace.origRot = {};
            self.startBalePlace.origTrans = {};
--            for i=1, 4 do
            for i=1, 2 do
                local node = getChildAt(self.startBalePlace.node, i-1);
                local x,y,z = getRotation(node);
                self.startBalePlace.origRot[i] = {x,y,z};
                local x,y,z = getTranslation(node);
                self.startBalePlace.origTrans[i] = {x,y,z};
            end;
        end;
    end;
    self.startBalePlace.count = 0;
  
    self.currentBalePlace = 1;
    self.balePlaces = {};
    local i=0;
    while true do
        local key = string.format("vehicle.balePlaces.balePlace(%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;
            table.insert(self.balePlaces, entry);
        end;
        i = i + 1;
    end;
  
    self.baleGrabSound = {};
    local baleGrabSoundFile = getXMLString(xmlFile, "vehicle.baleGrabSound#file");
    if baleGrabSoundFile ~= nil then
        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;
  
    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.BaleLoaderHydraulicSound = {};
    local BaleLoaderHydraulicSoundFile = getXMLString(xmlFile, "vehicle.BaleLoaderHydraulicSound#file");
    if BaleLoaderHydraulicSoundFile ~= nil then
        self.BaleLoaderHydraulicSound.sample = createSample("BaleLoaderHydraulicSound");
        loadSample(self.BaleLoaderHydraulicSound.sample, BaleLoaderHydraulicSoundFile, false);
        local pitch = getXMLFloat(xmlFile, "vehicle.BaleLoaderHydraulicSound#pitchOffset");
        if pitch ~= nil then
            setSamplePitch(self.BaleLoaderHydraulicSound.sample, pitch);
        end;
        self.BaleLoaderHydraulicSound.volume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.BaleLoaderHydraulicSound#volume"), 1);
        self.BaleLoaderHydraulicSound.enabled = false;
    end;
  
    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
            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;
end;
  
function Quaderballensammelwagen:delete()
  
    if (self.baleGrabParticleSystems) then
        Utils.deleteParticleSystem(self.baleGrabParticleSystems);
    end;
  
    -- avoid the bale nodes to be deleted twice (because they are linked the vehicle and because the Bale object is deleted)
    for i, balePlace in pairs(self.balePlaces) do
        if balePlace.bales ~= nil then
            for _, baleServerId in pairs(balePlace.bales) do
                local bale = networkGetObject(baleServerId);
                if bale ~= nil then
                    if bale.nodeId ~= 0 then
                        unlink(bale.nodeId);
                    end;
                end;
            end;
        end;
    end;
    for i, baleServerId in ipairs(self.startBalePlace.bales) do
        local bale = networkGetObject(baleServerId);
        if bale ~= nil then
            if bale.nodeId ~= 0 then
                unlink(bale.nodeId);
            end;
        end;
    end;
    if self.baleGrabSound.sample ~= nil then
        delete(self.baleGrabSound.sample);
        self.baleGrabSound.sample = nil;
    end;
    if self.BaleLoaderHydraulicSound.sample ~= nil then
        delete(self.BaleLoaderHydraulicSound.sample);
        self.BaleLoaderHydraulicSound.sample = nil;
    end;
end;
  
function Quaderballensammelwagen:readStream(streamId, connection)
  
    self.isInWorkPosition = streamReadBool(streamId);
    self.frontBalePusherDirection = streamReadIntN(streamId, 3);
    self.rotatePlatformDirection = streamReadIntN(streamId, 3);
 
    -- note: we do not sync grabberMoveState, this may lead to visual artifacts for a few seconds at the bigging
 
    if self.isInWorkPosition then
        Quaderballensammelwagen.moveToWorkPosition(self);
    end;
  
    local emptyState = streamReadUIntN(streamId, 4);
  
    self.currentBalePlace = streamReadInt8(streamId);
    self.fillLevel = streamReadFloat32(streamId);
  
    -- 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
    self.startBalePlace.count = streamReadUIntN(streamId, 3);
    for i=1, self.startBalePlace.count do
        local baleServerId = streamReadInt32(streamId);
  
        local attachNode = getChildAt(self.startBalePlace.node, i-1)
        self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=attachNode, trans={0,0,0}, rot={0,0,0} };
  
        table.insert(self.startBalePlace.bales, baleServerId);
    end;
  
  
    -- read bales at normal bale places
    for i=1, table.getn(self.balePlaces) do
        local balePlace = self.balePlaces[i];
  
        local numBales = streamReadUIntN(streamId, 3);
        if numBales > 0 then
            balePlace.bales = {};
  
            for baleI=1, numBales do
                local baleServerId = streamReadInt32(streamId, baleServerId);
                local x = streamReadFloat32(streamId);
                local y = streamReadFloat32(streamId);
                local z = streamReadFloat32(streamId);
  
                table.insert(balePlace.bales, baleServerId);
  
                self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=balePlace.node, trans={ x,y,z}, rot={0,0,0} };
            end;
        end;
    end;
  
    -- read num bales on platform
    -- read bales on BaleLoader
    -- ignore
  
    -- grabberMoveState ? int:nil
    -- isInWorkPosition  bool
    -- emptyState ? int:nil
    -- rotatePlatformDirection
    -- frontBalePusherDirection
    -- grabberIsMoving: bool
  
  
    -- update animations
    Quaderballensammelwagen.updateBalePlacesAnimations(self);
  
    if emptyState >= Quaderballensammelwagen.EMPTY_TO_WORK then
        self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_START);
        AnimatedVehicle.updateAnimations(self, 99999999);
        if emptyState >= Quaderballensammelwagen.EMPTY_ROTATE_PLATFORM then
            self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_ROTATE_PLATFORM);
            AnimatedVehicle.updateAnimations(self, 99999999);
            if emptyState >= Quaderballensammelwagen.EMPTY_ROTATE1 then
                self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_ROTATE1);
                AnimatedVehicle.updateAnimations(self, 99999999);
                if emptyState >= Quaderballensammelwagen.EMPTY_CLOSE_GRIPPERS then
                    self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_CLOSE_GRIPPERS);
                    AnimatedVehicle.updateAnimations(self, 99999999);
                    if emptyState >= Quaderballensammelwagen.EMPTY_HIDE_PUSHER1 then
                        self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER1);
                        AnimatedVehicle.updateAnimations(self, 99999999);
                        if emptyState >= Quaderballensammelwagen.EMPTY_HIDE_PUSHER2 then
                            self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER2);
                            AnimatedVehicle.updateAnimations(self, 99999999);
                            if emptyState >= Quaderballensammelwagen.EMPTY_ROTATE2 then
                                self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_ROTATE2);
                                AnimatedVehicle.updateAnimations(self, 99999999);
                                if emptyState >= Quaderballensammelwagen.EMPTY_WAIT_TO_DROP then
                                    self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_DROP);
                                    AnimatedVehicle.updateAnimations(self, 99999999);
  
  
                                    if emptyState == Quaderballensammelwagen.EMPTY_CANCEL or emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_REDO then
                                        self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_CANCEL);
                                        AnimatedVehicle.updateAnimations(self, 99999999);
  
                                        if emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_REDO then
                                            self:doStateChange(Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_REDO);
                                            AnimatedVehicle.updateAnimations(self, 99999999);
                                        end;
                                    elseif emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_SINK or emptyState == Quaderballensammelwagen.EMPTY_SINK then
                                        self:doStateChange(Quaderballensammelwagen.CHANGE_DROP_BALES);
                                        AnimatedVehicle.updateAnimations(self, 99999999);
 
                                        if emptyState == Quaderballensammelwagen.EMPTY_SINK then
                                            self:doStateChange(Quaderballensammelwagen.CHANGE_SINK);
                                            AnimatedVehicle.updateAnimations(self, 99999999);
                                        end;
                                    end;
                                end;
                            end;
                        end;
                    end;
                end;
            end;
        end;
    end;
    self.emptyState = emptyState;
end;
  
function Quaderballensammelwagen:writeStream(streamId, connection)
    streamWriteBool(streamId, self.isInWorkPosition);
    streamWriteIntN(streamId, self.frontBalePusherDirection, 3);
    streamWriteIntN(streamId, self.rotatePlatformDirection, 3);
  
    streamWriteUIntN(streamId, self.emptyState, 4);
  
    streamWriteInt8(streamId, self.currentBalePlace);
    streamWriteFloat32(streamId, self.fillLevel);
  
    -- write bale at bale grabber
    if streamWriteBool(streamId, self.baleGrabber.currentBale ~= nil) then
        streamWriteInt32(streamId, self.baleGrabber.currentBale);
    end;
  
    -- write bales at start bale places
    streamWriteUIntN(streamId, self.startBalePlace.count, 3);
    for i=1, self.startBalePlace.count do
        local baleServerId = self.startBalePlace.bales[i];
        streamWriteInt32(streamId, baleServerId);
    end;
  
    -- write bales at normal bale places
    for i=1, table.getn(self.balePlaces) do
        local balePlace = self.balePlaces[i];
  
        local numBales = 0;
        if balePlace.bales ~= nil then
            numBales = table.getn(balePlace.bales);
        end;
        streamWriteUIntN(streamId, numBales, 3);
        if balePlace.bales ~= nil then
            for baleI=1, numBales do
                local baleServerId = balePlace.bales[baleI];
                local bale = networkGetObject(baleServerId);
                local nodeId = bale.nodeId;
                local x,y,z = getTranslation(nodeId);
                streamWriteInt32(streamId, baleServerId);
                streamWriteFloat32(streamId, x);
                streamWriteFloat32(streamId, y);
                streamWriteFloat32(streamId, z);
            end;
        end;
    end;
end;
  
function Quaderballensammelwagen.updateBalePlacesAnimations(self)
	if self.isloadFromAttributesAndNodes == false then
		if self.currentBalePlace > 2 then
			-- currentBalePlace-1 is needs to be at the first position
			self:playAnimation("moveBalePlaces", 20, 0, true);
			self:setAnimationStopTime("moveBalePlaces", (self.currentBalePlace-2)/table.getn(self.balePlaces));
	  
			AnimatedVehicle.updateAnimations(self, 99999999);
		end;
	else
		if self.currentBalePlace > 2 then
			-- currentBalePlace-1 is needs to be at the first position
			self:playAnimation("moveBalePlacesToEmpty", 20, 0, true);
			self:setAnimationStopTime("moveBalePlacesToEmpty", (self.currentBalePlace-2)/table.getn(self.balePlaces));
	  
			AnimatedVehicle.updateAnimations(self, 99999999);
		end;
	end
  
    if self.startBalePlace.count >= 1 then
        self:playAnimation("bale1ToOtherSide", 20, nil, true);
        AnimatedVehicle.updateAnimations(self, 99999999);
        if self.startBalePlace.count >= 2 then
            Quaderballensammelwagen.rotatePlatform(self);
            -- self:playAnimation("balesToOtherRow", 20, nil, true);
            -- AnimatedVehicle.updateAnimations(self, 99999999);
            -- if self.startBalePlace.count >= 3 then
                -- self:playAnimation("bale3ToOtherSide", 20, nil, true);
                -- AnimatedVehicle.updateAnimations(self, 99999999);
                -- if self.startBalePlace.count >= 4 then
                    -- Quaderballensammelwagen.rotatePlatform(self);
                -- end;
            -- end;
        end;
    end;
end;
  
function Quaderballensammelwagen:mouseEvent(posX, posY, isDown, isUp, button)
  
end;
 
function Quaderballensammelwagen:draw()
 
    if self.emptyState == Quaderballensammelwagen.EMPTY_NONE then
        if self.grabberMoveState == nil then
            if self.isInWorkPosition then
                g_currentMission:addHelpButtonText(g_i18n:getText("BaleLoader_TRANSPORT"), self.workTransportButton);
            else
                g_currentMission:addHelpButtonText(g_i18n:getText("BaleLoader_WORK"), self.workTransportButton);
            end;
        end;
  
        if Quaderballensammelwagen.getAllowsStartUnloading(self) then
            g_currentMission:addHelpButtonText(g_i18n:getText("BaleLoader_UNLOAD"), self.emptyButton);
        end;
    else
        if self.emptyState >= Quaderballensammelwagen.EMPTY_TO_WORK and self.emptyState <= Quaderballensammelwagen.EMPTY_ROTATE2 then
            g_currentMission:addExtraPrintText(g_i18n:getText("BaleLoader_UP"));
        elseif self.emptyState == Quaderballensammelwagen.EMPTY_CANCEL or self.emptyState == Quaderballensammelwagen.EMPTY_SINK then
            g_currentMission:addExtraPrintText(g_i18n:getText("BaleLoader_DOWN"));
        elseif self.emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_DROP then
            g_currentMission:addHelpButtonText(g_i18n:getText("BaleLoader_READY"), self.emptyButton);
            g_currentMission:addHelpButtonText(g_i18n:getText("BaleLoader_ABORT"), self.emptyAbortButton);
        elseif self.emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_SINK then
            g_currentMission:addHelpButtonText(g_i18n:getText("BaleLoader_SINK"), self.emptyButton);
        elseif self.emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_REDO then
            g_currentMission:addHelpButtonText(g_i18n:getText("BaleLoader_UNLOAD"), self.emptyButton);
        end;
    end;
  
end
  
  
function Quaderballensammelwagen:keyEvent(unicode, sym, modifier, isDown)
  
  
end;
  
  
function Quaderballensammelwagen: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();
 
            table.insert(v.bales, networkGetObjectId(baleObject));
            self.balesToLoad[k] = nil;
        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);
  
                --print("mounting bale delayed: "..baleToMount.serverId..", pos: "..x.." "..y.." "..z);
                bale:mount(self, baleToMount.linkNode, x,y,z, rx,ry,rz);
                self.balesToMount[k] = nil;
            end;
        end;
    end;
  
    if self:getIsActive() then
        if self:getIsActiveForInput() and self.isClient then
            if InputBinding.hasEvent(self.emptyButton)  then
                g_client:getServerConnection():sendEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_BUTTON_EMPTY));
            elseif InputBinding.hasEvent(self.emptyAbortButton) then
                g_client:getServerConnection():sendEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_BUTTON_EMPTY_ABORT));
            elseif InputBinding.hasEvent(self.workTransportButton) then
                g_client:getServerConnection():sendEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_BUTTON_WORK_TRANSPORT));
            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.startBalePlace.count < 4 and self.frontBalePusherDirection == 0 and self.rotatePlatformDirection == 0 and self.emptyState == Quaderballensammelwagen.EMPTY_NONE and self.fillLevel < self.fillLevelMax then
        if self.isInWorkPosition and not self.grabberIsMoving and self.grabberMoveState == nil and self.startBalePlace.count < 2 and self.frontBalePusherDirection == 0 and self.rotatePlatformDirection == 0 and self.emptyState == Quaderballensammelwagen.EMPTY_NONE and self.fillLevel < self.fillLevelMax then
            self.allowGrabbing = true;
        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 = Quaderballensammelwagen.getBaleInRange(self, self.baleGrabber.grabNode);
                    if nearestBale ~= nil then
                        --Quaderballensammelwagen.CHANGE_GRAB_BALE
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_GRAB_BALE, networkGetObjectId(nearestBale)), true, nil, self);
                    end;
                end;
            end;
            if self.grabberMoveState ~= nil then
                if self.grabberMoveState == Quaderballensammelwagen.GRAB_MOVE_UP then
                    if not self:getIsAnimationPlaying("baleGrabberWorkToDrop") then
                        --Quaderballensammelwagen.CHANGE_GRAB_MOVE_UP
                        -- switch to grabberMoveState
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_GRAB_MOVE_UP), true, nil, self);
                    end;
                elseif self.grabberMoveState == Quaderballensammelwagen.GRAB_DROP_BALE then
                    if not self:getIsAnimationPlaying("baleGrabberDropBale") then
                        --Quaderballensammelwagen.CHANGE_GRAB_DROP_BALE
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_GRAB_DROP_BALE), true, nil, self);
                    end;
                elseif self.grabberMoveState == Quaderballensammelwagen.GRAB_MOVE_DOWN then
                    --Quaderballensammelwagen.CHANGE_GRAB_MOVE_DOWN
                    if not self:getIsAnimationPlaying("baleGrabberWorkToDrop") then
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_GRAB_MOVE_DOWN), true, nil, self);
                    end;
                end;
            end;
            if self.frontBalePusherDirection ~= 0 then
                if not self:getIsAnimationPlaying("frontBalePusher") then
                    --Quaderballensammelwagen.CHANGE_FRONT_PUSHER
                    g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_FRONT_PUSHER), true, nil, self);
                end;
            end;
            if self.rotatePlatformDirection ~= 0 then
                if not self:getIsAnimationPlaying("rotatePlatform") then
                    --Quaderballensammelwagen.CHANGE_ROTATE_PLATFORM
                    g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_ROTATE_PLATFORM), true, nil, self);
                end;
            end;
  
            if self.emptyState ~= Quaderballensammelwagen.EMPTY_NONE then
                if self.emptyState == Quaderballensammelwagen.EMPTY_TO_WORK then
                    if not self:getIsAnimationPlaying("baleGrabberTransportToWork") then
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_ROTATE_PLATFORM), true, nil, self);
                    end;
                elseif self.emptyState == Quaderballensammelwagen.EMPTY_ROTATE_PLATFORM then
                        if not self:getIsAnimationPlaying("rotatePlatform") then
                        --Quaderballensammelwagen.CHANGE_EMPTY_ROTATE1
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_ROTATE1), true, nil, self);
                    end;
                elseif self.emptyState == Quaderballensammelwagen.EMPTY_ROTATE1 then
                    if not self:getIsAnimationPlaying("emptyRotate") and not self:getIsAnimationPlaying("moveBalePlacesToEmpty") then
                        --Quaderballensammelwagen.CHANGE_EMPTY_CLOSE_GRIPPERS
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_CLOSE_GRIPPERS), true, nil, self);
                    end;
                elseif self.emptyState == Quaderballensammelwagen.EMPTY_CLOSE_GRIPPERS then
                    if not self:getIsAnimationPlaying("closeGrippers") then
                        --Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER1
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER1), true, nil, self);
                    end;
                elseif self.emptyState == Quaderballensammelwagen.EMPTY_HIDE_PUSHER1 then
                    if not self:getIsAnimationPlaying("emptyHidePusher1") then
                        --Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER2
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER2), true, nil, self);
                    end;
                elseif self.emptyState == Quaderballensammelwagen.EMPTY_HIDE_PUSHER2 then
					if self:getAnimationTime("moveBalePusherToEmpty") < 0.7 or not self:getIsAnimationPlaying("moveBalePusherToEmpty") then
						--Quaderballensammelwagen.CHANGE_EMPTY_ROTATE2
						g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_ROTATE2), true, nil, self);
					end;
                elseif self.emptyState == Quaderballensammelwagen.EMPTY_ROTATE2 then
                    if not self:getIsAnimationPlaying("emptyRotate") then
                        --Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_DROP
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_DROP), true, nil, self);
                    end;
                elseif self.emptyState == Quaderballensammelwagen.EMPTY_SINK then
                    if not self:getIsAnimationPlaying("emptyRotate") and
                        not self:getIsAnimationPlaying("moveBalePlacesToEmpty") and
                        not self:getIsAnimationPlaying("emptyHidePusher1") and
                        not self:getIsAnimationPlaying("rotatePlatform")
                    then
                        --Quaderballensammelwagen.CHANGE_EMPTY_STATE_NIL
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_STATE_NIL), true, nil, self);
                    end;
                elseif self.emptyState == Quaderballensammelwagen.EMPTY_CANCEL then
                    if not self:getIsAnimationPlaying("emptyRotate") then
                        --Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_REDO
                        g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_REDO), true, nil, self);
                    end;
                end;
            end;
        end;
  
        if self.isClient then
            if self.BaleLoaderHydraulicSound.sample ~= nil then
                local hasAnimationsPlaying = false;
                for _, v in pairs(self.activeAnimations) do
                    hasAnimationsPlaying = true;
                    break;
                end;
                if hasAnimationsPlaying then
                    if not self.BaleLoaderHydraulicSound.enabled and self:getIsActiveForSound() then
                        playSample(self.BaleLoaderHydraulicSound.sample, 0, self.BaleLoaderHydraulicSound.volume, 0);
                        self.BaleLoaderHydraulicSound.enabled = true;
                    end;
                else
                    if self.BaleLoaderHydraulicSound.enabled then
                        stopSample(self.BaleLoaderHydraulicSound.sample);
                        self.BaleLoaderHydraulicSound.enabled = false;
                    end;
                end;
            end;
        end;
    end;
  
end;
  
function Quaderballensammelwagen.getBaleInRange(self, refNode)
    local px, py, pz = getWorldTranslation(refNode);
    local nearestDistance = 3.0;
    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 
				or getUserAttribute(item.item.nodeId, "isSquarebale")
				or string.find(item.item.i3dFilename, "squarebale")
				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;
                    break;
                end;
            end;
        end;
    end;
    return nearestBale;
end;
  
function Quaderballensammelwagen:onDetach()
    if self.deactivateOnDetach then
        Quaderballensammelwagen.onDeactivate(self);
    else
        Quaderballensammelwagen.onDeactivateSounds(self)
    end;
end;
  
function Quaderballensammelwagen:onAttach()
end;
  
function Quaderballensammelwagen:onDeactivate()
    Utils.setEmittingState(self.baleGrabParticleSystems, false);
    Quaderballensammelwagen.onDeactivateSounds(self)
end;
  
function Quaderballensammelwagen:onDeactivateSounds()
    if self.BaleLoaderHydraulicSound.sample ~= nil then
        stopSample(self.BaleLoaderHydraulicSound.sample);
    end;
end;
  
 
function Quaderballensammelwagen:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
    self.currentBalePlace = 1;
  
    self.startBalePlace.count = 0;
    local numBales = 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 x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, baleKey.."#position"));
            local xRot,yRot,zRot = Utils.getVectorFromString(getXMLString(xmlFile, baleKey.."#rotation"));
            local balePlace = getXMLInt(xmlFile, baleKey.."#balePlace");
            local helper = getXMLInt(xmlFile, baleKey.."#helper");
            if balePlace == nil or (balePlace > 0 and (x == nil or y == nil or z == nil or xRot == nil or yRot == nil or zRot == nil)) or (balePlace < 1 and helper == nil) then
                print("Warning: Corrupt savegame, bale "..filename.." could not be loaded");
            else
                local translation;
                local rotation;
                if balePlace > 0 then
                    translation = {x,y,z};
                    rotation={xRot, yRot, zRot};
                else
                    translation = {0,0,0};
                    rotation={0,0,0};
                end;
                local parentNode = nil;
                local bales = nil;
                if balePlace < 1 then
                    if helper <= getNumOfChildren(self.startBalePlace.node) then
                        parentNode = getChildAt(self.startBalePlace.node, helper-1);
                        if self.startBalePlace.bales == nil then
                            self.startBalePlace.bales = {};
                        end;
                        bales = self.startBalePlace.bales;
                        self.startBalePlace.count = self.startBalePlace.count+1;
                    end;
                elseif balePlace <= table.getn(self.balePlaces) then
                    self.currentBalePlace = math.max(self.currentBalePlace, balePlace+1);
                    parentNode = self.balePlaces[balePlace].node;
                    if self.balePlaces[balePlace].bales == nil then
                        self.balePlaces[balePlace].bales = {};
                    end;
                    bales = self.balePlaces[balePlace].bales;
                end;
                if parentNode ~= nil then
                    numBales = numBales+1;
                    table.insert(self.balesToLoad, {parentNode=parentNode, filename=filename, bales=bales, translation=translation, rotation=rotation});
                end;
            end;
        end;
        i = i +1;
    end;
  
    -- update animations
	self.isloadFromAttributesAndNodes = true
    Quaderballensammelwagen.updateBalePlacesAnimations(self);
	self.isloadFromAttributesAndNodes = false
  
    self.fillLevel = numBales;
    return BaseMission.VEHICLE_LOAD_OK;
end
  
function Quaderballensammelwagen:getSaveAttributesAndNodes(nodeIdent)
  
    local attributes = "";
    local nodes = "";
    local baleNum = 0;
  
    for i, balePlace in pairs(self.balePlaces) do
        if balePlace.bales ~= nil then
            for _, baleServerId in pairs(balePlace.bales) do
                local bale = networkGetObject(baleServerId);
                if bale ~= nil then
                    local nodeId = bale.nodeId;
                    local x,y,z = getTranslation(nodeId);
                    local rx,ry,rz = getRotation(nodeId);
                    if baleNum>0 then
                        nodes = nodes.."\n";
                    end;
                    nodes = nodes..nodeIdent..'<bale filename="'..Utils.encodeToHTML(Utils.convertToNetworkFilename(bale.i3dFilename))..'" position="'..x..' '..y..' '..z..'" rotation="'..rx..' '..ry..' '..rz..'" balePlace="'..i..'" />';
                    baleNum = baleNum+1;
                end;
            end;
        end;
    end;
    for i, baleServerId in ipairs(self.startBalePlace.bales) do
        local bale = networkGetObject(baleServerId);
        if bale ~= nil then
            if baleNum>0 then
                nodes = nodes.."\n";
            end;
            nodes = nodes..nodeIdent..'<bale filename="'..Utils.encodeToHTML(Utils.convertToNetworkFilename(bale.i3dFilename))..'" balePlace="0" helper="'..i..'"/>';
            baleNum = baleNum+1;
        end;
    end;
    return attributes,nodes;
end
  
function Quaderballensammelwagen:doStateChange(id, nearestBaleServerId)
  
    if id == Quaderballensammelwagen.CHANGE_DROP_BALES then
        -- drop all bales to ground (and add to save by mission)
        self.currentBalePlace = 1;
        for _, balePlace in pairs(self.balePlaces) do
            if balePlace.bales ~= nil then
                for _, baleServerId in pairs(balePlace.bales) do
                    local bale = networkGetObject(baleServerId);
                    if bale ~= nil then
                        bale:unmount();
                    end;
                    self.balesToMount[baleServerId] = nil;
                end;
                balePlace.bales = nil;
            end;
        end;
        self.fillLevel = 0;
        self:playAnimation("closeGrippers", -1, nil, true);
        self.emptyState = Quaderballensammelwagen.EMPTY_WAIT_TO_SINK;
    elseif id == Quaderballensammelwagen.CHANGE_SINK then
        self:playAnimation("emptyRotate", -1, nil, true);
        self:playAnimation("moveBalePlacesToEmpty", -5, nil, true);
        self:playAnimation("emptyHidePusher1", -1, nil, true);
        self:playAnimation("rotatePlatform", -1, nil, true);
        if not self.isInWorkPosition then
			self:playAnimation("closeGrippers", 1, self:getAnimationTime("closeGrippers"), true);
            self:playAnimation("baleGrabberTransportToWork", -1, nil, true);
        end;
        self.emptyState = Quaderballensammelwagen.EMPTY_SINK;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_REDO then
        self:playAnimation("emptyRotate", 1, nil, true);
        self.emptyState = Quaderballensammelwagen.EMPTY_ROTATE2;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_START then
        -- move to work position in case it is not there now
        Quaderballensammelwagen.moveToWorkPosition(self);
        self.emptyState = Quaderballensammelwagen.EMPTY_TO_WORK;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_CANCEL then
        self:playAnimation("emptyRotate", -1, nil, true);
        self.emptyState = Quaderballensammelwagen.EMPTY_CANCEL;
    elseif id == Quaderballensammelwagen.CHANGE_MOVE_TO_TRANSPORT then
        if self.isInWorkPosition then
            self.grabberIsMoving = true;
            self.isInWorkPosition = false;
            -- move to transport position
            Quaderballensammelwagen.moveToTransportPosition(self);
        end;
    elseif id == Quaderballensammelwagen.CHANGE_MOVE_TO_WORK then
        if not self.isInWorkPosition then
            self.grabberIsMoving = true;
            self.isInWorkPosition = true;
            -- move to work position
            Quaderballensammelwagen.moveToWorkPosition(self);
        end;
    elseif id == Quaderballensammelwagen.CHANGE_GRAB_BALE then
        local bale = networkGetObject(nearestBaleServerId);
        self.baleGrabber.currentBale = nearestBaleServerId;
        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 = Quaderballensammelwagen.GRAB_MOVE_UP;
        self:playAnimation("baleGrabberWorkToDrop", 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 == Quaderballensammelwagen.CHANGE_GRAB_MOVE_UP then
        self:playAnimation("baleGrabberDropBale", 1, nil, true);
        if self.startBalePlace.count == 1 then
            self:playAnimation("bale1ToOtherSide", 1, nil, true);
        elseif self.startBalePlace.count == 3 then
            self:playAnimation("bale3ToOtherSide", 1,  nil, true);
        end;
        self.grabberMoveState = Quaderballensammelwagen.GRAB_DROP_BALE;
    elseif id == Quaderballensammelwagen.CHANGE_GRAB_DROP_BALE then
        -- drop bale at platform
--        if self.startBalePlace.count < 4 and self.startBalePlace.node ~= nil then
        if self.startBalePlace.count < 2 and self.startBalePlace.node ~= nil then
            local attachNode = getChildAt(self.startBalePlace.node, self.startBalePlace.count)
            local bale = networkGetObject(self.baleGrabber.currentBale);
            if bale ~= nil then
                bale:mount(self, attachNode, 0,0,0, 0,0,0);
                self.balesToMount[self.baleGrabber.currentBale] = nil;
            else
                self.balesToMount[self.baleGrabber.currentBale] = {serverId=self.baleGrabber.currentBale, linkNode=attachNode, trans={0,0,0}, rot={0,0,0} };
            end;
  
            self.startBalePlace.count = self.startBalePlace.count + 1;
            table.insert(self.startBalePlace.bales, self.baleGrabber.currentBale);
            self.baleGrabber.currentBale = nil;
            --setRotation(baleNode, 0, 0, 0);
            --setTranslation(baleNode, 0, 0, 0);
  
--            if self.startBalePlace.count == 2 then
            if self.startBalePlace.count == 1 then
                self.frontBalePusherDirection = 1;
                self:playAnimation("balesToOtherRow", 1, nil, true);
                self:playAnimation("frontBalePusher", 1, nil, true);
--            elseif self.startBalePlace.count == 4 then
            elseif self.startBalePlace.count == 2 then
                Quaderballensammelwagen.rotatePlatform(self);
            end;
  
            self.fillLevel = self.fillLevel + 1;
  
            self:playAnimation("baleGrabberDropBale", -1, nil, true);
            self:playAnimation("baleGrabberWorkToDrop", -1, nil, true);
            self.grabberMoveState = Quaderballensammelwagen.GRAB_MOVE_DOWN;
        end;
    elseif id == Quaderballensammelwagen.CHANGE_GRAB_MOVE_DOWN then
        self.grabberMoveState = nil;
    elseif id == Quaderballensammelwagen.CHANGE_FRONT_PUSHER then
        if self.frontBalePusherDirection > 0 then
            self:playAnimation("frontBalePusher", -1, nil, true);
            self.frontBalePusherDirection = -1;
        else
            self.frontBalePusherDirection = 0;
        end;
    elseif id == Quaderballensammelwagen.CHANGE_ROTATE_PLATFORM then
        if self.rotatePlatformDirection > 0 then
            -- drop bales
            local balePlace = self.balePlaces[self.currentBalePlace];
            self.currentBalePlace = self.currentBalePlace + 1;
            for i=1, table.getn(self.startBalePlace.bales) do
                local node = getChildAt(self.startBalePlace.node, i-1);
                local x,y,z = getTranslation(node);
                local rx,ry,rz = getRotation(node);
                local baleServerId = self.startBalePlace.bales[i];
                local bale = networkGetObject(baleServerId);
                if bale ~= nil then
                    bale:mount(self, balePlace.node, x,y,z, rx,ry,rz);
                    self.balesToMount[baleServerId] = nil;
                else
                    self.balesToMount[baleServerId] = {serverId=baleServerId, linkNode=balePlace.node, trans={ x,y,z}, rot={0,0,0} };
                end;
            end;
            balePlace.bales = self.startBalePlace.bales;
            self.startBalePlace.bales = {};
            self.startBalePlace.count = 0;
 
--            for i=1, 4 do
            for i=1, 2 do
                local node = getChildAt(self.startBalePlace.node, i-1);
                setRotation(node, unpack(self.startBalePlace.origRot[i]));
                setTranslation(node, unpack(self.startBalePlace.origTrans[i]));
            end;
  
            if self.emptyState == Quaderballensammelwagen.EMPTY_NONE then
                -- we are not waiting to start emptying, rotate back
                self.rotatePlatformDirection = -1;
                self:playAnimation("rotatePlatform", -1, nil, true);
            else
                self.rotatePlatformDirection = 0;
            end;
        else
            self.rotatePlatformDirection = 0;
        end;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_ROTATE_PLATFORM then
        self.emptyState = Quaderballensammelwagen.EMPTY_ROTATE_PLATFORM;
        if self.startBalePlace.count == 0 then
            self:playAnimation("rotatePlatform", 1, nil, true);
        else
            Quaderballensammelwagen.rotatePlatform(self)
        end;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_ROTATE1 then
        self:playAnimation("emptyRotate", 1, nil, true);
        self:setAnimationStopTime("emptyRotate", 0.2);
        local balePlacesTime = self:getRealAnimationTime("moveBalePlaces");
        self:playAnimation("moveBalePlacesToEmpty", 1.5, balePlacesTime/self:getAnimationDuration("moveBalePlacesToEmpty"), true);
        self:playAnimation("moveBalePusherToEmpty", 1.5, balePlacesTime/self:getAnimationDuration("moveBalePusherToEmpty"), true);
        self.emptyState = Quaderballensammelwagen.EMPTY_ROTATE1;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_CLOSE_GRIPPERS then
        self:playAnimation("closeGrippers", 1, nil, true);
        self.emptyState = Quaderballensammelwagen.EMPTY_CLOSE_GRIPPERS;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER1 then
        self:playAnimation("emptyHidePusher1", 1, nil, true);
        self.emptyState = Quaderballensammelwagen.EMPTY_HIDE_PUSHER1;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_HIDE_PUSHER2 then
        self:playAnimation("moveBalePusherToEmpty", -2, nil, true);
        self.emptyState = Quaderballensammelwagen.EMPTY_HIDE_PUSHER2;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_ROTATE2 then
        self:playAnimation("emptyRotate", 1, self:getAnimationTime("emptyRotate"), true);
        self.emptyState = Quaderballensammelwagen.EMPTY_ROTATE2;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_DROP then
        -- wait for the user to react (abort or drop)
        self.emptyState = Quaderballensammelwagen.EMPTY_WAIT_TO_DROP;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_STATE_NIL then
        self.emptyState = Quaderballensammelwagen.EMPTY_NONE;
    elseif id == Quaderballensammelwagen.CHANGE_EMPTY_WAIT_TO_REDO then
        self.emptyState = Quaderballensammelwagen.EMPTY_WAIT_TO_REDO;
    elseif id == Quaderballensammelwagen.CHANGE_BUTTON_EMPTY then
        -- Server only code
        assert(self.isServer);
        if self.emptyState ~= Quaderballensammelwagen.EMPTY_NONE then
            if self.emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_DROP then
                -- Quaderballensammelwagen.CHANGE_DROP_BALES
                g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_DROP_BALES), true, nil, self);
            elseif self.emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_SINK then
                -- Quaderballensammelwagen.CHANGE_SINK
                g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_SINK), true, nil, self);
            elseif self.emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_REDO then
                -- Quaderballensammelwagen.CHANGE_EMPTY_REDO
                g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_REDO), true, nil, self);
            end;
        else
            --Quaderballensammelwagen.CHANGE_EMPTY_START
            if Quaderballensammelwagen.getAllowsStartUnloading(self) then
                g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_START), true, nil, self);
            end;
        end;
    elseif id == Quaderballensammelwagen.CHANGE_BUTTON_EMPTY_ABORT then
        -- Server only code
        assert(self.isServer);
        if self.emptyState ~= Quaderballensammelwagen.EMPTY_NONE then
            if self.emptyState == Quaderballensammelwagen.EMPTY_WAIT_TO_DROP then
                --Quaderballensammelwagen.CHANGE_EMPTY_CANCEL
                g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_EMPTY_CANCEL), true, nil, self);
            end;
        end;
    elseif id == Quaderballensammelwagen.CHANGE_BUTTON_WORK_TRANSPORT then
        -- Server only code
        assert(self.isServer);
        if self.emptyState == Quaderballensammelwagen.EMPTY_NONE and self.grabberMoveState == nil then
            if self.isInWorkPosition then
                g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_MOVE_TO_TRANSPORT), true, nil, self);
            else
                g_server:broadcastEvent(BaleLoaderStateEvent:new(self, Quaderballensammelwagen.CHANGE_MOVE_TO_WORK), true, nil, self);
            end;
        end;
    end;
end;
  
function Quaderballensammelwagen.getAllowsStartUnloading(self)
    return self.fillLevel > 0 and self.rotatePlatformDirection == 0 and self.frontBalePusherDirection == 0 and not self.grabberIsMoving and self.grabberMoveState == nil and self.emptyState == Quaderballensammelwagen.EMPTY_NONE;
end;
  
function Quaderballensammelwagen.rotatePlatform(self)
    self.rotatePlatformDirection = 1;
    self:playAnimation("rotatePlatform", 1, nil, true);
    if self.startBalePlace.count > 0 then
        self:playAnimation("rotatePlatformMoveBales"..self.startBalePlace.count, 1,  nil, true);
    end;
    if self.currentBalePlace > 1 then
        -- currentBalePlace is needs to be at the first position
        self:playAnimation("moveBalePlaces", 1, (self.currentBalePlace-1)/table.getn(self.balePlaces), true);
        self:setAnimationStopTime("moveBalePlaces", (self.currentBalePlace)/table.getn(self.balePlaces));
    end;
end;
  
function Quaderballensammelwagen.moveToWorkPosition(self)
    self:playAnimation("baleGrabberTransportToWork", 1, Utils.clamp(self:getAnimationTime("baleGrabberTransportToWork"), 0, 1), true);
    self:playAnimation("closeGrippers", -1, Utils.clamp(self:getAnimationTime("closeGrippers"), 0, 1), true);
  
    if self.startBalePlace.count == 1 then
        self:playAnimation("bale1ToOtherSide", -0.5, nil, true);
    elseif self.startBalePlace.count == 3 then
        self:playAnimation("bale3ToOtherSide", -0.5, nil, true);
    end;
end;
  
function Quaderballensammelwagen.moveToTransportPosition(self)
    self:playAnimation("baleGrabberTransportToWork", -1, Utils.clamp(self:getAnimationTime("baleGrabberTransportToWork"), 0, 1), true);
    self:playAnimation("closeGrippers", 1, Utils.clamp(self:getAnimationTime("closeGrippers"), 0, 1), true);
  
    if self.startBalePlace.count == 1 then
        self:playAnimation("bale1ToOtherSide", 0.5, nil, true);
    --elseif self.startBalePlace.count == 3 then
        --self:playAnimation("bale3ToOtherSide", 0.5, nil, true);
    end;
end;