From LSL to SLua
For LSL Scripters
Section titled “For LSL Scripters”You already know how Second Life scripting works—events, permissions, object communication, inventory management. All of that knowledge transfers directly to SLua. This page focuses on the syntax and pattern differences you need to know.
Why Consider SLua?
Section titled “Why Consider SLua?”- 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
Syntax Quick Reference
Section titled “Syntax Quick Reference”Comments & Structure
Section titled “Comments & Structure”// Single line comment/* Multi-line comment */
integer x = 5; // Semicolons required-- Single line comment--[[ Multi-line comment ]]
local x: number = 5 -- Semicolons optional (rarely used)Variables & Types
Section titled “Variables & Types”// Type declarations requiredinteger 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];-- Dynamic typing with optional annotationslocal health: number = 100local damage: number = 25.5local name: string = "Alice"local pos: vector = vector(10, 20, 30)local rot: quaternion = quaternion(0, 0, 0, 1)local uuid: string = "..." -- keys are stringslocal items: {number} = {1, 2, 3} -- tables, not listsOperators
Section titled “Operators”| Operation | LSL | SLua | Notes |
|---|---|---|---|
| Not equal | != | ~= | Different operator |
| Logical AND | && | and | Word, not symbol |
| Logical OR | || | or | Word, not symbol |
| Logical NOT | ! | not | Word, not symbol |
| String concat | + | .. | Different operator |
| Increment | x++ | x = x + 1 or x += 1 | No ++ operator |
| Decrement | x-- | x = x - 1 or x -= 1 | No -- operator |
| Power | llPow(2, 3) | 2 ^ 3 | Native operator |
| Integer division | 7 / 4 → 1 | 7 // 4 → 1 | Use // not / |
| String length | llStringLength(s) | #s | Native operator |
Control Flow
Section titled “Control Flow”// if, then, elseif (x > 5) { // ...} else if (x > 0) { // ...} else { // ...}
// whilewhile (count > 0) { count--;}
// forfor (i = 0; i < 10; i++) { // ...}
// do-whiledo { count++;} while (count < 10);-- if, then, elseif x > 5 then -- ...elseif x > 0 then -- ...else -- ...end
-- whilewhile count > 0 do count -= 1end
-- for (numeric)for i = 0, 9 do -- ...end
-- repeat-until (like do-while)repeat count += 1until count >= 10Functions
Section titled “Functions”float calculateDamage(float base, float mult) { return base * mult;}
// Single return value onlyfloat result = calculateDamage(10.0, 1.5);-- Always use type annotationsfunction calculateDamage(base: number, mult: number): number return base * multend
-- Multiple return valuesfunction getStats(): (number, number, string) return 100, 50, "Healthy"end
local health: number, mana: number, status: string = getStats()Lists vs Tables
Section titled “Lists vs Tables”Tables are SLua’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).
Tables as Arrays (LSL list replacement)
Section titled “Tables as Arrays (LSL list replacement)”list items = [10, 20, 30, 40];
// 0-based indexinginteger first = llList2Integer(items, 0); // 10integer second = llList2Integer(items, 1); // 20
// List operationsitems += [50]; // Appendinteger len = llListLength(items);local items: {number} = {10, 20, 30, 40}
-- 1-based indexing (critical difference)local first: number = items[1] -- 10local second: number = items[2] -- 20
-- Table operationstable.insert(items, 50) -- Appendlocal len: number = #itemsTables as Dictionaries/Maps
Section titled “Tables as Dictionaries/Maps”LSL doesn’t have a dictionary/map type—you had to use two parallel lists for key-value pairs. SLua tables can store key-value pairs natively:
-- Dictionary/maplocal playerData: {[string]: number} = { ["Alice"] = 100, ["Bob"] = 85, ["Charlie"] = 92}
-- Access values by keylocal aliceScore: number = playerData["Alice"] -- 100
-- Add/update entriesplayerData["Diana"] = 88playerData["Alice"] = 105 -- Update
-- Check if key existsif playerData["Unknown"] ~= nil then ll.Say(0, "Found!")end
-- Iterate over key-value pairsfor name: string, score: number in playerData do ll.Say(0, `{name}: {score}`)endShorthand syntax for string keys:
-- Instead of brackets and quotes, use dot notationlocal player = { name = "Alice", health = 100, position = vector(10, 20, 30)}
ll.Say(0, player.name) -- "Alice"player.health = 50 -- UpdateMixed Tables
Section titled “Mixed Tables”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]}`) -- 10ll.Say(0, `Name: {data.name}`) -- "Alice"String Operations
Section titled “String Operations”string msg = "Hello" + " " + "World";string name = "Alice";string greeting = "Hi " + name + "!";
integer len = llStringLength(msg);string sub = llGetSubString(msg, 0, 4); // "Hello"local msg: string = `Hello World`local name: string = "Alice"local greeting: string = `Hi {name}!`
local len: number = #msg -- or string.len(msg)local sub: string = string.sub(msg, 1, 5) -- "Hello" (1-based!)ll* Functions
Section titled “ll* Functions”llSay(0, "Hello");llSetPos(<10, 20, 30>);llSetRot(<0, 0, 0, 1>);llGiveInventory(avatar, "Object");ll.Say(0, "Hello")ll.SetPos(vector(10, 20, 30))ll.SetRot(rotation(0, 0, 0, 1))ll.GiveInventory(avatar, "Object")Key difference: Use ll.FunctionName (dot notation), not llFunctionName.
Event Handling: The Big Change
Section titled “Event Handling: The Big Change”This is the most significant difference between LSL and SLua. Instead of state-based event handlers, SLua uses event callbacks with LLEvents:on().
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!"); } }}local clickCount: number = 0
ll.Say(0, "Ready!")
LLEvents:on("touch_start", function(num_detected: number) clickCount += 1 ll.Say(0, `Clicked {clickCount} times`)
if clickCount >= 5 then ll.Say(0, "Done counting!") endend)SLua Event Patterns
Section titled “SLua Event Patterns”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!`) endend)Global Variables
Section titled “Global Variables”// All script-level variables are globalinteger health = 100;string playerName = "Alice";
myFunction() { health = 50; // Modifies global}-- Script-level variables (no 'local') are globalhealth = 100playerName = "Alice"
function myFunction() -- Use 'local' for function-scoped variables local temp: number = 5 health = 50 -- Modifies globalendCommon Patterns
Section titled “Common Patterns”Door Script
Section titled “Door Script”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; } }}local isOpen: boolean = falselocal closedPos: vector = ll.GetPos()local openPos: vector = closedPos + vector(0, 0, 2)
LLEvents:on("touch_start", function(num_detected: number) if isOpen then ll.SetPos(closedPos) isOpen = false else ll.SetPos(openPos) isOpen = true endend)Timer Events
Section titled “Timer Events”integer counter = 0;
default { state_entry() { llSetTimerEvent(1.0); }
timer() { counter++; llSay(0, "Count: " + (string)counter); }}local counter: number = 0
LLTimers:every(1.0, function() counter += 1 ll.Say(0, `Count: {counter}`)end)Type Annotations Guide
Section titled “Type Annotations Guide”While types are optional, use them in your code! They catch errors and improve readability. Learn more about Luau’s type system.
-- Basic typeslocal count: number = 0local name: string = "Alice"local active: boolean = true
-- SL typeslocal pos: vector = vector(10, 20, 30)local rot: rotation = rotation(0, 0, 0, 1)local id: string = "uuid-here"
-- Tables/arrayslocal scores: {number} = {95, 87, 92}local names: {string} = {"Alice", "Bob"}
-- Custom typestype 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 playerendStandard Libraries
Section titled “Standard Libraries”SLua includes Lua standard libraries you can use alongside ll* functions. See the full Luau library reference for more details.
-- String librarystring.upper("hello") -- "HELLO"string.sub("hello", 1, 2) -- "he" (1-based!)string.format("Health: %d", health)
-- Table librarytable.insert(items, value)table.remove(items, index)table.sort(items)
-- Math librarymath.floor(3.7) -- 3math.ceil(3.2) -- 4math.random(1, 10) -- Random 1-10math.abs(-5) -- 5Next Steps
Section titled “Next Steps”- SLua Basics - Deep dive into the language
- SLua Events - Master event handling patterns
- SLua Reference - All ll* functions
- SLua FAQ - Common questions