@name Engine_Simulator
@inputs [Pod Chassis Engine Flywheel Slave FuelTank]:wirelink
@outputs [Engine_Active Engine_RPM Engine_Torque Flywheel_PowerInertia Engine_Ignition Gearbox_Gear Gearbox_Clutch Turbo_Boost Turbo_PSI Fuel_Capacity Fuel_Liters Flywheel_RPM]:number
@persist [C E S T]:entity
@persist [Engine_IdleRev, Engine_RevLimiter, Engine_Bore, Engine_Stroke, Engine_Cylinders Engine_Airflow Engine_Configuration Engine_Displacement Engine_PeakTorque Engine_Redline Engine_Mass Engine_FlywheelMass Engine_Idle]:number [Engine_TorqueTable]:array
@persist [Fuel_Enabled Fuel_Capacity Fuel_Air_Ratio Fuel_Density Air_Density]:number
@persist [Gearbox_FinalDrive Gearbox_Delay Gearbox_LockValue Gearbox_ClutchSpeed]:number [Gearbox_Ratios]:array
@persist [Turbo Turbo_Exp Turbo_Inertia Turbo_FlowScale Turbo_DragScale Turbo_PSIMax Turbo_AirPressure Turbo_MaxFlow Turbo_MaxRPM]:number
@persist [Engine_SoundOn Engine_SoundOff]:string
@trigger none
 
@persist [Gearbox_Ratio Gearbox_Gear Gearbox_Lock Gearbox_Clutch]:number
@persist [Throttle_Delay Clutch_Delay]:number
@persist Fuel_Liters:number
@persist [Brake Engine_IdleThrottle Engine_Ignition Engine_PowerCut Engine_Volume Flywheel_PowerInertia]:number
@persist Turbo_RPM:number
 
if (duped() | dupefinished()) {reset()} else {runOnTick(1)}
 
if (first()) {
    Engine_Bore = 93
    Engine_Stroke = 84
    Engine_Cylinders = 6
    Engine_Airflow = 235.5
    Engine_Idle = 800
    Engine_IdleRev = 0.1
    Engine_RevLimiter = 1
    Engine_TorqueTable = array(0.6, 0.7, 0.8, 0.74, 0.5)
    Engine_FlywheelMass = 5
    Engine_Configuration = 0
    Engine_Configurations = table(
        0 = "Inline-",
        1 = "V"
    )
 
    Gearbox_Ratios = array(
    2.95,
    1.62,
    1.00,
    0.68
    )
 
    Gearbox_Ratios[-1, number] = -2.14
    Gearbox_FinalDrive = 3.63
    Gearbox_Delay = 150
    Gearbox_ClutchSpeed = 0.1
    Gearbox_LockValue = 10.05
   
    Fuel_Enabled = 0
    Fuel_Air_Ratio = 1 / 12.5
    Fuel_Density = 0.745 # 0.72 | 0.745 ( Petrol | Diesel )
    Air_Density = 0.0012
   
    Engine_Displacement = round((Engine_Cylinders * pi() * Engine_Bore ^ 2 * Engine_Stroke / 4000000), 1)
    Engine_PeakTorque = round((Engine_Stroke / Engine_Bore * Engine_Displacement * Engine_Airflow) * (Engine_Stroke  / Engine_Bore), 2)
    Engine_PeakTorque *= 0.8
    Engine_Redline = round((Engine_Bore / Engine_Stroke / Engine_Cylinders * Engine_Airflow * 2000 / 6), 0)
    Engine_Redline *= (Engine_Bore / Engine_Stroke)
    Engine_Redline -= Engine_Redline % 100
    Engine_Redline = 7000 # Override original value
    Engine_Mass = round((floor(Engine_Displacement * 45 + Engine_Cylinders * 4 + clamp(Engine_Configuration, 0, 1) * 10)), 0)
    Engine_Mass += Engine_FlywheelMass
    Engine_Mass += (Gearbox_Ratios:count() * 8.66)
   
    Turbo = 1
    Turbo_Exp = 1.95
    Turbo_Inertia = 0.025
    Turbo_FlowScale = 75
    Turbo_DragScale = 0.01
    Turbo_PSIMax = 10
    Turbo_AirPressure = 24.7
    Turbo_MaxFlow = Engine_Redline * ((Turbo_PSIMax + Turbo_AirPressure) / Turbo_AirPressure)
    Turbo_MaxRPM = 140000
 
    C = Chassis:entity()
   
    S = Slave:entity()
        S:setMass(5e4)
        S:propGravity(0)
       
    E = Engine:entity()
        E:setMass(Engine_Mass)
        E:propInertia(maxVec(vec(Engine_Mass, Engine_Mass, 2), vec(40, 40, 2)))
   
    T = FuelTank:entity()
    Fuel_Capacity = T:boxSize():length()
    Fuel_Liters = Fuel_Capacity
   
    foreach (K, V:entity = Flywheel["Entities", array]) {
        V:setMass(Engine_Mass)
        V:propInertia(maxVec(vec(Engine_Mass, Engine_Mass, 2), vec(40, 40, 2)))
        V:propGravity(0)
    }
 
    setName(
        "Engine Simulator by PTuga and Unslinga" + "\n"
        + Engine_Configurations[Engine_Configuration, string] + Engine_Cylinders + "\n"
        + "Displacement: " + Engine_Displacement + " L\n"
        + "Peak Torque: " + Engine_PeakTorque + " nm\n"
        + "Redline: " + Engine_Redline + "\n"
        + "Flywheel Mass: " + Engine_FlywheelMass * 0.1 + " kg"
    )
 
    Engine_SoundOn = "acf_extra/vehiclefx/engines/diesel/newdiesel.wav"
    Engine_SoundOff = "acf_extra/vehiclefx/engines/diesel/newdiesel.wav"
 
    function void loadSounds() {
        E:soundPlay("On", 0, Engine_SoundOn:lower())
        E:soundPlay("Off", 0, Engine_SoundOff:lower())
        if (Turbo) {
            E:soundPlay("Turbo", 0, "acf_engines/turbine_small.wav")
        }
    }
   
    loadSounds()
   
    runOnLast(1)
}
 
Pod_Active = Pod:number("Active")
 
if (changed(Pod_Active) | Pod_Active) {
    Key_W = Pod:number("W")
    Key_S = Pod:number("S")
    Key_M1 = Pod:number("Mouse1")
    Key_M2 = Pod:number("Mouse2")
    Key_F = Pod:number("Light")
    Key_R = Pod:number("R")
    Key_Sp = Pod:number("Shift")
    Key_Sh = Pod:number("Space")
    Key_Alt = Pod:number("Alt")
    Key_Z = Pod:number("Zoom")
   
 
   
    if (changed(Key_F)) {
        if (Engine_Ignition == 1 && Engine_Active == 0) {
            Engine_Ignition = 2
        }
       
        if (Engine_Ignition == 2 & !Key_F) {
            Engine_Ignition = 1
        }
    }
   
    if (changed(Key_F) & Key_F) {
        if (Engine_Ignition == 0) {
            Engine_Ignition = 1
        }
   
        if (Engine_Ignition == 1 && Engine_Active == 1) {
            Engine_Ignition = 0
        }
    }
   
    Engine_Ignition = min(Engine_Ignition, (Engine_Active ? 1 : 2))
   
    if (changed(Engine_Ignition)) {
        if (Engine_Ignition == 2) {
            E:soundPlay("Start", 0, "acf_extra/vehiclefx/starters/starter2.wav")
            soundVolume("Start", 0.5)
        } else {
            soundStop("Start")
        }
    }
 
    soundPitch("Start", max(Engine_RPM / Engine_Idle, 0.35) * 200)
 
    Gearbox_Gear += (changed(Key_M1) & Key_M1) - (changed(Key_M2) & Key_M2)
    Gearbox_Gear *= !(changed(Key_R) & Key_R)
    Gearbox_Gear = clamp(Gearbox_Gear, Gearbox_Ratios:minIndex(), Gearbox_Ratios:count())
   
    if (changed(Gearbox_Gear)) {
        if (Gearbox_Ratios[Gearbox_Gear, number]) {
            Gearbox_Ratio = Gearbox_Ratios[Gearbox_Gear, number] * Gearbox_FinalDrive
        }
       
        C:soundPlay("Gear_Change", 0, "physics/plaster/ceiling_tile_impact_hard3.wav")
            soundPitch("Gear_Change", 60)
            soundVolume("Gear_Change", 0.3)
 
        timer("Gear_Change", Gearbox_Delay)
           
        Throttle_Delay = 1
        Clutch_Delay = 1
       
        foreach (K, V:entity = Flywheel["Entities", array]) {
            local Mass = max(Engine_Mass * Gearbox_Ratio, Engine_Mass)
            V:setMass(Mass)
            V:propInertia(maxVec(vec(Mass, Mass, 2), vec(40, 40, 2)))
        }
    }
   
    Brake += (Key_S - Brake) * 0.025
    Brake *= Key_S
}
 
if (clk("Beep")) {
    soundStop("Beep")
   
    C:soundPlay("Beep", 1, "buttons/bell1.wav")
    soundVolume("Beep", 0.2)
    soundPitch("Beep", 200)
}
 
Engine_Active = (Engine_RPM > 350) * sign(Engine_Ignition)
if (Engine_Ignition & !Engine_Active) {timer("Beep", 1200)}
if (changed(Engine_Active) & Engine_Active) {loadSounds(), stoptimer("Beep")}
if (!Engine_Active & (Engine_Ignition == 2) & (Gearbox_Clutch == 1)) {Engine_RPM += 25}
 
Chassis_Feedback = vec()
Flywheels_RPM = array()
 
foreach (K, V:entity = Flywheel["Entities", array]) {
    Flywheels_RPM[K, number] = V:angVelVector():dot(V:toLocalAxis(E:up())) / 6
   
    if (Key_S) {
        local Force = 500 * Flywheels_RPM[K, number] * Brake
        V:applyTorque(V:toLocalAxis(-E:up()) * Force)
        Chassis_Feedback += Force
    }
}
 
if (Chassis_Feedback) {C:applyTorque(Chassis_Feedback * C:toLocalAxis(E:up()))}
 
Flywheel_RPM = Flywheels_RPM:average()
 
Gearbox_Clutch -= Gearbox_Clutch * Gearbox_ClutchSpeed
if (!Gearbox_Gear | Clutch_Delay | Key_S | Key_Sp) {Gearbox_Clutch = 1}
if (Key_Sh) {Gearbox_Clutch = max(Gearbox_Clutch, 0.25)}
#Gearbox_Clutch = max(Gearbox_Clutch, 0.02, (Engine_RPM / Engine_Redline) * 0.1)
 
Engine_RealRPM = (Engine_RPM * Gearbox_Clutch) + ((Flywheel_RPM * Gearbox_Ratio) * (1 - Gearbox_Clutch))
 
Engine_Throttle = (Key_W ? (Key_Alt ? 1 : 0.50) : 0)
 
Engine_IdleThrottle += ((min(Engine_RPM, Engine_RealRPM) < Engine_Idle) ? Engine_IdleRev : -Engine_IdleRev)
Engine_IdleThrottle = clamp(Engine_IdleThrottle, 0, 0.5) * Engine_Active
 
Engine_Throttle = max(Engine_Throttle, Engine_IdleThrottle)
 
if (Fuel_Enabled) {
    Fuel_Liters -= ((((Engine_Displacement * (Engine_RPM/2)) * Air_Density * Fuel_Air_Ratio)/Fuel_Density)/(65*60) * max(Engine_Throttle, 0.01)) * Engine_Active
    Fuel_Liters = clamp(Fuel_Liters, 0, T:boxSize():length())
    Engine_Active *= (Fuel_Liters > 0)
    T:setMass(round((Fuel_Liters * Fuel_Density) + (T:boxSize():length() * 0.1), 1))
}
 
Engine_Throttle *= Engine_Active
Engine_Throttle *= !Throttle_Delay
 
Engine_PowerCut += ((Engine_RealRPM > Engine_Redline) ? 0.05 : -0.05)
Engine_PowerCut = clamp(Engine_PowerCut, 0, 1) * round(Engine_Throttle)
 
if (clk("Gear_Change")) {Throttle_Delay = 0, Clutch_Delay = 0}
 
Factor = clamp((Engine_RPM / Engine_Redline), 0, 1)
Engine_Torque = (5 * (1 - Factor) ^ 4 * Factor * Engine_TorqueTable[1, number]) + (10 * (1 - Factor) ^ 3 * Factor ^ 2 * Engine_TorqueTable[2, number]) + (10 * (1 - Factor) ^ 2 * Factor ^ 3 * Engine_TorqueTable[3, number]) + (5 * (1 - Factor) * Factor ^ 4 * Engine_TorqueTable[4, number]) + (Factor ^ 5 * Engine_TorqueTable[5, number])
Engine_Torque = clamp(Engine_Torque, 0, 1)
 
if (Turbo) {
    Turbo_PSI = Turbo_PSIMax * Turbo_RPM / Turbo_MaxRPM
 
    Turbo_Boost = (Turbo_PSI + Turbo_AirPressure) / Turbo_AirPressure
 
    Turbo_Flow = max(Engine_RPM, 1) * Turbo_Boost / Turbo_MaxFlow
 
    Turbo_Drag = (Turbo_MaxFlow * Turbo_DragScale) * (Turbo_RPM / Turbo_MaxRPM) * (1 - Engine_Throttle) / Turbo_Inertia
 
    Turbo_RPM = clamp(Turbo_RPM + (Turbo_FlowScale * (Turbo_Flow ^ Turbo_Exp)) / Turbo_Inertia - Turbo_Drag, 1, Turbo_MaxRPM)
   
    soundVolume("Turbo", (Turbo_Flow / 1.5 - 0.25))
    soundPitch("Turbo", 75 + 50 * Turbo_RPM / Turbo_MaxRPM)
   
    if (changed(Engine_Throttle) & !Engine_Throttle) {
        soundStop("BOV")
       
        E:soundPlay("BOV", 1, "acf_extra/vehiclefx/boost/turbo_hiss1.wav")
            soundVolume("BOV", max((Turbo_RPM / Turbo_MaxRPM) - 0.2, 0))
            soundPitch("BOV", 85 + 25 * (Turbo_RPM / Turbo_MaxRPM))
    }
   
    Engine_Torque *= (Turbo_Boost / 1.25)
}
 
Flywheel_PowerInertia += ((Engine_PeakTorque * Engine_Torque) - Flywheel_PowerInertia) / Engine_FlywheelMass
Flywheel_PowerInertia = max(Flywheel_PowerInertia, (Engine_PeakTorque * Engine_Torque))
 
if (Gearbox_Clutch < 1) {
    Engine_Inertia = clamp(Engine_RealRPM, 0, Engine_Redline) - (Flywheel_RPM * Gearbox_Ratio)
    Engine_Inertia *= (1 - Gearbox_Clutch)
    Engine_Inertia /= Engine_Redline
   
    Engine_Throttle -= ((0.285 * (Engine_RealRPM / Engine_Redline)) * (Engine_Throttle <= 0))
 
    Flywheel_Power = Flywheel_PowerInertia
    Flywheel_Power *= Gearbox_Ratio
    Flywheel_Power *= (1 - Gearbox_Clutch)
   
    Flywheel_Power *= min(clamp(Engine_Throttle, -1, ((1 - Engine_PowerCut) ^ 2)) + Engine_Inertia, 1)
    Flywheel_Power *= clamp(1 - (((Engine_RealRPM / Engine_RPM) - 1) * Engine_Throttle), -1, 1) ^ Engine_FlywheelMass
 
    if (Flywheel_Power) {
        foreach (K, V:entity = Flywheel["Entities", array]) {
            V:applyOffsetForce(E:right() * (Flywheel_Power / Flywheel["Entities", array]:count()), V:pos() - (E:forward() * 39.37))
            V:applyOffsetForce(E:right() * -(Flywheel_Power / Flywheel["Entities", array]:count()), V:pos() + (E:forward() * 39.37))
        }
 
        C:applyOffsetForce(E:right() * -Flywheel_Power, E:pos() - (E:forward() * 39.37))
        C:applyOffsetForce(E:right() * Flywheel_Power, E:pos() + (E:forward() * 39.37))
    }
   
    Gearbox_Feedback = (Flywheel_RPM * Gearbox_Ratio) - Engine_RPM
    Gearbox_Feedback /= Engine_FlywheelMass
}
 
Gearbox_Feedback *= (1 - Gearbox_Clutch)
 
Engine_Feedback = (sign(Engine_Throttle) * Engine_Redline - Engine_RPM) * (sign(Engine_Throttle) ? (abs(Engine_Throttle * Engine_Torque)) : (Engine_Active ? 0.03 : 0.13)) / 4
Engine_Feedback *= Gearbox_Clutch
 
Engine_RPM += min(Gearbox_Feedback + Engine_Feedback, (Engine_Redline - Engine_RPM) / (Engine_FlywheelMass * (Engine_Stroke / Engine_Bore)))
if (Engine_RPM > (Engine_Redline * 0.95) && Engine_RevLimiter) {Engine_RPM = Engine_Redline * 0.9}
Engine_RPM = min(Engine_RPM, Engine_Redline)
Engine_RPM *= random(0.995, 1.005)
 
Engine_Volume += (max(Engine_Throttle ^ 1.25, (Engine_RPM / 9000) * 0.25) - Engine_Volume) * 0.2
 
soundPitch("On", (Engine_RPM / 8000) * 155 + (50 * Engine_Active))
soundVolume("On", Engine_Volume * 0.75)
 
soundPitch("Off", (Engine_RPM / 8000) * 155 + (50 * Engine_Active))
soundVolume("Off", ((1 - Engine_Volume) ^ 1.5) * 0.25)
 
S:propFreeze(1)
S:setAng(E:angles())
 
if (last()) {
    foreach (K, V:entity = Flywheel["Entities", array]) {
         
    }
}
