log in
substrata logo

Luau Scripting in Substrata

You can create scripts in Substrata using the Luau programming language, which is Roblox's version of the Lua programming language.

Creating an object script

To create a script for your object, you will need to use the native Substrata client (i.e. not the web client). This can be downloaded from the main Substrata page.

To get all the scripting functionality, you may need the latest test Substrata client, available on the Substrata discord channel.

In the native client, double click on the object you want to script. Then click on the 'Edit' button in the script section:

This will pop up the script editor window (shown here with an existing script):

To create a Luau script, you must start your script with

--lua

This tells Substrata to expect a Luau script, as opposed to a Winter or XML script.

To apply changes in your script to the object, just close the script editor window.

Script execution environment

Your script will run on both the client and the server. For example, that means that every client that connects to substrata.info will run your script, and your script will also run on the substrata.info server.

Calling some functions or assigning some attributes will generally only have any immediate effect on either the client or server, depending on what attribute or function it is.

Attribute updates that affect physics behaviour will generally run client-side, because physics in Substrata is computed client side.

Other attribute updates will only run on the server, and then the updated object information will be immediately and automatically sent to all connected clients.

Debugging your script

You can print output from your script with the print function, for example
print("hello")

Debugging output, including print output is available in the log window (Tools > Show Log).

If an error occurs while running your script, the error while be shown in the log window (or the server log) as well, and the script will be stopped. To restart your script you will need to edit it, thereby reloading the script.

Here is an example of a message printed from print():

As well as the actual message, the UID of the script's object is also printed.

Since scripts run on the server as well, you can view debugging output from the server at https://substrata.info/script_log.

This page will show the last 1000 or so messages and errors, from all scripts created with your user account. You must be logged in to view this page.

Event Listeners

These functions can be defined in your script to listen for certain events. If you define one of these functions, they will be called when the event triggers for the object whose script it is.

You can also listen for these events on other objects with addEventListener.

onUserTouchedObject(avatar : Avatar, object : Object)
avatar : Avatar The avatar that touched the object.
object : Object The object that was touched.
Called when a user's avatar touches an object. If an avatar continues to touch an object, for example the avatar is standing on an object, this event will continue to be fired at a regular period of something like 0.5 s.
onUserUsedObject(avatar : Avatar, object : Object)
avatar : Avatar The avatar of the player that used the object.
object : Object The object that was used.
Called when a user uses an object. Currently users use objects by moving the mouse cursor over an object and pressing the E key.
onUserMovedNearToObject(avatar : Avatar, object : Object)
avatar : Avatar The avatar that moved near the object.
object : Object The object that the player moved near to.
Called when a user avatar moves near (within a certain distance) to an object. The distance is currently 20 metres.
onUserMovedAwayFromObject(avatar : Avatar, object : Object)
avatar : Avatar The avatar that moved near the object.
object : Object The object that the player moved away from.
Called when a user avatar moves away from (further than a certain distance) an object. The distance is currently 20 metres.
onUserEnteredParcel(avatar : Avatar, object : Object, parcel : Parcel)
avatar : Avatar The avatar that entered the parcel.
object : Object The object in the parcel.
parcel : Parcel The parcel.
Called when a user avatar enters the land parcel that the script object is inside.
onUserExitedParcel(avatar : Avatar, object : Object, parcel : Parcel)
avatar : Avatar The avatar that exited the parcel.
object : Object The object in the parcel.
parcel : Parcel The parcel.
Called when a user avatar exits the land parcel that the script object is inside.
onUserEnteredVehicle(avatar : Avatar, vehicle_ob : Object)
avatar : Avatar The avatar that entered the vehicle.
vehicle_ob : Object The vehicle object
Called when a user avatar enters (or sits on in the case of a bike) a vehicle.
onUserExitedVehicle(avatar : Avatar, vehicle_ob : Object)
avatar : Avatar The avatar that exited the vehicle.
vehicle_ob : Object The vehicle object
Called when a user avatar exits a vehicle.

Global functions

getObjectForUID(uid : number) : Object
uid : number The UID (unique identifier) of the object.
Returns the object that has the given UID. Throws exception if no such object exists.
showMessageToUser(msg : String, av : Avatar)
msg : StringThe message to send to the avatar's user.
av : AvatarThe avatar of the user to send the message to.
Shows a string message on the screen of the user of the given avatar. The message will be displayed for a few seconds.
createTimer(onTimerEvent : function, interval_time_s : number, repeating : bool) : number
onTimerEvent : functionThe timer event handler to be called when the timer fires.
interval_time_s : numberThe time interval, in seconds, until the timer fires.
repeating : boolShould the timer keep firing every interval_time_s seconds, or should it just fire once.

Creates a timer, when the timer interval is complete, onTimerEvent is called. onTimerEvent should be a function with the following signature:

onTimerEvent(object : Object)

Throws an exception if more than 4 timer events are created without being destroyed.

Returns a timer handle that can be passed to destroyTimer.

destroyTimer(timer_handle : number) : number
timer_handle : number)A timer handle that was previously returned from createTimer.

Destroys a timer. Has no effect if the timer has already completed (for non-repeating timers), or has already been destroyed.

addEventListener(event_name : string, ob_uid : number, handler : Function) : number
event_name : stringThe name of the event. Valid event names are "onUserUsedObject", "onUserTouchedObject", "onUserMovedNearToObject", "onUserMovedAwayFromObject", "onUserEnteredParcel", "onUserExitedParcel", "onUserEnteredVehicle", "onUserExitedVehicle"
ob_uid : numberUID of the object that the event will be listened on. For example, for onUserTouchedObject, this is the UID of the object that will have to be touched to trigger the touch event.
handler : FunctionA function that will be called when the event occurs. You can use your existing event handling functions such as onUserTouchedObject.

Adds a listener for an event on another object.

For example, this code listens for the touch event on the object with UID 583:

addEventListener("onUserTouchedObject", 583, onUserTouchedObject)
doHTTPGetRequestAsync(URL : string, additional_header_lines : table, onDone : function, onError : function)
URL : stringThe URL to request. Should be a http or https URL.
additional_header_lines : table A table with any additional header lines to send in the HTTP request.
onDone : function A function that will be called when the HTTP request has completed. onDone takes a single argument which is a table like so:
{
  response_code: number
  response_message : string
  mime_type : string
  body_data : buffer
}

response_code is the HTTP response code, e.g. 200 or 404.

response_message is the corresponding string message such as "OK" or "File not found".

mime_type is the content type of the response, and will be something like "text/html".

body_data is the main body of the response.

onError : function A function that will be called if an error is encountered while doing the HTTP request, for example a connection cannot be made to the server. onError takes a single argument which is a table like so:
{
  error_code : number
  error_description : string
}
Current error codes are
ErrorCode_OK = 0,
ErrorCode_Other = 1,
ErrorCode_RateLimited = 2

Sends an HTTP request to a server.

Requests are rate-limited: a maximum of 5 requests per 300 seconds are allowed per user. Exceeding this rate will result in onError being called with error_code = 2 (ErrorCode_RateLimited).

For example:


function onDone(result)
	print("response_code: " .. tostring(result.response_code))
	print("response_message: " .. result.response_message)
	print("mime_type: " .. result.mime_type)
	print("body_data: " .. result.body_data)
end

function onError(result)
	print("error_code: " .. tostring(result.error_code))
	print("error_description: " .. result.error_description)
end

doHTTPGetRequestAsync(
	"https://coolwebsite.com/someapi?id=dfdfsdf", -- URL
	{ Authorization = "Basic afsdefsdfd" } -- additional_header_lines
	onDone, onError
)
doHTTPPostRequestAsync(URL : string, post_content : string, content_type : string, additional_header_lines : table, onDone : function, onError : function)
URL : stringThe URL to request. Should be a http or https URL.
post_content : stringThe main body content of the post request.
content_type : stringThe conent type of the post body, e.g. .
additional_header_lines : table A table with any additional header lines to send in the HTTP request.
onDone : function A function that will be called when the HTTP request has completed. onDone takes a single argument which is a table like so:
{
  response_code: number
  response_message : string
  mime_type : string
  body_data : buffer
}

response_code is the HTTP response code, e.g. 200 or 404.

response_message is the corresponding string message such as "OK" or "File not found".

mime_type is the content type of the response, and will be something like "text/html".

body_data is the main body of the response.

onError : function A function that will be called if an error is encountered while doing the HTTP request, for example a connection cannot be made to the server. onError takes a single argument which is a table like so:
{
  error_code : number
  error_description : string
}
Current error codes are
ErrorCode_OK = 0,
ErrorCode_Other = 1,
ErrorCode_RateLimited = 2

Sends an HTTP post request to a server.

Requests are rate-limited: a maximum of 5 requests per 300 seconds are allowed per user. Exceeding this rate will result in onError being called with error_code = 2 (ErrorCode_RateLimited).

For example:


function onDone(result)
	print("response_code: " .. tostring(result.response_code))
	print("response_message: " .. result.response_message)
	print("mime_type: " .. result.mime_type)
	print("body_data: " .. result.body_data)
end

function onError(result)
	print("error_code: " .. tostring(result.error_code))
	print("error_description: " .. result.error_description)
end

doHTTPPostRequestAsync(
	"https://coolwebsite.com/someapi?id=dfdfsdf", -- URL
	"{ id : \"123\" }", -- post content
	"application/json", -- content type
	{ Authorization = "Basic afsdefsdfd" } -- additional_header_lines
	onDone, onError
)
getSecret(secret_name : string) : string
secret_name : string The name of the secret to get.

Get a secret string that has been set via the Account Secrets page (/secrets). This is useful for retrieving things like API keys that you want to use in a script, but don't want other users to see.

my_api_key = getSecret("MY_API_KEY")
parseJSON(json : string) : string
json : string A JSON string to parse.

Parse a JSON string and convert it to the equivalent Lua object.

JSON objects and arrays are converted to Lua tables. JSON null is converted to Lua nil.

parseJSON('[123.456, true, \"abc\", null]') 
-- returns the Lua array table {123.456, true, "abc", nil}
objectstorage.setItem(key : string, value : Any serialisable Lua value) : string
key : string The storage item key. Storage items are uniquely identified by the combination of the script object UID, and the key string.
value : Any serialisable Lua value The value to store. Can be a single Lua value like a number, or a Lua table.

Store a value in object storage. These values are persistently stored, and remain stored even over server restarts or script reloads and changes, unlike variables in Lua scripts.

Overwrites any existing values with the given key and script object.

For example, here is how you could store a visitor counter in object storage:

objectstorage.setItem("num_visits", new_num_visits)
objectstorage.getItem(key : string) : string
key : string The storage item key. Storage items are uniquely identified by the combination of the script object UID, and the key string.

Retrieves a value from object storage. Returns nil if there is no such item stored.

num_visits = objectstorage.getItem("num_visits")

Global variables

this_object : Object The object on which the script currently being executed is defined.
IS_CLIENT : boolean True if the script is running on the Substrata client, false if running on the server.
IS_SERVER : boolean True if the script is running on the Substrata server, false if running on the client.

Classes

Object

Attributes:

model_url : stringThe Substrata URL of the 3d model for the object
pos : Vec3dThe position of the object
axis : Vec3fThe axis of rotation of the object
angle : numberThe counter-clockwise angle of rotation of the object around axis, in radians.
scale : Vec3fThe scale factor in x, y, and z directions.
collidable : booleanIs this object physically collidable with? If false, avatars and physics objects will pass through it without colliding with it.
dynamic : booleanIs this object a dynamic physics object?
sensor : booleanSensor objects still emit touch events, but objects can pass through them.
content : stringContent field. For Text and Hypercard object types, this is the text that is displayed.
video_autoplay : booleanIf true, video is automatically played for Video object types.
video_loop : booleanIf true, video automatically loops back to the beginning and starts playing again. If false, just plays once.
video_muted : booleanIf true, video is muted when playing.
mass : numberObject mass in kg for physics engine. Only relevant when dynamic is true.
friction : numberObject friction. Friction of the physics body (dimensionless number, usually between 0 and 1, 0 = no friction, 1 = friction force equals force that presses the two bodies together).
restitution : numberRestitution of body (dimensionless number, usually between 0 and 1, 0 = completely inelastic collision response, 1 = completely elastic collision response).
centre_of_mass_offset_os : Vec3fOffset of centre of mass of object, in object coordinates. Can be used to e.g. give a vehicle a lower centre of mass than by default.
audio_source_url : string Substrata URL of the audio source for this object. Can be a mp3 or wav URL
audio_volume : number Volume factor for audio source. Default is 1.

Methods:

getNumMaterials() : number Returns the number of materials of the object.
getMaterial(mat_index : number) : Material Returns the material of the object with the given index.
Material

Attributes:

colour : Vec3fThe reflective colour of the object. Vec3f(1,1,1) is a white object. Non-linear sRGB.
colour_texture_url : stringThe URL of the colour texture.
emission_rgb : Vec3fThe colour of the emitted light from the object. Non-linear sRGB.
emission_texture_url : stringThe URL of the emission texture.
normal_map_url : stringThe URL of the normal map.
roughness_val : numberThe roughness of the material. 0 = perfectly smooth, 1 = very rough.
roughness_texture_url : string URL of the metallic-roughness texture. The metallic value is read from the blue channel, the roughness value is read from the green channel.
metallic_fraction_val : numberThe metallic fraction of the material. 0 = not metal. 1 = completely metal.
opacity_val : number How opaque (non-transparent) the material is. Currently this value just determines if the transparent shader is used - it's used in the case where opacity_val < 1.
tex_matrix : Matrix2fThe texture coordinate matrix. Default value is {1, 0, 0, 1}.
emission_lum_flux_or_lum : number For the spotlight object materials, this is luminous flux. For generic model materials, luminance.
hologram : booleanIs this a hologram material? Hologram materials don't block light, but just emit it.
double_sided : booleanShould we render back-faces of the triangles with this material? Rendering is faster when not rendering back-faces, but if your object triangle mesh is not fully sealed, it may result in visible gaps.
Avatar

Attributes:

pos : Vec3dThe position of the avatar. This is approximately the position of the eyes of the avatar for a standard avatar height, and will be 1.67 m above the surface the avatar is standing on.
name : stringThe name of the user who is controlling the avatar.
linear_velocity : Vec3fThe linear velocity of the avatar, in metres per second.
vehicle_inside : ObjectThe vehicle object the avatar is inside-of/riding, or nil if none.

Examples

Luau scripting examples

< Home