1. Course Overview
Welcome, future LSL masters! If you've moved beyond the basics of variables, functions, and simple event handlers, you're in the right place. This course, SCR-402, is where you transition from a scripter to a true systems architect in the virtual world.
My name is Sorin Todys, and I'll be your guide through this advanced journey. With over two decades of experience scripting in virtual environments, I've seen it all—from the simplest door script to region-spanning game systems. My goal is to impart the knowledge that separates functional scripts from truly elegant and efficient ones. We'll move beyond simple `if/else` logic and explore the robust structures that power the most impressive creations in Alife Virtual.
Learning Objectives
This course is designed to equip you with the skills to tackle large-scale scripting projects with confidence. We will focus on structure, efficiency, and interconnectivity.
- Understand and implement powerful state machines to manage complex object behaviors.
- Learn critical memory and performance optimization techniques to create lag-free experiences.
- Master inter-object communication using linked messages, allowing you to build sophisticated multi-prim devices.
- Explore how to connect your in-world objects to the wider web using HTTP-In requests.
- Learn to parse data from external web services, specifically focusing on handling JSON data with LSL's string and list functions.
What You Will Master
By the end of this course, you will be able to:
- Design and build complex objects that react intelligently to user interaction and their environment using states.
- Write scripts that are optimized for the Alife Virtual environment, minimizing script time and memory usage.
- Create intricate devices where multiple prims work together seamlessly, such as control panels, complex vehicles, or interactive displays.
- Build systems that can fetch or receive real-time data from external websites, such as news feeds, weather displays, or online leaderboards.
- Confidently debug and troubleshoot advanced scripting issues, including stack-heap collisions and communication errors.
Prerequisites
This is an Advanced course. It is essential that you have a solid foundation in LSL before enrolling. You should be comfortable with:
- All variable types (integer, float, string, key, vector, rotation, list).
- Writing custom functions.
- Standard control flow (if/else, for loops, while loops).
- Common events like
touch_start,timer, andlisten. - Basic prim and script permissions.
Completion of our "LSL Fundamentals (SCR-201)" and "Intermediate LSL Projects (SCR-301)" courses is highly recommended.
2. Lesson 1: Mastering State Machines and Memory Optimization
The single most powerful concept in advanced LSL is the state machine. It allows a single script to behave in dramatically different ways depending on its current 'state', without a messy web of `if/else` statements. We'll pair this with a crucial look at how to keep your scripts lean and fast.
Theory: Why States are Superior
Imagine a vendor. It can be 'idle', 'waiting for payment', 'delivering an item', or 'out of stock'. In a basic script, you might track this with a global integer variable (e.g., `integer gState = 0;`). Your `touch_start` event would then be a giant `if/else if` block:
if (gState == 0) { /* do idle stuff */ } else if (gState == 1) { /* do payment stuff */ } ...
This gets complicated, is hard to read, and is inefficient. LSL provides a much better way: the state keyword. A state is a self-contained block of code. Each state can have its own event handlers. When a script is in a particular state, only the event handlers within that state are active. This is cleaner, more organized, and more efficient for the script engine.
Every script begins in the special default state. You can transition to other states using the command state NewStateName;. When a script enters any state (including `default`), its state_entry() event is triggered. This is the perfect place to perform setup actions for that state.
Theory: Memory and Performance
Every script in Alife Virtual (based on OpenSim) has a limited amount of memory, typically 64KB. When your script uses too many large variables (especially long strings and lists) or has deeply nested function calls, you can get a dreaded "Stack-Heap Collision" error. This crashes your script. Optimizing memory is not optional for complex projects. Key strategies include:
- Use local variables: Variables declared inside a function or event are 'local' and are cleared from memory when the function ends. Global variables persist, consuming memory for the script's entire lifetime.
- Clear lists: If you have a large global list that you're done with, clear it to free up memory:
gMyList = []; - Avoid long strings in global variables: Store them in notecards and read them when needed.
- States help performance: The script engine doesn't have to check a long list of conditions; it just jumps to the active state's event handler.
Step-by-Step: Creating a Two-State Light Switch
Let's build a simple prim that acts as a light switch, toggling between an 'On' and 'Off' state.
- Create a Prim: Rez a simple cube prim in-world at the Alife Virtual School sandbox.
- Create a New Script: Go to the Content tab of the edit window, click "New Script", and name it `State_LightSwitch`.
- Clear and Paste Code: Double-click the script to open it. Delete the default "Hello, Avatar!" code and paste the following:
// Global variable to store the owner's key for security key gOwner; // The default state is our "Off" state default { state_entry() { // On script start or reset, get the owner's key gOwner = llGetOwner(); // Announce our current state llOwnerSay("Light is now OFF. Touch to turn ON."); // Set the prim to a 'dark' color llSetColor(<0.2, 0.2, 0.2>, ALL_SIDES); // Set the prim to not emit light llSetPrimitiveParams([PRIM_FULLBRIGHT, ALL_SIDES, FALSE, PRIM_GLOW, ALL_SIDES, 0.0]); } touch_start(integer total_number) { // Check if the person touching is the owner if (llDetectedKey(0) == gOwner) { // If it's the owner, switch to the "On" state state On; } } } // Our custom "On" state state On { state_entry() { // This runs as soon as we enter the 'On' state llOwnerSay("Light is now ON. Touch to turn OFF."); // Set the prim to a 'bright' color llSetColor(<1.0, 1.0, 0.8>, ALL_SIDES); // A bright yellow // Make the prim full bright and give it a glow llSetPrimitiveParams([PRIM_FULLBRIGHT, ALL_SIDES, TRUE, PRIM_GLOW, ALL_SIDES, 0.5]); } touch_start(integer total_number) { // Check if the person touching is the owner if (llDetectedKey(0) == gOwner) { // If it's the owner, switch back to the "default" (Off) state state default; } } } - Save and Observe: Save the script. You will see the message "Light is now OFF" in local chat, and the prim will turn dark. Touch it. It will turn bright yellow, glow, and you'll see "Light is now ON". Touch it again, and it switches back.
state, default, state_entry(), llSetColor(), llSetPrimitiveParams(), PRIM_FULLBRIGHT, PRIM_GLOW. Notice how clean the code is. The `touch_start` in each state only has one job.
3. Lesson 2: Inter-Object Communication with Link Messages
Complex creations are rarely a single prim. They are linksets—multiple prims linked together into one object. To make them work as a cohesive whole, the scripts in different prims need to talk to each other. This is done with Link Messages.
Advanced Techniques: The Power of `llMessageLinked`
Link messages are a highly efficient, private communication channel within a single linked object. They are much faster and more secure than using llSay and llListen.
The function to send a message is: llMessageLinked(integer linknum, integer num, string str, key id)
linknum: The link number of the prim you want to send the message to. The root prim is always 1. Child prims are 2, 3, 4, etc., in the order they were linked. You can also use special constants:LINK_ROOT: Sends only to the root prim.LINK_SET: Sends to all prims in the linkset (including the sender).LINK_ALL_OTHERS: Sends to all prims except the sender.LINK_ALL_CHILDREN: Sends to all child prims, but not the root.
num: An integer you can send. Often used as a "command" number.str: A string you can send. Can be data, a command name, etc.id: A key you can send. Useful for sending avatar keys or object UUIDs.
To receive a message, you use the link_message() event handler:
link_message(integer sender_num, integer num, string str, key id)
sender_num: The link number of the prim that sent the message.- The other parameters match what was sent by
llMessageLinked.
Practical Example: A Control Panel for our Light
Let's modify our light. We'll make it a 2-prim object: a small "switch" prim and a larger "bulb" prim. The script in the switch will tell the script in the bulb what to do.
- Build the Object:
- Rez a cube. This will be our "bulb". Resize it to be fairly large.
- Rez a second, smaller cube. This will be our "switch".
- Select the bulb first, then hold Shift and select the switch. The last prim selected (the switch) will be the root prim.
- Press Ctrl+L to link them. The entire object will be selected. Name it `Linked Light`.
- Script for the Switch (Root Prim - Link #1):
- Edit the `Linked Light` object. In the edit window, check "Edit Linked Parts".
- Select the small switch prim.
- Go to the Content tab and create a new script named `Switch_Control`. Paste this code:
// A simple toggle variable integer isOn = FALSE; // Define command numbers for clarity integer CMD_TURN_ON = 1; integer CMD_TURN_OFF = 2; default { state_entry() { llOwnerSay("Switch ready. Touch to operate light."); } touch_start(integer total_number) { // Toggle the state isOn = !isOn; if (isOn) { llOwnerSay("Sending ON command."); // Send message to all other prims in the linkset llMessageLinked(LINK_ALL_OTHERS, CMD_TURN_ON, "Turn On", NULL_KEY); } else { llOwnerSay("Sending OFF command."); // Send message to all other prims in the linkset llMessageLinked(LINK_ALL_OTHERS, CMD_TURN_OFF, "Turn Off", NULL_KEY); } } }
- Script for the Bulb (Child Prim - Link #2):
- While still in "Edit Linked Parts" mode, select the large bulb prim.
- Go to the Content tab and create a new script named `Bulb_Receiver`. Paste this code:
// Define command numbers - must match the sender! integer CMD_TURN_ON = 1; integer CMD_TURN_OFF = 2; default { state_entry() { // Start in the OFF state llSetColor(<0.2, 0.2, 0.2>, ALL_SIDES); llSetPrimitiveParams([PRIM_FULLBRIGHT, ALL_SIDES, FALSE, PRIM_GLOW, ALL_SIDES, 0.0]); } link_message(integer sender_num, integer num, string str, key id) { // Check if the message came from the root prim (link #1) if (sender_num == 1) { if (num == CMD_TURN_ON) { // Execute the ON command llSetColor(<1.0, 1.0, 0.8>, ALL_SIDES); llSetPrimitiveParams([PRIM_FULLBRIGHT, ALL_SIDES, TRUE, PRIM_GLOW, ALL_SIDES, 0.5]); } else if (num == CMD_TURN_OFF) { // Execute the OFF command llSetColor(<0.2, 0.2, 0.2>, ALL_SIDES); llSetPrimitiveParams([PRIM_FULLBRIGHT, ALL_SIDES, FALSE, PRIM_GLOW, ALL_SIDES, 0.0]); } } } }
- Save and Test: Save both scripts. Now, when you touch the small switch prim, it sends a command to the large bulb prim, which changes its appearance accordingly. You've created a complex, multi-prim device!
4. Lesson 3: Connecting to the Web: HTTP-In and JSON Parsing
This is where your creations can break the "fourth wall" of the virtual world. By using HTTP requests, your LSL scripts can send and, more importantly, receive data from external websites and APIs. This allows for dynamic, data-driven experiences that are impossible with LSL alone.
Advanced Applications: HTTP-In
While `llHTTPRequest` lets a script *send* data out, HTTP-In lets the outside world send data *in*. The process is:
- Your script calls
llRequestURL(). The LSL server generates a unique, temporary URL and returns it to your script in thehttp_response()event. - Your script then needs to provide this URL to an external web service (e.g., by sending it to your own web server, displaying it to the user, etc.).
- When that external service sends an HTTP POST or GET request to that unique URL, the LSL server forwards the request body and headers to your script, triggering the
http_request()event. - The URL is single-use by default, but you can request a persistent one (though this has security implications).
Real-World Scenario: Parsing JSON Data
Modern web APIs almost exclusively use JSON (JavaScript Object Notation) to format data. It looks like this: {"name":"Sorin", "level":402, "is_active":true}. LSL has no built-in `llParseJSON()` function. However, we can use a clever trick with llParseString2List to extract the data we need.
The trick is to treat the JSON string as a simple string and use characters like ", :, ,, {, and } as delimiters. It's not a perfect parser, but it's very effective for simple, known JSON structures.
Best Practices: A Simple Web-to-Prim Messenger
Let's create a prim that can receive a message from a web page and display it as floating text.
- Create the Prim and Script: Rez a prim, create a new script inside it called `Web_Receiver`, and paste the following code:
// Global variable to store our unique URL string gURL; default { state_entry() { // Request a URL from the server. The response will be handled // in the http_response event. llRequestURL(); llSetText("Requesting URL...", <1,1,1>, 1.0); } // This event receives the URL we requested http_response(key request_id, integer status, list metadata, string body) { if (status == 200) // 200 means OK { // The URL is in the body of the response gURL = body; llOwnerSay("My URL is: " + gURL); llOwnerSay("Go to a web tool (like Postman) and send a POST request to this URL."); llOwnerSay("The body should be simple JSON, like: {\"message\":\"Hello from the web!\"}"); llSetText("URL Ready. Waiting for message...", <1,1,1>, 1.0); } else { llSetText("Error getting URL: " + (string)status, <1,0,0>, 1.0); } } // This event is triggered when someone sends data TO our URL http_request(key request_id, string method, string body) { llOwnerSay("Received HTTP Request! Body: " + body); // --- Simple JSON Parsing --- // Let's parse a body like: {"message":"Your text here"} // 1. Split the string by the quote character list parts = llParseString2List(body, ["\""], []); // llParseString2List result for our example will be: // [ "{", "message", ":", "Your text here", "}" ] // The data we want is at index 3. if (llListGetCount(parts) >= 4) { string message = llList2String(parts, 3); llSetText(message, <1, 0.7, 0>, 1.0); // Display in orange llOwnerSay("Message displayed: " + message); // For a one-time use URL, we could reset here. // For this example, we'll wait for another message. } else { llSetText("Invalid JSON received.", <1,0,0>, 1.0); } } } - Get the URL: Save the script. It will immediately contact the Alife Virtual server and, a moment later, print a unique URL in local chat for you, the owner. Copy this URL.
- Send the Data: For this class, we will use a simple online tool like `reqbin.com` or the Postman application.
- Go to the tool and paste the URL you copied.
- Set the HTTP Method to POST.
- Go to the "Body" or "Content" section. Select "raw" and "JSON".
- In the body text area, type:
{"message":"Hello from the web!"} - Click "Send".
- Observe the Result: Back in Alife Virtual, you'll see your prim's floating text change to "Hello from the web!". Your