Skip to content

From LSL to Lua

You already know how Second Life scripting works—events, permissions, object communication, inventory management. All of that knowledge transfers directly to Lua. This page focuses on the syntax and pattern differences you need to know.

  • Faster execution and ~50% less memory than LSL/Mono
  • Modern features: coroutines, gradual typing, multiple return values
  • Easier development: familiar syntax, better tooling, standard libraries
  • Same ll functions* you already know
LSL
SLua
LSL
SLua

In many cases, Luau does typecasting automatically. Here’s how to do it explicitly:

LSL
SLua

LSL and Lua typecasts return different results on invalid input: LSL returns “zero”; Lua returns nil. The Lua examples include an or expressions that converts nil to what LSL typecast would have returned

OperationLSLLuaNotes
Not equal!=~=Different operator
Logical AND&&andWord, not symbol
Logical OR||orWord, not symbol
Logical NOT!notWord, not symbol
String concat+..Different operator
Incrementx++x = x + 1 or x += 1No ++ operator
Decrementx--x = x - 1 or x -= 1No -- operator
PowerllPow(2, 3)2 ^ 3Native operator
Integer division7 / 417 // 41Use // not /
String lengthllStringLength(s)#sNative operator
Bitwise ANDllGetAgentInfo(id) & AGENT_TYPINGbit32.btest(ll.GetAgentInfo(id), AGENT_TYPING)No & operator (1)
Bitwise ORPASSIVE | SCRIPTEDbit32.bor(PASSIVE, SCRIPTED)No | operator
Bitwise NOT~0bit32.bnot(0)No ~ operator (2)
Bitwise eXclusive OR6 ^ 3bit32.bxor(6, 3)^ means Power instead (2)
Bitwise Shift Left1 << 2bit32.lshift(1, 2)No << operator (2)
Bitwise Shift Right0x80000000 >> 2bit32.arshift(0x80000000, 2)No >> operator (2)

Notes:

  1. bit32.btest returns a boolean; bit32.band returns an integer. Both match the behavior of LSL &. Integers (bit32.band) don’t work inside if statements, unlike LSL. Use whichever is most appropriate.
  2. Hexadecimal numbers 0x8000000 and above are positive in Lua, but negative in LSL, due to different number formats (signed 32-bit int vs 64-bit float). Use bit32.s32 to wrap large numbers down to the 32-bit signed integer range for LSL compatability.
LSL
SLua
LSL
SLua

Tables are Lua’s most powerful data structure. Unlike LSL’s separate list type, tables serve two purposes: they work as both arrays (like LSL lists) and dictionaries/maps (which LSL doesn’t have).

LSL
SLua

LSL doesn’t have a dictionary/map type—you had to use two parallel lists for key-value pairs. Lua tables can store key-value pairs natively:

-- Dictionary/map
local playerData: {[string]: number} = {
["Alice"] = 100,
["Bob"] = 85,
["Charlie"] = 92
}
-- Access values by key
local aliceScore: number = playerData["Alice"] -- 100
-- Add/update entries
playerData["Diana"] = 88
playerData["Alice"] = 105 -- Update
-- Check if key exists
if playerData["Unknown"] ~= nil then
ll.Say(0, "Found!")
end
-- Iterate over key-value pairs
for name: string, score: number in playerData do
ll.Say(0, `{name}: {score}`)
end

Shorthand syntax for string keys:

-- Instead of brackets and quotes, use dot notation
local player = {
name = "Alice",
health = 100,
position = vector(10, 20, 30)
}
ll.Say(0, player.name) -- "Alice"
player.health = 50 -- Update

Tables can even mix numeric indices and string keys:

local data = {
10, 20, 30, -- Indices 1, 2, 3
name = "Alice", -- String key
active = true -- String key
}
ll.Say(0, `First: {data[1]}`) -- 10
ll.Say(0, `Name: {data.name}`) -- "Alice"
LSL
SLua
LSL
SLua

Key difference: Use ll.FunctionName (dot notation), not llFunctionName.

This is the most significant difference between LSL and Lua. Instead of state-based event handlers, Lua uses event callbacks with LLEvents:on().

LSL
SLua

Basic event handler:

LLEvents:on("touch_start", function(events: {DetectedEvent})
ll.Say(0, `Touched by {events[1]:getName()}`)
end)

Multiple events (run simultaneously):

LLEvents:on("touch_start", function(events: {DetectedEvent})
ll.Say(0, "Touched!")
end)
LLEvents:on("collision_start", function(events: {DetectedEvent})
ll.Say(0, "Collision!")
end)

Listen events:

ll.Listen(0, "", NULL_KEY, "")
LLEvents:on("listen", function(channel: number, name: string, id: string, message: string)
ll.Say(0, `{name} said: {message}`)
end)

Accessing detected data:

LLEvents:on("touch_start", function(events: {DetectedEvent})
for i, event in events do
local name: string = event:getName()
local key: uuid = event:getKey()
ll.Say(0, `{name} touched me!`)
end
end)
LSL
SLua
LSL
SLua
LSL
SLua

While types are optional, use them in your code! They catch errors and improve readability. Learn more about Luau’s type system.

-- Basic types
local count: number = 0
local name: string = "Alice"
local active: boolean = true
-- SL types
local pos: vector = vector(10, 20, 30)
local rot: rotation = rotation(0, 0, 0, 1)
local id: string = "uuid-here"
-- Tables/arrays
local scores: {number} = {95, 87, 92}
local names: {string} = {"Alice", "Bob"}
-- Custom types
type Player = {
name: string,
health: number,
position: vector
}
local player: Player = {
name = "Alice",
health = 100,
position = vector(0, 0, 0)
}
-- Function types (always use!)
function heal(player: Player, amount: number): Player
player.health = player.health + amount
return player
end

Lua includes Lua standard libraries you can use alongside ll* functions. See the full Luau library reference for more details.

-- String library
string.upper("hello") -- "HELLO"
string.sub("hello", 1, 2) -- "he" (1-based!)
string.format("Health: %d", health)
-- Table library
table.insert(items, value)
table.remove(items, index)
table.sort(items)
-- Math library
math.floor(3.7) -- 3
math.ceil(3.2) -- 4
math.random(1, 10) -- Random 1-10
math.abs(-5) -- 5