The controls class holds a car's controls as the physics receives them each frame, an AI car's included, together with the rest of the game's input record for that player. It is reached as a vehicle's input field (ss.player.input for the local player) and is a live object: reading a field reads what the game wrote this frame, and assigning a field holds that control from then on.

Instance members#

Instance members must be accessed through a vehicle's input field.

local input = ss.player.input
input.steer = 0.5
input:press("camera")
WARNING: Attempting to call instance members statically will result in an error!

Instance Methods#

What is a method?

FunctionReturn ValuesDescription
press(string name) nil Presses the named button for exactly one frame, which is what a tap of the button gives the game. name is one of camera, replay, powerplay1, powerplay2, full_powerbar or respot. See Pressing a button.
clear() nil Releases every held control, handing the car back to the pad or the AI.

Instance Fields#

TypeFieldRead-only?Description
number steer No The steering input, -1 (left) to 1 (right), after the game's steer curve. Setting this field holds it; see Holding a control.
number throttle No The throttle, 0 to 1. Setting this field holds it.
number brake No The brake, 0 to 1. Setting this field holds it.
boolean handbrake No Whether the handbrake is down. Setting this field holds it.
number look_x No The sideways look axis, -1 to 1: positive looks right, negative looks left. Setting this field holds it.
number look_y No The lengthways look axis, -1 to 1: positive looks forward, negative looks back. look_y = -1 is the rear view, facing the car's nose. Setting this field holds it.
boolean camera No Whether the camera button is down. Setting this field holds it.
boolean replay No Whether the instant replay button (IR on the Controls screen) is down. Setting this field holds it.
boolean powerplay1 No Whether the Power Play level 1 button went down on this frame. Setting this field holds it, which presses the button again every frame.
boolean powerplay2 No Whether the Power Play level 2 button went down on this frame. Setting this field holds it, which presses the button again every frame.
boolean pause Yes Whether the pause button is down. Read-only: the game's pause reads the keyboard and pad directly, so holding this field from a script does nothing.
boolean full_powerbar No The game's DebugCheat_FullPowerbar flag in the record. true fills the player's power bar. Setting this field holds it.
boolean respot No The game's manual respot: true puts the car back on the track without a crash. Setting this field holds it. car:respot() presses it once.
table override Yes A table of the controls currently held and their values.
table raw Yes The input record itself, indexed 0 to 63. Its slots are writable. See The raw record.

Holding a control#

The game writes the four physics controls (steer, throttle, brake and handbrake) once a frame, from the pad's bindings for a human and from the AI driver for an AI, and the hook writes the held values over them immediately after. Assigning a control holds it every frame until it is set to nil or the race ends. input = { throttle = 1, steer = 0.3 } holds several at once, input = nil or input:clear() releases them all, and override lists what is held.

Observed on the airport track: the idle local car drives off under throttle = 1, steer = 0.4; an AI car under throttle = 0, brake = 1 slows from 29 m/s to a stop in three seconds and pulls away again when released (it keeps steering by itself unless steer is held as well); the handbrake stops a car at speed within a second.

The other controls#

The fields other than the four physics controls are read off the game's own input record, an AI's included, and are held the same way: the hook writes a held value into the record every frame before the game reads it. The keys and pad buttons listed are the defaults; the Controls screen can rebind them.

TypeFieldDescription
number look_x -1 to 1: look right (positive) or left. look_x = 1 swings the view round to the car's side. Keys H and F, the right stick.
number look_y -1 to 1: look forward (positive) or back. look_y = -1 is the rear view, facing the car's nose. Keys T and G, the right stick.
boolean camera The camera button, while down. Key Q, pad Y.
boolean replay The instant replay button, while down. Key R, pad LB.
boolean powerplay1 Power Play level 1: true on the frame the button went down. Key 1, pad A.
boolean powerplay2 Power Play level 2: true on the frame the button went down. Key 2, pad B.
boolean pause The pause button, while down. Read-only. Key Esc, pad Start.
boolean full_powerbar The game's own debug flag in the record: true fills the player's power bar. No key.
boolean respot The manual respot: true on the frame the button went down. Pad Back; no key. The game only takes it from a script, since its modes switch the manual respot off.

Pressing a button#

press(name) presses camera, replay, powerplay1, powerplay2, full_powerbar or respot for exactly one frame, which is what a tap of the button gives the game: ss.player.input:press("camera") steps to the next camera once. The Power Play buttons are pressed-edge buttons in the game as well, so holding one through an assignment presses it again every frame, and a Power Play fires whenever one is in reach.

Observed with the idle local car: look_y = -1 turned the view round to the front of the car, look_x = 1 to its side, press("camera") switched the chase camera to the bumper camera, and full_powerbar held for three seconds filled the HUD's power bar from empty, where it stayed. A powerplay1 press with nothing in reach fired nothing, as a pad press would not.

The raw record#

raw[k], for k from 0 to 63, is the player's input record as the game's reader fills it from the bindings for a human (the AI fills its own cars' records). The axes are floats; the button slots hold the integers 0 and 1, and raw[k] reads them as integers. A write to a button slot takes an integer, with true and false counting as 1 and 0.

TypeFieldDescription
number [0] Brake, 0 to 1. Key S, pad LT.
number [2] Throttle, 0 to 1. Key W, pad RT.
number [3] Steer, -1 to 1, after the steer curve. Keys A and D, the left stick.
number [5], [6] look_x and look_y. Keys F, H and G, T, the right stick.
integer [7] camera, while held. Key Q, pad Y.
integer [8] replay, while held. Key R, pad LB.
integer [0xc] Pressed. Its use is not known (?). Key 2, pad A.
integer [0xf] pause, while held. Key Esc, pad Start.
integer [0xe], [0x10]..[0x12] While held. Unbound in the shipped tables.
integer [0x2a], [0x2b] powerplay1 and powerplay2, pressed. Keys 1 and 2, pad A and B.
integer [0x2c], [0x2d] Pressed. [0x2c] is unbound; [0x2d] is respot, pad Back.
integer [0x2e] full_powerbar.
integer [0x2f] Two bytes: Enter pressed in the low byte, then 1 in the next byte once the pad reader has run. The high half is never written.
TIP: The slots marked (?) are not fully researched.

A "while held" slot is 1 from the frame after the button went down until it comes up; a "pressed" slot is 1 for the single frame it went down. The other slots ([1], [4], [9] to [0xb], [0xd], [0x13] to [0x29], [0x30] and up) are not written for a human and hold leftovers.

The input event#

The input event runs once per player per frame, after the game has sampled the pad and filled the record and before it reads it, with ev.player and ev.is_local. A handler that sets raw[3] = -raw[3] for the local player inverts the steering before the game sees it. The held controls are applied after the handlers: the record fields immediately after them, the four physics controls after the game's own write.

The event is per frame, not per physics step: every step of the frame drives off this one record (one step at 60 fps and above with the uncapped loop, more below that). Measured at 120 fps, the event ran once in every frame of a whole race. It does not run while the game is paused, by the pause menu or by the window losing focus. Before the green light the local record carries values no key made (a throttle of 1 and a steer moving in sevenths during the countdown, as the record is quantised for the network), so a handler that wants the pad should wait for race_start.

Example#

local inverted = false
ss.on("race_start", function() inverted = true end)
ss.on("race_end", function() inverted = false end)

ss.on("input", function(ev)
	if inverted and ev.is_local then
		local raw = ss.vehicles[ev.player].input.raw
		raw[3] = -raw[3]                                   -- inverted steering, before the game reads it
	end
end)

ss.vehicles[1].input = { throttle = 0, brake = 1 }    -- an AI car brakes to a stop
ss.after(3, function() ss.vehicles[1].input:clear() end)