Jump to content

Best Scripter Tips and Shortcuts


Lexie Linden
 Share

You are about to reply to a thread that has been inactive for 365 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

My number one tip/shortcut is to leverage the already existing contributed work of others. That is, when I start a scripting project I first search the many archives of freely donated and licensed LSL scripts and examples to see what other folks have already done and how they did it. It would probably be a nice resource to create a wiki page in the LSL portal listing all the sites that freely redistribute LSL code examples. For starters, I use:

 

There are many more such repositories. Always be careful to comply with the licensing of these code examples. Most are free to use, modify, distribute, and sell but sometimes there are restrictions like your modifications must also be licensed under an open license or you must credit the original author, etc.

Well, anyway, that is my tip/shortcut. Be lazy!

  • Like 5
Link to comment
Share on other sites

If you're  just beginning to write scripts, you'll have a tendency to focus on syntax -- that is, on commands and the parameters that they expect. That's natural, and it is important.  However, that's the easy stuff and it's not the place to put most of your attention.  It's like expecting to learn German by memorizing a lot of words.  The true challenge is logic, and that's almost independent of the functions and words that you use.  Scripting, like using good language skills, is an exercise in expressing yourself clearly and unambiguously.  So, before you reach for the dictionary -- in our case, the collection of Function pages in the LSL wiki --  the first step is to figure out exactly what you want the script to do.  Often, that means thinking about the challenge in reverse, by asking "What outcome do I want to produce?" and then asking "What steps will the script need to take to get there?" and finally "Where do I start?".

One traditional approach to planning is to create a flow diagram.  If you're a raw beginner, or if you're an advanced scripter with a very complex task ahead, it can be quite helpful to draw out a road map of sorts.  It could look like a maze, perhaps, in which you have a decision to make each time you approach a new intersection ("Should I turn right or left?" "What information will help me decide when I reach this point?").  Beware, though, that most LSL scripts are more complicated than a simple linear maze that you follow from a well-defined starting point and leave at a unique exit.  LSL is structured around states and events, which means that your script spends most of its time being idle, waiting for some change in its environment to trigger a response.  Instead of a flow diagram that emphasizes the path through a maze, then, you might think through the logic of your script by focusing on triggers, regardless of where you might find them along the path.  In a real maze, for example, you might ask yourself a set of questions over and over, with each step you take: "Which compass direction am I facing?" "Did I just hear a bear?" "Have I been here before?" "How long has it been since I started?".  In an LSL script, you'd be constructing listen, sensor, touch, collision, or timer events, among others, to answer those questions, and the script would be prepared to act if any one of these were triggered at any time.  

Regardless of whether you plan your script by thinking linearly about a path through a maze or by planning how the script should respond if you hear a bear, the trick is to anticipate all of the things that might happen as script execution flows from whatever you started with to whatever you want to end up with.  That includes imagining all of the things that might happen if you make a wrong turn or if you reach a dead end or if you hear a tiger instead of a bear.  Computers are quite literal, so your script will give unexpected results if it senses trigger C when you have only told it to expect A or B. 

Once you build a script that ought to work, then, the next step is to look for potholes that you may have created unwittingly. It's easy to spot errors that you make with syntax, because the compiler and script editor will point those out. (Fixing them might be frustrating, of course.  If you've made a spelling mistake or forgotten a semicolon, it might take ages to spot, but at least the system tells you that you goofed.)  If you have made an error in logic, though, the script may work well but give bad results.  The most insidious ones look right at first, but show up when you test the script under extreme conditions.  So, you fill your script with diagnostic statements, using llOwnerSay or an equivalent function, and you monitor the values of specific variables as you take the script through its paces.  You use the output from each of those diagnostic statements to narrow down the places where your own logic was fuzzy, and to tune the script and clear up its logic. 

It can help to think of your script as a collection of subtasks, especially once you narrow down the search for a logical error.  Look at everything that happens within a single scope -- the block of functions that lie entirely within an event or between a pair of { brackets }.  What values are passed into the scope, and what have they become by the time you leave it?  Some of the most frustrating logical errors you will encounter are caused by bad counting.  Elements in lists in LSL are numbered from zero (0, 1, 2, 3, 4, ...), but links in linksets are numbered from one (1, 2, 3, 4, 5, ...), for example.  Or -- a nasty one -- the index of a list element may change if you delete other elements in the list, so if you have several elements to remove, count backwards from the end of the list to the start.  Other common logical errors happen when the value of a variable in one scope is changed by an action in a different scope.  You might store the UUID of an avatar identified in a sensor event, for example, but then overwrite that UUID in a timer event.  Tracking down these and other logical errors can take time.  Isolate them as well as you can by focusing on the scope where they occur, and then follow the values of specific variables within that scope -- not the values that you expect, but the values that the script actually generates.

One final note, not necessarily a logical one:  Neatness counts.  It's much easier to visualize your script's logical structure if you have been careful to indent each scope, for example.  It's easier to read a script if you make liberal use of white space.  Use variable names that have meaning for you ("AvName," for example, instead of ""X".)  Take every opportunity to minimize ambiguity about what you meant the script to do. Add parentheses and brackets even if they aren't strictly necessary, for example, so that it's perfectly clear how concepts are bundled together.  When you come back to a script six months later, you'll find it much easier to remember what each variable means and what each if or while is meant to do if you have taken time to add comment statements. Every scripter's style is personal and idiosyncratic, but each needs to be self consistent.

All of this attention to logic will pay off.  You can always look up the details of syntax in the LSL wiki if you need them, just as you can always refer to a dictionary if you don't remember the German word for "elbow".  Those are the mechanical details of scripting.  The thought, the flow of action, is in the logic that you build the script around.  You won't find that in a wiki or a dictionary.  You learn by studying well-scripted examples, by thinking, and by practicing.  And that's where the fun and satisfaction are.

  • Like 9
  • Thanks 1
Link to comment
Share on other sites

What Rolig said is absolutely true, no matter how you script (and there's lots of ways to go about it) you'll find it really helps to save precious time if you can tell at a glance what a variable (for instance) actually represents in terms of its function, or at least include notes/comments as you go. You're definitely going to be looking at the innards of your scripts again someday, and being able to read what's there saves a LOT of otherwise wasted time.

I also agree with getting into the habit of considering more than just the linear flow of what you're trying to get a script to do; anticipating as many possible outcomes or variants on what you're actually telling the script to do can give you a better sense of how to structure your scripts to avoid errors. LSL might all be in neat little rows visually down the text file, but those rows are full of wiggles, if you notice.

Learning the basics of structure and syntax are certainly important from the beginning, and you can read about them and know them fairly quickly, but the best advice I can give on a shortcut to intuitively "getting" LSL is to get ahold of as many examples as you can find, pick some that seem interesting, and get your hands dirty. Make it fun and it'll get easier the more you do it.

Edited by Berksey
Link to comment
Share on other sites

A couple of quick tips I find really useful that folks may not have come across before.

First, an answer to the sometimes puzzling question, in an attachment's on_rez event, how do I know if this is firing because the agent has just worn the attachment from inventory or if the agent was already wearing the attachment andhas just logged in?

The answer, which I discovered completely by accident in the Wiki, is to use llGetAgentSize.   While the agent is still loading, llGetAgentSize reports the default height for the avatar, which is 1.9 metres.   Since you're very rarely going to encounter someone who is exactly 1.90 metres tall, 

	on_rez(integer start_param)	{
		vector v = llGetAgentSize(llGetOwner());
		if(v.z == 1.9){
			//my owner has almost certainly just logged in rather than attached me from inventory
		}
	}

should work.

Another one, which I had wondered about for ages, is, if I know an object's uuid, how can I check that it's still there on the region?   You can use

 if (llGetAgentSize(id) != ZERO_VECTOR)

for avatars but what about objects?

Eventually I asked over the road at SLU and was given two different methods.

		if(llGetObjectDetails(id, [OBJECT_POS])!=[]){//doesn't have to be OBJECT_POS -- it can be just about any flag
			//it's on the region
		}
		//or, if the object isn't an attachment, then 
		if(llGetObjectPrimCount(id)){
			//it's there
		}

I've found both of these very useful, and I hope others do, too.

  • Like 4
Link to comment
Share on other sites

Sooner or later, every LSL scripter runs into a challenge that requires more than one timer to run simultaneously.  You're only allowed to have one timer event in a state, though, so what can you do?  There are several possibilties.  Here are three:

Multiplexing:

Supplement a fast timer with one or more global flags that keep track of things that happen over longer periods of time. For example, suppose you use llSetTimerEvent(1.0) to trigger a timer that "ticks" once a second and that you have a global integer called giTick.  You could write:
 

timer()

{

    ++giTick;

    llTriggerSound("Beep",0.3);

    if (giTick == 60)

    {

        llTriggerSound("BEEP!",1.0);

    }
}

that makes a soft "Beep" sound once a second and a much louder :"BEEP!" sound once a minute.  Multiplexing can get as complicated as you like.  You can monitor several flags or triggering conditions at once.  Just remember to make the basic interval for the timer event be the shortest one. Here's a more complex multiplexed timer, again assuming that you have llSetTimerEvent(1.0) and that you have a global giTick:
 

timer()

{

    giTick = (++giTick)%60;    // The Modoulo operation is very handy when you want to reset a flag periodically

    llTriggerSound("Beep",0.3);

    if (giTick == 0)

    {

        llTriggerSound("BEEP!",1.0);

    }

    else if (giTick%15 == 0)    // The Modulo operation is also handy for checking things that happen at regular intervals

    {

        llTriggerSound("Ding",0.6);

    }

}

Remember that you can also use llGetTime() to see how much time has passed since a script was reset or a llResetTime() command was issued, and that you can monitor llGetUnixTime() or related functions to see when you pass specific times in RL. Embedding those in a timer event is another sort of multiplexing, comparing one clock with another.

Relayed Timers:

Another possibility is to run a second timer in a separate script and tell it to report with a link message or (if it's in a different linkset) in chat.  This is an attractive possibility when you have completely separate processes happening in an environment but need to keep them in sync -- dance machines come to mind, for example -- or when the clock interval of one timer is not an even multiple of the other:
 

timer()   // Set to llSetTimerEvent(17.3), for example

{

    llMessageLinked(LINK_THIS,0,"TICK!",NULL_KEY);  //Sends a "TICK!" message to your other script every 17.3 seconds

}

So, your main script can use its timer to do whatever it wants, while keeping track of how the timed process in the other script is proceeding:
 

link_message(integer From, integer iCode, string message, key id)

{

    if (message == "TICK!")

    {

        ++ giTick;

        if (giTick == 10)

        {

            llOwnerSay("The clock in other script has been running for 173.0 seconds");

        }

}

Fake Timers:

My own favorite fake timer is llSensorRepeat, which can be used to build a "relayed timer" within your main script instead of having to use an external one.  All you need to do is tell the repeating sensor to look for something that you are absolutely certain does not exist.  Then use the no_sensor event as a fake timer.  For example, create

llSensorRepeat("BillThePirate","",AGENT,0.1,PI,17.3);   //Look for someone named "BillThePirate" who is standing within 0.1m of this spot

and then

no_sensor()

{

    llOwnerSay("The fake timer just ticked after 17.3 seconds.");

}

(My friend Maddy once tried to beat my fake timer by creating an alt named BillThePirate.  As far as I know, however, she never got him to stand 0.1m from my fake timer. o.O )

===========

These are the most popular ways to run multiple timers, but there are others.  These methods open the door to all manner of tricky timing schemes, so you can do as many timed processes at once as you like.

  • Like 5
  • Thanks 1
Link to comment
Share on other sites

On 3/19/2017 at 11:48 AM, Rolig Loon said:

(My friend Maddy once tried to beat my fake timer by creating an alt named BillThePirate.  As far as I know, however, she never got him to stand 0.1m from my fake timer. o.O )

Put the script in a root beer bottle, 0.1m won't stand a chance.

Link to comment
Share on other sites

  • 2 weeks later...

I'm starting to see that maybe a really thorough grasp of the Wiki, though it's extensive, could save a whole lot of trial and error, writing of rubbish scripts, and assorted other poopydoo. Especially in areas such as built-in functions, and the setting of prim properties!

I've gotten into the habit of keeping bookmarks for all of the most useful pages handy, such as the llSetClickAction page, etc. It's actually faster and easier sometimes than looking through folders full of code snippets (depending on which folder I'm in).

It doesn't just save time, it helps me to learn more as I go and even make better choices sometimes, because every possible option is listed on the same page. I've always been an advocate of "Read The Wiki!", but now I'm definitely more like, "USE The Wiki!"

  • Like 2
Link to comment
Share on other sites

  • 4 weeks later...

I generally dislike posting after myself, but it wouldn't let me edit my previous post to include more stuff... I guess it's been too long. Please forgive me bumping the thread like this, but I was moved to say this, and I have poor impulse control. My need to communicate this overrode my dislike for double-posting, so I have to go with it, or the universe might go kerflooey or something and then I won't be able to sleep at night, but I digress already...

I re-read this thread and saw it with new eyes, possibly because I've learned a little more since the last time I read it, and possibly because it takes me a while to get things sometimes... but the cheats for multiple timers are sheer genius, and a project I'm working on would have ended up being way overcomplicated and also probably rather crummy if not for this information being here. Seeing a limitation and saying "no" to it, and doing what you wanted to do anyway and making it work if that's what you really want to do, well, that's the kinda stuff I live for.

Are there more things like this, like for other functions? I run up against obstacles like the timer event limit all the time, and usually I just have to find some other way of doing whatever I'm trying to do, but some workarounds are a lot more work and rather a mess compared to being able to simply use the same function I was using, had I been able to. For instance, I'm trying to figure out how to deal "splash damage" using llsensor() instead of damage prims (it works for melee, because the direction keys can tell it to do different damage for a side strike than a direct hit, but my ranged weapons only use the left click in mouselook), and all I can think of is either using more than one sensor event (one large cone and one smaller cone), or somehow determining how close to the center of the sensor cone the targeted avatar was. The former involves less math (potentially, I would hope) than the latter, but I don't even know if either approach is possible. It mystifies me. And knowing my luck, the day I work out some complicated method, and edit every one of my pile of items, someone will post a snippet that would have saved me having to hack and slash my way through my lack of understanding of LSL and break it enough to make it go. xD I almost thought to call a second sensor event with the no_sensor event, but I don't think that would even work, and if it did, it would probably go round and round loopy and break everything anyway.

As a somewhat new person, in most dire need of the very subject matter of this thread, and from seeing a lot of questions on similar topics (how to do things some scripters might consider logically impossible) by other new people, I know all too well how much there is to learn and I gobble it up in quantity, but the quality of wizarding I've seen from a handful of the experienced people here makes me want more of it... If I hadn't seen the above timer cheats, one of my projects would be llSleep() spaghetti right now. Really grodey, unappetizing, llSleep() spaghetti. Are there more shortcuts like these, but for other stuff?

In appreciation for what I've gotten so far I'm hereby vowing to pay it forward in due course, and share anything I might ever find useful in saving time learning the logic of all of this. It's true, you can look up functions, but understanding why it all works the way it does and doesn't work the way it does not is, in my humble opinion, the key to higher-level wizarding.

Watch, next time I read this thread I'll be all like, "hey, NOW I get it!" again, too... <-<;

Edited by Berksey
Added an example...
  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

My advice is until you have the complete grasp and flow of how LSL works do not re-use code you have written before, rewrite it entirely. Build your rote memory skills until it comes naturally. Also if you learn how to fix a script, you'll learn how it works so break things and get messy but keep writing

  • Like 1
Link to comment
Share on other sites

I fully agree with the above. The only time I re-use anything I've already made is when I combine two versions, and even then I sometimes just pull them apart and merge one with the other, choosing the one that is more complex to add the simpler version's functions to it. When it's a hundred or more lines of code, it's sometimes just a pain to re-type what each file already has declared and so forth. Doesn't mean I won't read them both thoroughly first! And yeah, often it's just better to think it through while looking at the code and then start fresh. Sometimes I wake up and just start typing, after sleeping on it all night; this is often my best work, done while still fresh in my mind and I'm not awake enough to remember I had anything already typed up inworld.

And as far as breaking things is concerned, it is of infinite value compared to simply throwing a script in and having it work right off the bat. It's rarely that I'm able to write a script out and have it compile on the first go round, and the result is all my inworld scripts are working and all the stuff on my HDD is messy and broken or unfinished... But it helps me learn. By the way, throwing together two versions of the same script is often an excellent way to give oneself something broken to fix and learn from. xD

I think maybe one of the most valuable traits a scripter can possess is a refusal to settle for a script just working, too. There's always room for improvement, and if you're sitting on your laurels, you've got them on the wrong end of you, I believe the saying goes. xD

Edited by Berksey
Link to comment
Share on other sites

1 hour ago, Gayngel said:

My advice is until you have the complete grasp and flow of how LSL works do not re-use code you have written before, rewrite it entirely. Build your rote memory skills until it comes naturally. Also if you learn how to fix a script, you'll learn how it works so break things and get messy but keep writing

Yes, that's one of the life lessons you should have learned by the time you get out of your teens: "If you're not failing very often, you aren't trying hard enough."  ;)

  • Haha 1
Link to comment
Share on other sites

Best scripter tip? That has to be one by the Danish mathematician Piet Hein (1905-1996). Translated to English:

Quote

Put up in a place
where it's easy to see
the cryptic admonishment

T.T.T.

When you feel how depressingly
slowly you climb,
it's well to remember that
Things Take Time.

Writing a script usually takes between two and ten times as long as we think and if we remember this even before we write the first line of code, we save ourselves a lot of frustration

Edited by ChinRey
typo
  • Like 3
Link to comment
Share on other sites

That's quite similar to Dag Hammarskjöld's advice: "Mått aldrig bergets höjd förrän du nått toppen.  Då skal du se hur lågt det var." (Never measure the height of the mountain before you reach the top.  Then you will see how low it was.) 

  • Like 2
  • Haha 2
Link to comment
Share on other sites

39 minutes ago, ChinRey said:

Best scripter tip? That has to be one by the Danish mathematician Piet Hein (1905-1996). Translated to English:

Writing a script usually takes between two and ten times as long as we think and if we remember this even before we write the first line of code, we save ourselves a lot of frutsration

Worded another way...

The first 90% of a script takes 90% of the time to write.

The remaining 10% takes the other 90%.

  • Like 5
  • Haha 2
Link to comment
Share on other sites

On 17.5.2017 at 1:37 AM, Gayngel said:

My advice is until you have the complete grasp and flow of how LSL works do not re-use code you have written before, rewrite it entirely.

Not to mention re-using code by others.

The first reply in this thread recommends the many free script libraries on the internet and I think that is a very bad idea. This is mostly because the scripts you find there tend to be way outdated and not all of them were very well written in the first place.

But it's also because of one of the most common and dangerous programmers' anti-patterns, thoughtless copy and paste programming. Not copy and paste programming in general, just the thoughtless kind. Thoughtful copy and paste can be a great time saver.

Edited by ChinRey
  • Like 3
Link to comment
Share on other sites

Along with the usual free script repositories, there's another source for scripts to use as examples and to build on or learn from... some of them are from the same sources, but some aren't, and they're free and full perms... the Marketplace. There's quite a few decent sample scripts and freebies, and sometimes it's better than reinventing the wheel or the flickering lightbulb or whatever to modify one of these...

True, writing all of our own stuff is best,but most beginners wouldn't know where to start really without being able to see someone else's code first. If all I'd done was read about functions, I'd never have gotten anywhere in learning how to put together a script for a specific purpose, but looking at similar work to what I've wanted to make has helped me a great deal.

Anyway yeah, don't forget there's actually some decent working full perm scripts for free on the MP.

Edited by Berksey
  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Here's my take on the "multiple timers" problem.
All you really need is two variables, I think using linked messages is unnecessarily complex -- and resource heavy, just like sensors.

float timer1_start = 5;
float timer1_duration = 5;
float timer2_start = 10;
float timer2_duration = 3;

default
{
    state_entry()
    {
        llSetTimerEvent(0.1);
    }
    timer()
    {
        llSetText((string)llGetTime(), <1,1,1>, 1);
        
        if(llGetTime() >= (timer1_start + timer1_duration)) // first ends in 10s
        {
            llOwnerSay("timer 1 ended!");
            timer1_start = llGetTime(); // restart timer immediately, ends in 5s
        }
        if(llGetTime() >= (timer2_start + timer2_duration)) // first ends in 13s
        {
            llOwnerSay("timer 2 ended!");
            timer2_start = llGetTime(); // restart timer immediately, ends in 3s
        }
    }
}

If you copypaste the script into a box, you'll see the script's internal timer as hovertext, and the chat output will be something like this:
[00:00:10] Obj: timer 1 ended!
[00:00:13] Obj: timer 2 ended!
[00:00:15] Obj: timer 1 ended!
[00:00:16] Obj: timer 2 ended!
[00:00:19] Obj: timer 2 ended!
[00:00:20] Obj: timer 1 ended!
[00:00:22] Obj: timer 2 ended!
[00:00:25] Obj: timer 1 ended!
[00:00:25] Obj: timer 2 ended!
[00:00:28] Obj: timer 2 ended!
[00:00:30] Obj: timer 1 ended!

Edited by Wulfie Reanimator
added output
Link to comment
Share on other sites

Yes indeed.  That's another good example of multiplexing.

On 3/19/2017 at 11:48 AM, Rolig Loon said:

Remember that you can also use llGetTime() to see how much time has passed since a script was reset or a llResetTime() command was issued, and that you can monitor llGetUnixTime() or related functions to see when you pass specific times in RL. Embedding those in a timer event is another sort of multiplexing, comparing one clock with another.

 

Link to comment
Share on other sites

  • 1 month later...

list details = llGetObjectDetails(llGetKey(),[OBJECT_REZZER]);

key rezzer = llList2Key(details,0);

if(rezzer != llGetOwner())//object was rezzed by a script

this is useful when working on something that is only meant to be rezzed for a short time, such as ammo, but you need to disable the timer or collision event so it doesn't die while you're working on it

  • Like 3
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 365 days.

Please take a moment to consider if this thread is worth bumping.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...