Difference between revisions of "User:SuzannaLinn Resident/ScriptingClasses/Give Inventory"

From Second Life Wiki
Jump to navigation Jump to search
 
Line 575: Line 575:
* The limit is 10,000 items.
* The limit is 10,000 items.
* But it gets too slow to show the contents far before reaching the limit.
* But it gets too slow to show the contents far before reaching the limit.
== Saturday class ==
=== Philosophy ===
We are finishing week 6. Next week, week 7, we study how to read notecards. And in week 8, we will study the linkset data.
After week 8 we will have studied most of the basics, the scripting things that we will be using regularly in our scripts.
From week 9 classes will be more specific on one topic or another, starting with the easier ones. They will be acumulative, using knowledge from previous classes, but more independent of each other.
It's important that you practice all that we have studied writing scripts on your own, starting with a blank script better than doing copy-paste.
Reading scripts and understanding them is good. But it's not enough.
To learn to script we need to write our own scripts, make our errors, and solve them. Solving our errors is what helps more to learn.
Although we need to find a good balance, because our time is a limited resource.
It's good to spend 10 minutes looking for a syntax error, and 1 or 2 hours looking for an error in a complicated script. It helps to learn.
Using 5 hours to find an error also helps to learn, but it's not worth the time, that would be better invested in learning something else.
So, first try to find the solution by yourselves. But if you don't find it in a reasonable time, ask.
If you are writting a script and you can't find the way to advance, often is good to stop, go to another activity, and come back to the script later or next day. Sometimes what we can't find in two hours today, we find in two minutes next day.
Of course, when we know well how to script something, copy-paste from previous scripts is the best way. Much faster and less errors, since we are using code already tested.
But first we need to learn to script it. Otherwise when we can't find anything to copy-paste from, we will not know what to do.
And when you have your scripts ready send them to me.  It helps me to know your progress, and to give me ideas on what to comment and suggest you.
Another thing that helps to learn is writing scripts that are not just for practice.
Scripts that you want to have and use, scripts that are going to be really (or secondreally) used and working somewhere.
If you have ideas, or you are involved with some place that needs scripts, explain us.
If the script is in our level of knowledge, we could write it together in class. If it is more advanced we could do it in the future, or I could prepare a class on it.
So, looking for ideas for scripts that will be used somewhere.
Think about it, let's find good ideas for our practices...
=== Practices ===
I'm suggesting 3 different practices:
* 1) A script to list the inventory, but only with the items of one type (only notecards, or only landmarks, etc.).
** start with the "script 1 list contents" from Monday.
** listen to a channel, to say which type of inventory to list.
* 2) A script to list the total of items of each type (like: 3 notecards, 2 landsmarks, 1 script...).
** start with the "script 1 list contents" from Monday.
* 3) A script to give the contents of object to any person walking near the object (without touching the object).
** start with the "script 3 give folder" from Monday.
=== Answers to questions ===
How does LSL handle two active scripts in an object?
* They both run as the same time.
If both have a touch event, do both trigger at the same time?
* Yes, at about the same time, perhaps not exactly the same time, but more or less.
A questions related to class 1: Why we have function  string getFullName(key avatarId)  but we call it with  getfullName(personId)  or  getFullName(toucherId)? Why is not always  avatarId?
* In the script we start defining a new function. We choose the name "getFullName" for the function. We can choose the name that we like (better if it is a meaningful name). This function will return a string, that will be the name and username of a person.
* We want that the function can return the name of any person, so we add a parameter to receive the uuid of the person. The parameter is  key avatarId. When we call the function from somewhere else in our code, we will send the uuid that we want to get the name. Uuid's are of type "key", so our parameter has the type "key". We give the name "avatarId" to this parameter. Inside the function we will refer to this key as "avatarId".
* In other parts of the script, we call the function like  getFullName(id)  or  getFullName(llDetectedKey(0)). llDetectedKey(0) returns the uuid of the toucher in the event touch. We call the function using its name "getFullName".
* We must send to the function a value of type "key" as parameter. To send the value to the function we use any variable or function or value that we want. It must be of type key, to match the parameter of the function. But "avatarId" is only a name used inside the function. If we have the uuid of the person in the variable "id", we call the function as  getFullName(id).
* "avatarid" and "personid" are variable names.  The name of each variable doesn't matter to the functioning of the script and can be anything that makes sense to the scripter. Anything that starts with _, A-Z, or a-z, and has those or digits appended, so Aa1, Bug1, _ME_ are all valid names.
* It's just the script writer not always choosing the same name for the same concept.  We could have chosen AgentID, if our  personal naming convention starts global variables with a capital letter and technically it is an agent, the thing that represents a person in SL, whose UUID we are storing, not the person themselves, nor the avatar they happen to be wearing at any given time.  ... However that distinction is being pretty pedantic and even very experienced scripters will use 'avatar', 'person', etc. when referring to an agent.
* The actual name of a variable makes no difference to how a script functions.  We choose names that make sense to us so that the script is more readable.
* We can change the name of a variable, for instance "avatarId" to "personId".  As long as we change "avatarId" to "personId" everywhere that "avatarId" occurs, and as long as there are no other competing uses of the name "personId", the script will function exactly in the same way.
* Choice of variable names is entirely a matter of the scripter's preferences.  You should develop a style that makes sense and is readable to you, and that is reasonably consistent from script to script so that you don't have so much to figure out/remember when going back to look at an old script, or when copying a function from an older script to paste into a new one. Some common conventions you might want to consider:
** camelCaseNames for ordinary variable.  Starting all names with a lower case letter, or starting the global variables with an upper case letter and the locals with lowercase.
** ALL_CAPS_NAMES for constants.  This is a common convention, and one used in LSL.
** stNames, prefix names with scope and/or type indicators, in LSL.  Scope (the first letter in the name) may be one of g, l, or c, for global, local, or constant.  It is common to use only the g, and leave it off for local variable and use the ALL_CAPs convention for constants.  The second letter, if used, indicates the type of the variable, in LSL, i->integer, f->float, s->string, l->list, and b->boolean.  Note that a Boolean is just an integer used as a two value true or false flag.  This convention can be a good one in a more complex, team environment:  Examples of valid name declarations using the convention:
*** integer giListIndex;  // a global integer
*** float lfRandom = llFrand(10);  // a local random number between 0 and 10, but never 10.
*** string csHeader = "Some Heading Text, to put at the top of a message";  //a constant string using this convention instead of the ALL_CAPS one.
* As with variables, the names of user created functions are entirely the choice  of the scripter. Looking at the names, we could  assume what they do, but one would need to look at the place where the function is declared to be sure.
* Functions are always a name followed by something in parentheses.  If the stuff in parentheses is of the form (type name, type name...) we  are looking at the beginning of a function declaration, the code that determines exactly what the function does. The declaration may also include a type before the function name, in this case the function will return a value of that type when it is called.
* If the stuff in parentheses is a list of names and/or expressions, then we are looking at a function call, a place in the code where the function is actually used.  Each item in the list will be assigned to the corresponding variable in the function declaration when the function is called and the declaration code is executed.
* Stuff that also uses the name() format that aren't functions include flow control operators, like if(...), while(...), and for(...), and the various event declarations like state_entry.  Events are very like functions, except that their caling format (their names and the stuff in parens), is determined by the system, and cannot be changed by the scripter.  The other _big_ difference is that event code is executed when the system determine that the right triggering, conditions exist, not when some other part of the scripter's code calls it directly.
Is there a keyword "print"?
* "print" is a reserved keyword that does nothing for us. It's used by the lindens to print to the console, but we don't have a console.
How does llDetectedKey() work? When does it have more than one toucher?
* To get two touchers in the same event, and get them with llDetectedKey(0) and llDetectedKey(1), both touches must be nearly at the same time, which is very very uncommon to happen. It's a matter of milliseconds, with 0.1 seconds wiill be another event.
* Often we don't look at how many touchers there are and just use llDetectedKey(0). Only if it is very important not to miss any touch, we look at the number of touchers and loop on them.

Latest revision as of 10:57, 17 November 2024

Give Inventory

Monday class

We will chat a lot about "inventory" today. This inventory is always refered to the contents of an object. It has nothing to do with the big inventory where we have our clothes, landmards and everything else.


List the contents

-> Object 1 Inventory list contents

Before giving anything, let's start listing the contents of the box.

We will use a loop to look at all the items one by one.

We will use the keyword "for". Let's see how it works.

The "for" loop is used to repeat a block of code a specific number of times. It's helpful when we need to perform the same action multiple times, such as iterating through a list of items or executing a series of steps.

Here's a basic structure of a "for" loop:

	for (   initialization;    condition;    increment/decrement   )
	{
    		// Code to be executed repeatedly
	}

And, for instance, to say the numbers from 1 to 10:

	integer number;
	for ( number = 1; number < 11; number++ )
	{
    		llOwnerSay( (string)number );
	}

In the inizialitation part we initialize a variable that will be used to control the loop:

  • number = 0;

In the condition part there is a condition that must be true for the loop to continue iterating. The loop will keep executing as long as this condition remains true. Once the condition becomes false, the loop will terminate:

  • number < 10;

In the increment/decrement part we modify the variable used for controlling the loop. Typically, we'll either increment or decrement the variable. This step occurs at the end of each iteration:

  • number++

In our example, number is incremented by 1 after each iteration.

After saying the number 10, number is incremented to 11, the condition number<11 becomes false, the code is not executed any more and the loop finishes.

The three parts (init, condition, inc/dec) of the "for" have to be enclosed in parentheses: "(" and ")". The code executed in the loop have to be enclosed in curly brackets: "{" and "}"

First we need to know how many items there are inside the object.

The function is llGetInventoryNumber(). It has the parameter "type". The type can be notecard, landmark, texture, object, etc. It can also be all the items, with the constant INVENTORY_ALL.

We get the quantity of items with this:

  • totalItems = llGetInventoryNumber( INVENTORY_ALL );

The items are numbered starting with 0, so we want to loop from 0 to totalItems-1 one by one, with this:

  • for ( itemNumber = 0; itemNumber < totalItems; itemNumber++ )

Inside the "for" loop we want to get the name and the type of each item.

llGetInventoryName() gives us the name. It has 2 parameters, "type" and "number". Type is the same than in llGetInventoryCounter(), and we must use the same to make it work. So it will be:

  • itemName = llGetInventoryName( INVENTORY_ALL, itemNumber );

The type comes from llGetInventoryType() and the parameters are different. This function has one parameter that is "name". We need to get the name of the item before being able to get its type. And it is:

  • itemType = llGetInventoryType( itemName );

The type is a numeric code, from 0 to 21, and we want to show the description of the type.

To find our type description we will use the list TYPES.

"list" is a type of variable like "string", "integer" or "float".

But instead of one value, it can take, as its name say, a list of values, for instance:

  • list fruits = [ "Apple", "Banana", "Pear", "Melon", "Apricot" ];
  • list primeNumbers = [ 2, 3, 5, 7, 11, 13, 17, 19 ];

The values have to be enclosed in square brackets and can be of any type, also floats, vectors, keys, etc. A list can have a mix of values of different types, but we are not going to mix types.

Our list TYPES has 22 values, one for each inventory type. You see that some values are empty, because not all the type codes exist.

The function to get a value from a list is llList2String and its parameters are the list and the position that we want.

  • itemTypeDescription=llList2String( TYPES, itemType );

And finally we will say to ourselves, with llOwnerSay, the name and type of the object:

  • llOwnerSay( itemName + " (" + itemTypeDescription + ")" );

At the end of the script there is the list of the constants for the inventory types.

Now touch your box, to see your list of contents.


Give the contents

-> Object 2 Inventory give contents

Now it's time to make the box give the contents.

We will write a new function: giveInventory( key Receiver ). The receiver is the person who has touched the box, so you can give the contents to anybody. Later we will see how to call the function from the event touch_start.

We start exactly as in the list function, with the "for" loop:

	TottalItems = llGetInventoryNumber( INVENTORY_ALL );
	for ( itemNumber = 0; itemNumber < totalItems; itemNumber++ )

and getting name and type:

	itemName = llGetInventoryName( INVENTORY_ALL, itemNumber );
	itemType = llGetInventoryType( itemName  );

We usually don't want to give the scripts, so in this sample we will exclude them:

	if ( itemType != INVENTORY_SCRIPT )

And the function to give an item from the inventory is llGiveInventory(). It has two parameters: the receiver and the name of the item:

	llGiveInventory( receiver, itemName );

And finally in the event touch_start we will get the toucher with the llDetectedKey(0) function:

	toucher = llDetectedKey( 0 );

and call our function:

	giveInventory( toucher );

We could have done giveInventory( llDetectedKey( 0 ) ) and avoid creating the variable toucher. But I suggest to do it step by step. It makes a more clear script, for us and for other scripters whom we give it.

Now touch your box, to receive a copy of its contents. Also you can touch the box of other students.


Give the contents in a folder

.> Object 3 Inventory give folder

You have seen that this way of giving the contents is a bit uncomfortable. We get a window to close for each item and notecards opened.

We can do it better, giving the contents inside a folder.

The function to give a folder is llGiveInventoryList(). It has 3 parameters: the receiver, the folder name and a list of the items to give.

So before giving the contents we need to make a list with them.

We create am enpty list:

  • list items;

We can add items to the end of a list using the "+" operator: items = items + itemName, or in the "+=" short version:

  • items += itemName;

We write this line inside the "for" loop, and remove the previous llGiveInventory() line

Now we need a name for the folder, we will use the description of our box. We get the description of an object with llGetObjectDesc() :

  • folderName = llGetObjectDesc();

Using the object description is a way that any user of our "giving" box can change the folder name without touching the script.

And, out of the "for" loop we write:

  • llGiveInventoryList( receiver, folderName, items );

Now touch your box, to receive a folder with its contents. Also you can touch the box of other students.


Delete inventory

-> Object 4 Inventory remove content

As our last script we will remove one item from the inventory.

You have seen all the time that there is a notecard called "Unuseful notecard". Let's go to remove it.

The function is llRemoveInventory() and it has one parameter: the name of the item

  • llRemoveInventory( name );

But there is a problem. If the item that we want to remove doesn't exist the function gives an error. And if we call the function twice the first time will remove the item and the second time will not be there and we will get an error.

We will write a function that checks if an item exists:

  • integer itemExists( string name )

I suggest to write a new function each time that we are going to write something that could be reused. You can copy the itemExists function to another script any time that you need to check for something in the contents, in any kind of script. If you have it mixed with the removing function you can't reuse it.

Our iItemExists function returns an integer and we will return one of the constants TRUE or FALSE.

To know if the item is in the contents we will use llGetInventoryType(). We have already seen that it gives us the type of the item, but when the item doesn't exist it returns INVENTORY_NONE.

We will use an "if" to assign a value of TRUE or FALSE that we will return with the "return" command:

	if ( llGetInventoryType( name ) == INVENTORY_NONE ) {
         		exists = FALSE;
 	} else {
       		 exists = TRUE;
	}
	 return exists;

Remember to always use the double "==" when comparing values. The single "=" is only to assign a value to a variable. In this case it would show an error when saving, but in other cases can lead to a very confusing results.

We could also have written the itemExists function in only one line:

  • return ( llGetInventoryType( name ) == INVENTORY_NONE ).

I suggest to use the more detailed, although longer, way for clarity.

Now we will write our removeInventory( string name ) function that we will call from the event touch_start:

	removeInventory( string name ) {
    		if (itemExists( name ) ) {
        			llRemoveInventory( name );
       			llOwnerSay( "The item '" + name + "' has been removed" );
    		} else {
        			llOwnerSay( "The item '" + name + "' does not exist" );
    		}
	}

And call it inside the event touch_start:

	removeInventory( "Unuseful notecard" );

Now touch your box, to delete the notecard. And touch again to get the message that the notecard is not there.


Answers to questions

Are the curly braces always needed in a loop for?

  • Yes, unless it's a single statement. If there is only one statement the braces aren't needed, but is good to add them for clarity.

Does the script gets the information from the items contained in the object?

  • Yes, all these functions starting with llGetInventory*** get the info from the contents of the object.

What are the code numbers of the inventory types?

  • 0 - INVENTORY_TEXTURE
  • 1 - INVENTORY_SOUND
  • 3 - INVENTORY_LANDMARK
  • 5- INVENTORY_CLOTHING
  • 6 - INVENTORY_OBJECT
  • 7 - NVENTORY_NOTECARD
  • 10- INVENTORY_SCRIPT
  • 13- INVENTORY_BODYPART
  • 20- INVENTORY_ANIMATION
  • 21- INVENTORY_GESTURE

Can we assign another number to those items?

  • No, we can't assign another number of code to the types, it's predefined by LSL.
  • The constants are declared by the LL engineers and we can't change the way LSL functions handle them.
  • llGetInventoryType() always returns the codes that LSL has predefined, we need to work with the LSL codes.

What is the type with code 2, between "Sound" and "Landmark"?

  • It doesn't exist, the numbers of types are not correlative, there are codes not used
  • In our list of descriptions of types, we have an empty item for the ones that doesn't exist.

If it doesn't exist, why is it in our list of type descriptions?

  • Because we use the code of the type as the index of the list.
  • For instance, "clothing" has the type code 5 (the constant INVENTORY_CLOTHING has a value of 5) and we need "clothing" to be in the index 5 (position 6th) in the list to obtain the description using the code of type.
  • The list is a list of names indexed by the INVENTORY_xxx numbers. Those numbers are not all used at present. the "" entries are placeholders for unused values so we can index into the list by the appropriate number to get the text.

What happens if we leave the "" out of the list?

  • llGetInventoryType() returns an integer, the code of the type. For instance, "landmark" is 3.
  • If we remove the "" in the list:
    • ["Texture", "Sound", "Landmark", "Clothing", "Object", ...
  • When we go to obtain index 3 instead of "landmark" we will get "clothing".
  • The index would be off by the number of ""s you left out after the point you left them out.

Do we either have to identify the type or the type number, to get the right inventory item?

  • The type is a number, we use the constants INVENTORY_*** instead of the number
  • Internally to LSL, there is no difference between type, and type number, The constants are a convenience for coders not to have remember that 21 means the inventory item is a gesture.

Does the script work for any number of items in the inventory?

  • Yes, we get the quantity of items with llGetInventoryNumber() and loop on all of them.

Could I add a T shirt and a cat and it would just list them?

  • Yes, it will. Play with things. Break stuff! Much of what you will learn about scripting will come from all the ways you will find to do thing wrong, wrong, wrong!

Why does llGetInventory*** functions use names and not uuid's?

  • These functions are working from Item Name instead of by Item Key as many things do, because for some types of items, like textures, the keys associated in inventory (which is on an inventory server, not the sim per se) can be used to bypass the permission system's transfer lock.

Does != mean "is not"?

  • != means "not equal".

Why are we excluding the scripts when we give the items?

  • When we have an object that give contents, usually we have the contents and a script to send them, and we don't want to send the script, so we exclude it.
  • But we can send scripts it, if we want, like any other items.

Does llRemoveInventory() remove the notecard from object's content?

  • Yes, llRemoveInventory() removes one item from the object contents.
  • All of these functions operate on object Inventory, not our inventory.

Can the script remove any notecard?

  • Yes, and any other item that is in the contents of the object where the script is.
  • Including the script itself, which is often useful if the script is a one-shot for setting object properties.
  • Another use for this feature is cleaning up a mail/suggestion box where people can drop notes for you to read, or a game like a treasure hunt where the given item is removed when someone finds it.

Is there no error return from llRemoveInventory()?

  • llRemoveInventory() doesn't have a return value.
  • If we use it with an item that doesn't exist, the script throws an error and stops.

Is it possible to write a script that can remove any file type (for example all notecards or all ladmarks)?

  • Yes, we would loop through all items of the desired type.

Are there other ways to write our function itemExists()?

  • Yes, itemExists is another example of varying coding standards.
    • Instead of setting a variable, we could just return FALSE or TRUE directly in the if statement. This would be slightly more efficient.
    • By setting the variable, and returning at a single point, we are adhering to a SIngle Point of Return standard.
    • There are some fairly arcane reasons why this is a good idea put forth by people who think about how to standardize good coding practices.
    • In essence they boil down to, it makes it easier to add functionality or debugging code to an existing function if you have only a single point at which you know everything the existing function is doing is done.

Could we add an item with the script to the object inventory?

  • No, there is no way to do it. We can give items, but not add them.
  • The item has to be something the object has control of, it can't grab something from someone's inventory or from another object's. However, one object can give something in its inventory to another object, thus adding to the other object's inventory under script control.

Can we give an item from another box if they are linked?

  • No, we would need a script in the other box too.

Can RLV access our inventory?

  • Yes, RLV can access our inventory, but only some especially designed folders.


Wednesday class

A HUD to give contents to the people around us

This script is longer than usual, but you are very good students, and I decided to add it to the class. Don't worry if you need some time to understand it fully.

The idea of this example is to give things (which are copy and transfer), like a notecard, a texture, an object, etc., or several ones... to all the people who is around us.

For instance, if you are chatting with 10 people about an interesting place that you have discovered, and everyone wants the landmark, instead of giving the lm to them one by one, you will drop it into the HUD and give it to everybody with only one click on the HUD.

-> Script 5 Inventory distribute contents HUD

(lines 0 -39)

At the start there is a "map of functions", for a quick reference with the list of functions and on what is calling what.

It's good to write it so people who read our scripts can understand them faster. And it also can help us to remember how the script is designed when we use it after some time.

Next in the script there are the configurable parameters, only one in in this script.

GIVING_RANGE is the distance in meters that will be used to select the people whom we will give the HUD contents. In this case, we will give to anybody who is nearer to 40 meters from us.

We are going to study the functions in the script in the order that they will be executed, starting at state_entry.

Look at line 156, event state_entry.

(lines 156-166)

In lines 158-160 we assign some values. They will not change so we use all uppercase names for the variables, which mean that they are constants.

We could call the functions each time we need them, instead of using variables, but this way is faster to execute, faster to type and, if we choose good names, easier to remember and to read.

In lines 161-164, we change the name of the HUD, including our name, like "Suzanna's contents giver". The object name is used when giving, so it's good that people knows who is giving it.

We get our name with llDisplayName() and set it with llSetObjectName().

In line 163 we check if the name of the HUD, that we get with llGetObjectName() is the same that we have just assigned. It should be... why not?

Because the names of objects can't take extended characters, like accents, drawings, other fonts... and they are replaced by "?". And the maximum length of names of objects is 63, in case of a longer name it would be cut to the first 63 characters.

In case that the HUD hasn't taken our name, we use our username, with llGetUsername(), which can't have extended characters.

Of course you can comment out lines 161-164 and give the HUD the name that you want. I added it here as an excuse to explain something more :)

And to finish the initizialition process at the event state_entry, it calls checkContents().

Let's go to line 72 to see checkContents().

(lines 52-72)

This function looks at what is in our HUD contents. Now there is only 1 item, this script.

Look at your Class Materials folder and add the "Inventory distribute contents HUD". It appears as a white circle on bottom center of the screen. And you have received a message from your HUD saying: "Empty, drop items".

The HUD will send everything in it, except this script. So when there is only one item, which has to be this script, it has nothing to send and considers itself empty.

This is done in lines 74-79. It gets the total items, and if it is 1, sets the color to white and sends you the message. It also sets the folder name and the floating text to empty.

And the execution stops here. Later we will see what it does when there is more than one item.

Drag an drop an item, whatever you have in your inventory, into the HUD, if it is not a texture. To send a texture, edit the HUD, go to "contents" and copy the texture there. If you try dropping a texture to the HUD, it will show the texture on it, instead of adding it to contents.

Now the color of your HUD has changed to green, and it has sent you a message: "Ready, 1 items, drop more or touch to give".

When there is a change in the contents of the HUD, the script receives an event "changed".

Lets go to line 175 to look at it.

(lines 175-181)

The event gets, as parameter, what has changed. We check if it is the owner (the HUD has been given to someone else) or the contents, which is what has happened now.

And, having the contents changed, it goes to checkContents() again.

So let's move back to line 80.

(lines 72-92)

Now totalItems is 2 (the script and the thing that we have dropped in), so it calls getFolderName() in lines 81-82.

We could have used several ways to set a folder name, like:

  • manually changing the object description (to be got with llGetObjectDesc()).
  • manually saying it in a private channel (to be got with llListen() and the event "listen", as we have seen in previous classes).
  • being asked in a textbox (not yet, because we haven't studied text boxes and menus, we will do it in next classes).

But we have used a faster way: the folder name will be the same that the name of the first item to be dropped in the HUD. That's why we call getFolderName() when totalItems is 2.

Let's go to look at getFolderName() in line 41.

(lines 41-47)

We have only two items, our script and another one (that could be a script too). So, let's take it easy:

  • get the name of the first item, if it happens to be THIS_SCRIPT get the second.

Back to checkContents() in line 84.

(lines 72-92)

Now we are going to check if the items that we are dropping have the right permissions to be given. Which are copy and transfer. If items are no transfer, no way to give them. If items are no copy, we will give it to the first person around us and it will disappear from the contents.

It calls checkInventoryPerms(), which return TRUE or FALSE depending on the permissions being ok or not.

Let's go to line 49 to see it.

(lines 49-70)

We already now the process to look at the contents. The new functions are in lines 60-61.

llGetInventoryPermMask() retuns a bits value with the perms that it has. Its parameters are the name of the item and the permissions that we want to know. In this case our permissions as owner, using the LSL constant MASK_OWNER.

Other permissions are MASK_GROUP, MASK_EVERYONE or MASK_NEXT.

We check if the item has the copy and transfer permissions. If not the HUD sends a message and the function returns FALSE.

We also use this function to prepare the text with the list of items for the floating text and to show it.

And, back to checkContents(), in line 84.

(lines 72-92)

it sets HUD color and sends message depending on permissions. Remember that the total of items is always totalItems-1 because our script doesn't count.

By the way, it's difficult to get something without the right permissions into the HUD, it doesn't allow it to be dropped or copied to contents. But it's an excuse to explain another function :)

Now drop or copy some more items into the HUD. Don't click the HUD yet, first we are going to see what it will do.

Let's go to the event touch_start, in line 168.

(lines 168-173)

To see if the HUD is ready to give, we look at its color, if it's GREEN we call giveInventory().

We could have used a variable like isReady = TRUE or = FALSE. But no need, since we have the color that says it.

And let's go to look to giveInventory() in line 122.

(lines 122-152)

After declaring variables, in line 133 it calls getInventory(), that returns a list with the items to send.

getInventory() is in line 94.

(lines 94-107)

Nothing new to explain here. You already know it all.

Let's look at lines 134-135 in giveInventory().

(lines 122-152)

Here we have a new function, llGetAgentList().

llGetAgentList() retuns a list with the UUIDs of all the avatars, depending on the first parameter, that can be:

  • AGENT_LIST_PARCEL : all the avatars in this parcel.
  • AGENT_LIST_PARCEL_OWNER : all the avatars in this parcel and all parcels with the same owner in the sim.
  • AGENT_LIST_REGION : all the avatars in the sim.

The second parameter does nothing, it has to be an empty list []. Let's hope that linden developers will do something with it in the future.

In line 135 we get totalPeople with llGetListLength(), that returns the number of elements in a list. Remember, the number of the first element is 0, and the last element is totalPeople-1 in this case.

We get our position in myPos with llGetPos().

And now, in lines 137-149, we check if the avatars in the people list are inside our GIVING_RANGE (40 meters in this example).

We get the UUID of each avatar with llList2Key().

llGetAgentList() includes also your UUID, so we need to avoid sending the contents to ourselves. We do it with the "if" in line 139.

To get the position of the avatar we use llGetObjectDetails(). It has two parameters: the UUID of the avatar (or it could be an object) and a list with the information that we want the function to return.

There are lots of options, here we use OBJECT_POS which gives us the position of the avatar or the object. The function expects a list, so, even if there is only one element, we must use a list in the parameter. We make a list with one element just adding [ and ]: [OBJECT_POS].

llGetObjectDetails() also returns a list. If we ask for several informations, not only the position, it will return all the information in the list. In our script we get a list of one element, the person position, as return.

But we want a vector to assign to personPos. We will use llList2Vector() to typecast one element of the list to a vector, the element that we choose in the second parameter, in this case "0". List indexes start counting by 0, and a list of one element only has the element 0.

And to find the distance between personPos and myPos we use the function llVecDist() that return the distance.

It always returns a positive value. If the distance from A to B is 10, the distance from B to A is also 10.

And if it is inside GIVING_RANGE it gives the contents with llGiveInventoryList() and says message.

After sending the messages, the contents are removed from the HUD, calling removeInventory() in line 149.

removeInventory() is in line 109.

(lines 109-120)

We know everything here already.

Back in giveInventory(), in lines 150-151.

(lines 122-152)

It says a final message and calls checkContents(), which will set the HUD color to white, getting it ready for the next sending.

Now click the HUD to send the contents to the people around you.


Answers to questions

Can we change the permission of the item when they are in the HUD?

  • We can adjust the perms on an item after dropping it in the HUD using the object editor.
  • It will triggers the event "changed".

Do we always have to rez the hud to add items?

  • Yes, we have to add the HUD to add items. We can't add items to it while is only in the inventory, not added.

Is the position of the HUD the same as the position of avatar?

  • Yes, it is.

Why do we limit the range to give contents?

  • Limiting range is usually good even when restricting stuff to a parcel. That way we are giving to everyone in the room, say, but not in the skybox that happens to be 2000 meters overhead.

If the distance was 40.2 would be rounded to 40?

  • No, the distance is not rounded. personDistance is a float, so it has decimals.
  • With a distance of 40.2 the person would be out of range and we wouldn't send the contents
  • If personDistance were an integer, it would truncated to 40, that is, even 40.999 would round down to 40 instead of up to 50.

Would the object give if the person is exactly at 40 meters?

  • No, we are comparing to less than GIVING_RANGE, so 40 meters is out.

Where is the GIVING_RANGE set?

  • GIVING_RANGE is set to 40 at the start of the script, in line 26.
  • It can be changed to the distance that you prefer.

What's the maximum range to give?

  • llGiveInventory() and llGiveInventoryList() can give to anyone anywhere in SL.
  • The items will be sent, and if the person is not online, they will be received when coming online.
  • But if we are giving to an ojbect,the range is limited to the region.

Can we scan for people that is in anothe region?

  • No, we can only scan in the same region.
  • But if we know the names, there is a way to know if they are online, we will see it in a future class. We can know if they are online, but not where they are.

Can we send "transfer" only items?

  • We would want our permissions to be at least copy, transfer. And next owner permissions to be transfer only.

And about the so called gatchas, items that are no copy and just trasnfer?

  • The way gatcha's typically worked was that the vendor/giver gave the recipient a token object that is transfer only. That token could be passed between individuals indefinately. When someone 'opened' the token by rezzing it, etc., it, or a remote server gave the opener the actual gatcha object which typically had at least copy perms but was no-transfer. The token then deleted itself so that it couldn't be passed on farther.

What is a bitmask?

  • It's an integer where each bit represents a different thing.
    • For instance, in the event changed, the change is a bitmask.
    • One bit is 1 if the owner has changed, another bit is 1 if the contents have changed, and so on.
  • Bitmasks are useful when there are several things that can only be "yes" or "no"
  • Instead of using several integer variables, we pack all of them in a single integer, using a bit for each one.
  • A bit mask is used wnen you put a lot of one bit flags in a single integer. the mask is number, usually with only 1 bit set, that selects the bit out of the set of flags currently interested in. Like with the changed event, the changes argument is an integer with bits set for the kinds of changes that have occured. CHANGED_INVENTORY is a number with one bit set, that when Anded with changes, lets you know whether that specific bit was set in changes.

Any suggestion on how to clean inventory?

  • Make a junk folder. In that folder make a folder with the current date. Toss dubious stuff in that folder. A year from now, if you haven't gone looking for something in it, throw that dated folder away without looking at it.

What is the limit for items in a prim?

  • The limit is 10,000 items.
  • But it gets too slow to show the contents far before reaching the limit.


Saturday class

Philosophy

We are finishing week 6. Next week, week 7, we study how to read notecards. And in week 8, we will study the linkset data.

After week 8 we will have studied most of the basics, the scripting things that we will be using regularly in our scripts.

From week 9 classes will be more specific on one topic or another, starting with the easier ones. They will be acumulative, using knowledge from previous classes, but more independent of each other.

It's important that you practice all that we have studied writing scripts on your own, starting with a blank script better than doing copy-paste.

Reading scripts and understanding them is good. But it's not enough.

To learn to script we need to write our own scripts, make our errors, and solve them. Solving our errors is what helps more to learn.

Although we need to find a good balance, because our time is a limited resource.

It's good to spend 10 minutes looking for a syntax error, and 1 or 2 hours looking for an error in a complicated script. It helps to learn.

Using 5 hours to find an error also helps to learn, but it's not worth the time, that would be better invested in learning something else.

So, first try to find the solution by yourselves. But if you don't find it in a reasonable time, ask.

If you are writting a script and you can't find the way to advance, often is good to stop, go to another activity, and come back to the script later or next day. Sometimes what we can't find in two hours today, we find in two minutes next day.

Of course, when we know well how to script something, copy-paste from previous scripts is the best way. Much faster and less errors, since we are using code already tested.

But first we need to learn to script it. Otherwise when we can't find anything to copy-paste from, we will not know what to do.

And when you have your scripts ready send them to me. It helps me to know your progress, and to give me ideas on what to comment and suggest you.

Another thing that helps to learn is writing scripts that are not just for practice.

Scripts that you want to have and use, scripts that are going to be really (or secondreally) used and working somewhere.

If you have ideas, or you are involved with some place that needs scripts, explain us.

If the script is in our level of knowledge, we could write it together in class. If it is more advanced we could do it in the future, or I could prepare a class on it.

So, looking for ideas for scripts that will be used somewhere.

Think about it, let's find good ideas for our practices...


Practices

I'm suggesting 3 different practices:

  • 1) A script to list the inventory, but only with the items of one type (only notecards, or only landmarks, etc.).
    • start with the "script 1 list contents" from Monday.
    • listen to a channel, to say which type of inventory to list.
  • 2) A script to list the total of items of each type (like: 3 notecards, 2 landsmarks, 1 script...).
    • start with the "script 1 list contents" from Monday.
  • 3) A script to give the contents of object to any person walking near the object (without touching the object).
    • start with the "script 3 give folder" from Monday.


Answers to questions

How does LSL handle two active scripts in an object?

  • They both run as the same time.

If both have a touch event, do both trigger at the same time?

  • Yes, at about the same time, perhaps not exactly the same time, but more or less.

A questions related to class 1: Why we have function string getFullName(key avatarId) but we call it with getfullName(personId) or getFullName(toucherId)? Why is not always avatarId?

  • In the script we start defining a new function. We choose the name "getFullName" for the function. We can choose the name that we like (better if it is a meaningful name). This function will return a string, that will be the name and username of a person.
  • We want that the function can return the name of any person, so we add a parameter to receive the uuid of the person. The parameter is key avatarId. When we call the function from somewhere else in our code, we will send the uuid that we want to get the name. Uuid's are of type "key", so our parameter has the type "key". We give the name "avatarId" to this parameter. Inside the function we will refer to this key as "avatarId".
  • In other parts of the script, we call the function like getFullName(id) or getFullName(llDetectedKey(0)). llDetectedKey(0) returns the uuid of the toucher in the event touch. We call the function using its name "getFullName".
  • We must send to the function a value of type "key" as parameter. To send the value to the function we use any variable or function or value that we want. It must be of type key, to match the parameter of the function. But "avatarId" is only a name used inside the function. If we have the uuid of the person in the variable "id", we call the function as getFullName(id).
  • "avatarid" and "personid" are variable names. The name of each variable doesn't matter to the functioning of the script and can be anything that makes sense to the scripter. Anything that starts with _, A-Z, or a-z, and has those or digits appended, so Aa1, Bug1, _ME_ are all valid names.
  • It's just the script writer not always choosing the same name for the same concept. We could have chosen AgentID, if our personal naming convention starts global variables with a capital letter and technically it is an agent, the thing that represents a person in SL, whose UUID we are storing, not the person themselves, nor the avatar they happen to be wearing at any given time. ... However that distinction is being pretty pedantic and even very experienced scripters will use 'avatar', 'person', etc. when referring to an agent.
  • The actual name of a variable makes no difference to how a script functions. We choose names that make sense to us so that the script is more readable.
  • We can change the name of a variable, for instance "avatarId" to "personId". As long as we change "avatarId" to "personId" everywhere that "avatarId" occurs, and as long as there are no other competing uses of the name "personId", the script will function exactly in the same way.
  • Choice of variable names is entirely a matter of the scripter's preferences. You should develop a style that makes sense and is readable to you, and that is reasonably consistent from script to script so that you don't have so much to figure out/remember when going back to look at an old script, or when copying a function from an older script to paste into a new one. Some common conventions you might want to consider:
    • camelCaseNames for ordinary variable. Starting all names with a lower case letter, or starting the global variables with an upper case letter and the locals with lowercase.
    • ALL_CAPS_NAMES for constants. This is a common convention, and one used in LSL.
    • stNames, prefix names with scope and/or type indicators, in LSL. Scope (the first letter in the name) may be one of g, l, or c, for global, local, or constant. It is common to use only the g, and leave it off for local variable and use the ALL_CAPs convention for constants. The second letter, if used, indicates the type of the variable, in LSL, i->integer, f->float, s->string, l->list, and b->boolean. Note that a Boolean is just an integer used as a two value true or false flag. This convention can be a good one in a more complex, team environment: Examples of valid name declarations using the convention:
      • integer giListIndex; // a global integer
      • float lfRandom = llFrand(10); // a local random number between 0 and 10, but never 10.
      • string csHeader = "Some Heading Text, to put at the top of a message"; //a constant string using this convention instead of the ALL_CAPS one.
  • As with variables, the names of user created functions are entirely the choice of the scripter. Looking at the names, we could assume what they do, but one would need to look at the place where the function is declared to be sure.
  • Functions are always a name followed by something in parentheses. If the stuff in parentheses is of the form (type name, type name...) we are looking at the beginning of a function declaration, the code that determines exactly what the function does. The declaration may also include a type before the function name, in this case the function will return a value of that type when it is called.
  • If the stuff in parentheses is a list of names and/or expressions, then we are looking at a function call, a place in the code where the function is actually used. Each item in the list will be assigned to the corresponding variable in the function declaration when the function is called and the declaration code is executed.
  • Stuff that also uses the name() format that aren't functions include flow control operators, like if(...), while(...), and for(...), and the various event declarations like state_entry. Events are very like functions, except that their caling format (their names and the stuff in parens), is determined by the system, and cannot be changed by the scripter. The other _big_ difference is that event code is executed when the system determine that the right triggering, conditions exist, not when some other part of the scripter's code calls it directly.

Is there a keyword "print"?

  • "print" is a reserved keyword that does nothing for us. It's used by the lindens to print to the console, but we don't have a console.

How does llDetectedKey() work? When does it have more than one toucher?

  • To get two touchers in the same event, and get them with llDetectedKey(0) and llDetectedKey(1), both touches must be nearly at the same time, which is very very uncommon to happen. It's a matter of milliseconds, with 0.1 seconds wiill be another event.
  • Often we don't look at how many touchers there are and just use llDetectedKey(0). Only if it is very important not to miss any touch, we look at the number of touchers and loop on them.