--[[
Interface: FS19 1.2.0.1

Copyright (C) GtX (Andy), 2018

Author: GtX | Andy
Date: 13.12.2018

Support only at: https://ls-modcompany.com


History:
V 1.0.0.0 @ 13.12.2018 - Release Version for FS19. (Based / Converted from my FS17 'ServiceTrailer.lua'.)


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

No changes are to be made to this script without permission from GtX @ https://ls-modcompany.com
]]


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
			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;

				local vehiclesInTrigger = spec.serviceActivatable.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 spec.serviceActivatable.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 currentVehicle = spec.serviceActivatable.currentVehicle;
					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.objectActivated = false;
		self.isEnabled = 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;
	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:determineCurrentVehicle();
			g_gui:showDirectSellDialog({vehicle = self.currentVehicle, owner = self, ownWorkshop = true});
		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:determineCurrentVehicle();
		g_gui:showDirectSellDialog({vehicle = self.currentVehicle, owner = self, ownWorkshop = true});
	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;
					end;
				else
					if self.objectActivated then
						g_currentMission:removeActivatableObject(self);
						self.objectActivated = false;

						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
			self.currentVehicle = g_currentMission.nodeToObject[vehicleId];

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

			if self.spec.serviceInformation ~= nil then
				self.vehicle:raiseActive(); -- Only if 3D text is used.
			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.spec ~= nil and self.vehicle ~= nil then
		-- if self.warningCount == nil then
			-- self.warningCount = 1;
			-- if self.vehicle:getFillUnitFillLevel(self.spec.fuel.fillUnitIndex) <= 0 and self.vehicle:getFillUnitCapacity(self.spec.fuel.fillUnitIndex) ~= 0 then
				-- g_currentMission:addExtraPrintText(g_i18n:getText("info_firstFillTheTool"));
			-- end;
		-- else
			-- self.warningCount = nil;
		-- end;
	-- end;

	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;





