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
// Single line comment
/* Multi-line
comment */
integer x = 5; // Semicolons required
SLua
LSL
// Type declarations required
integer health = 100;
float damage = 25.5;
string name = "Alice";
vector pos = <10, 20, 30>;
rotation rot = <0, 0, 0, 1>;
key uuid = "...";
list items = [1, 2, 3];
SLua
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
LSL
// if, then, else
if (x > 5) {
// ...
} else if (x > 0) {
// ...
} else {
// ...
}
// while
while (count > 0) {
count--;
}
// for
for (i = 0; i < 10; i++) {
// ...
}
// do-while
do {
count++;
} while (count < 10);
SLua
LSL
float calculateDamage(float base, float mult) {
return base * mult;
}
// Single return value only
float result = calculateDamage(10.0, 1.5);
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
list items = [10, 20, 30, 40];
// 0-based indexing
integer first = llList2Integer(items, 0); // 10
integer second = llList2Integer(items, 1); // 20
// List operations
items += [50]; // Append
integer len = llListLength(items);
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
string msg = "Hello" + " " + "World";
string name = "Alice";
string greeting = "Hi " + name + "!";
integer len = llStringLength(msg);
string sub = llGetSubString(msg, 0, 4); // "Hello"
SLua
LSL
llSay(0, "Hello");
llSetPos(<10, 20, 30>);
llSetRot(<0, 0, 0, 1>);
llGiveInventory(avatar, "Object");
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
integer clickCount = 0;
default {
state_entry() {
llSay(0, "Ready!");
}
touch_start(integer num_detected) {
clickCount++;
llSay(0, "Clicked " + (string)clickCount + " times");
if (clickCount >= 5) {
llSay(0, "Done counting!");
}
}
}
SLua

Basic event handler:

LLEvents:on("touch_start", function(num_detected: number)
ll.Say(0, `Touched by {ll.DetectedName(1)}`)
end)

Multiple events (run simultaneously):

LLEvents:on("touch_start", function(num_detected: number)
ll.Say(0, "Touched!")
end)
LLEvents:on("collision_start", function(num_detected: number)
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(num_detected: number)
for i = 1, num_detected do
local name: string = ll.DetectedName(i)
local key: string = ll.DetectedKey(i)
ll.Say(0, `{name} touched me!`)
end
end)
LSL
// All script-level variables are global
integer health = 100;
string playerName = "Alice";
myFunction() {
health = 50; // Modifies global
}
SLua
LSL
integer isOpen = FALSE;
vector closedPos;
vector openPos;
default {
state_entry() {
closedPos = llGetPos();
openPos = closedPos + <0, 0, 2>;
}
touch_start(integer num) {
if (isOpen) {
llSetPos(closedPos);
isOpen = FALSE;
} else {
llSetPos(openPos);
isOpen = TRUE;
}
}
}
SLua
LSL
integer counter = 0;
default {
state_entry() {
llSetTimerEvent(1.0);
}
timer() {
counter++;
llSay(0, "Count: " + (string)counter);
}
}
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