--[[
Specialization to increase the RPM while tipping

Author:        Ifko[nator]
Date:        31.10.2015
Version:    1.5

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 @ 31.10.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

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(savegame)
    if SpecializationUtil.hasSpecialization(Drivable, self.specializations) then
        local modDesc = loadXMLFile("modDesc", increaseRPMWhileTippingDirectory .. "modDesc.xml");
        
        --## vehicle xml file
        self.maxRpmLocal = getXMLInt(xmlFile, "vehicle.increaseRPMWhileTipping#maxRpm");
        self.exhaustColorLocal = Utils.getVectorNFromString(getXMLString(xmlFile, "vehicle.increaseRPMWhileTipping#exhaustColor"), 4);
        self.testModeLocal = getXMLBool(modDesc, "modDesc.increaseRPMWhileTipping#testMode");
        
        --## modDesc file from 'zzz_IncreaseRPMWhileTipping.zip'
        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);
        self.testModeGlobal = Utils.getNoNil(getXMLBool(modDesc, "modDesc.increaseRPMWhileTipping#testMode"), false);
        
        --## check if value in the vehicle xml file exist, else took modDesc value
        if self.maxRpmLocal ~= nil then
            self.maxRpm = self.maxRpmLocal;
        else
            self.maxRpm = self.maxRpmGlobal;
        end;
        
        if self.exhaustColorLocal ~= nil then
            self.exhaustColor = self.exhaustColorLocal;
        else
            self.exhaustColor = self.exhaustColorGlobal;
        end;
        
        if self.testModeLocal ~= nil then
            self.testMode = self.testModeLocal;
        else
            self.testMode = self.testModeGlobal;
        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;
        
        delete(modDesc);
    end;

    self.doIncrease = false;
    self.isSelectable = true; --## make ANY vehicle selectable!
end;    

function IncreaseRPMWhileTipping:mouseEvent(posX, posY, isDown, isUp, button)
end;

function IncreaseRPMWhileTipping:keyEvent(unicode, sym, modifier, isDown)
end;

function IncreaseRPMWhileTipping:update(dt)    
    if self.isClient and self:getIsActive() then
        local trailer = nil;
        
        for _, attachedTrailer in pairs(self.attachedImplements) do
            if attachedTrailer.object ~= nil then
                --## one or more trailer attached

                trailer = attachedTrailer.object;
                
                if trailer.tipState ~= nil and trailer.tipState == Trailer.TIPSTATE_OPENING then
                    self.doIncrease = true;
                else
                    self.doIncrease = false;
                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 (self.tipState ~= nil and self.tipState == Trailer.TIPSTATE_OPENING) or trailer.tipState == Trailer.TIPSTATE_OPENING or trailer.doIncrease then
                    self.doIncrease = true;
                else
                    self.doIncrease = false;
                end;
            else
                --## no trailer attached
            
                if self.tipState ~= nil then
                    if self.tipState == Trailer.TIPSTATE_OPENING then
                        self.doIncrease = true;
                    else
                        self.doIncrease = false;
                    end;
                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
                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.testMode 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 .. ".");
    end;
end;