Contributors
- Fawresin
Disclaimer: The used coding style and naming conventions are for the reader's clarity and do not reflect the author's opinions.
The Linden Scripting Language (or just LSL), is a somewhat primitive language you are required to mess with if you want stuff to work within Second Life. Sometimes you'll have to get creative to accomplish certain things, so here is where I'll list some tips I've picked up over the years that may be useful to other scripters.
Also feel free to fork this document and add your own!
At some point, you may have come across a situation where you wanted to
prematurely exit a loop, or skip to the next iteration of one. In other
programming languages, this can be accomplished using the break
and continue
statements. Unfortunately LSL does not have these statements, so you have to
resort to other methods:
i) Make the iteration condition be false.
This method is simple, but has the drawback of requiring everything be wrapped
inside of an if/else
statement. It is also harder to deal with inside of
nested loops.
list words = ["one", "two", "three", "four"];
integer total = llGetListLength(words);
integer i;
for (i=0; i<total; ++i)
{
string word = llList2String(words, i);
if (word == "three")
{
// Cause the loop to exit the next time it checks the condition.
i = total;
}
else
{
// Ghetto way to skip the second element.
if (word != "two")
{
// Regular loop code.
}
}
}
ii) Using jump
statements, aka gotos.
This method can be used to break
out of nested loops, and can also be used
for continue
statements. You might've heard that using gotos is bad. While
that is true for the most part, there are times where it can be convenient.
Convenience is a nice thing to have in a language like LSL. To do this, you
place labels at the very end of the loop body, and right after it.
list words = ["one", "two", "three", "four"];
integer total = llGetListLength(words);
integer i;
for (i=0; i<total; ++i)
{
string word = llList2String(words, i);
if (word == "three")
{
// Exit the loop.
jump break;
}
else if (word == "two")
{
// Continue to the next iteration.
jump continue;
}
// Regular loop code.
@continue;
}
@break;
Occasionally you'll need to have separate timers, but LSL only lets you have one.
i) This method involves setting the interval to a low value which covers all of the timer intervals, and a variable for each "timer" that stores the script time at which they should execute.
float INTERVAL_1 = 2.0;
float INTERVAL_2 = 5.0;
float timer_1 = INTERVAL_1;
float timer_2 = INTERVAL_2;
default
{
state_entry()
{
llSetTimerEvent(1.0);
}
timer()
{
float time = llGetTime();
if (time >= timer_1)
{
llOwnerSay((string)time + " - Timer 1");
timer_1 = time + INTERVAL_1;
}
if (time >= timer_2)
{
llOwnerSay((string)time + " - Timer 2");
timer_2 = time + INTERVAL_2;
}
}
}
ii) Another method is to use llSensorRepeat on a sensor that will never detect anything, and use the no_sensor event handler as a secondary timer. This method is the most easy to use, but sacrifices a sensor.
float INTERVAL_1 = 2.0;
float INTERVAL_2 = 5.0;
default
{
state_entry()
{
llSetTimerEvent(INTERVAL_1);
llSensorRepeat("invalid", llGenerateKey(), AGENT, 0.01, 0.01, INTERVAL_2);
}
timer()
{
llOwnerSay("Timer 1");
}
no_sensor()
{
llOwnerSay("Timer 2");
}
}
Maybe you've come across a situation where you need to have separate timers that fire off one after another.
i) This method uses a variable that stores the "ID" of the timer, aka an integer that you increase the number of inside of each timer branch.
float TIMER_1 = 2.0;
float TIMER_2 = 5.0;
float TIMER_3 = 10.0;
integer timerID;
resetTimers()
{
timerID = 0;
llResetTime();
}
// Returns if the current timer ID is ready to go off.
integer isTimer(integer id)
{
float time = llGetTime();
integer is_timer =
timerID == id && (
// Add more timers here.
(time >= TIMER_1 && id == 0) ||
(time >= TIMER_2 && id == 1) ||
(time >= TIMER_3 && id == 2)
);
if (is_timer)
{
// This saves putting it inside of each branch.
++timerID;
}
return is_timer;
}
default
{
state_entry()
{
llSetTimerEvent(1.0);
resetTimers();
}
timer()
{
float time = llGetTime();
if (isTimer(0))
{
llOwnerSay((string)time + " - Timer 1");
}
else if (isTimer(1))
{
llOwnerSay((string)time + " - Timer 2");
}
else if (isTimer(2))
{
llOwnerSay((string)time + " - Timer 3");
resetTimers();
}
}
}
LSL's verbosity can be a little tiresome at times. This is probably common sense, but try to break things up into functions for anything that you find yourself repeating. Duplicate code sucks to read and write. You can also wrap LL's long function names to something simpler and maybe with more conveyance than the original.
Funnily enough, I accidentally came across a reserved print function while writing this.
// Print something to the owner.
println(string message)
{
llOwnerSay(message);
}
// Sets a link's primitive params using the sleepless function.
set_prim_params(integer link, list rules)
{
llSetLinkPrimitiveParamsFast(link, rules);
}
i) This method is more of a trick to alternatively writing for loops. It's a good way to iterate over a list without caring about order (unless it's backwards). It uses a while loop, and a single integer variable that decrements on each iteration. Since anything not zero is equal to TRUE, and zero itself is equal to FALSE, it will count down until the integer variable hits zero. However, you must use postfix decrementation or it will be short one iteration.
integer i = 100;
while (i--)
{
llOwnerSay((string)i);
}