LSL-201 Intermediate The School of Creation
Lead Instructor

Sorin Todys - Advanced expert with 20+ years of experience in virtual worlds


All classes take place in Alife Virtual World at our dedicated Alife Virtual School region

LSL Intermediate - Events, States & Control Flow

Explore the immersive 3D world of Alife Virtual - Your free virtual world alternative

Explore the immersive 3D world of Alife Virtual - Your free virtual world alternative
High-resolution image (1920×1080 pixels) from Alife Virtual World School

Course Code: LSL-201


Part of: The School of Creation

1. Course Overview

Welcome to LSL-201!

Hello and welcome, future master scripters! My name is Sorin Todys, and I'll be your guide on this exciting journey into the heart of LSL. With over 20 years of experience scripting in virtual worlds, I've seen LSL evolve from a simple tool into a powerful language capable of creating breathtakingly complex and interactive experiences. This course is where we move beyond the basics and start breathing real life into your creations.

In LSL-101, you learned the alphabet. Now, we're going to learn how to write poetry. We will explore the event-driven model that makes LSL so unique, master the art of controlling script behavior with state machines, and harness the power of loops and conditionals to make intelligent decisions.

What You Will Master:

  • Event-Driven Programming: Understand and utilize LSL's core event handlers like touch_start, timer, and listen.
  • State Machines: Organize complex scripts into manageable, logical states (e.g., 'on', 'off', 'armed', 'idle').
  • Control Flow: Direct your script's logic with if/else conditionals and for/while loops.
  • Interactive Objects: Build practical, multi-functional objects like command-operated devices, security systems, and simple vendors.
  • Debugging and Optimization: Learn professional techniques to find and fix bugs and write efficient, low-lag code.
Why is LSL so powerful? LSL allows your creations to react to the world and its inhabitants. A static sculpture is art, but a sculpture that changes color when you touch it, whispers secrets when you get close, or follows you around is magic. That's the power you're about to unlock.

Prerequisites Review

This is an intermediate course. We assume you are comfortable with the concepts from LSL-101 or have equivalent basic programming experience. You should already understand:

  • Basic LSL syntax and structure (semicolons, curly braces).
  • Declaring and using variables (integer, float, string, key).
  • What a function is and how to call it (e.g., llSay()).
  • The default state and a simple event like touch_start.

About Your Instructor

Sorin Todys has been a cornerstone of virtual world creation since the early 2000s. With two decades of dedicated LSL scripting experience, he has built everything from complex combat systems to sprawling interactive art installations. Sorin believes in a hands-on, practical approach to teaching, ensuring that students not only understand the theory but can immediately apply it to their own projects in Alife Virtual.

2. Lesson 1: The Heart of Interaction - Mastering Events & Conditionals

Theory & Concepts

LSL is an event-driven language. Unlike a traditional program that runs from top to bottom and then stops, an LSL script is always "on," waiting for something to happen. These "somethings" are called events. A touch, a collision, a timer tick, a chat message—these are all events that can trigger your code to run.

The real power comes when we combine events with conditionals. A conditional statement, like if/else, allows your script to make decisions. "If the person who touched me is the owner, do this. Else, do something different." This is the fundamental building block of all interactive objects.

LSL Syntax and Structure

An event handler is a special block of code that runs only when its corresponding event occurs. The syntax is straightforward:

event_name(arguments)
{
    // Your code to run goes here
}

The if/else structure lets you check if a condition is true:

if (condition_is_true)
{
    // Do this
}
else if (another_condition_is_true)
{
    // Do this instead
}
else
{
    // If nothing else was true, do this
}

Step-by-Step Tutorial: The "Smart Greeter"

Let's build a greeter that gives a special message to its owner and a general message to everyone else. This combines the touch_start event with an if/else conditional.

  1. Create a new prim (e.g., a box).
  2. Inside the prim, create a new script.
  3. Delete the default "Hello, Avatar" code and replace it with the following:
// LSL-201: Lesson 1 - Smart Greeter

// This script will greet the owner with a special message
// and other avatars with a general message.

default
{
    // The state_entry event runs once when the script starts
    // or is reset. It's a great place for setup.
    state_entry()
    {
        llSay(0, "Smart Greeter is ready. Touch me!");
    }

    // The touch_start event runs whenever an avatar
    // clicks or touches the prim.
    // 'integer total_number' is an argument passed by the system,
    // but we don't need it for this script.
    touch_start(integer total_number)
    {
        // llDetectedKey(0) returns the UUID (key) of the avatar
        // who triggered the touch event.
        key toucher = llDetectedKey(0);

        // llGetOwner() returns the UUID (key) of the prim's owner.
        key owner = llGetOwner();

        // This is our conditional check!
        // We check if the key of the 'toucher' is the same as
        // the key of the 'owner'.
        if (toucher == owner)
        {
            // If the condition is TRUE, this code runs.
            llSay(0, "Welcome back, Owner! It's great to see you.");
        }
        else
        {
            // If the condition is FALSE, this code runs instead.
            // We can get the name of the toucher using llDetectedName(0).
            string name = llDetectedName(0);
            llSay(0, "Hello, " + name + "! Welcome to Alife Virtual.");
        }
    }
}

Save the script. Now, when you touch the object, you'll get the owner message. Ask another avatar to touch it, and they will receive the public welcome message. You've just created your first intelligent object!

3. Lesson 2: Bringing Objects to Life - Timers and Loops

Automating Actions with Timers

What if you want an object to do something on its own, without being touched? For this, we use the timer() event. You can turn a timer on and set its interval using the llSetTimerEvent(float seconds) function. Once set, the timer() event will fire repeatedly every specified number of seconds.

To stop it, you simply call llSetTimerEvent(0).

Rapid-Fire Actions with Loops

Sometimes you need to repeat an action many times in quick succession. While a timer is great for paced events, a loop is for immediate, blocking repetition. LSL has two main types:

  • for loop: Used when you know exactly how many times you want to repeat an action. "Do this 10 times."
  • while loop: Used when you want to repeat an action as long as a condition is true. "Keep doing this while the light is green."
Warning: Long-running loops can cause severe lag and may be terminated by the server's script time limits. A for loop that iterates thousands of times or a while loop that never ends will freeze your script. For ongoing, repeated actions, a timer() event is almost always the better and safer choice.

Code Example 1: A Blinking Light

This script uses a timer and a conditional to make a prim blink on and off.

// LSL-201: Lesson 2 - Blinking Light

// A global variable to keep track of the light's state.
// We use an integer because LSL doesn't have a true boolean type.
// 1 = TRUE (On), 0 = FALSE (Off)
integer gIsOn = FALSE; // Global variables are often prefixed with 'g'

default
{
    state_entry()
    {
        // Set the prim to full bright so we can see the color change
        llSetPrimitiveParams([PRIM_FULLBRIGHT, ALL_SIDES, TRUE]);
        
        // Start the timer. The timer() event will now run every 1.5 seconds.
        llSetTimerEvent(1.5);
    }

    timer()
    {
        // Check if the light is currently on
        if (gIsOn == TRUE)
        {
            // If it's on, turn it off.
            llSetColor(<0.2, 0.2, 0.2>, ALL_SIDES); // Dark grey
            gIsOn = FALSE; // Update the state
        }
        else
        {
            // If it's off, turn it on.
            llSetColor(<1.0, 1.0, 0.0>, ALL_SIDES); // Bright yellow
            gIsOn = TRUE; // Update the state
        }
    }
    
    // It's good practice to clean up when the prim is removed
    on_rez(integer start_param)
    {
        // Reset the script to its initial state when rezzed
        llResetScript();
    }
}

Code Example 2: A Countdown with a for Loop

This script uses a for loop to do a quick countdown when touched.

// LSL-201: Lesson 2 - For Loop Countdown

default
{
    touch_start(integer total_number)
    {
        llSay(0, "Initiating countdown!");

        // A 'for' loop has three parts:
        // 1. Initialization: integer i = 5
        // 2. Condition: i > 0
        // 3. Increment/Decrement: --i (subtract 1 from i)
        // This loop will run as long as 'i' is greater than 0.
        integer i;
        for (i = 5; i > 0; --i)
        {
            // Say the current value of i
            llSay(0, (string)i + "...");
            
            // llSleep pauses the SCRIPT, not the whole world.
            // Use with caution, as it can contribute to lag.
            // For pauses longer than a second, a timer is better.
            llSleep(1.0);
        }
        
        llSay(0, "Lift off!");
    }
}

4. Lesson 3: The Power of Organization - Introduction to State Machines

Beyond a Single State

As your scripts grow, putting all your logic in the default state becomes messy and hard to manage. Imagine a door. It can be open, closed, or locked. Each of these is a distinct state with its own rules. When the door is locked, touching it should do nothing. When it's closed and unlocked, touching it should open it.

LSL allows you to define multiple states to organize your code. This is called a state machine, and it's one of LSL's most powerful features for creating complex, bug-free objects.

State Syntax

You define a new state just like the default state. To switch between states, you simply write state NewStateName;.

default
{
    touch_start(integer num)
    {
        llSay(0, "Entering the blue state.");
        state blue; // This is the command to change state
    }
}

state blue
{
    // This event runs automatically when the script ENTERS this state
    state_entry()
    {
        llSetColor(<0,0,1>, ALL_SIDES); // Blue
        llSay(0, "I am now blue. Touch me to go back to default.");
    }

    touch_start(integer num)
    {
        llSay(0, "Returning to default state.");
        state default; // Change back to the default state
    }
    
    // This event runs automatically when the script EXITS this state
    state_exit()
    {
        // Good for cleanup before changing state
        llSetColor(<1,1,1>, ALL_SIDES); // White
    }
}

Real-World Example: A Simple Security Orb

This orb has two states: unarmed and armed. Only the owner can arm or disarm it. When armed, it will detect avatars within a 10m range and send a warning.

// LSL-201: Lesson 3 - Multi-State Security Orb

// === GLOBAL VARIABLES ===
key gOwner; // Store the owner's key

// =======================
// ===== UNARMED STATE =====
// =======================
default // We'll use the 'default' state as our 'unarmed' state
{
    state_entry()
    {
        gOwner = llGetOwner(); // Get the owner's key on startup
        llSetColor(<0, 1, 0>, ALL_SIDES); // Green for unarmed
        llSetText("UNARMED\n(Touch to arm)", <1,1,1>, 1.0);
        
        // Make sure sensor and timer are off
        llSensorRemove();
        llSetTimerEvent(0.0);
    }

    touch_start(integer total_number)
    {
        // Check if the toucher is the owner
        if (llDetectedKey(0) == gOwner)
        {
            // If it is the owner, switch to the 'armed' state
            state armed;
        }
        else
        {
            llInstantMessage(llDetectedKey(0), "Access denied. Only the owner can arm this device.");
        }
    }
}


// =======================
// ====== ARMED STATE ======
// =======================
state armed
{
    state_entry()
    {
        llSay(0, "Security system ARMED.");
        llSetColor(<1, 0, 0>, ALL_SIDES); // Red for armed
        llSetText("ARMED\n(Touch to disarm)", <1,1,1>, 1.0);
        
        // Start a sensor that scans for avatars every 3 seconds
        // within a 10 meter radius.
        llSensorRepeat("", "", AGENT, 10.0, PI, 3.0);
    }

    touch_start(integer total_number)
    {
        // Only the owner can disarm the system
        if (llDetectedKey(0) == gOwner)
        {
            state default; // Go back to the default (unarmed) state
        }
    }

    // This event fires when the sensor finds something
    sensor(integer num_detected)
    {
        llSay(0, "ALERT! Intruder detected!");
        
        // We can get the name of the first detected avatar
        string intruderName = llDetectedName(0);
        llOwnerSay("Intruder '" + intruderName + "' detected near security orb.");
    }
    
    no_sensor()
    {
        // This event fires if the sensor scan finds nothing.
        // Good for letting you know the system is still working.
        llOwnerSay("All clear. System remains armed.");
    }

    state_exit()
    {
        // This runs when we leave the 'armed' state.
        // It's crucial to clean up here!
        llSay(0, "Security system DISARMED.");
        llSensorRemove(); // Turn off the sensor to save resources
    }
}

5. Lesson 4: Listening and Responding - The Chat Command System

Creating a Command Interface

Touching an object is great for simple interactions, but what if you want to give it more complex instructions? For that, we can teach our objects to listen to chat commands. This is done with two key components: the llListen() function and the listen() event.

llListen(integer channel, string name, key id, string msg) tells the script to start listening. The listen() event fires whenever a message matching the filter is "heard."

A common practice is to listen on a specific channel (e.g., channel 5) so the object doesn't have to process all public chat. Avatars can then issue commands by typing /5 command.

Combining Concepts: A Command-Driven Rezzer

This script will create an object that can rez different shapes based on chat commands. It combines everything we've learned: states, conditionals, and now, listening.

// LSL-201: Lesson 4 - Command Rezzer

// The channel our rezzer will listen on.
integer gChannel = 5;

default
{
    state_entry()
    {
        llSetText("Ready for commands on channel " + (string)gChannel, <1,1,0>, 1.0);
        
        // Start listening for commands from the owner ONLY on our specific channel.
        // llListen(channel, name, id, msg)
        // An empty string for name/msg means "listen for any name/message".
        llListen(gChannel, "", llGetOwner(), "");
    }

    // The listen event!
    // It receives the channel, name, id, and message that was heard.
    listen(integer channel, string name, key id, string message)
    {
        // It's good practice to trim whitespace and convert to lowercase
        // to make command matching easier and less error-prone.
        string command = llToLower(llStringTrim(message, STRING_TRIM));

        llOwnerSay("Received command: '" + command + "'");

        // --- Use conditionals to parse the command ---

        if (command == "rez cube")
        {
            // Rez a cube 1 meter in front of the object.
            // llRezObject(inventory_name, position, velocity, rotation, start_param)
            llRezObject("Cube", llGetPos() + <1, 0, 0>, ZERO_VECTOR, ZERO_ROTATION, 0);
            llSay(0, "Cube rezzed.");
        }
        else if (command == "rez sphere")
        {
            // Rez a sphere 1 meter in front of the object.
            llRezObject("Sphere", llGetPos() + <1, 0, 0>, ZERO_VECTOR, ZERO_ROTATION, 0);
            llSay(0, "Sphere rezzed.");
        }
        else if (command == "derez")
        {
            // A more complex command. We'll need a different lesson for this,
            // but we can acknowledge the command.
            llSay(0, "De-rez functionality not yet implemented. Use 'Clean Up' from the build menu.");
        }
        else if (command == "help")
        {
            llOwnerSay("Available commands: 'rez cube', 'rez sphere'");
        }
        else
        {
            llOwnerSay("Unknown command: '" + command + "'. Type 'help' for a list of commands.");
        }
    }
    
    on_rez(integer start_param)
    {
        llResetScript();
    }
}
To make this script work: You must create a Cube and a Sphere prim, take them into your inventory, and then drag them from your inventory into the rezzer prim's inventory. The names must match the strings in llRezObject() exactly ("Cube", "Sphere").

6. Hands-On LSL Exercises

Exercise 1: The Moody Prim

Write a script for a prim that says "Ouch! You touched me!" when touched. If it's not touched for 10 seconds, it should say "I'm feeling lonely..." in owner-say chat.
Expected Behavior: Responds to touches and uses a timer to detect a lack of touches.
Hint: You'll need touch_start() and timer(). In the touch_start event, reset the timer using llSetTimerEvent(10.0).

Exercise 2: The Bridge Builder

Write a script that, when touched, rezzes a "bridge" of 5 prims in a straight line.
Expected Behavior: A for loop runs on touch, calling llRezObject() 5 times with an increasing position vector.
Hint: Use a for loop from i = 1 to 5. Inside the loop, calculate the position using llGetPos() + <i, 0, 0>. You'll need a prim named "Bridge Section" in the object's inventory.

Exercise 3: The Traffic Light

Create