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 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.
Planning Before You Script
Before writing a single line, experienced scripters sketch a plan. This saves time and avoids rewrites. Ask yourself three questions:
- What should this object do? — Write it in plain English first
- What events will trigger those actions? — touch? timer? listen?
- What data does the script need to remember? — Variables and lists
Example plan for the door script:
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.
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.
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.
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!
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);
}
}
}
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.
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.");
}
}
}
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.
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.
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.");
}
}
}
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.
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.
What's Next
Congratulations — you've completed the Blackthorn University LSL Scripting course. You've covered:
- 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 Wiki —
wiki.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 requests —
llHTTPRequestlets your script call external web APIs - Link messages —
llMessageLinkedlets 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.