---
-- 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",
  },
  ["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",
  },
};
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.cpAI = "off";
  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);

    ----------------------------------
    -- 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);
      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);
  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;
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:handleChargeRPM();
    self:handlePipeMoveSound();
    self:handlePipeParticles();
    self:handleChargeSound(dt);
    self:handleChargeTip();
  end;
  self:handleChargeTriggers();
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));
    end;
  end;

  --------------------------------------------------------------------------------------------------------------------
  -- Courseplay plugin
  --------------------------------------------------------------------------------------------------------------------
  if self:getIsActive() and self.attacherVehicle ~= nil and self.attacherVehicle.drive
  and (self.attacherVehicle.ai_mode == 1 or self.attacherVehicle.ai_mode == 3) then
		if 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;
    elseif self.attacherVehicle.ai_mode == 1 then
      self.cpAI = "in";
    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

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

  --- 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 v.animSpeed > 0 then
      if self.isCharging 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 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;