Configuring LSL Scripts Using a Notecard
Introduction
When building projects in Second Life, eventually you will find that some aspect of the system you design needs to be configurable by the user. Perhaps you need to limit access to a list of users, set a starting volume for a sound, or set a communication channel for multiple prims to talk to each other (without overlapping with someone else who uses your script on an adjacent parcel). In all these cases you can use a notecard-based system to configure your script.
In this article I’ll describe one approach to notecard-based configuration, using real-world example code. The material covered will require a basic understanding of LSL scripting.
Script Review
The first step when implementing a configuration notecard is to look at your existing code and try to identify values that need to be user configurable. Look for both variables and hard-coded values.
Let’s use the following script from a basic waterfall as an example:
-
default
-
{
-
{
-
}
-
}
When looking at this script we can see two potential values that could be user-configurable. The first value is the volume of the “water_running” sound: not all users are going to want the same volume depending on the size of their plot and their desire for the sound to travel. The second value that is a candidate for configuration is the rate of the waterfall animation: some users may want a faster or slower-running waterfall.
Creating the Notecard
We want it to be very easy for our users to understand the configuration notecard and make changes to it. To help make our notecards easy to understand, we support the use of comments. We keep things easy to edit by using a simple syntax of name = value.
Create a new notecard and rename it Configuration, then place the following in the notecard and place the notecard in the prim’s inventory:
# THIS IS THE CONFIGURATION CARD. YOU CAN COMMENT OUT LINES WITH THE # SYMBOL. # BLANK LINES ARE ALLOWED, AND SPACES ARE ALLOWED BETWEEN THE NAME, EQUAL SIGN AND VALUE# THE VOLUME OF THE WATERFALL, VALUE NEEDS TO BE BETWEEN 0 AND 1, WITH 1 BEING FULL VOLUME # AND 0.5 BEING HALF VOLUME. THE HIGHER THE VOLUME, THE FURTHER AWAY YOU CAN BE AND STILL # HEAR THE WATERFALL. volume = 0.5 # THE SPEED OF THE WATERFALL ANIMATION, VALUE INDICATES CYCLES PER SECOND. # THE DEFAULT SHOULD BE FINE FOR MOST, CHANGE IN SMALL INCREMENTS IF YOU WANT # A FASTER OR SLOWER WATERFALL. speed = 0.05
The heavy use of comments keeps the notecard easy to understand and easy to read. I personally use all-caps for the comments to make it easy to tell the difference between comments and values. Using spaces between the name, equal sign and value helps the user read the configuration.
Parsing the Notecard
Now that we’ve identified our configurable values and created our notecard, we need to modify our code to use the notecard during script initialization.
The first thing we need to do is break our script into two states: one for initialization and one for operation. I personally use the default state for initialization and then move to a second state for the regular running of my script.
We also add global variables to our script for all the user-configurable values and to identify our configuration notecard.
Here’s an example of the previous script with separate states and global variables:
Note that we initialize the variables with default values just in case the configuration notecard is either missing or has no setting for a given value. We still need to fill in the default state, which in turn will call the Running state.
First we need to add two additional global variables for keeping track of which line we are reading in the notecard, and to read the notecard:
Next, we use the state_entry() event to inform the user that we are initializing the script, check whether the configuration notecard is present and read the first line of the notecard:
The llGetNotecardLine() function is going to request the first line of the notecard specified and return it as a dataserver() event, identified by the QueryID that we captured. The next block of code we need will capture the event and process the notecard line:
First thing we do is check whether the QueryID matched with the one we captured when issuing llGetNotecardLine(), if it does not match we ignore the data since it likely relates to a different function call that triggered the dataserver() event (this should not happen since the llGetNotecardLine() function is the only thing triggering a dataserver event in this state, but checking the QueryID is a best practice).
Next we check whether we got an EOF instead of an actual notecard line. If we did, we know that we’ve run out of lines and we may as well move into the running state.
Finally, we add a state_exit() event to signal to the user that processing is complete.
Now we add the code that actually does the work of processing the notecard lines:
First we add some variables to our event code to store the values while we work on them. Next, we check whether the notecard line starts with a “#” character, denoting the line as a comment that does not need parsing, or is blank. In either case we skip the remaining parsing code.
Now that we know we are working with a line that is neither blank nor a comment, we split the line into two parts with the llParseString2List() function, using the equals sign as a delimiter. Next we pull the name and value from the list using llList2String() and trim them using llStringTrim(). We are then left with a name and a value.
We walk through the possible names using a series of “if”/”else if” statements, checking values and making assignments. Since all values are strings, we may need to cast them to the appropriate type for our global variables.
Once we’ve parsed the line (or skipped parsing), we increment the NotecardLine variable to work on the next line and call llGetNotecardLine() to start the process over again, repeating until we reach the end of the notecard and encounter an EOF.
Handling Notecard Changes
There are three ways to handle a user making changes to the notecard:
- Instruct the user to open the script and click the Reset button at the bottom of the editor.
- Decide on a trigger (such as a dialog button) and either go back to the initialization state or call the llResetScript() function in response.
- Catch the changed() event when the user saves their changes and either go back to the initialization state or call the llResetScript() function in response.
The first option is far too inconvenient for our users. The second option is a common use case, so we’ll look at the third option.
Since our script resides in the Running state during operation, we’ll catch the changed() event there:
-
state Running
-
{
-
{
-
}
-
-
{
-
{
-
state default;
-
}
-
}
-
}
Because the changed event is fired by user edits to the notecard, our script automatically re-reads their configuration changes and applies them.
Providing Feedback to Users
One last thing you will want to do when reading the configuration notecard is provide feedback to the user so they know their changes were applied. This is best done during the state_exit() event of your initialization state:
-
{
-
}
Conclusion
The basic steps in this article can be used to add user-configurability to your scripts, making it easier for your users to tune settings and make adjustments without having to contact you for adjustments.
If you have any questions please feel free to IM me, Squeebee Wakawaka.
Here is our final script:
-
string CONFIG_CARD = "Configuration";
-
float VOLUME = 0.5;
-
float SPEED = 0.05;
-
-
integer NotecardLine;
-
key QueryID;
-
-
default
-
{
-
{
-
-
{
-
NotecardLine = 0;
-
}
-
{
-
state Running;
-
}
-
}
-
-
{
-
list temp;
-
string name;
-
string value;
-
-
{
-
{
-
{
-
-
{
-
}
-
{
-
}
-
}
-
NotecardLine++;
-
}
-
{
-
state Running;
-
}
-
}
-
}
-
-
{
-
}
-
}
-
-
state Running
-
{
-
{
-
}
-
-
{
-
{
-
state default;
-
}
-
}
-
}
September 25th, 2007 at 1:21 pm
[...] http://www.wakatech.com/articles/lsl-scripting-basics/configuring-lsl-scripts-using-a-notecard/ [...]
September 25th, 2007 at 3:50 pm
Nice tutorial. I like the idea of starting with a skeleton script then filling in section by section explaining as you go. Full marks.
September 26th, 2007 at 9:41 am
I left a comment on the forum already… Great tutorial!
October 21st, 2007 at 9:17 pm
Most useful indeed, i’ve been looking high and low for something as half decent as this
Thanks alot.
November 24th, 2007 at 7:56 am
Great tutorial!! I would made a tutorial with the same scheme.
December 19th, 2007 at 8:27 pm
WoW !!! Thanks Squeebee, great job on the tutorial.
February 3rd, 2008 at 3:09 am
Just what I was looking for. This configuration case will be put at re-use immediately in the first LSL script I’m working on. Saves me a few hours headache and did teach me best practice at the same time. As a former teacher of C in evening school knowing the efforts it takes to make good, well documented, progressive manuals and tutorials. I can only take my hat off to your work. Congrats mate! Well done indeed.
March 10th, 2008 at 3:58 pm
I found I wanted to pass a URL with an ‘=’ in it on a config line, so I rewrote the following code:
if ( llGetSubString(data, 0, 0) != “#” && llStringTrim(data, STRING_TRIM) != “” )
{
temp = llParseString2List(data, ["="], []);
name = llStringTrim(llToLower(llList2String(temp, 0)), STRING_TRIM);
value = llStringTrim(llList2String(temp, 1), STRING_TRIM);
to the following:
integer config_line = TRUE;
if (llStringTrim(data, STRING_TRIM) == “”) config_line = FALSE;
if (llGetSubString(data, 0, 0) == “#”) config_line = FALSE;
integer equals_offset = llSubStringIndex(data, “=”);
if (equals_offset == -1) config_line = FALSE;
if (config_line)
{
string name = llStringTrim(llGetSubString(data, 0, equals_offset – 1), STRING_TRIM);
string value = llStringTrim(llGetSubString(data, equals_offset + 1, -1), STRING_TRIM);
Which means that an ‘=’ can now be included in the value part of a config line.
March 24th, 2008 at 12:24 am
very nice explained and complete tutorial and very nice script too,my question is,how do i make a single script that change the colour for expample writing on chat against a notecard? thank you master
April 7th, 2008 at 5:35 pm
Great Tutorial….If all tutorials were this explicit “LSL” would not be a dirty word.
April 16th, 2008 at 9:35 pm
Wow, I was trying to figure this out on my own, and it just seemed too allusive…fortunately a friend reminded me of this WONDERFUL resource, and a better tutorial for this subject could not have been made. Thank you SO MUCH for providing this…its useful beyond measure.
May 19th, 2008 at 2:22 am
This is great tutorial mate!
Im using one script for weapons i sell in sl, and since they design for RP, they do have different properties. This way i can leave scritp AS IS, and configure it with this script via notecard, which is much more easier than editing LSL directly!
Thanks and keep it up
June 10th, 2008 at 4:09 pm
Looks good. I copied and pasted and it seems to get stuck at reading notecard, it won’t say the values to me.
October 28th, 2008 at 1:20 am
Thanks – worked perfectly for me .. great tutorial
February 18th, 2009 at 4:54 am
This tutorial is so well-paced and clear it was a pleasure to read.
Mysteryn11 of Sunnydale SL (home of Buffy the Vampire Slayer in Second Life)
http://slurl.com/secondlife/Sunnydale%20SL/128/128/2
March 12th, 2009 at 7:38 am
Great tutorial, really really helpful. Thanks a lot.
One question I have is how to use multiple notecards. Let’s say I have an object and the user configures it by dropping a notecard on to it. What happens when they drop another card? As I see it the script will need a mechansim to check which is the latest version of the card and then delete the others. LlRemoveInventory can handle the deletion but how do we go about finding which card to read and which to delete?
Thanks,
Pete
June 28th, 2009 at 4:32 pm
Great tutorial however it never actually gets to state_exit and therefore as Reverend Upshaw states it appears to the user that it gets stuck at initializing and though it completes reading the notecard never informs them that it does so. Simple fix though and kudo’s for the tutorial again.