Adding Behavior to Your Objects
Okay, now try scripting. Click the Build button and the IDE window is revealed. The cube shape and Create icon should be the defaults. Move the mouse cursor somewhere close to you and "rez" a cubic prim. While there are numerous shaping, positioning, and texturing features you can apply to a prim, focus on LSL scripting for now, beginning with an understanding of data types and moving to the structure of a script itself.
LSL Data Types
LSL supports the types in Table 1. Although some are obvious, others may surprise you. C programmers may be happy to see the explicit support for string operations, such as concatenate (via the "+" operator) and the usual operations supported for Java strings.
Data Type | Usage |
integer | Whole number in the range -2,147,483,648 to 2,147,483,647. |
float | Decimal number in the range 1.175494351E-38 to 3.402823466E+38. |
vector | Three floats in the form < x , y , z >. Usually a position, color, or Euler rotation. An example declaration is: vector a = <1,2,3>;. |
rotation | Rotations consist of four floats, in the form <x,y,z,s>. The s term connotes an angular measurement. |
key | A UUID (specialized string) used to identify something in SL, notably an agent, object, sound, texture, other inventory item, or dataserver request. |
string | A sequence of characters, limited only by the amount of free memory available to the script. |
list | A heterogeneous collection of other data types, (excluding list itself). |
In addition to C native types such as integer and float, LSL also explicitly supports the UTF-8 string type that behaves as you would expect (in Java, not C), and a key type for creating, manipulating, and representing globally unique identifiers (GUIDs). There are no user-defined constants, thus nothing like enum.
LSL is event-driven, features states, and supports 3D variable types (vector and quaternion). LSL has built-in functions for manipulating physics and social interaction among avatars. LSL adds some interesting keywords; for example, state, which is essentially a "go to" operator that forces a transition in a new state handler.
For representing collections, LSL supports only the list type. There are no arrays or dictionaries, and lists cannot contain other lists. Lists in LSL are sufficiently annoying that we provide an expanded description here.
The list type may contain heterogeneous elements comprised of lists of other primitive data types. Lists are created via comma-separated values (CSVs) of the other data types, enclosed by square brackets: "[" and "]." A typical list declaration would resemble:
// a list with a two integers, a // string, float, vector and rotation list l = [42,0, "electric sheep", 3.1416,<1.0,2,0>,<0,0,0,1>];
Lists also store metadata about each list item, but also its type, accessed through list accessor primitives llGetListEntryType and llCSV2List (an annoyance with LSL is that every built-in functionand there are myriadmust begin with "ll," as if you somehow needed a reminder as to whose scripting environment you're using). Lists can only be read using accessor functions (rather than bracket notation). Thus, to retrieve the first element from the aforementioned declared list and cast it to a string, you might use:
string element; element = llList2String(l,0);
Elements fetched from lists are accessed and cast to type in a single operation; llList2Float retrieves an element and casts it to float, llList2Integer casts the element to integer, and so on. One additional shortcoming: Because lists cannot contain other lists, multidimensional arrays are difficult to create and manage. One way to get around both the lack of arrays and the lack of an explicit object model is to use "list striding."
A "strided" list is simply a list with items grouped together in a fixed and repeating layout. If, for example, you needed in-memory containment to record visitors to your store, such a list might contain the visitor name, amount of Linden dollars spent, item purchased, and number of minutes spent on your premise. Although you would periodically persist this cache to a formal database, you might want a short-term cache. In this case, you might use a strided list:
integer STRIDE = 4; list patrons = ["Zeno Chambers",500, "Magic Broomstick","2006-10-31", "Biggie Pike", 50, "Flagon of Pumpkin juice","2006-10-31", "Mae Moriarity",25,"Halloween Hangover Kelper","2006-11-01"];
Then you could use one of the strided list convenience functions to assist in managing the list. To sort (ascending) a strided list, you would use:
patrons = llListSort(patrons, STRIDE, TRUE);
LSL Script Structure
If you select the Content tab, right-click and select New Script, you see a new script appear in the editor window. It should be identical to Listing One and your worldview should resemble Figure 2.
1 default 2 { 3 state_entry() 4 { 5 llSay(0, "Hello, Avatar!"); 6 } 7 8 touch_start(integer total_number) 9 { 10 llSay(0, "Touched."); 11 } 12 }
Inspecting a default new script reveals a few things about LSL. First of all, LSL is essentially a C-like language, with roughly equivalent syntax and similar control flow. First, notice that the logic for the object is normally contained within a code block called "default." You might think of the default block as the "static void main" of LSL. The default module must always be present, and represents the event callbacks for the object in which it exists. Other states may be present either in this module or in others in the Contents folder, and it's possible to transition to some other state handler (and back) via the state keyword.
The default script has two handlers, state_entry and touch_start. Reviewing the comprehensive list of event handlers at the LSL wiki, you see that state_entry is invoked whenever the state is entered and on creation of the instance of the object. Touch_start is invoked whenever the object is touched (you'll need to save and compile the script and close the IDE to see the effects of the touch_start callback). In both handlers, the llSay function is called; llSay broadcasts its argument (a text string) to anyone within a 10-square-meters range. Now that you've seen the "hello world" script, it's time to try diving off the deep end. Delete the contents of the script for the object and replace it with the script in Listing Two.
1 //This script forwards all surrounding chat to the via email 2 3 4 string g_Mail_Addr = "somename@some_domain.com"; 5 integer g_email = FALSE; 6 integer g_IM = FALSE; 7 integer listen_channel = 0; 8 integer command_channel = 2; 9 float g_maxtimeout = 180.0; 10 string g_emailStateOn = "Recording has been enabled!"; 11 string g_emailStateOff = "Recording has been disabled!"; 12 string g_IMStateOn = "IM relay has been enabled!"; 13 string g_IMStateOff = "IM relay has been disabled!"; 14 15 default 16 { 17 on_rez(integer param) { 18 llResetScript(); 19 }//on_rez 20 21 // toggle state during the touch handler 22 state_entry(){ // possible to listen on more than one channel? 23 llGiveInventory(llGetOwner(), llGetInventoryName(INVENTORY_OBJECT, 0)); 24 string owner = llGetOwner(); 25 llListen( command_channel, "", owner, "" ); 26 llListen( listen_channel, "", NULL_KEY, "" ); 27 llInstantMessage(owner, g_emailStateOff+" Type: '/2 hear! <email address>' to capture close proximity inputs."); 28 llInstantMessage(owner," Type: '/2 !hear' to disable capture."); 29 llInstantMessage(owner, " Type: '/2 IM!' to enable IM feedback."); 30 llSetTimerEvent(g_maxtimeout); 31 32 }//state entry 33 34 listen( integer channel, string name, key id, string message ){ 35 string owner = llGetOwner(); 36 if (channel == command_channel){ 37 list messageContent = llParseString2List(message, [" "], []); 38 integer len = llGetListLength(messageContent); 39 message = llList2String(messageContent,0); 40 if(message == "!hear"){ 41 g_email = FALSE; 42 llInstantMessage(owner, g_emailStateOff); 43 }//disable email 44 if(message == "hear!"){ 45 if (len < 2){ 46 llInstantMessage(owner, "incomplete message: I need an email address too"); 47 } else { 48 g_email = TRUE; 49 g_Mail_Addr = llList2String(messageContent,1); 50 llSetTimerEvent(g_maxtimeout); 51 llInstantMessage(owner, g_emailStateOn+" sending to "+g_Mail_Addr); 52 } 53 }//enable email 54 if(message == "!IM"){ //default mode 55 g_IM = FALSE; 56 llInstantMessage(owner, g_IMStateOff); 57 }//disable IM 58 if(message == "IM!"){ 59 g_IM = TRUE; 60 llInstantMessage(owner, g_IMStateOn); 61 }//enable IM 62 } 63 if(g_email){ 64 if(g_IM){ 65 //send IM to owner of chat channel relay if on 66 llInstantMessage(owner, message); 67 } 68 //send email to owner of chat channel relay 69 llEmail(g_Mail_Addr, "SL Listening", message); 70 }//end if(g_email) 71 }// end listen 72 73 timer(){ // on expire of timer, discontinue 74 g_email = FALSE; 75 g_IM = FALSE; 76 } 77 78 }//default