Concepts
Live objects
A live object is a proxy for something in the game: reading one of its fields reads the game at that moment,
and assigning a field writes the value back. The cars, the players, the props, the Nemesis actors and the race
are live objects. There are no get_ and set_ functions anywhere in the API.
Reading and writing#
local car = ss.vehicle -- the local player's car (nil outside a race)
print(car.speed, car.speed_kmh, car.race.lap)
car.position.y = car.position.y + 3 -- read the game, write it back: the car lifts 3 m
car.rotation.yaw = 90 -- degrees
car.velocity = car.forward * 40 -- m/s: the physics velocity is writable too
car:impulse(car.up * car.mass * 5) -- an action with nothing to read back is a method
ss.vehicles[2].position = car.position + car.forward * 40 -- another car, 40 m ahead of ours
ss.race.splits = 8 -- the race is an object in the same style
The reference tables mark each field as read-only or not. Anything that performs an action rather than holding
a value is a method, called with a colon: car:impulse(vec), prop:mem(0x9c, "int").
Groups#
Live objects come in groups, and every group works the same way. group[i] indexes from 0 (the
index the race events carry as player), group.count is the number of objects,
group.all() returns a plain table for ipairs, and group.each() returns an
iterator for a for loop.
| Type | Field | Description |
|---|---|---|
| vehicle | ss.vehicles |
The cars, one per player. ss.vehicle is the local player's. See ss.vehicles. |
| player | ss.players |
The players: position, lap, score, timers. ss.player is the local one. See ss.players. |
| prop | ss.props |
The level's movable props, with find and near searches. See ss.props. |
| powerplay | ss.powerplays |
The level's Power Plays, which a script can fire, reset and switch off. See ss.powerplays. |
| actor | ss.nemesis |
Survival's trucks and the attack modes' cars. See ss.nemesis. |
| routine | ss.routines |
The mod's own running routines. See Routines. |
for car in ss.vehicles.each() do
ss.log("%d: %s at %.0f km/h", car.player, car.state, car.speed_kmh)
end
local props = ss.props.all() -- a plain table, for ipairs or #
local first = ss.players[0] -- player 0, not necessarily the leaderLive vectors#
A field that is a vector (position, rotation, velocity,
angular_velocity) is live as well. car.position.y = 5 reads the transform, replaces
that one component and applies it. Assigning the whole field accepts anything vector-like:
{ x =, y =, z = }, an ss.vec3, { 1, 2, 3 }, another object's live
vector, or { pitch =, yaw =, roll = } for a rotation.
The operators (+, -, * number, / number) return plain copies
that do not write through, which is what makes car.position + car.forward * 40 a point and
car.position = that a move. :copy() returns a plain snapshot on purpose, for a value
that must not change while a loop runs over it. ss.vec3 lists the methods.
Validity#
A live object belongs to the race it was created in. Every object has a valid field, which is true
while the object is in the running race. An object kept across the end of a race reads valid == false,
returns nil for every other field, and raises an error when written to. When the race ends
(race_end), the groups are emptied before the event's handlers run, so a handler sees no cars and no
props rather than freed ones. It's recommended to fetch objects fresh on race_start rather than keep
them from the previous race.
Cost#
Every field read is one call into the hook, a few loads and a unit conversion. That is cheap enough for a
handler that reads a handful of values a frame, and expensive enough that a loop over many fields of many
objects should not read them one by one. A player's snapshot is a plain table of all its fields
from one call, which is useful for such loops. Measured in a race with 867 props:
ss.props.near(point, 400) takes 0.2 ms, a sweep of every prop reading state 0.5 ms,
ss.props.find("bollard") 0.1 ms, and moving a compound prop 0.1 ms. An
ss.params path resolves on every access by walking the tree, which takes a
fraction of a millisecond. It's recommended to keep a value that is read more than once per frame in a local.
Errors#
Assigning a read-only field (car.speed = 0, player.position = 1) results in an error,
as does writing to an object whose valid is false. An error inside a handler is caught and logged
with a traceback, and the other handlers still run; see
How to: Your first mod.