Part I – The Beginning

To begin this tutorial I would like to say there are three ways to write your code.  One; you can use your Second Life viewer to write the code and drop the items in a box.  Two; you can use notepad, then copy your code to your viewer to test it. Three; you can use the LSL-Editor, an external editor that has some amazing capability.  For the sake of my mind, I'm going to use the LSL-Editor for the tutorials, so by referring to "Rezz a..." or "Insert a..." do so according to the method you're using to follow this tutorial and the ones that follow.

Part 1.1 – Getting it all going

If you’re using the LSL-Editor

We’re going to create a project, otherwise known as a “Solution” in the editor.  To do this click File > New > Solution and you can put whatever you want in the boxes.  I used Name: Virtual Land, Location: C:\, Solution Name: VirtualLand and clicked Create Direcotry for solution.

Now in the right pane, SolutionExplorer, right click the VirtualLand text, second text set below the top, and click Add New Object (naming isn’t important).  Now right click on that object > Add new item > script.  Once you’ve got your object, and a script inside you can double click the script to start editing.

Everyone Else

For those of you using your viewer, you just rez a prim, and create a script inside :)

Part 1.2 – Global Variables

A global variable is something that can be set, and used throughout the script.  For instance, let’s say you get an object’s owner in the state_entry portion of the default state.  Well then you want to use the value later in the listen event, without declaring a variable before the default state, and assigning it within the state_entry when needed, it throws an error.  This is determined by the programming API’s scope, read about scope on the wiki.  Now that you understand scope, here’s the global variables we’ll be using:

// Global variables
key kOwner;
string sOwner;
vector vLocation;
string sRegion;
string sRegionRating;
integer iSqm;

integer listenID;
list btns;

// Default Variables
string ncLang = "English";
integer diagChan = -87;
float diagTimeout = 30.0;

There’s not much to say about this.  One thing you’ll notice is that my variable names are prefixed with the type they are, look at kOwner and sOwner.  This tells me one is a key, and one is a string, more simple this way, but you can name them what you want.

Part 1.3.1 – Default

Now we need to add a note card to our object.  If you don’t know how to add items yet then you got issues.  For my LSL-Editor users, please remove the filename extension from the note card, just right-click the note card and hit rename.

When a user wears my HUD I want them to be able to select their language based on language note cards in the inventory.  However, if the user ignores the first dialog when it’s setting up, I want them to be able to touch it and be able to select their language.  So, we need to get their information, make a dialog, a touch event, and a few other nifty things.  Here’s the code.

default
{
	state_entry()
	{
		// Gather general information
		kOwner = llGetOwner();
		sOwner = llKey2Name(kOwner);
		// Setting notecard buttons
		btns = [];
		for(integer i=0; i<llGetInventoryNumber(0x07);i++){
			btns += llGetInventoryName(0x07,i);
		}
		if(llGetObjectDesc()=="Default"){
			listenID = llListen(diagChan, "", kOwner, "");
			llSetTimerEvent(diagTimeout);
			llDialog(kOwner, "Language", btns, diagChan);
		}
	}
	listen(integer channel, string name, key id, string message)
	{
		if(channel == diagChan){
			// We know what we're gonna get.
			llSetObjectDesc(message);
			state startProgram;
		}
	}
	timer()
	{
		llListenRemove(listenID);
	}
	touch_start(integer total_number)
	{
		if(llGetObjectDesc()=="Default"){
			listenID = llListen(diagChan, "", kOwner, "");
			llSetTimerEvent(diagTimeout);
			llDialog(kOwner, "Language", btns, diagChan);
		}
	}
	changed(integer change)
	{
		if(change == 0x1 || change == 0x80){
			llResetScript();
		}
	}
}

Line 21-24:  We’re just assigning the variables, and clearing the btns list if it has any values.  Again, utilizing global variables.

Line 25-27: First I use a for loop (view more about for loops). But inside the for loop for the test set, I want to test the integer against the number of notecards in the prim’s inventory.  This is so that I can use the “i” integer to iterate and create a list of available languages based on note cards.

		for(integer i=0; i<llGetInventoryNumber(0x07);i++){
			btns += llGetInventoryName(0x07,i);
		}

Using llGetInventoryNumber(0×07) I get the number of note cards in the prim’s inventory, therefore llGetInventoryNumber(0×07) = 1, or you can say integer somInt = llGetInventoryNumber(0×07) and integer somInt == 1.  Notice the 0×07, that’s Hex code, otherwise known as an integer.  If you can’t remember the integers or hex codes, you can just replace 0×07 with INVENTORY_NOTECARD which is a constant.  More about inventory constants.

Now since I declared btns as a global, and already made sure it’s clear, on line 24, I am concatenating the list with the note card names within the loop via llGetInventoryName().  llGetInventoryName first asks what inventory type you want it to look at, and then which one, via a number.  Now numbers in LSL don’t start at one, they ALL start at zero.  Since we’re using the for loop and told the for loop to use a local integer, we can use that integer within this loop.  So, since we can use it in this loop, we then use it in the llGetIvnetoryName function and tell the function to use out local integer we created.  Again, you don’t have to use hex here, just use either an integer or one of the constants, up to you.  More about llGetInventoryName, more about Lists.

Line 28-32: This is so we can store the user’s language preference for later.  The “SAFEST” way to do this is to store it in the object’s description.  Since I’m shipping the HUD with a description of “Default” I want to check and see if this is the first time the user has purchased the hud. More about if-else statements.

		if(llGetObjectDesc()=="Default"){
			listenID = llListen(diagChan, "", kOwner, "");
			llSetTimerEvent(diagTimeout);
			llDialog(kOwner, "Language", btns, diagChan);
		}

Like I said, the if-else function checks to see if the objects description is “Default” with the equality comparative binary operator.  Since llGetObjectDesc() returns a string variable type, we can check it against our own string.  Hence the equality comparative binary operator.  If llGetObjectDesc isn’t the EXACT same as “Default” then the code will NOT execute. More about llGetObjectDesc(), more about binary operators.

If the If-Else does pass the test, then we need to use the listenID global that was declared earlier, that way I can kill it later, and assign it to llListen.  But with llListen, I only want it to listen on my dialog channel, and only for the owner, nothing more.  However, to ensure we’re listening to the owner only, we use the kOwner global we made in the beginning.  I hardly ever use the name portion of llListen, though it could be useful for objects to object communications, I’m not doing that right now.  More about llListen.

Now to be safe, as all good coders should, we want to set a timer event to kill the listener should the user hit the IGNORE button on the dialog we’re gonna call.  llSetTimerEvent is insanely simple, it just gets a float value, which of course we set as a global earlier.  More about llSetTimerEvent.

Once we’ve set some safety nets, now we are gonna throw the dialog to our user so they can select the language they want.  We throw llDialog to our owner only, we tell the dialog to put the text “Language”, and populate the buttons with our btns list we created within our for loop earlier.  Otherwise if we had say 12 note cards, we’d have to populate the list ourselves, pff, let the programming do it for you.  Finally we tell the dialog to send our response on the our dialog channel, we declared it earlier as a global, ya gotta love ‘em. More on llDialog.

Line 34-41: Since we made a llListen function earlier, that listen function is listening on the dialog channel only.  But a llListen is useless unless we say what to do with the information returned hence the listen event.

listen(integer channel, string name, key id, string message)
	{
		if(channel == diagChan){
			// We know what we're gonna get.
			llSetObjectDesc(message);
			state startProgram;
		}
	}

In the listen event, I made SURE that any data being passed to the event was on the dialog channel, again with the binary operator.  I suppose being that we told llListen to ONLY listen on the dialog channel, that you can do away with this safety net being that llListen is the ONLY function to envoke this event, but I always like to double check.  You could put a listen event in the script, but without a llListen function, it wouldn’t work, though I’m not sure if you’d get an error or not, never tried it.  More on listen.

Now that we’re absolutely positive of what we’re getting, we set the object’s description, via llSetObjectDesc, to the message we received, and since we know that what we receive is going to be a response from our language selection dialog, we want to store it in the description.  More on llSetObjectDesc.

Now that we’ve stored what we want, we simply jump to our starter state, more on this in Part 2.0

Line 42-45: This is envoked when you use llSetTimerEvent.

timer()
	{
		llListenRemove(listenID);
	}

Remember when we created the listenID, this is where we kill it with llListenRemove and effectively disabling the listen event.  So if the user doesn’t click the dialog within 30 seconds, the listen event silently dies.  I say silent because we don’t notify the user at all.  You could llSay(0,”Dialog timeout”) but most people don’t care.  More on llListenRemove.

Line 46-53: This is another safety net.

touch_start(integer total_number)
	{
		if(llGetObjectDesc()=="Default"){
			listenID = llListen(diagChan, "", kOwner, "");
			llSetTimerEvent(diagTimeout);
			llDialog(kOwner, "Language", btns, diagChan);
		}
	}

If the touch_start code block wasn’t here, and the user ignored the starting dialog, then he would have no option but to reset the script inside to get the dialog again.  Therefore restricting him to the English language.  For this I just copied a block set from the state_entry event, nothing more.  More on touch_start.

Line 54-59: This is our final safety net.

changed(integer change)
	{
		if(change == 0x1 || change == 0x80){
			llResetScript();
		}
	}

For example, lets say the user attaches the hud, puts information inside the script via dialogs or whatever, and then decides to sell it… or lets say he changes the inventory of the hud, adds a translation card, then the changed event resets the script.  Again I use hex instead of constants, and compare the change types.  However, this time, I compare one change type, and if that one fails, I check another one.  If none pass the If-Else test, then the script isn’t reset.  This time I use the boolean operator.  Very handy for multiple comparisons.  More about changed, more about boolean operators.

I’ll get to making Part 2 in a few days, be sure to check back okies :)

No comments yet.