--[[
Specialization to increase the RPM while tipping

Author:		Ifko[nator]
Date:		22.12.2015
Version:	1.9

History: 	V 1.0 @ 28.10.2015 - intial release
			V 1.1 @ 29.10.2015 - added support for the gear box addon by mogli
			V 1.5 @ 01.11.2015 - added support for vehicles wich have no standard indoor hud
								 added support for vehicles wich have no standard exhaust effect
								 added support for the exhaust flap
			V 1.8 @ 06.12.2015 - the RPM will now increase for the whole tipping duration if the body is not moving upwards
			V 1.9 @ 22.12.2015 - added support for the it runner hook lift trailer

Big Thanks to Marhu for helping me out with some issues !!
]]

IncreaseRPMWhileTipping = {};

local increaseRPMWhileTippingDirectory = g_currentModDirectory;

function IncreaseRPMWhileTipping.prerequisitesPresent(specializations)
    return true;
end;

function IncreaseRPMWhileTipping:load(xmlFile)
	local modDesc = loadXMLFile("modDesc", increaseRPMWhileTippingDirectory .. "modDesc.xml");
	
	if SpecializationUtil.hasSpecialization(Drivable, self.specializations) then
		--## modDesc file 
		self.maxRpmGlobal = Utils.getNoNil(getXMLInt(modDesc, "modDesc.increaseRPMWhileTipping#maxRpm"), self.motor.maxRpm);
		self.exhaustColorGlobal = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(modDesc, "modDesc.increaseRPMWhileTipping#exhaustColor"), "0 0 0 6"), 4);
		
		
		--## vehicle xml file
		self.maxRpm = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.increaseRPMWhileTipping#maxRpm"), self.maxRpmGlobal);
		self.exhaustColorLocal = Utils.getVectorNFromString(getXMLString(xmlFile, "vehicle.increaseRPMWhileTipping#exhaustColor"), 4);
		
		--## check if value in the vehicle xml file exist, else took modDesc value
		if self.exhaustColorLocal ~= nil then
			self.exhaustColor = self.exhaustColorLocal;
		else
			self.exhaustColor = self.exhaustColorGlobal;
		end;
		
		self.colorRed = self.exhaustColor[1];
		self.colorGreen = self.exhaustColor[2];
		self.colorBlue = self.exhaustColor[3];
		self.colorAlphaMax = self.exhaustColor[4];
		self.colorAlpha = 1;
		
		self.increaseRPM = SpecializationUtil.callSpecializationsFunction("increaseRPM");
		self.minRpmStart = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.motor#minRpm"), 1000);
		self.rpm = self.minRpmStart;
		
		IndoorHud.updateTick = Utils.overwrittenFunction(IndoorHud.updateTick, IncreaseRPMWhileTipping.updateTickIndoorHud);
		Motorized.updateTick = Utils.overwrittenFunction(Motorized.updateTick, IncreaseRPMWhileTipping.updateTickMotorized);
		
		self.hasChangedGearBoxAddon = false;
	end;
	
	self.isTestModeGlobal = Utils.getNoNil(getXMLBool(modDesc, "modDesc.increaseRPMWhileTipping#isTestMode"), false);
	self.isTestMode = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.increaseRPMWhileTipping#isTestMode"), self.isTestModeGlobal);

	self.doIncrease = false;
	self.isSelectable = true; --## make ANY vehicle selectable!
	
	self.isTipScrollerVehicle = hasXMLProperty(xmlFile, "vehicle.tipScrollerNodes");
	
	local filename = getXMLString(xmlFile, "vehicle.filename");
	
	if string.find(self.configFileName, "pdlc/itRunnerPack") and g_modNameToDirectory["pdlc_itRunnerPack"] then
		self.isItRunnerHookLiftTrailerModul = filename == "itRunner/containerSilage.i3d" or filename == "itRunner/containerDumper.i3d";
		self.isItRunnerHookLiftTrailer = filename == "itRunner/itRunner2633HD.i3d";
	else
		self.isItRunnerHookLiftTrailerModul = false;
		self.isItRunnerHookLiftTrailer = false;
	end;
	
	self.isOverloadVehicle = SpecializationUtil.hasSpecialization(Overloading, self.specializations) or string.find(filename, "flieglOverloadStation") or false;
	
	if self.isTestMode then
		print("	loaded IncreaseRPMWhileTipping in: '" .. self.configFileName .. "'.");
		print("	'isTipScrollerVehicle' is '" .. tostring(self.isTipScrollerVehicle) .. "'.");
		print("	'isItRunnerHookLiftTrailerModul' is '" .. tostring(self.isItRunnerHookLiftTrailerModul) .. "'.");
		print("	'isItRunnerHookLiftTrailer' is '" .. tostring(self.isItRunnerHookLiftTrailer) .. "'.");
		print("	'isOverloadVehicle' is '" .. tostring(self.isOverloadVehicle) .. "'.");
	end;
	
	delete(modDesc);
end;	

function IncreaseRPMWhileTipping:update(dt)	
	if self.isClient and self:getIsActive() then
		local trailer;
		
		for _, attachedTrailer in pairs(self.attachedImplements) do
			if attachedTrailer.object ~= nil then
				--## one or more trailer attached
				
				trailer = attachedTrailer.object;
				
				if trailer.isTipScrollerVehicle or trailer.isItRunnerHookLiftTrailerModul then
					self.doIncrease = trailer.tipState == Trailer.TIPSTATE_OPENING or trailer.tipState == Trailer.TIPSTATE_OPEN or trailer.doIncrease;
				elseif trailer.isItRunnerHookLiftTrailer then
					self.doIncrease = trailer:getIsAnimationPlaying("unfoldHand") or trailer.doIncrease;
				elseif trailer.isOverloadVehicle then
					self.doIncrease = trailer.pipeIsUnloading;
				else
					self.doIncrease = trailer.tipState == Trailer.TIPSTATE_OPENING or trailer.doIncrease;
				end;
			end;
		end;
		
		if SpecializationUtil.hasSpecialization(Drivable, self.specializations) and self.isMotorStarted then
			--## drivable vehicle
			
			if trailer ~= nil then
				--## one or more trailer attached
				
				if trailer.isTipScrollerVehicle or trailer.isItRunnerHookLiftTrailerModul then
					if self.isTipScrollerVehicle then
						self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING or self.tipState == Trailer.TIPSTATE_OPEN or trailer.tipState == Trailer.TIPSTATE_OPENING or trailer.tipState == Trailer.TIPSTATE_OPEN or trailer.doIncrease;
					else
						self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING or trailer.tipState == Trailer.TIPSTATE_OPENING or trailer.tipState == Trailer.TIPSTATE_OPEN or trailer.doIncrease;
					end;
				elseif trailer.isItRunnerHookLiftTrailer then
					if self.isTipScrollerVehicle then
						self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING or self.tipState == Trailer.TIPSTATE_OPEN or trailer:getIsAnimationPlaying("unfoldHand") or trailer.doIncrease;
					else
						self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING or trailer:getIsAnimationPlaying("unfoldHand") or trailer.doIncrease;
					end;
				elseif trailer.isOverloadVehicle then
					if self.isTipScrollerVehicle then
						self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING or self.tipState == Trailer.TIPSTATE_OPEN  or self.pipeIsUnloading or trailer.pipeIsUnloading or trailer.doIncrease;
					else
						self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING or self.pipeIsUnloading or trailer.pipeIsUnloading or trailer.doIncrease;
					end;
				elseif self.isOverloadVehicle then --## combine with cutter attached
					--self.doIncrease = self.pipeIsUnloading;
				else
					self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING or trailer.tipState == Trailer.TIPSTATE_OPENING or trailer.doIncrease;
				end;
			else
				--## no trailer attached
			
				if self.tipState ~= nil then
					if self.isTipScrollerVehicle then
						self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING or self.tipState == Trailer.TIPSTATE_OPEN;
					else
						self.doIncrease = self.tipState == Trailer.TIPSTATE_OPENING;
					end;
				elseif self.isOverloadVehicle then --## combine
					--self.doIncrease = self.pipeIsUnloading;
				else
					self.doIncrease = false;
				end;
			end;

			self:increaseRPM(dt, self.doIncrease);
		end;
	end;
end;

function IncreaseRPMWhileTipping:updateTickIndoorHud(updateTick, dt)	
	if self:getIsActive() then
        --## overwrite the whole 'IndoorHud:updateTick' function to stop update the rpm hud value while tipping
		
		if self.indoorHud.speed ~= nil then
            local maxSpeed = 30;
            if self.cruiseControl ~= nil then
                maxSpeed = self.cruiseControl.maxSpeed;
            end;
            self:setHudValue(self.indoorHud.speed, g_i18n:getSpeed(self:getLastSpeed() * self.speedDisplayScale), g_i18n:getSpeed(maxSpeed));
        end;
        
		if self.indoorHud.rpm ~= nil and self.rpm == self.minRpmStart then
			self:setHudValue(self.indoorHud.rpm, self.motor.lastMotorRpm, self.motor.maxRpm);
        end;
      
		if self.indoorHud.fuel ~= nil then
            self:setHudValue(self.indoorHud.fuel, self.fuelFillLevel, self.fuelCapacity);
        end;
		
		if self.indoorHud.fuel ~= nil then
            self:setHudValue(self.indoorHud.fuel, self.fuelFillLevel, self.fuelCapacity);
        end;
       
		if self.indoorHud.fillLevel ~= nil and self.fillLevel ~= nil and self.getCapacity ~= (nil and 0) then
            self:setHudValue(self.indoorHud.fillLevel, self.fillLevel, self:getCapacity());
        end;
        
		if self.indoorHud.operatingTime ~= nil and self.operatingTime ~= nil then
            local minutes = self.operatingTime / (1000 * 60);
            local hours = math.floor(minutes / 60);
            minutes = math.floor(minutes - hours * 60);
            local minutesString = string.format("%02d", minutes);
            self:setHudValue(self.indoorHud.operatingTime, tonumber(hours .. "." .. minutesString), self.maxOperatingTime);
        end;
        
		if self.indoorHud.workedHectars ~= nil and self.workedHectars ~= nil then
            self:setHudValue(self.indoorHud.workedHectars, self.workedHectars, 9999);
        end;
        
		if self.indoorHud.diameter ~= nil and self.lastDiameter ~= nil then
            self:setHudValue(self.indoorHud.diameter, self.lastDiameter*1000, 9999);
        end;
        
		if self.indoorHud.cutLength ~= nil and self.currentCutLength ~= nil then
            self:setHudValue(self.indoorHud.cutLength, self.currentCutLength*100, 9999);
        end;
        
		if self.indoorHud.time ~= nil then
            local minutes = g_currentMission.environment.currentMinute;
            local hours = g_currentMission.environment.currentHour;
            local minutesString = string.format("%02d", minutes);
            self:setHudValue(self.indoorHud.time, tonumber(hours .. "." .. minutesString), 9999);
        end;
        
		if self.indoorHud.cruiseControl ~= nil and self.cruiseControl ~= nil then
            self:setHudValue(self.indoorHud.cruiseControl, self.cruiseControl.speed, 9999);
        end;
    end;
end;

function IncreaseRPMWhileTipping:updateTickMotorized(updateTick, dt)
	--## overwrite the whole 'Motorized:updateTick' function to stop update the exhaust effect and flap while tipping
	
	if self.isServer then
        if math.abs(self.fuelFillLevel - self.sentFuelFillLevel) > 0.001 then
            self:raiseDirtyFlags(self.motorizedDirtyFlag);
            self.sentFuelFillLevel = self.fuelFillLevel;
        end;
		
		--## needed for refillig fuel
		if self.isFuelFilling then
			local delta = 0;
			
            if self.fuelFillTrigger ~= nil then
                delta = self.fuelFillLitersPerSecond * dt * 0.001;
                delta = self.fuelFillTrigger:fillFuel(self, delta);
            end;
            
			if delta <= 0.001 then
                self:setIsFuelFilling(false);
            end;
        end;
    end;
    
	if self.isClient then
        if self.isMotorStarted then
            if self.exhaustParticleSystems.minScale ~= nil and self.exhaustParticleSystems.maxScale ~= nil then
                local scale = Utils.lerp(self.exhaustParticleSystems.minScale, self.exhaustParticleSystems.maxScale, self.motor.lastMotorRpm / self.motor.maxRpm);
               
				Utils.setEmitCountScale(self.exhaustParticleSystems, scale);
                
				for _, ps in ipairs(self.exhaustParticleSystems) do
                    setParticleSystemLifespan(ps.geometry, ps.originalLifespan * scale, true);
                end;
            end;
            
			if self.exhaustFlap ~= nil and self.rpm == self.minRpmStart then
                local minRandom = -0.1;
                local maxRandom = 0.1;
                local angle = Utils.lerp(minRandom, maxRandom, math.random()) + self.exhaustFlap.maxRot * (self.motor.lastMotorRpm / self.motor.maxRpm);
                
				angle = Utils.clamp(angle, 0, self.exhaustFlap.maxRot);
                setRotation(self.exhaustFlap.node, angle, 0, 0);
            end;
            
			if self.exhaustEffects ~= nil then
                local lastSpeed = self:getLastSpeed();
                self.currentDirection = {localDirectionToWorld(self.rootNode, 0, 0, 1)};
                if self.lastDirection == nil then
                    self.lastDirection = self.currentDirection;
                end;
                
				local x,y,z = worldDirectionToLocal(self.rootNode, self.lastDirection[1], self.lastDirection[2], self.lastDirection[3]);
                local dot = z;
                
				dot = dot / Utils.vector2Length(x,z);
                local angle = math.acos(dot);
				
				if x < 0 then
                    angle = -angle;
                end;
                
				local steeringPercent = math.abs((angle / dt) / self.exhaustEffectMaxSteeringSpeed);
                self.lastDirection = self.currentDirection;
                
				for _, effect in pairs(self.exhaustEffects) do
                    local rpmScale = self.motor.lastMotorRpm / self.motor.maxRpm;
                    local scale = Utils.lerp(effect.minRpmScale, effect.maxRpmScale, rpmScale);
                    local forwardXRot = effect.forwardXRotations[2];
                    local forwardZRot = effect.forwardZRotations[2];
                    local steerXRot = effect.steerXRotations[2];
                    local steerZRot = effect.steerZRotations[2];
                    local r = Utils.lerp(effect.minRpmColor[1], effect.maxRpmColor[1], rpmScale);
                    local g = Utils.lerp(effect.minRpmColor[2], effect.maxRpmColor[2], rpmScale);
                    local b = Utils.lerp(effect.minRpmColor[3], effect.maxRpmColor[3], rpmScale);
                    local a = Utils.lerp(effect.minRpmColor[4], effect.maxRpmColor[4], rpmScale);
						
					if self.rpm == self.minRpmStart then
						setShaderParameter(effect.effectNode, "exhaustColor", r, g, b, a, false);
                    end;
					
					-- speed rotation
                    if self.movingDirection == 1 then
                        local percent = Utils.clamp(lastSpeed/effect.maxForwardSpeed, 0, 1);
                        
						forwardXRot = effect.forwardXRotations[1] * percent;
                        forwardZRot = effect.forwardZRotations[1] * percent;
                    elseif self.movingDirection == -1 then
                        local percent = Utils.clamp(lastSpeed/effect.maxBackwardSpeed, 0, 1);
                        
						forwardXRot = effect.forwardXRotations[3] * percent;
                        forwardZRot = effect.forwardZRotations[3] * percent;
                    end;
                   
					-- steering rotation
                    if angle > 0 then
                        steerXRot = effect.steerXRotations[1] * steeringPercent;
                        steerZRot = effect.steerZRotations[1] * steeringPercent;
                    elseif angle < 0 then
                        steerXRot = effect.steerXRotations[3] * steeringPercent;
                        steerZRot = effect.steerZRotations[3] * steeringPercent;
                    end;
                    
					-- target rotations
                    local targetXRot = forwardXRot + steerXRot;
                    local targetZRot = forwardZRot + steerZRot;
                    
					-- damping
                    if targetXRot > effect.xRot then
                        effect.xRot = math.min(effect.xRot + 0.003*dt, targetXRot);
                    else
                        effect.xRot = math.max(effect.xRot - 0.003*dt, targetXRot);
                    end;
                    
					if targetZRot > effect.xRot then
                        effect.zRot = math.min(effect.zRot + 0.003*dt, targetZRot);
                    else
                        effect.zRot = math.max(effect.zRot - 0.003*dt, targetZRot);
                    end;
					
                    setShaderParameter(effect.effectNode, "param", effect.xRot, effect.zRot, 0, scale, false);
                end;
            end;
        end;
	end;
end;

function IncreaseRPMWhileTipping:delete()
end;

function IncreaseRPMWhileTipping:increaseRPM(dt, isActive)
	if dt ~= nil then
		if isActive then		
			if self.mrGbMS ~= nil then
				--## gearBoxAddon by mogli found
				
				if self:mrGbMGetIsOn(true) and not self.hasChangedGearBoxAddon then
					self:mrGbMSetIsOnOff(false);
					self.hasChangedGearBoxAddon = true;
				end;
			end;
			
			if self.rpm < self.maxRpm then
				if self.indoorHud ~= nil and self.indoorHud.rpm ~= nil then
					self.rpm = self.rpm + 8;
					self:setHudValue(self.indoorHud.rpm, self.rpm, self.maxRpm);
				end;
				
				if self.colorAlpha < (self.colorAlphaMax - 0.05) then
					self.colorAlpha = self.colorAlpha + 0.05;
				end;
				
				if self.exhaustEffects ~= nil then
					for _, effect in pairs(self.exhaustEffects) do
						setShaderParameter(effect.effectNode, "exhaustColor", self.colorRed, self.colorGreen, self.colorBlue, self.colorAlpha, false);
					end;
				end;
				
				if self.exhaustFlap ~= nil then
					local minRandom = -0.1;
					local maxRandom = 0.1;
					local angle = Utils.lerp(minRandom, maxRandom, math.random()) + self.exhaustFlap.maxRot * (self.rpm / self.maxRpm);
					
					angle = Utils.clamp(angle, 0, self.exhaustFlap.maxRot);
					setRotation(self.exhaustFlap.node, angle, 0, 0);
				end;
			end;
			
			if self.rpm > self.maxRpm then
				self.rpm = self.maxRpm;
			end;
			
			self.motor.lastMotorRpm = math.min(self.minRpmStart + dt, self.maxRpm);
			self.motor.minRpm = math.max(self.motor.minRpm - dt, - self.maxRpm);
		else
			if self.mrGbMS ~= nil then
				--## gearBoxAddon by mogli found
				
				if self.hasChangedGearBoxAddon and self.rpm == self.minRpmStart then
					self:mrGbMSetIsOnOff(true);
					self.hasChangedGearBoxAddon = false;
				end;
			end;
			
			if self.rpm > self.minRpmStart then
				if self.indoorHud ~= nil and self.indoorHud.rpm ~= nil then
					self.rpm = self.rpm - 12;
					self:setHudValue(self.indoorHud.rpm, self.rpm, self.maxRpm);
				end;
				
				if self.colorAlpha > 1 then
					self.colorAlpha = self.colorAlpha - 0.05;
				end;
				
				if self.exhaustEffects ~= nil then
					for _, effect in pairs(self.exhaustEffects) do
						setShaderParameter(effect.effectNode, "exhaustColor", self.colorRed, self.colorGreen, self.colorBlue, self.colorAlpha, false);
					end;
				end;
				
				if self.exhaustFlap ~= nil then
					local minRandom = -0.1;
					local maxRandom = 0.1;
					local angle = Utils.lerp(minRandom, maxRandom, math.random()) + self.exhaustFlap.maxRot * (self.rpm / self.maxRpm);
					
					angle = Utils.clamp(angle, 0, self.exhaustFlap.maxRot);
					setRotation(self.exhaustFlap.node, angle, 0, 0);
				end;
			end;
			
			if self.rpm < self.minRpmStart then
				self.rpm = self.minRpmStart;
			end;
			
			self.motor.minRpm = math.min(self.motor.minRpm + dt * 2, self.minRpmStart);
		end;
	else
		self.motor.minRpm = self.minRpmStart;
	end;
end;
	
function IncreaseRPMWhileTipping:draw()
	if SpecializationUtil.hasSpecialization(Drivable, self.specializations) and self.isTestMode then
		setTextAlignment(RenderText.ALIGN_CENTER) 
		
		setTextBold(true);
		renderText(0.5, 0.53, 0.03, "Test Mode of the Increase RPM While Tipping Script is on!");
		
		setTextBold(false);
		renderText(0.5, 0.5, 0.025, "min rpm = " .. self.motor.minRpm .. ".");
		renderText(0.5, 0.475, 0.025, "do rpm increase = " .. tostring(self.doIncrease) .. ".");
		renderText(0.5, 0.45, 0.025, "tipping rpm = " .. self.rpm .. ".");
		renderText(0.5, 0.425, 0.025, "exhaust colors = r: " .. self.colorRed .. " g: " .. self.colorGreen .. " b: " .. self.colorBlue .. " a: " .. self.colorAlpha .. ".");
		renderText(0.5, 0.4, 0.025, "exhaust color alpha max = " .. self.colorAlphaMax .. ".");
		
		if self.pipeIsUnloading ~= nil then
			--renderText(0.5, 0.375, 0.025, "pipe is unloading = " .. tostring(self.pipeIsUnloading) .. ".");
		end;
	end;
end;

function IncreaseRPMWhileTipping:mouseEvent(posX, posY, isDown, isUp, button)end;
function IncreaseRPMWhileTipping:keyEvent(unicode, sym, modifier, isDown)end;