--[[
Interface: 1.4.1.0 b5334

Copyright (C) GtX (Andy), 2018

Author: GtX | Andy
Date: 13.12.2018

Contact:
GtX_Andy@protonmail.com

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.

Important:
Free for use in other mods - no permission needed.

No changes are to be made to this script without permission from GtX | Andy

An diesem Skript dürfen ohne Genehmigung von GtX | Andy keine Änderungen vorgenommen werden
]]


ServiceVehicle = {}

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)
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)
end

function ServiceVehicle:onLoad(savegame)
    self.spec_serviceVehicle = {}

    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

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

                            for i = 1, numMarkers do
                                local marker = getChildAt(groupNode, i - 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 = {}
                            -- spec.fuel.exhaustStartDelay = Utils.getNoNil(getXMLBool(self.xmlFile, exhaustKey .. "#exhaustStartDelay"), false)

                            -- 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.currentVehicle = nil

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

                                for i = 1, numMarkers do
                                    local marker = getChildAt(groupNode, i - 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
                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 and node ~= nil then
        local serviceInformation = {}

        serviceInformation.node = node
        serviceInformation.keyName = key

        local mainKey = "texts"
        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)
            else
                serviceInformation.text = backupText
            end
            mainKey = "headers"
        end

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

        local alignment = 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 = {}
        end

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

        table.insert(spec.serviceInformation[mainKey], serviceInformation)
    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.exhaustStartDelay and spec.fuel.isActive and spec.fuel.exhaustParticles ~= nil then
                -- -- Only start exhaust after start sound ends.
                -- if not spec.fuel.exhaustParticles.isEmitting then
                    -- if not g_soundManager:getIsSamplePlaying(spec.fuel.samples.pumpStart, 1.5 * dt) then
                        -- ParticleUtil.setEmittingState(spec.fuel.exhaustParticles, true)
                    -- end

                    -- raiseActive = true
                -- end
            -- end

            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 = spec.serviceActivatable.currentVehicle

            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 g_seasons ~= nil and g_directSellDialog ~= nil then
                    if spec.serviceActivatable.playerInTrigger == g_currentMission.player then
                        local dialogVehicle = g_directSellDialog.vehicle
                        if dialogVehicle ~= nil and dialogVehicle ~= self then
                            if g_directSellDialog.vehicle.configFileName ~= nil then
                                currentVehicle = g_directSellDialog.vehicle
                            end
                        end
                    end
                end

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

            if currentVehicle ~= nil and spec.serviceInformation ~= nil then
                if spec.serviceInformation.headers ~= nil then
                    local numHeaders = #spec.serviceInformation.headers
                    if numHeaders > 0 then
                        for i = 1, numHeaders do
                            ServiceVehicle:drawServiceInformation(self, spec.serviceInformation.headers[i])
                        end

                        raiseActive = true
                    end
                end

                if spec.serviceInformation.texts ~= nil then
                    local numTexts = #spec.serviceInformation.texts
                    local storeItem = g_storeManager:getItemByXMLFilename(currentVehicle.configFileName:lower())
                    if numTexts > 0 then
                        for i = 1, numTexts do
                            local serviceInfo = spec.serviceInformation.texts[i]
                            if serviceInfo.keyName == "vehicleOwner" then
                                local farm = g_farmManager:getFarmById(currentVehicle:getActiveFarm())
                                -- g_currentMission.player.visualInformation.playerName
                                serviceInfo.text = Utils.getNoNil(farm.name, "N/A")
                            elseif serviceInfo.keyName == "vehicleName" then
                                serviceInfo.text = string.format("%s  %s", g_brandManager:getBrandByIndex(storeItem.brandIndex).name, storeItem.name)
                            elseif serviceInfo.keyName == "vehicleOperatingTime" then
                                -- This is the same look as the vehicle hud. Store gui is wrong using 00:00 h
                                local minutes = currentVehicle.operatingTime / (1000 * 60)
                                local hours = math.floor(minutes / 60)
                                minutes = math.floor((minutes - hours * 60) / 6)
                                serviceInfo.text = string.format("%02d.%01d h", hours, minutes)
                            elseif serviceInfo.keyName == "vehicleFuel" then
                                if currentVehicle.spec_motorized ~= nil and currentVehicle.spec_motorized.consumersByFillTypeName ~= nil then
                                    local fillUnitIndex = currentVehicle.spec_motorized.consumersByFillTypeName.diesel.fillUnitIndex
                                    local fillLevel = currentVehicle:getFillUnitFillLevel(fillUnitIndex)
                                    serviceInfo.text = g_i18n:formatVolume(fillLevel, 2)
                                else
                                    serviceInfo.text = "N/A"
                                end
                            elseif serviceInfo.keyName == "vehicleAge" then
                                serviceInfo.text = string.format(g_i18n:getText("shop_age"), currentVehicle.age)
                            elseif serviceInfo.keyName == "vehicleCondition" then
                                local damagedPercent = math.floor(math.min(currentVehicle:getWearTotalAmount() * 100, 100))
                                local conditionPercent = tostring(100 - damagedPercent)
                                serviceInfo.text = conditionPercent .. " %"
                            end

                            ServiceVehicle:drawServiceInformation(self, serviceInfo)
                        end

                        raiseActive = true
                    end
                end
            end
        end

        if raiseActive then
            self:raiseActive()
        end
    end
end

function ServiceVehicle:drawServiceInformation(vehicle, serviceInfo)
    local x, y, z = getWorldTranslation(serviceInfo.node)
    local rx, ry, rz = getWorldRotation(serviceInfo.node)
    local r, g, b, a = unpack(serviceInfo.colour)

    setTextColor(r, g, b, a)
    --setTextBold(serviceInfo.bold)
    setTextAlignment(serviceInfo.alignment)

    renderText3D(x,y,z, rx,ry,rz, serviceInfo.size, serviceInfo.text)
    setTextAlignment(RenderText.ALIGN_LEFT) -- reset to default.
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

                local numMarkers = #markers.group
                if numMarkers > 1 then
                    setShaderParameter(markers.group[1], "colorScale", colour[1], colour[2], colour[3], colour[3], false)
                else
                    for i = 1, 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 not spec.fuel.exhaustStartDelay then
                    if spec.fuel.exhaustParticles ~= nil then
                        ParticleUtil.setEmittingState(spec.fuel.exhaustParticles, true)
                    end
                -- 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


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 currentVehicle ~= nil and currentVehicle ~= self.vehicle then
                self.currentVehicle = currentVehicle

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

                if self.spec.serviceInformation ~= nil then
                    self.vehicle:raiseActive() -- Only if 3D text is used.
                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.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(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 vehicle.spec_serviceVehicle ~= nil and vehicle.spec_serviceVehicle.serviceAnimation ~= nil and
        vehicle.spec_serviceVehicle.serviceAnimation.markers ~= nil and vehicle.spec_serviceVehicle.serviceAnimation.markers.showMarkers ~= showMarkers then

        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
end
