Getting started
The sample mods
This page walks through the two mods that ship with the hook under samples/mods. Each is a
ModInfo.ini and one Scripts\main.lua. To run one, copy its folder to
<game>\Mods\<name>, with lua51.dll next to dinput8.dll (see
Installation), and start a race.
Magnet#
Magnet makes every prop within 80 m of the player's car slide towards it, twenty times a second, stopping five metres short so that the props gather in a ring instead of piling under the wheels. It demonstrates the prop class: a search by distance, a velocity write that wakes a prop, and timers that start and stop with the race.
-- Magnet: the level's props slowly gravitate towards the player's car.
--
-- An example of the live prop objects (docs/lua-scripting.md, "Props"). Every
-- update the nearest props within RADIUS are given a velocity towards the
-- car on the horizontal plane (their vertical velocity is left alone, so
-- gravity still works), and the pull stops STOP_AT metres short so they
-- gather in a ring instead of piling under the wheels. The first velocity
-- write wakes a prop: a static one gets its physics body (the game's own
-- break-off), so bollards, bins and boxes start sliding in, and the AIs
-- plough through them.
--
-- Install: copy this folder to <game>\Mods\Magnet (Scripts\main.lua beside
-- ModInfo.ini); lua51.dll must be next to dinput8.dll.
local RADIUS = 80 -- metres: props this close feel the pull
local SPEED = 3 -- metres per second towards the car
local STOP_AT = 5 -- metres: the ring they gather in
local MAX_PROPS = 24 -- the nearest ones per update
local RATE = 0.05 -- seconds between updates (20 a second)
local pulling -- the timer while a race runs
local function pull()
local car = ss.vehicle
if not car then return end
local here = car.position:copy() -- a plain copy: the car may move while we loop
local moved = 0
for _, prop in ipairs(ss.props.near(here, RADIUS)) do
if moved >= MAX_PROPS then break end
if prop.state ~= 'broken' then
local at = prop.position
local dx, dz = here.x - at.x, here.z - at.z
local dist = math.sqrt(dx * dx + dz * dz)
if dist > STOP_AT then
local s = SPEED / dist
-- the write wakes the prop (a static one gets its body first); the
-- vertical component is kept so a falling prop keeps falling
prop.velocity = ss.vec3(dx * s, prop.velocity.y, dz * s)
moved = moved + 1
end
end
end
end
ss.on("race_start", function()
ss.log("magnet on: %d props in the level", ss.props.count)
pulling = ss.every(RATE, pull)
end)
ss.on("race_end", function()
if pulling then ss.off(pulling); pulling = nil end
ss.log("magnet off")
end)
-- a line every five seconds on how the gathering goes
ss.every(5, function()
local car = ss.vehicle
if not car or not pulling then return end
local near = ss.props.near(car.position, RADIUS)
local ring, nearest, sample = 0, nil, nil
for _, prop in ipairs(near) do
local d = prop.position:distance(car.position)
if d <= STOP_AT + 2 then ring = ring + 1 end
if not nearest or d < nearest then nearest = d; sample = prop end
end
ss.log("magnet: %d props within %d m, %d in the ring, nearest %.1f m (%s, %s, moving %s at %.1f m/s), car at %s",
#near, RADIUS, ring, nearest or -1, sample and sample.name or "-", sample and sample.state or "-",
sample and tostring(sample.velocity) or "-", sample and sample.velocity:length() or 0, tostring(car.position))
end)How it works#
pull runs from a repeating timer. It first takes car.position:copy(), a plain copy of
the car's position, because a live vector reads the game on
every access and the car moves while the loop runs. ss.props.near(point, radius) returns the props
within the radius, nearest first. The distances are compared on the hook's side, so the loop only reads the
props it is going to move.
The write to prop.velocity is what moves a prop. The horizontal components point at the car,
scaled to SPEED; the vertical component is the prop's own, so a prop that is falling keeps falling.
A prop that has never been touched is part of the level's static hull, and the first velocity write cuts it out
and gives it a physics body, the same thing the game does when a car first hits it. From then on the AIs drive
through moving bollards. Props the game has already broken apart (state == "broken") are skipped.
The timer starts on race_start and stops on race_end. By the time the
race_end handler runs the props are gone, so the timer must not fire again. The five-second report
shows the plain reads a prop offers: name, state, velocity and its
:length(), and :distance() between two positions.
Trucks#
Trucks puts four of Survival's trucks on the circuit in every race, on any track and in any mode. It demonstrates the Nemesis actors: a request for them before the race is built, and the live objects that follow them once it runs.
-- Trucks: every race gets four of Survival's trucks, on any track and in any mode.
--
-- An example of the Nemesis actors (docs/lua-scripting.md, "Nemesis actors").
-- ss.race.nemesis_slots names what the game builds at race start, by the
-- template files it ships: the trucks here, or 'DemolitionTemplate.params' for
-- the attack modes' cars. ss.nemesis[i] follows them once the race runs.
--
-- Install: copy this folder to <game>\Mods\Trucks (Scripts\main.lua beside
-- ModInfo.ini); lua51.dll must be next to dinput8.dll.
local TRUCKS = 4
local slots = {}
for i = 1, TRUCKS do slots[i] = 'TruckTemplate.params' end
ss.race.nemesis_slots = slots
ss.on("race_start", function()
ss.log("%d trucks on the track", ss.nemesis.count)
end)
-- a line every ten seconds: where the nearest truck is
ss.every(10, function()
local car = ss.vehicle
if not car then return end
local nearest, dist
for truck in ss.nemesis.each() do
if truck.kind == 'truck' then
local d = truck.position:distance(car.position)
if not dist or d < dist then nearest, dist = truck, d end
end
end
if nearest then
ss.log("nearest truck %d: %.0f m away at %.0f km/h", nearest.index, dist, nearest.speed * 3.6)
end
end)How it works#
ss.race.nemesis_slots is a list of the template files the game builds its actors from, one per
slot. The game offers the templates in every mode, not only in Survival. The mod sets the list from
main.lua, before any race, and the hook applies it when the next race builds its actors. Setting
the list to nil restores whatever the mode file says.
Once the race runs, ss.nemesis holds the actors as live objects: count,
each(), and on each actor kind (truck, car or
helicopter), index, a live position and speed in metres per
second. The ten-second report walks them and keeps the nearest truck. Nothing on an actor is writable yet.