--[[ An example of an Automatic Transmission implementation. Intercepts gear shifts and replaces them with a custom R/N/P/D logic, shifts gears on its own, sets up a torque converter and slighltly changes shifting logic to achieve more realistic smooth shifting. To use, include the file like this: ```lua local automaticTransmission = require('shared/physics/automatic-transmission')() function script.update(dt) automaticTransmission.update() end function script.reset() automaticTransmission.reset() end ``` This one requires you to set SUPPORTS_SHIFTER to 1, this is how this script can shift gears efficiently. Simple settings are also available, add to “drivetrain.ini”: ```ini [SCRIPT_AUTOMATIC_GEARBOX] SHIFT_TIME=0.2 ``` For more advanced customization, you can specify custom shifting logic by replacing the first line with: ```lua local automaticTransmission = require('shared/physics/automatic-transmission')({ shiftingLogic = automaticGearboxUtils.simpleDriveFnFactory(), -- or pass your own function lockCondition = function (carPh) return carPh.gear > 3 and carPh.speedKmh > 50 and carPh.gas < 0.9 end, skipTorqueConverterSetup = false, -- change to `true` if you’re going to set up your own torque converter }) ``` And, of course, feel free to instead just copy the entire logic into your own “script.lua” and change anything you want any way you want. This thing is meant to be an example and a shortcut for modders who are not entirely familiar with Lua scripting. ]] ---@param options nil|{shiftingLogic: AutomaticLogicFn, lockCondition: (fun(car: ac.StateCphysCar): boolean), skipTorqueConverterSetup: boolean} ---@return {update: fun(dt: number), reset: fun()} return function (options) if _G.__atConfigured then _G.__atConfigured = true error('Another custom transmission has already been configured') end -- Load automatic gearbox helper library: local automaticGearboxUtils = require('shared/physics/automatic-transmission-utils') -- Load settings: options = options or {} local shiftingLogic = options.shiftingLogic or automaticGearboxUtils.oilDriveFnFactory() local lockCondition = options.lockCondition or function (carPh) return carPh.gear > 3 and carPh.speedKmh > 50 and carPh.gas < 0.9 end if not options.skipTorqueConverterSetup then -- Set a torque converter from the library: automaticGearboxUtils.smartTorqueConverter() end local automaticTransmission = {} local carPh = ac.accessCarPhysics() -- To shift nicely and directly, script uses H-shifter: local cfgDrivetrain = ac.INIConfig.carData(car.index, 'drivetrain.ini') if not cfgDrivetrain:get('GEARBOX', 'SUPPORTS_SHIFTER', false) then error('Set GEARBOX/SUPPORTS_SHIFTER in drivetrain.ini to 1 for automatic transmission to work properly') end -- To ensure smooth shifts, we’re going to slightly alter final gear ratio live on shifts: local baseGearRatio = cfgDrivetrain:get('GEARS', 'FINAL', 4) -- Load gear ratios into a simple table: local gears = {} local gearsCount = cfgDrivetrain:get('GEARS', 'COUNT', 1) for i = 1, gearsCount do table.insert(gears, cfgDrivetrain:get('GEARS', 'GEAR_%d' % i, 1)) end if #gears < 2 then error('At least two gears are required') end -- This thing will help to compute when to shift as the car drives along: local automaticLogic = automaticGearboxUtils.logicHelper(shiftingLogic, cfgDrivetrain:get('SCRIPT_AUTOMATIC_GEARBOX', 'SHIFT_TIME', 0.2), 2, 1 + #gears, math.huge) -- And this thing will intercept car gear shift inputs and convert them into a R/N/P/D gearbox, -- including visual representation: local shifter = require('shared/physics/rnpd-shifting')() -- Other settings: local lockGap = cfgDrivetrain:get('SCRIPT_AUTOMATIC_GEARBOX', 'LOCK_RATIO_GAP', 0.15) local brakeDamageThreshold = cfgDrivetrain:get('SCRIPT_AUTOMATIC_GEARBOX', 'BRAKE_DAMAGE_THRESHOLD_KMH', 5) function automaticTransmission.update(dt) -- Update R/N/P/D gearbox logic: shifter:update() -- Three different programs based on selected gear local curProgram = shifter:program() local ratioMult, gearShiftProgress = 1, 0 if curProgram == 'P' or curProgram == 'N' then -- P and N both shift into neutral: ac.overrideSpecificValue(ac.CarPhysicsValueID.DrivetrainEngagedGear, 1) -- P can also brake with the gearbox: if curProgram == 'P' then automaticGearboxUtils.parkingBrake(brakeDamageThreshold) end else -- Non-P programs without pressed brakes should awake the car to get it to roll: if carPh.brake < 0.01 then ac.awakeCarPhysics() end if curProgram == 'R' then -- R program is simple, just engage rear gear and that’s it: ac.overrideSpecificValue(ac.CarPhysicsValueID.DrivetrainEngagedGear, 0) elseif curProgram == 'D' then -- D program instead simply engages first forward gear and doesn’t touch AC gearbox at all after that: -- carPh.requestedGearIndex = 2 -- Instead, we’re using that compute helper and smoothly transition between gear ratios of our own gearbox: local curGear, prevGear curGear, gearShiftProgress, prevGear = automaticLogic(carPh.gear, dt) -- Automatic gearboxes have this thing where they can shift out of one gear and into another kinda simultaneously, -- and to recreate this behavior we need a smooth transition between gear ratios. Here is a solution: if we’re -- in the middle of smoothly engaging a gear, we can estimate the gear ratio we’d have if it’d be smooth and -- adjust final gear ratio to match, just for a bit: if gearShiftProgress < 1 and gears[prevGear] and gears[curGear] then local idealRatio = math.lerp(gears[prevGear], gears[curGear], gearShiftProgress) local actualRatio = gears[curGear] ratioMult = idealRatio / actualRatio end ac.overrideSpecificValue(ac.CarPhysicsValueID.DrivetrainEngagedGear, curGear) elseif curProgram == 'R' then carPh.requestedGearIndex = 0 end end -- Variable final ratio to simulate smooth automatic gearbox shifting (works the best when setting clutch override to 0): ac.setGearsFinalRatio(baseGearRatio * ratioMult) -- Some cars sometimes lock automatic gearboxes to save fuel. If we wouldn’t lock, we still need -- to set clutch and threshold to 0 to get torque converter to work at all times: local lockClutch = curProgram == 'D' and gearShiftProgress >= 1 and automaticGearboxUtils.lastRatio > 1 - lockGap and automaticGearboxUtils.lastRatio < 1 + lockGap and lockCondition(carPh) ac.overrideSpecificValue(ac.CarPhysicsValueID.DrivetrainOpenThreshold, lockClutch and 0.05 or 0) ac.overrideSpecificValue(ac.CarPhysicsValueID.DrivetrainClutchOverride, lockClutch and 1 or 0) -- Debugging info: if __monitored then ac.debug('base gear', carPh.gear) ac.debug('ratio mult.', ratioMult) ac.debug('velocity ratio', automaticGearboxUtils.lastRatio) ac.debug('lock clutch', lockClutch) end end function automaticTransmission.reset() -- Make sure to let R/N/P/D thing know car has been reset: shifter:reset() end return automaticTransmission end