--
-- Combine
-- This is the specialization for combines
--
-- @author  Stefan Geiger
-- @date  30/11/08
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.
--
-- Edited for Lexion 770 by Defender

CombineSetWorkModeEvent = {};
CombineSetWorkModeEvent_mt = Class(CombineSetWorkModeEvent, Event);

InitEventClass(CombineSetWorkModeEvent, "CombineSetWorkModeEvent");

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

function CombineSetWorkModeEvent:new(object, enabled, turbo)
    local self = CombineSetWorkModeEvent:emptyNew()
    self.object = object;
    self.enabled = enabled;
    self.turbo = turbo;
    return self;
end;

function CombineSetWorkModeEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
    self.enabled = streamReadBool(streamId);
    self.turbo = streamReadBool(streamId);
    self.object = networkGetObject(id);
    self:run(connection);
end;

function CombineSetWorkModeEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
    streamWriteBool(streamId, self.enabled);
    streamWriteBool(streamId, self.turbo);
end;

function CombineSetWorkModeEvent:run(connection)
    self.object:setWorkMode(self.enabled, true, self.turbo);
    if not connection:getIsServer() then
        g_server:broadcastEvent(CombineSetWorkModeEvent:new(self.object, self.enabled, self.turbo), nil, connection, self.object);
    end;
end;

CombineSetCrusherEvent = {};
CombineSetCrusherEvent_mt = Class(CombineSetCrusherEvent, Event);

InitEventClass(CombineSetCrusherEvent, "CombineSetCrusherEvent");

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

function CombineSetCrusherEvent:new(object, enabled)
    local self = CombineSetCrusherEvent:emptyNew()
    self.object = object;
    self.enabled = enabled;
    return self;
end;

function CombineSetCrusherEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
    self.enabled = streamReadBool(streamId);
    self.object = networkGetObject(id);
    self:run(connection);
end;

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

function CombineSetCrusherEvent:run(connection)
    self.object:setCrusher(self.enabled, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(CombineSetCrusherEvent:new(self.object, self.enabled), nil, connection, self.object);
    end;
end;


Lexion770SetChopperEnableEvent = {};
Lexion770SetChopperEnableEvent_mt = Class(Lexion770SetChopperEnableEvent, Event);

InitEventClass(Lexion770SetChopperEnableEvent, "Lexion770SetChopperEnableEvent");

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

function Lexion770SetChopperEnableEvent:new(object, enabled, fruitType)
    local self = Lexion770SetChopperEnableEvent:emptyNew()
    self.object = object;
    self.enabled = enabled;
    self.fruitType = fruitType;
    return self;
end;

function Lexion770SetChopperEnableEvent:readStream(streamId, connection)
    self.object = networkGetObject(streamReadInt32(streamId));
    self.enabled = streamReadBool(streamId);
    self.fruitType = streamReadUIntN(streamId, FruitUtil.sendNumBits);
    self:run(connection);
end;

function Lexion770SetChopperEnableEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
    streamWriteBool(streamId, self.enabled);
    streamWriteUIntN(streamId, self.fruitType, FruitUtil.sendNumBits);
end;

function Lexion770SetChopperEnableEvent:run(connection)
    Lexion770SetChopperEnableEvent.execute(self.object, self.enabled, self.fruitType);
end;

function Lexion770SetChopperEnableEvent.execute(object, enabled, fruitType)
    if object.currentChopperParticleSystem ~= nil then
        Utils.setEmittingState(object.currentChopperParticleSystem, false);
    end;
    object.currentChopperParticleSystem = object.chopperParticleSystems[fruitType];
    if object.currentChopperParticleSystem == nil then
        object.currentChopperParticleSystem = object.defaultChopperParticleSystem;
    end;

    if object.currentGrainTankParticleSystem ~= nil then
        Utils.setEmittingState(object.currentGrainTankParticleSystem, false);
    end;
    object.currentGrainTankParticleSystem = object.grainTankParticleSystems[fruitType];
    if object.currentGrainTankParticleSystem == nil then
        object.currentGrainTankParticleSystem = object.defaultGrainTankParticleSystem;
    end;

    if enabled then
        Utils.setEmittingState(object.currentChopperParticleSystem, true);
        Utils.setEmittingState(object.currentGrainTankParticleSystem, true);
    else
        Utils.setEmittingState(object.currentChopperParticleSystem, false);
        Utils.setEmittingState(object.currentGrainTankParticleSystem, false);
    end;
end;

Lexion770SetStrawEnableEvent = {};
Lexion770SetStrawEnableEvent_mt = Class(Lexion770SetStrawEnableEvent, Event);

InitEventClass(Lexion770SetStrawEnableEvent, "Lexion770SetStrawEnableEvent");

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

function Lexion770SetStrawEnableEvent:new(object, enabled, fruitType)
    local self = Lexion770SetStrawEnableEvent:emptyNew()
    self.object = object;
    self.enabled = enabled;
    self.fruitType = fruitType;
    return self;
end;

function Lexion770SetStrawEnableEvent:readStream(streamId, connection)
    self.object = networkGetObject(streamReadInt32(streamId));
    self.enabled = streamReadBool(streamId);
    self.fruitType = streamReadUIntN(streamId, FruitUtil.sendNumBits);
    self:run(connection);
end;

function Lexion770SetStrawEnableEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
    streamWriteBool(streamId, self.enabled);
    streamWriteUIntN(streamId, self.fruitType, FruitUtil.sendNumBits);
end;

function Lexion770SetStrawEnableEvent:run(connection)
    Lexion770SetStrawEnableEvent.execute(self.object, self.enabled, self.fruitType);
end;

function Lexion770SetStrawEnableEvent.execute(object, enabled, fruitType)
    if object.currentStrawParticleSystem ~= nil then
        Utils.setEmittingState(object.currentStrawParticleSystem, false);
    end;
    object.currentStrawParticleSystem = object.strawParticleSystems[fruitType];
    if object.currentStrawParticleSystem == nil then
        object.currentStrawParticleSystem = object.defaultStrawParticleSystem;
    end;

    if object.currentGrainTankParticleSystem ~= nil then
        Utils.setEmittingState(object.currentGrainTankParticleSystem, false);
    end;
    object.currentGrainTankParticleSystem = object.grainTankParticleSystems[fruitType];
    if object.currentGrainTankParticleSystem == nil then
        object.currentGrainTankParticleSystem = object.defaultGrainTankParticleSystem;
    end;

    if enabled then
        Utils.setEmittingState(object.currentStrawParticleSystem, true);
        Utils.setEmittingState(object.currentGrainTankParticleSystem, true);
    else
        Utils.setEmittingState(object.currentStrawParticleSystem, false);
        Utils.setEmittingState(object.currentGrainTankParticleSystem, false);
    end;
end;

Lexion770 = {};

function Lexion770.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Steerable, specializations);
end;

function Lexion770:load(xmlFile)
    self.allowGrainTankFruitType = Lexion770.allowGrainTankFruitType;
    self.emptyGrainTankIfLowFillLevel = Lexion770.emptyGrainTankIfLowFillLevel;
    self.setGrainTankFillLevel = SpecializationUtil.callSpecializationsFunction("setGrainTankFillLevel");
    self.workModeStart = SpecializationUtil.callSpecializationsFunction("workModeStart");
    self.workModeEnd = SpecializationUtil.callSpecializationsFunction("workModeEnd");
    self.setWorkMode = SpecializationUtil.callSpecializationsFunction("setWorkMode");

    self.setCrusher = SpecializationUtil.callSpecializationsFunction("setCrusher");
    self.crusherStart = SpecializationUtil.callSpecializationsFunction("crusherStart");
    self.crusherEnd = SpecializationUtil.callSpecializationsFunction("crusherEnd");

    self.startThreshing = SpecializationUtil.callSpecializationsFunction("startThreshing");
    self.stopThreshing = SpecializationUtil.callSpecializationsFunction("stopThreshing");
    self.setIsThreshing = SpecializationUtil.callSpecializationsFunction("setIsThreshing");
    self.setPipeOpening = SpecializationUtil.callSpecializationsFunction("setPipeOpening");
    self.setPipeState = SpecializationUtil.callSpecializationsFunction("setPipeState");
    self.getFruitTypeAndFillLevelToUnload = Lexion770.getFruitTypeAndFillLevelToUnload;
    self.findAutoAimTrailerToUnload = Lexion770.findAutoAimTrailerToUnload;
    self.findTrailerToUnload = Lexion770.findTrailerToUnload;
    self.findTrailerRaycastCallback = Lexion770.findTrailerRaycastCallback;
    self.getIshreshingAllowed = Lexion770.getIshreshingAllowed;

    if self.isClient then
        local threshingStartSound = getXMLString(xmlFile, "vehicle.threshingStartSound#file");
        if threshingStartSound ~= nil and threshingStartSound ~= "" then
            threshingStartSound = Utils.getFilename(threshingStartSound, self.baseDirectory);
            self.threshingStartSound = createSample("threshingStartSound");
            loadSample(self.threshingStartSound, threshingStartSound, false);
            self.threshingStartSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingStartSound#pitchOffset"), 1);
            self.threshingStartSoundPitchScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingStartSound#pitchScale"), 0);
            self.threshingStartSoundPitchMax = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingStartSound#pitchMax"), 2.0);
        end;
        self.threshingSoundActive = false;

        local threshingSound = getXMLString(xmlFile, "vehicle.threshingSound#file");
        if threshingSound ~= nil and threshingSound ~= "" then
            threshingSound = Utils.getFilename(threshingSound, self.baseDirectory);
            self.threshingSound = createSample("threshingSound");
            loadSample(self.threshingSound, threshingSound, false);
            self.threshingSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingSound#pitchOffset"), 1);
            self.threshingSoundPitchScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingSound#pitchScale"), 0);
            self.threshingSoundPitchMax = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingSound#pitchMax"), 2.0);
        end;

        local threshingStopSound = getXMLString(xmlFile, "vehicle.threshingStopSound#file");
        if threshingStopSound ~= nil and threshingStopSound ~= "" then
            threshingStopSound = Utils.getFilename(threshingStopSound, self.baseDirectory);
            self.threshingStopSound = createSample("threshingStopSound");
            loadSample(self.threshingStopSound, threshingStopSound, false);
            self.threshingStopSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingStopSound#pitchOffset"), 1);
            self.threshingStopSoundPitchScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingStopSound#pitchScale"), 0);
            self.threshingStopSoundPitchMax = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.threshingStopSound#pitchMax"), 2.0);
        end;

        local pipeSound = getXMLString(xmlFile, "vehicle.pipeSound#file");
        if pipeSound ~= nil and pipeSound ~= "" then
            pipeSound = Utils.getFilename(pipeSound, self.baseDirectory);
            self.pipeSound = createSample("pipeSound");
            loadSample(self.pipeSound, pipeSound, false);
            self.pipeSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.pipeSound#pitchOffset"), 1);
            self.pipeSoundPitchScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.pipeSound#pitchScale"), 0);
            self.pipeSoundPitchMax = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.pipeSound#pitchMax"), 2.0);
        end;
    end;

    self.chopperBlind = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperBlind#index"));

    self.pipeParticleSystems = {};
    self.grainTankParticleSystems = {};

    self.pipeNodes = {};
    self.numPipeStates = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.pipe#numStates"), 0);
    self.currentPipeState = 1;
    self.targetPipeState = 1;
    self.pipeStateIsUnloading = {};
    self.pipeStateIsAutoAiming = {};
    local unloadingPipeStates = Utils.getVectorNFromString(getXMLString(xmlFile, "vehicle.pipe#unloadingStates"));
    if unloadingPipeStates ~= nil then
        for i=1, table.getn(unloadingPipeStates) do
            if unloadingPipeStates[i] ~= nil then
                self.pipeStateIsUnloading[unloadingPipeStates[i] ] = true;
            end;
        end;
    end;
    local autoAimPipeStates = Utils.getVectorNFromString(getXMLString(xmlFile, "vehicle.pipe#autoAimStates"));
    if autoAimPipeStates ~= nil then
        for i=1, table.getn(autoAimPipeStates) do
            if autoAimPipeStates[i] ~= nil then
                self.pipeStateIsAutoAiming[autoAimPipeStates[i] ] = true;
            end;
        end;
    end;
    local i = 0;
    while true do
        local key = string.format("vehicle.pipe.node(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
        local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index"));
        if node ~= nil then
            local entry = {};
            entry.node = node;
            entry.autoAimXRotation = Utils.getNoNil(getXMLBool(xmlFile, key.."#autoAimXRotation"), false);
            entry.autoAimYRotation = Utils.getNoNil(getXMLBool(xmlFile, key.."#autoAimYRotation"), false);
            entry.autoAimInvertZ = Utils.getNoNil(getXMLBool(xmlFile, key.."#autoAimInvertZ"), false);
            entry.states = {};
            for state=1,self.numPipeStates do
                local stateKey = key..string.format(".state%d", state);
                entry.states[state] = {};
                local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, stateKey.."#translation"));
                if x == nil or y == nil or z == nil then
                    x,y,z = getTranslation(node);
                end;
                entry.states[state].translation = {x,y,z};
                local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, stateKey.."#rotation"));
                if x == nil or y == nil or z == nil then
                    x,y,z = getRotation(node);
                else
                    x,y,z = math.rad(x),math.rad(y),math.rad(z);
                end;
                entry.states[state].rotation = {x,y,z};
            end;
            local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#translationSpeeds"));
            if x ~= nil and y ~= nil and z ~= nil then
                x,y,z = x*0.001,y*0.001,z*0.001;
                if x ~= 0 or y ~= 0 or z ~= 0 then
                    entry.translationSpeeds = {x,y,z};
                end;
            end;
            local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotationSpeeds"));
            if x ~= nil and y ~= nil and z ~= nil then
                x,y,z = math.rad(x)*0.001,math.rad(y)*0.001,math.rad(z)*0.001;
                if x ~= 0 or y ~= 0 or z ~= 0 then
                    entry.rotationSpeeds = {x,y,z};
                end;
            end;

            local x,y,z = getTranslation(node);
            entry.curTranslation = {x,y,z};
            local x,y,z = getRotation(node);
            entry.curRotation = {x,y,z};
            table.insert(self.pipeNodes, entry);
        end;
        i = i + 1;
    end;
    if table.getn(self.pipeNodes) == 0 then
        -- use the old format

        local node = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pipe#index"));
        if node ~= nil then
            self.numPipeStates = 2;

            local entry = {};
            entry.node = node;
            entry.states = {};
            entry.states[1] = {};
            entry.states[2] = {};

            local x,y,z = getRotation(node);
            entry.states[1].rotation = {0,0,z};
            entry.states[2].rotation = {math.rad(10),math.rad(-90),z};

            entry.rotationSpeeds = {0.00006, 0.0006, 0};

            local x,y,z = getRotation(node);
            entry.curRotation = {x,y,z};

            table.insert(self.pipeNodes, entry);

            self.pipeStateIsUnloading[2] = true;
        end;
    end;

    local pipeFlapLid = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pipeFlapLid#index"));
    if pipeFlapLid ~= nil then
        if self.numPipeStates ~= 2 then
            print("Error: pipeFlapLid is only support with 2 pipe states in '"..self.configFileName.."'.");
        else
            local entry = {};
            entry.node = pipeFlapLid;
            entry.states = {};
            entry.states[1] = {};
            entry.states[2] = {};

            entry.states[1].rotation = {0,0,0};
            entry.states[2].rotation = {0,math.rad(-90),0};

            entry.rotationSpeeds = {0, 0.0006, 0};

            local x,y,z = getRotation(pipeFlapLid);
            entry.curRotation = {x,y,z};

            table.insert(self.pipeNodes, entry);
        end;
    end;

    if table.getn(self.pipeNodes) > 0 then

        self.pipeRaycastNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pipe#raycastNodeIndex"));

        -- load the pipe particle system for each fruit type
        local i = 0;
        while true do
            local key = string.format("vehicle.pipeParticleSystems.pipeParticleSystem(%d)", i);
            local t = getXMLString(xmlFile, key .. "#type");
            if t == nil then
                break;
            end;

            local desc = FruitUtil.fruitTypes[t];
            if desc ~= nil then
                local currentPS = {};

                local particleNode = Utils.loadParticleSystem(xmlFile, currentPS, key, self.components, false, "$data/vehicles/particleSystems/wheatParticleSystem.i3d", self.baseDirectory, self.pipeNodes[1].node);
                self.pipeParticleSystems[desc.index] = currentPS;
                if self.defaultPipeParticleSystem == nil then
                    self.defaultPipeParticleSystem = currentPS;
                end;

                if self.pipeRaycastNode == nil then
                    self.pipeRaycastNode = particleNode;
                end;
            end;
            i = i + 1;
        end;

        if self.pipeRaycastNode == nil then
            self.pipeRaycastNode = self.components[1].node;
        end;
    end;
    self.pipeRaycastDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.pipe#raycastDistance"), 7);

    self.convertedFruits = {};
    local i = 0;
    while true do
        local key = string.format("vehicle.convertedFruits.convertedFruit(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
        local inputType = getXMLString(xmlFile, key .. "#input");
        local outputType = getXMLString(xmlFile, key .. "#output");

        if inputType ~= nil and outputType ~= nil then
            local inputDesc = FruitUtil.fruitTypes[inputType];
            local outputDesc = FruitUtil.fruitTypes[outputType];
            if inputDesc ~= nil and outputDesc ~= nil then
                self.convertedFruits[inputDesc.index] = outputDesc.index;
            end;
        end;

        i = i + 1;
    end;

    self.allowsThreshing = true;

    self.pipeLight = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pipeLight#index"));

    self.rotorFan = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.rotorFan#index"));
    
    self.pipeScroll = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.pipeScroll#index"));
    
    self.pipeScrollPSRoot = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.grainTankParticleSystems#node"));
    
    if self.pipeScrollPSRoot ~= nil then
      local i = 0;
      while true do
        local key = string.format("vehicle.grainTankParticleSystems.grainTankParticleSystem(%d)", i);
        local t = getXMLString(xmlFile, key .. "#type");
        if t == nil then
            break;
        end;

        local desc = FruitUtil.fruitTypes[t];
        if desc ~= nil then
            local currentPS = {};

            local particleNode = Utils.loadParticleSystem(xmlFile, currentPS, key, self.components, false, "Particel/LexionKorntankGetreide.i3d", self.baseDirectory, self.pipeScrollPSRoot);
            self.grainTankParticleSystems[desc.index] = currentPS;
            if self.grainTankParticleSystems == nil then
                self.grainTankParticleSystems = currentPS;
            end;
        end;
        i = i + 1;
      end;
    end;
    
    self.grainTankCapacity = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.grainTankCapacity"), 200);
    self.grainTankUnloadingCapacity = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.grainTankUnloadingCapacity"), 10);
    self.grainTankCrowded = false;

    self.allowThreshingDuringRain = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.allowThreshingDuringRain"), false);
    
	self.wiper = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.wiper#index"));
	self.strawSheet = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.strawSheet#index"));

	self.chopper = {};
	self.chopper.node = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart1#transportNode"));
	self.chopper.zylinderNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart1#transportZylRot"));
	self.chopper.zylinderPunch = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart1#transportZylTrans"));
	self.chopper.translationPunch = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart1#fixPointTrans"));
	self.chopper.rotationPunch = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart1#fixPointRot"));
	local ax1, ay1, az1 = getWorldTranslation(self.chopper.zylinderPunch);
    local bx1, by1, bz1 = getWorldTranslation(self.chopper.translationPunch);
    self.chopper.punchDistance = Utils.vector3Length(ax1-bx1, ay1-by1, az1-bz1);
	self.chopper.strawNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart2#strawNode"));
	self.chopper.strawNode2 = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart2#strawNode2"));
	self.chopper.zylinderNode2 = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart2#strawZylRot"));
	self.chopper.zylinderPunch2 = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart2#strawZylTrans"));
	self.chopper.translationPunch2 = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart2#fixPointTrans"));
	self.chopper.rotationPunch2 = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart2#fixPointRot"));
	local ax2, ay2, az2 = getWorldTranslation(self.chopper.zylinderPunch2);
    local bx2, by2, bz2 = getWorldTranslation(self.chopper.translationPunch2);
    self.chopper.punchDistance2 = Utils.vector3Length(ax2-bx2, ay2-by2, az2-bz2);
	self.chopper.drumStrawLeft = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart3#drumStrawLeft"));
	self.chopper.drumStrawRight = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart3#drumStrawRight"));
	self.chopper.sheetLeftSmall = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart3#sheetLeftSmall"));
	self.chopper.sheetLeftBig = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart3#sheetLeftBig"));
	self.chopper.sheetRightSmall = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart3#sheetRightSmall"));
	self.chopper.sheetRightBig = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.chopperPart3#sheetRightBig"));


    local numLights = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.workLights#count"), 0);
    self.workLights = {};
    self.realLights = {};
    for i=1, numLights do
        local lightnamei = string.format("vehicle.workLights.light%d", i);
        local node = Utils.indexToObject(self.components, getXMLString(xmlFile, lightnamei .. "#index"));
        local node1 = Utils.indexToObject(self.components, getXMLString(xmlFile, lightnamei .. "#realLight"));
        if node ~= nil then
            setVisibility(node, false);
            table.insert(self.workLights, node);
            if node1~=nil then
              table.insert(self.realLights, node1)
            end;
        end;
    end;

    self.workLightCoronas = {};
    local i = 0;
    while true do
        local key = string.format("vehicle.workLightCoronas.lightCorona(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
        local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#index"));
        if node ~= nil then
            setVisibility(node, false);
            table.insert(self.workLightCoronas, node);
        end;
        i = i + 1;
    end;

    self.workLightCones = {};
    local i = 0;
    while true do
        local key = string.format("vehicle.workLightCones.lightCone(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
        local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#index"));
        if node ~= nil then
            setVisibility(node, false);
            table.insert(self.workLightCones, node);
        end;
        i = i + 1;
    end;

    if self.isClient then
      local tankAnimRootNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.tankAnim#rootNode"));
      self.tankAnimCharSet = 0;
      if tankAnimRootNode ~= nil and tankAnimRootNode ~= 0 then
        self.tankAnimCharSet = getAnimCharacterSet(tankAnimRootNode);
        if self.tankAnimCharSet ~= 0 then
          local clip = getAnimClipIndex(self.tankAnimCharSet, getXMLString(xmlFile, "vehicle.tankAnim#animationClip"));
          assignAnimTrackClip(self.tankAnimCharSet, 0, clip);
          setAnimTrackLoopState(self.tankAnimCharSet, 0, false);
          self.tankAnimSpeedScale = 1;
          self.tankAnimDuration = getAnimClipDuration(self.tankAnimCharSet, clip);
        end;
      end;
    
        -- grain planes
        self.grainTankPlanes = {};
        local i = 0;
        while true do
            local key = string.format("vehicle.grainTankPlanes.grainTankPlane(%d)", i);
            if not hasXMLProperty(xmlFile, key) then
                break;
            end;
            local grainTankPlane = {};
            grainTankPlane.nodes = {};
            local fruitType = getXMLString(xmlFile, key.."#type");
            if fruitType ~= nil then
                local nodeI = 0;
                while true do
                    local nodeKey = key..string.format(".node(%d)", nodeI);
                    if not hasXMLProperty(xmlFile, nodeKey) then
                        break;
                    end;
                    local node = Utils.indexToObject(self.components, getXMLString(xmlFile, nodeKey.."#index"));
                    if node ~= nil then
                        local defaultX, defaultY, defaultZ = getTranslation(node);
                        local defaultRX, defaultRY, defaultRZ = getRotation(node);
                        setVisibility(node, false);

                        local animCurve = AnimCurve:new(linearInterpolatorTransRotScale);
                        local keyI = 0;
                        while true do
                            local animKey = nodeKey..string.format(".key(%d)", keyI);
                            local keyTime = getXMLFloat(xmlFile, animKey.."#time");
                            local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, animKey.."#translation"));
                            if y == nil then
                                y = getXMLFloat(xmlFile, animKey.."#y");
                            end;
                            local rx,ry,rz = Utils.getVectorFromString(getXMLString(xmlFile, animKey.."#rotation"));
                            local sx,sy,sz = Utils.getVectorFromString(getXMLString(xmlFile, animKey.."#scale"));
                            if keyTime == nil then
                                break;
                            end;
                            local x = Utils.getNoNil(x, defaultX);
                            local y = Utils.getNoNil(y, defaultY);
                            local z = Utils.getNoNil(z, defaultZ);
                            local rx = Utils.getNoNil(rx, defaultRX);
                            local ry = Utils.getNoNil(ry, defaultRY);
                            local rz = Utils.getNoNil(rz, defaultRZ);
                            local sx = Utils.getNoNil(sx, 1);
                            local sy = Utils.getNoNil(sy, 1);
                            local sz = Utils.getNoNil(sz, 1);
                            animCurve:addKeyframe({x=x, y=y, z=z, rx=rx, ry=ry, rz=rz, sx=sx, sy=sy, sz=sz, time = keyTime});
                            keyI = keyI +1;
                        end;
                        if keyI == 0 then
                            local minY, maxY = Utils.getVectorFromString(getXMLString(xmlFile, nodeKey.."#minMaxY"));
                            local minY = Utils.getNoNil(minY, defaultY);
                            local maxY = Utils.getNoNil(maxY, defaultY);
                            animCurve:addKeyframe({x=defaultX, y=minY, z=defaultZ, rx=defaultRX, ry=defaultRY, rz=defaultRZ, sx=1, sy=1, sz=1, time = 0});
                            animCurve:addKeyframe({x=defaultX, y=maxY, z=defaultZ, rx=defaultRX, ry=defaultRY, rz=defaultRZ, sx=1, sy=1, sz=1, time = 1});
                        end;
                        table.insert(grainTankPlane.nodes, {node=node, animCurve = animCurve});
                    end;
                    nodeI = nodeI +1;
                end;
                if table.getn(grainTankPlane.nodes) > 0 then
                    if self.defaultGrainTankPlane == nil then
                        self.defaultGrainTankPlane = grainTankPlane;
                    end;
                    self.grainTankPlanes[fruitType] = grainTankPlane;
                end;
            end;
            i = i +1;
        end;
        if self.defaultGrainTankPlane==nil then
            self.grainTankPlanes = nil;
        end;

        if self.grainTankPlanes == nil then
            if hasXMLProperty(xmlFile, "vehicle.grainTankPlane.node") then
                print("Warning: '"..self.configFileName.. "' uses old grainTankPlane format, which is not supported anymore.");
            end;
        end;

        -- chopper particle system
        self.chopperParticleSystems = {};
        local i = 0;
        while true do
            local key = string.format("vehicle.chopperParticleSystems.chopperParticleSystem(%d)", i);
            local t = getXMLString(xmlFile, key .. "#type");
            if t == nil then
                break;
            end;
            local desc = FruitUtil.fruitTypes[t];
            if desc ~= nil then
                local currentPS = {};

                local particleNode = Utils.loadParticleSystem(xmlFile, currentPS, key, self.components, false, "$data/vehicles/particleSystems/threshingChopperParticleSystem.i3d", self.baseDirectory);
                self.chopperParticleSystems[desc.index] = currentPS;
                if self.defaultChopperParticleSystem == nil then
                    self.defaultChopperParticleSystem = currentPS;
                end;
            end;
            i = i + 1;
        end;

        self.chopperToggleTime = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.chopperParticleSystems#toggleTime"), 2500);
        self.chopperEnableTime = nil;
        self.chopperDisableTime = nil;

        -- start particle system
        self.strawParticleSystems = {};
        local i = 0;
        while true do
            local key = string.format("vehicle.strawParticleSystems.strawParticleSystem(%d)", i);
            local t = getXMLString(xmlFile, key .. "#type");
            if t == nil then
                break;
            end;
            local desc = FruitUtil.fruitTypes[t];
            if desc ~= nil then
                local currentPS = {};

                local particleNode = Utils.loadParticleSystem(xmlFile, currentPS, key, self.components, false, "$data/vehicles/particleSystems/threshingStrawParticleSystem.i3d", self.baseDirectory);
                self.strawParticleSystems[desc.index] = currentPS;
                if self.defaultStrawParticleSystem == nil then
                    self.defaultStrawParticleSystem = currentPS;
                end;
            end;
            i = i + 1;
        end;
    end;

    self.strawToggleTime = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.strawParticleSystems#toggleTime"), 2500);
    self.strawEnableTime = nil;
    self.strawDisableTime = nil;

    self.strawEmitState = false;
    self.autoBeacon=false;


    self.combineSize = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.combineSize"), 1);

    local numStrawAreas = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.strawAreas#count"), 0);
    self.strawAreas = {}
    for i=1, numStrawAreas do
        local area = {};
        local areanamei = string.format("vehicle.strawAreas.strawArea%d", i);
        area.start = Utils.indexToObject(self.components, getXMLString(xmlFile, areanamei .. "#startIndex"));
        area.width = Utils.indexToObject(self.components, getXMLString(xmlFile, areanamei .. "#widthIndex"));
        area.height = Utils.indexToObject(self.components, getXMLString(xmlFile, areanamei .. "#heightIndex"));
        table.insert(self.strawAreas, area);
    end;
    self.workMode = false;
    self.crusherEnabled = false;
    self.isThreshing = false;
    self.chopperActivated = false;
    self.defaultChopperState = false;
    self.hasStraw = false;
    --self.pipeOpening = false;
    --self.pipeOpen = false;
    --self.pipeClose = true;
    self.pipeIsUnloading = false;
    self.pipeParticleActivated = false;
    self.pipeParticleDeactivateTime = 0;

    self.moveStrawSmall = -0.07;
	  self.moveStrawBig = -0.07;
	  self.maxSpeed = 0;

    --[[if self.isServer then
        self.sentPipeOpening = self.pipeOpening;
    end;]]

    self.threshingScale = 1;

    self.grainTankFruitTypes = {};
    self.grainTankFruitTypes[FruitUtil.FRUITTYPE_UNKNOWN] = true;

    local fruitTypes = getXMLString(xmlFile, "vehicle.grainTankFruitTypes#fruitTypes");
    if fruitTypes ~= nil then
        local types = Utils.splitString(" ", fruitTypes);
        for k,v in pairs(types) do
            local desc = FruitUtil.fruitTypes[v];
            if desc ~= nil then
                self.grainTankFruitTypes[desc.index] = true;
            end;
        end;
    end;

    self.currentGrainTankFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
    self.grainTankFillLevel = 0;

    self.grainTankTempFillLevel = 0;
    self.grainTankTempFruitType = FruitUtil.FRUITTYPE_UNKNOWN;

    self.minThreshold = 0.05;

    self.speedDisplayScale = 1.0;
    self.drawFillLevel = true;

    self.attachedCutters = {};
    self.numAttachedCutters = 0;

    self.lastArea = 0;
    self.lastFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
    self.lastValidFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
    self.lastOutputFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
    self.lastValidOutputFruitType = FruitUtil.FRUITTYPE_UNKNOWN;

    self.combineDirtyFlag = self:getNextDirtyFlag();


    self:setGrainTankFillLevel(0.0, FruitUtil.FRUITTYPE_UNKNOWN);
end;

function Lexion770:delete()

    for k,v in pairs(self.pipeParticleSystems) do
        Utils.deleteParticleSystem(v);
    end;

    for k,v in pairs(self.grainTankParticleSystems) do
        Utils.deleteParticleSystem(v);
    end;

    for k,v in pairs(self.chopperParticleSystems) do
        Utils.deleteParticleSystem(v);
    end;
    for k,v in pairs(self.strawParticleSystems) do
        Utils.deleteParticleSystem(v);
    end;

    if self.threshingStartSound ~= nil then
        delete(self.threshingStartSound);
    end;
    if self.threshingSoundActive then
        delete(self.threshingSound);
        self.threshingSoundActive = false;
    end;
    if self.threshingStopSound ~= nil then
        delete(self.threshingStopSound);
    end;
    if self.pipeSound ~= nil then
        delete(self.pipeSound);
    end;

end;

function Lexion770:readStream(streamId, connection)
    local fillLevel = streamReadFloat32(streamId);
    local fillType = streamReadUIntN(streamId, FruitUtil.sendNumBits);
    self.pipeParticleActived = streamReadBool(streamId);
    self.pipeIsUnloading = streamReadBool(streamId);
    local pipeState = streamReadUIntN(streamId, 3);
    local workMode = streamReadBool(streamId);
    local crusher = streamReadBool(streamId);
    local isThreshing = streamReadBool(streamId);
    self:setGrainTankFillLevel(fillLevel, fillType);
    self:setPipeState(pipeState, true);
    self:setWorkMode(workMode, true, true);
    self:setCrusher(crusher, true);
    self:setIsThreshing(isThreshing, true);

    local chopperPSenabled = streamReadBool(streamId);
    local chopperPSFruitType streamReadUIntN(streamId, FruitUtil.sendNumBits);
    local strawPSenabled = streamReadBool(streamId);
    local strawPSFruitType streamReadUIntN(streamId, FruitUtil.sendNumBits);

    Lexion770SetChopperEnableEvent.execute(self, chopperPSenabled, chopperPSFruitType);
    Lexion770SetStrawEnableEvent.execute(self, strawPSenabled, strawPSFruitType);


    self.lastValidFruitType = streamReadUIntN(streamId, FruitUtil.sendNumBits);
    self.lastValidOutputFruitType = self.lastValidFruitType;
    if self.convertedFruits[self.lastValidFruitType] ~= nil then
        self.lastValidOutputFruitType = self.convertedFruits[self.lastValidFruitType];
    end;

    if self.lastValidFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
        local fruitDesc = FruitUtil.fruitIndexToDesc[self.lastValidFruitType];
        self.hasStraw = fruitDesc.hasStraw;
        if fruitDesc.hasStraw then
            self.chopperActivated = self.crusherEnabled;
        else
            self.chopperActivated = true;
        end;
    else
        self.chopperActivated = self.crusherEnabled;
    end;
end;

function Lexion770:writeStream(streamId, connection)
    streamWriteFloat32(streamId, self.grainTankFillLevel);
    streamWriteUIntN(streamId, self.currentGrainTankFruitType, FruitUtil.sendNumBits);
    streamWriteBool(streamId, self.pipeParticleActived);
    streamWriteBool(streamId, self.pipeIsUnloading);
    streamWriteUIntN(streamId, self.targetPipeState, 3);
    streamWriteBool(streamId, self.workMode);
    streamWriteBool(streamId, self.crusherEnabled);
    streamWriteBool(streamId, self.isThreshing);

    streamWriteBool(streamId, self.chopperPSenabled);
    streamWriteUIntN(streamId, self.chopperPSFruitType, FruitUtil.sendNumBits);
    streamWriteBool(streamId, self.strawPSenabled);
    streamWriteUIntN(streamId, self.strawPSFruitType, FruitUtil.sendNumBits);

    streamWriteUIntN(streamId, self.lastValidFruitType, FruitUtil.sendNumBits);
end;

function Lexion770:readUpdateStream(streamId, timestamp, connection)
    if connection:getIsServer() then
        if streamReadBool(streamId) then
            local fillLevel = streamReadFloat32(streamId);
            local fillType = streamReadUIntN(streamId, FruitUtil.sendNumBits);

            self:setGrainTankFillLevel(fillLevel, fillType);

            self.lastValidFruitType = streamReadUIntN(streamId, FruitUtil.sendNumBits);
            self.lastValidOutputFruitType = self.lastValidFruitType;
            if self.convertedFruits[self.lastValidFruitType] ~= nil then
                self.lastValidOutputFruitType = self.convertedFruits[self.lastValidFruitType];
            end;

            if self.lastValidFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
                local fruitDesc = FruitUtil.fruitIndexToDesc[self.lastValidFruitType];
                self.hasStraw = fruitDesc.hasStraw;
                if fruitDesc.hasStraw then
                    self.chopperActivated = self.crusherEnabled;
                else
                    self.chopperActivated = true;
                end;
            else
                self.chopperActivated = self.crusherEnabled;
            end;
        end;
    end;
end;

function Lexion770:writeUpdateStream(streamId, connection, dirtyMask)
    if not connection:getIsServer() then
        if streamWriteBool(streamId, bitAND(dirtyMask, self.combineDirtyFlag) ~= 0) then
            streamWriteFloat32(streamId, self.grainTankFillLevel);
            streamWriteUIntN(streamId, self.currentGrainTankFruitType, FruitUtil.sendNumBits);
            streamWriteUIntN(streamId, self.lastValidFruitType, FruitUtil.sendNumBits);
        end;
    end;
end;

function Lexion770:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
    local fillLevel = getXMLFloat(xmlFile, key.."#grainTankFillLevel");
    local fruitType = getXMLString(xmlFile, key.."#grainTankFruitType");
    if fillLevel ~= nil and fruitType ~= nil then
        local fruitTypeDesc = FruitUtil.fruitTypes[fruitType];
        if fruitTypeDesc ~= nil then
            self:setGrainTankFillLevel(fillLevel, fruitTypeDesc.index);
        end;
    end;
    return BaseMission.VEHICLE_LOAD_OK;
end;

function Lexion770:getSaveAttributesAndNodes(nodeIdent)
    local fruitType = "unknown";
    if self.currentGrainTankFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
        fruitType = FruitUtil.fruitIndexToDesc[self.currentGrainTankFruitType].name;
    end;
    local attributes = 'grainTankFillLevel="'..self.grainTankFillLevel..'" grainTankFruitType="'..fruitType..'"';
    return attributes, nil;
end;

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

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

function Lexion770:update(dt)

    if self.isServer then
        if not self.workMode and self.grainTankFillLevel >= 3600 then
            self:setWorkMode(true, false, true);
        end;
        if (self.grainTankFillLevel >= self.grainTankCapacity*0.8) and not self.autoBeacon then
          self:setBeaconLightsVisibility(true);
          self.autoBeacon=true;
        end;                                    
        
        if (self.grainTankFillLevel < self.grainTankCapacity*0.8) and self.autoBeacon then
          self:setBeaconLightsVisibility(false);
          self.autoBeacon = false;
        end;        
    end;

    if not self:getIsActive() and self.autoBeacon then
        if self.beaconLightsActive then
            for _, beaconLight in pairs(self.beaconLights) do
                rotate(beaconLight.node, 0, beaconLight.speed*dt, 0);
            end;
        end;    
    end;
    if self:getIsActive() then

        if self.isClient then
            if self.isThreshing and self:getIsActiveForSound() then
                if not self.threshingSoundActive and self.threshingSound ~= nil and self.playThreshingSoundTime <= self.time then
                    playSample(self.threshingSound, 0, 1, 0);
                    self.threshingSoundActive = true;
                end;
            end;
        end;

        if self.isClient and self:getIsActiveForInput() then
            if not self.isThreshing and self.grainTankFillLevel < self.grainTankCapacity or self.grainTankCapacity <= 3600 then
              if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA3) then
                self:setWorkMode(not self.workMode);
              end;
            end;
            if self.workMode then
                if InputBinding.hasEvent(InputBinding.ACTIVATE_THRESHING) then
                    self:setIsThreshing(not self.isThreshing);
                end;
                if (self.hasStraw) or ((self.currentGrainTankFruitType~=FruitUtil.FRUITTYPE_UNKNOWN) and (FruitUtil.fruitIndexToDesc[self.currentGrainTankFruitType].hasStraw)) or (self.currentGrainTankFruitType==FruitUtil.FRUITTYPE_UNKNOWN) then
                  if InputBinding.hasEvent(InputBinding.ACTIVATE_CRUSHER) then
                    self:setCrusher(not self.crusherEnabled);
                  end;
                end;
            end;

            if InputBinding.hasEvent(InputBinding.EMPTY_GRAIN) then
                local nextState = self.targetPipeState+1;
                if nextState > self.numPipeStates then
                    nextState = 1;
                end;
                self:setPipeState(nextState);
            end;
        end;

        if self.isServer then
            if self.grainTankFillLevel >= self.grainTankCapacity and self.grainTankCapacity > 0 then
                self:setIsThreshing(false);
            end;
        end;

		    if self.isThreshing and self.rotorFan ~= nil then
			    rotate(self.rotorFan, dt*0.005, 0, 0);
        end;

--        if self.isThreshing and self.chopperActivated then
        if self.workMode and self.crusherEnabled then
			    rotate(self.chopper.drumStrawLeft, 0, -dt*0.75, 0);
			    rotate(self.chopper.drumStrawRight, 0, dt*0.75, 0);
			    local yMaxBig = Utils.degToRad(55);
			    local yMinBig = Utils.degToRad(-55);
			    local yMaxSmall = Utils.degToRad(32.5);
			    local yMinSmall = Utils.degToRad(-32.5);
			    local xsheetLeftSmall, ysheetLeftSmall, zsheetLeftSmall = getRotation(self.chopper.sheetLeftSmall);
			    local xsheetLeftBig, ysheetLeftBig, zsheetLeftBig = getRotation(self.chopper.sheetLeftBig);
			    local xsheetRightSmall, ysheetRightSmall, zsheetRightSmall = getRotation(self.chopper.sheetRightSmall);
			    local xsheetRightBig, ysheetRightBig, zsheetRightBig = getRotation(self.chopper.sheetRightBig);
			    --special Thanks to Sven777b
				  if (ysheetLeftBig > yMaxBig or ysheetLeftBig < yMinBig) then
				    self.moveStrawBig = self.moveStrawBig*-1;
		      end;
				  if (ysheetLeftSmall > yMaxSmall or ysheetLeftSmall < yMinSmall) then
				    self.moveStrawSmall = self.moveStrawSmall*-1;
		      end;
			    ysheetLeftSmall = ysheetLeftSmall + self.moveStrawSmall*0.827;
			    ysheetLeftBig = ysheetLeftBig + self.moveStrawBig*1.4;
			    ysheetRightSmall = ysheetRightSmall + self.moveStrawSmall*0.827;
			    ysheetRightBig = ysheetRightBig + self.moveStrawBig*1.4;

			    setRotation(self.chopper.sheetLeftSmall, 0, ysheetLeftSmall, 0);
			    setRotation(self.chopper.sheetLeftBig, 0, ysheetLeftBig, 0);
			    setRotation(self.chopper.sheetRightSmall, 0, ysheetRightSmall, 0);
			    setRotation(self.chopper.sheetRightBig, 0, ysheetRightBig, 0);
        end;

		if self.isThreshing and self.pipeScroll ~= nil then
			rotate(self.pipeScroll, -dt*0.0075, 0, 0);
		end;		
		
	  if g_currentMission.environment.lastRainScale <= 0.02 and g_currentMission.environment.timeSinceLastRain > 20 then
		  setRotation(self.wiper, 0, 0, 0);
    else
		  rotate(self.wiper, 0, 0, dt*0.005);
    end;

	  if self.chopper ~= nil then
		  local ax1, ay1, az1 = getWorldTranslation(self.chopper.zylinderNode);
		  local bx1, by1, bz1 = getWorldTranslation(self.chopper.rotationPunch);
      local x1, y1, z1 = worldDirectionToLocal(getParent(self.chopper.zylinderNode), bx1-ax1, by1-ay1, bz1-az1);
      setDirection(self.chopper.zylinderNode,  0, y1*-1, z1*-1, 0, 0, 1);
      if self.chopper.zylinderPunch ~= nil then
        local distance1 = Utils.vector3Length(ax1-bx1, ay1-by1, az1-bz1);
        setTranslation(self.chopper.zylinderPunch, 0, 0, (distance1-self.chopper.punchDistance)*-1);
      end;

      local ax2, ay2, az2 = getWorldTranslation(self.chopper.zylinderNode2);
      local bx2, by2, bz2 = getWorldTranslation(self.chopper.rotationPunch2);
      local x3, y3, z3 = worldDirectionToLocal(getParent(self.chopper.zylinderNode2), bx2-ax2, by2-ay2, bz2-az2);
      setDirection(self.chopper.zylinderNode2,  x3, y3, z3, 0, 0, 1);
      if self.chopper.zylinderPunch2 ~= nil then
        local distance2 = Utils.vector3Length(ax2-bx2, ay2-by2, az2-bz2);
        setTranslation(self.chopper.zylinderPunch2, 0, (distance2-self.chopper.punchDistance2)*-1, 0);
      end;

      local xstraw, ystraw, zstraw = getRotation(self.chopper.strawNode); --29|0
      local x1straw, y1straw, z1straw = getRotation(self.chopper.strawNode2); --29|0|0
      local x2straw, y2straw, z2straw = getRotation(self.chopper.node); --29|0|0|0
      local xBlech, yBlech, zBlech = getRotation(self.strawSheet);
      local moveSpeed = 0.0005;
      local nodeMin = -80*3.1456/180.0;
      local node2Min = 80*3.1456/180.0;
      local xRotMax = 41*3.1456/180.0;
      local xRotMax2 = 47*3.1456/180.0;
      local xRotMin2 = 0*3.1456/180.0;
      local xRotMin2straw = -10*3.1456/180.0;
      if not self.workMode then
        xstraw = xstraw+dt*moveSpeed;
        if xstraw > xRotMin2 then
          xstraw = xRotMin2; --0
        end;
        x2straw = x2straw-dt*moveSpeed;
        if x2straw < xRotMin2 then
          x2straw = xRotMin2; --0
        end;
        if xstraw >= xRotMin2 then
          xBlech = xBlech+dt*moveSpeed*1.25;
          if xBlech > xRotMax2 then
            xBlech = xRotMax2;
          end;
          x1straw = x1straw+dt*moveSpeed;
          if x1straw > xRotMax then
            x1straw = xRotMax; --41
          end;
        end;
      else
--        if self.chopperActivated then  --self.strawActive
        if self.crusherEnabled then
          xBlech = xBlech-dt*moveSpeed*1.21;
          if xBlech < xRotMin2straw then
            xBlech = xRotMin2straw;
          end;
          x1straw = x1straw-dt*moveSpeed;
          if x1straw < xRotMin2 then
            x1straw = xRotMin2
          end;
          if x1straw <= xRotMin2 then
            x2straw = x2straw+dt*moveSpeed;
            if x2straw > node2Min then
              x2straw = node2Min;
            end;
            xstraw = xstraw-dt*moveSpeed;
            if xstraw < nodeMin then
              xstraw = nodeMin;
            end;
          end;
        else
          xBlech = xBlech-dt*moveSpeed*1.21;
          if xBlech < xRotMin2 then
            xBlech = xRotMin2;
          end;
          x1straw = x1straw-dt*moveSpeed;
          if x1straw < xRotMin2 then
            x1straw = xRotMin2
          end;
          x2straw = x2straw-dt*moveSpeed;
          if x2straw < xRotMin2 then
            x2straw = xRotMin2;
          end;
          xstraw = xstraw+dt*moveSpeed;
          if xstraw > xRotMin2 then
            xstraw = xRotMin2;
          end;
        end;
      end;
      setRotation(self.chopper.strawNode, xstraw, 0, 0);
      setRotation(self.chopper.node, x2straw, 0, 0);
      setRotation(self.chopper.strawNode2, x1straw, 0, 0);
      setRotation(self.strawSheet, xBlech, 0, 0);
    end;

        local chopperBlindRotationSpeed = 0.001;
        local minRotX = -83*3.1415/180.0;
        if self.chopperBlind ~= nil then
            local x,y,z = getRotation(self.chopperBlind);
            if self.chopperActivated then
                x = x-dt*chopperBlindRotationSpeed;
                if x < minRotX then
                    x = minRotX;
                end;
            else
                x = x+dt*chopperBlindRotationSpeed;
                if x > 0.0 then
                    x = 0.0;
                end;
            end;
            setRotation(self.chopperBlind, x, y, z);
        end;

        local doAutoAiming = self.pipeStateIsAutoAiming[self.currentPipeState];
        local targetTrailer = nil;
        if doAutoAiming then
            targetTrailer = self:findAutoAimTrailerToUnload(self.lastValidOutputFruitType);

            if targetTrailer == nil then
                doAutoAiming = false;
            end;
        end;
        if (self.currentPipeState ~= self.targetPipeState or doAutoAiming) and self.targetPipeState <= self.numPipeStates then
            local autoAimX, autoAimY, autoAimZ;
            if doAutoAiming then
                autoAimX, autoAimY, autoAimZ = getWorldTranslation(targetTrailer.fillAutoAimTargetNode);
            end;


            local moved = false;
            for i=1, table.getn(self.pipeNodes) do
                local nodeMoved = false;
                local pipeNode = self.pipeNodes[i];

                local state = pipeNode.states[self.targetPipeState];
                if pipeNode.translationSpeeds ~= nil then
                    for i=1, 3 do
                        if pipeNode.curTranslation[i] ~= state.translation[i] then
                            nodeMoved = true;
                            if pipeNode.curTranslation[i] < state.translation[i] then
                                pipeNode.curTranslation[i] = math.min(pipeNode.curTranslation[i] + dt*pipeNode.translationSpeeds[i], state.translation[i]);
                            else
                                pipeNode.curTranslation[i] = math.max(pipeNode.curTranslation[i] - dt*pipeNode.translationSpeeds[i], state.translation[i]);
                            end;
                        end;
                    end;
                    setTranslation(pipeNode.node, pipeNode.curTranslation[1],pipeNode.curTranslation[2],pipeNode.curTranslation[3])
                end;
                if pipeNode.rotationSpeeds ~= nil then
                    for i=1, 3 do
                        local targetRotation = state.rotation[i];
                        if doAutoAiming then
                            if pipeNode.autoAimXRotation and i == 1 then
                                local x,y,z = getWorldTranslation(pipeNode.node);
                                local x,y,z = worldDirectionToLocal(getParent(pipeNode.node), autoAimX-x, autoAimY-y, autoAimZ-z);
                                targetRotation = -math.atan2(y,z);
                                if pipeNode.autoAimInvertZ then
                                    targetRotation = targetRotation+math.pi;
                                end;
                                targetRotation = Utils.normalizeRotationForShortestPath(targetRotation, pipeNode.curRotation[i]);
                            elseif pipeNode.autoAimYRotation and i == 2 then
                                local x,y,z = getWorldTranslation(pipeNode.node);
                                local x,y,z = worldDirectionToLocal(getParent(pipeNode.node), autoAimX-x, autoAimY-y, autoAimZ-z);
                                targetRotation = math.atan2(x,z);
                                if pipeNode.autoAimInvertZ then
                                    targetRotation = targetRotation+math.pi;
                                end;
                                targetRotation = Utils.normalizeRotationForShortestPath(targetRotation, pipeNode.curRotation[i]);
                            end;
                        end;
                        if pipeNode.curRotation[i] ~= targetRotation then
                            nodeMoved = true;
                            if pipeNode.curRotation[i] < targetRotation then
                                pipeNode.curRotation[i] = math.min(pipeNode.curRotation[i] + dt*pipeNode.rotationSpeeds[i], targetRotation);
                            else
                                pipeNode.curRotation[i] = math.max(pipeNode.curRotation[i] - dt*pipeNode.rotationSpeeds[i], targetRotation);
                            end;
                        end;
                    end;
                    setRotation(pipeNode.node, pipeNode.curRotation[1],pipeNode.curRotation[2],pipeNode.curRotation[3])
                end;
                moved = moved or nodeMoved;

                if nodeMoved and self.setMovingToolDirty ~= nil then
                    self:setMovingToolDirty(pipeNode.node);
                end;
            end;
            if not moved then
                self.currentPipeState = self.targetPipeState;
            end;
        end;

        if self.isClient then
            if self.motor ~= nil then
                if self.motor.speedLevel == 1 then
                    self.speedDisplayScale = 0.7;
                elseif self.motor.speedLevel == 2 or self.motor.speedLevel == 4 then
                    self.speedDisplayScale = 0.75;
                else
                    self.speedDisplayScale = 1.0;
                end;
            end;

            if self.currentPipeState ~= self.targetPipeState then
                if self.pipeSound ~= nil and not self.pipeSoundEnabled then
                    if self:getIsActiveForSound() then
                        setSamplePitch(self.pipeSound, self.pipeSoundPitchOffset);
                        playSample(self.pipeSound, 0, 1, 0);
                        self.pipeSoundEnabled = true;
                    end;
                end;
            else
                if self.pipeSound ~= nil and self.pipeSoundEnabled then
                    stopSample(self.pipeSound);
                    self.pipeSoundEnabled = false;
                end;
            end;
        end;

    end;

end;

function Lexion770:updateTick(dt)
    if self.isServer then
        if self.lastFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
            self.lastValidFruitType = self.lastFruitType;
        end;
        if self.lastOutputFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
            self.lastValidOutputFruitType = self.lastOutputFruitType;
        end;
        self.lastArea = 0;
        self.lastFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
        self.lastOutputFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
        self.grainTankTempFillLevel = 0;

        local disableChopperEmit = true;
        local disableStrawEmit = true;

        if self.isThreshing then
            local lastArea = 0;
            local fruitType = FruitUtil.FRUITTYPE_UNKNOWN;
            for cutter,implement in pairs(self.attachedCutters) do
                if cutter.reelStarted then
                    if cutter.lastArea > 0 then
                        for cutter1,implement in pairs(self.attachedCutters) do
                            cutter1:setFruitType(cutter.currentFruitType);
                        end;
                        self.currentGrainTankFruitType = cutter.currentFruitType;
                        fruitType = cutter.currentFruitType;
                        lastArea = lastArea + cutter.lastArea;
                    end;
                end;
            end;
            self.lastArea = lastArea;
            self.lastFruitType = fruitType;
            local outputFruitType = fruitType;
            if self.convertedFruits[fruitType] ~= nil then
                outputFruitType = self.convertedFruits[fruitType];
            end;
            self.lastOutputFruitType = outputFruitType;
            if self.lastArea > 0 then
                local fruitDesc = FruitUtil.fruitIndexToDesc[fruitType];
                self.hasStraw = fruitDesc.hasStraw;
                if fruitDesc.hasStraw then
                    self.chopperActivated = self.crusherEnabled;
                else
                    self.chopperActivated = true;
                end;

                if self.chopperActivated then
                    if self.chopperEnableTime == nil then
                        self.chopperEnableTime = self.time + self.chopperToggleTime;
                    else
                        self.chopperDisableTime = nil;
                    end;
                    self.chopperPSFruitType = fruitType;
                    disableChopperEmit = false;
                else
                    if self.strawEnableTime == nil then
                        self.strawEnableTime = self.time + self.strawToggleTime;
                    else
                        self.strawDisableTime = nil;
                    end;
                    self.strawPSFruitType = fruitType;
                    disableStrawEmit = false;
                end;

                -- 8000/1200 = 6.66 liter/meter
                -- 8000/1200 / 6 = 1.111 liter/m^2
                -- 8000/1200 / 6 / 2^2 = 0.277777 liter / density pixel (density is 4096^2, on a area of 2048m^2
                local pixelToSqm = g_currentMission:getFruitPixelsToSqm()  / g_currentMission.maxFruitValue; -- 4096px are mapped to 2048m
                local literPerSqm = 1;
                if (fruitType ~= FruitUtil.FRUITTYPE_UNKNOWN) then
                    literPerSqm = FruitUtil.fruitIndexToDesc[fruitType].literPerSqm; -- * (1 + 0.5 * (3 - g_currentMission.missionStats.difficulty));
                    if (outputFruitType ~= fruitType and outputFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN) then
                        literPerSqm = literPerSqm * FruitUtil.fruitIndexToDesc[outputFruitType].literPerSqm;
                    end;
                end;

                --local literPerPixel = 8000/1200 / 6 / (2*2);

                --literPerPixel = literPerPixel*1.5;
                local sqm = self.lastArea*pixelToSqm;

                local deltaFillLevel = sqm*literPerSqm*self.threshingScale;

                if self.grainTankCapacity > 0 then
                    local newFillLevel = self.grainTankFillLevel+deltaFillLevel;
                    self:setGrainTankFillLevel(newFillLevel, outputFruitType);
                else
                    self.grainTankTempFillLevel = deltaFillLevel;
                    self.grainTankTempFruitType = outputFruitType;
                end;
            end;
        end;
       
        if disableChopperEmit and self.chopperDisableTime == nil then
            self.chopperDisableTime = self.time + self.chopperToggleTime;
        end;
        if disableStrawEmit and self.strawDisableTime == nil then
            self.strawDisableTime = self.time + self.strawToggleTime;
        end;


        if self.chopperEnableTime ~= nil and self.chopperEnableTime <= self.time then
            self.chopperPSenabled = true;
            self.chopperEnableTime = nil;
        end;
        if self.strawEnableTime ~= nil and self.strawEnableTime <= self.time then
            self.strawPSenabled = true;
            self.strawEnableTime = nil;
            self.strawEmitState = true;
        end;
        if self.strawEmitState then
            local cuttingAreasSend = {};
            for k, strawArea in pairs(self.strawAreas) do
                local x,y,z = getWorldTranslation(strawArea.start);
                local x1,y1,z1 = getWorldTranslation(strawArea.width);
                local x2,y2,z2 = getWorldTranslation(strawArea.height);
                local old, total = Utils.getFruitWindrowArea(self.strawPSFruitType, x, z, x1, z1, x2, z2);
                local value = 1+math.floor(old / total + 0.7); -- round, biased to the bigger value
                value = math.min(value, g_currentMission.maxWindrowValue);
                --Utils.updateFruitWindrowArea(self.strawPSFruitType, x, z, x1, z1, x2, z2, value, true);
                table.insert(cuttingAreasSend, {x,z,x1,z1,x2,z2,value});
            end;

            if (table.getn(cuttingAreasSend) > 0) then
                CombineAreaEvent.runLocally(cuttingAreasSend, self.strawPSFruitType);
                g_server:broadcastEvent(CombineAreaEvent:new(cuttingAreasSend, self.strawPSFruitType));
            end;
        end;


        self.pipeIsUnloading = false;
        self.pipeParticleActivated = false;
        if self.pipeStateIsUnloading[self.currentPipeState] then
            local fruitType, fillLevel, useGrainTank = self:getFruitTypeAndFillLevelToUnload();
            if fillLevel > 0 then
                self.pipeParticleActivated = true;
                self.pipeIsUnloading = true;

                -- test if we should drain the grain tank
                local trailer = self:findTrailerToUnload(fruitType);
                if trailer == nil then
                    self.pipeIsUnloading = false;
                else
                    trailer:resetFillLevelIfNeeded(FruitUtil.fruitTypeToFillType[fruitType]);

                    local deltaLevel = fillLevel;
                    if useGrainTank then
                        deltaLevel = self.grainTankUnloadingCapacity*dt/1000.0;
                    end;
                    deltaLevel = math.min(deltaLevel, trailer.capacity - trailer.fillLevel);

                    fillLevel = fillLevel-deltaLevel;
                    if fillLevel <= 0.0 then
                        deltaLevel = deltaLevel+fillLevel;
                        fillLevel = 0.0;
                        self.pipeIsUnloading = false;
                    elseif deltaLevel == 0 then
                        self.pipeIsUnloading = false;
                    end;
                    if useGrainTank then
                        self:setGrainTankFillLevel(fillLevel, fruitType);
                    end;
                    trailer:setFillLevel(trailer.fillLevel+deltaLevel, FruitUtil.fruitTypeToFillType[fruitType]);
                end;
                if not self.pipeIsUnloading and useGrainTank then
                    self.pipeParticleActivated = false;
                end;
            end;
        end;


        if self.grainTankFillLevel ~= self.sentGrainTankFillLevel or self.currentGrainTankFruitType ~= self.sentGrainTankFruitType or self.lastValidFruitType ~= self.sentLastValidFruitType then
            --g_server:broadcastEvent(CombineFillEvent:new(self, self.grainTankFillLevel, self.currentGrainTankFruitType), nil, nil, self);
            self:raiseDirtyFlags(self.combineDirtyFlag);
            self.sentGrainTankFillLevel = self.grainTankFillLevel;
            self.sentGrainTankFruitType = self.currentGrainTankFruitType;
            self.sentLastValidFruitType = self.lastValidFruitType;
        end;
        if self.pipeParticleActivated ~= self.sentPipeParticleActivated or self.pipeIsUnloading ~= self.sentPipeIsUnloading then
            g_server:broadcastEvent(CombinePipeParticleActivatedEvent:new(self, self.pipeParticleActivated, self.pipeIsUnloading), nil, nil, self);
            self.sentPipeParticleActivated = self.pipeParticleActivated;
            self.sentPipeIsUnloading = self.pipeIsUnloading;
        end;
        if self.chopperPSenabled~= self.sentChopperPSenabled or (self.chopperPSenabled and self.chopperPSFruitType ~= self.sentChopperPSFruitType) then
           g_server:broadcastEvent(Lexion770SetChopperEnableEvent:new(self, self.chopperPSenabled, self.chopperPSFruitType), true, nil, self);
           self.sentChopperPSFruitType = self.chopperPSFruitType;
           self.sentChopperPSenabled = self.chopperPSenabled;
        end
        if self.strawPSenabled ~= self.sentStrawPSenabled or (self.strawPSenabled and self.strawPSFruitType ~= self.sentStrawPSFruitType) then
           g_server:broadcastEvent(Lexion770SetStrawEnableEvent:new(self, self.strawPSenabled, self.strawPSFruitType), true, nil, self);
           self.sentStrawPSFruitType = self.strawPSFruitType;
           self.sentStrawPSenabled = self.strawPSenabled;
        end;

        if self.chopperDisableTime ~= nil and self.chopperDisableTime <= self.time then
            self.chopperPSenabled = false;
            self.chopperDisableTime = nil;
        end;
        if self.strawDisableTime ~= nil and self.strawDisableTime <= self.time then
            self.strawPSenabled = false;
            self.strawDisableTime = nil;
            self.strawEmitState = false;
        end;
    end;

    if self.pipeParticleActivated then
        self.pipeParticleDeactivateTime = self.time + 100;
        local currentPipeParticleSystem = self.pipeParticleSystems[self.currentGrainTankFruitType];
        if currentPipeParticleSystem == nil then
            currentPipeParticleSystem = self.defaultPipeParticleSystem;
        end;
        if currentPipeParticleSystem ~= self.currentPipeParticleSystem then
            if self.currentPipeParticleSystem ~= nil then
                Utils.setEmittingState(self.currentPipeParticleSystem, false);
            end;
        end;
        self.currentPipeParticleSystem = currentPipeParticleSystem;
        Utils.setEmittingState(self.currentPipeParticleSystem, true);
    else
        if self.pipeParticleDeactivateTime <= self.time and self.currentPipeParticleSystem ~= nil then
            Utils.setEmittingState(self.currentPipeParticleSystem, false);
            self.currentPipeParticleSystem = nil;
        end;
    end;   
end;

function Lexion770:draw()
    if self.isClient then
        local percent = 0;
        if self.grainTankCapacity > 0 then
            percent = self.grainTankFillLevel/self.grainTankCapacity*100;
            if self.currentPipeState == 2 and not self.pipeParticleActivated and self.grainTankFillLevel > 0 then
                g_currentMission:addExtraPrintText(g_i18n:getText("Move_the_pipe_over_a_trailer"));
            elseif self.grainTankFillLevel == self.grainTankCapacity then
                g_currentMission:addExtraPrintText(g_i18n:getText("Dump_corn_to_continue_threshing"));
            end;
        end;
        if self.drawFillLevel then
            self:drawGrainLevel(self.grainTankFillLevel, self.grainTankCapacity, 95);
        else
            self:drawGrainLevel(0,0, 101);
        end;
        if self.numAttachedCutters > 0 then
          if self.workMode then
            if self.isThreshing then
                g_currentMission:addHelpButtonText(g_i18n:getText("Turn_off_cutter"), InputBinding.ACTIVATE_THRESHING);
            else
                if self.grainTankFillLevel <= 3600 then
                   g_currentMission:addHelpButtonText(g_i18n:getText("Transport_mode"), InputBinding.IMPLEMENT_EXTRA3);
                else
                   g_currentMission:addExtraPrintText(g_i18n:getText("Empty_bunker"));
                end;
                g_currentMission:addHelpButtonText(g_i18n:getText("Turn_on_cutter"), InputBinding.ACTIVATE_THRESHING);
            end;
            if (self.hasStraw) or ((self.currentGrainTankFruitType~=FruitUtil.FRUITTYPE_UNKNOWN) and (FruitUtil.fruitIndexToDesc[self.currentGrainTankFruitType].hasStraw)) or (self.currentGrainTankFruitType==FruitUtil.FRUITTYPE_UNKNOWN) then
              if self.crusherEnabled then
                g_currentMission:addHelpButtonText(g_i18n:getText("Turn_off_crusher"), InputBinding.ACTIVATE_CRUSHER);
              else
                g_currentMission:addHelpButtonText(g_i18n:getText("Turn_on_crusher"), InputBinding.ACTIVATE_CRUSHER);
              end;
            end;
          else
            g_currentMission:addHelpButtonText(g_i18n:getText("Work_mode"), InputBinding.IMPLEMENT_EXTRA3);
          end;
        end;
        if self.numPipeStates == 2 then
            if self.targetPipeState == 2 then
                g_currentMission:addHelpButtonText(g_i18n:getText("Pipe_in"), InputBinding.EMPTY_GRAIN);
            else
                if percent > 80 then
                    g_currentMission:addHelpButtonText(g_i18n:getText("Dump_corn"), InputBinding.EMPTY_GRAIN);
                end;
            end;
        end;

        if self.currentGrainTankFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
            g_currentMission:setFruitOverlayFruitType(self.currentGrainTankFruitType);
        end;

        local printRainWarning = false;
        local printSpeedLevelWarning = false;
        local speedLevelStr;
        local speedLevelKeyStr;
        local speedLevel = 10;
        for _, implement in pairs(self.attachedCutters) do
            local cutter = implement.object;
            printRainWarning = printRainWarning or cutter.printRainWarning;

            if math.abs(cutter.speedViolationTimer - cutter.speedViolationMaxTime) > 2 then
                printSpeedLevelWarning = true;
                if Cutter.getUseLowSpeedLimit(cutter) then
                    if speedLevel > 1 then
                        speedLevel = 1;
                        speedLevelStr = "1";
                        speedLevelKeyStr = InputBinding.getKeyNamesOfDigitalAction(InputBinding.SPEED_LEVEL1)
                    end;
                else
                    if speedLevel > 2 then
                        speedLevel = 2;
                        speedLevelStr = "2";
                        speedLevelKeyStr = InputBinding.getKeyNamesOfDigitalAction(InputBinding.SPEED_LEVEL2)
                    end;
                end;
            end;
        end;

        if printRainWarning then
            g_currentMission:addWarning(g_i18n:getText("Dont_do_threshing_during_rain_or_hail"), 0.018, 0.033);
        end;
        if printSpeedLevelWarning then
            g_currentMission:addWarning(g_i18n:getText("Dont_drive_to_fast") .. "\n" .. string.format(g_i18n:getText("Cruise_control_levelN"), speedLevelStr, speedLevelKeyStr), 0.07+0.022, 0.019+0.029);
        end;
    end;

end;

function Lexion770:onEnter(isControlling)
end;

function Lexion770:onLeave()
    if self.deactivateOnLeave then
        Lexion770.onDeactivate(self);
    else
        Lexion770.onDeactivateSounds(self)
    end;
    self:setBeaconLightsVisibility(self.autoBeacon, true);    
end;

function Lexion770:onDeactivate()
    self:stopThreshing();

    for k,v in pairs(self.grainTankParticleSystems) do
        Utils.setEmittingState(v, false);
    end;

    for k,v in pairs(self.chopperParticleSystems) do
        Utils.setEmittingState(v, false);
    end;
    for k,v in pairs(self.strawParticleSystems) do
        Utils.setEmittingState(v, false);
    end;

    self.chopperEnableTime = nil;
    self.chopperDisableTime = nil;
    self.strawEnableTime = nil;
    self.strawDisableTime = nil;
    self.strawEmitState = false;
    Lexion770.onDeactivateSounds(self)
end;

function Lexion770:onDeactivateSounds()
    if self.pipeSound ~= nil and self.pipeSoundEnabled then
        stopSample(self.pipeSound);
        self.pipeSoundEnabled = false;
    end;
    if self.threshingSoundActive then
        stopSample(self.threshingSound);
        self.threshingSoundActive = false;
    end;
end;

function Lexion770:attachImplement(implement)
    local object = implement.object;
    if object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTER then
        self.attachedCutters[object] = implement;
        self.numAttachedCutters = self.numAttachedCutters+1;
        -- cutter assumes the thresher's loaded fruit type
        object:setFruitType(self.currentGrainTankFruitType);
        for a,o in pairs(self.attacherJoints) do
          if o.jointType == Vehicle.JOINTTYPE_CUTTER then
            if self.defaultRotMax == nil then
              self.defaultRotMax = o.maxRot[1]; 
            end;
            local uarot = getUserAttribute(object.rootNode, "rotation");
            if uarot ~= nil then
              o.maxRot[1] = Utils.degToRad(uarot);
            else
              o.maxRot[1] = self.defaultRotMax;
            end;
          end;
        end;
    end;
end;

function Lexion770:detachImplement(implementIndex)
    local object = self.attachedImplements[implementIndex].object;
    if object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTER then
        self.numAttachedCutters = self.numAttachedCutters-1;
        if self.numAttachedCutters == 0 then
            self:stopThreshing();
            self:setCrusher(false);
        end;
        self.attachedCutters[object] = nil;
    end;
end;

function Lexion770:allowGrainTankFruitType(fruitType)
    local allowed = false;

    if self.grainTankFruitTypes[fruitType] then -- is fruit type accepted by combine?
        if self.currentGrainTankFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then -- is combine currently not empty?
            if self.currentGrainTankFruitType ~= fruitType then -- is there another fill type in the tank?
                if self.grainTankCapacity == 0 or self.grainTankFillLevel / self.grainTankCapacity <= self.minThreshold then
                    allowed = true; -- fill level is low enough to be overridden
                end;
            else
                allowed = true; -- fill type is the same as the combine's current fill type
            end;
        else
            allowed = true; -- combine is empty --> FruitUtil.FRUITTYPE_UNKNOWN
        end;
    end;

    return allowed;
end;

function Lexion770:emptyGrainTankIfLowFillLevel()
    if self.grainTankCapacity == 0 or self.grainTankFillLevel / self.grainTankCapacity <= self.minThreshold then
        self.grainTankFillLevel = 0; -- empty the combine
        --return true;
    end;
    --return false;
end;

function Lexion770:setGrainTankFillLevel(fillLevel, fruitType)
    if not self:allowGrainTankFruitType(fruitType) then
        return;
    end;

    --self:emptyGrainTankIfLowFillLevel();

    self.grainTankFillLevel = Utils.clamp(fillLevel, 0, self.grainTankCapacity);

    self.currentGrainTankFruitType = fruitType;

    if self.isClient then
        if self.currentGrainTankPlane ~= nil then
            for _, node in ipairs(self.currentGrainTankPlane.nodes) do
                setVisibility(node.node, false);
            end;
            self.currentGrainTankPlane = nil;
        end;
        if self.grainTankPlanes ~= nil and self.defaultGrainTankPlane ~= nil and fruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
            local fruitTypeName = FruitUtil.fruitIndexToDesc[fruitType].name;
            local grainPlane = self.grainTankPlanes[fruitTypeName];
            if grainPlane == nil then
                grainPlane = self.defaultGrainTankPlane;
            end;
            local t = self.grainTankFillLevel/self.grainTankCapacity
            for _, node in ipairs(grainPlane.nodes) do
                local x,y,z, rx,ry,rz, sx,sy,sz = node.animCurve:get(t);

                setTranslation(node.node, x, y, z);
                setRotation(node.node, rx, ry, rz);
                setScale(node.node, sx, sy, sz);
                setVisibility(node.node, self.grainTankFillLevel > 0);
            end;
            self.currentGrainTankPlane = grainPlane;
        end;
    end;

    if self.grainTankFillLevel <= 0 then
        for cutter,implement in pairs(self.attachedCutters) do
            cutter:resetFruitType();
        end;
        self.currentGrainTankFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
    else
        for cutter,implement in pairs(self.attachedCutters) do
            cutter:setFruitType(self.currentGrainTankFruitType);
        end;
    end;
end;

function Lexion770:startThreshing()
    if not self.isThreshing then
        if self.numAttachedCutters > 0 then
            self.chopperActivated = self.crusherEnabled;
            self.isThreshing = true;

            for cutter,implement in pairs(self.attachedCutters) do
                self:setJointMoveDown(implement.jointDescIndex, true, true);

                cutter:setReelSpeedScale(1); --0.003);
                cutter:onStartReel();
            end;

            if self.isClient then
                local threshingSoundOffset = 0;
                if self.threshingStartSound ~= nil then
                    if self:getIsActiveForSound() then
                        setSamplePitch(self.threshingStartSound, self.threshingStartSoundPitchOffset);
                        playSample(self.threshingStartSound, 1, 1, 0);
                    end;
                    threshingSoundOffset = getSampleDuration(self.threshingStartSound);
                end;

                self.playThreshingSoundTime = self.time+threshingSoundOffset;
            end;
        end;
    end;
end;

function Lexion770:stopThreshing()

    if self.isThreshing then

        if self.isClient then
            if self.threshingSound ~= nil then
                stopSample(self.threshingSound);
            end;

            if self.threshingStopSound ~= nil and self.threshingSoundActive and self:getIsActiveForSound() then
                setSamplePitch(self.threshingStopSound, self.threshingStopSoundPitchOffset);
                playSample(self.threshingStopSound, 1, 1, 0);
                self.threshingSoundActive = false;
            end;
        end;

        self.chopperActivated = false;
        self.isThreshing = false;
        for cutter,implement in pairs(self.attachedCutters) do
            self:setJointMoveDown(implement.jointDescIndex, false, true);
            cutter:onStopReel();
        end;
    end;

end;

function Lexion770:workModeStart(turbo)
    if not self.workMode then
        if (self.numAttachedCutters > 0) or turbo then
            self.workMode = true;
            if self.tankAnimCharSet ~= nil and self.tankAnimCharSet ~= 0 then
              if turbo then
                setAnimTrackTime(self.tankAnimCharSet, 0, self.tankAnimDuration);
              else
                  if getAnimTrackTime(self.tankAnimCharSet, 0) < 0.0 then
                    setAnimTrackTime(self.tankAnimCharSet, 0, 0.0);
                  end;
              end;
              setAnimTrackSpeedScale(self.tankAnimCharSet, 0, self.tankAnimSpeedScale);
              enableAnimTrack(self.tankAnimCharSet, 0);
            end;
            for _, light in pairs(self.realLights) do
              setLightRange(light, 250);
            end;
            for _, light in pairs(self.workLights) do
              setVisibility(light, true);
            end;
            for _, light in pairs(self.workLightCoronas) do
              setVisibility(light, true);
            end;
            for _, light in pairs(self.workLightCones) do
              setVisibility(light, true);
            end;
        end;
    end;
end;

function Lexion770:workModeEnd()

    if self.workMode and self.grainTankFillLevel <= 3600 then
        if self.isThreshing then
           self:setIsThreshing(false);
        end;
        self.workMode = false;

        if self.tankAnimCharSet ~= nil and self.tankAnimCharSet ~= 0 then
          if getAnimTrackTime(self.tankAnimCharSet, 0) > self.tankAnimDuration then
            setAnimTrackTime(self.tankAnimCharSet, 0, self.tankAnimDuration);
          end;
          setAnimTrackSpeedScale(self.tankAnimCharSet, 0, -self.tankAnimSpeedScale);
          enableAnimTrack(self.tankAnimCharSet, 0);
        end;
        for _, light in pairs(self.realLights) do
          setLightRange(light, 80);
        end;
        for _, light in pairs(self.workLights) do
          setVisibility(light, false);
        end;
        for _, light in pairs(self.workLightCoronas) do
          setVisibility(light, false);
        end;
        for _, light in pairs(self.workLightCones) do
          setVisibility(light, false);
        end;
    end;

end;

function Lexion770:crusherStart()
    if self.workMode and not self.crusherEnabled then
      self.crusherEnabled=true;
      if self.isThreshing and self.hasStraw then
        self.chopperActivated = self.crusherEnabled;
        if self.lastArea>0 then
          self.chopperEnableTime = self.time;
          self.chopperDisableTime = nil;
          self.strawDisableTime = self.time;
          self.strawEnableTime = nil;
        end;
      end;
    end;
end;

function Lexion770:crusherEnd()
    if self.workMode and self.crusherEnabled then
      self.crusherEnabled=false;
      if self.isThreshing and self.hasStraw then
        self.chopperActivated = self.crusherEnabled;
        if self.lastArea>0 then
          self.strawEnableTime = self.time;
          self.strawDisableTime = nil;
          self.chopperDisableTime = self.time;
          self.chopperEnableTime = nil;
        end;
      end;
    end;
end;

function Lexion770:setPipeOpening(pipeOpening, noEventSend)
    if pipeOpening then
        self:setPipeState(2, noEventSend);
    else
        self:setPipeState(1, noEventSend);
    end;
end;

function Lexion770:setPipeState(pipeState, noEventSend)
    if self.targetPipeState ~= pipeState then
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(CombineSetPipeStateEvent:new(self, pipeState));
            else
                g_client:getServerConnection():sendEvent(CombineSetPipeStateEvent:new(self, pipeState), nil, nil, self);
            end;
        end;
        self.targetPipeState = pipeState;
        self.currentPipeState = 0;
    end;
end;

function Lexion770:setIsThreshing(isThreshing, noEventSend)
    if isThreshing ~= self.isThreshing then
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(CombineSetThreshingEnabledEvent:new(self, isThreshing), nil, nil, self);
            else
                g_client:getServerConnection():sendEvent(CombineSetThreshingEnabledEvent:new(self, isThreshing));
            end;
        end;
        if isThreshing then
            self:startThreshing();
        else
            self:stopThreshing();
        end;
    end;
end;

function Lexion770:setWorkMode(workMode, noEventSend, turbo)
    if workMode ~= self.workMode then
        if turbo == nil then
          turbo = false;
        end;
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(CombineSetWorkModeEvent:new(self, workMode, turbo), nil, nil, self);
            else
                g_client:getServerConnection():sendEvent(CombineSetWorkModeEvent:new(self, workMode, turbo));
            end;
        end;
        if workMode then
            self:workModeStart(turbo);
        else
            self:workModeEnd();
        end;
    end;
end;

function Lexion770:setCrusher(enabled, noEventSend)
    if enabled ~= self.crusherEnabled then
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(CombineSetCrusherEvent:new(self, enabled), nil, nil, self);
            else
                g_client:getServerConnection():sendEvent(CombineSetCrusherEvent:new(self, enabled));
            end;
        end;
        if enabled then
            self:crusherStart();
        else
            self:crusherEnd();
        end;
    end;
end;


function Lexion770:getIshreshingAllowed(earlyWarning)
    if self.allowThreshingDuringRain then
        return true;
    end;
    if earlyWarning ~= nil and earlyWarning == true then
        if g_currentMission.environment.lastRainScale <= 0.02 and g_currentMission.environment.timeSinceLastRain > 20 then
            return true;
        end;
    else
        if g_currentMission.environment.lastRainScale <= 0.1 and g_currentMission.environment.timeSinceLastRain > 20 then
            return true;
        end;
    end;
    return false;
end;

function Lexion770:getFruitTypeAndFillLevelToUnload()
    local fillLevel = self.grainTankFillLevel;
    local fruitType = self.currentGrainTankFruitType;
    local useGrainTank = self.grainTankCapacity > 0;
    if not useGrainTank then
        fillLevel = self.grainTankTempFillLevel;
        fruitType = self.grainTankTempFruitType;
    end;
    return fruitType, fillLevel, useGrainTank;
end;

function Lexion770:findAutoAimTrailerToUnload(fruitType)
    local trailer = nil;
    local smallestTrailerId = nil;
    if self.trailersInRange ~= nil then
        for trailerInRange, pipeStage in pairs(self.trailersInRange) do
            if trailerInRange:allowFillType(FruitUtil.fruitTypeToFillType[fruitType]) and trailerInRange.allowFillFromAir and trailerInRange.fillLevel < trailerInRange.capacity then
                local id = networkGetObjectId(trailerInRange);
                -- always take the trailer with the smalles network id. This is deterministic and is the same on the client
                if trailer == nil or id < smallestTrailerId then
                    trailer = trailerInRange;
                    smallestTrailerId = id;
                end;
            end;
        end;
    end;
    return trailer;
end;

function Lexion770:findTrailerToUnload(fruitType)

    local x,y,z = getWorldTranslation(self.pipeRaycastNode);
    local dx,dy,dz = localDirectionToWorld(self.pipeRaycastNode, 0,-1,0);

    self.trailerFound = 0;
    raycastAll(x, y, z, dx,dy,dz, "findTrailerRaycastCallback", self.pipeRaycastDistance, self);

    local trailer = g_currentMission.nodeToVehicle[self.trailerFound];
    if trailer == nil or not trailer:allowFillType(FruitUtil.fruitTypeToFillType[fruitType]) or not trailer.allowFillFromAir or trailer.fillLevel >= trailer.capacity then
        return nil;
    end;
    return trailer;
end;

function Lexion770:findTrailerRaycastCallback(transformId, x, y, z, distance)

    local vehicle = g_currentMission.nodeToVehicle[transformId];
    if vehicle ~= nil then
        if vehicle.exactFillRootNode == transformId then
            self.trailerFound = transformId;
            return false;
        end;
    end;

    return true;

end;