Class 8 of 8

Practical Project

Build a complete, working in-world script using everything you've learned.

Step 1 of 8
Step 1

Project Overview

This is your final class. Instead of introducing new concepts, you'll apply everything from Classes 1–7 to build a complete, working script that you can drop into Second Life or OpenSim right now.

You have three project options. Choose the one that interests you most — or build all three!

Project options
Project A — Region Greeter
An object that detects nearby avatars and greets them by name. Owner can set a custom greeting via chat command.
Project B — Door Script
A door that toggles open/closed on touch, plays a sound, and can be locked by the owner. Uses position offset and state variables.
Project C — HUD Counter
A heads-up display that counts button presses and lets the owner reset the count with a chat command.

We'll walk through all three over the following steps. Each project is self-contained — you can skip to the one you want.

Step 2

Planning Before You Script

Before writing a single line, experienced scripters sketch a plan. This saves time and avoids rewrites. Ask yourself three questions:

  1. What should this object do? — Write it in plain English first
  2. What events will trigger those actions? — touch? timer? listen?
  3. What data does the script need to remember? — Variables and lists

Example plan for the door script:

Door plan

Does: toggle between open and closed position on touch. Owner can lock it.
Events: state_entry (save start pos), touch_start (toggle), listen (lock/unlock command)
Variables: isOpen (integer), isLocked (integer), closedPos (vector)

With the plan written, the code almost writes itself — you're just translating each bullet point into LSL syntax.

Step 3

Project A — Greeter Core

The greeter uses llSensorRepeat to scan for nearby avatars every 30 seconds. When it finds someone, it says hello to each one by name.

LSLProject A — Greeter (Part 1: sensor core)
string greeting = "Welcome to Blackthorn!";

default
{
    state_entry()
    {
        // Scan 10m radius for avatars every 30 seconds
        llSensorRepeat("", NULL_KEY, AGENT, 10.0, PI, 30.0);
        llOwnerSay("Greeter active. Scanning every 30 seconds.");
    }

    sensor(integer num_detected)
    {
        integer i;
        for (i = 0; i < num_detected; i++)
        {
            string avName = llDetectedName(i);
            llSay(0, greeting + " " + avName + "!");
        }
    }

    no_sensor()
    {
        // Nobody nearby — script just waits quietly
    }
}

AGENT is a constant that tells the sensor to look for avatars only (not objects). PI is the full forward arc — it scans all directions.

Step 4

Project A — Custom Greeting Command

Extend the greeter so the owner can change the greeting text with a chat command: /99 setgreeting Hello from the temple!

LSLProject A — Complete Greeter
string greeting = "Welcome to Blackthorn!";

default
{
    state_entry()
    {
        llSensorRepeat("", NULL_KEY, AGENT, 10.0, PI, 30.0);
        llListen(99, "", llGetOwner(), "");
        llOwnerSay("Greeter ready. Use /99 setgreeting  to change the message.");
    }

    sensor(integer num_detected)
    {
        integer i;
        for (i = 0; i < num_detected; i++)
        {
            llSay(0, greeting + " " + llDetectedName(i) + "!");
        }
    }

    no_sensor() { }

    listen(integer channel, string name, key id, string message)
    {
        list   parts   = llParseString2List(message, [" "], []);
        string command = llToLower(llList2String(parts, 0));

        if (command == "setgreeting" && llGetListLength(parts) >= 2)
        {
            // Rebuild the rest of the parts into one string
            // by removing the first word from the original message
            integer cmdLen = llStringLength(llList2String(parts, 0)) + 1;
            greeting = llGetSubString(message, cmdLen, -1);
            llOwnerSay("Greeting updated to: " + greeting);
        }
    }
}
Step 5

Project B — Door Core

The door script remembers its closed position, then on each touch it either moves up (open) or back down (closed). The key is llGetLocalPos to capture the starting position.

LSLProject B — Door toggle (core)
integer isOpen   = FALSE;
vector  closedPos;
vector  openOffset = <0.0, 0.0, 2.5>;   // Door slides up 2.5m when open

default
{
    state_entry()
    {
        closedPos = llGetLocalPos();   // Remember starting position
        llOwnerSay("Door ready. Touch to toggle.");
    }

    touch_start(integer num_detected)
    {
        if (!isOpen)
        {
            // Open: move up
            llSetLocalPos(closedPos + openOffset);
            isOpen = TRUE;
            llOwnerSay("Door opened.");
        }
        else
        {
            // Close: return to original position
            llSetLocalPos(closedPos);
            isOpen = FALSE;
            llOwnerSay("Door closed.");
        }
    }
}
Tip

llSetLocalPos moves the object relative to its parent linkset. llSetPos moves it in world coordinates. For a door that's part of a building, use llSetLocalPos.

Step 6

Project B — Complete Door with Lock & Sound

The complete door adds an owner-only lock command and a sound effect. Replace the UUID strings with actual sounds from your inventory.

LSLProject B — Complete Door Script
integer isOpen     = FALSE;
integer isLocked   = FALSE;
vector  closedPos;
vector  openOffset = <0.0, 0.0, 2.5>;

// Replace these with UUIDs of sounds in your inventory
string soundOpen  = "replace-with-open-sound-uuid";
string soundClose = "replace-with-close-sound-uuid";

default
{
    state_entry()
    {
        closedPos = llGetLocalPos();
        llListen(99, "", llGetOwner(), "");
        llOwnerSay("Door ready. /99 lock or /99 unlock to control access.");
    }

    touch_start(integer num_detected)
    {
        key toucher = llDetectedKey(0);

        if (isLocked && toucher != llGetOwner())
        {
            llSay(0, "This door is locked.");
            return;
        }

        if (!isOpen)
        {
            llSetLocalPos(closedPos + openOffset);
            llPlaySound(soundOpen, 1.0);
            isOpen = TRUE;
        }
        else
        {
            llSetLocalPos(closedPos);
            llPlaySound(soundClose, 1.0);
            isOpen = FALSE;
        }
    }

    listen(integer channel, string name, key id, string message)
    {
        string cmd = llStringTrim(llToLower(message), STRING_TRIM);

        if (cmd == "lock")
        {
            isLocked = TRUE;
            llOwnerSay("Door locked. Only you can open it.");
        }
        else if (cmd == "unlock")
        {
            isLocked = FALSE;
            llOwnerSay("Door unlocked.");
        }
    }
}
Step 7

Project C — HUD Counter

A HUD is a script inside an object attached to the screen overlay (not worn on the body). This script counts how many times you touch it and lets you reset the count with /99 reset.

LSLProject C — Complete HUD Counter
integer pressCount = 0;
integer listenHandle;

default
{
    state_entry()
    {
        listenHandle = llListen(99, "", llGetOwner(), "");
        llOwnerSay("HUD Counter ready. Touch to count. /99 reset to clear.");
        llOwnerSay("Count: " + (string)pressCount);
    }

    touch_start(integer num_detected)
    {
        pressCount++;
        llOwnerSay("Count: " + (string)pressCount);
    }

    listen(integer channel, string name, key id, string message)
    {
        string cmd = llStringTrim(llToLower(message), STRING_TRIM);

        if (cmd == "reset")
        {
            pressCount = 0;
            llOwnerSay("Counter reset. Count: 0");
        }
    }

    // Reset the script cleanly when rezzed or re-attached
    on_rez(integer start_param)
    {
        llResetScript();
    }

    attach(key id)
    {
        if (id != NULL_KEY)
        {
            // Just attached — announce we're running
            llOwnerSay("HUD Counter attached and ready.");
        }
    }
}

To attach the object as a HUD: right-click it in your inventory and select Attach HUD → Center (or any HUD position). The attach event fires when this happens.

Step 8

What's Next

Congratulations — you've completed the Blackthorn University LSL Scripting course. You've covered:

What you've learned
The event-driven model
How LSL sleeps and wakes on events
All six data types
integer, float, string, vector, rotation, key
Core events
touch_start, listen, timer, collision, sensor
Conditionals & loops
if/else, while, for, do-while
Lists & strings
Storing data and parsing chat commands
Complete scripts
Greeter, door, and HUD from scratch

Where to go from here:

  • LSL Wikiwiki.secondlife.com/wiki/LSL_Portal — the complete function reference
  • States — explore using multiple states to build finite state machines (doors with open/locked/broken states)
  • HTTP requestsllHTTPRequest lets your script call external web APIs
  • Link messagesllMessageLinked lets scripts in multi-prim objects communicate
  • Experiences — the Experience system allows advanced avatar interactions without permission dialogs

You now have the foundation to learn any of those. Keep building — the best way to improve is to write more scripts.