---
-- Overloader
-- This is the specialization for overloaders
--
-- @author  Claus G. Pedersen (Satis)
-- @date  29/12/12
--
-- Copyright Satis (C) 2012
-- Copyright Note: You are allowed to redistribute, use and edit it as long I'm credited for the original script.

local translationList = {
    ["en"] = {
        ["overloader_movePipeLeft"] = "Move Pipe Left",
        ["overloader_movePipeRight"] = "Move Pipe Right",
        ["overloader_movePipeUp"] = "Move Pipe Up",
        ["overloader_movePipeDown"] = "Move Pipe Down",
        ["overloader_foldPipe"] = "Fold Pipe",
        ["overloader_unfoldPipe"] = "Unfold Pipe",
        ["overloader_chargeOn"] = "Charge On",
        ["overloader_chargeOff"] = "Charge Off",
        ["overloader_isEmpty"] = "%s is empty!",
        ["overloader_cp_controls"] = "Courseplay is controlling %s",
        ["overloader_togglePipeLight"] = "Toggle Pipe Light",
        ["overloader_warningChargeTipNotInPos"] = "The pipe must be %s to be able to tip charge at this tipping point",
        ["overloader_inWarningChargeTipPos"] = "folded",
        ["overloader_outWarningChargeTipPos"] = "unfolded",
    },
    ["de"] = {
        ["overloader_movePipeLeft"] = "Rohr nach links bewegen",
        ["overloader_movePipeRight"] = "Rohr nach rechts bewegen",
        ["overloader_movePipeUp"] = "Rohr nach oben bewegen",
        ["overloader_movePipeDown"] = "Rohr nach unten bewegen",
        ["overloader_foldPipe"] = "Rohr zuklappen",
        ["overloader_unfoldPipe"] = "Rohr aufklappen",
        ["overloader_chargeOn"] = "überladen an",
        ["overloader_chargeOff"] = "überladen aus",
        ["overloader_isEmpty"] = "%s ist leer!",
        ["overloader_cp_controls"] = "Courseplay steuert %s",
        ["overloader_togglePipeLight"] = "Rohr Beleuchtung ein-/ausschalten",
        ["overloader_warningChargeTipNotInPos"] = "Das Rohr muss %s sein um an diesem Abladepunkt abzuladen",
        ["overloader_inWarningChargeTipPos"] = "eingeklappt",
        ["overloader_outWarningChargeTipPos"] = "ausgeklappt",
    },
};
local lang = g_languageShort;
if translationList[lang] == nil then lang = "en"; end;
for i18nString, text in pairs(translationList[lang]) do
    if not g_i18n:hasText(i18nString) then
        g_i18n:setText(i18nString, text);
    end;
end;



overloader = {};

function overloader.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Trailer, specializations);
end;

--- Initiate Overloader Specialization
-- @param xmlFile The XML file that have the information we need.
--
function overloader:load(xmlFile)
    --- Set local values
    local aNameSearch = { "vehicle.name." .. g_languageShort, "vehicle.name.en", "vehicle.name", "vehicle#type" };
    local xmlKey = "vehicle.Overloader.";
    local i = 0;
    local tempFile = "";
    local removeItem = false;

    --- Set functions
    self.onChargeTrigger = overloader.onChargeTrigger;
    self.handleChargeAnimation = SpecializationUtil.callSpecializationsFunction("handleChargeAnimation");
    self.handleChargeRPM = SpecializationUtil.callSpecializationsFunction("handleChargeRPM");
    self.handleChargeSound = SpecializationUtil.callSpecializationsFunction("handleChargeSound");
    self.handleChargeTip = SpecializationUtil.callSpecializationsFunction("handleChargeTip");
    self.handleChargeTriggers = SpecializationUtil.callSpecializationsFunction("handleChargeTriggers");
    self.handlePipeMoveSound = SpecializationUtil.callSpecializationsFunction("handlePipeMoveSound");
    self.handlePipeMoveAnimation = SpecializationUtil.callSpecializationsFunction("handlePipeMoveAnimation");
    self.handlePipeParticles = SpecializationUtil.callSpecializationsFunction("handlePipeParticles");
    self.handleGrainTransfer = SpecializationUtil.callSpecializationsFunction("handleGrainTransfer");
    self.getText = overloader.getText;
    self.getAudioSound = overloader.getAudioSound;
    self.getPipeAnimation = overloader.getPipeAnimation;

    --- Set values
    self.overloaderVersion = 2;
    self.cpAI = "off";
    self.cpVersionNeeded = {[1] = 3, [2] = 41, [3] = 0185};
    self.cpChecked = false;
    self.cpUseOldSystem = false;
    self.ChargeTipWarning = false;

    self.isCharging = false;
    self.sendIsCharging = false;
    self.isChargTipping = false;
    self.lastAttacherVehicle = nil;
    self.pipeIsMoving = false;
    self.pipeEventIsMoving = false;
    self.isSenderOfPipeEventIsMoving = false;
    self.pipePosition = "in";
    self.pipeAnimations = {["savedTime"] = 0,};
    self.chargeTriggerList = {};
    self.isTrailerInRange = false;
    self.sendIsTrailerInRange = false;
    self.trailerFound = false;
    self.trailerToOverload = nil;
    self.ULWName = "";

    --------------------------------------------------------------------------------------------------------------------
    -- only load resourses if we are a client.
    --------------------------------------------------------------------------------------------------------------------
    if self.isClient then
        ----------------------------------
        -- Get mod real name
        ----------------------------------
        for nIndex, sXMLPath in pairs(aNameSearch) do
            self.ULWName = getXMLString(xmlFile, sXMLPath);
            if self.ULWName ~= nil then break; end;
        end;
        if self.ULWName == nil then self.ULWName = g_i18n:getText("UNKNOWN") end;

        ----------------------------------
        -- Get can charge tip
        ----------------------------------
        self.canChargeTip = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.Overloader#canChargeTip"), false);
        self.ChargeTipInPos = Utils.getNoNil(getXMLString(xmlFile, "vehicle.Overloader#ChargeTipInPos"), "in");
        self.AlternativeTippingInPos = Utils.getNoNil(getXMLString(xmlFile, "vehicle.Overloader#AlternativeTippingInPos"), "out");
        self.allowAlternativeTippingInAllPos = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.Overloader#allowAlternativeTippingInAllPos"), false);

        ----------------------------------
        -- More Realistic Mod Settings.
        ----------------------------------
        if self.isRealistic then
            --- realisticTrailer settings
            self.realTippingPowerConsumption = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.realTippingPowerConsumption"), 0);
            self.realTippingPowerConsumptionWhenOpened = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.realTippingPowerConsumption#activeWhenOpened"), false); -- mainly for belt-trailer
            self.realTippingPowerConsumptionWhenClosing = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.realTippingPowerConsumption#activeWhenClosing"), false); -- mainly for push-off trailer
            self.realTrailerUnloadingNeedsEngineRev = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.realTrailerUnloadingNeedsEngineRev"), true);

            --- Overloader settings
            self.realOverloaderChargePowerConsumption = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.realOverloaderChargePowerConsumption"), 0);
            self.realOverloaderPipeMovePowerConsumption = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.realOverloaderPipeMovePowerConsumption"), 0);

            self.realOverloaderChargingNeedsEngineRev = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.realOverloaderChargingNeedsEngineRev"), true);

            self.realCurrentPowerConsumption = 0;
        end;

        ----------------------------------
        -- Set charge animations
        ----------------------------------
        self.chargeAnimations = {};
        i = 0;
        while true do
            local key = string.format(xmlKey .. "chargeAnimations.animation(%d)", i);
            local info = {}
            info.animName = getXMLString(xmlFile, key .. "#nameOfModelAnimations");
            info.animSpeed = 0;
            if info.animName == nil then
                local index = getXMLString(xmlFile, key .. "#index");
                if index == nil then
                    break;
                end;
                info.obj = Utils.indexToObject(self.components, index);
                info.axis = Utils.getNoNil(getXMLString(xmlFile, key .. "#rotateAxis"), "z");
                info.reverse = Utils.getNoNil(getXMLBool(xmlFile, key .. "#reverseDirection"), false);
                info.speed = Utils.getNoNil(getXMLInt(xmlFile, key .. "#rotateRpm"), 100);
                table.insert(self.chargeAnimations, info);
            else
                info.animAtMaxSpeed = false;
                if SpecializationUtil.hasSpecialization(ModelAnimated, self.specializations) then
                    table.insert(self.chargeAnimations, info);
                else
                    print("Warning: Missing ModelAnimated Specialization. Animation: \""..info.animName.."\" was not added.")
                    break;
                end;
            end;
            i = i + 1;
        end;

        ----------------------------------
        -- Set charge overlay if set
        ----------------------------------
        self.chargeOverlay = getXMLString(xmlFile, xmlKey .. "chargeOverlay#file");
        if self.chargeOverlay ~= nil then
            tempFile = Utils.getFilename(self.chargeOverlay, self.baseDirectory);
            self.chargeOverlay = Overlay:new("hudPDAControl", tempFile, g_currentMission.hudTipperOverlay.x, g_currentMission.hudTipperOverlay.y, g_currentMission.hudTipperOverlay.width, g_currentMission.hudTipperOverlay.height);
        end;

        ----------------------------------
        -- Get charge RPM value
        ----------------------------------
        self.chargeRPMValue = getXMLInt(xmlFile, xmlKey .. "chargeRPM#value");

        ----------------------------------
        -- Set charge sound
        ----------------------------------
        self.chargeStartSound = hasXMLProperty(xmlFile, xmlKey .. "chargeStartSound");
        self.chargeLoopSound = hasXMLProperty(xmlFile, xmlKey .. "chargeLoopSound");
        if self.chargeStartSound and self.chargeLoopSound then
            removeItem = false;
            local sounds = {["start"] = "Start", ["loop"] = "Loop",};
            self.chargeSound = {};
            self.chargeSound.soundSwitch = 0;
            self.chargeSound.isPlaying = false;
            for k, v in pairs(sounds) do
                self.chargeSound[k], removeItem = self:getAudioSound(xmlFile, xmlKey, ("charge%sSound"):format(v), v=="Loop");
            end;
            if removeItem then
                if self.chargeSound.start.sound then
                    delete(self.chargeSound.start.sound);
                end;
                if self.chargeSound.loop.sound then
                    delete(self.chargeSound.loop.sound);
                end;
                self.chargeSound = nil;
                print("Warning: chargeSound not loaded. Look above to check why.");
            end;
        end;
        self.chargeStartSound = nil;
        self.chargeLoopSound = nil;

        ----------------------------------
        -- Set pipe move sound
        ----------------------------------
        if hasXMLProperty(xmlFile, xmlKey .. "pipeMoveSound") then
            removeItem = false;
            self.pipeMoveSound, removeItem = self:getAudioSound(xmlFile, xmlKey, "pipeMoveSound", true);
            if removeItem then
                if self.pipeMoveSound.sound then
                    delete(self.pipeMoveSound.sound);
                end;
                self.pipeMoveSound = nil;
                print("Warning: pipeMoveSound not loaded. Look above to check why.");
            end;
        end;

        ----------------------------------
        -- Get pipe animations
        ----------------------------------

        --- foldPipeAnim
        self.pipeAnimations.fold = self:getPipeAnimation(xmlFile, xmlKey, "foldPipeAnim");
        if self.pipeAnimations.fold then
            self.pipeAnimations.fold.enableLRUDInPos = getXMLString(xmlFile, xmlKey .. "foldPipeAnim#enableManualMoveInPos");
            self.pipeAnimations.fold.manuelLRPosWhenFold = getXMLInt(xmlFile, xmlKey .. "foldPipeAnim#manuelLRPosWhenFold");
            self.pipeAnimations.fold.manuelUDPosWhenFold = getXMLInt(xmlFile, xmlKey .. "foldPipeAnim#manuelUDPosWhenFold");
        end;

        --- movePipeLeftRightAnim
        self.pipeAnimations.moveLR = self:getPipeAnimation(xmlFile, xmlKey, "movePipeLeftRightAnim");
        if self.pipeAnimations.moveLR then
            self.pipeAnimations.moveLR.disableFold = Utils.getNoNil(getXMLBool(xmlFile, xmlKey .. "movePipeLeftRightAnim#disableFoldAnimationOnMove"), false);
            self.pipeAnimations.moveLR.disableOnPos = getXMLString(xmlFile, xmlKey .. "movePipeLeftRightAnim#disableOnPosition");
            self.pipeAnimations.moveLR.linkWithFold = Utils.getNoNil(getXMLBool(xmlFile, xmlKey .. "movePipeLeftRightAnim#linkWithFold"), false);
            if InputBinding.INPUT_overloader_movePipeLeft == nil then
                self.pipeAnimations.moveLR = nil;
                print("Warning: No inputBinding with name: INPUT_overloader_movePipeLeft. movePipeLeftRightAnim not loaded.");
            elseif InputBinding.INPUT_overloader_movePipeRight == nil then
                self.pipeAnimations.moveLR = nil;
                print("Warning: No inputBinding with name: INPUT_overloader_movePipeRight. movePipeLeftRightAnim not loaded.");
            else
                self.pipeAnimations.moveLR.InputBinding = {};
                self.pipeAnimations.moveLR.InputBinding[1] = InputBinding.INPUT_overloader_movePipeLeft;
                self.pipeAnimations.moveLR.InputBinding[2] = InputBinding.INPUT_overloader_movePipeRight;
                self.pipeAnimations.moveLR.reverseDirection = Utils.getNoNil(getXMLString(xmlFile, xmlKey .. "movePipeLeftRightAnim#reverseDirection"), false);
            end;
        end;

        --- movePipeUpDownAnim
        self.pipeAnimations.moveUD = self:getPipeAnimation(xmlFile, xmlKey, "movePipeUpDownAnim");
        if self.pipeAnimations.moveUD then
            self.pipeAnimations.moveUD.disableFold = Utils.getNoNil(getXMLBool(xmlFile, xmlKey .. "movePipeUpDownAnim#disableFoldAnimationOnMove"), false);
            self.pipeAnimations.moveUD.disableOnPos = getXMLString(xmlFile, xmlKey .. "movePipeUpDownAnim#disableOnPosition");
            self.pipeAnimations.moveUD.linkWithFold = Utils.getNoNil(getXMLBool(xmlFile, xmlKey .. "movePipeUpDownAnim#linkWithFold"), false);
            if InputBinding.INPUT_overloader_movePipeUp == nil then
                self.pipeAnimations.moveUD = nil;
                print("Warning: No inputBinding with name: INPUT_overloader_movePipeUp. movePipeUpDownAnim not loaded.");
            elseif InputBinding.INPUT_overloader_movePipeDown == nil then
                self.pipeAnimations.moveUD = nil;
                print("Warning: No inputBinding with name: INPUT_overloader_movePipeDown. movePipeUpDownAnim not loaded.");
            else
                self.pipeAnimations.moveUD.InputBinding = {};
                self.pipeAnimations.moveUD.InputBinding[1] = InputBinding.INPUT_overloader_movePipeUp;
                self.pipeAnimations.moveUD.InputBinding[2] = InputBinding.INPUT_overloader_movePipeDown;
                self.pipeAnimations.moveUD.reverseDirection = Utils.getNoNil(getXMLString(xmlFile, xmlKey .. "movePipeUpDownAnim#reverseDirection"), false);
            end;
        end;

        ----------------------------------
        -- Get pipe light
        ----------------------------------
        self.pipeLight = Utils.indexToObject(self.components, getXMLString(xmlFile, xmlKey .. "pipelight#node"));
        if self.pipeLight ~= nil then
            if InputBinding.INPUT_overloader_pipeLight == nil then
                self.pipeLight = nil;
                print("Warning: No input inputBinding with name: INPUT_overloader_pipeLight. pipelight was not added.");
            else
                setVisibility(self.pipeLight, false);

                self.pipeLightSound = createSample("tss");
                loadSample(self.pipeLightSound, Utils.getFilename("$dataS2/sounds/switchFlashlight.wav", self.baseDirectory), false);
            end;
        end;

        ----------------------------------
        -- Get pipe particles
        ----------------------------------
        self.pipeParticles = {};
        local i = 0;
        while true do
            local key = string.format(xmlKey .. "pipeParticles.pipeParticle(%d)", i);
            local t = getXMLString(xmlFile, key .. "#type");
            if t == nil then
                break;
            end;
            local desc = FruitUtil.fruitTypes[t];
            if desc ~= nil then
                local fillType = FruitUtil.fruitTypeToFillType[desc.index];
                local currentPS = {};
                local particleNode = Utils.loadParticleSystem(xmlFile, currentPS, key, self.components, false, "particleSystems/wheatParticleSystem.i3d", self.baseDirectory);
                self.pipeParticles[fillType] = currentPS;
            end;
            i = i + 1;
        end;
    end;

    --------------------------------------------------------------------------------------------------------------------
    -- Only load this if we are server
    --------------------------------------------------------------------------------------------------------------------
    if self.isServer then
        ----------------------------------
        -- Set charge triggers
        ----------------------------------
        self.chargeTriggers = {};
        i = 0;
        while true do
            local key = string.format(xmlKey .. "chargeTriggers.Trigger(%d)", i);
            local index = getXMLString(xmlFile, key .. "#index");
            if index == nil then
                break;
            end;
            local val = Utils.indexToObject(self.components, index);
            self.chargeTriggers[val] = Utils.getNoNil(getXMLString(xmlFile, key .. "#pipePosition"), "none");

            i = i + 1;
        end;
        for k, v in pairs(self.chargeTriggers) do
            addTrigger(k, "onChargeTrigger", self);
        end;

        ----------------------------------
        -- Get charge liters pr second.
        ----------------------------------
        self.literPerSecond = Utils.getNoNil(getXMLInt(xmlFile, xmlKey .. "literPerSecond#value"), 250);

        ----------------------------------
        -- More Realistic Mod Settings.
        ----------------------------------
        if self.isRealistic then
            self.isRealisticTrailer = true;

            -- "adding" the "realMaxDischargeSpeed" to the trailer tipping animation
            local i = 0;
            while true do
                local key = string.format("vehicle.tipAnimations.tipAnimation(%d)", i);
                if not hasXMLProperty(xmlFile, key) then
                    break;
                end;

                if self.tipAnimations[i+1]~= nil then
                    self.tipAnimations[i+1].realMaxDischargeSpeed = Utils.getNoNil(getXMLFloat(xmlFile, key.."#realMaxDischargeSpeed"), self.literPerSecond);
                    self.tipAnimations[i+1].realDischargeAnimTimeFx = Utils.getNoNil(getXMLFloat(xmlFile, key.."#realDischargeAnimTimeFx"), 1);
                end;

                i = i + 1;
            end;
        end;
    end;
end;

--- Engien delete function.
--
function overloader:delete()
    if self.chargeSound ~= nil then
        delete(self.chargeSound.start.sound);
        delete(self.chargeSound.loop.sound);
    end;
    if self.pipeMoveSound ~= nil then
        delete(self.pipeMoveSound.sound);
    end;
    for _, particleSystem in pairs(self.pipeParticles) do
        Utils.deleteParticleSystem(particleSystem);
    end;
    if self.isServer then
        for k, _ in pairs(self.chargeTriggers) do
            removeTrigger(k);
        end;
    end;
end;

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

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

--- Get information from server when joining multiplayer game
-- @param streamId   The MP unique stream id used betwean host and client
-- @param connection The MP connection
--
function overloader:readStream(streamId, connection)
    local i;
    local tableNum;
    local debugNum = 9;
    -- Set trailer to overload info if one is in range
    local bool; -- = streamReadBool(streamId);
    if streamReadBool(streamId) then
        self.isTrailerInRange = true;
        self.sendIsTrailerInRange = self.isTrailerInRange;
        self.trailerToOverload = networkGetObject(streamReadInt32(streamId));
        if self.trailerToOverload then
            self.trailerFound = self.trailerToOverload.rootNode;
        else
            self.isTrailerInRange = false;
        end;
    end;

    -- Set animation positions and if it's playing
    bool = streamReadBool(streamId);
    if bool then
        -- Set is playing
        self.pipeIsMoving = streamReadBool(streamId);
        self.pipePosition = streamReadString(streamId);
        -- Set saved time for linked animation
        self.pipeAnimations.savedTime = streamReadFloat32(streamId); --self.pipeAnimations.savedTime
        -- Set animation infoes
        tableNum = streamReadInt8(streamId);
        for i = 1, tableNum, 1 do
            local key = streamReadString(streamId);
            local name = streamReadString(streamId);
            local isPlaying = streamReadBool(streamId);
            local direction = streamReadInt8(streamId);
            local animTime = streamReadFloat32(streamId);
            local linkWithFold = streamReadBool(streamId);
            self.pipeAnimations[key].isPlaying = isPlaying;
            self.pipeAnimations[key].direction = direction;

            if linkWithFold then
                self.pipeAnimations.savedTime = animTime;
            end;
            if isPlaying then
                self.pipeAnimations[key].handler.playAnimation(self, name, direction, animTime, true);
            else
                self.pipeAnimations[key].handler.setAnimationStopTime(self, name, animTime);
                self.pipeAnimations[key].handler.playAnimation(self, name, 10, self.pipeAnimations[key].handler.getAnimationTime(self, name), true);
            end;
        end;
    end;

    -- Set if wee are charging
    self.isCharging = streamReadBool(streamId);
end;

--- Send information to client when they join the multiplayer game
-- @param streamId   The MP unique stream id used betwean host and client
-- @param connection The MP connection
--
function overloader:writeStream(streamId, connection)
    local i = 0;
    local isNotEmpty = false;

    -- Send trailer to overload info if one is in range
    streamWriteBool(streamId, self.isTrailerInRange);
    if self.isTrailerInRange then
        streamWriteInt32(streamId, networkGetObjectId(self.trailerToOverload));
        --self.trailerFound = rootNote of self.trailerToOverload object
    end;

    -- Send animation positions and if it's playing
    i = 0;
    isNotEmpty = next(self.pipeAnimations) ~= nil;
    streamWriteBool(streamId, isNotEmpty);
    if isNotEmpty then
        local Vanims = {};
        local Manims = {};

        streamWriteBool(streamId, self.pipeIsMoving); -- is pipe moving
        streamWriteString(streamId, self.pipePosition); -- what is the pipe position
        streamWriteFloat32(streamId, self.pipeAnimations.savedTime); --self.pipeAnimations.savedTime

        for k, v in pairs(self.pipeAnimations) do
            if k == "fold" or k == "moveLR" or k == "moveLR" then
                i = i + 1;
            end;
        end;
        streamWriteInt8(streamId, i); --Num in table
        for k, v in pairs(self.pipeAnimations) do
            local animTime = 0;
            if k == "fold" or k == "moveLR" or k == "moveLR" then
                streamWriteString(streamId, k);      --table: fold, moveLR, moveUD
                streamWriteString(streamId, v.name); --Animation name
                streamWriteBool(streamId, v.isPlaying); --Is animation playing
                streamWriteInt8(streamId, v.direction); --Direction of animation
                if k == "fold" or v.linkWithFold then
                    animTime = self.pipeAnimations.savedTime;
                else
                    animTime = v.handler.getAnimationTime(self, v.name);
                end;
                streamWriteFloat32(streamId, animTime); --current animation time
                streamWriteBool(streamId, v.linkWithFold);
            end;
        end;
    end;

    -- Send if wee are charging
    streamWriteBool(streamId, self.isCharging);
end;

--- Get updated information from server
-- @param streamId   The MP unique stream id used betwean host and client
-- @param timestamp  Server current time.
-- @param connection The MP connection
--
function overloader:readUpdateStream(streamId, timestamp, connection)
    if connection:getIsServer() then
        -- Set trailer to overload info if one is in range
        --local bool = streamReadBool(streamId);
        --if self.isTrailerInRange ~= bool then
        if streamReadBool(streamId) then
            self.isTrailerInRange = true;
            self.trailerToOverload = networkGetObject(streamReadInt32(streamId));
            self.trailerFound = self.trailerToOverload.rootNode;
        else
            self.isTrailerInRange = false;
            self.trailerFound = false;
            self.trailerToOverload = nil;
        end;
        --end;
    end;
end;

--- Send updated information to clients
-- @param streamId   The MP unique stream id used betwean host and client
-- @param connection The MP connection
-- @param dirtyMask  Unsure what this is!?!
--
function overloader:writeUpdateStream(streamId, connection, dirtyMask)
    if not connection:getIsServer() then
        -- Send trailer to overload info if one is in range
        streamWriteBool(streamId, self.isTrailerInRange);
        --if self.sendIsTrailerInRange ~= self.isTrailerInRange then
        if self.isTrailerInRange then
            --self.sendIsTrailerInRange = self.isTrailerInRange;
            --if self.isTrailerInRange then
            streamWriteInt32(streamId, networkGetObjectId(self.trailerToOverload));
            --end;
        end;
    end;
end;

--------------------------------------------------------------------------------------------------------------------
-- Handle onDetach.
--------------------------------------------------------------------------------------------------------------------
function overloader:onDetach()
    if self.chargeRPMValue ~= nil and self.lastAttacherVehicle ~= nil then
        self.lastAttacherVehicle:setThrottleRPM(self);
        self.lastAttacherVehicle = nil;
    end;
    self.isCharging = false;
end;

--------------------------------------------------------------------------------------------------------------------
--- Engien draw function.
-- Updates on each new frame
--------------------------------------------------------------------------------------------------------------------
function overloader:draw()
    if self.pipeLight ~= nil then
        g_currentMission:addHelpButtonText( g_i18n:getText("overloader_togglePipeLight"), InputBinding.INPUT_overloader_pipeLight);
    end;
    if self.cpAI == "off" then
        --------------------------------------------------------------------------------------------------------------------
        -- moveLeftRigh toolTip
        --------------------------------------------------------------------------------------------------------------------
        if self.pipeAnimations.moveLR and (self.pipeAnimations.moveLR.disableOnPos == nil or self.pipeAnimations.moveLR.disableOnPos ~= self.pipePosition) then
            g_currentMission:addHelpButtonText( g_i18n:getText("overloader_movePipeLeft"), InputBinding.INPUT_overloader_movePipeLeft);
            g_currentMission:addHelpButtonText( g_i18n:getText("overloader_movePipeRight"), InputBinding.INPUT_overloader_movePipeRight);
        end;

        --------------------------------------------------------------------------------------------------------------------
        -- moveUpDown toolTip
        --------------------------------------------------------------------------------------------------------------------
        if self.pipeAnimations.moveUD and (self.pipeAnimations.moveUD.disableOnPos == nil or self.pipeAnimations.moveUD.disableOnPos ~= self.pipePosition) then
            g_currentMission:addHelpButtonText( g_i18n:getText("overloader_movePipeUp"), InputBinding.INPUT_overloader_movePipeUp);
            g_currentMission:addHelpButtonText( g_i18n:getText("overloader_movePipeDown"), InputBinding.INPUT_overloader_movePipeDown);
        end;

        --------------------------------------------------------------------------------------------------------------------
        -- fold toolTip
        --------------------------------------------------------------------------------------------------------------------
        if self.pipeAnimations.fold then
            if (self.pipeAnimations.fold.isPlaying and self.pipeAnimations.fold.direction == 1) or (not self.pipeAnimations.fold.isPlaying and self.pipePosition ~= "in") then
                g_currentMission:addHelpButtonText( g_i18n:getText("overloader_foldPipe"), InputBinding.IMPLEMENT_EXTRA2);
            else
                g_currentMission:addHelpButtonText( g_i18n:getText("overloader_unfoldPipe"), InputBinding.IMPLEMENT_EXTRA2);
            end;
        end;

        --------------------------------------------------------------------------------------------------------------------
        -- charge toolTip
        --------------------------------------------------------------------------------------------------------------------
        if self.isCharging then
            g_currentMission:addHelpButtonText( g_i18n:getText("overloader_chargeOff"), InputBinding.IMPLEMENT_EXTRA);
        else
            if self.isTrailerInRange and self.fillLevel <= 0.0 then
                g_currentMission:addExtraPrintText( (g_i18n:getText("overloader_isEmpty")):format(self.ULWName));
            elseif self.isTrailerInRange and self.trailerToOverload:allowFillType(self.currentFillType, true) then
                g_currentMission:addHelpButtonText( g_i18n:getText("overloader_chargeOn"), InputBinding.IMPLEMENT_EXTRA);
            end;
        end;
    else
        g_currentMission:addExtraPrintText( (g_i18n:getText("overloader_cp_controls")):format(self.ULWName));
    end;

    --------------------------------------------------------------------------------------------------------------------
    -- Display charge icon if in charge range
    --------------------------------------------------------------------------------------------------------------------
    if self.chargeOverlay ~= nil and self.isCharging == false and self.isTrailerInRange and self.fillLevel > 0 then
        self.chargeOverlay:render();
    end;

    --------------------------------------------------------------------------------------------------------------------
    -- Display warning message
    --------------------------------------------------------------------------------------------------------------------
    if self.cpAI == "off" and self.ChargeTipWarning then
        setTextBold(false);
        setTextColor(1, 1, 1, 1);
        local msg = g_i18n:getText(("overloader_warningChargeTipNotInPos")):format( g_i18n:getText(string.format("overloader_%sWarningChargeTipPos", self.ChargeTipWarning)) );
        g_currentMission:addWarning(msg);
    end;
end;

--- Update Tick - Slower than update
-- @param dt Time since last run in milliseconds
--
function overloader:updateTick(dt)
    --- Handle slower update states
    --------------------------------------------------------------------------------------------------------------------
    if self.isClient then
        self:handleChargeTip();
        self:handleChargeRPM();
        self:handlePipeMoveSound();
        self:handlePipeParticles();
        self:handleChargeSound(dt);
    end;
    self:handleChargeTriggers();

    --- Let the server handle the grain transfer
    --------------------------------------------------------------------------------------------------------------------
    self:handleGrainTransfer(dt);

    --- More Realistic handler
    if self.isRealistic and self.isServer and self.isActive then
        self.realToolNeedEngineRev = false;
        self.realCurrentPowerConsumption = 0;
        --- the trailer is "charging"
        if self.isCharging
        --- or the trailer is "charge tipping"
        or (self.canChargeTip and (self.tipState == Trailer.TIPSTATE_OPENING or self.tipState == Trailer.TIPSTATE_OPEN)) then
            self.realCurrentPowerConsumption = self.realCurrentPowerConsumption + self.realOverloaderChargePowerConsumption;
            if self.realOverloaderChargingNeedsEngineRev then
                self.realToolNeedEngineRev = true;
            end;
        end;

        --- the trailer is "normal tipping"
        if not self.canChargeTip and (self.tipState == Trailer.TIPSTATE_OPENING
        or (self.realTippingPowerConsumptionWhenOpened and self.tipState == Trailer.TIPSTATE_OPEN)
        or (self.realTippingPowerConsumptionWhenClosing and self.tipState == Trailer.TIPSTATE_CLOSING)) then
            self.realCurrentPowerConsumption = self.realCurrentPowerConsumption + self.realTippingPowerConsumption;
            if self.realTrailerUnloadingNeedsEngineRev then
                self.realToolNeedEngineRev = true;
            end;
        end;

        if self.pipeIsMoving then
            self.realCurrentPowerConsumption = self.realCurrentPowerConsumption + self.realOverloaderPipeMovePowerConsumption;
        end
    end;
end;

--- Update - Faster than updateTick
-- @param dt Time since last run in milliseconds
--
function overloader:update(dt)
    --------------------------------------------------------------------------------------------------------------------
    -- Pipe light key input handler:
    --------------------------------------------------------------------------------------------------------------------
    if self.pipeLight ~= nil and self.isClient and self:getIsActiveForInput(false) and not self:hasInputConflictWithSelection() then
        if InputBinding.hasEvent(InputBinding.INPUT_overloader_pipeLight) then
            setVisibility(self.pipeLight, not getVisibility(self.pipeLight));

            if self:getIsActiveForSound() then
                playSample(self.pipeLightSound, 1, 1, 0);
            end;
        end;
    end;

    --------------------------------------------------------------------------------------------------------------------
    -- Courseplay plugin
    --------------------------------------------------------------------------------------------------------------------
    if not self.cpChecked then
        self.cpChecked = true;
        --- Check if we need to use the old or new cp handler.
        if courseplay ~= nil and courseplay.versionDisplay == nil then
            self.cpUseOldSystem = true;
        elseif courseplay ~= nil and courseplay.versionDisplay ~= nil then
            for i=1,3,1 do
                if tonumber(courseplay.versionDisplay[i]) < self.cpVersionNeeded[i] then
                    self.cpUseOldSystem = true;
                    break;
                end
            end
        end
    end

    if self:getIsActive() and self.attacherVehicle ~= nil and self.attacherVehicle.drive
            and (self.attacherVehicle.ai_mode == 1 or self.attacherVehicle.ai_mode == 3) then

        --- This is only used for older CP versions.
        if self.cpUseOldSystem and self.attacherVehicle.ai_mode == 3 then

            --- Move pipe if not in the right position
            ------------------------------------------------------------------------------------------------------------------
            if self.attacherVehicle.ai_state == 0 and self.attacherVehicle.movingDirection == 0 and self.cpAI ~= "out" then
                self.cpAI = "out";
            end;
            if self.filllevel == 0 or self.attacherVehicle.ai_state > 0 and self.cpAI ~= "in" then
                self.cpAI = "in";
            end;

            --- Turn on charge if in position
            ------------------------------------------------------------------------------------------------------------------
            if self.isTrailerInRange and self.attacherVehicle ~= nil and self.attacherVehicle.isMotorStarted then
                if self.trailerToOverload:allowFillType(self.currentFillType, true) then
                    self.isCharging = true;
                end;
            end;
        end

        if self.canChargeTip and self.attacherVehicle.ai_mode == 1 then
            if self.attacherVehicle.cp.currentTipTrigger then
                if self.attacherVehicle.cp.currentTipTrigger.stationName == "AlternativeTipUniversal Trigger" and (self.AlternativeTippingInPos == "in" or self.AlternativeTippingInPos == "out") then
                    self.cpAI = self.AlternativeTippingInPos;
                elseif self.attacherVehicle.cp.currentTipTrigger.stationName ~= "AlternativeTipUniversal Trigger" and (self.ChargeTipInPos == "in" or self.ChargeTipInPos == "out") then
                    self.cpAI = self.ChargeTipInPos;
                else
                    self.cpAI = "in";
                end;
            else
                self.cpAI = "in";
            end;
        end;
    else
        self.cpAI = "off";
    end;

    if self.cpAI == "off" then
        --- Key input handler:
        --------------------------------------------------------------------------------------------------------------------
        if self.isClient and self.isSelected and self:getIsActiveForInput() and not self:hasInputConflictWithSelection() then
            if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA) then
                self.isCharging = not self.isCharging;
            end;
        end;

        --- Check if we are actually able to charge
        --------------------------------------------------------------------------------------------------------------------
        if self.isCharging then
            if self.isTrailerInRange and self.attacherVehicle ~= nil and self.attacherVehicle.isMotorStarted then
                if not self.trailerToOverload:allowFillType(self.currentFillType, true) then
                    self.isCharging = false;
                end;
            else
                self.isCharging = false;
            end;
        end;
    end;

    --- If we are chargeing, then send the info to all
    --------------------------------------------------------------------------------------------------------------------
    if self.sendIsCharging ~= self.isCharging then
        self.sendIsCharging = self.isCharging;
        OverloaderChargeEvent:sendEvent(self, self.isCharging);
    end

    --- Handle fast update states
    --------------------------------------------------------------------------------------------------------------------
    if self.isClient then
        self:handleChargeAnimation(dt);
        self:handlePipeMoveAnimation()
    end;
end;

--- Handle Charge Animation
-- @param dt Time since last run in milliseconds
--
function overloader:handleChargeAnimation(dt)
    for k, v in pairs(self.chargeAnimations) do
        if self.isCharging or self.isChargTipping or v.animSpeed > 0 then
            if self.isCharging or self.isChargTipping then
                v.animSpeed = math.min(v.animSpeed + (dt/1000.0), 1);
            else
                v.animSpeed = math.max(v.animSpeed - (dt/1000.0), 0);
            end;
            if v.animName ~= nil and v.animSpeed < 1 then
                --- Start model animation
                ------------------------------------------------------------------------------------------------------------------
                if not v.animAtMaxSpeed then
                    self:playModelAnimation(v.animName, v.animSpeed, self:getModelAnimationTime(v.animName), true);
                end;
                if v.animSpeed == 1 then
                    v.animAtMaxSpeed = true;
                else
                    v.animAtMaxSpeed = false;
                end;
            elseif v.axis and v.speed and v.obj then
                --- Handle Simple Rotaion Animations
                ------------------------------------------------------------------------------------------------------------------
                local rot = {};
                rot["x"], rot["y"], rot["z"] = 0, 0, 0;
                rot[v.axis] = ((v.speed * 360 / 60) * (dt/1000.0)) * v.animSpeed;
                if v.reverse then
                    rot[v.axis] = -rot[v.axis];
                end;
                rot = Utils.getRadiansFromString(("%f %f %f"):format(rot["x"], rot["y"], rot["z"]), 3);
                rotate(v.obj, rot[1], rot[2], rot[3]);
            end;
        else
            --- Stop model animation if on and charging is of
            ------------------------------------------------------------------------------------------------------------------
            if v.animName ~= nil and self:getIsModelAnimationPlaying(v.animName) then
                self:stopModelAnimation(v.animName, true);
            end;
        end;
    end;
end;

--- Handle charge RPM
-- Handle the charge RPM if the attacherVehicle have throttleRPM specialization
function overloader:handleChargeRPM()
    if self.chargeRPMValue ~= nil and self.attacherVehicle ~= nil and self.attacherVehicle.setThrottleRPM ~= nil and not self.attacherVehicle.isRealistic then
        if self.isCharging or self.isChargTipping then
            if self.lastAttacherVehicle == nil then
                self.lastAttacherVehicle = self.attacherVehicle;
            end;
            self.attacherVehicle:setThrottleRPM(self, self.chargeRPMValue);
        else
            self.attacherVehicle:setThrottleRPM(self);
            self.lastAttacherVehicle = nil;
        end;
    end;
end;

--- Handle Grain Transfer
-- @param dt Time since last run in milliseconds
--
function overloader:handleGrainTransfer(dt)
    if self.literPerSecond ~= nil and self.isServer and self.isCharging then
        if self.trailerToOverload == nil or self.trailerToOverload.fillLevel == self.trailerToOverload.capacity or self.fillLevel == 0 then
            self.isCharging = false;
        else
            local deltaLevel = self.literPerSecond * (dt/1000);
            deltaLevel = math.min(deltaLevel, self.trailerToOverload.capacity - self.trailerToOverload.fillLevel);
            self.trailerToOverload:setFillLevel(self.trailerToOverload.fillLevel + math.min(deltaLevel, self.fillLevel), self.currentFillType);
            self:setFillLevel(self.fillLevel - math.min(deltaLevel, self.fillLevel), self.currentFillType);
        end;
    end;
end;

--- Handle charge sound
-- @param dt Time since last run in milliseconds
--
function overloader:handleChargeSound(dt)
    if self.chargeSound ~= nil then
        if self.isCharging or self.isChargTipping then
            if not self.chargeSound.isPlaying or self.chargeSound.soundSwitch > 0 then
                self.chargeSound.soundSwitch = self.chargeSound.soundSwitch - dt;
                self.chargeSound.isPlaying = true;
                if not getVisibility(self.chargeSound.start.sound) then
                    setVisibility(self.chargeSound.start.sound, true);
                    self.chargeSound.soundSwitch = self.chargeSound.start.duration;
                end;

                if self.chargeSound.soundSwitch <= 0 and not getVisibility(self.chargeSound.loop.sound) then
                    setVisibility(self.chargeSound.loop.sound, true);
                end;
            end;
        else
            if self.chargeSound.isPlaying then
                setVisibility(self.chargeSound.start.sound, false);
                setVisibility(self.chargeSound.loop.sound, false);
                self.chargeSound.isPlaying = false;
            end;
        end;
    end;
end;

--- Handle pipe move animations
--
function overloader:handlePipeMoveAnimation()
    local fold = self.pipeAnimations.fold;
    local moveLRUD;
    local isMoving = false;
    local lockFold = false;
    local axisPos = 0;
    local savedTime;

    if self.isClient and self.cpAI ~= "off" and fold ~= nil then
        if self.cpAI == "out" and self.pipePosition ~= "out" then
            axisPos = 1;
        elseif self.cpAI == "in" and self.pipePosition ~= "in" then
            axisPos = -1;
        else
            axisPos = 0;
        end;
        if axisPos == 0 then
            if fold.isPlaying then
                self.pipeAnimations.savedTime = fold.handler.getAnimationTime(self, fold.name);
                fold.isPlaying = false;
                fold.wasPlaying = true;
            end;
        elseif axisPos ~= fold.direction then
            fold.direction = axisPos;
            fold.handler.playAnimation(self, fold.name, fold.direction, self.pipeAnimations.savedTime);
            fold.isPlaying = true;
            isMoving = true;
            self.pipePosition = "none";
            --OverloaderAnimEvent:sendEvent(self, self.pipeAnimations.savedTime, self.pipePosition);
            OverloaderPipeIsMovingEvent:sendEvent(self, true);
            self.isSenderOfPipeEventIsMoving = true;
        end;
    end;

    if self.isClient and self.isSelected and self:getIsActiveForInput() and not self:hasInputConflictWithSelection() then
        -- Handle left and right pipe animation
        for _,v in pairs({"moveLR","moveUD"}) do
            moveLRUD = self.pipeAnimations[v];
            if moveLRUD ~= nil then
                if self.cpAI == "off" then
                    if moveLRUD.disableOnPos == nil or moveLRUD.disableOnPos ~= self.pipePosition then
                        if InputBinding.isPressed(moveLRUD.InputBinding[1]) and InputBinding.isPressed(moveLRUD.InputBinding[2]) == false then
                            axisPos = 1;
                        elseif InputBinding.isPressed(moveLRUD.InputBinding[2]) and InputBinding.isPressed(moveLRUD.InputBinding[1]) == false then
                            axisPos = -1;
                        else
                            axisPos = 0;
                        end;

                        if moveLRUD.reverseDirection == true then
                            if axisPos < 0 then
                                axisPos = math.abs(axisPos);
                            else
                                axisPos = -axisPos;
                            end;
                        end;
                        if moveLRUD.linkWithFold and (fold.isPlaying or moveLRUD.isPlaying == false) then
                            if fold.isPlaying then
                                self.pipeAnimations.savedTime = fold.handler.getAnimationTime(self, fold.name);
                            end;
                            savedTime = self.pipeAnimations.savedTime;
                        else
                            savedTime = moveLRUD.handler.getAnimationTime(self, moveLRUD.name);
                        end;

                        if axisPos >= -0.01 and axisPos <= 0.01 then
                            axisPos = 0;
                        elseif (axisPos <= -0.01 and savedTime == 0) or (axisPos >= 0.01 and savedTime == 1) then
                            axisPos = 0;
                        end;

                        if fold.enableLRUDInPos ~= nil and fold.enableLRUDInPos ~= self.pipePosition then
                            axisPos = 0;
                        end;
                        if axisPos == 0 then
                            if moveLRUD.isPlaying then
                                if moveLRUD.linkWithFold then
                                    self.pipeAnimations.savedTime = moveLRUD.handler.getAnimationTime(self, moveLRUD.name);
                                end;
                                moveLRUD.isPlaying = false;
                                moveLRUD.wasPlaying = true;
                            end;
                        elseif axisPos ~= 0 then
                            if axisPos ~= moveLRUD.direction then
                                moveLRUD.direction = axisPos;
                                moveLRUD.handler.playAnimation(self, moveLRUD.name, axisPos, savedTime);
                                moveLRUD.isPlaying = true;
                                moveLRUD.savedTime = nil;
                                isMoving = true;
                                if moveLRUD.linkWithFold then
                                    self.pipePosition = "none";
                                    OverloaderAnimEvent:sendEvent(self, self.pipeAnimations.savedTime, self.pipePosition);
                                end;
                                OverloaderPipeIsMovingEvent:sendEvent(self, true);
                                self.isSenderOfPipeEventIsMoving = true;
                            end;
                            if moveLRUD.disableFold then
                                lockFold = true;
                            end;
                        end;
                    end;
                else
                    if moveLRUD.linkWithFold and moveLRUD.isPlaying then
                        self.pipeAnimations.savedTime = moveLRUD.handler.getAnimationTime(self, moveLRUD.name);
                        moveLRUD.isPlaying = false;
                        moveLRUD.wasPlaying = true;
                    end;
                end;
            end;
        end;

        -- Handle foldPipe animation
        if fold ~= nil then
            if self.cpAI == "off" then
                if lockFold then
                    if fold.isPlaying then
                        fold.isPlaying = false;
                        self.pipeAnimations.savedTime = fold.handler.getAnimationTime(self, fold.name);
                    end;
                elseif InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA2) then
                    if (fold.isPlaying and fold.direction == 1) or (not fold.isPlaying and self.pipePosition ~= "in") then
                        fold.direction = -1;
                    else
                        fold.direction = 1;
                    end;

                    fold.handler.playAnimation(self, fold.name, fold.direction, self.pipeAnimations.savedTime);
                    fold.isPlaying = true;
                    isMoving = true;
                    self.pipePosition = "none";
                    OverloaderAnimEvent:sendEvent(self, self.pipeAnimations.savedTime, self.pipePosition);
                    OverloaderPipeIsMovingEvent:sendEvent(self, true);
                    self.isSenderOfPipeEventIsMoving = true;
                    if fold.enableLRUDInPos ~= nil then
                        for _,v in pairs({"moveLR","moveUD"}) do
                            local attribute = {["moveLR"] = "manuelLRPosWhenFold", ["moveUD"] = "manuelUDPosWhenFold",};
                            moveLRUD = self.pipeAnimations[v];
                            if moveLRUD ~= nil then
                                if fold[attribute[v]] ~= nil and moveLRUD.linkWithFold == false then
                                    local direction = fold[attribute[v]];
                                    if fold.direction == -1 then
                                        if direction < 0 then
                                            direction = math.abs(direction);
                                        else
                                            direction = -direction;
                                        end;
                                    end;
                                    moveLRUD.handler.playAnimation(self, moveLRUD.name, direction, moveLRUD.handler.getAnimationTime(self, moveLRUD.name));
                                end;
                            end;
                        end;
                    end;
                end;
            end;
        end;
    end;

    if self.pipeIsMoving or isMoving then
        for k, v in pairs(self.pipeAnimations) do
            if type(v) == "table" then
                if (v.handler.getIsAnimationPlaying(self, v.name) and v.isPlaying)
                        or (k ~= "fold" and fold.enableLRUDInPos ~= nil and fold.isPlaying == true and v.linkWithFold == false and v.handler.getIsAnimationPlaying(self, v.name)) then
                    isMoving = true;
                else
                    if (v.isPlaying or v.wasPlaying) and (k == "fold" or v.linkWithFold) then
                        if v.handler.getAnimationTime(self, v.name) == 1 then
                            self.pipePosition = "out";
                            self.pipeAnimations.savedTime = 1;
                        elseif v.handler.getAnimationTime(self, v.name) == 0 then
                            self.pipePosition = "in";
                            self.pipeAnimations.savedTime = 0;
                        else
                            self.pipeAnimations.savedTime = v.handler.getAnimationTime(self, v.name);
                        end;
                        OverloaderAnimEvent:sendEvent(self, self.pipeAnimations.savedTime, self.pipePosition);
                    end;

                    self.pipeAnimations[k].wasPlaying = nil;
                    self.pipeAnimations[k].direction = 0;
                    v.handler.stopAnimation(self, v.name);
                    self.pipeAnimations[k].isPlaying = false;
                end;
            end;
        end;
    end;
    self.pipeIsMoving = isMoving;

    if self.isSenderOfPipeEventIsMoving and self.pipeIsMoving == false then
        self.isSenderOfPipeEventIsMoving = false;
        OverloaderPipeIsMovingEvent:sendEvent(self, false);
    end;

end;

--- Handle pipe move sound
--
function overloader:handlePipeMoveSound()
    if self.pipeMoveSound ~= nil then
        if self.pipeIsMoving or self.pipeEventIsMoving then
            if not getVisibility(self.pipeMoveSound.sound) then
                setVisibility(self.pipeMoveSound.sound, true);
            end;
        else
            if getVisibility(self.pipeMoveSound.sound) then
                setVisibility(self.pipeMoveSound.sound, false);
            end;
        end;
    end;
end;

--- Handle pipe charge particles
--
function overloader:handlePipeParticles()
    if self.isCharging then
        if not self.lastFillType then
            self.lastFillType = self.currentFillType;
        end;
        Utils.setEmittingState(self.pipeParticles[self.lastFillType], true);
    else
        Utils.setEmittingState(self.pipeParticles[self.lastFillType], false);
        self.lastFillType = nil;
    end;
end;

--- Handle charge tip
--
function overloader:handleChargeTip()
    if self.canChargeTip then
        if self.tipState == Trailer.TIPSTATE_OPENING or self.tipState == Trailer.TIPSTATE_OPEN then
            self.isChargTipping = true;
        else
            self.isChargTipping = false;
        end;
    end;
end;

--- Hangle Charge Triggers
-- Handle trailer triggers that was fired.
function overloader:handleChargeTriggers()
    if self.isServer and self.attacherVehicle ~= nil and self.attacherVehicle.isMotorStarted then
        local isTrailerInRange = false;
        local trailerFound = false;
        local trailerToOverload;
        for k, v in pairs(self.chargeTriggerList) do
            if v.pipePosition == "none" or (isTrailerInRange == false and self.pipeIsMoving == false and v.pipePosition == self.pipePosition) then
                isTrailerInRange = true;
                trailerFound = v.otherId;
                trailerToOverload = g_currentMission.nodeToVehicle[v.otherId];
                if v.pipePosition == "none" then
                    break;
                end;
            end;
        end;

        -- Only change if needed to
        if self.trailerFound ~= trailerFound then
            self.isTrailerInRange = isTrailerInRange;
            self.trailerFound = trailerFound;
            self.trailerToOverload = trailerToOverload;
        end;
    elseif self.isServer and self.isTrailerInRange then
        self.isTrailerInRange = false;
        self.trailerFound = false;
        self.trailerToOverload = nil;
    end;
end;

--- On Charge Trigger - Handle trigger events
-- @param triggerId    The triggerId of the trigger that fired.
-- @param otherId      The node id that was hit.
-- @param onEnter      True if entered the node.
-- @param onLeave      True if leaving the node.
-- @param onStay       True if staying in the node.
-- @param otherShapeId The shape id that was hit.
--
function overloader:onChargeTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if (onStay or onEnter) and self.chargeTriggers[triggerId] then
        if self.chargeTriggerList[triggerId] == nil then
            if g_currentMission.nodeToVehicle[otherId] ~= nil then
                self.chargeTriggerList[triggerId] = {};
                self.chargeTriggerList[triggerId].counter = 1;
                self.chargeTriggerList[triggerId].otherId = otherId;
                self.chargeTriggerList[triggerId].pipePosition = self.chargeTriggers[triggerId];
            end;
        else
            self.chargeTriggerList[triggerId].counter = self.chargeTriggerList[triggerId].counter + 1;
        end;
    elseif onLeave then
        if self.chargeTriggerList[triggerId] then
            if self.chargeTriggerList[triggerId].counter == 1 then
                self.chargeTriggerList[triggerId] = nil;
            else
                self.chargeTriggerList[triggerId].counter = self.chargeTriggerList[triggerId].counter - 1;
            end;
        end;
    end;
end;

--- Get audio sound from xmlFile
-- @param xmlFile   string.  Xml file path
-- @param xmlKey    string.  The key path to the handler.
-- @param xmlHandle string.  The handler in the xml file.
-- @param loops     boolean. Do the audio sound loop?
--
-- @returns table, bool. Returns the sound table and a boolean on if it was created.
--
function overloader:getAudioSound(xmlFile, xmlKey, xmlHandle, loops)
    local deleteSound = false;
    local sound = {};
    sound.attachToNode = getXMLString(xmlFile, xmlKey .. xmlHandle .. "#attachToNode");
    if sound.attachToNode == nil then
        print(("Warning: \"attachToNode\" attribut missing in %s."):format(xmlHandle));
        deleteSound = true;
    end;
    sound.file = getXMLString(xmlFile, xmlKey .. xmlHandle .. "#file");
    if sound.file == nil then
        print(("Warning: \"file\" attribut missing in %s."):format(xmlHandle));
        deleteSound = true;
    end;

    if not deleteSound then
        sound.radius = Utils.getNoNil(getXMLInt(xmlFile, xmlKey .. xmlHandle .. "#radius"), 30);
        sound.innerRadius = Utils.getNoNil(getXMLInt(xmlFile, xmlKey .. xmlHandle .. "#innerRadius"), 15);
        sound.volume = Utils.getNoNil(getXMLFloat(xmlFile, xmlKey .. xmlHandle .. "#volume"), 1);
        --sound.loops = Utils.getNoNil(getXMLBool(xmlFile, xmlKey .. xmlHandle .. "#loops"), false);
        if loops then sound.loops = 0; else sound.loops = 1; end;

        sound.file = Utils.getFilename(sound.file, self.baseDirectory);
        sound.sound = createAudioSource(xmlHandle, sound.file, sound.radius, sound.innerRadius, sound.volume, sound.loops);
        if sound.loops == 1 then
            sound.duration = getSampleDuration(getAudioSourceSample(sound.sound));
        end;

        sound.attachToNode = Utils.indexToObject(self.components, sound.attachToNode);
        link(sound.attachToNode, sound.sound);
        setVisibility(sound.sound, false);
    end;

    return sound, deleteSound;
end;

--- Get pipe animation from xmlFile
-- @param xmlFile   string. Xml file path
-- @param xmlKey    string. The key path to the handler.
-- @param xmlHandle string. The handler in the xml file.
--
-- @returns table. Returns the animation table or nil if not created.
--
function overloader:getPipeAnimation(xmlFile, xmlKey, xmlHandle)
    if hasXMLProperty(xmlFile, xmlKey .. xmlHandle) then
        local removeItem = false;
        local animation = {};
        animation.direction = 0;
        animation.isPlaying = false;
        animation.name = getXMLString(xmlFile, xmlKey .. xmlHandle .. "#animationName");
        if animation.name == nil then
            print(("Warning: \"animationName\" attribut must be set in %s."):format(xmlHandle));
            removeItem = true;
        end;
        animation.type = getXMLString(xmlFile, xmlKey .. xmlHandle .. "#animationType");
        animation.handler = getXMLString(xmlFile, xmlKey .. xmlHandle .. "#animationType");
        if animation.type == nil then
            print(("Warning: \"animationType\" attribut must be set in %s."):format(xmlHandle));
            removeItem = true;
        elseif not (animation.type == "AnimatedVehicle" or animation.type == "ModelAnimated") then
            print(("Warning: \"animationType\" attribut must be either \"AnimatedVehicle\" or \"ModelAnimated\" in %s."):format(xmlHandle));
            removeItem = true;
        end;
        if removeItem then
            print(("Warning: %s was not loaded. Look above to check why."):format(xmlHandle));
            return nil;
        else
            if animation.type == "AnimatedVehicle" then
                if SpecializationUtil.hasSpecialization(AnimatedVehicle, self.specializations) then
                    animation.handler = AnimatedVehicle;
                else
                    print("Warning: Missing AnimatedVehicle Specialization. Animation: \""..animation.name.."\" was not added.")
                    return nil;
                end;
            elseif animation.type == "ModelAnimated" then
                if SpecializationUtil.hasSpecialization(ModelAnimated, self.specializations) then
                    animation.handler = ModelAnimated;
                else
                    print("Warning: Missing ModelAnimated Specialization. Animation: \""..animation.name.."\" was not added.")
                    return nil;
                end;
            end;
        end;
        return animation;
    else
        return nil;
    end;
end;

function overloader:getSaveAttributesAndNodes(nodeIdent)
    local attributes = "";
    local animTime = 0;
    if self.pipeAnimations.fold then
        animTime = self.pipeAnimations.savedTime;
        attributes = attributes .. 'pipeAnim1Time="' .. animTime .. '"';
    else
        attributes = 'pipeAnim1Time=""';
    end;
    for k,v in ipairs({"moveLR", "moveUD"}) do
        local anim = self.pipeAnimations[v];
        if anim then
            if anim.linkWithFold then
                animTime = self.pipeAnimations.savedTime;
            else
                animTime = anim.handler.getAnimationTime(self, anim.name);
            end;
            attributes = attributes .. ' pipeAnim' .. k+1 .. 'Time="' .. animTime .. '"';
        else
            attributes = attributes .. ' pipeAnim' .. k+1 .. 'Time=""';
        end;
    end;

    attributes = attributes .. ' pipePosition="'..self.pipePosition..'"';

    return attributes, nil;
end;

function overloader:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
    local xmlString = getXMLString(xmlFile, key.."#pipeAnim1Time");
    local animTime = 0;
    local anim;
    local direction = 10;
    if xmlString and xmlString ~= "" then
        animTime = getXMLFloat(xmlFile, key.."#pipeAnim1Time");
        anim = self.pipeAnimations.fold;
        if anim then
            anim.handler.setAnimationStopTime(self, anim.name, animTime);
            self.pipeAnimations.savedTime = animTime;
            anim.handler.playAnimation(self, anim.name, direction, nil, true);
        end;
    end;

    for k,v in ipairs({"moveLR", "moveUD"}) do
        xmlString = getXMLString(xmlFile, key.."#pipeAnim" .. k+1 .. "Time");
        if xmlString and xmlString ~= "" then
            animTime = getXMLFloat(xmlFile, key.."#pipeAnim" .. k+1 .. "Time");
            anim = self.pipeAnimations[v];
            if anim then
                if anim.linkWithFold then
                    self.pipeAnimations.savedTime = animTime;
                end;
                anim.handler.setAnimationStopTime(self, anim.name, animTime);
                anim.handler.playAnimation(self, anim.name, direction, nil, true);
            end;
        end;
    end;

    self.pipePosition = Utils.getNoNil(getXMLString(xmlFile, key.."#pipePosition"), "in");

    return BaseMission.VEHICLE_LOAD_OK;
end;

---
-- Overloader Animation Event Class
-- This is the event for overloader animations
--
-- @author  Claus G. Pedersen (Satis)
-- @date  29/12/12
--
-- Copyright Satis (C) 2012
-- Copyright Note: You are allowed to redistribute, use and edit it as long I'm credited for the original script.

OverloaderAnimEvent = {};
OverloaderAnimEvent_mt = Class(OverloaderAnimEvent, Event);

InitEventClass(OverloaderAnimEvent, "OverloaderAnimEvent");

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

function OverloaderAnimEvent:new(object, savedTime, pipePosition)
    local self = OverloaderAnimEvent:emptyNew();
    self.object = object;
    self.savedTime = savedTime;
    self.pipePosition = pipePosition;
    return self;
end;

function OverloaderAnimEvent:readStream(streamId, connection)
    self.object = networkGetObject(streamReadInt32(streamId));
    self.savedTime = streamReadFloat32(streamId);
    self.pipePosition = streamReadString(streamId);
    self:run(connection);
end;

function OverloaderAnimEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
    streamWriteFloat32(streamId, self.savedTime);
    streamWriteString(streamId, self.pipePosition);
end;

function OverloaderAnimEvent:run(connection)
    self.object.pipePosition = self.pipePosition;
    self.object.pipeAnimations.savedTime = self.savedTime;
    if not connection:getIsServer() then
        g_server:broadcastEvent(OverloaderAnimEvent:new(self.object, self.savedTime, self.pipePosition), nil, connection, self.object);
    end;
end;

function OverloaderAnimEvent:sendEvent(object, savedTime, pipePosition, noEventSend)
    if noEventSend == nil or noEventSend == false then
        if g_server ~= nil then
            g_server:broadcastEvent(OverloaderAnimEvent:new(object, savedTime, pipePosition), nil, nil, object);
        else
            g_client:getServerConnection():sendEvent(OverloaderAnimEvent:new(object, savedTime, pipePosition));
        end;
    end;
end;


---
-- Overloader Charge Event Class
-- This is the event for overloader charging
--
-- @author  Claus G. Pedersen (Satis)
-- @date  29/12/12
--
-- Copyright Satis (C) 2012
-- Copyright Note: You are allowed to redistribute, use and edit it as long I'm credited for the original script.

OverloaderChargeEvent = {};
OverloaderChargeEvent_mt = Class(OverloaderChargeEvent, Event);

InitEventClass(OverloaderChargeEvent, "OverloaderChargeEvent");

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

function OverloaderChargeEvent:new(object, isCharging)
    local self = OverloaderChargeEvent:emptyNew();
    self.object = object;
    self.isCharging = isCharging;
    return self;
end;

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

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

function OverloaderChargeEvent:run(connection)
    self.object.isCharging = self.isCharging;
    self.object.sendIsCharging = self.isCharging;
    if not connection:getIsServer() then
        g_server:broadcastEvent(OverloaderChargeEvent:new(self.object, self.isCharging), nil, connection, self.object);
    end;
end;

function OverloaderChargeEvent:sendEvent(object, isCharging, noEventSend)
    if noEventSend == nil or noEventSend == false then
        if g_server ~= nil then
            g_server:broadcastEvent(OverloaderChargeEvent:new(object, isCharging), nil, nil, object);
        else
            g_client:getServerConnection():sendEvent(OverloaderChargeEvent:new(object, isCharging));
        end;
    end;
end;


---
-- Overloader Pipe Is Moving Event Class
-- This is the event for overloader animations
--
-- @author  Claus G. Pedersen (Satis)
-- @date  29/12/12
--
-- Copyright Satis (C) 2012
-- Copyright Note: You are allowed to redistribute, use and edit it as long I'm credited for the original script.

OverloaderPipeIsMovingEvent = {};
OverloaderPipeIsMovingEvent_mt = Class(OverloaderPipeIsMovingEvent, Event);

InitEventClass(OverloaderPipeIsMovingEvent, "OverloaderPipeIsMovingEvent");

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

function OverloaderPipeIsMovingEvent:new(object, isMoving)
    local self = OverloaderPipeIsMovingEvent:emptyNew();
    self.object = object;
    self.pipeEventIsMoving = isMoving;
    return self;
end;

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

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

function OverloaderPipeIsMovingEvent:run(connection)
    self.object.pipeEventIsMoving = self.pipeEventIsMoving;
    if not connection:getIsServer() then
        g_server:broadcastEvent(OverloaderPipeIsMovingEvent:new(self.object, self.pipeEventIsMoving), nil, connection, self.object);
    end;
end;

function OverloaderPipeIsMovingEvent:sendEvent(object, isMoving, noEventSend)
    if noEventSend == nil or noEventSend == false then
        if g_server ~= nil then
            g_server:broadcastEvent(OverloaderPipeIsMovingEvent:new(object, isMoving), nil, nil, object);
        else
            g_client:getServerConnection():sendEvent(OverloaderPipeIsMovingEvent:new(object, isMoving));
        end;
    end;
end;


-----------------------------------------------------------------------
-- Overwrite Trailer:updateTick(dt) for better controll of canChargeTip
-----------------------------------------------------------------------
--- Make sure this overwrite script is only runned once in case some other mods also uses the overloader.lua
if not Trailer.updateTickOverwritenByOverloaderSpecialization then
    Trailer.updateTickOverwritenByOverloaderSpecialization = true;

    --- Copy the old Trailer.updateTick before destroying and recreating it.
    local oldTrailerupdateTick = Trailer.updateTick;

    Trailer.updateTick = function(self, dt)
        self.ChargeTipWarning = false;

        --- Check if we are in the correct pipe position
        if self.canChargeTip then
            local canTip = true;
            local triggers =  g_currentMission.trailerTipTriggers[self];
            if triggers then
                if triggers[1].stationName == "AlternativeTipUniversal Trigger" and not self.allowAlternativeTippingInAllPos and (self.AlternativeTippingInPos == "in" or self.AlternativeTippingInPos == "out") then
                    --- Alternative tip triggers
                    if self.pipePosition ~= self.AlternativeTippingInPos then
                        canTip = false;
                        self.ChargeTipWarning = self.AlternativeTippingInPos;
                    end;
                elseif triggers[1].stationName ~= "AlternativeTipUniversal Trigger" and (self.ChargeTipInPos == "in" or self.ChargeTipInPos == "out") then
                    --- Normal tip triggers
                    if self.pipePosition ~= self.ChargeTipInPos then
                        canTip = false;
                        self.ChargeTipWarning = self.ChargeTipInPos;
                    end;
                end;
            end;

            if not canTip and (self.tipState == Trailer.TIPSTATE_OPENING or self.tipState == Trailer.TIPSTATE_OPEN)then
                self:onEndTip();
            end
        end

        oldTrailerupdateTick(self, dt);
    end;
end;