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

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

DLCBaleloaderFixed.STATE_MAX = 30;

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

function DLCBaleloaderFixed:load(xmlFile)

    self.doStateChange = DLCBaleloaderFixed.doStateChange;
    self.lockBales = DLCBaleloaderFixed.lockBales;

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

    self.loadedBales = {};
    self.loadedBalesCount = 0;
    self.onBaleTrigger = DLCBaleloaderFixed.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 = DLCBaleloaderFixed.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 = DLCBaleloaderFixed.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 DLCBaleloaderFixed: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 DLCBaleloaderFixed:readStream(streamId, connection)

    self.isInWorkPosition = streamReadBool(streamId);

    if self.isInWorkPosition then
        DLCBaleloaderFixed.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 >= DLCBaleloaderFixed.EMPTY_NONE then
        self:doStateChange(DLCBaleloaderFixed.CHANGE_EMPTY_NONE);
        AnimatedVehicle.updateAnimations(self, 99999999);
        if emptyState >= DLCBaleloaderFixed.EMPTY_OPENING then
            self:doStateChange(DLCBaleloaderFixed.CHANGE_EMPTY_START);
            AnimatedVehicle.updateAnimations(self, 99999999);
            if emptyState >= DLCBaleloaderFixed.EMPTY_IN_PROCESS then
                self:doStateChange(DLCBaleloaderFixed.CHANGE_EMPTY_IN_PROCESS);
                AnimatedVehicle.updateAnimations(self, 99999999);
                if emptyState == DLCBaleloaderFixed.EMPTY_CLOSING then
                    self:doStateChange(DLCBaleloaderFixed.CHANGE_EMPTY_CANCEL);
                    AnimatedVehicle.updateAnimations(self, 99999999);
                end;
            end;
        end;
    end;

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

function DLCBaleloaderFixed: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 DLCBaleloaderFixed:mouseEvent(posX, posY, isDown, isUp, button)
end;

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

function DLCBaleloaderFixed: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, v.fillLevel);
            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, v.fillLevel);
            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(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_BUTTON_WORK_TRANSPORT));
            elseif InputBinding.hasEvent(self.emptyButton) then
                g_client:getServerConnection():sendEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_BUTTON_EMPTY));
            elseif InputBinding.hasEvent(self.emptyAbortButton) then
                g_client:getServerConnection():sendEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.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 == DLCBaleloaderFixed.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 ~= DLCBaleloaderFixed.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 = DLCBaleloaderFixed.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 = DLCBaleloaderFixed.getBaleInRange(self, self.baleGrabber.grabNode);
                    if nearestBale ~= nil then
                        g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_GRAB_BALE, networkGetObjectId(nearestBale)), true, nil, self);
                    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(DLCBaleloaderFixedStateEvent: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 == DLCBaleloaderFixed.GRAB_BALE then
                    if not self:getIsAnimationPlaying("baleGrabberMoveToSlider") then
                        --DLCBaleloaderFixed.CHANGE_GRABBED_BALE
                        local usedSlider = nil;
                        for k, slider in pairs(self.freeBaleSliders) do
                            usedSlider = slider;
                            break;
                        end;
                        g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_GRABBED_BALE, nil, usedSlider.animId), true, nil, self);
                    end;
                elseif self.grabberMoveState == DLCBaleloaderFixed.GRABBED_BALE then
                    if not self:getIsAnimationPlaying(self.freeBaleSliders[1].animation) then
                        --DLCBaleloaderFixed.CHANGE_MOVE_TO_POSITION
                        g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_MOVE_TO_POSITION), true, nil, self);
                    end;
                end;
            end;
            if self.emptyState ~= DLCBaleloaderFixed.EMPTY_NONE then
                if self.emptyState == DLCBaleloaderFixed.EMPTY_OPENING then
                    if not self:getIsAnimationPlaying("unloading") then
                        g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_EMPTY_IN_PROCESS), true, nil, self);
                    end;
                elseif self.emptyState == DLCBaleloaderFixed.EMPTY_CLOSING then
                    if not self:getIsAnimationPlaying("unloading") then
                        g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_EMPTY_NONE), true, nil, self);
                    end;
                elseif self.emptyState == DLCBaleloaderFixed.EMPTY_IN_PROCESS then
                    self.emptyBaleForce = 0.0004; -- Changed from 0.0002 to 0.0004 since MR bales need more force to be pushed.
                    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 DLCBaleloaderFixed:draw()
    if self:getIsActiveForInput(true) then
        if self.emptyState == DLCBaleloaderFixed.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 DLCBaleloaderFixed.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 DLCBaleloaderFixed.getBaleInRange(self, refNode)
    local px, py, pz = getWorldTranslation(refNode);
    local showMessage = false;
    local showMessageDistance = 5;
    local nearestDistance = 1.3;
    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 DLCBaleloaderFixed:onDetach()
    self:lockBales(false, true);
    if self.deactivateOnDetach then
        DLCBaleloaderFixed.onDeactivate(self);
    else
        DLCBaleloaderFixed.onDeactivateSounds(self)
    end;

    self.attachedJoint = nil;
end;

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

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

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

function DLCBaleloaderFixed: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;
            local fillLevel = Utils.getNoNil(getXMLFloat(xmlFile, baleKey.."#fillLevel"), 375);

            table.insert(self.balesToLoad, {parentNode=usedSlider.node, filename=filename, bale=usedSlider, translation=translation, rotation=rotation, fillLevel=fillLevel});
        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 fillLevel = Utils.getNoNil(getXMLFloat(xmlFile, baleKey.."#fillLevel"), 375);
            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}, fillLevel=fillLevel});
        end;
        i = i +1;
    end;

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

    AnimatedVehicle.updateAnimations(self, 99999999);

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

    return BaseMission.VEHICLE_LOAD_OK;
end

function DLCBaleloaderFixed: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..'", fillLevel="'..fillLevel..'" />';
            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..'" fillLevel="'..bale.fillLevel..'" />';
                baleNum = baleNum+1;
            end;
        end;
    end;


    return attributes,nodes;
end

function DLCBaleloaderFixed:doStateChange(id, nearestBaleServerId, sliderId)

    if id == DLCBaleloaderFixed.CHANGE_EMPTY_START then
        self:playAnimation("unloading", 1, Utils.clamp(self:getAnimationTime("unloading"), 0, 1), true);
        self.emptyState = DLCBaleloaderFixed.EMPTY_OPENING;
    elseif id == DLCBaleloaderFixed.CHANGE_EMPTY_CANCEL then
        self:playAnimation("unloading", -1, Utils.clamp(self:getAnimationTime("unloading"), 0, 1), true);
        self.emptyState = DLCBaleloaderFixed.EMPTY_CLOSING;
    elseif id == DLCBaleloaderFixed.CHANGE_EMPTY_IN_PROCESS then
        self.emptyState = DLCBaleloaderFixed.EMPTY_IN_PROCESS;
    elseif id == DLCBaleloaderFixed.CHANGE_EMPTY_NONE then
        self.emptyState = DLCBaleloaderFixed.EMPTY_NONE;
    elseif id == DLCBaleloaderFixed.CHANGE_MOVE_TO_TRANSPORT then
        if self.isInWorkPosition then
            self.grabberIsMoving = true;
            self.isInWorkPosition = false;
            -- move to transport position
            DLCBaleloaderFixed.moveToTransportPosition(self);
            if self.baleSliderSoundEnabled then
                stopSample(self.baleSliderSound);
                self.baleSliderSoundEnabled = false;
            end;
        end;
    elseif id == DLCBaleloaderFixed.CHANGE_MOVE_TO_WORK then
        if not self.isInWorkPosition then
            self.grabberIsMoving = true;
            self.isInWorkPosition = true;
            -- move to work position
            DLCBaleloaderFixed.moveToWorkPosition(self);
        end;
    elseif id == DLCBaleloaderFixed.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 = DLCBaleloaderFixed.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 == DLCBaleloaderFixed.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 = DLCBaleloaderFixed.GRABBED_BALE;
        self.grabberMoveState = nil;
        self:playAnimation(usedSlider.animation, 1, nil, true);

    elseif id == DLCBaleloaderFixed.CHANGE_BUTTON_EMPTY then
        -- Server only code
        assert(self.isServer);
        if self.emptyState == DLCBaleloaderFixed.EMPTY_NONE then
            --DLCBaleloaderFixed.CHANGE_EMPTY_START
            if DLCBaleloaderFixed.getAllowsStartUnloading(self) then
                g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_EMPTY_START), true, nil, self);
            end;
        end;
    elseif id == DLCBaleloaderFixed.CHANGE_BUTTON_EMPTY_ABORT then
        -- Server only code
        assert(self.isServer);
        if self.emptyState ~= DLCBaleloaderFixed.EMPTY_NONE then
            --DLCBaleloaderFixed.CHANGE_EMPTY_CANCEL
            g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_EMPTY_CANCEL), true, nil, self);
        end;
    elseif id == DLCBaleloaderFixed.CHANGE_BUTTON_WORK_TRANSPORT then
        -- Server only code
        assert(self.isServer);
        if self.emptyState == DLCBaleloaderFixed.EMPTY_NONE and self.grabberMoveState == nil then
            if self.isInWorkPosition then
                g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_MOVE_TO_TRANSPORT), true, nil, self);
            else
                g_server:broadcastEvent(DLCBaleloaderFixedStateEvent:new(self, DLCBaleloaderFixed.CHANGE_MOVE_TO_WORK), true, nil, self);
            end;
        end;
    elseif id >= DLCBaleloaderFixed.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();

            --DURAL : add the current velocity to the bale
            local velX, velY, velZ = getLinearVelocity(self.components[1].node);
            local speed = 2+math.random(); --between 2 and 3 m/s (7.2 and 9.6kph)
            local velX2, velY2, velZ2 = localDirectionToWorld(self.components[1].node, 0, speed, -speed);
            setLinearVelocity(bale.nodeId, velX+velX2, velY+velY2, velZ+velZ2, 0, 0, 0);
        end;
        self.balesToMount[usedSlider.currentBale] = nil;

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

function DLCBaleloaderFixed.updateBalePlacesAnimations(self)

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

end;

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

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

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

function DLCBaleloaderFixed:lockBales(doLock, noEventSend)

    if self.lockState ~= doLock then
        if self.isServer and noEventSend == nil or noEventSend == false then
            g_server:broadcastEvent(DLCBaleloaderFixedLockEvent: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 DLCBaleloaderFixed: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;


DLCBaleloaderFixedStateEvent = {};
DLCBaleloaderFixedStateEvent_mt = Class(DLCBaleloaderFixedStateEvent, Event);

InitEventClass(DLCBaleloaderFixedStateEvent, "DLCBaleloaderFixedStateEvent");

function DLCBaleloaderFixedStateEvent:emptyNew()
    local self = Event:new(DLCBaleloaderFixedStateEvent_mt);
    return self;
end;

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

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

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

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

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

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

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

DLCBaleloaderFixedLockEvent = {};
DLCBaleloaderFixedLockEvent_mt = Class(DLCBaleloaderFixedLockEvent, Event);

InitEventClass(DLCBaleloaderFixedLockEvent, "DLCBaleloaderFixedLockEvent");

function DLCBaleloaderFixedLockEvent:emptyNew()
    local self = Event:new(DLCBaleloaderFixedLockEvent_mt);
    return self;
end;

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

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

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

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