--[[ A simple helper intercepting commands to change gears and instead replacing everything with a simple R/N/P/D gearbox. To use, simply add to the start `local shifter = require('shared/physics/rnpd-shifting')()`, and then make sure to call `shifter:update()` at your update step, and `shifter:reset()` from `script.reset` function. Feel free to copy code from this library to your script if you’d want to change anything. An example of extended use of these functions is available at: https://github.com/ac-custom-shaders-patch/acc-lua-examples/blob/main/cars_physics/gearboxes/script_at.lua https://github.com/ac-custom-shaders-patch/acc-lua-examples/blob/main/cars_physics/gearboxes/script_amt.lua ]] ---@diagnostic disable ---@class RnpdShifter ---@field private _carPh ac.StateCphysCar ---@field private _programs string[] ---@field private _hShifterMap table ---@field private _shiftErrorCooldown number ---@field private _prevGearRequest integer ---@field private _program string local RnpdShifter = class('RnpdShifter') ---@type fun(cfg: nil|{extraPrograms: string[], hShifterMap: table}): RnpdShifter function RnpdShifter:initialize(cfg) self._carPh = ac.accessCarPhysics() self._programs = table.chain({'R', 'N', 'P', 'D'}, cfg and cfg.extraPrograms or {}) self._hShifterMap = cfg and cfg.hShifterMap or {[0] = 'R', [2] = 'P', [3] = 'D'} self._shiftErrorCooldown = -1 self._prevGearRequest = 0 -- track gear requests from user self:reset() end ---Make sure to call this function from `script.reset()`. function RnpdShifter:reset() -- Switching to parking on reset self._program = self._carPh.gear == 0 and 'R' or self._carPh.gear == 1 and (self._carPh.speedKmh > 5 and 'N' or 'P') or 'D' ac.setGearLabel(self._program, false) end ---@return 'R'|'N'|'P'|'D' @Return currently active program. function RnpdShifter:program() return self._program end ---Update state of the gearbox. ---@return boolean @Returns `true` if current program has changed. function RnpdShifter:update() local shifted = false -- Get user gear inputs and redirect them into selecting a gear: R, N, P or D if car.isAIControlled then -- AIs are just driving forward at all times if self._program ~= 'D' then self._program = 'D' ac.setGearLabel(self._program, false) shifted = true end else local desiredGear = self._program if self._carPh.requestedGearIndex ~= -1 then -- Not sure if this is a good map, but I feel like if all cars would have their maps -- set differently, it’d be in the spirit of automatic gearboxes desiredGear = self._hShifterMap[self._carPh.requestedGearIndex] or 'N' else local gearRequest = self._carPh.gearUp and 1 or self._carPh.gearDown and -1 or 0 if gearRequest ~= self._prevGearRequest then desiredGear = self._programs[math.clamp(table.indexOf(self._programs, self._program) + gearRequest, 1, #self._programs)] self._prevGearRequest = gearRequest end end if desiredGear and desiredGear ~= self._program then -- Let’s say, to change gears brakes should be pressed if self._carPh.brake > 0.5 and self._carPh.speedKmh < 1 then self._program, shifted = desiredGear, true ac.setGearLabel(self._program, false) elseif self._shiftErrorCooldown > os.preciseClock() then ac.setMessage('Automatic gearbox', 'Stop the car and press brakes to shift gears') else self._shiftErrorCooldown = os.preciseClock() + 1 ac.showNotification('automaticShiftBlocked') end end end self._carPh.gearUp, self._carPh.gearDown, self._carPh.requestedGearIndex = false, false, -1 return shifted end return class.emmy(RnpdShifter, RnpdShifter.initialize)