--[[
Interface: 1.5.1.0 b6732

Copyright (C) GtX (Andy), 2018

Author: GtX | Andy
Date: 13.12.2018

Contact:
https://forum.giants-software.com
https://github.com/GtX-Andy

History:
V 1.0.0.0 @ 13.12.2018 - Release Version for FS19. (Based / Converted from my FS17 'ServiceTrailer.lua'.)
V 1.0.1.0 @ 20.05.2019 - Add support for 'Workshop Tabber 1.0.0.0'
V 1.0.2.0 @ 25.07.2019 - Added support for Seasons Workshop changes.
V 1.0.3.0 @ 11.03.2020 - Fixed update error when placing a 'mission pallet' in service trigger, fix MP event bugs, small optimisations.

Important:
Free for use in other mods - no permission needed.
No changes are to be made to this script without permission from GtX | Andy

Frei verwendbar - keine erlaubnis nötig
An diesem Skript dürfen ohne Genehmigung von GtX | Andy keine Änderungen vorgenommen werden
]]


ServiceVehicle = {}
ServiceVehicle.MOD_NAME = g_currentModName

function ServiceVehicle.prerequisitesPresent(specializations)
    return true
end

function ServiceVehicle.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad",                     ServiceVehicle)
    SpecializationUtil.registerEventListener(vehicleType, "onDelete",                   ServiceVehicle)
    SpecializationUtil.registerEventListener(vehicleType, "onReadStream",               ServiceVehicle)
    SpecializationUtil.registerEventListener(vehicleType, "onWriteStream",              ServiceVehicle)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate",                   ServiceVehicle)
    SpecializationUtil.registerEventListener(vehicleType, "onFillUnitFillLevelChanged", ServiceVehicle)
end

function ServiceVehicle.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "setServiceMarker",            ServiceVehicle.setServiceMarker)
    SpecializationUtil.registerFunction(vehicleType, "setServiceFuelState",         ServiceVehicle.setServiceFuelState)
    SpecializationUtil.registerFunction(vehicleType, "setServiceMarkerState",       ServiceVehicle.setServiceMarkerState)
    SpecializationUtil.registerFunction(vehicleType, "setServiceAnimationState",    ServiceVehicle.setServiceAnimationState)
    SpecializationUtil.registerFunction(vehicleType, "updateServiceInformation",    ServiceVehicle.updateServiceInformation)
    SpecializationUtil.registerFunction(vehicleType, "getCurrentVehicleToService",  ServiceVehicle.getCurrentVehicleToService)
end

function ServiceVehicle:onLoad(savegame)
    self.spec_serviceVehicle = self[string.format("spec_%s.serviceVehicle", ServiceVehicle.MOD_NAME)]

    if self.spec_serviceVehicle == nil then
        g_logManager:error("[%s] Specialization with name 'serviceVehicle' was not found in modDesc!", ServiceVehicle.MOD_NAME)
    end

    local spec = self.spec_serviceVehicle
    local specKey = "vehicle.serviceVehicle"

    local triggers = {}
    triggers.playerTrigger = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, specKey .. ".service#playerTriggerNode"), self.i3dMappings)
    triggers.sellTriggerNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, specKey .. ".service#sellTriggerNode"), self.i3dMappings)

    -- Only the triggers are required for the script to work. All other features are optional.
    -- l10n entries 'action_activateService' and 'action_deactivateService' are also a minimum requirement. (No inputBinding is needed.)
    if triggers.playerTrigger ~= nil and triggers.sellTriggerNode ~= nil then
        spec.texts = {}
        spec.texts.activateService = ServiceVehicle.returnText("action_activateService", "Activate Service Function")
        spec.texts.deactivateService = ServiceVehicle.returnText("action_deactivateService", "Deactivate Service Function")

        if self.spec_animatedVehicle ~= nil then
            local animationName = getXMLString(self.xmlFile, specKey .. ".service.animation#name")
            if animationName ~= nil and self:getAnimationExists(animationName) then
                spec.texts.activateServiceMenu = ServiceVehicle.returnText("action_activateServiceMenu", "Service Menu")

                spec.serviceAnimation = {}
                spec.serviceAnimation.name = animationName
                spec.serviceAnimation.isActive = false
                spec.serviceAnimation.finished = false

                local groupNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, specKey .. ".service.animation.markers#groupNode"), self.i3dMappings)
                if groupNode ~= nil then
                    spec.serviceAnimation.markers = {}
                    spec.serviceAnimation.markers.groupNode = groupNode
                    setVisibility(groupNode, false)

                    spec.texts.triggerMarkers = ServiceVehicle.returnText("action_triggerMarkers", "Trigger Marker")

                    local useShader = Utils.getNoNil(getXMLBool(self.xmlFile, specKey .. ".service.animation.markers#useEmissiveFalloffShader"), false)
                    if useShader then
                        spec.serviceAnimation.markers.useShader = useShader
                        spec.serviceAnimation.markers.active = false
                        spec.serviceAnimation.markers.vehiclesInTrigger = false
                        spec.serviceAnimation.markers.numMarkers = 0

                        local numMarkers = getNumOfChildren(groupNode)
                        if numMarkers > 0 then
                            spec.serviceAnimation.markers.group = {}

                            for i = 1, numMarkers do
                                local marker = getChildAt(groupNode, i - 1)
                                spec.serviceAnimation.markers.numMarkers = spec.serviceAnimation.markers.numMarkers + 1

                                table.insert(spec.serviceAnimation.markers.group, marker)
                            end
                        end
                    end

                    local showServiceMarkers = true
                    if savegame ~= nil and not savegame.resetVehicles then
                        local key = string.format("%s.%s.serviceVehicle.triggerMarkers#serviceActive", savegame.key, self.customEnvironment)
                        showServiceMarkers = Utils.getNoNil(getXMLBool(savegame.xmlFile, key), showServiceMarkers)
                    end
                    spec.serviceAnimation.markers.showMarkers = showServiceMarkers
                end
            end
        end

        if self.isClient then
            if hasXMLProperty(self.xmlFile, specKey .. ".service.textRender3D") then
                local keyToText = {["mainHeader"] = string.upper(g_i18n:getText("ui_garageOverview")),
                                    ["vehicleOwner"] = string.upper(g_i18n:getText("ui_farmName")) .. " :",
                                    ["vehicleName"] = string.upper(g_i18n:getText("ui_vehicle")) .. " :",
                                    ["vehicleOperatingTime"] = string.upper(g_i18n:getText("ui_operatingHours")) .. " :",
                                    ["vehicleAge"] = string.upper(g_i18n:getText("ui_age")) .. " :",
                                    ["vehicleFuel"] = string.upper(g_i18n:getText("fillType_diesel")) .. " :",
                                    ["vehicleCondition"] = string.upper(g_i18n:getText("ui_condition")) .. " :"}

                for i = 1, 7 do
                    local keyName = {"mainHeader", "vehicleOwner", "vehicleName", "vehicleOperatingTime", "vehicleAge", "vehicleFuel", "vehicleCondition"}
                    local headersKey = specKey .. ".service.textRender3D.headers"
                    ServiceVehicle:load3DText(self, headersKey, keyName[i], keyToText[keyName[i]])
                end

                for i = 1, 6 do
                    local keyName = {"vehicleOwner", "vehicleName", "vehicleOperatingTime", "vehicleAge", "vehicleFuel", "vehicleCondition"}
                    local textsKey = specKey .. ".service.textRender3D.texts"
                    ServiceVehicle:load3DText(self, textsKey, keyName[i], "")
                end
            end
        end

        local serviceActivatable = ServiceVehicleActivatable:new(self, triggers)
        if serviceActivatable then
            spec.serviceActivatable = serviceActivatable
        else
            self.spec_serviceVehicle = {}
        end
    else
        if triggers.playerTrigger ~= nil then
            g_logManager:error("[ServiceVehicle] - Service Player Trigger could not be found in '%s'! This is a minimum requirement. ",  self.configFileName)
        end

        if triggers.sellTriggerNode ~= nil then
            g_logManager:error("[ServiceVehicle] - Vehicle Service Trigger could not be found in '%s'! This is a minimum requirement. ",  self.configFileName)
        end
    end

    if self.spec_fillTriggerVehicle ~= nil and self.spec_fillUnit ~= nil then
        local fillUnitIndex = Utils.getNoNil(getXMLInt(self.xmlFile, specKey .. ".fuel#fillUnitIndex"), 1)
        local fillUnit = self:getFillUnitByIndex(fillUnitIndex)
        local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName("DIESEL") -- 32

        if fillUnit.supportedFillTypes ~= nil then
            if fillUnit.supportedFillTypes[fillTypeIndex] ~= nil then
                local playerTrigger = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, specKey .. ".fuel#playerTriggerNode"), self.i3dMappings)
                if playerTrigger ~= nil then
                    spec.texts.diesel = ServiceVehicle.returnText("fillType_diesel", "Diesel")
                    spec.texts.startFuelPump = ServiceVehicle.returnText("action_startFuelPump", "Start Fuel Pump")
                    spec.texts.stopFuelPump = ServiceVehicle.returnText("action_stopFuelPump", "Stop Fuel Pump")
                    spec.texts.fuelTankEmpty = ServiceVehicle.returnText("warning_fuelTankEmpty", "First Refill Fuel Tanker!")

                    spec.fuel = {}
                    spec.fuel.isActive = false
                    spec.fuel.fillUnitIndex = fillUnitIndex

                    if self.spec_animatedVehicle ~= nil then
                        local animationName = getXMLString(self.xmlFile, specKey .. ".fuel.animation#name")
                        if animationName ~= nil and self:getAnimationExists(animationName) then
                            spec.fuelAnimation = {}
                            spec.fuelAnimation.name = animationName
                        end
                    end

                    if self.isClient then
                        spec.fuel.animationNodes = g_animationManager:loadAnimations(self.xmlFile, specKey .. ".fuel.animationNodes", self.components, self, self.i3dMappings)

                        spec.fuel.samples = {}
                        spec.fuel.samples.pumpStart = g_soundManager:loadSampleFromXML(self.xmlFile, specKey .. ".fuel.sounds", "pumpStart", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self)
                        spec.fuel.samples.pumpStop  = g_soundManager:loadSampleFromXML(self.xmlFile, specKey .. ".fuel.sounds", "pumpStop", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self)
                        spec.fuel.samples.pumpWork  = g_soundManager:loadSampleFromXML(self.xmlFile, specKey .. ".fuel.sounds", "pumpWork", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self)

                        local exhaustKey = specKey .. ".fuel.exhaustParticles"
                        if hasXMLProperty(self.xmlFile, exhaustKey) then
                            spec.fuel.exhaustParticles = {}

                            -- Apparently 'i3dMappings' support to 'node' was not added so this is the work around.
                            local linkNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, exhaustKey .. "#linkNode"), self.i3dMappings)
                            ParticleUtil.loadParticleSystem(self.xmlFile, spec.fuel.exhaustParticles, exhaustKey, self.components, false, nil, self.baseDirectory, linkNode)
                        end
                    end

                    local groupNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, specKey .. ".fuel.markers#groupNode"), self.i3dMappings)
                    if groupNode ~= nil then
                        spec.fuel.markers = {}
                        spec.fuel.markers.groupNode = groupNode
                        setVisibility(groupNode, false)

                        spec.texts.triggerMarkers = ServiceVehicle.returnText("action_triggerMarkers", "Trigger Marker")

                        local useShader = Utils.getNoNil(getXMLBool(self.xmlFile, specKey .. ".fuel.markers#useEmissiveFalloffShader"), false)
                        if useShader then
                            spec.fuel.markers.useShader = useShader
                            spec.fuel.markers.active = false
                            spec.fuel.markers.vehiclesInTrigger = false
                            spec.fuel.markers.numMarkers = 0

                            local numMarkers = getNumOfChildren(groupNode)
                            if numMarkers > 0 then
                                spec.fuel.markers.group = {}

                                for i = 1, numMarkers do
                                    local marker = getChildAt(groupNode, i - 1)
                                    spec.fuel.markers.numMarkers = spec.fuel.markers.numMarkers + 1

                                    table.insert(spec.fuel.markers.group, marker)
                                end
                            end

                            local showFuelMarkers = true
                            if savegame ~= nil and not savegame.resetVehicles then
                                local key = string.format("%s.%s.serviceVehicle.triggerMarkers#fuelActive", savegame.key, self.customEnvironment)
                                showFuelMarkers = Utils.getNoNil(getXMLBool(savegame.xmlFile, key), showFuelMarkers)
                            end
                            spec.fuel.markers.showMarkers = showFuelMarkers
                        end
                    end

                    local activatable = ServiceVehicleFuelActivatable:new(self, playerTrigger)
                    if activatable ~= nil then
                        spec.fuelActivatable = activatable
                    else
                        spec.fuel = nil
                    end

                    -- Fix issue in base 'fillVehicle' function not allowing 'fillLitersPerSecond' to be used and fill sound not stopping if vehicle is running as 'delta = 0' was never reached.
                    local fillTrigger = self.spec_fillTriggerVehicle.fillTrigger
                    fillTrigger.fillVehicle = Utils.overwrittenFunction(fillTrigger.fillVehicle, function(trigger, superFunc, vehicle, delta, dt)
                        if trigger.fillLitersPerSecond ~= nil then
                            delta = math.max(math.min(delta, trigger.fillLitersPerSecond * 0.001 * dt), 0.009)
                        end

                        local farmId = vehicle:getActiveFarm()

                        if trigger.sourceObject ~= nil then
                            local sourceFuelFillLevel = trigger.sourceObject:getFillUnitFillLevel(trigger.fillUnitIndex)
                            if sourceFuelFillLevel > 0 and g_currentMission.accessHandler:canFarmAccess(farmId, trigger.sourceObject) then
                                delta = math.min(delta, sourceFuelFillLevel)

                                if delta <= 0 then
                                    return 0
                                end
                            else
                                return 0
                            end
                        end

                        local fillType = trigger:getCurrentFillType()
                        local fillUnitIndex = vehicle:getFirstValidFillUnitToFill(fillType)

                        if fillUnitIndex == nil then
                            return 0
                        end

                        delta = vehicle:addFillUnitFillLevel(farmId, fillUnitIndex, delta, fillType, ToolType.TRIGGER, nil)

                        if delta > 0 then
                            if trigger.sourceObject ~= nil then
                                trigger.sourceObject:addFillUnitFillLevel(farmId, trigger.fillUnitIndex, -delta, fillType, ToolType.TRIGGER, nil)
                            end
                        end

                        if delta < 0.009 then
                            delta = 0
                        end

                        return delta
                    end)
                else
                    g_logManager:error("[ServiceVehicle] - Fuel Player Trigger could not be found in '%s'! This is a minimum requirement.",  self.configFileName)
                end
            else
                g_logManager:error("[ServiceVehicle] - DIESEL fillType could not be found at fillUnitIndex '%d' in '%s'!",  fillUnitIndex, self.configFileName)
            end
        end
    end
end

function ServiceVehicle:load3DText(vehicle, baseKey, key, backupText)
    local spec = vehicle.spec_serviceVehicle
    local textsKey = baseKey .. "." .. key

    local node = I3DUtil.indexToObject(vehicle.components, getXMLString(vehicle.xmlFile, textsKey .. "#node"), vehicle.i3dMappings)

    if spec ~= nil then
        local serviceInformation = {}

        serviceInformation.node = node
        serviceInformation.keyName = key

        serviceInformation.text = backupText

        local mainKey, numName = "texts", "numTexts"
        if baseKey == "vehicle.serviceVehicle.service.textRender3D.headers" then
            local text = getXMLString(vehicle.xmlFile, textsKey .. "#text")

            if text ~= nil then
                serviceInformation.text = g_i18n:getText(text)
            end

            mainKey, numName = "headers", "numHeaders"
        end

        serviceInformation.size = Utils.getNoNil(getXMLFloat(vehicle.xmlFile, textsKey .. "#textSize"), 0.04)
        serviceInformation.colour = Utils.getNoNil(StringUtil.getVectorNFromString(getXMLString(vehicle.xmlFile, textsKey .. "#textColour"), 4), {0.843, 0.745, 0.705, 1.0})

        local alignment = string.upper(Utils.getNoNil(getXMLString(vehicle.xmlFile, textsKey .. "#textAlignment"), "LEFT"))
        if alignment == "RIGHT" then
            serviceInformation.alignment = RenderText.ALIGN_RIGHT
        elseif alignment == "CENTER" then
            serviceInformation.alignment = RenderText.ALIGN_CENTER
        else
            serviceInformation.alignment = RenderText.ALIGN_LEFT
        end

        if spec.serviceInformation == nil then
            spec.serviceInformation = {
                textsByName = {},
                numHeaders = 0,
                numTexts = 0
            }
        end

        spec.serviceInformation.textsByName[key] = serviceInformation

        if serviceInformation.node ~= nil then
            if spec.serviceInformation[mainKey] == nil then
                spec.serviceInformation[mainKey] = {}
            end

            table.insert(spec.serviceInformation[mainKey], serviceInformation)
            spec.serviceInformation[numName] = #spec.serviceInformation[mainKey]
        end
    end
end

function ServiceVehicle:onDelete()
    local spec = self.spec_serviceVehicle

    if spec.serviceActivatable ~= nil then
        spec.serviceActivatable:delete()
    end

    if spec.fuel ~= nil then
        if spec.fuelActivatable ~= nil then
            spec.fuelActivatable:delete()
        end

        if self.isClient then
            g_soundManager:deleteSamples(spec.fuel.samples)
            ParticleUtil.deleteParticleSystem(spec.fuel.exhaustParticles)
            g_animationManager:deleteAnimations(spec.fuel.animationNodes)
        end
    end
end

function ServiceVehicle:saveToXMLFile(xmlFile, key, usedModNames)
    local spec = self.spec_serviceVehicle

    if spec.serviceAnimation ~= nil and spec.serviceAnimation.markers ~= nil then
        setXMLBool(xmlFile, key .. ".triggerMarkers#serviceActive", spec.serviceAnimation.markers.showMarkers)
    end

    if spec.fuel ~= nil and spec.fuel.markers ~= nil then
        setXMLBool(xmlFile, key .. ".triggerMarkers#fuelActive", spec.fuel.markers.showMarkers)
    end
end

function ServiceVehicle:onReadStream(streamId, connection)
    local spec = self.spec_serviceVehicle

    if spec.serviceAnimation ~= nil then
        local isActive = streamReadBool(streamId)
        self:setServiceAnimationState(isActive, true)

        if spec.serviceAnimation.markers ~= nil then
            local showMarkers = streamReadBool(streamId)
            self:setServiceMarkerState(showMarkers, true, true)
        end
    end

    if spec.fuel ~= nil then
        local isActive = streamReadBool(streamId)
        self:setServiceFuelState(isActive, true)

        if spec.fuel.markers ~= nil then
            local showMarkers = streamReadBool(streamId)
            self:setServiceMarkerState(showMarkers, false, true)
        end
    end
end

function ServiceVehicle:onWriteStream(streamId, connection)
    local spec = self.spec_serviceVehicle

    if spec.serviceAnimation ~= nil then
        local isActive = spec.serviceAnimation.isActive
        streamWriteBool(streamId, isActive)

        if spec.serviceAnimation.markers ~= nil then
            local markersActive = spec.serviceAnimation.markers.showMarkers
            streamWriteBool(streamId, markersActive)
        end
    end

    if spec.fuel ~= nil then
        local isActive = spec.fuel.isActive
        streamWriteBool(streamId, spec.fuel.isActive)

        if spec.fuel.markers ~= nil then
            local markersActive = spec.fuel.markers.showMarkers
            streamWriteBool(streamId, markersActive)
        end
    end
end

function ServiceVehicle:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
    if self.isClient then
        local raiseActive = false
        local spec = self.spec_serviceVehicle

        if spec.fuel ~= nil and spec.fuelActivatable ~= nil then
            if spec.fuel.markers ~= nil then
                local vehiclesInTrigger = false

                for vehicle, count in pairs(self.spec_fillTriggerVehicle.fillTrigger.vehiclesTriggerCount) do
                    if count > 0 then
                        vehiclesInTrigger = true
                        break
                    end
                end

                raiseActive = self:setServiceMarker(spec.fuel.markers, vehiclesInTrigger, spec.fuel.isActive, false, raiseActive)
            end
        end

        if spec.serviceActivatable ~= nil then
            local currentVehicle = self:getCurrentVehicleToService(spec.serviceActivatable)

            if spec.serviceAnimation ~= nil and spec.serviceAnimation.markers ~= nil then
                local serviceMarkers = spec.serviceAnimation.markers
                local animationTime = self:getAnimationTime(spec.serviceAnimation.name)

                spec.serviceAnimation.finished = animationTime == 1

                if spec.serviceAnimation.finished then
                    raiseActive = self:setServiceMarker(serviceMarkers, currentVehicle ~= nil, spec.serviceAnimation.isActive, false, raiseActive)
                else
                    raiseActive = self:setServiceMarker(serviceMarkers, currentVehicle ~= nil, false, spec.serviceAnimation.isActive, raiseActive)
                end
            end

            -- Update headers and vehicle texts
            if spec.serviceInformation ~= nil then
                raiseActive = self:updateServiceInformation(currentVehicle, spec.serviceInformation, raiseActive)
            end
        end

        if raiseActive then
            self:raiseActive()
        end
    end
end

function ServiceVehicle:onFillUnitFillLevelChanged(fillUnitIndex, fillLevelDelta, fillTypeIndex, toolType, fillPositionData, appliedDelta)
    local spec = self.spec_serviceVehicle

    if spec.fuel ~= nil then
        if spec.fuel.isActive and spec.fuel.fillUnitIndex == fillUnitIndex then
            if self:getFillUnitFillLevel(fillUnitIndex) <= 0 then
                self:setServiceFuelState(false, true)
            end
        end
    end
end

function ServiceVehicle:getCurrentVehicleToService(activatable)
    if g_seasons ~= nil and g_directSellDialog ~= nil then
        if activatable.playerInTrigger == g_currentMission.player then
            if ServiceVehicle.getIsVehicleValid(self, g_directSellDialog.vehicle) then
                return g_directSellDialog.vehicle
            end
        end
    end

    return activatable.currentVehicle
end

function ServiceVehicle:updateServiceInformation(currentVehicle, serviceInfo, raiseActive)
    if currentVehicle ~= nil then
        -- Collect current vehicle information each loop as it changes
        local farmName = "N/A"
        local farm = g_farmManager:getFarmById(currentVehicle:getActiveFarm())
        if farm ~= nil and (farm.name ~= nil and farm.name ~= "") then
            farmName = farm.name
        end

        serviceInfo.textsByName.vehicleOwner.text = farmName
        serviceInfo.textsByName.vehicleName.text = currentVehicle:getFullName()

        local minutes = currentVehicle.operatingTime / (1000 * 60)
        local hours = math.floor(minutes / 60)
        minutes = math.floor((minutes - hours * 60) / 6)
        serviceInfo.textsByName.vehicleOperatingTime.text = string.format("%02d.%01d h", hours, minutes)

        local dieselLevel = "N/A"
        if currentVehicle.spec_motorized ~= nil and currentVehicle.spec_motorized.consumersByFillTypeName ~= nil then
            local fillLevel = currentVehicle:getFillUnitFillLevel(currentVehicle.spec_motorized.consumersByFillTypeName.diesel.fillUnitIndex)
            dieselLevel = g_i18n:formatVolume(fillLevel, 2)
        end

        serviceInfo.textsByName.vehicleFuel.text = dieselLevel
        serviceInfo.textsByName.vehicleAge.text = string.format(g_i18n:getText("shop_age"), currentVehicle.age)

        local damagedPercent = math.floor(math.min(currentVehicle:getWearTotalAmount() * 100, 100))
        serviceInfo.textsByName.vehicleCondition.text = tostring(100 - damagedPercent) .. " %"

        -- Render Headers
        for i = 1, serviceInfo.numHeaders do
            ServiceVehicle.drawServiceInformation(self, serviceInfo.headers[i])
        end

        -- Render Vehicle Texts
        for i = 1, serviceInfo.numTexts do
            ServiceVehicle.drawServiceInformation(self, serviceInfo.texts[i])
        end

        -- Reset once at the end for mods that do not use text drawing correctly.
        setTextColor(1, 1, 1, 1)
        setTextAlignment(RenderText.ALIGN_LEFT)

        raiseActive = true
    end

    return raiseActive
end

function ServiceVehicle:setServiceMarker(markers, vehiclesInTrigger, isActive, forceActive, raiseActive)
    if markers.showMarkers then
        if isActive ~= markers.active then
            setVisibility(markers.groupNode, isActive)
            markers.active = isActive
        end

        if markers.useShader then
            if markers.vehiclesInTrigger ~= vehiclesInTrigger then
                local colour = {1, 0, 0, 1}

                if vehiclesInTrigger then
                    colour = {0, 1, 0, 1}
                end

                if markers.numMarkers == 1 then
                    setShaderParameter(markers.group[1], "colorScale", colour[1], colour[2], colour[3], colour[3], false)
                else
                    for i = 1, markers.numMarkers do
                        setShaderParameter(markers.group[i], "colorScale", colour[1], colour[2], colour[3], colour[3], false)
                    end
                end

                markers.vehiclesInTrigger = vehiclesInTrigger
            end

            if isActive or forceActive then
                raiseActive = true
            end
        else
            raiseActive = forceActive
        end
    else
        if getVisibility(markers.groupNode) then
            setVisibility(markers.groupNode, false)
            markers.active = false
        end
    end

    return raiseActive
end

function ServiceVehicle:setServiceAnimationState(isActive, noEventSend)
    ServiceVehicleAnimationEvent.sendEvent(self, isActive, noEventSend)

    local spec = self.spec_serviceVehicle

    if isActive ~= spec.serviceAnimation.isActive then
        spec.serviceAnimation.isActive = isActive

        local animationTime = self:getAnimationTime(spec.serviceAnimation.name)
        if isActive and animationTime < 1 then
            self:playAnimation(spec.serviceAnimation.name, 1, animationTime, true)
        elseif isActive and animationTime >= 1 then
            self:playAnimation(spec.serviceAnimation.name, 1, 0, true)
        elseif animationTime > 0 then
            self:playAnimation(spec.serviceAnimation.name, -1, animationTime, true)
        end

        self:raiseActive()
    end
end

function ServiceVehicle:setServiceMarkerState(showMarkers, isService, noEventSend)
    ServiceVehicleMarkerEvent.sendEvent(self, showMarkers, isService, noEventSend)

    local spec = self.spec_serviceVehicle

    if isService then
        spec.serviceAnimation.markers.showMarkers = showMarkers
    else
        spec.fuel.markers.showMarkers = showMarkers
    end

    if self.isClient then
        self:raiseActive()
    end
end

function ServiceVehicle:setServiceFuelState(isActive, noEventSend)
    ServiceVehicleFuelEvent.sendEvent(self, isActive, noEventSend)

    local spec = self.spec_serviceVehicle
    if isActive ~= spec.fuel.isActive then

        spec.fuel.isActive = isActive

        if spec.fuelAnimation ~= nil then
            local animationTime = self:getAnimationTime(spec.fuelAnimation.name)
            if isActive and animationTime < 1 then
                self:playAnimation(spec.fuelAnimation.name, 1, animationTime, true)
            elseif isActive and animationTime >= 1 then
                self:playAnimation(spec.fuelAnimation.name, 1, 0, true)
            elseif animationTime > 0 then
                self:playAnimation(spec.fuelAnimation.name, -1, animationTime, true)
            end
        end

        if self.isClient then
            if isActive then
                if spec.fuel.samples ~= nil then
                    g_soundManager:stopSample(spec.fuel.samples.pumpStop)
                    g_soundManager:playSample(spec.fuel.samples.pumpStart)
                    g_soundManager:playSample(spec.fuel.samples.pumpWork, 0, spec.fuel.samples.pumpStart)
                end

                if spec.fuel.animationNodes ~= nil then
                    g_animationManager:startAnimations(spec.fuel.animationNodes)
                end

                if spec.fuel.exhaustParticles ~= nil then
                    ParticleUtil.setEmittingState(spec.fuel.exhaustParticles, true)
                end
            else
                if spec.fuel.samples ~= nil then
                    g_soundManager:stopSample(spec.fuel.samples.pumpStart)
                    g_soundManager:stopSample(spec.fuel.samples.pumpWork)
                    g_soundManager:playSample(spec.fuel.samples.pumpStop)
                end

                if spec.fuel.animationNodes ~= nil then
                    g_animationManager:stopAnimations(spec.fuel.animationNodes)
                end

                if spec.fuel.exhaustParticles ~= nil then
                    ParticleUtil.setEmittingState(spec.fuel.exhaustParticles, false)
                end
            end
        end

        self:raiseActive()
    end
end

function ServiceVehicle.returnText(i18n, backupText)
    if g_i18n:hasText(i18n) then
        return g_i18n:getText(i18n) -- Try and give l10n
    end

    return backupText -- If l10n is missing because of another bad mod override then return backup.
end

function ServiceVehicle.drawServiceInformation(vehicle, serviceInfo)
    if serviceInfo.node ~= nil then
        local x, y, z = getWorldTranslation(serviceInfo.node)
        local rx, ry, rz = getWorldRotation(serviceInfo.node)

        setTextAlignment(serviceInfo.alignment)
        setTextColor(serviceInfo.colour[1], serviceInfo.colour[2], serviceInfo.colour[3], serviceInfo.colour[4])

        renderText3D(x,y,z, rx,ry,rz, serviceInfo.size, serviceInfo.text)
    end
end

function ServiceVehicle.getIsVehicleValid(vehicle, vehicleToCheck)
    if vehicleToCheck ~= nil and vehicleToCheck ~= vehicle then
        return vehicleToCheck.configFileName ~= nil
    end

    return false
end


ServiceVehicleActivatable = {}
local ServiceVehicleActivatable_mt = Class(ServiceVehicleActivatable)

function ServiceVehicleActivatable:new(vehicle, triggers)
    local spec = vehicle.spec_serviceVehicle

    if spec ~= nil and triggers ~= nil and triggers.playerTrigger ~= nil and triggers.sellTriggerNode ~= nil then
        local self = {}
        setmetatable(self, ServiceVehicleActivatable_mt)

        self.vehicle = vehicle
        self.spec = spec

        self.playerTrigger = triggers.playerTrigger
        self.sellTriggerNode = triggers.sellTriggerNode
        addTrigger(self.playerTrigger, "triggerCallback", self)
        addTrigger(self.sellTriggerNode, "sellAreaTriggerCallback", self)

        self.playerInTrigger = nil
        self.objectActivated = false
        self.isEnabled = true

        -- WorkShopTabber Fix
        if VehicleSellingPoint.showNextVehicle ~= nil then
            self.showNextVehicle = VehicleSellingPoint.showNextVehicle
        end

        self.originalOnActivateObject = VehicleSellingPoint.onActivateObject

        self.ownWorkshop = true

        self.vehicleInRange = {}
        self.currentVehicle = nil
        self.currentVehicleId = 0

        self.activateText = "ERROR WITH TEXT"

        self.hasExtraInputs = spec.serviceAnimation ~= nil
        if self.hasExtraInputs then
            self.hasMarkers = spec.serviceAnimation.markers
        end

        return self
    else
        return nil
    end
end

function ServiceVehicleActivatable:delete()
    if self.objectActivated then
        g_currentMission:removeActivatableObject(self)
        self.objectActivated = false
        self.playerInTrigger = nil
    end

    if self.playerTrigger ~= nil then
        removeTrigger(self.playerTrigger)
        self.playerTrigger = nil
    end

    if self.sellTriggerNode ~= nil then
        removeTrigger(self.sellTriggerNode)
        self.sellTriggerNode = nil
    end
end

function ServiceVehicleActivatable:getIsActivatable()
    self:setActivatableText()
    return self.isEnabled and g_currentMission.controlPlayer and (self.vehicle:getActiveFarm() == 0 or g_currentMission:getFarmId() == self.vehicle:getActiveFarm())
end

function ServiceVehicleActivatable:onActivateObject()
    if self.spec ~= nil and self.vehicle ~= nil then
        if self.hasExtraInputs then
            self.vehicle:setServiceAnimationState(not self.spec.serviceAnimation.isActive, false)
        else
            self:originalOnActivateObject()
        end
    end
end

function ServiceVehicleActivatable:shouldRemoveActivatable()
    return false
end

function ServiceVehicleActivatable:setActivatableText()
    if self.spec ~= nil then
        if self.hasExtraInputs then
            self:addRemoveActionEvent()

            if self.spec.serviceAnimation.isActive then
                self.activateText = self.spec.texts.deactivateService
            else
                self.activateText = self.spec.texts.activateService
            end
        else
            self.activateText = self.spec.texts.activateServiceMenu
        end
    end
end

function ServiceVehicleActivatable:addRemoveActionEvent()
    if self.objectActivated then
        if self.eventIdActivateService == nil then
            local _, eventId = g_inputBinding:registerActionEvent(InputAction.SV_ACTIVATE_SERVICE, self, self.onInputActivateService, false, true, false, false)
            self.eventIdActivateService = eventId
        end

        if self.eventIdActivateMarkers == nil and self.hasMarkers then
            local _, eventId = g_inputBinding:registerActionEvent(InputAction.SV_SET_MARKERS, self, self.onInputSetMarkers, false, true, false, false)
            self.eventIdActivateMarkers = eventId
        end
    else
        if self.eventIdActivateService ~= nil then
            g_inputBinding:removeActionEvent(self.eventIdActivateService)
            self.eventIdActivateService = nil
        end

        if self.eventIdActivateMarkers ~= nil then
            g_inputBinding:removeActionEvent(self.eventIdActivateMarkers)
            self.eventIdActivateMarkers = nil
        end
    end
end

function ServiceVehicleActivatable:drawActivate()
    if self.hasExtraInputs then
        local spec = self.vehicle.spec_serviceVehicle

        if self.spec.serviceAnimation ~= nil then
            local active = self.spec.serviceAnimation.finished

            if self.eventIdActivateService ~= nil then
                g_inputBinding:setActionEventText(self.eventIdActivateService, self.spec.texts.activateServiceMenu)

                g_inputBinding:setActionEventTextVisibility(self.eventIdActivateService, active)
                g_inputBinding:setActionEventActive(self.eventIdActivateService, active)
            end

            if self.eventIdActivateMarkers ~= nil and self.hasMarkers then
                if self.spec.serviceAnimation.markers.showMarkers then
                    g_inputBinding:setActionEventText(self.eventIdActivateMarkers, string.format(g_i18n:getText("action_turnOffOBJECT"), self.spec.texts.triggerMarkers))
                else
                    g_inputBinding:setActionEventText(self.eventIdActivateMarkers, string.format(g_i18n:getText("action_turnOnOBJECT"), self.spec.texts.triggerMarkers))
                end

                g_inputBinding:setActionEventTextVisibility(self.eventIdActivateMarkers, active)
                g_inputBinding:setActionEventActive(self.eventIdActivateMarkers, active)
            end
        end
    end
end

function ServiceVehicleActivatable.onInputActivateService(self, actionName, inputValue, callbackState, isAnalog)
    if self.objectActivated then
        self:originalOnActivateObject()
    end
end

function ServiceVehicleActivatable.onInputSetMarkers(self, actionName, inputValue, callbackState, isAnalog)
    if self.objectActivated then
        local serviceMarkers = self.spec.serviceAnimation.markers
        self.vehicle:setServiceMarkerState(not serviceMarkers.showMarkers, true, false)
    end
end

function ServiceVehicleActivatable:triggerCallback(triggerId, otherId, onEnter, onLeave, onStay)
    if self.isEnabled then
        if onEnter or onLeave then
            if g_currentMission.player ~= nil and otherId == g_currentMission.player.rootNode then
                if onEnter then
                    if not self.objectActivated then
                        g_currentMission:addActivatableObject(self)
                        self.objectActivated = true
                        self.playerInTrigger = g_currentMission.player
                    end
                else
                    if self.objectActivated then
                        g_currentMission:removeActivatableObject(self)
                        self.objectActivated = false
                        self.playerInTrigger =nil

                        if self.hasExtraInputs then
                            self:addRemoveActionEvent()
                        end
                    end
                end
            end
        end
    end
end

function ServiceVehicleActivatable:sellAreaTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if self.isEnabled then
        if otherShapeId ~= nil and (onEnter or onLeave) then
            if onEnter then
                self.vehicleInRange[otherShapeId] = true
            elseif onLeave then
                self.vehicleInRange[otherShapeId] = nil
            end

            self:determineCurrentVehicle()
        end
    end
end

function ServiceVehicleActivatable:determineCurrentVehicle()
    self.currentVehicle = nil

    for vehicleId, inRange in pairs(self.vehicleInRange) do
        if inRange ~= nil then
            local currentVehicle = g_currentMission.nodeToObject[vehicleId]

            if ServiceVehicle.getIsVehicleValid(self.vehicle, currentVehicle) then
                self.currentVehicle = currentVehicle

                if self.currentVehicle:getOwnerFarmId() ~= self.vehicle:getActiveFarm() or
                    not SpecializationUtil.hasSpecialization(Rideable, self.currentVehicle.specializations) then

                    break
                end

                -- Only if 3D text is used.
                if self.spec.serviceInformation ~= nil then
                    self.vehicle:raiseActive()
                end
            end
        end

        self.vehicleInRange[vehicleId] = nil
    end
end


ServiceVehicleFuelActivatable = {}
local ServiceVehicleFuelActivatable_mt = Class(ServiceVehicleFuelActivatable)

function ServiceVehicleFuelActivatable:new(vehicle, playerTrigger)
    local spec = vehicle.spec_serviceVehicle

    if spec ~= nil and spec.fuel ~= nil and playerTrigger ~= nil then
        local self = {}
        setmetatable(self, ServiceVehicleFuelActivatable_mt)

        self.vehicle = vehicle
        self.spec = spec

        self.playerTrigger = playerTrigger
        addTrigger(self.playerTrigger, "triggerCallback", self)

        self.objectActivated = false
        self.isEnabled = true

        self.activateText = "ERROR WITH TEXT"

        self.hasMarkers = spec.fuel.markers ~= nil

        return self, ""
    end

    return nil
end

function ServiceVehicleFuelActivatable:delete()
    if self.objectActivated then
        g_currentMission:removeActivatableObject(self)
        self.objectActivated = false
    end

    if self.playerTrigger ~= nil then
        removeTrigger(self.playerTrigger)
        self.playerTrigger = nil
    end
end

function ServiceVehicleFuelActivatable:triggerCallback(triggerId, otherId, onEnter, onLeave, onStay)
    if self.isEnabled then
        if onEnter or onLeave then
            if g_currentMission.player ~= nil and otherId == g_currentMission.player.rootNode then
                if onEnter then
                    if not self.objectActivated then
                        g_currentMission:addActivatableObject(self)
                        self.objectActivated = true
                    end
                else
                    if self.objectActivated then
                        g_currentMission:removeActivatableObject(self)
                        self.objectActivated = false

                        if self.hasMarkers then
                            self:addRemoveActionEvent()
                        end
                    end
                end
            end
        end
    end
end

function ServiceVehicleFuelActivatable:getIsActivatable()
    self:setActivatableText()
    return self.isEnabled and g_currentMission.controlPlayer and (self.vehicle:getActiveFarm() == 0 or g_currentMission:getFarmId() == self.vehicle:getActiveFarm())
end

function ServiceVehicleFuelActivatable:onActivateObject()
    if self.spec ~= nil and self.vehicle ~= nil then
        if self.vehicle:getFillUnitFillLevel(self.spec.fuel.fillUnitIndex) > 0 and self.vehicle:getFillUnitCapacity(self.spec.fuel.fillUnitIndex) ~= 0 then
            self.vehicle:setServiceFuelState(not self.spec.fuel.isActive, false)
        else
            g_currentMission:showBlinkingWarning(self.spec.texts.fuelTankEmpty)
        end
    end
end

function ServiceVehicleFuelActivatable:drawActivate()
    if self.hasMarkers and self.eventIdActivateMarkers ~= nil then
        if self.spec.fuel.markers.showMarkers then
            g_inputBinding:setActionEventText(self.eventIdActivateMarkers, string.format(g_i18n:getText("action_turnOffOBJECT"), self.spec.texts.triggerMarkers))
        else
            g_inputBinding:setActionEventText(self.eventIdActivateMarkers, string.format(g_i18n:getText("action_turnOnOBJECT"), self.spec.texts.triggerMarkers))
        end

        g_inputBinding:setActionEventTextVisibility(self.eventIdActivateMarkers, self.spec.fuel.isActive)
        g_inputBinding:setActionEventActive(self.eventIdActivateMarkers, self.spec.fuel.isActive)
    end
end

function ServiceVehicleFuelActivatable:shouldRemoveActivatable()
    return false
end

function ServiceVehicleFuelActivatable:setActivatableText()
    if self.hasMarkers then
        self:addRemoveActionEvent()

        if self.spec ~= nil then
            local percent = 100 * self.vehicle:getFillUnitFillLevelPercentage(self.spec.fuel.fillUnitIndex)
            if self.spec.fuel.isActive then
                self.activateText = string.format("%s ( %s - %d %s )", self.spec.texts.stopFuelPump, self.spec.texts.diesel, percent, "%")
            else
                self.activateText = string.format("%s ( %s - %d %s )", self.spec.texts.startFuelPump, self.spec.texts.diesel, percent, "%")
            end
        end
    end
end

function ServiceVehicleFuelActivatable:addRemoveActionEvent()
    if self.objectActivated then
        if self.eventIdActivateMarkers == nil and self.hasMarkers then
            local _, eventId = g_inputBinding:registerActionEvent(InputAction.SV_SET_MARKERS, self, self.onInputSetMarkers, false, true, false, false)
            self.eventIdActivateMarkers = eventId
        end
    else
        if self.eventIdActivateMarkers ~= nil then
            g_inputBinding:removeActionEvent(self.eventIdActivateMarkers)
            self.eventIdActivateMarkers = nil
        end
    end
end

function ServiceVehicleFuelActivatable.onInputSetMarkers(self, actionName, inputValue, callbackState, isAnalog)
    if self.objectActivated then
        local fuelMarkers = self.spec.fuel.markers
        self.vehicle:setServiceMarkerState(not fuelMarkers.showMarkers, false, false)
    end
end


ServiceVehicleAnimationEvent = {}
ServiceVehicleAnimationEvent_mt = Class(ServiceVehicleAnimationEvent, Event)
InitEventClass(ServiceVehicleAnimationEvent, "ServiceVehicleAnimationEvent")

function ServiceVehicleAnimationEvent:emptyNew()
    local self = Event:new(ServiceVehicleAnimationEvent_mt)
    return self
end

function ServiceVehicleAnimationEvent:new(object, isActive)
    local self = ServiceVehicleAnimationEvent:emptyNew()
    self.object = object
    self.isActive = isActive
    return self
end

function ServiceVehicleAnimationEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.isActive = streamReadBool(streamId)
    self:run(connection)
end

function ServiceVehicleAnimationEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteBool(streamId, self.isActive)
end

function ServiceVehicleAnimationEvent:run(connection)
    self.object:setServiceAnimationState(self.isActive, true)

    if not connection:getIsServer() then
        g_server:broadcastEvent(ServiceVehicleAnimationEvent:new(self.object, self.isActive), nil, connection, self.object)
    end
end

function ServiceVehicleAnimationEvent.sendEvent(vehicle, isActive, noEventSend)
    if vehicle.spec_serviceVehicle ~= nil and vehicle.spec_serviceVehicle.serviceAnimation ~= nil and
        vehicle.spec_serviceVehicle.serviceAnimation.isActive ~= isActive then
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(ServiceVehicleAnimationEvent:new(vehicle, isActive), nil, nil, vehicle)
            else
                g_client:getServerConnection():sendEvent(ServiceVehicleAnimationEvent:new(vehicle, isActive))
            end
        end
    end
end


ServiceVehicleFuelEvent = {}
ServiceVehicleFuelEvent_mt = Class(ServiceVehicleFuelEvent, Event)
InitEventClass(ServiceVehicleFuelEvent, "ServiceVehicleFuelEvent")

function ServiceVehicleFuelEvent:emptyNew()
    local self = Event:new(ServiceVehicleFuelEvent_mt)
    return self
end

function ServiceVehicleFuelEvent:new(object, isActive)
    local self = ServiceVehicleFuelEvent:emptyNew()
    self.object = object
    self.isActive = isActive
    return self
end

function ServiceVehicleFuelEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.isActive = streamReadBool(streamId)
    self:run(connection)
end

function ServiceVehicleFuelEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteBool(streamId, self.isActive)
end

function ServiceVehicleFuelEvent:run(connection)
    self.object:setServiceFuelState(self.isActive, true)

    if not connection:getIsServer() then
        g_server:broadcastEvent(ServiceVehicleFuelEvent:new(self.object, self.isActive), nil, connection, self.object)
    end
end

function ServiceVehicleFuelEvent.sendEvent(vehicle, isActive, noEventSend)
    if vehicle.spec_serviceVehicle ~= nil and vehicle.spec_serviceVehicle.fuel ~= nil and
        vehicle.spec_serviceVehicle.fuel.isActive ~= isActive then
        if noEventSend == nil or noEventSend == false then
            if g_server ~= nil then
                g_server:broadcastEvent(ServiceVehicleFuelEvent:new(vehicle, isActive), nil, nil, vehicle)
            else
                g_client:getServerConnection():sendEvent(ServiceVehicleFuelEvent:new(vehicle, isActive))
            end
        end
    end
end


ServiceVehicleMarkerEvent = {}
ServiceVehicleMarkerEvent_mt = Class(ServiceVehicleMarkerEvent, Event)
InitEventClass(ServiceVehicleMarkerEvent, "ServiceVehicleMarkerEvent")

function ServiceVehicleMarkerEvent:emptyNew()
    local self = Event:new(ServiceVehicleMarkerEvent_mt)
    return self
end

function ServiceVehicleMarkerEvent:new(object, showMarkers, isService)
    local self = ServiceVehicleMarkerEvent:emptyNew()
    self.object = object
    self.showMarkers = showMarkers
    self.isService = isService
    return self
end

function ServiceVehicleMarkerEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.showMarkers = streamReadBool(streamId)
    self.isService = streamReadBool(streamId)
    self:run(connection)
end

function ServiceVehicleMarkerEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteBool(streamId, self.showMarkers)
    streamWriteBool(streamId, self.isService)
end

function ServiceVehicleMarkerEvent:run(connection)
    self.object:setServiceMarkerState(self.showMarkers, self.isService, true)

    if not connection:getIsServer() then
        g_server:broadcastEvent(ServiceVehicleMarkerEvent:new(self.object, self.showMarkers, self.isService), nil, connection, self.object)
    end
end

function ServiceVehicleMarkerEvent.sendEvent(vehicle, showMarkers, isService, noEventSend)
    if noEventSend == nil or noEventSend == false then
        if g_server ~= nil then
            g_server:broadcastEvent(ServiceVehicleMarkerEvent:new(vehicle, showMarkers, isService), nil, nil, vehicle)
        else
            g_client:getServerConnection():sendEvent(ServiceVehicleMarkerEvent:new(vehicle, showMarkers, isService))
        end
    end
end
